summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/browser-menus.js
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/client/framework/browser-menus.js
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/framework/browser-menus.js')
-rw-r--r--devtools/client/framework/browser-menus.js390
1 files changed, 390 insertions, 0 deletions
diff --git a/devtools/client/framework/browser-menus.js b/devtools/client/framework/browser-menus.js
new file mode 100644
index 000000000..3d6c4def6
--- /dev/null
+++ b/devtools/client/framework/browser-menus.js
@@ -0,0 +1,390 @@
+/* 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 {LocalizationHelper} = require("devtools/shared/l10n");
+const MENUS_L10N = new LocalizationHelper("devtools/client/locales/menus.properties");
+
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
+
+// Keep list of inserted DOM Elements in order to remove them on unload
+// Maps browser xul document => list of DOM Elements
+const FragmentsCache = new Map();
+
+function l10n(key) {
+ return MENUS_L10N.getStr(key);
+}
+
+/**
+ * Create a xul:key element
+ *
+ * @param {XULDocument} doc
+ * The document to which keys are to be added.
+ * @param {String} id
+ * key's id, automatically prefixed with "key_".
+ * @param {String} shortcut
+ * The key shortcut value.
+ * @param {String} keytext
+ * If `shortcut` refers to a function key, refers to the localized
+ * string to describe a non-character shortcut.
+ * @param {String} modifiers
+ * Space separated list of modifier names.
+ * @param {Function} oncommand
+ * The function to call when the shortcut is pressed.
+ *
+ * @return XULKeyElement
+ */
+function createKey({ doc, id, shortcut, keytext, modifiers, oncommand }) {
+ let k = doc.createElement("key");
+ k.id = "key_" + id;
+
+ if (shortcut.startsWith("VK_")) {
+ k.setAttribute("keycode", shortcut);
+ if (keytext) {
+ k.setAttribute("keytext", keytext);
+ }
+ } else {
+ k.setAttribute("key", shortcut);
+ }
+
+ if (modifiers) {
+ k.setAttribute("modifiers", modifiers);
+ }
+
+ // Bug 371900: command event is fired only if "oncommand" attribute is set.
+ k.setAttribute("oncommand", ";");
+ k.addEventListener("command", oncommand);
+
+ return k;
+}
+
+/**
+ * Create a xul:menuitem element
+ *
+ * @param {XULDocument} doc
+ * The document to which keys are to be added.
+ * @param {String} id
+ * Element id.
+ * @param {String} label
+ * Menu label.
+ * @param {String} accesskey (optional)
+ * Access key of the menuitem, used as shortcut while opening the menu.
+ * @param {Boolean} isCheckbox (optional)
+ * If true, the menuitem will act as a checkbox and have an optional
+ * tick on its left.
+ *
+ * @return XULMenuItemElement
+ */
+function createMenuItem({ doc, id, label, accesskey, isCheckbox }) {
+ let menuitem = doc.createElement("menuitem");
+ menuitem.id = id;
+ menuitem.setAttribute("label", label);
+ if (accesskey) {
+ menuitem.setAttribute("accesskey", accesskey);
+ }
+ if (isCheckbox) {
+ menuitem.setAttribute("type", "checkbox");
+ menuitem.setAttribute("autocheck", "false");
+ }
+ return menuitem;
+}
+
+/**
+ * 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;
+ let menuId = "menuitem_" + id;
+
+ // Prevent multiple entries for the same tool.
+ if (doc.getElementById(menuId)) {
+ return;
+ }
+
+ let oncommand = function (id, event) {
+ let window = event.target.ownerDocument.defaultView;
+ gDevToolsBrowser.selectToolCommand(window.gBrowser, id);
+ }.bind(null, id);
+
+ let key = null;
+ if (toolDefinition.key) {
+ key = createKey({
+ doc,
+ id,
+ shortcut: toolDefinition.key,
+ modifiers: toolDefinition.modifiers,
+ oncommand: oncommand
+ });
+ }
+
+ let menuitem = createMenuItem({
+ doc,
+ id: "menuitem_" + id,
+ label: toolDefinition.menuLabel || toolDefinition.label,
+ accesskey: toolDefinition.accesskey
+ });
+ if (key) {
+ // Refer to the key in order to display the key shortcut at menu ends
+ menuitem.setAttribute("key", key.id);
+ }
+ menuitem.addEventListener("command", oncommand);
+
+ return {
+ key,
+ menuitem
+ };
+}
+
+/**
+ * Create xul menuitem, 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 { key, menuitem } = createToolMenuElements(toolDefinition, doc);
+
+ if (key) {
+ attachKeybindingsToBrowser(doc, key);
+ }
+
+ 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(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 key = doc.getElementById("key_" + toolId);
+ if (key) {
+ key.remove();
+ }
+
+ let menuitem = doc.getElementById("menuitem_" + toolId);
+ if (menuitem) {
+ menuitem.remove();
+ }
+}
+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 fragKeys = doc.createDocumentFragment();
+ let fragMenuItems = doc.createDocumentFragment();
+
+ for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
+ if (!toolDefinition.inMenu) {
+ continue;
+ }
+
+ let elements = createToolMenuElements(toolDefinition, doc);
+
+ if (!elements) {
+ continue;
+ }
+
+ if (elements.key) {
+ fragKeys.appendChild(elements.key);
+ }
+ fragMenuItems.appendChild(elements.menuitem);
+ }
+
+ attachKeybindingsToBrowser(doc, fragKeys);
+
+ let mps = doc.getElementById("menu_devtools_separator");
+ if (mps) {
+ mps.parentNode.insertBefore(fragMenuItems, mps);
+ }
+}
+
+/**
+ * Add global menus and shortcuts that are not panel specific.
+ *
+ * @param {XULDocument} doc
+ * The document to which keys and menus are to be added.
+ */
+function addTopLevelItems(doc) {
+ let keys = doc.createDocumentFragment();
+ let menuItems = doc.createDocumentFragment();
+
+ let { menuitems } = require("../menus");
+ for (let item of menuitems) {
+ if (item.separator) {
+ let separator = doc.createElement("menuseparator");
+ separator.id = item.id;
+ menuItems.appendChild(separator);
+ } else {
+ let { id, l10nKey } = item;
+
+ // Create a <menuitem>
+ let menuitem = createMenuItem({
+ doc,
+ id,
+ label: l10n(l10nKey + ".label"),
+ accesskey: l10n(l10nKey + ".accesskey"),
+ isCheckbox: item.checkbox
+ });
+ menuitem.addEventListener("command", item.oncommand);
+ menuItems.appendChild(menuitem);
+
+ if (item.key && l10nKey) {
+ // Create a <key>
+ let shortcut = l10n(l10nKey + ".key");
+ let key = createKey({
+ doc,
+ id: item.key.id,
+ shortcut: shortcut,
+ keytext: shortcut.startsWith("VK_") ? l10n(l10nKey + ".keytext") : null,
+ modifiers: item.key.modifiers,
+ oncommand: item.oncommand
+ });
+ // Refer to the key in order to display the key shortcut at menu ends
+ menuitem.setAttribute("key", key.id);
+ keys.appendChild(key);
+ }
+ if (item.additionalKeys) {
+ // Create additional <key>
+ for (let key of item.additionalKeys) {
+ let shortcut = l10n(key.l10nKey + ".key");
+ let node = createKey({
+ doc,
+ id: key.id,
+ shortcut: shortcut,
+ keytext: shortcut.startsWith("VK_") ? l10n(key.l10nKey + ".keytext") : null,
+ modifiers: key.modifiers,
+ oncommand: item.oncommand
+ });
+ keys.appendChild(node);
+ }
+ }
+ }
+ }
+
+ // Cache all nodes before insertion to be able to remove them on unload
+ let nodes = [];
+ for (let node of keys.children) {
+ nodes.push(node);
+ }
+ for (let node of menuItems.children) {
+ nodes.push(node);
+ }
+ FragmentsCache.set(doc, nodes);
+
+ attachKeybindingsToBrowser(doc, keys);
+
+ let menu = doc.getElementById("menuWebDeveloperPopup");
+ menu.appendChild(menuItems);
+
+ // There is still "Page Source" menuitem hardcoded into browser.xul. Instead
+ // of manually inserting everything around it, move it to the expected
+ // position.
+ let pageSource = doc.getElementById("menu_pageSource");
+ let endSeparator = doc.getElementById("devToolsEndSeparator");
+ menu.insertBefore(pageSource, endSeparator);
+}
+
+/**
+ * Remove global menus and shortcuts that are not panel specific.
+ *
+ * @param {XULDocument} doc
+ * The document to which keys and menus are to be added.
+ */
+function removeTopLevelItems(doc) {
+ let nodes = FragmentsCache.get(doc);
+ if (!nodes) {
+ return;
+ }
+ FragmentsCache.delete(doc);
+ for (let node of nodes) {
+ node.remove();
+ }
+}
+
+/**
+ * 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) {
+ addTopLevelItems(doc);
+
+ addAllToolsToMenu(doc);
+};
+
+/**
+ * Remove menus and shortcuts from a browser document
+ *
+ * @param {XULDocument} doc
+ * The document to which keys and menus are to be removed.
+ */
+exports.removeMenus = function (doc) {
+ // We only remove top level entries. Per-tool entries are removed while
+ // unregistering each tool.
+ removeTopLevelItems(doc);
+};