Bug 1275942 - Add option to wait for debugger on startup. r=jdescottes
Adds a `--wait-for-jsdebugger` CLI option which will spin the event loop until
the debugger connects to allow debugging _some_ startup code paths, such as the
beginning of the `browser.js` file.
Startup code paths that run before the DevTools CLI handler will be missed for
now. This can be improved in the future if we move this to an earlier phase of
app startup.
MozReview-Commit-ID: J1A8lWLETGY
--- a/devtools/client/devtools-startup.js
+++ b/devtools/client/devtools-startup.js
@@ -8,17 +8,17 @@
* core modules like 'devtools-browser.js' that hooks the browser windows
* and ensure setting up tools.
*
* Be careful to lazy load dependencies as much as possible.
**/
"use strict";
-const { interfaces: Ci, utils: Cu } = Components;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const kDebuggerPrefs = [
"devtools.debugger.remote-enabled",
"devtools.chrome.enabled"
];
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
function DevToolsStartup() {}
@@ -119,19 +119,38 @@ DevToolsStartup.prototype = {
}
return remoteDebuggingEnabled;
},
handleDebuggerFlag: function (cmdLine) {
if (!this._isRemoteDebuggingEnabled()) {
return;
}
+
+ let devtoolsThreadResumed = false;
+ let pauseOnStartup = cmdLine.handleFlag("wait-for-jsdebugger", false);
+ if (pauseOnStartup) {
+ let observe = function (subject, topic, data) {
+ devtoolsThreadResumed = true;
+ Services.obs.removeObserver(observe, "devtools-thread-resumed");
+ };
+ Services.obs.addObserver(observe, "devtools-thread-resumed", false);
+ }
+
const { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
BrowserToolboxProcess.init();
+ if (pauseOnStartup) {
+ // Spin the event loop until the debugger connects.
+ let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
+ while (!devtoolsThreadResumed) {
+ thread.processNextEvent(true);
+ }
+ }
+
if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
cmdLine.preventDefault = true;
}
},
/**
* Handle the --start-debugger-server command line flag. The options are:
* --start-debugger-server
@@ -198,16 +217,19 @@ DevToolsStartup.prototype = {
if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
cmdLine.preventDefault = true;
}
},
/* eslint-disable max-len */
helpInfo: " --jsconsole Open the Browser Console.\n" +
" --jsdebugger Open the Browser Toolbox.\n" +
+ " --wait-for-jsdebugger Spin event loop until JS debugger connects.\n" +
+ " Enables debugging (some) application startup code paths.\n" +
+ " Only has an effect when `--jsdebugger` is also supplied.\n" +
" --devtools Open DevTools on initial load.\n" +
" --start-debugger-server [ws:][ <port> | <path> ] Start the debugger server on\n" +
" a TCP port or Unix domain socket path. Defaults to TCP port\n" +
" 6000. Use WebSocket protocol if ws: prefix is specified.\n",
/* eslint-disable max-len */
classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
--- a/devtools/server/actors/chrome.js
+++ b/devtools/server/actors/chrome.js
@@ -45,17 +45,28 @@ function ChromeActor(connection) {
// Defines the default docshell selected for the tab actor
let window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
// Default to any available top level window if there is no expected window
// (for example when we open firefox with -webide argument)
if (!window) {
window = Services.wm.getMostRecentWindow(null);
}
- // On xpcshell, there is no window/docshell
+
+ // We really want _some_ window at least, so fallback to the hidden window if
+ // there's nothing else (such as during early startup).
+ if (!window) {
+ try {
+ window = Services.appShell.hiddenDOMWindow;
+ } catch (e) {
+ // On XPCShell, the above line will throw.
+ }
+ }
+
+ // On XPCShell, there is no window/docshell
let docShell = window ? window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
: null;
Object.defineProperty(this, "docShell", {
value: docShell,
configurable: true
});
}
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -1015,18 +1015,18 @@ const ThreadActor = ActorClassWithSpec(t
this._options.pauseOnExceptions = aRequest.pauseOnExceptions;
this._options.ignoreCaughtExceptions = aRequest.ignoreCaughtExceptions;
this.maybePauseOnExceptions();
this._maybeListenToEvents(aRequest);
}
let packet = this._resumed();
this._popThreadPause();
- // Tell anyone who cares of the resume (as of now, that's the xpcshell
- // harness)
+ // Tell anyone who cares of the resume (as of now, that's the xpcshell harness and
+ // devtools-startup.js when handling the --wait-for-jsdebugger flag)
if (Services.obs) {
Services.obs.notifyObservers(this, "devtools-thread-resumed", null);
}
return packet;
}, error => {
return error instanceof Error
? { error: "unknownError",
message: DevToolsUtils.safeErrorString(error) }