Bug 1369801 - dt-addon: simplify devtools addon bootstrap and extract prefs loading;r=ochameau draft
authorJulian Descottes <jdescottes@mozilla.com>
Thu, 20 Jul 2017 18:31:46 +0200
changeset 658589 326cdb125d2b52adca6b437cf444fe599b40fbb9
parent 658588 6f6e9754187899d1949e9467217f677396ac8e55
child 658590 124212ffae5a63ad6626fa07c3af462554667ef0
push id77820
push userjdescottes@mozilla.com
push dateMon, 04 Sep 2017 11:44:42 +0000
reviewersochameau
bugs1369801
milestone57.0a1
Bug 1369801 - dt-addon: simplify devtools addon bootstrap and extract prefs loading;r=ochameau MozReview-Commit-ID: 13SxWssW0Xr
devtools/bootstrap.js
devtools/client/jar.mn
devtools/client/preferences/DevToolsPreferences.jsm
--- a/devtools/bootstrap.js
+++ b/devtools/bootstrap.js
@@ -3,186 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global content, APP_SHUTDOWN */
 /* exported startup, shutdown, install, uninstall */
 
 "use strict";
 
 const Cu = Components.utils;
-const Ci = Components.interfaces;
 const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
-const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
-const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
-
-// MultiWindowKeyListener instance for Ctrl+Alt+R key
-let listener;
-// nsIURI to the addon root folder
-let resourceURI;
-
-function actionOccurred(id) {
-  let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-  let Telemetry = require("devtools/client/shared/telemetry");
-  let telemetry = new Telemetry();
-  telemetry.actionOccurred(id);
-}
-
-// Synchronously fetch the content of a given URL
-function readURI(uri) {
-  let stream = NetUtil.newChannel({
-    uri: NetUtil.newURI(uri, "UTF-8"),
-    loadUsingSystemPrincipal: true}
-  ).open2();
-  let count = stream.available();
-  let data = NetUtil.readInputStreamToString(stream, count, {
-    charset: "UTF-8"
-  });
-
-  stream.close();
-
-  return data;
-}
-
-/**
- * Interpret the processing instructions contained in a preferences file, based on a
- * limited set of supported #if statements. After we ship as an addon, we don't want to
- * introduce anymore processing instructions, so all unrecognized preprocessing
- * instructions will be treated as an error.
- *
- * This function is mostly copied from devtools/client/inspector/webpack/prefs-loader.js
- *
- * @param  {String} content
- *         The string content of a preferences file.
- * @return {String} the content stripped of preprocessing instructions.
- */
-function interpretPreprocessingInstructions(content) {
-  const ifMap = {
-    "#if MOZ_UPDATE_CHANNEL == beta": AppConstants.MOZ_UPDATE_CHANNEL === "beta",
-    "#if defined(NIGHTLY_BUILD)": AppConstants.NIGHTLY_BUILD,
-    "#ifdef MOZ_DEV_EDITION": AppConstants.MOZ_DEV_EDITION,
-    "#ifdef RELEASE_OR_BETA": AppConstants.RELEASE_OR_BETA,
-  };
-
-  let lines = content.split("\n");
-  let ignoring = false;
-  let newLines = [];
-  let continuation = false;
-  for (let line of lines) {
-    if (line.startsWith("#if")) {
-      if (!(line in ifMap)) {
-        throw new Error("missing line in ifMap: " + line);
-      }
-      ignoring = !ifMap[line];
-    } else if (line.startsWith("#else")) {
-      ignoring = !ignoring;
-    } else if (line.startsWith("#endif")) {
-      ignoring = false;
-    }
-
-    let isPrefLine = /^ *(sticky_)?pref\("([^"]+)"/.test(line);
-    if (continuation || (!ignoring && isPrefLine)) {
-      newLines.push(line);
-
-      // The call to pref(...); might span more than one line.
-      continuation = !/\);/.test(line);
-    }
-  }
-  return newLines.join("\n");
-}
 
-// Read a preference file and set all of its defined pref as default values
-// (This replicates the behavior of preferences files from mozilla-central)
-function processPrefFile(url) {
-  let content = readURI(url);
-  content = interpretPreprocessingInstructions(content);
-  content.match(/pref\("[^"]+",\s*.+\s*\)/g).forEach(item => {
-    let m = item.match(/pref\("([^"]+)",\s*(.+)\s*\)/);
-    let name = m[1];
-    let val = m[2].trim();
-
-    // Prevent overriding prefs that have been changed by the user
-    if (Services.prefs.prefHasUserValue(name)) {
-      return;
-    }
-    let defaultBranch = Services.prefs.getDefaultBranch("");
-    if ((val.startsWith("\"") && val.endsWith("\"")) ||
-        (val.startsWith("'") && val.endsWith("'"))) {
-      val = val.substr(1, val.length - 2);
-      val = val.replace(/\\"/g, '"');
-      defaultBranch.setCharPref(name, val);
-    } else if (val.match(/[0-9]+/)) {
-      defaultBranch.setIntPref(name, parseInt(val, 10));
-    } else if (val == "true" || val == "false") {
-      defaultBranch.setBoolPref(name, val == "true");
-    } else {
-      console.log("Unable to match preference type for value:", val);
-    }
-  });
-}
-
-function setPrefs() {
-  processPrefFile(resourceURI.spec + "./client/preferences/devtools.js");
-  processPrefFile(resourceURI.spec + "./client/preferences/debugger.js");
-  processPrefFile(resourceURI.spec + "./client/webide/webide-prefs.js");
-}
-
-// Helper to listen to a key on all windows
-function MultiWindowKeyListener({ keyCode, ctrlKey, altKey, callback }) {
-  let keyListener = function (event) {
-    if (event.ctrlKey == !!ctrlKey &&
-        event.altKey == !!altKey &&
-        event.keyCode === keyCode) {
-      callback(event);
-
-      // Call preventDefault to avoid duplicated events when
-      // doing the key stroke within a tab.
-      event.preventDefault();
-    }
-  };
-
-  let observer = function (window, topic, data) {
-    // Listen on keyup to call keyListener only once per stroke
-    if (topic === "domwindowopened") {
-      window.addEventListener("keyup", keyListener);
-    } else {
-      window.removeEventListener("keyup", keyListener);
-    }
-  };
-
-  return {
-    start: function () {
-      // Automatically process already opened windows
-      let e = Services.ww.getWindowEnumerator();
-      while (e.hasMoreElements()) {
-        let window = e.getNext();
-        observer(window, "domwindowopened", null);
-      }
-      // And listen for new ones to come
-      Services.ww.registerNotification(observer);
-    },
-
-    stop: function () {
-      Services.ww.unregisterNotification(observer);
-      let e = Services.ww.getWindowEnumerator();
-      while (e.hasMoreElements()) {
-        let window = e.getNext();
-        observer(window, "domwindowclosed", null);
-      }
-    }
-  };
-}
-
-let getTopLevelWindow = function (window) {
-  return window.QueryInterface(Ci.nsIInterfaceRequestor)
-               .getInterface(Ci.nsIWebNavigation)
-               .QueryInterface(Ci.nsIDocShellTreeItem)
-               .rootTreeItem
-               .QueryInterface(Ci.nsIInterfaceRequestor)
-               .getInterface(Ci.nsIDOMWindow);
-};
+XPCOMUtils.defineLazyGetter(this, "DevToolsPreferences", function () {
+  return Cu.import("chrome://devtools/content/preferences/DevToolsPreferences.jsm", {}).DevToolsPreferences;
+});
 
 function unload(reason) {
   // This frame script is going to be executed in all processes:
   // parent and child
   Services.ppmm.loadProcessScript("data:,(" + function (scriptReason) {
     /* Flush message manager cached frame scripts as well as chrome locales */
     let obs = Components.classes["@mozilla.org/observer-service;1"]
                         .getService(Components.interfaces.nsIObserverService);
@@ -210,137 +45,25 @@ function unload(reason) {
   Cu.unload("resource://devtools/shared/Parser.jsm");
   Cu.unload("resource://devtools/client/shared/DOMHelpers.jsm");
   Cu.unload("resource://devtools/client/shared/widgets/VariablesView.jsm");
   Cu.unload("resource://devtools/client/responsivedesign/responsivedesign.jsm");
   Cu.unload("resource://devtools/client/shared/widgets/AbstractTreeItem.jsm");
   Cu.unload("resource://devtools/shared/deprecated-sync-thenables.js");
 }
 
-function reload(event) {
-  // We automatically reload the toolbox if we are on a browser tab
-  // with a toolbox already opened
-  let reloadToolbox = false;
-  if (event) {
-    let top = getTopLevelWindow(event.view);
-    let isBrowser = top.location.href.includes("/browser.xul");
-    if (isBrowser && top.gBrowser) {
-      // We do not use any devtools code before the call to Loader.jsm reload as
-      // any attempt to use Loader.jsm to load a module will instanciate a new
-      // Loader.
-      let nbox = top.gBrowser.getNotificationBox();
-      reloadToolbox =
-        top.document.getAnonymousElementByAttribute(nbox, "class",
-          "devtools-toolbox-bottom-iframe") ||
-        top.document.getAnonymousElementByAttribute(nbox, "class",
-          "devtools-toolbox-side-iframe") ||
-        Services.wm.getMostRecentWindow("devtools:toolbox");
-    }
-  }
-  let browserConsole = Services.wm.getMostRecentWindow("devtools:webconsole");
-  let reopenBrowserConsole = false;
-  if (browserConsole) {
-    browserConsole.close();
-    reopenBrowserConsole = true;
-  }
-
-  dump("Reload DevTools.  (reload-toolbox:" + reloadToolbox + ")\n");
-
-  // Invalidate xul cache in order to see changes made to chrome:// files
-  Services.obs.notifyObservers(null, "startupcache-invalidate");
-
-  unload("reload");
-
-  // Update the preferences before starting new code
-  setPrefs();
-
-  const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-  devtools.require("devtools/client/framework/devtools-browser");
-
-  // Go over all top level windows to reload all devtools related things
-  let windowsEnum = Services.wm.getEnumerator(null);
-  while (windowsEnum.hasMoreElements()) {
-    let window = windowsEnum.getNext();
-    let windowtype = window.document.documentElement.getAttribute("windowtype");
-    if (windowtype == "navigator:browser" && window.gBrowser) {
-      // Enumerate tabs on firefox windows
-      for (let tab of window.gBrowser.tabs) {
-        let browser = tab.linkedBrowser;
-        let location = browser.documentURI.spec;
-        let mm = browser.messageManager;
-        // To reload JSON-View tabs and any devtools document
-        if (location.startsWith("about:debugging") ||
-            location.startsWith("chrome://devtools/")) {
-          browser.reload();
-        }
-        // We have to use a frame script to query "baseURI"
-        mm.loadFrameScript("data:text/javascript,new " + function () {
-          let isJSONView =
-            content.document.baseURI.startsWith("resource://devtools/");
-          if (isJSONView) {
-            content.location.reload();
-          }
-        }, false);
-      }
-    } else if (windowtype === "devtools:webide") {
-      window.location.reload();
-    }
-  }
-
-  if (reloadToolbox) {
-    // Reopen the toolbox automatically if we are reloading from toolbox
-    // shortcut and are on a browser window.
-    // Wait for a second before opening the toolbox to avoid races
-    // between the old and the new one.
-    let {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
-    setTimeout(() => {
-      let { TargetFactory } = devtools.require("devtools/client/framework/target");
-      let { gDevTools } = devtools.require("devtools/client/framework/devtools");
-      let top = getTopLevelWindow(event.view);
-      let target = TargetFactory.forTab(top.gBrowser.selectedTab);
-      gDevTools.showToolbox(target);
-    }, 1000);
-  }
-
-  // Browser console document can't just be reloaded.
-  // HUDService is going to close it on unload.
-  // Instead we have to manually toggle it.
-  if (reopenBrowserConsole) {
-    let {HUDService} = devtools.require("devtools/client/webconsole/hudservice");
-    HUDService.toggleBrowserConsole();
-  }
-
-  actionOccurred("reloadAddonReload");
+function startup(data) {
+  // Load DevToolsPreferences as early as possible.
+  DevToolsPreferences.loadPrefs();
 }
 
-function startup(data) {
-  dump("DevTools addon started.\n");
-
-  resourceURI = data.resourceURI;
-
-  listener = new MultiWindowKeyListener({
-    keyCode: Ci.nsIDOMKeyEvent.DOM_VK_R, ctrlKey: true, altKey: true,
-    callback: reload
-  });
-  listener.start();
-
-  reload();
-}
 function shutdown(data, reason) {
   // On browser shutdown, do not try to cleanup anything
   if (reason == APP_SHUTDOWN) {
     return;
   }
 
-  listener.stop();
-  listener = null;
-
   unload("disable");
 }
-function install() {
-  try {
-    actionOccurred("reloadAddonInstalled");
-  } catch (e) {
-    // When installing on Firefox builds without devtools, telemetry doesn't
-    // work yet and throws.
-  }
-}
+
+function install() {}
+
 function uninstall() {}
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -1,16 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [features/devtools@mozilla.org] chrome.jar:
 %   content devtools %content/
     content/preferences/debugger.js (preferences/debugger.js)
 *   content/preferences/devtools.js (preferences/devtools.js)
+    content/preferences/DevToolsPreferences.jsm (preferences/DevToolsPreferences.jsm)
     content/webide/webide-prefs.js (webide/webide-prefs.js)
     content/shared/vendor/d3.js (shared/vendor/d3.js)
     content/shared/vendor/dagre-d3.js (shared/vendor/dagre-d3.js)
     content/shared/widgets/widgets.css (shared/widgets/widgets.css)
     content/netmonitor/src/assets/styles/netmonitor.css (netmonitor/src/assets/styles/netmonitor.css)
     content/shared/widgets/VariablesView.xul (shared/widgets/VariablesView.xul)
     content/netmonitor/index.html (netmonitor/index.html)
     content/webconsole/webconsole.xhtml (webconsole/webconsole.xhtml)
new file mode 100644
--- /dev/null
+++ b/devtools/client/preferences/DevToolsPreferences.jsm
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cu = Components.utils;
+
+const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+this.EXPORTED_SYMBOLS = ["DevToolsPreferences"];
+
+// Synchronously fetch the content of a given URL
+function readURI(uri) {
+  let stream = NetUtil.newChannel({
+    uri: NetUtil.newURI(uri, "UTF-8"),
+    loadUsingSystemPrincipal: true}
+  ).open2();
+  let count = stream.available();
+  let data = NetUtil.readInputStreamToString(stream, count, {
+    charset: "UTF-8"
+  });
+
+  stream.close();
+
+  return data;
+}
+
+/**
+ * Cleanup the preferences file content from all empty and comment lines.
+ *
+ * @param  {String} content
+ *         The string content of a preferences file.
+ * @return {String} the content stripped of unnecessary lines.
+ */
+function cleanupPreferencesFileContent(content) {
+  let lines = content.split("\n");
+  let newLines = [];
+  let continuation = false;
+  for (let line of lines) {
+    let isPrefLine = /^ *pref\("([^"]+)"/.test(line);
+    if (continuation || isPrefLine) {
+      newLines.push(line);
+      // The call to pref(...); might span more than one line.
+      continuation = !/\);/.test(line);
+    }
+  }
+  return newLines.join("\n");
+}
+
+// Read a preference file and set all of its defined pref as default values
+// (This replicates the behavior of preferences files from mozilla-central)
+function processPrefFile(url) {
+  let content = readURI(url);
+  content = cleanupPreferencesFileContent(content);
+  content.match(/pref\("[^"]+",\s*.+\s*\)/g).forEach(item => {
+    let m = item.match(/pref\("([^"]+)",\s*(.+)\s*\)/);
+    let name = m[1];
+    let val = m[2].trim();
+
+    // Prevent overriding prefs that have been changed by the user
+    if (Services.prefs.prefHasUserValue(name)) {
+      return;
+    }
+    let defaultBranch = Services.prefs.getDefaultBranch("");
+    if ((val.startsWith("\"") && val.endsWith("\"")) ||
+        (val.startsWith("'") && val.endsWith("'"))) {
+      val = val.substr(1, val.length - 2);
+      val = val.replace(/\\"/g, '"');
+      defaultBranch.setCharPref(name, val);
+    } else if (val.match(/[0-9]+/)) {
+      defaultBranch.setIntPref(name, parseInt(val, 10));
+    } else if (val == "true" || val == "false") {
+      defaultBranch.setBoolPref(name, val == "true");
+    } else {
+      console.log("Unable to match preference type for value:", val);
+    }
+  });
+}
+
+this.DevToolsPreferences = {
+  loadPrefs: function () {
+    processPrefFile("chrome://devtools/content/preferences/devtools.js");
+    processPrefFile("chrome://devtools/content/preferences/debugger.js");
+    processPrefFile("chrome://devtools/content/webide/webide-prefs.js");
+  }
+};