Bug 1248603 - Factor out menu and shortcut creation to dedicated module. r=jryans
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',