123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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 {PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
- const Services = require("Services");
- const {Task} = require("devtools/shared/task");
- const Menu = require("devtools/client/framework/menu");
- const MenuItem = require("devtools/client/framework/menu-item");
- const {
- VIEW_NODE_SELECTOR_TYPE,
- VIEW_NODE_PROPERTY_TYPE,
- VIEW_NODE_VALUE_TYPE,
- VIEW_NODE_IMAGE_URL_TYPE,
- VIEW_NODE_LOCATION_TYPE,
- } = require("devtools/client/inspector/shared/node-types");
- const clipboardHelper = require("devtools/shared/platform/clipboard");
- const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
- const {LocalizationHelper} = require("devtools/shared/l10n");
- const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
- const PREF_ENABLE_MDN_DOCS_TOOLTIP =
- "devtools.inspector.mdnDocsTooltip.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, options) {
- this.view = view;
- this.inspector = this.view.inspector;
- this.styleDocument = this.view.styleDocument;
- this.styleWindow = this.view.styleWindow;
- this.isRuleView = options.isRuleView;
- this._onAddNewRule = this._onAddNewRule.bind(this);
- 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._onCopyPropertyDeclaration = this._onCopyPropertyDeclaration.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._onShowMdnDocs = this._onShowMdnDocs.bind(this);
- this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
- }
- module.exports = StyleInspectorMenu;
- StyleInspectorMenu.prototype = {
- /**
- * Display the style inspector context menu
- */
- show: function (event) {
- try {
- this._openMenu({
- target: event.explicitOriginalTarget,
- screenX: event.screenX,
- screenY: event.screenY,
- });
- } catch (e) {
- console.error(e);
- }
- },
- _openMenu: function ({ target, screenX = 0, screenY = 0 } = { }) {
- // In the sidebar we do not have this.styleDocument.popupNode
- // so we need to save the node ourselves.
- this.styleDocument.popupNode = target;
- this.styleWindow.focus();
- let menu = new Menu();
- let 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(),
- });
- let menuitemCopyLocation = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyLocation"),
- click: () => {
- this._onCopyLocation();
- },
- visible: false,
- });
- let menuitemCopyRule = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyRule"),
- click: () => {
- this._onCopyRule();
- },
- visible: this.isRuleView,
- });
- let copyColorAccessKey = "styleinspector.contextmenu.copyColor.accessKey";
- let menuitemCopyColor = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyColor"),
- accesskey: STYLE_INSPECTOR_L10N.getStr(copyColorAccessKey),
- click: () => {
- this._onCopyColor();
- },
- visible: this._isColorPopup(),
- });
- let copyUrlAccessKey = "styleinspector.contextmenu.copyUrl.accessKey";
- let menuitemCopyUrl = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyUrl"),
- accesskey: STYLE_INSPECTOR_L10N.getStr(copyUrlAccessKey),
- click: () => {
- this._onCopyUrl();
- },
- visible: this._isImageUrl(),
- });
- let copyImageAccessKey = "styleinspector.contextmenu.copyImageDataUrl.accessKey";
- let menuitemCopyImageDataUrl = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyImageDataUrl"),
- accesskey: STYLE_INSPECTOR_L10N.getStr(copyImageAccessKey),
- click: () => {
- this._onCopyImageDataUrl();
- },
- visible: this._isImageUrl(),
- });
- let copyPropDeclarationLabel = "styleinspector.contextmenu.copyPropertyDeclaration";
- let menuitemCopyPropertyDeclaration = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr(copyPropDeclarationLabel),
- click: () => {
- this._onCopyPropertyDeclaration();
- },
- visible: false,
- });
- let menuitemCopyPropertyName = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyPropertyName"),
- click: () => {
- this._onCopyPropertyName();
- },
- visible: false,
- });
- let menuitemCopyPropertyValue = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyPropertyValue"),
- click: () => {
- this._onCopyPropertyValue();
- },
- visible: false,
- });
- let 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 :
- menuitemCopyPropertyDeclaration.visible = true;
- menuitemCopyPropertyName.visible = true;
- break;
- case VIEW_NODE_VALUE_TYPE :
- menuitemCopyPropertyDeclaration.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(menuitemCopyPropertyDeclaration);
- menu.append(menuitemCopyPropertyName);
- menu.append(menuitemCopyPropertyValue);
- menu.append(menuitemCopySelector);
- menu.append(new MenuItem({
- type: "separator",
- }));
- // Select All
- let selectAllAccessKey = "styleinspector.contextmenu.selectAll.accessKey";
- let 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
- let addRuleAccessKey = "styleinspector.contextmenu.addNewRule.accessKey";
- let menuitemAddRule = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.addNewRule"),
- accesskey: STYLE_INSPECTOR_L10N.getStr(addRuleAccessKey),
- click: () => {
- this._onAddNewRule();
- },
- visible: this.isRuleView,
- disabled: !this.isRuleView ||
- this.inspector.selection.isAnonymousNode(),
- });
- menu.append(menuitemAddRule);
- // Show MDN Docs
- let mdnDocsAccessKey = "styleinspector.contextmenu.showMdnDocs.accessKey";
- let menuitemShowMdnDocs = new MenuItem({
- label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs"),
- accesskey: STYLE_INSPECTOR_L10N.getStr(mdnDocsAccessKey),
- click: () => {
- this._onShowMdnDocs();
- },
- visible: (Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP) &&
- this._isPropertyName()),
- });
- menu.append(menuitemShowMdnDocs);
- // Show Original Sources
- let sourcesAccessKey = "styleinspector.contextmenu.toggleOrigSources.accessKey";
- let 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);
- return menu;
- },
- _hasTextSelected: function () {
- let hasTextSelected;
- let selection = this.styleWindow.getSelection();
- let node = this._getClickedNode();
- if (node.nodeName == "input" || node.nodeName == "textarea") {
- let { 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: function () {
- let 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: function () {
- this._colorToCopy = "";
- let container = this._getClickedNode();
- if (!container) {
- return false;
- }
- let isColorNode = el => el.dataset && "color" in el.dataset;
- while (!isColorNode(container)) {
- container = container.parentNode;
- if (!container) {
- return false;
- }
- }
- this._colorToCopy = container.dataset.color;
- return true;
- },
- _isPropertyName: function () {
- let nodeInfo = this._getClickedNodeInfo();
- if (!nodeInfo) {
- return false;
- }
- return nodeInfo.type == VIEW_NODE_PROPERTY_TYPE;
- },
- /**
- * Check if the current node (clicked node) is an image URL
- *
- * @return {Boolean} true if the node is an image url
- */
- _isImageUrl: function () {
- let nodeInfo = this._getClickedNodeInfo();
- if (!nodeInfo) {
- return false;
- }
- return nodeInfo.type == VIEW_NODE_IMAGE_URL_TYPE;
- },
- /**
- * Get the DOM Node container for the current popupNode.
- * If popupNode is a textNode, return the parent node, otherwise return
- * popupNode itself.
- *
- * @return {DOMNode}
- */
- _getClickedNode: function () {
- let container = null;
- let node = this.styleDocument.popupNode;
- if (node) {
- let isTextNode = node.nodeType == node.TEXT_NODE;
- container = isTextNode ? node.parentElement : node;
- }
- return container;
- },
- /**
- * Select all text.
- */
- _onSelectAll: function () {
- let selection = this.styleWindow.getSelection();
- selection.selectAllChildren(this.view.element);
- },
- /**
- * Copy the most recently selected color value to clipboard.
- */
- _onCopy: function () {
- this.view.copySelection(this.styleDocument.popupNode);
- },
- /**
- * Copy the most recently selected color value to clipboard.
- */
- _onCopyColor: function () {
- clipboardHelper.copyString(this._colorToCopy);
- },
- /*
- * Retrieve the url for the selected image and copy it to the clipboard
- */
- _onCopyUrl: function () {
- 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
- */
- _onCopyImageDataUrl: Task.async(function* () {
- if (!this._clickedNodeInfo) {
- return;
- }
- let message;
- try {
- let inspectorFront = this.inspector.inspector;
- let imageUrl = this._clickedNodeInfo.value.url;
- let data = yield inspectorFront.getImageDataFromURL(imageUrl);
- message = yield data.data.string();
- } catch (e) {
- message =
- STYLE_INSPECTOR_L10N.getStr("styleinspector.copyImageDataUrlError");
- }
- clipboardHelper.copyString(message);
- }),
- /**
- * Show docs from MDN for a CSS property.
- */
- _onShowMdnDocs: function () {
- let cssPropertyName = this.styleDocument.popupNode.textContent;
- let anchor = this.styleDocument.popupNode.parentNode;
- let cssDocsTooltip = this.view.tooltips.cssDocs;
- cssDocsTooltip.show(anchor, cssPropertyName);
- },
- /**
- * Add a new rule to the current element.
- */
- _onAddNewRule: function () {
- this.view._onAddRule();
- },
- /**
- * Copy the rule source location of the current clicked node.
- */
- _onCopyLocation: function () {
- if (!this._clickedNodeInfo) {
- return;
- }
- clipboardHelper.copyString(this._clickedNodeInfo.value);
- },
- /**
- * Copy the rule property declaration of the current clicked node.
- */
- _onCopyPropertyDeclaration: function () {
- if (!this._clickedNodeInfo) {
- return;
- }
- let textProp = this._clickedNodeInfo.value.textProperty;
- clipboardHelper.copyString(textProp.stringifyProperty());
- },
- /**
- * Copy the rule property name of the current clicked node.
- */
- _onCopyPropertyName: function () {
- if (!this._clickedNodeInfo) {
- return;
- }
- clipboardHelper.copyString(this._clickedNodeInfo.value.property);
- },
- /**
- * Copy the rule property value of the current clicked node.
- */
- _onCopyPropertyValue: function () {
- if (!this._clickedNodeInfo) {
- return;
- }
- clipboardHelper.copyString(this._clickedNodeInfo.value.value);
- },
- /**
- * Copy the rule of the current clicked node.
- */
- _onCopyRule: function () {
- let ruleEditor =
- this.styleDocument.popupNode.parentNode.offsetParent._ruleEditor;
- let rule = ruleEditor.rule;
- clipboardHelper.copyString(rule.stringifyRule());
- },
- /**
- * Copy the rule selector of the current clicked node.
- */
- _onCopySelector: function () {
- if (!this._clickedNodeInfo) {
- return;
- }
- clipboardHelper.copyString(this._clickedNodeInfo.value);
- },
- /**
- * Toggle the original sources pref.
- */
- _onToggleOrigSources: function () {
- let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
- Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
- },
- destroy: function () {
- this.popupNode = null;
- this.styleDocument.popupNode = null;
- this.view = null;
- this.inspector = null;
- this.styleDocument = null;
- this.styleWindow = null;
- }
- };
|