diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /devtools/client/framework/browser-menus.js | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-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.js | 390 |
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); +}; |