Title: [295623] trunk
Revision
295623
Author
drou...@apple.com
Date
2022-06-16 19:17:50 -0700 (Thu, 16 Jun 2022)

Log Message

Web Inspector: Elements: rework CSS pseudo class toggles
https://bugs.webkit.org/show_bug.cgi?id=241655

Reviewed by Patrick Angle.

Move the forced pseudo class checkboxes to the bottom of the sidebar next to the class list
checkboxes (though only one of them can be enabled at a time) since more space is needed to support
new (and future) pseudo classes and using space at the bottom of the sidebar is better than at the
top (since there's already precedent with the class list checkboxes).

Test: inspector/css/forcePseudoState.html

* Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.js:
(WI.GeneralStyleDetailsSidebarPanel):
(WI.GeneralStyleDetailsSidebarPanel.prototype.get minimumWidth):
(WI.GeneralStyleDetailsSidebarPanel.prototype.attached):
(WI.GeneralStyleDetailsSidebarPanel.prototype.layout):
(WI.GeneralStyleDetailsSidebarPanel.prototype.addEventListeners):
(WI.GeneralStyleDetailsSidebarPanel.prototype.removeEventListeners):
(WI.GeneralStyleDetailsSidebarPanel.prototype.initialLayout):
(WI.GeneralStyleDetailsSidebarPanel.prototype.sizeDidChange):
(WI.GeneralStyleDetailsSidebarPanel.prototype._updateClassListContainer): Added.
(WI.GeneralStyleDetailsSidebarPanel.prototype._updateForcedPseudoClassContainer): Added.
(WI.GeneralStyleDetailsSidebarPanel.prototype._handleNodeChanged):
(WI.GeneralStyleDetailsSidebarPanel.prototype._forcedPseudoClassCheckboxChanged): Added.
(WI.GeneralStyleDetailsSidebarPanel.prototype._updatePseudoClasasCheckboxes): Added.
(WI.GeneralStyleDetailsSidebarPanel.prototype._classListToggleButtonClicked): Renamed from `_classToggleButtonClicked`.
(WI.GeneralStyleDetailsSidebarPanel.prototype._forcedPseudoClassToggleButtonClicked): Added.
(WI.GeneralStyleDetailsSidebarPanel.prototype.styleDetailsPanelFocusLastPseudoClassCheckbox): Deleted.
(WI.GeneralStyleDetailsSidebarPanel.prototype.get _initialScrollOffset): Deleted.
(WI.GeneralStyleDetailsSidebarPanel.prototype._updateNoForcedPseudoClassesScrollOffset): Deleted.
(WI.GeneralStyleDetailsSidebarPanel.prototype._handleForcedPseudoClassCheckboxKeydown): Deleted.
* Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.css:
(.sidebar > .panel.details.css-style > .content ~ :is(.options-container, .class-list-container, .forced-pseudo-class-container)): ADded.
(.sidebar > .panel.details.css-style > .content ~ :is(.options-container, .class-list-container)):
(.sidebar > .panel.details.css-style > .content:not(.supports-new-rule, .has-filter-bar) ~ :is(.options-container, .class-list-container, .forced-pseudo-class-container)): Renamed from `.sidebar > .panel.details.css-style > .content:not(.supports-new-rule, .has-filter-bar) ~ :is(.options-container, .class-list-container)`.
(.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle): Renamed from `.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle`.
(.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle:focus): Renamed from `.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle:focus`.
(.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle::before): Renamed from `.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle::before`.
(.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle:is(.selected, :hover)): Renamed from `.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle:is(.selected, :hover)`.
(.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle:is(.selected, :hover)::before): Renamed from `.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle:is(.selected, :hover)::before`.
(.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle:not(.selected):hover::before): Renamed from `.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle:not(.selected):hover::before`.
(.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle.selected:active::before): Renamed from `.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle.selected:active::before`.
(.sidebar > .panel.details.css-style > .content:not(.supports-new-rule) ~ .options-container > .new-rule, .sidebar > .panel.details.css-style > .content:not(.supports-toggle-class-list) ~ .options-container > .toggle.class-list, .sidebar > .panel.details.css-style > .content:not(.supports-toggle-forced-pseudo-class) ~ .options-container > .toggle.forced-pseudo-class, .sidebar > .panel.details.css-style > .content:not(.has-filter-bar) ~ .options-container > .filter-bar, .sidebar > .panel.details.css-style > .content:not(.supports-new-rule):not(.supports-toggle-class-list):not(.supports-toggle-forced-pseudo-class):not(.has-filter-bar) ~ .options-container): Renamed from `.sidebar > .panel.details.css-style > .content:not(.supports-new-rule) ~ .options-container > .new-rule, .sidebar > .panel.details.css-style > .content:not(.supports-toggle-css-class) ~ .options-container > .toggle-class-toggle, .sidebar > .panel.details.cs
 s-style > .content:not(.has-filter-bar) ~ .options-container > .filter-bar, .sidebar > .panel.details.css-style > .content:not(.supports-new-rule):not(.supports-toggle-class):not(.has-filter-bar) ~ .options-container`.
(.sidebar > .panel.details.css-style > .content ~ :is(.class-list-container, .forced-pseudo-class-container)): Added.
(.sidebar > .panel.details.css-style > .content ~ :is(.class-list-container, .forced-pseudo-class-container)[hidden]): Added.
(.sidebar > .panel.details.css-style > .content ~ .class-list-container):
(.sidebar > .panel.details.css-style > .content ~ .forced-pseudo-class-container): Added.
(.sidebar > .panel.details.css-style > .content ~ .forced-pseudo-class-container > label): Added.

* Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js:
(WI.SpreadsheetRulesStyleDetailsPanel.prototype.get supportsToggleCSSClassList): Added.
(WI.SpreadsheetRulesStyleDetailsPanel.prototype.get supportsToggleCSSForcedPseudoClass): Added.
(WI.SpreadsheetRulesStyleDetailsPanel.prototype.get initialToggleCSSForcedPseudoClassState): Added.
(WI.SpreadsheetRulesStyleDetailsPanel.prototype.spreadsheetCSSStyleDeclarationSectionStartEditingAdjacentRule):
Remove `styleDetailsPanelFocusLastPseudoClassCheckbox` as the forced pseudo class checkboxes are no
longer at the top, and therefore should not be focused when shift-tabbing from the first property.

* Source/WebInspectorUI/UserInterface/Views/StyleDetailsPanel.js:
(WI.StyleDetailsPanel.prototype.get supportsToggleCSSClassList): Renamed from `get supportsToggleCSSClass`.
(WI.StyleDetailsPanel.prototype.get supportsToggleCSSForcedPseudoClass):
(WI.StyleDetailsPanel.prototype.get _initialScrollOffset): Deleted.
No need to adjust the initial `scrollTop` for the forced pseudo class checkboxes since they are no
longer at the top.

* Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.js:
(WI.ComputedStyleDetailsPanel.prototype.get supportsToggleCSSClassList): Added.
(WI.ComputedStyleDetailsPanel.prototype.get supportsToggleCSSForcedPseudoClass): Added.
(WI.ComputedStyleDetailsPanel.prototype.get initialToggleCSSForcedPseudoClassState): Added.
* Source/WebInspectorUI/UserInterface/Views/FontDetailsPanel.js:
(WI.FontDetailsPanel.prototype.get supportsToggleCSSClass): Deleted.
Allow `WI.StyleDetailsPanel` subclasses to control whether they support forcing pseudo classes (and
if so whether the container should initially be shown).

* Source/_javascript_Core/inspector/protocol/CSS.json:
* Source/WebCore/inspector/agents/InspectorCSSAgent.h:
* Source/WebCore/inspector/agents/InspectorCSSAgent.cpp:
(WebCore::computePseudoClassMask): Deleted.
(WebCore::InspectorCSSAgent::forcePseudoState):
* Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js:
(WI.CSSManager.displayNameForForceablePseudoClass): Added.
(WI.CSSManager.prototype.canForcePseudoClass): Renamed from `canForcePseudoClasses`.
Add enum values for `:focus-visible` and `:focus-within`.
Drive-by: Make `CSS.ForceablePseudoClass` enum instead of having it be an inline/anonymous enum for
          only `CSS.forcePseudoState`.

* Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js:
(WI.appendContextMenuItemsForDOMNode):
* Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js:
(WI.SpreadsheetCSSStyleDeclarationSection.prototype._populateIconElementContextMenu):
Handle `WI.CSSManager.ForceablePseudoClass` now being an object instead of an array.

* Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js:

* LayoutTests/inspector/css/forcePseudoState.html: Added.
* LayoutTests/inspector/css/forcePseudoState-expected.txt: Added.

Canonical link: https://commits.webkit.org/251628@main

Modified Paths

Added Paths

Diff

Added: trunk/LayoutTests/inspector/css/forcePseudoState-expected.txt (0 => 295623)


--- trunk/LayoutTests/inspector/css/forcePseudoState-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/css/forcePseudoState-expected.txt	2022-06-17 02:17:50 UTC (rev 295623)
@@ -0,0 +1,52 @@
+Testing CSS.forcePseudoState.
+
+
+== Running test suite: CSS.forcePseudoState
+-- Running test case: CSS.forcePseudoState.active
+Forcing pseudo class...
+PASS: Should have one enabled pseudo class.
+PASS: Should change styles.
+Removing forced pseudo class...
+PASS: Should not have any enabled pseudo classes.
+PASS: Should change back to initial style.
+
+-- Running test case: CSS.forcePseudoState.focus
+Forcing pseudo class...
+PASS: Should have one enabled pseudo class.
+PASS: Should change styles.
+Removing forced pseudo class...
+PASS: Should not have any enabled pseudo classes.
+PASS: Should change back to initial style.
+
+-- Running test case: CSS.forcePseudoState.focus-visible
+Forcing pseudo class...
+PASS: Should have one enabled pseudo class.
+PASS: Should change styles.
+Removing forced pseudo class...
+PASS: Should not have any enabled pseudo classes.
+PASS: Should change back to initial style.
+
+-- Running test case: CSS.forcePseudoState.focus-within
+Forcing pseudo class...
+PASS: Should have one enabled pseudo class.
+PASS: Should change styles.
+Removing forced pseudo class...
+PASS: Should not have any enabled pseudo classes.
+PASS: Should change back to initial style.
+
+-- Running test case: CSS.forcePseudoState.hover
+Forcing pseudo class...
+PASS: Should have one enabled pseudo class.
+PASS: Should change styles.
+Removing forced pseudo class...
+PASS: Should not have any enabled pseudo classes.
+PASS: Should change back to initial style.
+
+-- Running test case: CSS.forcePseudoState.visited
+Forcing pseudo class...
+PASS: Should have one enabled pseudo class.
+PASS: Should change styles.
+Removing forced pseudo class...
+PASS: Should not have any enabled pseudo classes.
+PASS: Should change back to initial style.
+

Added: trunk/LayoutTests/inspector/css/forcePseudoState.html (0 => 295623)


--- trunk/LayoutTests/inspector/css/forcePseudoState.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/css/forcePseudoState.html	2022-06-17 02:17:50 UTC (rev 295623)
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function test() {
+    let testElement = null;
+    let nodeStyles = null;
+
+    let suite = InspectorTest.createAsyncSuite("CSS.forcePseudoState");
+
+    [
+        {forceablePseudoClass: WI.CSSManager.ForceablePseudoClass.Active, expectedBackgroundColor: "rgb(0, 0, 10)"},
+        {forceablePseudoClass: WI.CSSManager.ForceablePseudoClass.Focus, expectedBackgroundColor: "rgb(0, 0, 20)"},
+        {forceablePseudoClass: WI.CSSManager.ForceablePseudoClass.FocusVisible, expectedBackgroundColor: "rgb(0, 0, 30)"},
+        {forceablePseudoClass: WI.CSSManager.ForceablePseudoClass.FocusWithin, expectedBackgroundColor: "rgb(0, 0, 40)"},
+        {forceablePseudoClass: WI.CSSManager.ForceablePseudoClass.Hover, expectedBackgroundColor: "rgb(0, 0, 50)"},
+        {forceablePseudoClass: WI.CSSManager.ForceablePseudoClass.Visited, expectedBackgroundColor: "rgb(0, 0, 60)"},
+    ].forEach(({forceablePseudoClass, expectedBackgroundColor}) => {
+        suite.addTestCase({
+            name: "CSS.forcePseudoState." + forceablePseudoClass,
+            async test() {
+                await nodeStyles.refreshIfNeeded();
+
+                InspectorTest.assert(Object.shallowEqual(testElement.enabledPseudoClasses, []), "Should not have any enabled pseudo classes.");
+                InspectorTest.assert(nodeStyles.computedStyle.propertyForName("background-color").value === "rgb(0, 0, 0)", "Should have expected initial style.");
+
+                InspectorTest.log("Forcing pseudo class...");
+                await Promise.all([
+                    testElement.awaitEvent(WI.DOMNode.Event.EnabledPseudoClassesChanged),
+                    testElement.setPseudoClassEnabled(forceablePseudoClass, true),
+                ]);
+                await nodeStyles.refreshIfNeeded();
+                InspectorTest.expectShallowEqual(testElement.enabledPseudoClasses, [forceablePseudoClass], "Should have one enabled pseudo class.");
+                InspectorTest.expectEqual(nodeStyles.computedStyle.propertyForName("background-color").value, expectedBackgroundColor, "Should change styles.");
+
+                InspectorTest.log("Removing forced pseudo class...");
+                await Promise.all([
+                    testElement.awaitEvent(WI.DOMNode.Event.EnabledPseudoClassesChanged),
+                    testElement.setPseudoClassEnabled(forceablePseudoClass, false),
+                ]);
+                await nodeStyles.refreshIfNeeded();
+                InspectorTest.expectShallowEqual(testElement.enabledPseudoClasses, [], "Should not have any enabled pseudo classes.");
+                InspectorTest.expectEqual(nodeStyles.computedStyle.propertyForName("background-color").value, "rgb(0, 0, 0)", "Should change back to initial style.");
+            },
+        });
+    });
+
+    WI.domManager.requestDocument((documentNode) => {
+        documentNode.querySelector("#test-element", (contentNodeId) => {
+            if (contentNodeId) {
+                testElement = WI.domManager.nodeForId(contentNodeId);
+                nodeStyles = WI.cssManager.stylesForNode(testElement);
+                suite.runTestCasesAndFinish();
+            } else {
+                InspectorTest.fail("DOM node not found.");
+                InspectorTest.completeTest();
+            }
+        });
+    });
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Testing CSS.forcePseudoState.</p>
+
+    <style>
+#test-element {
+    background-color: rgb(0, 0, 0);
+}
+#test-element:active {
+    background-color: rgb(0, 0, 10);
+}
+#test-element:focus {
+    background-color: rgb(0, 0, 20);
+}
+#test-element:focus-visible {
+    background-color: rgb(0, 0, 30);
+}
+#test-element:focus-within {
+    background-color: rgb(0, 0, 40);
+}
+#test-element:hover {
+    background-color: rgb(0, 0, 50);
+}
+#test-element:visited {
+    background-color: rgb(0, 0, 60);
+}
+    </style>
+    <a href="" id="test-element"></a>
+</body>
+</html>

Modified: trunk/Source/_javascript_Core/inspector/protocol/CSS.json (295622 => 295623)


--- trunk/Source/_javascript_Core/inspector/protocol/CSS.json	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/_javascript_Core/inspector/protocol/CSS.json	2022-06-17 02:17:50 UTC (rev 295623)
@@ -55,6 +55,19 @@
             "description": "Pseudo-style identifier (see <code>enum PseudoId</code> in <code>RenderStyleConstants.h</code>)."
         },
         {
+            "id": "ForceablePseudoClass",
+            "type": "string",
+            "enum": [
+                "active",
+                "focus",
+                "focus-visible",
+                "focus-within",
+                "hover",
+                "visited"
+            ],
+            "description": "Pseudo-style identifier (see <code>enum PseudoId</code> in <code>RenderStyleConstants.h</code>)."
+        },
+        {
             "id": "PseudoIdMatches",
             "type": "object",
             "description": "CSS rule collection for a single pseudo style.",
@@ -424,7 +437,7 @@
             "targetTypes": ["page"],
             "parameters": [
                 { "name": "nodeId", "$ref": "DOM.NodeId", "description": "The element id for which to force the pseudo state." },
-                { "name": "forcedPseudoClasses", "type": "array", "items": { "type": "string", "enum": ["active", "focus", "hover", "visited"] }, "description": "Element pseudo classes to force when computing the element's style." }
+                { "name": "forcedPseudoClasses", "type": "array", "items": { "$ref": "ForceablePseudoClass" }, "description": "Element pseudo classes to force when computing the element's style." }
             ]
         },
         {

Modified: trunk/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp (295622 => 295623)


--- trunk/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp	2022-06-17 02:17:50 UTC (rev 295623)
@@ -81,39 +81,6 @@
 
 using namespace Inspector;
 
-enum ForcePseudoClassFlags {
-    PseudoClassNone = 0,
-    PseudoClassHover = 1 << 0,
-    PseudoClassFocus = 1 << 1,
-    PseudoClassActive = 1 << 2,
-    PseudoClassVisited = 1 << 3
-};
-
-static unsigned computePseudoClassMask(const JSON::Array& pseudoClassArray)
-{
-    static NeverDestroyed<String> active(MAKE_STATIC_STRING_IMPL("active"));
-    static NeverDestroyed<String> hover(MAKE_STATIC_STRING_IMPL("hover"));
-    static NeverDestroyed<String> focus(MAKE_STATIC_STRING_IMPL("focus"));
-    static NeverDestroyed<String> visited(MAKE_STATIC_STRING_IMPL("visited"));
-    if (!pseudoClassArray.length())
-        return PseudoClassNone;
-
-    unsigned result = PseudoClassNone;
-    for (auto& pseudoClassValue : pseudoClassArray) {
-        auto pseudoClass = pseudoClassValue->asString();
-        if (pseudoClass == active)
-            result |= PseudoClassActive;
-        else if (pseudoClass == hover)
-            result |= PseudoClassHover;
-        else if (pseudoClass == focus)
-            result |= PseudoClassFocus;
-        else if (pseudoClass == visited)
-            result |= PseudoClassVisited;
-    }
-
-    return result;
-}
-
 class InspectorCSSAgent::StyleSheetAction : public InspectorHistory::Action {
     WTF_MAKE_NONCOPYABLE(StyleSheetAction);
 public:
@@ -428,19 +395,7 @@
     if (!nodeId)
         return false;
 
-    unsigned forcedPseudoState = m_nodeIdToForcedPseudoState.get(nodeId);
-    switch (pseudoClassType) {
-    case CSSSelector::PseudoClassActive:
-        return forcedPseudoState & PseudoClassActive;
-    case CSSSelector::PseudoClassFocus:
-        return forcedPseudoState & PseudoClassFocus;
-    case CSSSelector::PseudoClassHover:
-        return forcedPseudoState & PseudoClassHover;
-    case CSSSelector::PseudoClassVisited:
-        return forcedPseudoState & PseudoClassVisited;
-    default:
-        return false;
-    }
+    return m_nodeIdToForcedPseudoState.get(nodeId).contains(pseudoClassType);
 }
 
 std::optional<Protocol::CSS::PseudoId> InspectorCSSAgent::protocolValueForPseudoId(PseudoId pseudoId)
@@ -924,13 +879,45 @@
     if (!element)
         return makeUnexpected(errorString);
 
-    // Return early if the forced pseudo state was already set correctly.
-    unsigned forcedPseudoState = computePseudoClassMask(forcedPseudoClasses);
-    if (forcedPseudoState) {
-        auto iterator = m_nodeIdToForcedPseudoState.add(nodeId, 0).iterator;
-        if (forcedPseudoState == iterator->value)
-            return { };
-        iterator->value = forcedPseudoState;
+    PseudoClassHashSet forcedPseudoClassesToSet;
+    for (const auto& pseudoClassValue : forcedPseudoClasses.get()) {
+        auto pseudoClassString = pseudoClassValue->asString();
+        if (!pseudoClassString)
+            return makeUnexpected("Unexpected non-string value in given forcedPseudoClasses"_s);
+
+        auto pseudoClass = Protocol::Helpers::parseEnumValueFromString<Protocol::CSS::ForceablePseudoClass>(pseudoClassString);
+        if (!pseudoClass)
+            return makeUnexpected(makeString("Unknown forcedPseudoClass: ", pseudoClassString));
+
+        switch (*pseudoClass) {
+        case Protocol::CSS::ForceablePseudoClass::Active:
+            forcedPseudoClassesToSet.add(CSSSelector::PseudoClassActive);
+            break;
+
+        case Protocol::CSS::ForceablePseudoClass::Hover:
+            forcedPseudoClassesToSet.add(CSSSelector::PseudoClassHover);
+            break;
+
+        case Protocol::CSS::ForceablePseudoClass::Focus:
+            forcedPseudoClassesToSet.add(CSSSelector::PseudoClassFocus);
+            break;
+
+        case Protocol::CSS::ForceablePseudoClass::FocusVisible:
+            forcedPseudoClassesToSet.add(CSSSelector::PseudoClassFocusVisible);
+            break;
+
+        case Protocol::CSS::ForceablePseudoClass::FocusWithin:
+            forcedPseudoClassesToSet.add(CSSSelector::PseudoClassFocusWithin);
+            break;
+
+        case Protocol::CSS::ForceablePseudoClass::Visited:
+            forcedPseudoClassesToSet.add(CSSSelector::PseudoClassVisited);
+            break;
+        }
+    }
+
+    if (!forcedPseudoClassesToSet.isEmpty()) {
+        m_nodeIdToForcedPseudoState.set(nodeId, WTFMove(forcedPseudoClassesToSet));
         m_documentsWithForcedPseudoStates.add(&element->document());
     } else {
         if (!m_nodeIdToForcedPseudoState.remove(nodeId))

Modified: trunk/Source/WebCore/inspector/agents/InspectorCSSAgent.h (295622 => 295623)


--- trunk/Source/WebCore/inspector/agents/InspectorCSSAgent.h	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebCore/inspector/agents/InspectorCSSAgent.h	2022-06-17 02:17:50 UTC (rev 295623)
@@ -137,7 +137,7 @@
     typedef HashMap<Inspector::Protocol::CSS::StyleSheetId, RefPtr<InspectorStyleSheet>> IdToInspectorStyleSheet;
     typedef HashMap<CSSStyleSheet*, RefPtr<InspectorStyleSheet>> CSSStyleSheetToInspectorStyleSheet;
     typedef HashMap<RefPtr<Document>, Vector<RefPtr<InspectorStyleSheet>>> DocumentToViaInspectorStyleSheet; // "via inspector" stylesheets
-    typedef HashMap<Inspector::Protocol::DOM::NodeId, unsigned> NodeIdToForcedPseudoState;
+    typedef HashSet<CSSSelector::PseudoClassType, IntHash<CSSSelector::PseudoClassType>, WTF::StrongEnumHashTraits<CSSSelector::PseudoClassType>> PseudoClassHashSet;
 
     InspectorStyleSheetForInlineStyle& asInspectorStyleSheet(StyledElement&);
     Element* elementForId(Inspector::Protocol::ErrorString&, Inspector::Protocol::DOM::NodeId);
@@ -171,7 +171,7 @@
     HashMap<Node*, Ref<InspectorStyleSheetForInlineStyle>> m_nodeToInspectorStyleSheet; // bogus "stylesheets" with elements' inline styles
     DocumentToViaInspectorStyleSheet m_documentToInspectorStyleSheet;
     HashMap<Document*, HashSet<CSSStyleSheet*>> m_documentToKnownCSSStyleSheets;
-    NodeIdToForcedPseudoState m_nodeIdToForcedPseudoState;
+    HashMap<Inspector::Protocol::DOM::NodeId, PseudoClassHashSet> m_nodeIdToForcedPseudoState;
     HashSet<Document*> m_documentsWithForcedPseudoStates;
 
     int m_lastStyleSheetId { 1 };

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -1179,6 +1179,8 @@
 /* Property value for `font-variant-alternates: proportional-width`. */
 localizedStrings["Proportional-Width Variants @ Font Details Sidebar Property Value"] = "Proportional-Width Variants";
 localizedStrings["Protocol"] = "Protocol";
+/* Label for button that shows controls for toggling CSS pseudo-classes on the selected element. */
+localizedStrings["Pseudo @ Styles details sidebar panel"] = "Pseudo";
 localizedStrings["Pseudo-Element"] = "Pseudo-Element";
 localizedStrings["Query Parameters"] = "Query Parameters";
 localizedStrings["Query String"] = "Query String";
@@ -1615,6 +1617,7 @@
 localizedStrings["Titling Capitals @ Font Details Sidebar Property Value"] = "Titling Capitals";
 localizedStrings["To improve CPU utilization reduce or batch workloads when the page is not visible or during times when the page is not being interacted with."] = "To improve CPU utilization reduce or batch workloads when the page is not visible or during times when the page is not being interacted with.";
 localizedStrings["Toggle Classes"] = "Toggle Classes";
+localizedStrings["Toggle Pseudo Classes"] = "Toggle Pseudo Classes";
 localizedStrings["Toggle Visibility"] = "Toggle Visibility";
 localizedStrings["Top Functions"] = "Top Functions";
 localizedStrings["Total"] = "Total";

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/CSSManager.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -264,6 +264,27 @@
         }
     }
 
+    static displayNameForForceablePseudoClass(pseudoClass)
+    {
+        switch (pseudoClass) {
+        case WI.CSSManager.ForceablePseudoClass.Active:
+            return WI.unlocalizedString(":active");
+        case WI.CSSManager.ForceablePseudoClass.Focus:
+            return WI.unlocalizedString(":focus");
+        case WI.CSSManager.ForceablePseudoClass.FocusVisible:
+            return WI.unlocalizedString(":focus-visible");
+        case WI.CSSManager.ForceablePseudoClass.FocusWithin:
+            return WI.unlocalizedString(":focus-within");
+        case WI.CSSManager.ForceablePseudoClass.Hover:
+            return WI.unlocalizedString(":hover");
+        case WI.CSSManager.ForceablePseudoClass.Visited:
+            return WI.unlocalizedString(":visited");
+        }
+
+        console.assert(false, "Unknown pseudo class", pseudoClass);
+        return "";
+    }
+
     // Public
 
     get propertyNameCompletions() { return this._propertyNameCompletions; }
@@ -340,9 +361,29 @@
         return InspectorBackend.hasCommand("Page.setForcedAppearance") && this._defaultAppearance;
     }
 
-    canForcePseudoClasses()
+    canForcePseudoClass(pseudoClass)
     {
-        return InspectorBackend.hasCommand("CSS.forcePseudoState");
+        if (!InspectorBackend.hasCommand("CSS.forcePseudoState"))
+            return false;
+
+        if (!pseudoClass)
+            return true;
+
+        switch (pseudoClass) {
+        case WI.CSSManager.ForceablePseudoClass.Active:
+        case WI.CSSManager.ForceablePseudoClass.Focus:
+        case WI.CSSManager.ForceablePseudoClass.Hover:
+        case WI.CSSManager.ForceablePseudoClass.Visited:
+            return true;
+
+        case WI.CSSManager.ForceablePseudoClass.FocusVisible:
+        case WI.CSSManager.ForceablePseudoClass.FocusWithin:
+            // COMPATIBILITY (iOS 15.4): CSS.ForceablePseudoClass did not exist yet.
+            return !!InspectorBackend.Enum.CSS.ForceablePseudoClass;
+        }
+
+        console.assert(false, "Unknown pseudo class", pseudoClass);
+        return false;
     }
 
     propertyNameHasOtherVendorPrefix(name)
@@ -801,5 +842,14 @@
 };
 
 WI.CSSManager.PseudoElementNames = ["before", "after"];
-WI.CSSManager.ForceablePseudoClasses = ["active", "focus", "hover", "visited"];
+
+WI.CSSManager.ForceablePseudoClass = {
+    Active: "active",
+    Focus: "focus",
+    FocusVisible: "focus-visible",
+    FocusWithin: "focus-within",
+    Hover: "hover",
+    Visited: "visited",
+};
+
 WI.CSSManager.PreferredInspectorStyleSheetSymbol = Symbol("css-manager-preferred-inspector-style-sheet");

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ComputedStyleDetailsPanel.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -66,6 +66,21 @@
         return this._boxModelDiagramRow?.minimumWidth ?? 0;
     }
 
+    get supportsToggleCSSClassList()
+    {
+        return true;
+    }
+
+    get supportsToggleCSSForcedPseudoClass()
+    {
+        return true;
+    }
+
+    get initialToggleCSSForcedPseudoClassState()
+    {
+        return false;
+    }
+
     get variablesGroupingMode()
     {
         console.assert(this._variablesGroupingModeScopeBar.selectedItems[0], "No selected variables grouping mode", this._variablesGroupingModeScopeBar.selectedItems);

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -332,18 +332,21 @@
             contextMenu.appendSeparator();
         }
 
-        if (!options.disallowEditing && WI.cssManager.canForcePseudoClasses() && domNode.attached) {
+        if (!options.disallowEditing && WI.cssManager.canForcePseudoClass() && domNode.attached) {
             contextMenu.appendSeparator();
 
             let pseudoSubMenu = contextMenu.appendSubMenuItem(WI.UIString("Forced Pseudo-Classes", "A context menu item to force (override) a DOM node's pseudo-classes"));
 
             let enabledPseudoClasses = domNode.enabledPseudoClasses;
-            WI.CSSManager.ForceablePseudoClasses.forEach((pseudoClass) => {
+            for (let pseudoClass of Object.values(WI.CSSManager.ForceablePseudoClass)) {
+                if (!WI.cssManager.canForcePseudoClass(pseudoClass))
+                    continue;
+
                 let enabled = enabledPseudoClasses.includes(pseudoClass);
-                pseudoSubMenu.appendCheckboxItem(pseudoClass.capitalize(), () => {
+                pseudoSubMenu.appendCheckboxItem(WI.CSSManager.displayNameForForceablePseudoClass(pseudoClass), () => {
                     domNode.setPseudoClassEnabled(pseudoClass, !enabled);
                 }, enabled);
-            });
+            }
         }
 
         if (WI.domDebuggerManager.supported && isElement && !domNode.isPseudoElement() && attached) {

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/FontDetailsPanel.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Views/FontDetailsPanel.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/FontDetailsPanel.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -39,11 +39,6 @@
 
     // Public
 
-    get supportsToggleCSSClass()
-    {
-        return false;
-    }
-
     refresh(significantChange)
     {
         super.refresh(significantChange);

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.css (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.css	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.css	2022-06-17 02:17:50 UTC (rev 295623)
@@ -28,40 +28,17 @@
     position: relative;
 }
 
-.sidebar > .panel.details.css-style > .content > .pseudo-classes {
-    padding: 6px 10px;
-    border-bottom: 1px solid var(--border-color);
-
-    display: flex;
-    flex-flow: row wrap;
-    justify-content: space-around;
+.sidebar > .panel.details.css-style > .content ~ :is(.options-container, .class-list-container, .forced-pseudo-class-container) {
+    /* Make sure options and class containers are above details section headers, but below popovers. */
+    z-index: calc(var(--z-index-header) + 1);
+    width: 100%;
+    border-top: 1px solid var(--border-color);
 }
 
-.sidebar > .panel.details.css-style > .content > .pseudo-classes > .group {
-    display: inline-flex;
-    flex-flow: row wrap;
-    justify-content: space-around;
-    flex: 1;
-}
-
-.sidebar > .panel.details.css-style > .content > .pseudo-classes > .group > label {
-    color: hsl(0, 0%, 37%);
-
-    margin: 0 5px;
-    min-width: 55px;
-
-    display: inline-block;
-    white-space: nowrap;
-}
-
-.sidebar > .panel.details.css-style > .content ~ :matches(.options-container, .class-list-container) {
+.sidebar > .panel.details.css-style > .content ~ :is(.options-container, .class-list-container) {
     display: flex;
     flex-shrink: 0;
     align-items: center;
-    /* Make sure options and class containers are above details section headers, but below popovers. */
-    z-index: calc(var(--z-index-header) + 1);
-    width: 100%;
-    border-top: 1px solid var(--border-color);
 }
 
 .sidebar > .panel.details.css-style > .content ~ .options-container {
@@ -69,7 +46,7 @@
     padding-top: 2px;
 }
 
-.sidebar > .panel.details.css-style > .content:not(.supports-new-rule, .has-filter-bar) ~ :matches(.options-container, .class-list-container) {
+.sidebar > .panel.details.css-style > .content:not(.supports-new-rule, .has-filter-bar) ~ :is(.options-container, .class-list-container, .forced-pseudo-class-container) {
     display: none;
 }
 
@@ -88,7 +65,7 @@
     background-color: transparent;
 }
 
-.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle {
+.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle {
     position: relative;
     margin: 0 0 1px;
     margin-inline-end: 5px;
@@ -101,11 +78,11 @@
     appearance: none;
 }
 
-.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle:focus {
+.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle:focus {
     outline-offset: var(--focus-ring-outline-offset);
 }
 
-.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle::before {
+.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle::before {
     position: absolute;
     content: "";
     left: 0;
@@ -117,41 +94,45 @@
     z-index: -1;
 }
 
-.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle:matches(.selected, :hover) {
+.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle:is(.selected, :hover) {
     color: var(--selected-foreground-color);
 }
 
-.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle:matches(.selected, :hover)::before {
+.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle:is(.selected, :hover)::before {
     background-color: var(--glyph-color-active);
 }
 
-.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle:not(.selected):hover::before {
+.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle:not(.selected):hover::before {
     opacity: 0.5;
 }
 
-.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle-class-toggle.selected:active::before {
+.sidebar > .panel.details.css-style > .content ~ .options-container > .toggle.selected:active::before {
     filter: brightness(0.8);
 }
 
 .sidebar > .panel.details.css-style > .content:not(.supports-new-rule) ~ .options-container > .new-rule,
-.sidebar > .panel.details.css-style > .content:not(.supports-toggle-css-class) ~ .options-container > .toggle-class-toggle,
+.sidebar > .panel.details.css-style > .content:not(.supports-toggle-class-list) ~ .options-container > .toggle.class-list,
+.sidebar > .panel.details.css-style > .content:not(.supports-toggle-forced-pseudo-class) ~ .options-container > .toggle.forced-pseudo-class,
 .sidebar > .panel.details.css-style > .content:not(.has-filter-bar) ~ .options-container > .filter-bar,
-.sidebar > .panel.details.css-style > .content:not(.supports-new-rule):not(.supports-toggle-class):not(.has-filter-bar) ~ .options-container {
+.sidebar > .panel.details.css-style > .content:not(.supports-new-rule):not(.supports-toggle-class-list):not(.supports-toggle-forced-pseudo-class):not(.has-filter-bar) ~ .options-container {
     display: none;
 }
 
-.sidebar > .panel.details.css-style > .content ~ .class-list-container {
-    flex-wrap: wrap;
-    max-height: 75px;
+.sidebar > .panel.details.css-style > .content ~ :is(.class-list-container, .forced-pseudo-class-container) {
     padding: 3px 2px;
     background-color: var(--panel-background-color);
-    overflow-y: auto;
 }
 
-.sidebar > .panel.details.css-style > .content ~ .class-list-container[hidden] {
+.sidebar > .panel.details.css-style > .content ~ :is(.class-list-container, .forced-pseudo-class-container)[hidden] {
     display: none;
 }
 
+.sidebar > .panel.details.css-style > .content ~ .class-list-container {
+    flex-wrap: wrap;
+    max-height: 75px;
+    overflow-y: auto;
+}
+
 .sidebar > .panel.details.css-style > .content ~ .class-list-container > .new-class {
     display: flex;
     width: 100%;
@@ -167,6 +148,15 @@
     margin: 1px 3px;
 }
 
+.sidebar > .panel.details.css-style > .content ~ .forced-pseudo-class-container {
+    display: block;
+    column-count: 2;
+}
+
+.sidebar > .panel.details.css-style > .content ~ .forced-pseudo-class-container > label {
+    display: block;
+}
+
 .sidebar > .panel.details.css-style > .content.filter-in-progress .filter-matching {
     display: inline;
     background-color: hsla(53, 83%, 53%, 0.5);
@@ -202,39 +192,12 @@
 }
 
 @media (prefers-color-scheme: dark) {
-    .sidebar > .panel.details.css-style > .content > .pseudo-classes > .group > label {
-        color: var(--text-color-secondary);
-    }
-
     .sidebar > .panel.details.css-style > .content ~ .options-container > .new-rule {
         filter: var(--filter-invert);
     }
 }
 
-.panel.exclusive-presentation.details.css-style > .content > .pseudo-classes {
-    height: var(--navigation-bar-height);
-    flex-flow: row nowrap;
-    justify-content: center;
-}
-
-.panel.exclusive-presentation.details.css-style > .content > .pseudo-classes > .group {
-    flex-flow: row nowrap;
-    justify-content: center;
-    flex: 0;
-}
-
-.panel.exclusive-presentation.details.css-style > .content > .rules {
-    position: absolute; 
-    top: var(--navigation-bar-height);
-    left:0;
-    right:0;
-    bottom:0;
-    overflow-x: hidden;
-    overflow-y: auto;
-}
-
-.multi-sidebar.showing-multiple > .sidebar > .panel.details:not(.style-rules) > .content > .pseudo-classes,
-.multi-sidebar.showing-multiple > .sidebar > .panel.details:not(.style-rules) > .options-container > .toggle-class-toggle,
-.multi-sidebar.showing-multiple > .sidebar > .panel.details:not(.style-rules) > .class-list-container {
+.multi-sidebar.showing-multiple > .sidebar > .panel.details:not(.style-rules) > .options-container > .toggle,
+.multi-sidebar.showing-multiple > .sidebar > .panel.details:not(.style-rules) > :is(.class-list-container, .forced-pseudo-class-container) {
     display: none;
 }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/GeneralStyleDetailsSidebarPanel.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -35,8 +35,13 @@
         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 = {};
+        if (this._panel.supportsToggleCSSClassList && InspectorBackend.hasCommand("DOM.resolveNode"))
+            this._classListContainerToggledSetting = new WI.Setting(identifier + "-class-list-container-toggled", !!WI.Setting.migrateValue("class-list-container-toggled"));
+
+        if (this._panel.supportsToggleCSSForcedPseudoClass && WI.cssManager.canForcePseudoClass()) {
+            this._forcedPseudoClassContainerToggledSetting = new WI.Setting(identifier + "-forced-pseudo-class-container-toggled", this._panel.initialToggleCSSForcedPseudoClassState);
+            this._checkboxForForcedPseudoClass = new Map;
+        }
     }
 
     // Public
@@ -45,16 +50,7 @@
 
     get minimumWidth()
     {
-        let minimumWidth = Math.max(super.minimumWidth, this._panel.minimumWidth || 0);
-
-        if (this._forcedPseudoClassContainer && this.exclusive) {
-            let pseudoClassMinimumWidth = 0;
-            for (let child of this._forcedPseudoClassContainer.children)
-                pseudoClassMinimumWidth += child.offsetWidth;
-            minimumWidth = Math.max(minimumWidth, pseudoClassMinimumWidth);
-        }
-
-        return minimumWidth;
+        return Math.max(super.minimumWidth, this._panel.minimumWidth || 0);
     }
 
     supportsDOMNode(nodeToInspect)
@@ -71,17 +67,11 @@
 
         console.assert(this.visible, `Shown panel ${this._identifier} must be visible.`);
 
-        this._updateNoForcedPseudoClassesScrollOffset();
         this._panel.markAsNeedsRefresh(this.domNode);
     }
 
     // StyleDetailsPanel delegate
 
-    styleDetailsPanelFocusLastPseudoClassCheckbox(styleDetailsPanel)
-    {
-        this._forcedPseudoClassCheckboxes[WI.CSSManager.ForceablePseudoClasses.lastValue].focus();
-    }
-
     styleDetailsPanelFocusFilterBar(styleDetailsPanel)
     {
         if (this._filterBar)
@@ -96,11 +86,14 @@
         if (!domNode || domNode.destroyed)
             return;
 
-        this.contentView.element.scrollTop = this._initialScrollOffset;
+        this.contentView.element.scrollTop = 0;
         this._panel.markAsNeedsRefresh(domNode);
 
-        this._updatePseudoClassCheckboxes();
-        this._populateClassToggles();
+        if (this._forcedPseudoClassContainerToggledSetting)
+            this._updatePseudoClassCheckboxes();
+
+        if (this._classListContainerToggledSetting)
+            this._populateClassToggles();
     }
 
     addEventListeners()
@@ -109,9 +102,13 @@
         if (!effectiveDOMNode)
             return;
 
-        effectiveDOMNode.addEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this);
-        effectiveDOMNode.addEventListener(WI.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this);
-        effectiveDOMNode.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this);
+        if (this._forcedPseudoClassContainerToggledSetting)
+            effectiveDOMNode.addEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this);
+
+        if (this._classListContainerToggledSetting) {
+            effectiveDOMNode.addEventListener(WI.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this);
+            effectiveDOMNode.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this);
+        }
     }
 
     removeEventListeners()
@@ -120,52 +117,21 @@
         if (!effectiveDOMNode)
             return;
 
-        effectiveDOMNode.removeEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this);
-        effectiveDOMNode.removeEventListener(WI.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this);
-        effectiveDOMNode.removeEventListener(WI.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this);
+        if (this._forcedPseudoClassContainerToggledSetting)
+            effectiveDOMNode.removeEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this);
+
+        if (this._classListContainerToggledSetting) {
+            effectiveDOMNode.removeEventListener(WI.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this);
+            effectiveDOMNode.removeEventListener(WI.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this);
+        }
     }
 
     initialLayout()
     {
-        if (WI.cssManager.canForcePseudoClasses()) {
-            this._forcedPseudoClassContainer = document.createElement("div");
-            this._forcedPseudoClassContainer.className = "pseudo-classes";
-
-            let groupElement = null;
-
-            WI.CSSManager.ForceablePseudoClasses.forEach(function(pseudoClass) {
-                // We don't localize the label since it is a CSS pseudo-class from the CSS standard.
-                let label = pseudoClass.capitalize();
-
-                let labelElement = document.createElement("label");
-
-                let checkboxElement = document.createElement("input");
-                checkboxElement.addEventListener("keydown", this._handleForcedPseudoClassCheckboxKeydown.bind(this, pseudoClass));
-                checkboxElement.addEventListener("change", this._forcedPseudoClassCheckboxChanged.bind(this, pseudoClass));
-                checkboxElement.type = "checkbox";
-
-                this._forcedPseudoClassCheckboxes[pseudoClass] = checkboxElement;
-
-                labelElement.appendChild(checkboxElement);
-                labelElement.append(label);
-
-                if (!groupElement || groupElement.children.length === 2) {
-                    groupElement = document.createElement("div");
-                    groupElement.className = "group";
-                    this._forcedPseudoClassContainer.appendChild(groupElement);
-                }
-
-                groupElement.appendChild(labelElement);
-            }, this);
-
-            this.contentView.element.appendChild(this._forcedPseudoClassContainer);
-        }
-
         this._showPanel(this._panel);
 
-        if (InspectorBackend.hasCommand("DOM.resolveNode")) {
+        if (this._classListContainerToggledSetting) {
             this._classListContainer = this.element.createChild("div", "class-list-container");
-            this._classListContainer.hidden = true;
 
             this._addClassContainer = this._classListContainer.createChild("div", "new-class");
             this._addClassContainer.title = WI.UIString("Add a Class");
@@ -178,6 +144,25 @@
             this._addClassInput.addEventListener("blur", this._addClassInputBlur.bind(this));
         }
 
+        if (this._forcedPseudoClassContainerToggledSetting) {
+            this._forcedPseudoClassContainer = this.element.appendChild(document.createElement("div"));
+            this._forcedPseudoClassContainer.className = "forced-pseudo-class-container";
+
+            for (let pseudoClass of Object.values(WI.CSSManager.ForceablePseudoClass)) {
+                if (!WI.cssManager.canForcePseudoClass(pseudoClass))
+                    continue;
+
+                let labelElement = this._forcedPseudoClassContainer.appendChild(document.createElement("label"));
+
+                let checkboxElement = labelElement.appendChild(document.createElement("input"));
+                checkboxElement.addEventListener("change", this._forcedPseudoClassCheckboxChanged.bind(this, pseudoClass));
+                checkboxElement.type = "checkbox";
+                this._checkboxForForcedPseudoClass.set(pseudoClass, checkboxElement);
+
+                labelElement.append(WI.CSSManager.displayNameForForceablePseudoClass(pseudoClass));
+            }
+        }
+
         let optionsContainer = this.element.createChild("div", "options-container");
 
         let newRuleButton = optionsContainer.createChild("img", "new-rule");
@@ -192,16 +177,25 @@
             optionsContainer.appendChild(this._filterBar.element);
         }
 
-        if (this._classListContainer) {
-            this._classToggleButton = optionsContainer.createChild("button", "toggle-class-toggle");
-            this._classToggleButton.textContent = WI.UIString("Classes");
-            this._classToggleButton.title = WI.UIString("Toggle Classes");
-            this._classToggleButton.addEventListener("click", this._classToggleButtonClicked.bind(this));
+        if (this._classListContainerToggledSetting) {
+            this._classListToggleButton = optionsContainer.createChild("button", "toggle class-list");
+            this._classListToggleButton.textContent = WI.UIString("Classes");
+            this._classListToggleButton.title = WI.UIString("Toggle Classes");
+            this._classListToggleButton.addEventListener("click", this._classListToggleButtonClicked.bind(this));
 
-            if (this._classListContainerToggledSetting.value)
-                this._classToggleButtonClicked();
+            this._updateClassListContainer();
         }
 
+        if (this._forcedPseudoClassContainerToggledSetting) {
+            this._forcedPseudoClassToggleButton = optionsContainer.appendChild(document.createElement("button"));
+            this._forcedPseudoClassToggleButton.className = "toggle forced-pseudo-class";
+            this._forcedPseudoClassToggleButton.textContent = WI.UIString("Pseudo", "Pseudo @ Styles details sidebar panel", "Label for button that shows controls for toggling CSS pseudo-classes on the selected element.");
+            this._forcedPseudoClassToggleButton.title = WI.UIString("Toggle Pseudo Classes");
+            this._forcedPseudoClassToggleButton.addEventListener("click", this._forcedPseudoClassToggleButtonClicked.bind(this));
+
+            this._updateForcedPseudoClassContainer();
+        }
+
         WI.cssManager.addEventListener(WI.CSSManager.Event.StyleSheetAdded, this._styleSheetAddedOrRemoved, this);
         WI.cssManager.addEventListener(WI.CSSManager.Event.StyleSheetRemoved, this._styleSheetAddedOrRemoved, this);
     }
@@ -210,8 +204,6 @@
     {
         super.sizeDidChange();
 
-        this._updateNoForcedPseudoClassesScrollOffset();
-
         if (this._panel)
             this._panel.sizeDidChange();
     }
@@ -218,19 +210,6 @@
 
     // Private
 
-    get _initialScrollOffset()
-    {
-        if (!WI.cssManager.canForcePseudoClasses())
-            return 0;
-        return this.domNode && this.domNode.enabledPseudoClasses.length ? 0 : WI.GeneralStyleDetailsSidebarPanel.NoForcedPseudoClassesScrollOffset;
-    }
-
-    _updateNoForcedPseudoClassesScrollOffset()
-    {
-        if (this._forcedPseudoClassContainer)
-            WI.GeneralStyleDetailsSidebarPanel.NoForcedPseudoClassesScrollOffset = this._forcedPseudoClassContainer.offsetHeight;
-    }
-
     _showPanel()
     {
         this.contentView.addSubview(this._panel);
@@ -239,24 +218,27 @@
             this.contentView.element.classList.toggle(WI.GeneralStyleDetailsSidebarPanel.FilterInProgressClassName, this._filterBar.hasActiveFilters());
     }
 
-    _handleNodeChanged(event)
+    _updateClassListContainer()
     {
-        this.contentView.element.classList.toggle("supports-new-rule", this._panel.supportsNewRule);
-        this.contentView.element.classList.toggle("supports-toggle-css-class", this._panel.supportsToggleCSSClass);
+        let hidden = !this._classListContainerToggledSetting.value;
+        this._classListToggleButton.classList.toggle("selected", !hidden);
+        this._classListContainer.hidden = hidden;
+
+        this._populateClassToggles();
     }
 
-    _handleForcedPseudoClassCheckboxKeydown(pseudoClass, event)
+    _updateForcedPseudoClassContainer()
     {
-        if (event.key !== "Tab" || event.shiftKey)
-            return;
+        let hidden = !this._forcedPseudoClassContainerToggledSetting.value;
+        this._forcedPseudoClassToggleButton.classList.toggle("selected", !hidden);
+        this._forcedPseudoClassContainer.hidden = hidden;
+    }
 
-        if (WI.CSSManager.ForceablePseudoClasses.lastValue === pseudoClass) {
-            // Last checkbox is currently focused.
-            if (this._panel.focusFirstSection) {
-                this._panel.focusFirstSection();
-                event.preventDefault();
-            }
-        }
+    _handleNodeChanged(event)
+    {
+        this.contentView.element.classList.toggle("supports-new-rule", this._panel.supportsNewRule);
+        this.contentView.element.classList.toggle("supports-toggle-class-list", this._panel.supportsToggleCSSClassList);
+        this.contentView.element.classList.toggle("supports-toggle-forced-pseudo-class", this._panel.supportsToggleCSSForcedPseudoClass);
     }
 
     _forcedPseudoClassCheckboxChanged(pseudoClass, event)
@@ -270,7 +252,7 @@
 
         effectiveDOMNode.setPseudoClassEnabled(pseudoClass, event.target.checked);
 
-        this._forcedPseudoClassCheckboxes[pseudoClass].focus();
+        this._checkboxForForcedPseudoClass.get(pseudoClass).focus();
     }
 
     _updatePseudoClassCheckboxes()
@@ -284,10 +266,8 @@
 
         let enabledPseudoClasses = effectiveDOMNode.enabledPseudoClasses;
 
-        for (let pseudoClass in this._forcedPseudoClassCheckboxes) {
-            let checkboxElement = this._forcedPseudoClassCheckboxes[pseudoClass];
+        for (let [pseudoClass, checkboxElement] of this._checkboxForForcedPseudoClass)
             checkboxElement.checked = enabledPseudoClasses.includes(pseudoClass);
-        }
     }
 
     _handleNodeAttributeModified(event)
@@ -314,14 +294,28 @@
             this._panel.newRuleButtonContextMenu(event);
     }
 
-    _classToggleButtonClicked(event)
+    _classListToggleButtonClicked(event)
     {
-        this._classToggleButton.classList.toggle("selected");
-        this._classListContainer.hidden = !this._classListContainer.hidden;
-        this._classListContainerToggledSetting.value = !this._classListContainer.hidden;
-        this._populateClassToggles();
+        if (this._forcedPseudoClassContainerToggledSetting) {
+            this._forcedPseudoClassContainerToggledSetting.value = false;
+            this._updateForcedPseudoClassContainer();
+        }
+
+        this._classListContainerToggledSetting.value = !this._classListContainerToggledSetting.value;
+        this._updateClassListContainer();
     }
 
+    _forcedPseudoClassToggleButtonClicked(event)
+    {
+        if (this._classListContainerToggledSetting) {
+            this._classListContainerToggledSetting.value = false;
+            this._updateClassListContainer();
+        }
+
+        this._forcedPseudoClassContainerToggledSetting.value = !this._forcedPseudoClassContainerToggledSetting.value;
+        this._updateForcedPseudoClassContainer();
+    }
+
     _addClassContainerClicked(event)
     {
         this._addClassContainer.classList.add("active");
@@ -448,7 +442,6 @@
     }
 };
 
-WI.GeneralStyleDetailsSidebarPanel.NoForcedPseudoClassesScrollOffset = 30; // Default height of the forced pseudo classes container. Updated in sizeDidChange.
 WI.GeneralStyleDetailsSidebarPanel.FilterInProgressClassName = "filter-in-progress";
 WI.GeneralStyleDetailsSidebarPanel.FilterMatchingSectionHasLabelClassName = "filter-section-has-label";
 WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName = "filter-matching";

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -526,17 +526,19 @@
                     createNewRule(selector, text);
                 };
 
-                if (WI.CSSManager.ForceablePseudoClasses.every((className) => !this._style.selectorText.includes(":" + className))) {
+                if (WI.cssManager.canForcePseudoClass() && Object.values(WI.CSSManager.ForceablePseudoClass).every((className) => !this._style.selectorText.includes(":" + className))) {
                     contextMenu.appendSeparator();
 
-                     for (let pseudoClass of WI.CSSManager.ForceablePseudoClasses) {
-                        if (pseudoClass === "visited" && this._style.node.nodeName() !== "A")
+                    for (let pseudoClass of Object.values(WI.CSSManager.ForceablePseudoClass)) {
+                        if (!WI.cssManager.canForcePseudoClass(pseudoClass))
                             continue;
 
+                        if (pseudoClass === WI.CSSManager.ForceablePseudoClass.Visited && this._style.node.nodeName() !== "A")
+                            continue;
+
                         let pseudoClassSelector = ":" + pseudoClass;
                         contextMenu.appendItem(WI.UIString("Add %s Rule").format(pseudoClassSelector), () => {
-                            if (WI.cssManager.canForcePseudoClasses())
-                                this._style.node.setPseudoClassEnabled(pseudoClass, true);
+                            this._style.node.setPseudoClassEnabled(pseudoClass, true);
 
                             addPseudoRule(pseudoClassSelector);
                         });

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetRulesStyleDetailsPanel.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -54,6 +54,21 @@
         return this.nodeStyles && !this.nodeStyles.node.isInUserAgentShadowTree() && InspectorBackend.hasCommand("CSS.addRule");
     }
 
+    get supportsToggleCSSClassList()
+    {
+        return true;
+    }
+
+    get supportsToggleCSSForcedPseudoClass()
+    {
+        return true;
+    }
+
+    get initialToggleCSSForcedPseudoClassState()
+    {
+        return true;
+    }
+
     refresh(significantChange)
     {
         // We only need to do a rebuild on significant changes. Other changes are handled
@@ -168,14 +183,9 @@
         index += delta;
 
         while (this._sections[index] !== currentSection) {
-            if (index < 0) {
-                if (this._delegate && this._delegate.styleDetailsPanelFocusLastPseudoClassCheckbox) {
-                    this._delegate.styleDetailsPanelFocusLastPseudoClassCheckbox(this);
-                    break;
-                }
-
+            if (index < 0)
                 index = this._sections.length - 1;
-            } else if (index >= this._sections.length) {
+            else if (index >= this._sections.length) {
                 if (this._delegate && this._delegate.styleDetailsPanelFocusFilterBar) {
                     this._delegate.styleDetailsPanelFocusFilterBar(this);
                     break;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/StyleDetailsPanel.js (295622 => 295623)


--- trunk/Source/WebInspectorUI/UserInterface/Views/StyleDetailsPanel.js	2022-06-17 01:43:11 UTC (rev 295622)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/StyleDetailsPanel.js	2022-06-17 02:17:50 UTC (rev 295623)
@@ -56,12 +56,18 @@
         return false;
     }
 
-    get supportsToggleCSSClass()
+    get supportsToggleCSSClassList()
     {
         // Overriden by subclasses if needed.
-        return true;
+        return false;
     }
 
+    get supportsToggleCSSForcedPseudoClass()
+    {
+        // Overriden by subclasses if needed.
+        return false;
+    }
+
     attached()
     {
         super.attached();
@@ -114,13 +120,6 @@
 
     // Private
 
-    get _initialScrollOffset()
-    {
-        if (!WI.cssManager.canForcePseudoClasses())
-            return 0;
-        return this.nodeStyles.node.enabledPseudoClasses.length ? 0 : WI.GeneralStyleDetailsSidebarPanel.NoForcedPseudoClassesScrollOffset;
-    }
-
     _refreshNodeStyles()
     {
         if (!this._nodeStyles)
@@ -132,7 +131,7 @@
     {
         significantChange = this._forceSignificantChange || significantChange || false;
 
-        var previousScrollTop = this._initialScrollOffset;
+        let previousScrollTop = 0;
 
         // Only remember the scroll position if the previous node is the same as this one.
         if (this.element.parentNode && this._previousRefreshNodeIdentifier === this._nodeStyles.node.id)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to