Bug 1774937 - [devtools] Allow to debug slow script without having devtools opened beforehand. r=jdescottes

The test doesn't really cover this fix as mochitest head files will force loading gDevTools...
To properly cover that we would have to have this test in another file and folder without any head.
This will make it hard to reuse Debugger test helpers. Let's assume we cover the modified feature
while not covering the very precise regression I fix here.

Differential Revision: https://phabricator.services.mozilla.com/D150155
This commit is contained in:
Alexandre Poirot 2022-06-30 17:18:55 +00:00
parent d3f1f1cf11
commit 1ae58de96b
5 changed files with 139 additions and 82 deletions

View File

@ -39,3 +39,53 @@ add_task(async function openDebuggerFirst() {
await dbg.toolbox.closeToolbox();
await removeTab(gBrowser.selectedTab);
});
add_task(async function openDebuggerFromDialog() {
const tab = await addTab(EXAMPLE_URL + "doc-slow-script.html");
const alert = BrowserTestUtils.waitForGlobalNotificationBar(
window,
"process-hang"
);
// /!\ Hack this attribute in order to force showing the "debug script" button
// on all channels. Otherwise it is only displayed in dev edition.
tab.linkedBrowser.browsingContext.watchedByDevTools = true;
info("Execute an infinite loop");
// Note that spawn will return a promise that may be rejected because of the infinite loop
// And mochitest may consider this as an error. So ignore any rejection.
SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
content.wrappedJSObject.infiniteLoop();
}).catch(e => {});
info("Wait for the slow script warning");
const notification = await alert;
info("Click on the debug script button");
const buttons = notification.buttonContainer.getElementsByTagName("button");
// The first button is "stop", the second is "debug script"
buttons[1].click();
info("Wait for the toolbox to appear and have the debugger initialized");
await waitFor(async () => {
const tb = await gDevTools.getToolboxForTab(gBrowser.selectedTab);
if (tb) {
await tb.getPanelWhenReady("jsdebugger");
return true;
}
return false;
});
const toolbox = await gDevTools.getToolboxForTab(gBrowser.selectedTab);
ok(toolbox, "Got a toolbox");
const dbg = createDebuggerContext(toolbox);
info("Waiting for the debugger to be paused");
await waitForPaused(dbg);
const source = findSource(dbg, "doc-slow-script.html");
assertPausedAtSourceAndLine(dbg, source.id, 14);
info("Close toolbox and tab");
await dbg.toolbox.closeToolbox();
await removeTab(gBrowser.selectedTab);
});

View File

@ -12,7 +12,6 @@
* browser window is ready (i.e. fired browser-delayed-startup-finished event)
**/
const { Cc, Ci } = require("chrome");
const Services = require("Services");
const { gDevTools } = require("devtools/client/framework/devtools");
const {
@ -465,83 +464,6 @@ var gDevToolsBrowser = (exports.gDevToolsBrowser = {
}
},
/**
* Hook the JS debugger tool to the "Debug Script" button of the slow script
* dialog.
*/
setSlowScriptDebugHandler() {
const debugService = Cc["@mozilla.org/dom/slow-script-debug;1"].getService(
Ci.nsISlowScriptDebug
);
async function slowScriptDebugHandler(tab, callback) {
const toolbox = await gDevTools.showToolboxForTab(tab, {
toolId: "jsdebugger",
});
const threadFront = toolbox.threadFront;
// Break in place, which means resuming the debuggee thread and pausing
// right before the next step happens.
switch (threadFront.state) {
case "paused":
// When the debugger is already paused.
threadFront.resumeThenPause();
break;
case "attached":
// When the debugger is already open.
const onPaused = threadFront.once("paused");
threadFront.interrupt();
await onPaused;
threadFront.resumeThenPause();
break;
case "resuming":
// The debugger is newly opened.
const onResumed = threadFront.once("resumed");
await threadFront.interrupt();
await onResumed;
threadFront.resumeThenPause();
break;
default:
throw Error(
"invalid thread front state in slow script debug handler: " +
threadFront.state
);
}
callback();
}
debugService.activationHandler = function(window) {
const chromeWindow = window.browsingContext.topChromeWindow;
let setupFinished = false;
slowScriptDebugHandler(chromeWindow.gBrowser.selectedTab, () => {
setupFinished = true;
});
// Don't return from the interrupt handler until the debugger is brought
// up; no reason to continue executing the slow script.
const utils = window.windowUtils;
utils.enterModalState();
Services.tm.spinEventLoopUntil(
"devtools-browser.js:debugService.activationHandler",
() => {
return setupFinished;
}
);
utils.leaveModalState();
};
debugService.remoteActivationHandler = function(browser, callback) {
const chromeWindow = browser.ownerDocument.defaultView;
const tab = chromeWindow.gBrowser.getTabForBrowser(browser);
chromeWindow.gBrowser.selected = tab;
slowScriptDebugHandler(tab, function() {
callback.finishDebuggerStartup();
}).catch(console.error);
};
},
/**
* Add the menuitem for a tool to all open browser windows.
*
@ -771,8 +693,6 @@ gDevTools.on("tool-registered", function(toolId) {
}
});
gDevToolsBrowser.setSlowScriptDebugHandler();
gDevTools.on("tool-unregistered", function(toolId) {
gDevToolsBrowser._removeToolFromWindows(toolId);
});

View File

@ -479,6 +479,7 @@ DevToolsStartup.prototype = {
this.sendEntryPointTelemetry("CommandLine");
}
}
this.setSlowScriptDebugHandler();
},
/**
@ -1094,6 +1095,92 @@ DevToolsStartup.prototype = {
this.recorded = true;
},
/**
* Hook the debugger tool to the "Debug Script" button of the slow script dialog.
*/
setSlowScriptDebugHandler() {
const debugService = Cc["@mozilla.org/dom/slow-script-debug;1"].getService(
Ci.nsISlowScriptDebug
);
debugService.activationHandler = window => {
const chromeWindow = window.browsingContext.topChromeWindow;
let setupFinished = false;
this.slowScriptDebugHandler(chromeWindow.gBrowser.selectedTab).then(
() => {
setupFinished = true;
}
);
// Don't return from the interrupt handler until the debugger is brought
// up; no reason to continue executing the slow script.
const utils = window.windowUtils;
utils.enterModalState();
Services.tm.spinEventLoopUntil(
"devtools-browser.js:debugService.activationHandler",
() => {
return setupFinished;
}
);
utils.leaveModalState();
};
debugService.remoteActivationHandler = async (browser, callback) => {
try {
// Force selecting the freezing tab
const chromeWindow = browser.ownerGlobal;
const tab = chromeWindow.gBrowser.getTabForBrowser(browser);
chromeWindow.gBrowser.selectedTab = tab;
await this.slowScriptDebugHandler(tab);
} catch (e) {
console.error(e);
}
callback.finishDebuggerStartup();
};
},
/**
* Called by setSlowScriptDebugHandler, when a tab freeze because of a slow running script
*/
async slowScriptDebugHandler(tab) {
const require = this.initDevTools("SlowScript");
const { gDevTools } = require("devtools/client/framework/devtools");
const toolbox = await gDevTools.showToolboxForTab(tab, {
toolId: "jsdebugger",
});
const threadFront = toolbox.threadFront;
// Break in place, which means resuming the debuggee thread and pausing
// right before the next step happens.
switch (threadFront.state) {
case "paused":
// When the debugger is already paused.
threadFront.resumeThenPause();
break;
case "attached":
// When the debugger is already open.
const onPaused = threadFront.once("paused");
threadFront.interrupt();
await onPaused;
threadFront.resumeThenPause();
break;
case "resuming":
// The debugger is newly opened.
const onResumed = threadFront.once("resumed");
await threadFront.interrupt();
await onResumed;
threadFront.resumeThenPause();
break;
default:
throw Error(
"invalid thread front state in slow script debug handler: " +
threadFront.state
);
}
},
// Used by tests and the toolbox to register the same key shortcuts in toolboxes loaded
// in a window window.
get KeyShortcuts() {

View File

@ -1780,7 +1780,7 @@ devtools.main:
release_channel_collection: opt-out
expiry_version: never
extra_keys:
entrypoint: How was the toolbox opened? CommandLine, ContextMenu, HamburgerMenu, KeyShortcut, SessionRestore or SystemMenu
entrypoint: How was the toolbox opened? CommandLine, ContextMenu, HamburgerMenu, KeyShortcut, SessionRestore, SystemMenu or SlowScript
first_panel: The name of the first panel opened.
host: "Toolbox host (positioning): bottom, side, window or other."
splitconsole: Indicates whether the split console was open.

View File

@ -11385,7 +11385,7 @@
"alert_emails": ["dev-developer-tools@lists.mozilla.org", "apoirot@mozilla.com"],
"expires_in_version": "never",
"kind": "categorical",
"labels": ["KeyShortcut", "SystemMenu", "HamburgerMenu", "ContextMenu", "CommandLine", "SessionRestore"],
"labels": ["KeyShortcut", "SystemMenu", "HamburgerMenu", "ContextMenu", "CommandLine", "SessionRestore", "SlowScript"],
"releaseChannelCollection": "opt-out",
"description": "Records how the user is triggering Developer Tools startup."
},