forked from mirrors/gecko-dev
Differential Revision: https://phabricator.services.mozilla.com/D20554 --HG-- rename : devtools/client/inspector/inspector.js => devtools/client/inspector/markup/markup-context-menu.js
776 lines
25 KiB
JavaScript
776 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, "copyLongString", "devtools/client/inspector/shared/utils", true);
|
|
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;
|
|
}
|
|
|
|
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() {
|
|
if (!this.selection.isNode()) {
|
|
return;
|
|
}
|
|
|
|
copyLongString(this.walker.innerHTML(this.selection.nodeFront));
|
|
}
|
|
|
|
/**
|
|
* Copy the outerHTML of the selected Node to the clipboard.
|
|
*/
|
|
_copyOuterHTML() {
|
|
if (!this.selection.isNode()) {
|
|
return;
|
|
}
|
|
|
|
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 panel = this.toolbox.getPanel("webconsole");
|
|
const jsterm = panel.hud.jsterm;
|
|
|
|
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,
|
|
};
|
|
jsterm.requestEvaluation(evalString, options).then((res) => {
|
|
jsterm.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;
|