Bug 690771 - Implement the debugger pause request (at the main loop); r=dcamp

This commit is contained in:
Panos Astithas 2012-02-10 09:46:10 +02:00
parent 105b2271c1
commit 7934cbf31d
12 changed files with 230 additions and 34 deletions

View File

@ -93,16 +93,16 @@ DebuggerView.Stackframes = {
let resume = document.getElementById("resume");
let status = document.getElementById("status");
// if we're paused, show a pause label and disable the resume button
// If we're paused, show a pause label and a resume label on the button.
if (aState === "paused") {
status.textContent = DebuggerView.getStr("pausedState");
resume.disabled = false;
resume.label = DebuggerView.getStr("resumeLabel");
} else if (aState === "attached") {
// if we're attached, do the opposite
// If we're attached, do the opposite.
status.textContent = DebuggerView.getStr("runningState");
resume.disabled = true;
resume.label = DebuggerView.getStr("pauseLabel");
} else {
// no valid state parameter
// No valid state parameter.
status.textContent = "";
}
},
@ -272,10 +272,14 @@ DebuggerView.Stackframes = {
},
/**
* Listener handling the resume button click event.
* Listener handling the pause/resume button click event.
*/
_onResumeButtonClick: function DVF__onResumeButtonClick() {
ThreadState.activeThread.resume();
if (ThreadState.activeThread.paused) {
ThreadState.activeThread.resume();
} else {
ThreadState.activeThread.interrupt();
}
},
/**

View File

@ -53,7 +53,7 @@
<div id="body" class="vbox flex">
<xul:toolbar id="dbg-toolbar">
<xul:button id="close">&debuggerUI.closeButton;</xul:button>
<xul:button id="resume">&debuggerUI.resumeButton;</xul:button>
<xul:button id="resume"/>
<xul:menulist id="scripts"/>
</xul:toolbar>
<div id="dbg-content" class="hbox flex">

View File

@ -69,6 +69,7 @@ _BROWSER_TEST_FILES = \
browser_dbg_stack-04.js \
browser_dbg_location-changes.js \
browser_dbg_script-switching.js \
browser_dbg_pause-resume.js \
head.js \
$(NULL)

View File

@ -0,0 +1,74 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test() {
debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.debuggerWindow;
testPause();
});
}
function testPause() {
is(gDebugger.StackFrames.activeThread.paused, false,
"Should be running after debug_tab_pane.");
let button = gDebugger.document.getElementById("resume");
is(button.label, gDebugger.DebuggerView.getStr("pauseLabel"),
"Button label should be pause when running.");
gPane.activeThread.addOneTimeListener("paused", function() {
Services.tm.currentThread.dispatch({ run: function() {
let frames = gDebugger.DebuggerView.Stackframes._frames;
let childNodes = frames.childNodes;
is(gDebugger.StackFrames.activeThread.paused, true,
"Should be paused after an interrupt request.");
is(button.label, gDebugger.DebuggerView.getStr("resumeLabel"),
"Button label should be resume when paused.");
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames when paused in the main loop.");
testResume();
}}, 0);
});
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.getElementById("resume"),
gDebugger);
}
function testResume() {
gPane.activeThread.addOneTimeListener("resumed", function() {
Services.tm.currentThread.dispatch({ run: function() {
is(gDebugger.StackFrames.activeThread.paused, false,
"Should be paused after an interrupt request.");
let button = gDebugger.document.getElementById("resume");
is(button.label, gDebugger.DebuggerView.getStr("pauseLabel"),
"Button label should be pause when running.");
removeTab(gTab);
finish();
}}, 0);
});
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.getElementById("resume"),
gDebugger);
}

View File

@ -55,8 +55,10 @@ function testFrameParameters()
localNodes[1].expand();
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count = 0;
let intervalID = content.setInterval(function(){
let intervalID = window.setInterval(function(){
if (++count > 50) {
ok(false, "Timed out while polling for the properties.");
resumeAndFinish();
@ -64,7 +66,7 @@ function testFrameParameters()
if (!localNodes[0].fetched || !localNodes[1].fetched) {
return;
}
content.clearInterval(intervalID);
window.clearInterval(intervalID);
is(localNodes[0].querySelector(".property > .title > .key")
.textContent, "__proto__ ",
"Should have the right property name for __proto__.");

View File

@ -19,12 +19,6 @@
- button that closes the debugger UI. -->
<!ENTITY debuggerUI.closeButton "Close">
<!-- LOCALIZATION NOTE (debuggerUI.resumeButton): This is the label for the
- button that resumes the debugger, after it has reached a paused state. In
- a paused state the debugger can be used to inspect stack frames, local,
- variables etc. -->
<!ENTITY debuggerUI.resumeButton "Resume">
<!-- LOCALIZATION NOTE (debuggerUI.stackTitle): This is the label for the
- widget that displays the call stack frames in the debugger. -->
<!ENTITY debuggerUI.stackTitle "Call stack">

View File

@ -6,6 +6,14 @@
# A good criteria is the language in which you'd find the best
# documentation on web development on the web.
# LOCALIZATION NOTE (pauseLabel): The label that is displayed on the pause
# button when the debugger is in a running state.
pauseLabel=Pause
# LOCALIZATION NOTE (resumeLabel): The label that is displayed on the pause
# button when the debugger is in a paused state.
resumeLabel=Resume
# LOCALIZATION NOTE (pausedState): The label that is displayed when the
# debugger is in a paused state.
pausedState=Paused

View File

@ -204,6 +204,7 @@ const DebugProtocolTypes = {
"delete": "delete",
"detach": "detach",
"frames": "frames",
"interrupt": "interrupt",
"listTabs": "listTabs",
"nameAndParameters": "nameAndParameters",
"ownPropertyNames": "ownPropertyNames",
@ -509,12 +510,13 @@ function ThreadClient(aClient, aActor) {
ThreadClient.prototype = {
_state: "paused",
get state() { return this._state; },
get paused() { return this._state === "paused"; },
_actor: null,
get actor() { return this._actor; },
_assertPaused: function TC_assertPaused(aCommand) {
if (this._state !== "paused") {
if (!this.paused) {
throw aCommand + " command sent while not paused.";
}
},
@ -545,6 +547,21 @@ ThreadClient.prototype = {
});
},
/**
* Interrupt a running thread.
*
* @param function aOnResponse
* Called with the response packet.
*/
interrupt: function TC_interrupt(aOnResponse) {
let packet = { to: this._actor, type: DebugProtocolTypes.interrupt };
this._client.request(packet, function(aResponse) {
if (aOnResponse) {
aOnResponse(aResponse);
}
});
},
/**
* Send a clientEvaluate packet to the debuggee. Response
* will be a resume packet.
@ -696,8 +713,7 @@ ThreadClient.prototype = {
* true if there are more stack frames available on the server.
*/
get moreFrames() {
return this.state === "paused"
&& (!this._frameCache || this._frameCache.length == 0
return this.paused && (!this._frameCache || this._frameCache.length == 0
|| !this._frameCache[this._frameCache.length - 1].oldest);
},

View File

@ -365,25 +365,25 @@ BrowserTabActor.prototype = {
},
/**
* Suppresses content-initiated events. Called right before entering the
* nested event loop.
* Prepare to enter a nested event loop by disabling debuggee events.
*/
preNest: function BTA_preNest() {
this.browser.contentWindow
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.suppressEventHandling(true);
let windowUtils = this.browser.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
},
/**
* Re-enables content-initiated events. Called right after exiting the
* nested event loop.
* Prepare to exit a nested event loop by enabling debuggee events.
*/
postNest: function BTA_postNest(aNestData) {
this.browser.contentWindow
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.suppressEventHandling(false);
let windowUtils = this.browser.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
},
/**

View File

@ -176,7 +176,7 @@ ThreadActor.prototype = {
// now.
return null;
} catch(e) {
dumpn(e);
Cu.reportError(e);
return { error: "notAttached", message: e.toString() };
}
},
@ -346,6 +346,46 @@ ThreadActor.prototype = {
return packet;
},
/**
* Handle a protocol request to pause the debuggee.
*/
onInterrupt: function TA_onScripts(aRequest) {
if (this.state == "exited") {
return { type: "exited" };
} else if (this.state == "paused") {
// TODO: return the actual reason for the existing pause.
return { type: "paused", why: { type: "alreadyPaused" } };
} else if (this.state != "running") {
return { error: "wrongState",
message: "Received interrupt request in " + this.state +
" state." };
}
try {
// Put ourselves in the paused state.
let packet = this._paused();
if (!packet) {
return { error: "notInterrupted" };
}
packet.why = { type: "interrupted" };
// Send the response to the interrupt request now (rather than
// returning it), because we're going to start a nested event loop
// here.
this.conn.send(packet);
// Start a nested event loop.
this._nest();
// We already sent a response to this request, don't send one
// now.
return null;
} catch(e) {
Cu.reportError(e);
return { error: "notInterrupted", message: e.toString() };
}
},
/**
* Return the Debug.Frame for a frame mentioned by the protocol.
*/
@ -626,7 +666,8 @@ ThreadActor.prototype = {
this.conn.send(packet);
return this._nest();
} catch(e) {
dumpn("Got an exception during onDebuggerStatement: " + e + ': ' + e.stack);
Cu.reportError("Got an exception during onDebuggerStatement: " + e +
": " + e.stack);
return undefined;
}
},
@ -670,6 +711,7 @@ ThreadActor.prototype.requestTypes = {
"resume": ThreadActor.prototype.onResume,
"clientEvaluate": ThreadActor.prototype.onClientEvaluate,
"frames": ThreadActor.prototype.onFrames,
"interrupt": ThreadActor.prototype.onInterrupt,
"releaseMany": ThreadActor.prototype.onReleaseMany,
"setBreakpoint": ThreadActor.prototype.onSetBreakpoint,
"scripts": ThreadActor.prototype.onScripts
@ -1079,7 +1121,7 @@ BreakpointActor.prototype = {
this.conn.send(packet);
return this.threadActor._nest();
} catch(e) {
dumpn("Got an exception during hit: " + e + ': ' + e.stack);
Cu.reportError("Got an exception during hit: " + e + ': ' + e.stack);
return undefined;
}
},

View File

@ -0,0 +1,54 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var gClient;
var gDebuggee;
function run_test()
{
DebuggerServer.addActors("resource://test/testactors.js");
DebuggerServer.init();
gDebuggee = testGlobal("test-1");
DebuggerServer.addTestGlobal(gDebuggee);
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
gClient.connect(function(aType, aTraits) {
getTestGlobalContext(gClient, "test-1", function(aContext) {
test_attach(aContext);
});
});
do_test_pending();
}
function test_attach(aContext)
{
gClient.attachThread(aContext.actor, function(aResponse, aThreadClient) {
do_check_eq(aThreadClient.paused, true);
aThreadClient.resume(function() {
test_interrupt();
});
});
}
function test_interrupt()
{
do_check_eq(gClient.activeThread.paused, false);
gClient.activeThread.interrupt(function(aResponse) {
do_check_eq(gClient.activeThread.paused, true);
gClient.activeThread.resume(function() {
do_check_eq(gClient.activeThread.paused, false);
cleanup();
});
});
}
function cleanup()
{
gClient.addListener("closed", function(aEvent) {
do_test_finished();
});
gClient.close();
}

View File

@ -39,3 +39,4 @@ tail =
[test_objectgrips-02.js]
[test_objectgrips-03.js]
[test_objectgrips-04.js]
[test_interrupt.js]