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 291387 d03f089bbab80cd36c4b0e82cbbaa1eebdb52528
parent 291386 6b710da110000b8d4b0116281fa319ab78879fbe
child 291388 836da2d40b19d6503e640897e7834ed36a8e8dab
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjryans
bugs1248603
milestone48.0a1
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',