Bug 1359855 - Automatically initialize DevTools when accessing DevToolsShim.gDevTools. r=jdescottes
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 24 Jul 2017 14:30:24 +0200
changeset 370599 1b4dea0fe0213d36833103764921046ec41bcca6
parent 370598 ff507dd06af9bce63188818602b624f5de3d1412
child 370600 e3fa073f5b946cb1d3470446278d222de599ec90
push id47092
push userapoirot@mozilla.com
push dateMon, 24 Jul 2017 17:34:13 +0000
treeherderautoland@1b4dea0fe021 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1359855
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1359855 - Automatically initialize DevTools when accessing DevToolsShim.gDevTools. r=jdescottes WebExtension tests expect DevTools to be automatically initialized, they access DevToolsShim.gDevTools.getTargetForTab in order to open DevTools. MozReview-Commit-ID: 3VQRlxgBkI0
devtools/shim/DevToolsShim.jsm
--- a/devtools/shim/DevToolsShim.jsm
+++ b/devtools/shim/DevToolsShim.jsm
@@ -30,60 +30,78 @@ function removeItem(array, callback) {
  * listening to events, register tools, themes. As soon as a DevTools addon is installed
  * the DevToolsShim will forward all the requests received until then to the real DevTools
  * instance.
  *
  * DevToolsShim.isInstalled() can also be used to know if DevTools are currently
  * installed.
  */
 this.DevToolsShim = {
-  gDevTools: null,
+  _gDevTools: null,
   listeners: [],
   tools: [],
   themes: [],
 
   /**
+   * Lazy getter for the `gDevTools` instance. Should only be called when users interacts
+   * with DevTools as it will force loading them.
+   *
+   * @return {DevTools} a devtools instance (from client/framework/devtools)
+   */
+  get gDevTools() {
+    if (!this.isInstalled()) {
+      throw new Error(`Trying to interact with DevTools, but they are not installed`);
+    }
+
+    if (!this.isInitialized()) {
+      this._initDevTools();
+    }
+
+    return this._gDevTools;
+  },
+
+  /**
    * Check if DevTools are currently installed (but not necessarily initialized).
    *
    * @return {Boolean} true if DevTools are installed.
    */
   isInstalled: function () {
     return Services.io.getProtocolHandler("resource")
              .QueryInterface(Ci.nsIResProtocolHandler)
              .hasSubstitution("devtools");
   },
 
   /**
    * Check if DevTools have already been initialized.
    *
    * @return {Boolean} true if DevTools are initialized.
    */
   isInitialized: function () {
-    return !!this.gDevTools;
+    return !!this._gDevTools;
   },
 
   /**
    * Register an instance of gDevTools. Should be called by DevTools during startup.
    *
    * @param {DevTools} a devtools instance (from client/framework/devtools)
    */
   register: function (gDevTools) {
-    this.gDevTools = gDevTools;
+    this._gDevTools = gDevTools;
     this._onDevToolsRegistered();
-    this.gDevTools.emit("devtools-registered");
+    this._gDevTools.emit("devtools-registered");
   },
 
   /**
    * Unregister the current instance of gDevTools. Should be called by DevTools during
    * shutdown.
    */
   unregister: function () {
     if (this.isInitialized()) {
-      this.gDevTools.emit("devtools-unregistered");
-      this.gDevTools = null;
+      this._gDevTools.emit("devtools-unregistered");
+      this._gDevTools = null;
     }
   },
 
   /**
    * The following methods can be called before DevTools are initialized:
    * - on
    * - off
    * - registerTool
@@ -97,110 +115,110 @@ this.DevToolsShim = {
 
   /**
    * This method is used by browser/components/extensions/ext-devtools.js for the events:
    * - toolbox-created
    * - toolbox-destroyed
    */
   on: function (event, listener) {
     if (this.isInitialized()) {
-      this.gDevTools.on(event, listener);
+      this._gDevTools.on(event, listener);
     } else {
       this.listeners.push([event, listener]);
     }
   },
 
   /**
    * This method is currently only used by devtools code, but is kept here for consistency
    * with on().
    */
   off: function (event, listener) {
     if (this.isInitialized()) {
-      this.gDevTools.off(event, listener);
+      this._gDevTools.off(event, listener);
     } else {
       removeItem(this.listeners, ([e, l]) => e === event && l === listener);
     }
   },
 
   /**
    * This method is only used by the addon-sdk and should be removed when Firefox 56 is
    * no longer supported.
    */
   registerTool: function (tool) {
     if (this.isInitialized()) {
-      this.gDevTools.registerTool(tool);
+      this._gDevTools.registerTool(tool);
     } else {
       this.tools.push(tool);
     }
   },
 
   /**
    * This method is only used by the addon-sdk and should be removed when Firefox 56 is
    * no longer supported.
    */
   unregisterTool: function (tool) {
     if (this.isInitialized()) {
-      this.gDevTools.unregisterTool(tool);
+      this._gDevTools.unregisterTool(tool);
     } else {
       removeItem(this.tools, t => t === tool);
     }
   },
 
   /**
    * This method is only used by the addon-sdk and should be removed when Firefox 56 is
    * no longer supported.
    */
   registerTheme: function (theme) {
     if (this.isInitialized()) {
-      this.gDevTools.registerTheme(theme);
+      this._gDevTools.registerTheme(theme);
     } else {
       this.themes.push(theme);
     }
   },
 
   /**
    * This method is only used by the addon-sdk and should be removed when Firefox 56 is
    * no longer supported.
    */
   unregisterTheme: function (theme) {
     if (this.isInitialized()) {
-      this.gDevTools.unregisterTheme(theme);
+      this._gDevTools.unregisterTheme(theme);
     } else {
       removeItem(this.themes, t => t === theme);
     }
   },
 
   /**
    * Called from SessionStore.jsm in mozilla-central when saving the current state.
    *
    * @return {Array} array of currently opened scratchpad windows. Empty array if devtools
    *         are not installed
    */
   getOpenedScratchpads: function () {
     if (!this.isInitialized()) {
       return [];
     }
 
-    return this.gDevTools.getOpenedScratchpads();
+    return this._gDevTools.getOpenedScratchpads();
   },
 
   /**
    * Called from SessionStore.jsm in mozilla-central when restoring a state that contained
    * opened scratchpad windows.
    */
   restoreScratchpadSession: function (scratchpads) {
     if (!this.isInstalled()) {
       return;
     }
 
     if (!this.isInitialized()) {
       this._initDevTools();
     }
 
-    this.gDevTools.restoreScratchpadSession(scratchpads);
+    this._gDevTools.restoreScratchpadSession(scratchpads);
   },
 
   /**
    * Called from nsContextMenu.js in mozilla-central when using the Inspect Element
    * context menu item.
    *
    * @param {XULTab} tab
    *        The browser tab on which inspect node was used.
@@ -211,40 +229,36 @@ this.DevToolsShim = {
    * @return {Promise} a promise that resolves when the node is selected in the inspector
    *         markup view or that resolves immediately if DevTools are not installed.
    */
   inspectNode: function (tab, selectors) {
     if (!this.isInstalled()) {
       return Promise.resolve();
     }
 
-    if (!this.isInitialized()) {
-      this._initDevTools();
-    }
-
     return this.gDevTools.inspectNode(tab, selectors);
   },
 
   _initDevTools: function () {
     let { loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
     loader.require("devtools/client/framework/devtools-browser");
   },
 
   _onDevToolsRegistered: function () {
     // Register all pending event listeners on the real gDevTools object.
     for (let [event, listener] of this.listeners) {
-      this.gDevTools.on(event, listener);
+      this._gDevTools.on(event, listener);
     }
 
     for (let tool of this.tools) {
-      this.gDevTools.registerTool(tool);
+      this._gDevTools.registerTool(tool);
     }
 
     for (let theme of this.themes) {
-      this.gDevTools.registerTheme(theme);
+      this._gDevTools.registerTheme(theme);
     }
 
     this.listeners = [];
     this.tools = [];
     this.themes = [];
   },
 };
 
@@ -272,19 +286,11 @@ let addonSdkMethods = [
  * therefore DevTools should always be available when they are called.
  */
 let webExtensionsMethods = [
   "getTheme",
 ];
 
 for (let method of [...addonSdkMethods, ...webExtensionsMethods]) {
   this.DevToolsShim[method] = function () {
-    if (!this.isInstalled()) {
-      throw new Error(`Method ${method} unavailable if DevTools are not installed`);
-    }
-
-    if (!this.isInitialized()) {
-      this._initDevTools();
-    }
-
     return this.gDevTools[method].apply(this.gDevTools, arguments);
   };
 }