Bug 717749 - Part 2: Hook up the debugger to the slow script debug service. (r=past)

This commit is contained in:
Shu-yu Guo 2014-05-20 18:27:25 -07:00
parent 287faf4d54
commit 87dc6d7e59
4 changed files with 119 additions and 5 deletions

View File

@ -462,6 +462,13 @@ ThreadState.prototype = {
* Update the UI after a thread state change.
*/
_update: function(aEvent) {
// Ignore "interrupted" events, which are generated by the slow script
// dialog and internal events such as setting breakpoints, to avoid UI
// flicker.
if (aEvent == "interrupted") {
return;
}
DebuggerView.Toolbar.toggleResumeButtonState(this.activeThread.state);
if (gTarget && (aEvent == "paused" || aEvent == "resumed")) {
@ -568,6 +575,11 @@ StackFrames.prototype = {
this._currentReturnedValue = aPacket.why.frameFinished.return;
}
break;
// If paused by an explicit interrupt, which are generated by the slow
// script dialog and internal events such as setting breakpoints, ignore
// the event to avoid UI flicker.
case "interrupted":
return;
}
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);

View File

@ -580,6 +580,79 @@ let gDevToolsBrowser = {
mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset);
},
/**
* Hook the JS debugger tool to the "Debug Script" button of the slow script
* dialog.
*/
setSlowScriptDebugHandler: function DT_setSlowScriptDebugHandler() {
let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
.getService(Ci.nsISlowScriptDebug);
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
debugService.activationHandler = function(aWindow) {
let chromeWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
let target = devtools.TargetFactory.forTab(chromeWindow.gBrowser.selectedTab);
let setupFinished = false;
gDevTools.showToolbox(target, "jsdebugger").then(toolbox => {
let threadClient = toolbox.getCurrentPanel().panelWin.gThreadClient;
// Break in place, which means resuming the debuggee thread and pausing
// right before the next step happens.
switch (threadClient.state) {
case "paused":
// When the debugger is already paused.
threadClient.breakOnNext();
setupFinished = true;
break;
case "attached":
// When the debugger is already open.
threadClient.interrupt(() => {
threadClient.breakOnNext();
setupFinished = true;
});
break;
case "resuming":
// The debugger is newly opened.
threadClient.addOneTimeListener("resumed", () => {
threadClient.interrupt(() => {
threadClient.breakOnNext();
setupFinished = true;
});
});
break;
default:
throw Error("invalid thread client state in slow script debug handler: " +
threadClient.state);
}
});
// Don't return from the interrupt handler until the debugger is brought
// up; no reason to continue executing the slow script.
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
utils.enterModalState();
while (!setupFinished) {
tm.currentThread.processNextEvent(true);
}
utils.leaveModalState();
};
},
/**
* Unset the slow script debug handler.
*/
unsetSlowScriptDebugHandler: function DT_unsetSlowScriptDebugHandler() {
let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
.getService(Ci.nsISlowScriptDebug);
debugService.activationHandler = undefined;
},
/**
* Detect the presence of a Firebug.
@ -669,6 +742,10 @@ let gDevToolsBrowser = {
}
}
}
if (toolDefinition.id === "jsdebugger") {
gDevToolsBrowser.setSlowScriptDebugHandler();
}
},
/**
@ -844,6 +921,10 @@ let gDevToolsBrowser = {
for (let win of gDevToolsBrowser._trackedBrowserWindows) {
gDevToolsBrowser._removeToolFromMenu(toolId, win.document);
}
if (toolId === "jsdebugger") {
gDevToolsBrowser.unsetSlowScriptDebugHandler();
}
},
/**

View File

@ -1493,6 +1493,16 @@ ThreadClient.prototype = {
this._doResume(null, aOnResponse);
},
/**
* Resume then pause without stepping.
*
* @param function aOnResponse
* Called with the response packet.
*/
breakOnNext: function (aOnResponse) {
this._doResume({ type: "break" }, aOnResponse);
},
/**
* Step over a function call.
*

View File

@ -951,7 +951,15 @@ ThreadActor.prototype = {
},
_makeOnStep: function ({ thread, pauseAndRespond, startFrame,
startLocation }) {
startLocation, steppingType }) {
// Breaking in place: we should always pause.
if (steppingType === "break") {
return function () {
return pauseAndRespond(this);
};
}
// Otherwise take what a "step" means into consideration.
return function () {
// onStep is called with 'this' set to the current frame.
@ -996,7 +1004,7 @@ ThreadActor.prototype = {
/**
* Define the JS hook functions for stepping.
*/
_makeSteppingHooks: function (aStartLocation) {
_makeSteppingHooks: function (aStartLocation, steppingType) {
// Bind these methods and state because some of the hooks are called
// with 'this' set to the current frame. Rather than repeating the
// binding in each _makeOnX method, just do it once here and pass it
@ -1008,7 +1016,8 @@ ThreadActor.prototype = {
createValueGrip: this.createValueGrip.bind(this),
thread: this,
startFrame: this.youngestFrame,
startLocation: aStartLocation
startLocation: aStartLocation,
steppingType: steppingType
};
return {
@ -1029,7 +1038,7 @@ ThreadActor.prototype = {
*/
_handleResumeLimit: function (aRequest) {
let steppingType = aRequest.resumeLimit.type;
if (["step", "next", "finish"].indexOf(steppingType) == -1) {
if (["break", "step", "next", "finish"].indexOf(steppingType) == -1) {
return reject({ error: "badParameterType",
message: "Unknown resumeLimit type" });
}
@ -1037,7 +1046,8 @@ ThreadActor.prototype = {
const generatedLocation = getFrameLocation(this.youngestFrame);
return this.sources.getOriginalLocation(generatedLocation)
.then(originalLocation => {
const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation);
const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation,
steppingType);
// Make sure there is still a frame on the stack if we are to continue
// stepping.
@ -1047,6 +1057,7 @@ ThreadActor.prototype = {
case "step":
this.dbg.onEnterFrame = onEnterFrame;
// Fall through.
case "break":
case "next":
if (stepFrame.script) {
stepFrame.onStep = onStep;