Bug 1248603 - Factor out menu and shortcut creation to dedicated module. r=jryans
authorAlexandre Poirot <poirot.alex@gmail.com>
Fri, 01 Apr 2016 05:49:00 -0700
changeset 291328 d03f089bbab80cd36c4b0e82cbbaa1eebdb52528
parent 291327 6b710da110000b8d4b0116281fa319ab78879fbe
child 291329 836da2d40b19d6503e640897e7834ed36a8e8dab
push id30131
push userkwierso@gmail.com
push dateFri, 01 Apr 2016 22:43:45 +0000
treeherdermozilla-central@c40c0b2f3b4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjryans
bugs1248603
milestone48.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 1248603 - Factor out menu and shortcut creation to dedicated module. r=jryans
devtools/client/framework/browser-menus.js
devtools/client/framework/devtools-browser.js
devtools/client/framework/moz.build
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/browser-menus.js
@@ -0,0 +1,270 @@
+/* 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";
+
+/**
+ * This module inject dynamically menu items and key shortcuts into browser UI.
+ *
+ * Menu and shortcut definitions are fetched from:
+ * - devtools/client/menus for top level entires
+ * - devtools/client/definitions for tool-specifics entries
+ */
+
+const Services = require("Services");
+
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+
+/**
+ * Add a <key> to <keyset id="devtoolsKeyset">.
+ * 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).
+ *
+ * @param {XULDocument} doc
+ *        The document to which keys are to be added
+ * @param {XULElement} or {DocumentFragment} keys
+ *        Keys to add
+ */
+function attachKeybindingsToBrowser(doc, keys) {
+  let devtoolsKeyset = doc.getElementById("devtoolsKeyset");
+
+  if (!devtoolsKeyset) {
+    devtoolsKeyset = doc.createElement("keyset");
+    devtoolsKeyset.setAttribute("id", "devtoolsKeyset");
+  }
+  devtoolsKeyset.appendChild(keys);
+  let mainKeyset = doc.getElementById("mainKeyset");
+  mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset);
+}
+
+/**
+ * Add a menu entry for a tool definition
+ *
+ * @param {Object} toolDefinition
+ *        Tool definition of the tool to add a menu entry.
+ * @param {XULDocument} doc
+ *        The document to which the tool menu item is to be added.
+ */
+function createToolMenuElements(toolDefinition, doc) {
+  let id = toolDefinition.id;
+
+  // Prevent multiple entries for the same tool.
+  if (doc.getElementById("Tools:" + id)) {
+    return;
+  }
+
+  let cmd = doc.createElement("command");
+  cmd.id = "Tools:" + id;
+  cmd.setAttribute("oncommand",
+      'gDevToolsBrowser.selectToolCommand(gBrowser, "' + id + '");');
+
+  let key = null;
+  if (toolDefinition.key) {
+    key = doc.createElement("key");
+    key.id = "key_" + id;
+
+    if (toolDefinition.key.startsWith("VK_")) {
+      key.setAttribute("keycode", toolDefinition.key);
+    } else {
+      key.setAttribute("key", toolDefinition.key);
+    }
+
+    key.setAttribute("command", cmd.id);
+    key.setAttribute("modifiers", toolDefinition.modifiers);
+  }
+
+  let bc = doc.createElement("broadcaster");
+  bc.id = "devtoolsMenuBroadcaster_" + id;
+  bc.setAttribute("label", toolDefinition.menuLabel || toolDefinition.label);
+  bc.setAttribute("command", cmd.id);
+
+  if (key) {
+    bc.setAttribute("key", "key_" + id);
+  }
+
+  let appmenuitem = doc.createElement("menuitem");
+  appmenuitem.id = "appmenuitem_" + id;
+  appmenuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id);
+
+  let menuitem = doc.createElement("menuitem");
+  menuitem.id = "menuitem_" + id;
+  menuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id);
+
+  if (toolDefinition.accesskey) {
+    menuitem.setAttribute("accesskey", toolDefinition.accesskey);
+  }
+
+  return {
+    cmd: cmd,
+    key: key,
+    bc: bc,
+    appmenuitem: appmenuitem,
+    menuitem: menuitem
+  };
+}
+
+/**
+ * Create xul menuitem, command, broadcaster and key elements for a given tool.
+ * And then insert them into browser DOM.
+ *
+ * @param {XULDocument} doc
+ *        The document to which the tool is to be registered.
+ * @param {Object} toolDefinition
+ *        Tool definition of the tool to register.
+ * @param {Object} prevDef
+ *        The tool definition after which the tool menu item is to be added.
+ */
+function insertToolMenuElements(doc, toolDefinition, prevDef) {
+  let elements = createToolMenuElements(toolDefinition, doc);
+
+  doc.getElementById("mainCommandSet").appendChild(elements.cmd);
+
+  if (elements.key) {
+    attachKeybindingsToBrowser(doc, elements.key);
+  }
+
+  doc.getElementById("mainBroadcasterSet").appendChild(elements.bc);
+
+  let amp = doc.getElementById("appmenu_webDeveloper_popup");
+  if (amp) {
+    let ref;
+
+    if (prevDef != null) {
+      let menuitem = doc.getElementById("appmenuitem_" + prevDef.id);
+      ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
+    } else {
+      ref = doc.getElementById("appmenu_devtools_separator");
+    }
+
+    if (ref) {
+      amp.insertBefore(elements.appmenuitem, ref);
+    }
+  }
+
+  let ref;
+
+  if (prevDef) {
+    let menuitem = doc.getElementById("menuitem_" + prevDef.id);
+    ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
+  } else {
+    ref = doc.getElementById("menu_devtools_separator");
+  }
+
+  if (ref) {
+    ref.parentNode.insertBefore(elements.menuitem, ref);
+  }
+}
+exports.insertToolMenuElements = insertToolMenuElements;
+
+/**
+ * Remove a tool's menuitem from a window
+ *
+ * @param {string} toolId
+ *        Id of the tool to add a menu entry for
+ * @param {XULDocument} doc
+ *        The document to which the tool menu item is to be removed from
+ */
+function removeToolFromMenu(toolId, doc) {
+  let command = doc.getElementById("Tools:" + toolId);
+  if (command) {
+    command.parentNode.removeChild(command);
+  }
+
+  let key = doc.getElementById("key_" + toolId);
+  if (key) {
+    key.parentNode.removeChild(key);
+  }
+
+  let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId);
+  if (bc) {
+    bc.parentNode.removeChild(bc);
+  }
+
+  let appmenuitem = doc.getElementById("appmenuitem_" + toolId);
+  if (appmenuitem) {
+    appmenuitem.parentNode.removeChild(appmenuitem);
+  }
+
+  let menuitem = doc.getElementById("menuitem_" + toolId);
+  if (menuitem) {
+    menuitem.parentNode.removeChild(menuitem);
+  }
+}
+exports.removeToolFromMenu = removeToolFromMenu;
+
+/**
+ * Add all tools to the developer tools menu of a window.
+ *
+ * @param {XULDocument} doc
+ *        The document to which the tool items are to be added.
+ */
+function addAllToolsToMenu(doc) {
+  let fragCommands = doc.createDocumentFragment();
+  let fragKeys = doc.createDocumentFragment();
+  let fragBroadcasters = doc.createDocumentFragment();
+  let fragAppMenuItems = doc.createDocumentFragment();
+  let fragMenuItems = doc.createDocumentFragment();
+
+  for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
+    if (!toolDefinition.inMenu) {
+      continue;
+    }
+
+    let elements = createToolMenuElements(toolDefinition, doc);
+
+    if (!elements) {
+      continue;
+    }
+
+    fragCommands.appendChild(elements.cmd);
+    if (elements.key) {
+      fragKeys.appendChild(elements.key);
+    }
+    fragBroadcasters.appendChild(elements.bc);
+    fragAppMenuItems.appendChild(elements.appmenuitem);
+    fragMenuItems.appendChild(elements.menuitem);
+  }
+
+  let mcs = doc.getElementById("mainCommandSet");
+  mcs.appendChild(fragCommands);
+
+  attachKeybindingsToBrowser(doc, fragKeys);
+
+  let mbs = doc.getElementById("mainBroadcasterSet");
+  mbs.appendChild(fragBroadcasters);
+
+  let amps = doc.getElementById("appmenu_devtools_separator");
+  if (amps) {
+    amps.parentNode.insertBefore(fragAppMenuItems, amps);
+  }
+
+  let mps = doc.getElementById("menu_devtools_separator");
+  if (mps) {
+    mps.parentNode.insertBefore(fragMenuItems, mps);
+  }
+}
+
+/**
+ * Detect the presence of a Firebug.
+ */
+function isFirebugInstalled() {
+  let bootstrappedAddons = Services.prefs.getCharPref("extensions.bootstrappedAddons");
+  return bootstrappedAddons.indexOf("firebug@software.joehewitt.com") != -1;
+}
+
+/**
+ * Add menus and shortcuts to a browser document
+ *
+ * @param {XULDocument} doc
+ *        The document to which keys and menus are to be added.
+ */
+exports.addMenus = function (doc) {
+  addAllToolsToMenu(doc);
+
+  if (isFirebugInstalled()) {
+    let broadcaster = doc.getElementById("devtoolsMenuBroadcaster_DevToolbox");
+    broadcaster.removeAttribute("key");
+  }
+};
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -19,16 +19,17 @@ const Telemetry = require("devtools/clie
 const { gDevTools } = require("./devtools");
 const { when: unload } = require("sdk/system/unload");
 
 // Load target and toolbox lazily as they need gDevTools to be fully initialized
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
+loader.lazyRequireGetter(this, "BrowserMenus", "devtools/client/framework/browser-menus");
 
 loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
 
 const bundle = Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
 
 const TABS_OPEN_PEAK_HISTOGRAM = "DEVTOOLS_TABS_OPEN_PEAK_LINEAR";
 const TABS_OPEN_AVG_HISTOGRAM = "DEVTOOLS_TABS_OPEN_AVERAGE_LINEAR";
 const TABS_PINNED_PEAK_HISTOGRAM = "DEVTOOLS_TABS_PINNED_PEAK_LINEAR";
@@ -345,59 +346,32 @@ var gDevToolsBrowser = exports.gDevTools
 
   /**
    * Add this DevTools's presence to a browser window's document
    *
    * @param {XULDocument} doc
    *        The document to which menuitems and handlers are to be added
    */
   _registerBrowserWindow: function(win) {
+    BrowserMenus.addMenus(win.document);
+
     this.updateCommandAvailability(win);
     this.ensurePrefObserver();
     gDevToolsBrowser._trackedBrowserWindows.add(win);
     win.addEventListener("unload", this);
-    gDevToolsBrowser._addAllToolsToMenu(win.document);
-
-    if (this._isFirebugInstalled()) {
-      let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
-      broadcaster.removeAttribute("key");
-    }
 
     let tabContainer = win.gBrowser.tabContainer;
     tabContainer.addEventListener("TabSelect", this, false);
     tabContainer.addEventListener("TabOpen", this, false);
     tabContainer.addEventListener("TabClose", this, false);
     tabContainer.addEventListener("TabPinned", this, false);
     tabContainer.addEventListener("TabUnpinned", this, false);
   },
 
   /**
-   * Add a <key> to <keyset id="devtoolsKeyset">.
-   * 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).
-   *
-   * @param {XULDocument} doc
-   *        The document to which keys are to be added
-   * @param {XULElement} or {DocumentFragment} keys
-   *        Keys to add
-   */
-  attachKeybindingsToBrowser: function DT_attachKeybindingsToBrowser(doc, keys) {
-    let devtoolsKeyset = doc.getElementById("devtoolsKeyset");
-
-    if (!devtoolsKeyset) {
-      devtoolsKeyset = doc.createElement("keyset");
-      devtoolsKeyset.setAttribute("id", "devtoolsKeyset");
-    }
-    devtoolsKeyset.appendChild(keys);
-    let mainKeyset = doc.getElementById("mainKeyset");
-    mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset);
-  },
-
-  /**
    * Hook the JS debugger tool to the "Debug Script" button of the slow script
    * dialog.
    */
   setSlowScriptDebugHandler: function DT_setSlowScriptDebugHandler() {
     let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
                          .getService(Ci.nsISlowScriptDebug);
     let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
 
@@ -480,26 +454,16 @@ var gDevToolsBrowser = exports.gDevTools
    */
   unsetSlowScriptDebugHandler: function DT_unsetSlowScriptDebugHandler() {
     let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
                          .getService(Ci.nsISlowScriptDebug);
     debugService.activationHandler = undefined;
   },
 
   /**
-   * Detect the presence of a Firebug.
-   *
-   * @return promise
-   */
-  _isFirebugInstalled: function DT_isFirebugInstalled() {
-    let bootstrappedAddons = Services.prefs.getCharPref("extensions.bootstrappedAddons");
-    return bootstrappedAddons.indexOf("firebug@software.joehewitt.com") != -1;
-  },
-
-  /**
    * Add the menuitem for a tool to all open browser windows.
    *
    * @param {object} toolDefinition
    *        properties of the tool to add
    */
   _addToolToWindows: function DT_addToolToWindows(toolDefinition) {
     // No menu item or global shortcut is required for options panel.
     if (!toolDefinition.inMenu) {
@@ -524,180 +488,24 @@ var gDevToolsBrowser = exports.gDevTools
       }
       if (def === toolDefinition) {
         break;
       }
       prevDef = def;
     }
 
     for (let win of gDevToolsBrowser._trackedBrowserWindows) {
-      let doc = win.document;
-      let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
-
-      doc.getElementById("mainCommandSet").appendChild(elements.cmd);
-
-      if (elements.key) {
-        this.attachKeybindingsToBrowser(doc, elements.key);
-      }
-
-      doc.getElementById("mainBroadcasterSet").appendChild(elements.bc);
-
-      let amp = doc.getElementById("appmenu_webDeveloper_popup");
-      if (amp) {
-        let ref;
-
-        if (prevDef != null) {
-          let menuitem = doc.getElementById("appmenuitem_" + prevDef.id);
-          ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
-        } else {
-          ref = doc.getElementById("appmenu_devtools_separator");
-        }
-
-        if (ref) {
-          amp.insertBefore(elements.appmenuitem, ref);
-        }
-      }
-
-      let ref;
-
-      if (prevDef) {
-        let menuitem = doc.getElementById("menuitem_" + prevDef.id);
-        ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
-      } else {
-        ref = doc.getElementById("menu_devtools_separator");
-      }
-
-      if (ref) {
-        ref.parentNode.insertBefore(elements.menuitem, ref);
-      }
+      BrowserMenus.insertToolMenuElements(win.document, toolDefinition, prevDef);
     }
 
     if (toolDefinition.id === "jsdebugger") {
       gDevToolsBrowser.setSlowScriptDebugHandler();
     }
   },
 
-  /**
-   * Add all tools to the developer tools menu of a window.
-   *
-   * @param {XULDocument} doc
-   *        The document to which the tool items are to be added.
-   */
-  _addAllToolsToMenu: function DT_addAllToolsToMenu(doc) {
-    let fragCommands = doc.createDocumentFragment();
-    let fragKeys = doc.createDocumentFragment();
-    let fragBroadcasters = doc.createDocumentFragment();
-    let fragAppMenuItems = doc.createDocumentFragment();
-    let fragMenuItems = doc.createDocumentFragment();
-
-    for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
-      if (!toolDefinition.inMenu) {
-        continue;
-      }
-
-      let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
-
-      if (!elements) {
-        return;
-      }
-
-      fragCommands.appendChild(elements.cmd);
-      if (elements.key) {
-        fragKeys.appendChild(elements.key);
-      }
-      fragBroadcasters.appendChild(elements.bc);
-      fragAppMenuItems.appendChild(elements.appmenuitem);
-      fragMenuItems.appendChild(elements.menuitem);
-    }
-
-    let mcs = doc.getElementById("mainCommandSet");
-    mcs.appendChild(fragCommands);
-
-    this.attachKeybindingsToBrowser(doc, fragKeys);
-
-    let mbs = doc.getElementById("mainBroadcasterSet");
-    mbs.appendChild(fragBroadcasters);
-
-    let amps = doc.getElementById("appmenu_devtools_separator");
-    if (amps) {
-      amps.parentNode.insertBefore(fragAppMenuItems, amps);
-    }
-
-    let mps = doc.getElementById("menu_devtools_separator");
-    if (mps) {
-      mps.parentNode.insertBefore(fragMenuItems, mps);
-    }
-  },
-
-  /**
-   * Add a menu entry for a tool definition
-   *
-   * @param {string} toolDefinition
-   *        Tool definition of the tool to add a menu entry.
-   * @param {XULDocument} doc
-   *        The document to which the tool menu item is to be added.
-   */
-  _createToolMenuElements: function DT_createToolMenuElements(toolDefinition, doc) {
-    let id = toolDefinition.id;
-
-    // Prevent multiple entries for the same tool.
-    if (doc.getElementById("Tools:" + id)) {
-      return;
-    }
-
-    let cmd = doc.createElement("command");
-    cmd.id = "Tools:" + id;
-    cmd.setAttribute("oncommand",
-        'gDevToolsBrowser.selectToolCommand(gBrowser, "' + id + '");');
-
-    let key = null;
-    if (toolDefinition.key) {
-      key = doc.createElement("key");
-      key.id = "key_" + id;
-
-      if (toolDefinition.key.startsWith("VK_")) {
-        key.setAttribute("keycode", toolDefinition.key);
-      } else {
-        key.setAttribute("key", toolDefinition.key);
-      }
-
-      key.setAttribute("command", cmd.id);
-      key.setAttribute("modifiers", toolDefinition.modifiers);
-    }
-
-    let bc = doc.createElement("broadcaster");
-    bc.id = "devtoolsMenuBroadcaster_" + id;
-    bc.setAttribute("label", toolDefinition.menuLabel || toolDefinition.label);
-    bc.setAttribute("command", cmd.id);
-
-    if (key) {
-      bc.setAttribute("key", "key_" + id);
-    }
-
-    let appmenuitem = doc.createElement("menuitem");
-    appmenuitem.id = "appmenuitem_" + id;
-    appmenuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id);
-
-    let menuitem = doc.createElement("menuitem");
-    menuitem.id = "menuitem_" + id;
-    menuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id);
-
-    if (toolDefinition.accesskey) {
-      menuitem.setAttribute("accesskey", toolDefinition.accesskey);
-    }
-
-    return {
-      cmd: cmd,
-      key: key,
-      bc: bc,
-      appmenuitem: appmenuitem,
-      menuitem: menuitem
-    };
-  },
-
   hasToolboxOpened: function(win) {
     let tab = win.gBrowser.selectedTab;
     for (let [target, toolbox] of gDevTools._toolboxes) {
       if (target.tab == tab) {
         return true;
       }
     }
     return false;
@@ -724,60 +532,25 @@ var gDevToolsBrowser = exports.gDevTools
   /**
    * Remove the menuitem for a tool to all open browser windows.
    *
    * @param {string} toolId
    *        id of the tool to remove
    */
   _removeToolFromWindows: function DT_removeToolFromWindows(toolId) {
     for (let win of gDevToolsBrowser._trackedBrowserWindows) {
-      gDevToolsBrowser._removeToolFromMenu(toolId, win.document);
+      BrowserMenus.removeToolFromMenu(toolId, win.document);
     }
 
     if (toolId === "jsdebugger") {
       gDevToolsBrowser.unsetSlowScriptDebugHandler();
     }
   },
 
   /**
-   * Remove a tool's menuitem from a window
-   *
-   * @param {string} toolId
-   *        Id of the tool to add a menu entry for
-   * @param {XULDocument} doc
-   *        The document to which the tool menu item is to be removed from
-   */
-  _removeToolFromMenu: function DT_removeToolFromMenu(toolId, doc) {
-    let command = doc.getElementById("Tools:" + toolId);
-    if (command) {
-      command.parentNode.removeChild(command);
-    }
-
-    let key = doc.getElementById("key_" + toolId);
-    if (key) {
-      key.parentNode.removeChild(key);
-    }
-
-    let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId);
-    if (bc) {
-      bc.parentNode.removeChild(bc);
-    }
-
-    let appmenuitem = doc.getElementById("appmenuitem_" + toolId);
-    if (appmenuitem) {
-      appmenuitem.parentNode.removeChild(appmenuitem);
-    }
-
-    let menuitem = doc.getElementById("menuitem_" + toolId);
-    if (menuitem) {
-      menuitem.parentNode.removeChild(menuitem);
-    }
-  },
-
-  /**
    * Called on browser unload to remove menu entries, toolboxes and event
    * listeners from the closed browser window.
    *
    * @param  {XULWindow} win
    *         The window containing the menu entry
    */
   _forgetBrowserWindow: function(win) {
     gDevToolsBrowser._trackedBrowserWindows.delete(win);
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -7,16 +7,17 @@
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 TEST_HARNESS_FILES.xpcshell.devtools.client.framework.test += [
     'test/shared-redux-head.js',
 ]
 
 DevToolsModules(
     'about-devtools-toolbox.js',
     'attach-thread.js',
+    'browser-menus.js',
     'devtools-browser.js',
     'devtools.js',
     'gDevTools.jsm',
     'selection.js',
     'sidebar.js',
     'source-location.js',
     'target-from-url.js',
     'target.js',