Bug 1369801 - dt-addon: simplify devtools addon bootstrap and extract prefs loading;r=ochameau
authorJulian Descottes <jdescottes@mozilla.com>
Thu, 20 Jul 2017 18:31:46 +0200
changeset 419863 1b1a7c913f135d850a02899c30104e7db305378d
parent 419862 e2100bb65c97594d46184573c61a87592182f293
child 419864 ad2a532a50f023a28490bf19659e9635abb99ffd
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1369801
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 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 = /^ *(sticky_)?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");
+  }
+};