Now that the JsTerm is a React component, we shouldn't let external consumers have to get the instance in order to perform some actions on the console input. We start with the most used actions, setInputValue and getInputValue, and might extend that in the future. Differential Revision: https://phabricator.services.mozilla.com/D20685
777 lines
25 KiB
JavaScript
777 lines
25 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* 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";
|
|
|
|
const Services = require("Services");
|
|
const promise = require("promise");
|
|
const { LocalizationHelper } = require("devtools/shared/l10n");
|
|
|
|
loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
|
|
loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
|
|
loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
|
|
|
|
loader.lazyGetter(this, "TOOLBOX_L10N", function() {
|
|
return new LocalizationHelper("devtools/client/locales/toolbox.properties");
|
|
});
|
|
|
|
const INSPECTOR_L10N =
|
|
new LocalizationHelper("devtools/client/locales/inspector.properties");
|
|
|
|
/**
|
|
* Context menu for the Markup view.
|
|
*/
|
|
class MarkupContextMenu {
|
|
constructor(markup) {
|
|
this.markup = markup;
|
|
this.inspector = markup.inspector;
|
|
this.selection = this.inspector.selection;
|
|
this.target = this.inspector.target;
|
|
this.telemetry = this.inspector.telemetry;
|
|
this.toolbox = this.inspector.toolbox;
|
|
this.walker = this.inspector.walker;
|
|
}
|
|
|
|
destroy() {
|
|
this.markup = null;
|
|
this.inspector = null;
|
|
this.selection = null;
|
|
this.target = null;
|
|
this.telemetry = null;
|
|
this.toolbox = null;
|
|
this.walker = null;
|
|
}
|
|
|
|
show(event) {
|
|
if (!(event.originalTarget instanceof Element) ||
|
|
event.originalTarget.closest("input[type=text]") ||
|
|
event.originalTarget.closest("input:not([type])") ||
|
|
event.originalTarget.closest("textarea")) {
|
|
return;
|
|
}
|
|
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
this._openMenu({
|
|
screenX: event.screenX,
|
|
screenY: event.screenY,
|
|
target: event.target,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This method is here for the benefit of copying links.
|
|
*/
|
|
_copyAttributeLink(link) {
|
|
this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => {
|
|
clipboardHelper.copyString(url);
|
|
}, console.error);
|
|
}
|
|
|
|
/**
|
|
* Copy the full CSS Path of the selected Node to the clipboard.
|
|
*/
|
|
_copyCssPath() {
|
|
if (!this.selection.isNode()) {
|
|
return;
|
|
}
|
|
|
|
this.telemetry.scalarSet("devtools.copy.full.css.selector.opened", 1);
|
|
this.selection.nodeFront.getCssPath().then(path => {
|
|
clipboardHelper.copyString(path);
|
|
}).catch(console.error);
|
|
}
|
|
|
|
/**
|
|
* Copy the data-uri for the currently selected image in the clipboard.
|
|
*/
|
|
_copyImageDataUri() {
|
|
const container = this.markup.getContainer(this.selection.nodeFront);
|
|
if (container && container.isPreviewable()) {
|
|
container.copyImageDataUri();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copy the innerHTML of the selected Node to the clipboard.
|
|
*/
|
|
_copyInnerHTML() {
|
|
this.markup.copyInnerHTML();
|
|
}
|
|
|
|
/**
|
|
* Copy the outerHTML of the selected Node to the clipboard.
|
|
*/
|
|
_copyOuterHTML() {
|
|
this.markup.copyOuterHTML();
|
|
}
|
|
|
|
/**
|
|
* Copy a unique selector of the selected Node to the clipboard.
|
|
*/
|
|
_copyUniqueSelector() {
|
|
if (!this.selection.isNode()) {
|
|
return;
|
|
}
|
|
|
|
this.telemetry.scalarSet("devtools.copy.unique.css.selector.opened", 1);
|
|
this.selection.nodeFront.getUniqueSelector().then(selector => {
|
|
clipboardHelper.copyString(selector);
|
|
}).catch(console.error);
|
|
}
|
|
|
|
/**
|
|
* Copy the XPath of the selected Node to the clipboard.
|
|
*/
|
|
_copyXPath() {
|
|
if (!this.selection.isNode()) {
|
|
return;
|
|
}
|
|
|
|
this.telemetry.scalarSet("devtools.copy.xpath.opened", 1);
|
|
this.selection.nodeFront.getXPath().then(path => {
|
|
clipboardHelper.copyString(path);
|
|
}).catch(console.error);
|
|
}
|
|
|
|
/**
|
|
* Delete the selected node.
|
|
*/
|
|
_deleteNode() {
|
|
if (!this.selection.isNode() ||
|
|
this.selection.isRoot()) {
|
|
return;
|
|
}
|
|
|
|
// If the markup panel is active, use the markup panel to delete
|
|
// the node, making this an undoable action.
|
|
if (this.markup) {
|
|
this.markup.deleteNode(this.selection.nodeFront);
|
|
} else {
|
|
// remove the node from content
|
|
this.walker.removeNode(this.selection.nodeFront);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Duplicate the selected node
|
|
*/
|
|
_duplicateNode() {
|
|
if (!this.selection.isElementNode() ||
|
|
this.selection.isRoot() ||
|
|
this.selection.isAnonymousNode() ||
|
|
this.selection.isPseudoElementNode()) {
|
|
return;
|
|
}
|
|
|
|
this.walker.duplicateNode(this.selection.nodeFront).catch(console.error);
|
|
}
|
|
|
|
/**
|
|
* Edit the outerHTML of the selected Node.
|
|
*/
|
|
_editHTML() {
|
|
if (!this.selection.isNode()) {
|
|
return;
|
|
}
|
|
|
|
this.markup.beginEditingOuterHTML(this.selection.nodeFront);
|
|
}
|
|
|
|
/**
|
|
* Jumps to the custom element definition in the debugger.
|
|
*/
|
|
_jumpToCustomElementDefinition() {
|
|
const { url, line } = this.selection.nodeFront.customElementLocation;
|
|
this.toolbox.viewSourceInDebugger(url, line, "show_custom_element");
|
|
}
|
|
|
|
/**
|
|
* Add attribute to node.
|
|
* Used for node context menu and shouldn't be called directly.
|
|
*/
|
|
_onAddAttribute() {
|
|
const container = this.markup.getContainer(this.selection.nodeFront);
|
|
container.addAttribute();
|
|
}
|
|
|
|
/**
|
|
* Copy attribute value for node.
|
|
* Used for node context menu and shouldn't be called directly.
|
|
*/
|
|
_onCopyAttributeValue() {
|
|
clipboardHelper.copyString(this.nodeMenuTriggerInfo.value);
|
|
}
|
|
|
|
/**
|
|
* This method is here for the benefit of the node-menu-link-copy menu item
|
|
* in the inspector contextual-menu.
|
|
*/
|
|
_onCopyLink() {
|
|
this.copyAttributeLink(this.contextMenuTarget.dataset.link);
|
|
}
|
|
|
|
/**
|
|
* Edit attribute for node.
|
|
* Used for node context menu and shouldn't be called directly.
|
|
*/
|
|
_onEditAttribute() {
|
|
const container = this.markup.getContainer(this.selection.nodeFront);
|
|
container.editAttribute(this.nodeMenuTriggerInfo.name);
|
|
}
|
|
|
|
/**
|
|
* This method is here for the benefit of the node-menu-link-follow menu item
|
|
* in the inspector contextual-menu.
|
|
*/
|
|
_onFollowLink() {
|
|
const type = this.contextMenuTarget.dataset.type;
|
|
const link = this.contextMenuTarget.dataset.link;
|
|
this.markup.followAttributeLink(type, link);
|
|
}
|
|
|
|
/**
|
|
* Remove attribute from node.
|
|
* Used for node context menu and shouldn't be called directly.
|
|
*/
|
|
_onRemoveAttribute() {
|
|
const container = this.markup.getContainer(this.selection.nodeFront);
|
|
container.removeAttribute(this.nodeMenuTriggerInfo.name);
|
|
}
|
|
|
|
/**
|
|
* Paste the contents of the clipboard as adjacent HTML to the selected Node.
|
|
*
|
|
* @param {String} position
|
|
* The position as specified for Element.insertAdjacentHTML
|
|
* (i.e. "beforeBegin", "afterBegin", "beforeEnd", "afterEnd").
|
|
*/
|
|
_pasteAdjacentHTML(position) {
|
|
const content = this._getClipboardContentForPaste();
|
|
if (!content) {
|
|
return promise.reject("No clipboard content for paste");
|
|
}
|
|
|
|
const node = this.selection.nodeFront;
|
|
return this.markup.insertAdjacentHTMLToNode(node, position, content);
|
|
}
|
|
|
|
/**
|
|
* Paste the contents of the clipboard into the selected Node's inner HTML.
|
|
*/
|
|
_pasteInnerHTML() {
|
|
const content = this._getClipboardContentForPaste();
|
|
if (!content) {
|
|
return promise.reject("No clipboard content for paste");
|
|
}
|
|
|
|
const node = this.selection.nodeFront;
|
|
return this.markup.getNodeInnerHTML(node).then(oldContent => {
|
|
this.markup.updateNodeInnerHTML(node, content, oldContent);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Paste the contents of the clipboard into the selected Node's outer HTML.
|
|
*/
|
|
_pasteOuterHTML() {
|
|
const content = this._getClipboardContentForPaste();
|
|
if (!content) {
|
|
return promise.reject("No clipboard content for paste");
|
|
}
|
|
|
|
const node = this.selection.nodeFront;
|
|
return this.markup.getNodeOuterHTML(node).then(oldContent => {
|
|
this.markup.updateNodeOuterHTML(node, content, oldContent);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show Accessibility properties for currently selected node
|
|
*/
|
|
async _showAccessibilityProperties() {
|
|
const a11yPanel = await this.toolbox.selectTool("accessibility");
|
|
// Select the accessible object in the panel and wait for the event that
|
|
// tells us it has been done.
|
|
const onSelected = a11yPanel.once("new-accessible-front-selected");
|
|
a11yPanel.selectAccessibleForNode(this.selection.nodeFront, "inspector-context-menu");
|
|
await onSelected;
|
|
}
|
|
|
|
/**
|
|
* Show DOM properties
|
|
*/
|
|
_showDOMProperties() {
|
|
this.toolbox.openSplitConsole().then(() => {
|
|
const panel = this.toolbox.getPanel("webconsole");
|
|
const jsterm = panel.hud.jsterm;
|
|
|
|
jsterm.execute("inspect($0)");
|
|
jsterm.focus();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Use in Console.
|
|
*
|
|
* Takes the currently selected node in the inspector and assigns it to a
|
|
* temp variable on the content window. Also opens the split console and
|
|
* autofills it with the temp variable.
|
|
*/
|
|
_useInConsole() {
|
|
this.toolbox.openSplitConsole().then(() => {
|
|
const {hud} = this.toolbox.getPanel("webconsole");
|
|
|
|
const evalString = `{ let i = 0;
|
|
while (window.hasOwnProperty("temp" + i) && i < 1000) {
|
|
i++;
|
|
}
|
|
window["temp" + i] = $0;
|
|
"temp" + i;
|
|
}`;
|
|
|
|
const options = {
|
|
selectedNodeActor: this.selection.nodeFront.actorID,
|
|
};
|
|
hud.jsterm.requestEvaluation(evalString, options).then((res) => {
|
|
hud.setInputValue(res.result);
|
|
this.inspector.emit("console-var-ready");
|
|
});
|
|
});
|
|
}
|
|
|
|
_buildA11YMenuItem(menu) {
|
|
if (!(this.selection.isElementNode() || this.selection.isTextNode()) ||
|
|
!Services.prefs.getBoolPref("devtools.accessibility.enabled")) {
|
|
return;
|
|
}
|
|
|
|
const showA11YPropsItem = new MenuItem({
|
|
id: "node-menu-showaccessibilityproperties",
|
|
label: INSPECTOR_L10N.getStr("inspectorShowAccessibilityProperties.label"),
|
|
click: () => this._showAccessibilityProperties(),
|
|
disabled: true,
|
|
});
|
|
|
|
// Only attempt to determine if a11y props menu item needs to be enabled if
|
|
// AccessibilityFront is enabled.
|
|
if (this.inspector.accessibilityFront.enabled) {
|
|
this._updateA11YMenuItem(showA11YPropsItem);
|
|
}
|
|
|
|
menu.append(showA11YPropsItem);
|
|
}
|
|
|
|
_getAttributesSubmenu(isEditableElement) {
|
|
const attributesSubmenu = new Menu();
|
|
const nodeInfo = this.nodeMenuTriggerInfo;
|
|
const isAttributeClicked = isEditableElement && nodeInfo &&
|
|
nodeInfo.type === "attribute";
|
|
|
|
attributesSubmenu.append(new MenuItem({
|
|
id: "node-menu-add-attribute",
|
|
label: INSPECTOR_L10N.getStr("inspectorAddAttribute.label"),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorAddAttribute.accesskey"),
|
|
disabled: !isEditableElement,
|
|
click: () => this._onAddAttribute(),
|
|
}));
|
|
attributesSubmenu.append(new MenuItem({
|
|
id: "node-menu-copy-attribute",
|
|
label: INSPECTOR_L10N.getFormatStr("inspectorCopyAttributeValue.label",
|
|
isAttributeClicked ? `${nodeInfo.value}` : ""),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorCopyAttributeValue.accesskey"),
|
|
disabled: !isAttributeClicked,
|
|
click: () => this._onCopyAttributeValue(),
|
|
}));
|
|
attributesSubmenu.append(new MenuItem({
|
|
id: "node-menu-edit-attribute",
|
|
label: INSPECTOR_L10N.getFormatStr("inspectorEditAttribute.label",
|
|
isAttributeClicked ? `${nodeInfo.name}` : ""),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorEditAttribute.accesskey"),
|
|
disabled: !isAttributeClicked,
|
|
click: () => this._onEditAttribute(),
|
|
}));
|
|
attributesSubmenu.append(new MenuItem({
|
|
id: "node-menu-remove-attribute",
|
|
label: INSPECTOR_L10N.getFormatStr("inspectorRemoveAttribute.label",
|
|
isAttributeClicked ? `${nodeInfo.name}` : ""),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorRemoveAttribute.accesskey"),
|
|
disabled: !isAttributeClicked,
|
|
click: () => this._onRemoveAttribute(),
|
|
}));
|
|
|
|
return attributesSubmenu;
|
|
}
|
|
|
|
/**
|
|
* Returns the clipboard content if it is appropriate for pasting
|
|
* into the current node's outer HTML, otherwise returns null.
|
|
*/
|
|
_getClipboardContentForPaste() {
|
|
const content = clipboardHelper.getText();
|
|
if (content && content.trim().length > 0) {
|
|
return content;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
_getCopySubmenu(markupContainer, isSelectionElement) {
|
|
const copySubmenu = new Menu();
|
|
copySubmenu.append(new MenuItem({
|
|
id: "node-menu-copyinner",
|
|
label: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.label"),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.accesskey"),
|
|
disabled: !isSelectionElement,
|
|
click: () => this._copyInnerHTML(),
|
|
}));
|
|
copySubmenu.append(new MenuItem({
|
|
id: "node-menu-copyouter",
|
|
label: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.label"),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.accesskey"),
|
|
disabled: !isSelectionElement,
|
|
click: () => this._copyOuterHTML(),
|
|
}));
|
|
copySubmenu.append(new MenuItem({
|
|
id: "node-menu-copyuniqueselector",
|
|
label: INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.accesskey"),
|
|
disabled: !isSelectionElement,
|
|
click: () => this._copyUniqueSelector(),
|
|
}));
|
|
copySubmenu.append(new MenuItem({
|
|
id: "node-menu-copycsspath",
|
|
label: INSPECTOR_L10N.getStr("inspectorCopyCSSPath.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorCopyCSSPath.accesskey"),
|
|
disabled: !isSelectionElement,
|
|
click: () => this._copyCssPath(),
|
|
}));
|
|
copySubmenu.append(new MenuItem({
|
|
id: "node-menu-copyxpath",
|
|
label: INSPECTOR_L10N.getStr("inspectorCopyXPath.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorCopyXPath.accesskey"),
|
|
disabled: !isSelectionElement,
|
|
click: () => this._copyXPath(),
|
|
}));
|
|
copySubmenu.append(new MenuItem({
|
|
id: "node-menu-copyimagedatauri",
|
|
label: INSPECTOR_L10N.getStr("inspectorImageDataUri.label"),
|
|
disabled: !isSelectionElement || !markupContainer ||
|
|
!markupContainer.isPreviewable(),
|
|
click: () => this._copyImageDataUri(),
|
|
}));
|
|
|
|
return copySubmenu;
|
|
}
|
|
|
|
/**
|
|
* Link menu items can be shown or hidden depending on the context and
|
|
* selected node, and their labels can vary.
|
|
*
|
|
* @return {Array} list of visible menu items related to links.
|
|
*/
|
|
_getNodeLinkMenuItems() {
|
|
const linkFollow = new MenuItem({
|
|
id: "node-menu-link-follow",
|
|
visible: false,
|
|
click: () => this._onFollowLink(),
|
|
});
|
|
const linkCopy = new MenuItem({
|
|
id: "node-menu-link-copy",
|
|
visible: false,
|
|
click: () => this._onCopyLink(),
|
|
});
|
|
|
|
// Get information about the right-clicked node.
|
|
const popupNode = this.contextMenuTarget;
|
|
if (!popupNode || !popupNode.classList.contains("link")) {
|
|
return [linkFollow, linkCopy];
|
|
}
|
|
|
|
const type = popupNode.dataset.type;
|
|
if ((type === "uri" || type === "cssresource" || type === "jsresource")) {
|
|
// Links can't be opened in new tabs in the browser toolbox.
|
|
if (type === "uri" && !this.target.chrome) {
|
|
linkFollow.visible = true;
|
|
linkFollow.label = INSPECTOR_L10N.getStr(
|
|
"inspector.menu.openUrlInNewTab.label");
|
|
} else if (type === "cssresource") {
|
|
linkFollow.visible = true;
|
|
linkFollow.label = TOOLBOX_L10N.getStr(
|
|
"toolbox.viewCssSourceInStyleEditor.label");
|
|
} else if (type === "jsresource") {
|
|
linkFollow.visible = true;
|
|
linkFollow.label = TOOLBOX_L10N.getStr(
|
|
"toolbox.viewJsSourceInDebugger.label");
|
|
}
|
|
|
|
linkCopy.visible = true;
|
|
linkCopy.label = INSPECTOR_L10N.getStr(
|
|
"inspector.menu.copyUrlToClipboard.label");
|
|
} else if (type === "idref") {
|
|
linkFollow.visible = true;
|
|
linkFollow.label = INSPECTOR_L10N.getFormatStr(
|
|
"inspector.menu.selectElement.label", popupNode.dataset.link);
|
|
}
|
|
|
|
return [linkFollow, linkCopy];
|
|
}
|
|
|
|
_getPasteSubmenu(isEditableElement) {
|
|
const isPasteable = isEditableElement && this._getClipboardContentForPaste();
|
|
const disableAdjacentPaste = !isPasteable ||
|
|
this.selection.isRoot() ||
|
|
this.selection.isBodyNode() ||
|
|
this.selection.isHeadNode();
|
|
const disableFirstLastPaste = !isPasteable ||
|
|
(this.selection.isHTMLNode() && this.selection.isRoot());
|
|
|
|
const pasteSubmenu = new Menu();
|
|
pasteSubmenu.append(new MenuItem({
|
|
id: "node-menu-pasteinnerhtml",
|
|
label: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.label"),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.accesskey"),
|
|
disabled: !isPasteable,
|
|
click: () => this._pasteInnerHTML(),
|
|
}));
|
|
pasteSubmenu.append(new MenuItem({
|
|
id: "node-menu-pasteouterhtml",
|
|
label: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.label"),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.accesskey"),
|
|
disabled: !isPasteable,
|
|
click: () => this._pasteOuterHTML(),
|
|
}));
|
|
pasteSubmenu.append(new MenuItem({
|
|
id: "node-menu-pastebefore",
|
|
label: INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.accesskey"),
|
|
disabled: disableAdjacentPaste,
|
|
click: () => this._pasteAdjacentHTML("beforeBegin"),
|
|
}));
|
|
pasteSubmenu.append(new MenuItem({
|
|
id: "node-menu-pasteafter",
|
|
label: INSPECTOR_L10N.getStr("inspectorHTMLPasteAfter.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorHTMLPasteAfter.accesskey"),
|
|
disabled: disableAdjacentPaste,
|
|
click: () => this._pasteAdjacentHTML("afterEnd"),
|
|
}));
|
|
pasteSubmenu.append(new MenuItem({
|
|
id: "node-menu-pastefirstchild",
|
|
label: INSPECTOR_L10N.getStr("inspectorHTMLPasteFirstChild.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorHTMLPasteFirstChild.accesskey"),
|
|
disabled: disableFirstLastPaste,
|
|
click: () => this._pasteAdjacentHTML("afterBegin"),
|
|
}));
|
|
pasteSubmenu.append(new MenuItem({
|
|
id: "node-menu-pastelastchild",
|
|
label: INSPECTOR_L10N.getStr("inspectorHTMLPasteLastChild.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorHTMLPasteLastChild.accesskey"),
|
|
disabled: disableFirstLastPaste,
|
|
click: () => this._pasteAdjacentHTML("beforeEnd"),
|
|
}));
|
|
|
|
return pasteSubmenu;
|
|
}
|
|
|
|
_openMenu({ target, screenX = 0, screenY = 0 } = {}) {
|
|
if (this.selection.isSlotted()) {
|
|
// Slotted elements should not show any context menu.
|
|
return null;
|
|
}
|
|
|
|
const markupContainer = this.markup.getContainer(this.selection.nodeFront);
|
|
|
|
this.contextMenuTarget = target;
|
|
this.nodeMenuTriggerInfo = markupContainer &&
|
|
markupContainer.editor.getInfoAtNode(target);
|
|
|
|
const isSelectionElement = this.selection.isElementNode() &&
|
|
!this.selection.isPseudoElementNode();
|
|
const isEditableElement = isSelectionElement &&
|
|
!this.selection.isAnonymousNode();
|
|
const isDuplicatableElement = isSelectionElement &&
|
|
!this.selection.isAnonymousNode() &&
|
|
!this.selection.isRoot();
|
|
const isScreenshotable = isSelectionElement &&
|
|
this.selection.nodeFront.isTreeDisplayed;
|
|
|
|
const menu = new Menu();
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-edithtml",
|
|
label: INSPECTOR_L10N.getStr("inspectorHTMLEdit.label"),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorHTMLEdit.accesskey"),
|
|
disabled: !isEditableElement,
|
|
click: () => this._editHTML(),
|
|
}));
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-add",
|
|
label: INSPECTOR_L10N.getStr("inspectorAddNode.label"),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorAddNode.accesskey"),
|
|
disabled: !this.inspector.canAddHTMLChild(),
|
|
click: () => this.inspector.addNode(),
|
|
}));
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-duplicatenode",
|
|
label: INSPECTOR_L10N.getStr("inspectorDuplicateNode.label"),
|
|
disabled: !isDuplicatableElement,
|
|
click: () => this._duplicateNode(),
|
|
}));
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-delete",
|
|
label: INSPECTOR_L10N.getStr("inspectorHTMLDelete.label"),
|
|
accesskey: INSPECTOR_L10N.getStr("inspectorHTMLDelete.accesskey"),
|
|
disabled: !this.markup.isDeletable(this.selection.nodeFront),
|
|
click: () => this._deleteNode(),
|
|
}));
|
|
|
|
menu.append(new MenuItem({
|
|
label: INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.accesskey"),
|
|
submenu: this._getAttributesSubmenu(isEditableElement),
|
|
}));
|
|
|
|
menu.append(new MenuItem({
|
|
type: "separator",
|
|
}));
|
|
|
|
// Set the pseudo classes
|
|
for (const name of ["hover", "active", "focus", "focus-within"]) {
|
|
const menuitem = new MenuItem({
|
|
id: "node-menu-pseudo-" + name,
|
|
label: name,
|
|
type: "checkbox",
|
|
click: () => this.inspector.togglePseudoClass(":" + name),
|
|
});
|
|
|
|
if (isSelectionElement) {
|
|
const checked = this.selection.nodeFront.hasPseudoClassLock(":" + name);
|
|
menuitem.checked = checked;
|
|
} else {
|
|
menuitem.disabled = true;
|
|
}
|
|
|
|
menu.append(menuitem);
|
|
}
|
|
|
|
menu.append(new MenuItem({
|
|
type: "separator",
|
|
}));
|
|
|
|
menu.append(new MenuItem({
|
|
label: INSPECTOR_L10N.getStr("inspectorCopyHTMLSubmenu.label"),
|
|
submenu: this._getCopySubmenu(markupContainer, isSelectionElement),
|
|
}));
|
|
|
|
menu.append(new MenuItem({
|
|
label: INSPECTOR_L10N.getStr("inspectorPasteHTMLSubmenu.label"),
|
|
submenu: this._getPasteSubmenu(isEditableElement),
|
|
}));
|
|
|
|
menu.append(new MenuItem({
|
|
type: "separator",
|
|
}));
|
|
|
|
const isNodeWithChildren = this.selection.isNode() &&
|
|
markupContainer.hasChildren;
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-expand",
|
|
label: INSPECTOR_L10N.getStr("inspectorExpandNode.label"),
|
|
disabled: !isNodeWithChildren,
|
|
click: () => this.markup.expandAll(this.selection.nodeFront),
|
|
}));
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-collapse",
|
|
label: INSPECTOR_L10N.getStr("inspectorCollapseAll.label"),
|
|
disabled: !isNodeWithChildren || !markupContainer.expanded,
|
|
click: () => this.markup.collapseAll(this.selection.nodeFront),
|
|
}));
|
|
|
|
menu.append(new MenuItem({
|
|
type: "separator",
|
|
}));
|
|
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-scrollnodeintoview",
|
|
label: INSPECTOR_L10N.getStr("inspectorScrollNodeIntoView.label"),
|
|
accesskey:
|
|
INSPECTOR_L10N.getStr("inspectorScrollNodeIntoView.accesskey"),
|
|
disabled: !isSelectionElement,
|
|
click: () => this.markup.scrollNodeIntoView(),
|
|
}));
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-screenshotnode",
|
|
label: INSPECTOR_L10N.getStr("inspectorScreenshotNode.label"),
|
|
disabled: !isScreenshotable,
|
|
click: () => this.inspector.screenshotNode().catch(console.error),
|
|
}));
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-useinconsole",
|
|
label: INSPECTOR_L10N.getStr("inspectorUseInConsole.label"),
|
|
click: () => this._useInConsole(),
|
|
}));
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-showdomproperties",
|
|
label: INSPECTOR_L10N.getStr("inspectorShowDOMProperties.label"),
|
|
click: () => this._showDOMProperties(),
|
|
}));
|
|
|
|
if (this.selection.nodeFront.customElementLocation) {
|
|
menu.append(new MenuItem({
|
|
type: "separator",
|
|
}));
|
|
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-jumptodefinition",
|
|
label: INSPECTOR_L10N.getStr("inspectorCustomElementDefinition.label"),
|
|
click: () => this._jumpToCustomElementDefinition(),
|
|
}));
|
|
}
|
|
|
|
this._buildA11YMenuItem(menu);
|
|
|
|
const nodeLinkMenuItems = this._getNodeLinkMenuItems();
|
|
if (nodeLinkMenuItems.filter(item => item.visible).length > 0) {
|
|
menu.append(new MenuItem({
|
|
id: "node-menu-link-separator",
|
|
type: "separator",
|
|
}));
|
|
}
|
|
|
|
for (const menuitem of nodeLinkMenuItems) {
|
|
menu.append(menuitem);
|
|
}
|
|
|
|
menu.popup(screenX, screenY, this.toolbox);
|
|
return menu;
|
|
}
|
|
|
|
async _updateA11YMenuItem(menuItem) {
|
|
const hasMethod = await this.target.actorHasMethod("domwalker",
|
|
"hasAccessibilityProperties");
|
|
if (!hasMethod) {
|
|
return;
|
|
}
|
|
|
|
const hasA11YProps = await this.walker.hasAccessibilityProperties(
|
|
this.selection.nodeFront);
|
|
if (hasA11YProps) {
|
|
this.toolbox.doc.getElementById(menuItem.id).disabled = menuItem.disabled = false;
|
|
}
|
|
|
|
this.inspector.emit("node-menu-updated");
|
|
}
|
|
}
|
|
|
|
module.exports = MarkupContextMenu;
|