Web Inspector: Elements: allow WebKit engineers to edit UserAgent shadow trees
https://bugs.webkit.org/show_bug.cgi?id=203159

Reviewed by Brian Burg.

Source/JavaScriptCore:

* inspector/protocol/DOM.json:
Add `setAllowEditingUserAgentShadowTrees` command.

Source/WebCore:

Test: inspector/dom/setAllowEditingUserAgentShadowTrees.html

* inspector/agents/InspectorDOMAgent.h:
* inspector/agents/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::assertEditableNode):
(WebCore::InspectorDOMAgent::setInspectedNode):
(WebCore::InspectorDOMAgent::setAllowEditingUserAgentShadowTrees): Added.

Source/WebInspectorUI:

* UserInterface/Controllers/DOMManager.js:
(WI.DOMManager.prototype.initializeTarget):
(WI.DOMManager.supportsEditingUserAgentShadowTrees): Added.
* UserInterface/Models/CSSStyleDeclaration.js:
(WI.CSSStyleDeclaration.prototype.get editable):
* UserInterface/Views/ContextMenuUtilities.js:
(WI.appendContextMenuItemsForDOMNode):
* UserInterface/Views/DOMTreeElement.js:
(WI.DOMTreeElement.prototype.get editable):
(WI.DOMTreeElement.prototype._startEditingTarget):
(WI.DOMTreeElement.prototype.populateDOMNodeContextMenu):

* UserInterface/Views/GeneralStyleDetailsSidebarPanel.js:
(WI.GeneralStyleDetailsSidebarPanel):
(WI.GeneralStyleDetailsSidebarPanel.prototype._showPanel):
(WI.GeneralStyleDetailsSidebarPanel.prototype._handleNodeChanged): Added.
* UserInterface/Views/StyleDetailsPanel.js:
(WI.StyleDetailsPanel.prototype.get supportsNewRule): Added.
(WI.StyleDetailsPanel.prototype.markAsNeedsRefresh):
(WI.StyleDetailsPanel.prototype.refresh):
* UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js:
(WI.SpreadsheetRulesStyleDetailsPanel.prototype.get supportsNewRule): Added.
(WI.SpreadsheetRulesStyleDetailsPanel.prototype.newRuleButtonClicked):
(WI.SpreadsheetRulesStyleDetailsPanel.prototype.newRuleButtonContextMenu):
Don't show the "Add Rule" button if the selected node is inside a UserAgent shadow tree.

* UserInterface/Base/Setting.js:
* UserInterface/Views/SettingsTabContentView.js:
(WI.SettingsTabContentView.prototype._createEngineeringSettingsView):

LayoutTests:

* inspector/dom/setAllowEditingUserAgentShadowTrees.html: Added.
* inspector/dom/setAllowEditingUserAgentShadowTrees-expected.txt: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251306 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index bb1ab46..ea7dea5 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,13 @@
+2019-10-18  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Elements: allow WebKit engineers to edit UserAgent shadow trees
+        https://bugs.webkit.org/show_bug.cgi?id=203159
+
+        Reviewed by Brian Burg.
+
+        * inspector/dom/setAllowEditingUserAgentShadowTrees.html: Added.
+        * inspector/dom/setAllowEditingUserAgentShadowTrees-expected.txt: Added.
+
 2019-10-17  Dean Jackson  <dino@apple.com>
 
         texImage2D for a half-float texture only accepts null
diff --git a/LayoutTests/inspector/dom/setAllowEditingUserAgentShadowTrees-expected.txt b/LayoutTests/inspector/dom/setAllowEditingUserAgentShadowTrees-expected.txt
new file mode 100644
index 0000000..5154244
--- /dev/null
+++ b/LayoutTests/inspector/dom/setAllowEditingUserAgentShadowTrees-expected.txt
@@ -0,0 +1,13 @@
+Tests for DOM.setAllowEditingUserAgentShadowTrees.
+
+
+
+== Running test suite: DOM.setAllowEditingUserAgentShadowTrees
+-- Running test case: DOM.setAllowEditingUserAgentShadowTrees.Disabled
+PASS: Should produce an exception.
+Error: Node for given nodeId is in a shadow tree
+PASS: Node text content should not have changed.
+
+-- Running test case: DOM.setAllowEditingUserAgentShadowTrees.Enabled
+PASS: Node text content should have changed.
+
diff --git a/LayoutTests/inspector/dom/setAllowEditingUserAgentShadowTrees.html b/LayoutTests/inspector/dom/setAllowEditingUserAgentShadowTrees.html
new file mode 100644
index 0000000..38807e5
--- /dev/null
+++ b/LayoutTests/inspector/dom/setAllowEditingUserAgentShadowTrees.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    InspectorTest.debug();
+
+    let userAgentShadowTreeChildNode = null;
+
+    let suite = InspectorTest.createAsyncSuite("DOM.setAllowEditingUserAgentShadowTrees");
+
+    suite.addTestCase({
+        name: "DOM.setAllowEditingUserAgentShadowTrees.Disabled",
+        description: "Check that other DOM commands do not work when disabled.",
+        async test() {
+            await DOMAgent.setAllowEditingUserAgentShadowTrees(false);
+
+            await InspectorTest.expectException(async () => {
+                await DOMAgent.setNodeValue(userAgentShadowTreeChildNode.id, "changed");
+            });
+
+            InspectorTest.expectEqual(userAgentShadowTreeChildNode.nodeValue(), "initial", "Node text content should not have changed.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "DOM.setAllowEditingUserAgentShadowTrees.Enabled",
+        description: "Check that other DOM commands work as expected when enabled.",
+        async test() {
+            await DOMAgent.setAllowEditingUserAgentShadowTrees(true);
+
+            await DOMAgent.setNodeValue(userAgentShadowTreeChildNode.id, "changed");
+
+            InspectorTest.expectEqual(userAgentShadowTreeChildNode.nodeValue(), "changed", "Node text content should have changed.");
+        },
+    });
+
+    WI.domManager.requestDocument((documentNode) => {
+        WI.domManager.querySelector(documentNode.id, "#x", (contentNodeId) => {
+            let inputNode = WI.domManager.nodeForId(contentNodeId);
+
+            let fragment = inputNode.shadowRoots()[0];
+            InspectorTest.assert(fragment);
+            InspectorTest.assert(fragment.nodeType() === Node.DOCUMENT_FRAGMENT_NODE);
+
+            fragment.getSubtree(-1, () => {
+                let textContainerNode = fragment.firstChild;
+                InspectorTest.assert(textContainerNode);
+                InspectorTest.assert(textContainerNode.isInUserAgentShadowTree());
+
+                userAgentShadowTreeChildNode = textContainerNode.firstChild;
+                InspectorTest.assert(userAgentShadowTreeChildNode);
+                InspectorTest.assert(userAgentShadowTreeChildNode.nodeType() === Node.TEXT_NODE);
+
+                suite.runTestCasesAndFinish();
+            });
+        });
+    });
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Tests for DOM.setAllowEditingUserAgentShadowTrees.</p>
+<input id="x" value="initial">
+</body>
+</html>
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 53116e2..7781883 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,13 @@
+2019-10-18  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Elements: allow WebKit engineers to edit UserAgent shadow trees
+        https://bugs.webkit.org/show_bug.cgi?id=203159
+
+        Reviewed by Brian Burg.
+
+        * inspector/protocol/DOM.json:
+        Add `setAllowEditingUserAgentShadowTrees` command.
+
 2019-10-18  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         [Clang][Windows] Options.cpp(317,25): error: no matching function for call to 'optionTypeSpecificIndex'
diff --git a/Source/JavaScriptCore/inspector/protocol/DOM.json b/Source/JavaScriptCore/inspector/protocol/DOM.json
index be47129..38cb48b 100644
--- a/Source/JavaScriptCore/inspector/protocol/DOM.json
+++ b/Source/JavaScriptCore/inspector/protocol/DOM.json
@@ -535,6 +535,13 @@
             "parameters": [
                 { "name": "nodeId", "$ref": "NodeId", "description": "DOM node id to be accessible by means of $0 command line API." }
             ]
+        },
+        {
+            "name": "setAllowEditingUserAgentShadowTrees",
+            "description": "Controls whether any DOM commands work for nodes inside a UserAgent shadow tree.",
+            "parameters": [
+                { "name": "allow", "type": "boolean" }
+            ]
         }
     ],
     "events": [
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index ef3679b..c8d1ac9 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,18 @@
+2019-10-18  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Elements: allow WebKit engineers to edit UserAgent shadow trees
+        https://bugs.webkit.org/show_bug.cgi?id=203159
+
+        Reviewed by Brian Burg.
+
+        Test: inspector/dom/setAllowEditingUserAgentShadowTrees.html
+
+        * inspector/agents/InspectorDOMAgent.h:
+        * inspector/agents/InspectorDOMAgent.cpp:
+        (WebCore::InspectorDOMAgent::assertEditableNode):
+        (WebCore::InspectorDOMAgent::setInspectedNode):
+        (WebCore::InspectorDOMAgent::setAllowEditingUserAgentShadowTrees): Added.
+
 2019-10-17  Ryosuke Niwa  <rniwa@webkit.org>
 
         Document::m_closeAfterStyleRecalc is unnecessary
diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp
index eea5bca..e751a5c 100644
--- a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp
+++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp
@@ -461,7 +461,7 @@
     Node* node = assertNode(errorString, nodeId);
     if (!node)
         return nullptr;
-    if (node->isInUserAgentShadowTree()) {
+    if (node->isInUserAgentShadowTree() && !m_allowEditingUserAgentShadowTrees) {
         errorString = "Node for given nodeId is in a shadow tree"_s;
         return nullptr;
     }
@@ -1395,7 +1395,7 @@
     if (!node)
         return;
 
-    if (node->isInUserAgentShadowTree()) {
+    if (node->isInUserAgentShadowTree() && !m_allowEditingUserAgentShadowTrees) {
         errorString = "Node for given nodeId is in a shadow tree"_s;
         return;
     }
@@ -2614,4 +2614,9 @@
     return toJS(&state, deprecatedGlobalObjectForPrototype(&state), BindingSecurity::checkSecurityForNode(state, node));
 }
 
+void InspectorDOMAgent::setAllowEditingUserAgentShadowTrees(ErrorString&, bool allow)
+{
+    m_allowEditingUserAgentShadowTrees = allow;
+}
+
 } // namespace WebCore
diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.h b/Source/WebCore/inspector/agents/InspectorDOMAgent.h
index f34994e..157cc82 100644
--- a/Source/WebCore/inspector/agents/InspectorDOMAgent.h
+++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.h
@@ -148,6 +148,7 @@
     void markUndoableState(ErrorString&) override;
     void focus(ErrorString&, int nodeId) override;
     void setInspectedNode(ErrorString&, int nodeId) override;
+    void setAllowEditingUserAgentShadowTrees(ErrorString&, bool allow) final;
 
     // InspectorInstrumentation
     int identifierForNode(Node&);
@@ -256,10 +257,6 @@
     std::unique_ptr<HighlightConfig> m_inspectModeHighlightConfig;
     std::unique_ptr<InspectorHistory> m_history;
     std::unique_ptr<DOMEditor> m_domEditor;
-    bool m_searchingForNode { false };
-    bool m_suppressAttributeModifiedEvent { false };
-    bool m_suppressEventListenerChangedEvent { false };
-    bool m_documentRequested { false };
 
 #if ENABLE(VIDEO)
     Timer m_mediaMetricsTimer;
@@ -318,6 +315,12 @@
     HashSet<const Event*> m_dispatchedEvents;
     HashMap<int, InspectorEventListener> m_eventListenerEntries;
     int m_lastEventListenerId { 1 };
+
+    bool m_searchingForNode { false };
+    bool m_suppressAttributeModifiedEvent { false };
+    bool m_suppressEventListenerChangedEvent { false };
+    bool m_documentRequested { false };
+    bool m_allowEditingUserAgentShadowTrees { false };
 };
 
 } // namespace WebCore
diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog
index e896a30..feefa30 100644
--- a/Source/WebInspectorUI/ChangeLog
+++ b/Source/WebInspectorUI/ChangeLog
@@ -1,5 +1,42 @@
 2019-10-18  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: Elements: allow WebKit engineers to edit UserAgent shadow trees
+        https://bugs.webkit.org/show_bug.cgi?id=203159
+
+        Reviewed by Brian Burg.
+
+        * UserInterface/Controllers/DOMManager.js:
+        (WI.DOMManager.prototype.initializeTarget):
+        (WI.DOMManager.supportsEditingUserAgentShadowTrees): Added.
+        * UserInterface/Models/CSSStyleDeclaration.js:
+        (WI.CSSStyleDeclaration.prototype.get editable):
+        * UserInterface/Views/ContextMenuUtilities.js:
+        (WI.appendContextMenuItemsForDOMNode):
+        * UserInterface/Views/DOMTreeElement.js:
+        (WI.DOMTreeElement.prototype.get editable):
+        (WI.DOMTreeElement.prototype._startEditingTarget):
+        (WI.DOMTreeElement.prototype.populateDOMNodeContextMenu):
+
+        * UserInterface/Views/GeneralStyleDetailsSidebarPanel.js:
+        (WI.GeneralStyleDetailsSidebarPanel):
+        (WI.GeneralStyleDetailsSidebarPanel.prototype._showPanel):
+        (WI.GeneralStyleDetailsSidebarPanel.prototype._handleNodeChanged): Added.
+        * UserInterface/Views/StyleDetailsPanel.js:
+        (WI.StyleDetailsPanel.prototype.get supportsNewRule): Added.
+        (WI.StyleDetailsPanel.prototype.markAsNeedsRefresh):
+        (WI.StyleDetailsPanel.prototype.refresh):
+        * UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js:
+        (WI.SpreadsheetRulesStyleDetailsPanel.prototype.get supportsNewRule): Added.
+        (WI.SpreadsheetRulesStyleDetailsPanel.prototype.newRuleButtonClicked):
+        (WI.SpreadsheetRulesStyleDetailsPanel.prototype.newRuleButtonContextMenu):
+        Don't show the "Add Rule" button if the selected node is inside a UserAgent shadow tree.
+
+        * UserInterface/Base/Setting.js:
+        * UserInterface/Views/SettingsTabContentView.js:
+        (WI.SettingsTabContentView.prototype._createEngineeringSettingsView):
+
+2019-10-18  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Elements: $0 is shown for the wrong node when selecting elements in a user agent shadow tree
         https://bugs.webkit.org/show_bug.cgi?id=203155
 
diff --git a/Source/WebInspectorUI/UserInterface/Base/Setting.js b/Source/WebInspectorUI/UserInterface/Base/Setting.js
index f155779..b3874ee 100644
--- a/Source/WebInspectorUI/UserInterface/Base/Setting.js
+++ b/Source/WebInspectorUI/UserInterface/Base/Setting.js
@@ -202,6 +202,7 @@
     engineeringPauseForInternalScripts: new WI.Setting("engineering-pause-for-internal-scripts", false),
     engineeringShowInternalObjectsInHeapSnapshot: new WI.Setting("engineering-show-internal-objects-in-heap-snapshot", false),
     engineeringShowPrivateSymbolsInHeapSnapshot: new WI.Setting("engineering-show-private-symbols-in-heap-snapshot", false),
+    engineeringAllowEditingUserAgentShadowTrees: new WI.Setting("engineering-allow-editing-user-agent-shadow-trees", false),
 
     // Debug
     debugShowConsoleEvaluations: new WI.Setting("debug-show-console-evaluations", false),
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js
index 7352e5b..599f398 100644
--- a/Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js
+++ b/Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js
@@ -67,6 +67,11 @@
             setTimeout(() => {
                 this.ensureDocument();
             });
+
+            if (WI.isEngineeringBuild) {
+                if (DOMManager.supportsEditingUserAgentShadowTrees({target}))
+                    target.DOMAgent.setAllowEditingUserAgentShadowTrees(WI.settings.engineeringAllowEditingUserAgentShadowTrees.value);
+            }
         }
     }
 
@@ -88,6 +93,15 @@
             && InspectorBackend.hasCommand("DOM.removeBreakpointForEventListener");
     }
 
+    static supportsEditingUserAgentShadowTrees({frontendOnly, target} = {})
+    {
+        target = target || InspectorBackend;
+        return WI.isEngineeringBuild
+            && WI.settings.engineeringAllowEditingUserAgentShadowTrees.value
+            && (frontendOnly || target.hasCommand("DOM.setAllowEditingUserAgentShadowTrees"));
+
+    }
+
     // Public
 
     get inspectedNode() { return this._inspectedNode; }
diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js b/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js
index 0e97884..02b6074 100644
--- a/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js
+++ b/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js
@@ -100,7 +100,7 @@
             return this._ownerRule && this._ownerRule.editable;
 
         if (this._type === WI.CSSStyleDeclaration.Type.Inline)
-            return !this._node.isInUserAgentShadowTree();
+            return !this._node.isInUserAgentShadowTree() || WI.DOMManager.supportsEditingUserAgentShadowTrees();
 
         return false;
     }
diff --git a/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js b/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
index e9317c7..fe4f8c7 100644
--- a/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
+++ b/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
@@ -282,7 +282,8 @@
 
         contextMenu.appendSeparator();
 
-        if (!options.excludeLogElement && !domNode.isInUserAgentShadowTree() && !domNode.isPseudoElement()) {
+        let canLogShadowTree = !domNode.isInUserAgentShadowTree() || WI.DOMManager.supportsEditingUserAgentShadowTrees({frontendOnly: true});
+        if (!options.excludeLogElement && canLogShadowTree && !domNode.isPseudoElement()) {
             let label = isElement ? WI.UIString("Log Element", "Log (print) DOM element to Console") : WI.UIString("Log Node", "Log (print) DOM node to Console");
             contextMenu.appendItem(label, () => {
                 WI.RemoteObject.resolveNode(domNode, WI.RuntimeManager.ConsoleObjectGroup).then((remoteObject) => {
diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
index 40200cc..0b32a6e 100644
--- a/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
+++ b/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
@@ -244,7 +244,10 @@
     {
         let node = this.representedObject;
 
-        if (node.isShadowRoot() || node.isInUserAgentShadowTree())
+        if (node.isShadowRoot())
+            return false;
+
+        if (node.isInUserAgentShadowTree() && !WI.DOMManager.supportsEditingUserAgentShadowTrees())
             return false;
 
         if (node.isPseudoElement())
@@ -723,7 +726,10 @@
         if (this.treeOutline.selectedDOMNode() !== this.representedObject)
             return false;
 
-        if (this.representedObject.isShadowRoot() || this.representedObject.isInUserAgentShadowTree())
+        if (this.representedObject.isShadowRoot())
+            return false;
+
+        if (this.representedObject.isInUserAgentShadowTree() && !WI.DOMManager.supportsEditingUserAgentShadowTrees())
             return false;
 
         if (this.representedObject.isPseudoElement())
@@ -765,7 +771,7 @@
         contextMenu.appendSeparator();
 
         let isEditableNode = this.representedObject.nodeType() === Node.ELEMENT_NODE && this.editable;
-        let isNonShadowEditable = !this.representedObject.isInUserAgentShadowTree() && isEditableNode;
+        let isNonShadowEditable = isEditableNode && (!this.representedObject.isInUserAgentShadowTree() || WI.DOMManager.supportsEditingUserAgentShadowTrees());
         let alreadyEditingHTML = this._htmlEditElement && WI.isBeingEdited(this._htmlEditElement);
 
         if (isEditableNode) {
diff --git a/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.js
index c02a3e3..fa446f7 100644
--- a/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.js
+++ b/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.js
@@ -30,7 +30,10 @@
         super(identifier, displayName);
 
         this.element.classList.add("css-style");
+
+        console.assert(panelConstructor instanceof WI.StyleDetailsPanel);
         this._panel = new panelConstructor(this);
+        this._panel.addEventListener(WI.StyleDetailsPanel.Event.NodeChanged, this._handleNodeChanged, this);
 
         this._classListContainerToggledSetting = new WI.Setting("class-list-container-toggled", false);
         this._forcedPseudoClassCheckboxes = {};
@@ -223,10 +226,14 @@
         if (this._filterBar)
             this.contentView.element.classList.toggle(WI.GeneralStyleDetailsSidebarPanel.FilterInProgressClassName, hasFilter && this._filterBar.hasActiveFilters());
 
-        this.contentView.element.classList.toggle("supports-new-rule", typeof this._panel.newRuleButtonClicked === "function");
         this._panel.shown();
     }
 
+    _handleNodeChanged(event)
+    {
+        this.contentView.element.classList.toggle("supports-new-rule", this._panel.supportsNewRule);
+    }
+
     _handleForcedPseudoClassCheckboxKeydown(pseudoClass, event)
     {
         if (event.key !== "Tab")
diff --git a/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js b/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
index 153b3dc..0c9f8d4 100644
--- a/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
+++ b/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
@@ -398,6 +398,9 @@
 
         let engineeringSettingsView = new WI.SettingsView("engineering", WI.unlocalizedString("Engineering"));
 
+        let elementsGroup = engineeringSettingsView.addGroup(WI.unlocalizedString("Elements:"));
+        elementsGroup.addSetting(WI.settings.engineeringAllowEditingUserAgentShadowTrees, WI.unlocalizedString("Allow editing UserAgent shadow trees"));
+
         let debuggingGroup = engineeringSettingsView.addGroup(WI.unlocalizedString("Debugging:"));
         debuggingGroup.addSetting(WI.settings.engineeringShowInternalScripts, WI.unlocalizedString("Show WebKit-internal scripts"));
         debuggingGroup.addSetting(WI.settings.engineeringPauseForInternalScripts, WI.unlocalizedString("Pause in WebKit-internal scripts"));
diff --git a/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js b/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js
index 336529c..f7d8a70 100644
--- a/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js
+++ b/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js
@@ -48,6 +48,11 @@
 
     // Public
 
+    get supportsNewRule()
+    {
+        return this.nodeStyles && !this.nodeStyles.node.isInUserAgentShadowTree();
+    }
+
     refresh(significantChange)
     {
         // We only need to do a rebuild on significant changes. Other changes are handled
@@ -93,17 +98,11 @@
 
     newRuleButtonClicked()
     {
-        if (this.nodeStyles.node.isInUserAgentShadowTree())
-            return;
-
         this._addNewRule();
     }
 
     newRuleButtonContextMenu(event)
     {
-        if (this.nodeStyles.node.isInUserAgentShadowTree())
-            return;
-
         let styleSheets = WI.cssManager.styleSheets.filter(styleSheet => styleSheet.hasInfo() && !styleSheet.isInlineStyleTag() && !styleSheet.isInlineStyleAttributeStyleSheet());
         if (!styleSheets.length)
             return;
diff --git a/Source/WebInspectorUI/UserInterface/Views/StyleDetailsPanel.js b/Source/WebInspectorUI/UserInterface/Views/StyleDetailsPanel.js
index f94bde4..ab6d3f5 100644
--- a/Source/WebInspectorUI/UserInterface/Views/StyleDetailsPanel.js
+++ b/Source/WebInspectorUI/UserInterface/Views/StyleDetailsPanel.js
@@ -51,6 +51,12 @@
         return this._nodeStyles;
     }
 
+    get supportsNewRule()
+    {
+        // Overridden by subclasses if needed.
+        return false;
+    }
+
     shown()
     {
         if (this._visible)
@@ -85,6 +91,8 @@
 
             this._nodeStyles = WI.cssManager.stylesForNode(domNode);
 
+            this.dispatchEventToListeners(WI.StyleDetailsPanel.Event.NodeChanged);
+
             console.assert(this._nodeStyles);
             if (!this._nodeStyles)
                 return;
@@ -102,7 +110,6 @@
     refresh(significantChange)
     {
         // Implemented by subclasses.
-        this.dispatchEventToListeners(WI.StyleDetailsPanel.Event.Refreshed);
     }
 
     // Protected
@@ -162,5 +169,5 @@
 };
 
 WI.StyleDetailsPanel.Event = {
-    Refreshed: "style-details-panel-refreshed"
+    NodeChanged: "style-details-panel-node-changed",
 };