Title: [286494] trunk/Source/WebCore
Revision
286494
Author
an...@apple.com
Date
2021-12-03 09:10:13 -0800 (Fri, 03 Dec 2021)

Log Message

[:has() pseudo-class] Improve result caching
https://bugs.webkit.org/show_bug.cgi?id=233806

Reviewed by Simon Fraser.

Improve caching to to avoid O(n^2) cases during invalidation and style resolution.

* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::matchHasPseudoClass const):

Cache matches as well as failures.
Cache failures that don't apply to subtrees.
Check cached matches and failures for all :has() match element types.

* style/SelectorMatchingState.h:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (286493 => 286494)


--- trunk/Source/WebCore/ChangeLog	2021-12-03 17:08:36 UTC (rev 286493)
+++ trunk/Source/WebCore/ChangeLog	2021-12-03 17:10:13 UTC (rev 286494)
@@ -1,3 +1,21 @@
+2021-12-03  Antti Koivisto  <an...@apple.com>
+
+        [:has() pseudo-class] Improve result caching
+        https://bugs.webkit.org/show_bug.cgi?id=233806
+
+        Reviewed by Simon Fraser.
+
+        Improve caching to to avoid O(n^2) cases during invalidation and style resolution.
+
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::matchHasPseudoClass const):
+
+        Cache matches as well as failures.
+        Cache failures that don't apply to subtrees.
+        Check cached matches and failures for all :has() match element types.
+
+        * style/SelectorMatchingState.h:
+
 2021-12-03  Alex Christensen  <achristen...@webkit.org>
 
         Add room for more bytecode in WKContentRuleList file format

Modified: trunk/Source/WebCore/css/SelectorChecker.cpp (286493 => 286494)


--- trunk/Source/WebCore/css/SelectorChecker.cpp	2021-12-03 17:08:36 UTC (rev 286493)
+++ trunk/Source/WebCore/css/SelectorChecker.cpp	2021-12-03 17:10:13 UTC (rev 286494)
@@ -1249,8 +1249,22 @@
 
 bool SelectorChecker::matchHasPseudoClass(CheckingContext& checkingContext, const Element& element, const CSSSelector& hasSelector) const
 {
-    // FIXME: This could be better in terms of performance.
+    auto* cache = checkingContext.selectorMatchingState ? &checkingContext.selectorMatchingState->hasPseudoClassMatchCache : nullptr;
 
+    Style::HasPseudoClassMatch* cachedMatch = nullptr;
+    if (cache) {
+        cachedMatch = &cache->add(Style::makeHasPseudoClassCacheKey(hasSelector, element), Style::HasPseudoClassMatch::None).iterator->value;
+        switch (*cachedMatch) {
+        case Style::HasPseudoClassMatch::Matches:
+            return true;
+        case Style::HasPseudoClassMatch::Fails:
+        case Style::HasPseudoClassMatch::FailsSubtree:
+            return false;
+        case Style::HasPseudoClassMatch::None:
+            break;
+        }
+    }
+
     SelectorChecker hasChecker(element.document());
     bool matchedInsideScope = false;
 
@@ -1267,69 +1281,90 @@
     };
 
     auto checkDescendants = [&](const Element& descendantRoot) {
-        auto descendants = descendantsOfType<Element>(descendantRoot);
-        if (!descendants.first())
-            return false;
-
-        if (checkingContext.selectorMatchingState) {
-            // See if we already know this :has() selector doesn't match in this subtree.
-            auto& failureCache = checkingContext.selectorMatchingState->hasPseudoClassDescendantFailureCache;
-            if (!failureCache.isEmpty()) {
-                for (auto* ancestor = descendantRoot.parentElement(); ancestor; ancestor = ancestor->parentElement()) {
-                    if (failureCache.contains(Style::makeHasPseudoClassCacheKey(hasSelector, *ancestor)))
-                        return false;
+        for (auto it = descendantsOfType<Element>(descendantRoot).begin(); it;) {
+            auto& descendant = *it;
+            if (cache) {
+                auto key = Style::makeHasPseudoClassCacheKey(hasSelector, descendant);
+                if (cache->get(key) == Style::HasPseudoClassMatch::FailsSubtree) {
+                    it.traverseNextSkippingChildren();
+                    continue;
                 }
             }
-        }
-
-        matchedInsideScope = false;
-
-        for (auto& descendant : descendants) {
             if (checkRelative(descendant))
                 return true;
-        }
 
-        if (checkingContext.selectorMatchingState && !matchedInsideScope) {
-            auto& failureCache = checkingContext.selectorMatchingState->hasPseudoClassDescendantFailureCache;
-            failureCache.add(Style::makeHasPseudoClassCacheKey(hasSelector, descendantRoot));
+            it.traverseNext();
         }
 
         return false;
     };
 
-    auto matchElement = Style::computeHasPseudoClassMatchElement(hasSelector);
+    auto match = [&] {
+        auto matchElement = Style::computeHasPseudoClassMatchElement(hasSelector);
 
-    switch (matchElement) {
-    case Style::MatchElement::HasChild:
-        for (auto& child : childrenOfType<Element>(element)) {
-            if (checkRelative(child))
+        switch (matchElement) {
+        // :has(> .child)
+        case Style::MatchElement::HasChild:
+            for (auto& child : childrenOfType<Element>(element)) {
+                if (checkRelative(child))
+                    return true;
+            }
+            break;
+        // :has(.descendant)
+        case Style::MatchElement::HasDescendant: {
+            if (!element.firstElementChild())
+                return false;
+            if (cache) {
+                // See if we already know this descendant selector doesn't match in this subtree.
+                for (auto* ancestor = element.parentElement(); ancestor; ancestor = ancestor->parentElement()) {
+                    auto key = Style::makeHasPseudoClassCacheKey(hasSelector, *ancestor);
+                    if (cache->get(key) == Style::HasPseudoClassMatch::FailsSubtree)
+                        return false;
+                }
+            }
+            if (checkDescendants(element))
                 return true;
+            break;
         }
-        break;
-    case Style::MatchElement::HasDescendant: {
-        if (checkDescendants(element))
-            return true;
-        break;
-    }
-    case Style::MatchElement::HasSibling:
-        for (auto* sibling = element.nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
-            if (checkRelative(*sibling))
-                return true;
+        // FIXME: Add a separate case for adjacent combinator.
+        // :has(+ .sibling)
+        // :has(~ .sibling)
+        case Style::MatchElement::HasSibling:
+            for (auto* sibling = element.nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
+                if (checkRelative(*sibling))
+                    return true;
+            }
+            break;
+        // FIXME: Add a separate case for adjacent combinator.
+        // :has(+ .sibling .descendant)
+        // :has(~ .sibling .descendant)
+        case Style::MatchElement::HasSiblingDescendant:
+            for (auto* sibling = element.nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
+                if (checkDescendants(*sibling))
+                    return true;
+            }
+            break;
+
+        default:
+            ASSERT_NOT_REACHED();
+            break;
         }
-        break;
-    case Style::MatchElement::HasSiblingDescendant:
-        for (auto* sibling = element.nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
-            if (checkDescendants(*sibling))
-                return true;
-        }
-        break;
+        return false;
+    };
 
-    default:
-        ASSERT_NOT_REACHED();
-        break;
+    auto result = match();
+
+    if (cachedMatch) {
+        *cachedMatch = [&] {
+            if (result)
+                return Style::HasPseudoClassMatch::Matches;
+            if (matchedInsideScope)
+                return Style::HasPseudoClassMatch::Fails;
+            return Style::HasPseudoClassMatch::FailsSubtree;
+        }();
     }
 
-    return false;
+    return result;
 }
 
 bool SelectorChecker::checkScrollbarPseudoClass(const CheckingContext& checkingContext, const Element& element, const CSSSelector& selector) const

Modified: trunk/Source/WebCore/style/SelectorMatchingState.h (286493 => 286494)


--- trunk/Source/WebCore/style/SelectorMatchingState.h	2021-12-03 17:08:36 UTC (rev 286493)
+++ trunk/Source/WebCore/style/SelectorMatchingState.h	2021-12-03 17:10:13 UTC (rev 286494)
@@ -25,15 +25,17 @@
 #pragma once
 
 #include "SelectorFilter.h"
-#include <wtf/HashSet.h>
+#include <wtf/HashMap.h>
 
 namespace WebCore::Style {
 
 using HasPseudoClassCacheKey = std::pair<const CSSSelector*, const Element*>;
 
+enum class HasPseudoClassMatch : uint8_t { None, Matches, Fails, FailsSubtree };
+
 struct SelectorMatchingState {
     SelectorFilter selectorFilter;
-    HashSet<HasPseudoClassCacheKey> hasPseudoClassDescendantFailureCache;
+    HashMap<HasPseudoClassCacheKey, HasPseudoClassMatch> hasPseudoClassMatchCache;
 };
 
 inline HasPseudoClassCacheKey makeHasPseudoClassCacheKey(const CSSSelector& selector, const Element& element)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to