mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 15:55:36 +00:00
ecf8bd7f2d
Differential Revision: https://phabricator.services.mozilla.com/D16684 --HG-- extra : moz-landing-system : lando
350 lines
9.6 KiB
JavaScript
350 lines
9.6 KiB
JavaScript
/* 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 into browser UI.
|
|
*
|
|
* Menu definitions are fetched from:
|
|
* - devtools/client/menus for top level entires
|
|
* - devtools/client/definitions for tool-specifics entries
|
|
*/
|
|
|
|
const {Cu} = require("chrome");
|
|
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);
|
|
loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
|
|
|
|
let telemetry = null;
|
|
|
|
// 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:menuitem element
|
|
*
|
|
* @param {XULDocument} doc
|
|
* The document to which menus 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 }) {
|
|
const menuitem = doc.createXULElement("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 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) {
|
|
const id = toolDefinition.id;
|
|
const menuId = "menuitem_" + id;
|
|
|
|
// Prevent multiple entries for the same tool.
|
|
if (doc.getElementById(menuId)) {
|
|
return;
|
|
}
|
|
|
|
const oncommand = (async function(id, event) {
|
|
try {
|
|
const window = event.target.ownerDocument.defaultView;
|
|
await gDevToolsBrowser.selectToolCommand(window.gBrowser, id, Cu.now());
|
|
sendEntryPointTelemetry(window);
|
|
} catch (e) {
|
|
console.error(`Exception while opening ${id}: ${e}\n${e.stack}`);
|
|
}
|
|
}).bind(null, id);
|
|
|
|
const menuitem = createMenuItem({
|
|
doc,
|
|
id: "menuitem_" + id,
|
|
label: toolDefinition.menuLabel || toolDefinition.label,
|
|
accesskey: toolDefinition.accesskey,
|
|
});
|
|
// Refer to the key in order to display the key shortcut at menu ends
|
|
// This <key> element is being created by devtools/client/devtools-startup.js
|
|
menuitem.setAttribute("key", "key_" + id);
|
|
menuitem.addEventListener("command", oncommand);
|
|
|
|
return {
|
|
menuitem,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Send entry point telemetry explaining how the devtools were launched when
|
|
* launched from the System Menu.. This functionality also lives inside
|
|
* `devtools/startup/devtools-startup.js` but that codepath is only used the
|
|
* first time a toolbox is opened for a tab.
|
|
*/
|
|
function sendEntryPointTelemetry(window) {
|
|
if (!telemetry) {
|
|
telemetry = new Telemetry();
|
|
}
|
|
|
|
telemetry.addEventProperty(window, "open", "tools", null, "shortcut", "");
|
|
|
|
telemetry.addEventProperty(
|
|
window, "open", "tools", null, "entrypoint", "SystemMenu"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
const { menuitem } = createToolMenuElements(toolDefinition, doc);
|
|
|
|
let ref;
|
|
if (prevDef) {
|
|
const 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) {
|
|
const key = doc.getElementById("key_" + toolId);
|
|
if (key) {
|
|
key.remove();
|
|
}
|
|
|
|
const 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) {
|
|
const fragKeys = doc.createDocumentFragment();
|
|
const fragMenuItems = doc.createDocumentFragment();
|
|
|
|
for (const toolDefinition of gDevTools.getToolDefinitionArray()) {
|
|
if (!toolDefinition.inMenu) {
|
|
continue;
|
|
}
|
|
|
|
const elements = createToolMenuElements(toolDefinition, doc);
|
|
|
|
if (!elements) {
|
|
continue;
|
|
}
|
|
|
|
if (elements.key) {
|
|
fragKeys.appendChild(elements.key);
|
|
}
|
|
fragMenuItems.appendChild(elements.menuitem);
|
|
}
|
|
|
|
const mps = doc.getElementById("menu_devtools_separator");
|
|
if (mps) {
|
|
mps.parentNode.insertBefore(fragMenuItems, mps);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add global menus that are not panel specific.
|
|
*
|
|
* @param {XULDocument} doc
|
|
* The document to which menus are to be added.
|
|
*/
|
|
function addTopLevelItems(doc) {
|
|
const menuItems = doc.createDocumentFragment();
|
|
|
|
const { menuitems } = require("../menus");
|
|
for (const item of menuitems) {
|
|
if (item.separator) {
|
|
const separator = doc.createXULElement("menuseparator");
|
|
separator.id = item.id;
|
|
menuItems.appendChild(separator);
|
|
} else {
|
|
const { id, l10nKey } = item;
|
|
|
|
// Create a <menuitem>
|
|
const 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.keyId) {
|
|
menuitem.setAttribute("key", "key_" + item.keyId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cache all nodes before insertion to be able to remove them on unload
|
|
const nodes = [];
|
|
for (const node of menuItems.children) {
|
|
nodes.push(node);
|
|
}
|
|
FragmentsCache.set(doc, nodes);
|
|
|
|
const 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.
|
|
const pageSource = doc.getElementById("menu_pageSource");
|
|
const endSeparator = doc.getElementById("devToolsEndSeparator");
|
|
menu.insertBefore(pageSource, endSeparator);
|
|
}
|
|
|
|
/**
|
|
* Remove global menus that are not panel specific.
|
|
*
|
|
* @param {XULDocument} doc
|
|
* The document to which menus are to be added.
|
|
*/
|
|
function removeTopLevelItems(doc) {
|
|
const nodes = FragmentsCache.get(doc);
|
|
if (!nodes) {
|
|
return;
|
|
}
|
|
FragmentsCache.delete(doc);
|
|
for (const node of nodes) {
|
|
node.remove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add menus to a browser document
|
|
*
|
|
* @param {XULDocument} doc
|
|
* The document to which menus are to be added.
|
|
*/
|
|
exports.addMenus = function(doc) {
|
|
addTopLevelItems(doc);
|
|
|
|
addAllToolsToMenu(doc);
|
|
|
|
require("../webreplay/menu").addWebReplayMenu(doc);
|
|
};
|
|
|
|
/**
|
|
* Remove menus from a browser document
|
|
*
|
|
* @param {XULDocument} doc
|
|
* The document to which 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);
|
|
};
|
|
|
|
/**
|
|
* This is used for about:devtools-toolbox and that we are hiding the main toolbox toggle
|
|
* menu item, as well as all the tool items displayed on the menu. But we keep the
|
|
* non-toolbox menu items such as Scratchpad, Browser Console etc.
|
|
*
|
|
* @param {XULDocument} doc
|
|
* @param {boolean} isEnabled
|
|
*/
|
|
function setDevtoolsMenuItemsEnabled(doc, isEnabled) {
|
|
setMenuItemEnabled(doc, "menu_devToolbox", isEnabled);
|
|
|
|
for (const toolDefinition of gDevTools.getToolDefinitionArray()) {
|
|
if (!toolDefinition.inMenu) {
|
|
continue;
|
|
}
|
|
setMenuItemEnabled(doc, "menuitem_" + toolDefinition.id, isEnabled);
|
|
}
|
|
}
|
|
|
|
function setMenuItemEnabled(doc, menuItemId, isEnabled) {
|
|
const menuItem = doc.getElementById(menuItemId);
|
|
if (menuItem) {
|
|
if (isEnabled) {
|
|
menuItem.removeAttribute("hidden");
|
|
} else {
|
|
menuItem.setAttribute("hidden", true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable all devtools menu items.
|
|
*
|
|
* @param {XULDocument} doc
|
|
*/
|
|
exports.enableDevtoolsMenuItems = function(doc) {
|
|
setDevtoolsMenuItemsEnabled(doc, true);
|
|
};
|
|
|
|
/**
|
|
* Disable all devtools menu items.
|
|
*
|
|
* @param {XULDocument} doc
|
|
*/
|
|
exports.disableDevtoolsMenuItems = function(doc) {
|
|
setDevtoolsMenuItemsEnabled(doc, false);
|
|
};
|