Bug 1275942 - Add option to wait for debugger on startup. r=jdescottes draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Thu, 02 Mar 2017 14:50:30 -0600
changeset 494295 ec2546eab87ff266420bed171056d67003ff2169
parent 492168 c54c652af04c5a918610bc3d419250177a68b9f7
child 548077 7ef761bf293a41db2946f485a13f0bd11e308656
push id48007
push userbmo:jryans@gmail.com
push dateTue, 07 Mar 2017 00:44:27 +0000
reviewersjdescottes
bugs1275942
milestone54.0a1
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
devtools/client/devtools-startup.js
devtools/server/actors/chrome.js
devtools/server/actors/script.js
--- 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) }