mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-04 02:57:38 +00:00
Bug 690771 - Implement the debugger pause request (at the main loop); r=dcamp
This commit is contained in:
parent
105b2271c1
commit
7934cbf31d
@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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">
|
||||
|
@ -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)
|
||||
|
||||
|
74
browser/devtools/debugger/test/browser_dbg_pause-resume.js
Normal file
74
browser/devtools/debugger/test/browser_dbg_pause-resume.js
Normal 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);
|
||||
}
|
@ -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__.");
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
54
toolkit/devtools/debugger/tests/unit/test_interrupt.js
Normal file
54
toolkit/devtools/debugger/tests/unit/test_interrupt.js
Normal 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();
|
||||
}
|
||||
|
@ -39,3 +39,4 @@ tail =
|
||||
[test_objectgrips-02.js]
|
||||
[test_objectgrips-03.js]
|
||||
[test_objectgrips-04.js]
|
||||
[test_interrupt.js]
|
||||
|
Loading…
Reference in New Issue
Block a user