diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/inspector/shared/style-inspector-menu.js | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/inspector/shared/style-inspector-menu.js')
-rw-r--r-- | devtools/client/inspector/shared/style-inspector-menu.js | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/devtools/client/inspector/shared/style-inspector-menu.js b/devtools/client/inspector/shared/style-inspector-menu.js new file mode 100644 index 0000000000..e8eae77dae --- /dev/null +++ b/devtools/client/inspector/shared/style-inspector-menu.js @@ -0,0 +1,502 @@ +/* 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 { + VIEW_NODE_SELECTOR_TYPE, + VIEW_NODE_PROPERTY_TYPE, + VIEW_NODE_VALUE_TYPE, + VIEW_NODE_IMAGE_URL_TYPE, + VIEW_NODE_LOCATION_TYPE, +} = require("resource://devtools/client/inspector/shared/node-types.js"); + +loader.lazyRequireGetter( + this, + "Menu", + "resource://devtools/client/framework/menu.js" +); +loader.lazyRequireGetter( + this, + "MenuItem", + "resource://devtools/client/framework/menu-item.js" +); +loader.lazyRequireGetter( + this, + "getRuleFromNode", + "resource://devtools/client/inspector/rules/utils/utils.js", + true +); +loader.lazyRequireGetter( + this, + "clipboardHelper", + "resource://devtools/shared/platform/clipboard.js" +); + +const STYLE_INSPECTOR_PROPERTIES = + "devtools/shared/locales/styleinspector.properties"; +const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); +const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES); + +const PREF_ORIG_SOURCES = "devtools.source-map.client-service.enabled"; + +/** + * Style inspector context menu + * + * @param {RuleView|ComputedView} view + * RuleView or ComputedView instance controlling this menu + * @param {Object} options + * Option menu configuration + */ +function StyleInspectorMenu(view, { isRuleView = false } = {}) { + this.view = view; + this.inspector = this.view.inspector; + this.styleWindow = this.view.styleWindow || this.view.doc.defaultView; + this.isRuleView = isRuleView; + + this._onCopy = this._onCopy.bind(this); + this._onCopyColor = this._onCopyColor.bind(this); + this._onCopyImageDataUrl = this._onCopyImageDataUrl.bind(this); + this._onCopyLocation = this._onCopyLocation.bind(this); + this._onCopyDeclaration = this._onCopyDeclaration.bind(this); + this._onCopyPropertyName = this._onCopyPropertyName.bind(this); + this._onCopyPropertyValue = this._onCopyPropertyValue.bind(this); + this._onCopyRule = this._onCopyRule.bind(this); + this._onCopySelector = this._onCopySelector.bind(this); + this._onCopyUrl = this._onCopyUrl.bind(this); + this._onSelectAll = this._onSelectAll.bind(this); + this._onToggleOrigSources = this._onToggleOrigSources.bind(this); +} + +module.exports = StyleInspectorMenu; + +StyleInspectorMenu.prototype = { + /** + * Display the style inspector context menu + */ + show(event) { + try { + this._openMenu({ + target: event.target, + screenX: event.screenX, + screenY: event.screenY, + }); + } catch (e) { + console.error(e); + } + }, + + _openMenu({ target, screenX = 0, screenY = 0 } = {}) { + this.currentTarget = target; + this.styleWindow.focus(); + + const menu = new Menu(); + + const menuitemCopy = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy"), + accesskey: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.copy.accessKey" + ), + click: () => { + this._onCopy(); + }, + disabled: !this._hasTextSelected(), + }); + const menuitemCopyLocation = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.copyLocation" + ), + click: () => { + this._onCopyLocation(); + }, + visible: false, + }); + const menuitemCopyRule = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyRule"), + click: () => { + this._onCopyRule(); + }, + visible: this.isRuleView, + }); + const copyColorAccessKey = "styleinspector.contextmenu.copyColor.accessKey"; + const menuitemCopyColor = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.copyColor" + ), + accesskey: STYLE_INSPECTOR_L10N.getStr(copyColorAccessKey), + click: () => { + this._onCopyColor(); + }, + visible: this._isColorPopup(), + }); + const copyUrlAccessKey = "styleinspector.contextmenu.copyUrl.accessKey"; + const menuitemCopyUrl = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyUrl"), + accesskey: STYLE_INSPECTOR_L10N.getStr(copyUrlAccessKey), + click: () => { + this._onCopyUrl(); + }, + visible: this._isImageUrl(), + }); + const copyImageAccessKey = + "styleinspector.contextmenu.copyImageDataUrl.accessKey"; + const menuitemCopyImageDataUrl = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.copyImageDataUrl" + ), + accesskey: STYLE_INSPECTOR_L10N.getStr(copyImageAccessKey), + click: () => { + this._onCopyImageDataUrl(); + }, + visible: this._isImageUrl(), + }); + const copyDeclarationLabel = "styleinspector.contextmenu.copyDeclaration"; + const menuitemCopyDeclaration = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr(copyDeclarationLabel), + click: () => { + this._onCopyDeclaration(); + }, + visible: false, + }); + const menuitemCopyPropertyName = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.copyPropertyName" + ), + click: () => { + this._onCopyPropertyName(); + }, + visible: false, + }); + const menuitemCopyPropertyValue = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.copyPropertyValue" + ), + click: () => { + this._onCopyPropertyValue(); + }, + visible: false, + }); + const menuitemCopySelector = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.copySelector" + ), + click: () => { + this._onCopySelector(); + }, + visible: false, + }); + + this._clickedNodeInfo = this._getClickedNodeInfo(); + if (this.isRuleView && this._clickedNodeInfo) { + switch (this._clickedNodeInfo.type) { + case VIEW_NODE_PROPERTY_TYPE: + menuitemCopyDeclaration.visible = true; + menuitemCopyPropertyName.visible = true; + break; + case VIEW_NODE_VALUE_TYPE: + menuitemCopyDeclaration.visible = true; + menuitemCopyPropertyValue.visible = true; + break; + case VIEW_NODE_SELECTOR_TYPE: + menuitemCopySelector.visible = true; + break; + case VIEW_NODE_LOCATION_TYPE: + menuitemCopyLocation.visible = true; + break; + } + } + + menu.append(menuitemCopy); + menu.append(menuitemCopyLocation); + menu.append(menuitemCopyRule); + menu.append(menuitemCopyColor); + menu.append(menuitemCopyUrl); + menu.append(menuitemCopyImageDataUrl); + menu.append(menuitemCopyDeclaration); + menu.append(menuitemCopyPropertyName); + menu.append(menuitemCopyPropertyValue); + menu.append(menuitemCopySelector); + + menu.append( + new MenuItem({ + type: "separator", + }) + ); + + // Select All + const selectAllAccessKey = "styleinspector.contextmenu.selectAll.accessKey"; + const menuitemSelectAll = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.selectAll" + ), + accesskey: STYLE_INSPECTOR_L10N.getStr(selectAllAccessKey), + click: () => { + this._onSelectAll(); + }, + }); + menu.append(menuitemSelectAll); + + menu.append( + new MenuItem({ + type: "separator", + }) + ); + + // Add new rule + const addRuleAccessKey = "styleinspector.contextmenu.addNewRule.accessKey"; + const menuitemAddRule = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.addNewRule" + ), + accesskey: STYLE_INSPECTOR_L10N.getStr(addRuleAccessKey), + click: () => this.view._onAddRule(), + visible: this.isRuleView, + disabled: !this.isRuleView || this.inspector.selection.isAnonymousNode(), + }); + menu.append(menuitemAddRule); + + // Show Original Sources + const sourcesAccessKey = + "styleinspector.contextmenu.toggleOrigSources.accessKey"; + const menuitemSources = new MenuItem({ + label: STYLE_INSPECTOR_L10N.getStr( + "styleinspector.contextmenu.toggleOrigSources" + ), + accesskey: STYLE_INSPECTOR_L10N.getStr(sourcesAccessKey), + click: () => { + this._onToggleOrigSources(); + }, + type: "checkbox", + checked: Services.prefs.getBoolPref(PREF_ORIG_SOURCES), + }); + menu.append(menuitemSources); + + menu.popup(screenX, screenY, this.inspector.toolbox.doc); + return menu; + }, + + _hasTextSelected() { + let hasTextSelected; + const selection = this.styleWindow.getSelection(); + + const node = this._getClickedNode(); + if (node.nodeName == "input" || node.nodeName == "textarea") { + const { selectionStart, selectionEnd } = node; + hasTextSelected = + isFinite(selectionStart) && + isFinite(selectionEnd) && + selectionStart !== selectionEnd; + } else { + hasTextSelected = selection.toString() && !selection.isCollapsed; + } + + return hasTextSelected; + }, + + /** + * Get the type of the currently clicked node + */ + _getClickedNodeInfo() { + const node = this._getClickedNode(); + return this.view.getNodeInfo(node); + }, + + /** + * A helper that determines if the popup was opened with a click to a color + * value and saves the color to this._colorToCopy. + * + * @return {Boolean} + * true if click on color opened the popup, false otherwise. + */ + _isColorPopup() { + this._colorToCopy = ""; + + const container = this._getClickedNode(); + if (!container) { + return false; + } + + const colorNode = container.closest("[data-color]"); + if (!colorNode) { + return false; + } + + this._colorToCopy = colorNode.dataset.color; + return true; + }, + + /** + * Check if the current node (clicked node) is an image URL + * + * @return {Boolean} true if the node is an image url + */ + _isImageUrl() { + const nodeInfo = this._getClickedNodeInfo(); + if (!nodeInfo) { + return false; + } + return nodeInfo.type == VIEW_NODE_IMAGE_URL_TYPE; + }, + + /** + * Get the DOM Node container for the current target node. + * If the target node is a text node, return the parent node, otherwise return + * the target node itself. + * + * @return {DOMNode} + */ + _getClickedNode() { + const node = this.currentTarget; + + if (!node) { + return null; + } + + return node.nodeType === node.TEXT_NODE ? node.parentElement : node; + }, + + /** + * Select all text. + */ + _onSelectAll() { + const selection = this.styleWindow.getSelection(); + + if (this.isRuleView) { + selection.selectAllChildren( + this.currentTarget.closest("#ruleview-container-focusable") + ); + } else { + selection.selectAllChildren(this.view.element); + } + }, + + /** + * Copy the most recently selected color value to clipboard. + */ + _onCopy() { + this.view.copySelection(this.currentTarget); + }, + + /** + * Copy the most recently selected color value to clipboard. + */ + _onCopyColor() { + clipboardHelper.copyString(this._colorToCopy); + }, + + /* + * Retrieve the url for the selected image and copy it to the clipboard + */ + _onCopyUrl() { + if (!this._clickedNodeInfo) { + return; + } + + clipboardHelper.copyString(this._clickedNodeInfo.value.url); + }, + + /** + * Retrieve the image data for the selected image url and copy it to the + * clipboard + */ + async _onCopyImageDataUrl() { + if (!this._clickedNodeInfo) { + return; + } + + let message; + try { + const inspectorFront = this.inspector.inspectorFront; + const imageUrl = this._clickedNodeInfo.value.url; + const data = await inspectorFront.getImageDataFromURL(imageUrl); + message = await data.data.string(); + } catch (e) { + message = STYLE_INSPECTOR_L10N.getStr( + "styleinspector.copyImageDataUrlError" + ); + } + + clipboardHelper.copyString(message); + }, + + /** + * Copy the rule source location of the current clicked node. + */ + _onCopyLocation() { + if (!this._clickedNodeInfo) { + return; + } + + clipboardHelper.copyString(this._clickedNodeInfo.value); + }, + + /** + * Copy the CSS declaration of the current clicked node. + */ + _onCopyDeclaration() { + if (!this._clickedNodeInfo) { + return; + } + + const textProp = this._clickedNodeInfo.value.textProperty; + clipboardHelper.copyString(textProp.stringifyProperty()); + }, + + /** + * Copy the rule property name of the current clicked node. + */ + _onCopyPropertyName() { + if (!this._clickedNodeInfo) { + return; + } + + clipboardHelper.copyString(this._clickedNodeInfo.value.property); + }, + + /** + * Copy the rule property value of the current clicked node. + */ + _onCopyPropertyValue() { + if (!this._clickedNodeInfo) { + return; + } + + clipboardHelper.copyString(this._clickedNodeInfo.value.value); + }, + + /** + * Copy the rule of the current clicked node. + */ + _onCopyRule() { + const node = this._getClickedNode(); + const rule = getRuleFromNode(node, this.view._elementStyle); + clipboardHelper.copyString(rule.stringifyRule()); + }, + + /** + * Copy the rule selector of the current clicked node. + */ + _onCopySelector() { + if (!this._clickedNodeInfo) { + return; + } + + clipboardHelper.copyString(this._clickedNodeInfo.value); + }, + + /** + * Toggle the original sources pref. + */ + _onToggleOrigSources() { + const isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES); + Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled); + }, + + destroy() { + this.currentTarget = null; + this.view = null; + this.inspector = null; + this.styleWindow = null; + }, +}; |