Bug 1395535 - initDevTools is async for scenarios where devtools addon did not start;r=ochameau draft
authorJulian Descottes <jdescottes@mozilla.com>
Tue, 05 Sep 2017 14:44:58 +0200
changeset 659092 c1fd6ed947089b3c6f86807457e526bbd9a24794
parent 659091 aff9ca5b49f4b965b3473e6f6e873cca789243e8
child 659093 b81455f6e068f8e825df04743ad3b18e6c57908b
push id78008
push userjdescottes@mozilla.com
push dateTue, 05 Sep 2017 13:43:47 +0000
reviewersochameau
bugs1395535
milestone57.0a1
Bug 1395535 - initDevTools is async for scenarios where devtools addon did not start;r=ochameau Most code paths using initDevTools don't use the return value and therefore don't need to wait on the promise. Code paths that can accommodate for async initDevTools can then be migrated in order to still work if DevTools addon was only installed but not started. Some code paths are probably challenging to migrate: - command line arguments are processed even before the addon is installed via sideload - system menu needs to be sync MozReview-Commit-ID: lZIq0vr3eb
devtools/shim/devtools-startup.js
--- a/devtools/shim/devtools-startup.js
+++ b/devtools/shim/devtools-startup.js
@@ -183,17 +183,17 @@ DevToolsStartup.prototype = {
    */
   developerToggleCreated: false,
 
   /**
    * Flag that checks if the DevTools addon has been installed and started.
    */
   isDevToolsAvailable: false,
 
-  handle: function (cmdLine) {
+  handle(cmdLine) {
     // Update isDevToolsAvailable by checking if resource://devtools is registered.
     this.isDevToolsAvailable = Services.io.getProtocolHandler("resource")
                                .QueryInterface(Ci.nsIResProtocolHandler)
                                .hasSubstitution("devtools");
 
     let consoleFlag = cmdLine.handleFlag("jsconsole", false);
     let debuggerFlag = cmdLine.handleFlag("jsdebugger", false);
     let devtoolsFlag = cmdLine.handleFlag("devtools", false);
@@ -289,19 +289,19 @@ DevToolsStartup.prototype = {
       id: id,
       type: "view",
       viewId: "PanelUI-developer",
       shortcutId: "key_toggleToolbox",
       tooltiptext: "developer-button.tooltiptext2",
       defaultArea: AppConstants.MOZ_DEV_EDITION ?
                      CustomizableUI.AREA_NAVBAR :
                      CustomizableUI.AREA_PANEL,
-      onViewShowing: (event) => {
+      onViewShowing: async function (event) {
         // Ensure creating the menuitems in the system menu before trying to copy them.
-        this.initDevTools("HamburgerMenu");
+        await this.initDevTools("HamburgerMenu");
 
         // Populate the subview with whatever menuitems are in the developer
         // menu. We skip menu elements, because the menu panel has no way
         // of dealing with those right now.
         let doc = event.target.ownerDocument;
 
         let menu = doc.getElementById("menuWebDeveloperPopup");
 
@@ -311,17 +311,17 @@ DevToolsStartup.prototype = {
         itemsToDisplay.push(doc.getElementById("goOfflineMenuitem"));
 
         let developerItems = doc.getElementById("PanelUI-developerItems");
         // Import private helpers from CustomizableWidgets
         let { clearSubview, fillSubviewFromMenuItems } =
           Cu.import("resource:///modules/CustomizableWidgets.jsm", {});
         clearSubview(developerItems);
         fillSubviewFromMenuItems(itemsToDisplay, developerItems);
-      },
+      }.bind(this),
       onInit(anchor) {
         // Since onBeforeCreated already bails out when initialized, we can call
         // it right away.
         this.onBeforeCreated(anchor.ownerDocument);
       },
       onBeforeCreated(doc) {
         // Bug 1223127, CUI should make this easier to do.
         if (doc.getElementById("PanelUI-developerItems")) {
@@ -363,18 +363,18 @@ DevToolsStartup.prototype = {
 
     // Appending a <key> element is not always enough. The <keyset> needs
     // to be detached and reattached to make sure the <key> is taken into
     // account (see bug 832984).
     let mainKeyset = doc.getElementById("mainKeyset");
     mainKeyset.parentNode.insertBefore(keyset, mainKeyset);
   },
 
-  onKey(window, key) {
-    let require = this.initDevTools("KeyShortcut");
+  async onKey(window, key) {
+    let require = await this.initDevTools("KeyShortcut");
     let { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
     gDevToolsBrowser.onKeyShortcut(window, key);
   },
 
   // Create a <xul:key> DOM Element
   createKey(doc, { id, toolId, shortcut, modifiers: mod }, oncommand) {
     let k = doc.createElement("key");
     k.id = "key_" + (id || toolId);
@@ -391,32 +391,39 @@ DevToolsStartup.prototype = {
 
     // Bug 371900: command event is fired only if "oncommand" attribute is set.
     k.setAttribute("oncommand", ";");
     k.addEventListener("command", oncommand);
 
     return k;
   },
 
-  initDevTools: function (reason) {
+  initDevTools(reason) {
     if (!this.isDevToolsAvailable) {
       // Under very specific circumstances, DevTools addon might only be installed but not
       // started. In this case we attempt to reload the addon.
       const { AddonManager } = Components.utils.import("resource://gre/modules/AddonManager.jsm", {});
-      AddonManager.getAddonByID("devtools@mozilla.org", (addon) => {
-        addon.enabled = true;
-        addon.active = true;
-        addon.reload();
 
-        // DevTools should now be ready to be used.
-        this.isDevToolsAvailable = true;
+      return new Promise((resolve, reject) => {
+        AddonManager.getAddonByID("devtools@mozilla.org", (addon) => {
+          if (!addon) {
+            reject("Firefox DevTools addon had to be reloaded , please try again." +
+                   "Force addon scan via `--setpref extensions.startupScanScopes=15`");
+          }
+          addon.enabled = true;
+          addon.active = true;
+          addon.reload();
+
+          // DevTools should now be ready to be used.
+          this.isDevToolsAvailable = true;
+
+          // Call initDevTools again, which should now resolve correctly.
+          resolve(this.initDevTools(reason));
+        });
       });
-
-      throw new Error("Firefox DevTools addon had to be reloaded , please try again." +
-                      "Force addon scan via `--setpref extensions.startupScanScopes=15`");
     }
 
     if (reason && !this.recorded) {
       // Only save the first call for each firefox run as next call
       // won't necessarely start the tool. For example key shortcuts may
       // only change the currently selected tool.
       try {
         Services.telemetry.getHistogramById("DEVTOOLS_ENTRY_POINT")
@@ -429,41 +436,41 @@ DevToolsStartup.prototype = {
     if (!this.initialized) {
       Services.prefs.setBoolPref("devtools.enabled", true);
     }
     this.initialized = true;
     let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
     // Ensure loading main devtools module that hooks up into browser UI
     // and initialize all devtools machinery.
     require("devtools/client/framework/devtools-browser");
-    return require;
+    return Promise.resolve(require);
   },
 
-  handleConsoleFlag: function (cmdLine) {
+  async handleConsoleFlag(cmdLine) {
     let window = Services.wm.getMostRecentWindow("devtools:webconsole");
     if (!window) {
-      this.initDevTools("CommandLine");
+      await this.initDevTools("CommandLine");
 
       let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
       let { HUDService } = require("devtools/client/webconsole/hudservice");
       let { console } = Cu.import("resource://gre/modules/Console.jsm", {});
       HUDService.toggleBrowserConsole().catch(console.error);
     } else {
       // the Browser Console was already open
       window.focus();
     }
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
   // Open the toolbox on the selected tab once the browser starts up.
-  handleDevToolsFlag: function (window) {
-    const require = this.initDevTools("CommandLine");
+  async handleDevToolsFlag(window) {
+    const require = await this.initDevTools("CommandLine");
     const {gDevTools} = require("devtools/client/framework/devtools");
     const {TargetFactory} = require("devtools/client/framework/target");
     let target = TargetFactory.forTab(window.gBrowser.selectedTab);
     gDevTools.showToolbox(target);
   },
 
   _isRemoteDebuggingEnabled() {
     let remoteDebuggingEnabled = false;
@@ -483,17 +490,17 @@ DevToolsStartup.prototype = {
       console.error(new Error(errorMsg));
       // Dump as well, as we're doing this from a commandline, make sure people
       // don't miss it:
       dump(errorMsg + "\n");
     }
     return remoteDebuggingEnabled;
   },
 
-  handleDebuggerFlag: function (cmdLine) {
+  handleDebuggerFlag(cmdLine) {
     if (!this._isRemoteDebuggingEnabled()) {
       return;
     }
 
     let devtoolsThreadResumed = false;
     let pauseOnStartup = cmdLine.handleFlag("wait-for-jsdebugger", false);
     if (pauseOnStartup) {
       let observe = function (subject, topic, data) {
@@ -533,17 +540,17 @@ DevToolsStartup.prototype = {
    *   Start the server on a Unix domain socket.
    *
    * --start-debugger-server ws:6789
    *   Start the WebSocket server on port 6789.
    *
    * --start-debugger-server ws:
    *   Start the WebSocket server on the default port (taken from d.d.remote-port)
    */
-  handleDebuggerServerFlag: function (cmdLine, portOrPath) {
+  handleDebuggerServerFlag(cmdLine, portOrPath) {
     if (!this._isRemoteDebuggingEnabled()) {
       return;
     }
 
     let webSocket = false;
     let defaultPort = Services.prefs.getIntPref("devtools.debugger.remote-port");
     if (portOrPath === true) {
       // Default to pref values if no values given on command line
@@ -614,17 +621,17 @@ DevToolsStartup.prototype = {
 
 /**
  * Singleton object that represents the JSON View in-content tool.
  * It has the same lifetime as the browser.
  */
 const JsonView = {
   initialized: false,
 
-  initialize: function () {
+  initialize() {
     // Prevent loading the frame script multiple times if we call this more than once.
     if (this.initialized) {
       return;
     }
     this.initialized = true;
 
     // Load JSON converter module. This converter is responsible
     // for handling 'application/json' documents and converting
@@ -642,17 +649,17 @@ const JsonView = {
   },
 
   // Message handlers for events from child processes
 
   /**
    * Save JSON to a file needs to be implemented here
    * in the parent process.
    */
-  onSave: function (message) {
+  onSave(message) {
     let chrome = Services.wm.getMostRecentWindow("navigator:browser");
     let browser = chrome.gBrowser.selectedBrowser;
     if (message.data.url === null) {
       // Save original contents
       chrome.saveBrowser(browser, false, message.data.windowID);
     } else {
       // The following code emulates saveBrowser, but:
       // - Uses the given blob URL containing the custom contents to save.