Web Inspector: consolidate DOMTreeElement contextmenu items into submenus
authorwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 2 Nov 2017 03:34:44 +0000 (03:34 +0000)
committerwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 2 Nov 2017 03:34:44 +0000 (03:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=178996

Reviewed by Joseph Pecoraro.

* Localizations/en.lproj/localizedStrings.js:

* UserInterface/Views/ContextMenu.js:
(WI.ContextSubMenuItem.prototype.appendItem):
(WI.ContextSubMenuItem.prototype.appendSubMenuItem):
(WI.ContextSubMenuItem.prototype.appendCheckboxItem):
(WI.ContextSubMenuItem.prototype.pushItem):
(WI.ContextSubMenuItem.prototype._pushItem): Deleted.
Made `pushItem` public so that it is possible to create a ContextMenuItem without
immediately adding it to the ContextMenu.

(WI.ContextSubMenuItem.prototype._buildDescriptor):
(WI.ContextMenu.prototype._buildDescriptor):
Filter out submenus that have no items.

* UserInterface/Views/ContextMenuUtilities.js:
(WI.appendContextMenuItemsForDOMNode):
* UserInterface/Views/DOMTreeElement.js:
(WI.DOMTreeElement.prototype.toggleElementVisibility):
(WI.DOMTreeElement.prototype._populateTagContextMenu):
(WI.DOMTreeElement.prototype._populateTextContextMenu):
(WI.DOMTreeElement.prototype._populateNodeContextMenu):
(WI.DOMTreeElement.prototype._populateForcedPseudoStateItems): Deleted.
Add relevant items to the provided submenus and reorder the entire contextmenu for better
readability and consistency.

* UserInterface/Views/DOMTreeOutline.js:
(WI.DOMTreeOutline.prototype.populateContextMenu):
Create submenus for Add, Edit, Copy, and Delete, and pass them to the DOMTreeElement
"populate*ContextMenu" functions so that they can add items to each submenu.

(WI.DOMTreeOutline.prototype._hideElement):
Move to DOMTreeElement so that it can be used by a contextmenu item.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@224314 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Views/ContextMenu.js
Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js

index 2298866..c2a1e84 100644 (file)
@@ -1,3 +1,44 @@
+2017-11-01  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: consolidate DOMTreeElement contextmenu items into submenus
+        https://bugs.webkit.org/show_bug.cgi?id=178996
+
+        Reviewed by Joseph Pecoraro.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+        * UserInterface/Views/ContextMenu.js:
+        (WI.ContextSubMenuItem.prototype.appendItem):
+        (WI.ContextSubMenuItem.prototype.appendSubMenuItem):
+        (WI.ContextSubMenuItem.prototype.appendCheckboxItem):
+        (WI.ContextSubMenuItem.prototype.pushItem):
+        (WI.ContextSubMenuItem.prototype._pushItem): Deleted.
+        Made `pushItem` public so that it is possible to create a ContextMenuItem without
+        immediately adding it to the ContextMenu.
+
+        (WI.ContextSubMenuItem.prototype._buildDescriptor):
+        (WI.ContextMenu.prototype._buildDescriptor):
+        Filter out submenus that have no items.
+
+        * UserInterface/Views/ContextMenuUtilities.js:
+        (WI.appendContextMenuItemsForDOMNode):
+        * UserInterface/Views/DOMTreeElement.js:
+        (WI.DOMTreeElement.prototype.toggleElementVisibility):
+        (WI.DOMTreeElement.prototype._populateTagContextMenu):
+        (WI.DOMTreeElement.prototype._populateTextContextMenu):
+        (WI.DOMTreeElement.prototype._populateNodeContextMenu):
+        (WI.DOMTreeElement.prototype._populateForcedPseudoStateItems): Deleted.
+        Add relevant items to the provided submenus and reorder the entire contextmenu for better
+        readability and consistency.
+
+        * UserInterface/Views/DOMTreeOutline.js:
+        (WI.DOMTreeOutline.prototype.populateContextMenu):
+        Create submenus for Add, Edit, Copy, and Delete, and pass them to the DOMTreeElement
+        "populate*ContextMenu" functions so that they can add items to each submenu.
+
+        (WI.DOMTreeOutline.prototype._hideElement):
+        Move to DOMTreeElement so that it can be used by a contextmenu item.
+
 2017-11-01  Ross Kirsling  <ross.kirsling@sony.com>
 
         Web Inspector: Improve UX of Layers tab visualization
index 9c0ee07..2b6f5aa 100644 (file)
@@ -64,9 +64,9 @@ localizedStrings["2D"] = "2D";
 localizedStrings["Accessibility"] = "Accessibility";
 localizedStrings["Action"] = "Action";
 localizedStrings["Activity Viewer"] = "Activity Viewer";
+localizedStrings["Add"] = "Add";
 localizedStrings["Add %s Rule"] = "Add %s Rule";
 localizedStrings["Add Action"] = "Add Action";
-localizedStrings["Add Attribute"] = "Add Attribute";
 localizedStrings["Add Breakpoint"] = "Add Breakpoint";
 localizedStrings["Add New"] = "Add New";
 localizedStrings["Add New Class"] = "Add New Class";
@@ -169,7 +169,7 @@ localizedStrings["Canvas Overview"] = "Canvas Overview";
 localizedStrings["Canvases"] = "Canvases";
 localizedStrings["Cap"] = "Cap";
 localizedStrings["Caps"] = "Caps";
-localizedStrings["Capture Element Screenshot"] = "Capture Element Screenshot";
+localizedStrings["Capture Screenshot"] = "Capture Screenshot";
 localizedStrings["Capturing"] = "Capturing";
 localizedStrings["Catch Variables"] = "Catch Variables";
 localizedStrings["Categories"] = "Categories";
@@ -245,15 +245,14 @@ localizedStrings["Continue script execution (%s or %s)"] = "Continue script exec
 localizedStrings["Continue to Here"] = "Continue to Here";
 localizedStrings["Controls"] = "Controls";
 localizedStrings["Cookies"] = "Cookies";
+localizedStrings["Copy"] = "Copy";
 localizedStrings["Copy Action"] = "Copy Action";
 localizedStrings["Copy Link Address"] = "Copy Link Address";
 localizedStrings["Copy Path to Property"] = "Copy Path to Property";
 localizedStrings["Copy Row"] = "Copy Row";
 localizedStrings["Copy Rule"] = "Copy Rule";
 localizedStrings["Copy Selected"] = "Copy Selected";
-localizedStrings["Copy Selector Path"] = "Copy Selector Path";
 localizedStrings["Copy Table"] = "Copy Table";
-localizedStrings["Copy XPath"] = "Copy XPath";
 localizedStrings["Copy as HTML"] = "Copy as HTML";
 localizedStrings["Copy as cURL"] = "Copy as cURL";
 localizedStrings["Could not fetch properties. Object may no longer exist."] = "Could not fetch properties. Object may no longer exist.";
@@ -289,7 +288,6 @@ localizedStrings["Delay"] = "Delay";
 localizedStrings["Delete"] = "Delete";
 localizedStrings["Delete Breakpoint"] = "Delete Breakpoint";
 localizedStrings["Delete Breakpoints"] = "Delete Breakpoints";
-localizedStrings["Delete Node"] = "Delete Node";
 localizedStrings["Detach into separate window"] = "Detach into separate window";
 localizedStrings["Detached"] = "Detached";
 localizedStrings["Details"] = "Details";
@@ -322,10 +320,7 @@ localizedStrings["Dynamically calculated for the parent element"] = "Dynamically
 localizedStrings["Dynamically calculated for the selected element"] = "Dynamically calculated for the selected element";
 localizedStrings["Dynamically calculated for the selected element and did not match"] = "Dynamically calculated for the selected element and did not match";
 localizedStrings["Edit"] = "Edit";
-localizedStrings["Edit Attribute"] = "Edit Attribute";
 localizedStrings["Edit Breakpoint…"] = "Edit Breakpoint…";
-localizedStrings["Edit Text"] = "Edit Text";
-localizedStrings["Edit as HTML"] = "Edit as HTML";
 localizedStrings["Edit configuration"] = "Edit configuration";
 localizedStrings["Edit custom gradient"] = "Edit custom gradient";
 localizedStrings["Edit “%s”"] = "Edit “%s”";
@@ -458,6 +453,7 @@ localizedStrings["Group by Event"] = "Group by Event";
 localizedStrings["Group by Node"] = "Group by Node";
 localizedStrings["Grouping Method"] = "Grouping Method";
 localizedStrings["Grow"] = "Grow";
+localizedStrings["HTML"] = "HTML";
 localizedStrings["HTML Attributes"] = "HTML Attributes";
 localizedStrings["HTTP"] = "HTTP";
 localizedStrings["Headers"] = "Headers";
@@ -816,6 +812,7 @@ localizedStrings["Selected Node"] = "Selected Node";
 localizedStrings["Selected Symbol"] = "Selected Symbol";
 localizedStrings["Selected Value"] = "Selected Value";
 localizedStrings["Selected WebSocket"] = "Selected WebSocket";
+localizedStrings["Selector Path"] = "Selector Path";
 localizedStrings["Self"] = "Self";
 localizedStrings["Self Size"] = "Self Size";
 localizedStrings["Self Time"] = "Self Time";
@@ -920,6 +917,7 @@ localizedStrings["Summary"] = "Summary";
 localizedStrings["TCP"] = "TCP";
 localizedStrings["Tab width:"] = "Tab width:";
 localizedStrings["Tabs"] = "Tabs";
+localizedStrings["Tag"] = "Tag";
 localizedStrings["Take snapshot"] = "Take snapshot";
 localizedStrings["Template Content"] = "Template Content";
 localizedStrings["Text"] = "Text";
@@ -953,6 +951,7 @@ localizedStrings["Timer Removed"] = "Timer Removed";
 localizedStrings["Timestamp \u2014 %s"] = "Timestamp \u2014 %s";
 localizedStrings["Timing"] = "Timing";
 localizedStrings["Toggle Classes"] = "Toggle Classes";
+localizedStrings["Toggle Visibility"] = "Toggle Visibility";
 localizedStrings["Top"] = "Top";
 localizedStrings["Top Functions"] = "Top Functions";
 localizedStrings["Total"] = "Total";
@@ -1028,6 +1027,7 @@ localizedStrings["X2"] = "X2";
 localizedStrings["XHR"] = "XHR";
 localizedStrings["XHR Breakpoints"] = "XHR Breakpoints";
 localizedStrings["XHRs"] = "XHRs";
+localizedStrings["XPath"] = "XPath";
 localizedStrings["Y"] = "Y";
 localizedStrings["Y1"] = "Y1";
 localizedStrings["Y2"] = "Y2";
index a08adde..47a6b68 100644 (file)
@@ -96,7 +96,7 @@ WI.ContextSubMenuItem = class ContextSubMenuItem extends WI.ContextMenuItem
     appendItem(label, handler, disabled)
     {
         let item = new WI.ContextMenuItem(this._contextMenu, "item", label, disabled);
-        this._pushItem(item);
+        this.pushItem(item);
         this._contextMenu._setHandler(item.id(), handler);
         return item;
     }
@@ -104,14 +104,14 @@ WI.ContextSubMenuItem = class ContextSubMenuItem extends WI.ContextMenuItem
     appendSubMenuItem(label, disabled)
     {
         let item = new WI.ContextSubMenuItem(this._contextMenu, label, disabled);
-        this._pushItem(item);
+        this.pushItem(item);
         return item;
     }
 
     appendCheckboxItem(label, handler, checked, disabled)
     {
         let item = new WI.ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked);
-        this._pushItem(item);
+        this.pushItem(item);
         this._contextMenu._setHandler(item.id(), handler);
         return item;
     }
@@ -122,7 +122,7 @@ WI.ContextSubMenuItem = class ContextSubMenuItem extends WI.ContextMenuItem
             this._pendingSeparator = true;
     }
 
-    _pushItem(item)
+    pushItem(item)
     {
         if (this._pendingSeparator) {
             this._items.push(new WI.ContextMenuItem(this._contextMenu, "separator"));
@@ -136,9 +136,14 @@ WI.ContextSubMenuItem = class ContextSubMenuItem extends WI.ContextMenuItem
         return !this._items.length;
     }
 
+    // Private
+
     _buildDescriptor()
     {
-        let subItems = this._items.map((item) => item._buildDescriptor());
+        if (this.isEmpty())
+            return null;
+
+        let subItems = this._items.map((item) => item._buildDescriptor()).filter((item) => !!item);
         return {type: "subMenu", label: this._label, enabled: !this._disabled, subItems};
     }
 };
@@ -224,7 +229,7 @@ WI.ContextMenu = class ContextMenu extends WI.ContextSubMenuItem
 
     _buildDescriptor()
     {
-        return this._items.map((item) => item._buildDescriptor());
+        return this._items.map((item) => item._buildDescriptor()).filter((item) => !!item);
     }
 
     _itemSelected(id)
index 630fe1d..a089f5e 100644 (file)
@@ -109,31 +109,26 @@ WI.appendContextMenuItemsForDOMNode = function(contextMenu, domNode, options = {
     if (!(domNode instanceof WI.DOMNode))
         return;
 
-    let isElement = domNode.nodeType() === Node.ELEMENT_NODE;
-    if (isElement) {
-        contextMenu.appendItem(WI.UIString("Scroll Into View"), () => {
-            domNode.scrollIntoView();
-        });
-    }
-
-    contextMenu.appendSeparator();
+    let copySubMenu = options.copySubMenu || contextMenu.appendSubMenuItem(WI.UIString("Copy"));
 
+    let isElement = domNode.nodeType() === Node.ELEMENT_NODE;
     if (domNode.ownerDocument && isElement) {
-        contextMenu.appendItem(WI.UIString("Copy Selector Path"), () => {
+        copySubMenu.appendItem(WI.UIString("Selector Path"), () => {
             let cssPath = WI.cssPath(domNode);
             InspectorFrontendHost.copyText(cssPath);
         });
     }
 
     if (domNode.ownerDocument && !domNode.isPseudoElement()) {
-        contextMenu.appendItem(WI.UIString("Copy XPath"), () => {
+        copySubMenu.appendItem(WI.UIString("XPath"), () => {
             let xpath = WI.xpath(domNode);
             InspectorFrontendHost.copyText(xpath);
         });
     }
 
+    contextMenu.appendSeparator();
+
     if (domNode.isCustomElement()) {
-        contextMenu.appendSeparator();
         contextMenu.appendItem(WI.UIString("Jump to Definition"), () => {
             function didGetFunctionDetails(error, response) {
                 if (error)
@@ -164,6 +159,8 @@ WI.appendContextMenuItemsForDOMNode = function(contextMenu, domNode, options = {
                 remoteObject.release();
             });
         });
+
+        contextMenu.appendSeparator();
     }
 
     if (WI.domDebuggerManager.supported && isElement && !domNode.isPseudoElement() && domNode.ownerDocument) {
@@ -193,7 +190,7 @@ WI.appendContextMenuItemsForDOMNode = function(contextMenu, domNode, options = {
     }
 
     if (window.PageAgent) {
-        contextMenu.appendItem(WI.UIString("Capture Element Screenshot"), () => {
+        contextMenu.appendItem(WI.UIString("Capture Screenshot"), () => {
             PageAgent.snapshotNode(domNode.id, (error, dataURL) => {
                 if (error) {
                     const target = WI.mainTarget;
@@ -224,4 +221,12 @@ WI.appendContextMenuItemsForDOMNode = function(contextMenu, domNode, options = {
             });
         });
     }
+
+    if (isElement) {
+        contextMenu.appendItem(WI.UIString("Scroll Into View"), () => {
+            domNode.scrollIntoView();
+        });
+    }
+
+    contextMenu.appendSeparator();
 };
index 5a9235a..c160cc3 100644 (file)
@@ -283,6 +283,38 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
         return this.children[index];
     }
 
+    toggleElementVisibility()
+    {
+        let effectiveNode = this.representedObject;
+        if (effectiveNode.isPseudoElement()) {
+            effectiveNode = effectiveNode.parentNode;
+            console.assert(effectiveNode);
+            if (!effectiveNode)
+                return;
+        }
+
+        if (effectiveNode.nodeType() !== Node.ELEMENT_NODE)
+            return;
+
+        function inspectedPage_node_injectStyleAndToggleClass() {
+            let hideElementStyleSheetIdOrClassName = "__WebInspectorHideElement__";
+            let styleElement = document.getElementById(hideElementStyleSheetIdOrClassName);
+            if (!styleElement) {
+                styleElement = document.createElement("style");
+                styleElement.id = hideElementStyleSheetIdOrClassName;
+                styleElement.textContent = "." + hideElementStyleSheetIdOrClassName + " { visibility: hidden !important; }";
+                document.head.appendChild(styleElement);
+            }
+
+            this.classList.toggle(hideElementStyleSheetIdOrClassName);
+        }
+
+        WI.RemoteObject.resolveNode(effectiveNode).then((object) => {
+            object.callFunction(inspectedPage_node_injectStyleAndToggleClass, undefined, false, () => { });
+            object.release();
+        });
+    }
+
     _createTooltipForNode()
     {
         var node = this.representedObject;
@@ -697,90 +729,109 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
         return false;
     }
 
-    _populateTagContextMenu(contextMenu, event)
+    _populateTagContextMenu(contextMenu, event, subMenus)
     {
         let node = this.representedObject;
-        if (!node.isInUserAgentShadowTree()) {
-            let attribute = event.target.enclosingNodeOrSelfWithClass("html-attribute");
+        let isNonShadowEditable = !node.isInUserAgentShadowTree() && this.editable;
 
-            if (event.target && event.target.tagName === "A") {
-                let url = event.target.href;
+        if (event.target && event.target.tagName === "A") {
+            let url = event.target.href;
 
-                contextMenu.appendItem(WI.UIString("Open in New Tab"), () => {
-                    const frame = null;
-                    WI.openURL(url, frame, {alwaysOpenExternally: true});
+            contextMenu.appendItem(WI.UIString("Open in New Tab"), () => {
+                const frame = null;
+                WI.openURL(url, frame, {alwaysOpenExternally: true});
+            });
+
+            if (WI.frameResourceManager.resourceForURL(url)) {
+                contextMenu.appendItem(WI.UIString("Reveal in Resources Tab"), () => {
+                    let frame = WI.frameResourceManager.frameForIdentifier(node.frameIdentifier);
+
+                    const options = {
+                        ignoreNetworkTab: true,
+                        ignoreSearchTab: true,
+                    };
+                    WI.openURL(url, frame, options);
                 });
+            }
+
+            contextMenu.appendItem(WI.UIString("Copy Link Address"), () => {
+                InspectorFrontendHost.copyText(url);
+            });
+        }
 
-                if (WI.frameResourceManager.resourceForURL(url)) {
-                    contextMenu.appendItem(WI.UIString("Reveal in Resources Tab"), () => {
-                        let frame = WI.frameResourceManager.frameForIdentifier(node.frameIdentifier);
+        contextMenu.appendSeparator();
 
-                        const options = {
-                            ignoreNetworkTab: true,
-                            ignoreSearchTab: true,
-                        };
-                        WI.openURL(url, frame, options);
-                    });
-                }
+        this._populateNodeContextMenu(contextMenu, subMenus);
 
-                contextMenu.appendItem(WI.UIString("Copy Link Address"), () => {
-                    InspectorFrontendHost.copyText(url);
-                });
+        contextMenu.appendItem(WI.UIString("Toggle Visibility"), this.toggleElementVisibility.bind(this));
 
-                contextMenu.appendSeparator();
-            }
+        subMenus.add.appendItem(WI.UIString("Attribute"), this._addNewAttribute.bind(this), !isNonShadowEditable);
 
-            // Add attribute-related actions.
-            if (this.editable) {
-                contextMenu.appendItem(WI.UIString("Add Attribute"), this._addNewAttribute.bind(this));
-                if (attribute)
-                    contextMenu.appendItem(WI.UIString("Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target));
-                contextMenu.appendSeparator();
-            }
+        let attribute = event.target.enclosingNodeOrSelfWithClass("html-attribute");
+        subMenus.edit.appendItem(WI.UIString("Attribute"), this._startEditingAttribute.bind(this, attribute, event.target), !attribute || ! isNonShadowEditable);
 
-            if (WI.cssStyleManager.canForcePseudoClasses()) {
-                let pseudoSubMenu = contextMenu.appendSubMenuItem(WI.UIString("Forced Pseudo-Classes"));
-                this._populateForcedPseudoStateItems(pseudoSubMenu);
-                contextMenu.appendSeparator();
-            }
+        let attributeName = null;
+        if (attribute) {
+            let attributeNameElement = attribute.getElementsByClassName("html-attribute-name")[0];
+            if (attributeNameElement)
+                attributeName = attributeNameElement.textContent.trim();
         }
 
-        this._populateNodeContextMenu(contextMenu);
-    }
+        let attributeValue = this.representedObject.getAttribute(attributeName);
+        subMenus.copy.appendItem(WI.UIString("Attribute"), () => {
+            let text = attributeName;
+            if (attributeValue)
+                text += "=\"" + attributeValue.replace(/"/g, "\\\"") + "\"";
+            InspectorFrontendHost.copyText(text);
+        }, !attribute || !isNonShadowEditable);
 
-    _populateForcedPseudoStateItems(subMenu)
-    {
-        var node = this.representedObject;
-        var enabledPseudoClasses = node.enabledPseudoClasses;
-        // These strings don't need to be localized as they are CSS pseudo-classes.
-        WI.CSSStyleManager.ForceablePseudoClasses.forEach(function(pseudoClass) {
-            var label = pseudoClass.capitalize();
-            var enabled = enabledPseudoClasses.includes(pseudoClass);
-            subMenu.appendCheckboxItem(label, function() {
-                node.setPseudoClassEnabled(pseudoClass, !enabled);
-            }, enabled, false);
-        });
+        subMenus.delete.appendItem(WI.UIString("Attribute"), () => {
+            this.representedObject.removeAttribute(attributeName);
+        }, !attribute || !isNonShadowEditable);
+
+        subMenus.edit.appendItem(WI.UIString("Tag"), () => {
+            this._startEditingTagName();
+        }, !isNonShadowEditable);
+
+        contextMenu.appendSeparator();
+
+        if (WI.cssStyleManager.canForcePseudoClasses()) {
+            let pseudoSubMenu = contextMenu.appendSubMenuItem(WI.UIString("Forced Pseudo-Classes"));
+
+            let enabledPseudoClasses = this.representedObject.enabledPseudoClasses;
+            WI.CSSStyleManager.ForceablePseudoClasses.forEach((pseudoClass) => {
+                let enabled = enabledPseudoClasses.includes(pseudoClass);
+                pseudoSubMenu.appendCheckboxItem(pseudoClass.capitalize(), () => {
+                    this.representedObject.setPseudoClassEnabled(pseudoClass, !enabled);
+                }, enabled);
+            });
+
+            contextMenu.appendSeparator();
+        }
     }
 
-    _populateTextContextMenu(contextMenu, textNode)
+    _populateTextContextMenu(contextMenu, textNode, subMenus)
     {
-        if (this.editable)
-            contextMenu.appendItem(WI.UIString("Edit Text"), this._startEditingTextNode.bind(this, textNode));
+        this._populateNodeContextMenu(contextMenu, subMenus);
 
-        this._populateNodeContextMenu(contextMenu);
+        subMenus.edit.appendItem(WI.UIString("Text"), this._startEditingTextNode.bind(this, textNode), !this.editable);
+
+        subMenus.copy.appendItem(WI.UIString("Text"), () => {
+            InspectorFrontendHost.copyText(textNode.textContent);
+        }, !textNode.textContent.length);
     }
 
-    _populateNodeContextMenu(contextMenu)
+    _populateNodeContextMenu(contextMenu, subMenus)
     {
         let node = this.representedObject;
 
-        // Add free-form node-related actions.
-        if (this.editable)
-            contextMenu.appendItem(WI.UIString("Edit as HTML"), this._editAsHTML.bind(this));
-        if (!node.isPseudoElement())
-            contextMenu.appendItem(WI.UIString("Copy as HTML"), this._copyHTML.bind(this));
-        if (this.editable)
-            contextMenu.appendItem(WI.UIString("Delete Node"), this.remove.bind(this));
+        // FIXME: <https://webkit.org/b/179042> Web Inspector: add contextmenu item to arbitrarily add HTML/Child to DOMTree
+        subMenus.edit.appendItem(WI.UIString("HTML"), this._editAsHTML.bind(this), !this.editable);
+        subMenus.copy.appendItem(WI.UIString("HTML"), this._copyHTML.bind(this), node.isPseudoElement());
+        subMenus.delete.appendItem(WI.UIString("Node"), this.remove.bind(this), !this.editable);
+
+        for (let subMenu of Object.values(subMenus))
+            contextMenu.pushItem(subMenu);
     }
 
     _startEditing()
index f03265f..9b44cde 100644 (file)
@@ -243,14 +243,24 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline
         let commentNode = event.target.enclosingNodeOrSelfWithClass("html-comment");
         let pseudoElement = event.target.enclosingNodeOrSelfWithClass("html-pseudo-element");
 
+        let subMenus = {
+            add: new WI.ContextSubMenuItem(contextMenu, WI.UIString("Add")),
+            edit: new WI.ContextSubMenuItem(contextMenu, WI.UIString("Edit")),
+            copy: new WI.ContextSubMenuItem(contextMenu, WI.UIString("Copy")),
+            delete: new WI.ContextSubMenuItem(contextMenu, WI.UIString("Delete")),
+        };
+
         if (tag && treeElement._populateTagContextMenu)
-            treeElement._populateTagContextMenu(contextMenu, event);
+            treeElement._populateTagContextMenu(contextMenu, event, subMenus);
         else if (textNode && treeElement._populateTextContextMenu)
-            treeElement._populateTextContextMenu(contextMenu, textNode);
+            treeElement._populateTextContextMenu(contextMenu, textNode, subMenus);
         else if ((commentNode || pseudoElement) && treeElement._populateNodeContextMenu)
-            treeElement._populateNodeContextMenu(contextMenu);
+            treeElement._populateNodeContextMenu(contextMenu, subMenus);
 
-        const options = {excludeRevealElement: this._excludeRevealElementContextMenu};
+        let options = {
+            excludeRevealElement: this._excludeRevealElementContextMenu,
+            copySubMenu: subMenus.copy,
+        };
         WI.appendContextMenuItemsForDOMNode(contextMenu, treeElement.representedObject, options);
 
         super.populateContextMenu(contextMenu, event, treeElement);
@@ -490,38 +500,7 @@ WI.DOMTreeOutline = class DOMTreeOutline extends WI.TreeOutline
 
         event.preventDefault();
 
-        var effectiveNode = this.selectedTreeElement.representedObject;
-        console.assert(effectiveNode);
-        if (!effectiveNode)
-            return;
-
-        if (effectiveNode.isPseudoElement()) {
-            effectiveNode = effectiveNode.parentNode;
-            console.assert(effectiveNode);
-            if (!effectiveNode)
-                return;
-        }
-
-        if (effectiveNode.nodeType() !== Node.ELEMENT_NODE)
-            return;
-
-        function inspectedPage_node_injectStyleAndToggleClass() {
-            let hideElementStyleSheetIdOrClassName = "__WebInspectorHideElement__";
-            let styleElement = document.getElementById(hideElementStyleSheetIdOrClassName);
-            if (!styleElement) {
-                styleElement = document.createElement("style");
-                styleElement.id = hideElementStyleSheetIdOrClassName;
-                styleElement.textContent = "." + hideElementStyleSheetIdOrClassName + " { visibility: hidden !important; }";
-                document.head.appendChild(styleElement);
-            }
-
-            this.classList.toggle(hideElementStyleSheetIdOrClassName);
-        }
-
-        WI.RemoteObject.resolveNode(effectiveNode).then((object) => {
-            object.callFunction(inspectedPage_node_injectStyleAndToggleClass, undefined, false, () => { });
-            object.release();
-        });
+        this.selectedTreeElement.toggleElementVisibility();
     }
 };