Bug 1359855 - Automatically initialize DevTools when accessing DevToolsShim.gDevTools. r=jdescottes draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 24 Jul 2017 14:30:24 +0200
changeset 614482 14afea2352ebb9a2de776841ffb43558672a494d
parent 614031 7f2d272a1fb00d6fd3e223f9e8f93ba2d4d9449d
child 614497 9befc68c2833d94424d3dd77218fbc182e79743b
child 614979 02832e5140a63d803c32ffec46161f00796f17a5
push id70013
push userbmo:poirot.alex@gmail.com
push dateMon, 24 Jul 2017 15:23:13 +0000
reviewersjdescottes
bugs1359855
milestone56.0a1
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);
   };
 }