Title: [286365] trunk
Revision
286365
Author
an...@apple.com
Date
2021-12-01 09:04:27 -0800 (Wed, 01 Dec 2021)

Log Message

[:has() pseudo-class] Sibling combinator invalidation
https://bugs.webkit.org/show_bug.cgi?id=233696

Reviewed by Simon Fraser.

LayoutTests/imported/w3c:

* web-platform-tests/css/selectors/invalidation/has-sibling-expected.txt: Added.
* web-platform-tests/css/selectors/invalidation/has-sibling.html: Added.

Source/WebCore:

Invalidate style correctly for sibling combinators in :has() arguments.

Also add new MatchElement::HasSiblingDescendant value used for :has() arguments that
match the subject elements siblings descendants, like ':has(~ div .descendant)'.
Use it to avoid unnecessary big tree traversals in simple cases.

Test: imported/w3c/web-platform-tests/css/selectors/invalidation/has-sibling.html

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

Minimal traversals for HasSibling and HasSiblingDescendant.

* style/ChildChangeInvalidation.cpp:
(WebCore::Style::needsTraversal):
(WebCore::Style::needsDescendantTraversal):
* style/RuleFeature.cpp:
(WebCore::Style::isSiblingOrSubject):
(WebCore::Style::isHasPseudoClassMatchElement):
(WebCore::Style::computeHasPseudoClassMatchElement):
* style/RuleFeature.h:
* style/StyleInvalidator.cpp:
(WebCore::Style::Invalidator::invalidateStyleWithMatchElement):

Invalidation traversal.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (286364 => 286365)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2021-12-01 16:50:06 UTC (rev 286364)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2021-12-01 17:04:27 UTC (rev 286365)
@@ -1,3 +1,13 @@
+2021-12-01  Antti Koivisto  <an...@apple.com>
+
+        [:has() pseudo-class] Sibling combinator invalidation
+        https://bugs.webkit.org/show_bug.cgi?id=233696
+
+        Reviewed by Simon Fraser.
+
+        * web-platform-tests/css/selectors/invalidation/has-sibling-expected.txt: Added.
+        * web-platform-tests/css/selectors/invalidation/has-sibling.html: Added.
+
 2021-12-01  Patrick Griffis  <pgrif...@igalia.com>
 
         CSP: Update URL stripping in reports to match other implementations

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-sibling-expected.txt (0 => 286365)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-sibling-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-sibling-expected.txt	2021-12-01 17:04:27 UTC (rev 286365)
@@ -0,0 +1,73 @@
+
+PASS initial_color
+PASS add .test to first_sibling
+PASS remove .test from first_sibling
+PASS add .test to second_sibling
+PASS remove .test from second_sibling
+PASS add .test to third_sibling
+PASS remove .test from third_sibling
+PASS add .test to first_sibling_child
+PASS remove .test from first_sibling_child
+PASS add .test to first_sibling_descendant
+PASS remove .test from first_sibling_descendant
+PASS add .test to third_sibling_child
+PASS remove .test from third_sibling_child
+PASS add .test to third_sibling_descendant
+PASS remove .test from third_sibling_descendant
+PASS insert element div.test before first_sibling
+PASS remove element div.test before first_sibling
+PASS insert element div.test before second_sibling
+PASS remove element div.test before second_sibling
+PASS insert element div.test before third_sibling
+PASS remove element div.test before third_sibling
+PASS insert element div.test before first_sibling_child
+PASS remove element div.test before first_sibling_child
+PASS insert element div.test before first_sibling_descendant
+PASS remove element div.test before first_sibling_descendant
+PASS insert element div.test before third_sibling_child
+PASS remove element div.test before third_sibling_child
+PASS insert element div.test before third_sibling_descendant
+PASS remove element div.test before third_sibling_descendant
+PASS insert element div.test after first_sibling
+PASS remove element div.test after first_sibling
+PASS insert element div.test after second_sibling
+PASS remove element div.test after second_sibling
+PASS insert element div.test after third_sibling
+PASS remove element div.test after third_sibling
+PASS insert element div.test after first_sibling_child
+PASS remove element div.test after first_sibling_child
+PASS insert element div.test after first_sibling_descendant
+PASS remove element div.test after first_sibling_descendant
+PASS insert element div.test after third_sibling_child
+PASS remove element div.test after third_sibling_child
+PASS insert element div.test after third_sibling_descendant
+PASS remove element div.test after third_sibling_descendant
+PASS insert tree div>div.test before first_sibling
+PASS remove tree div>div.test before first_sibling
+PASS insert tree div>div.test before second_sibling
+PASS remove tree div>div.test before second_sibling
+PASS insert tree div>div.test before third_sibling
+PASS remove tree div>div.test before third_sibling
+PASS insert tree div>div.test before first_sibling_child
+PASS remove tree div>div.test before first_sibling_child
+PASS insert tree div>div.test before first_sibling_descendant
+PASS remove tree div>div.test before first_sibling_descendant
+PASS insert tree div>div.test before third_sibling_child
+PASS remove tree div>div.test before third_sibling_child
+PASS insert tree div>div.test before third_sibling_descendant
+PASS remove tree div>div.test before third_sibling_descendant
+PASS insert tree div>div.test after first_sibling
+PASS remove tree div>div.test after first_sibling
+PASS insert tree div>div.test after second_sibling
+PASS remove tree div>div.test after second_sibling
+PASS insert tree div>div.test after third_sibling
+PASS remove tree div>div.test after third_sibling
+PASS insert tree div>div.test after first_sibling_child
+PASS remove tree div>div.test after first_sibling_child
+PASS insert tree div>div.test after first_sibling_descendant
+PASS remove tree div>div.test after first_sibling_descendant
+PASS insert tree div>div.test after third_sibling_child
+PASS remove tree div>div.test after third_sibling_child
+PASS insert tree div>div.test after third_sibling_descendant
+PASS remove tree div>div.test after third_sibling_descendant
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-sibling.html (0 => 286365)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-sibling.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-sibling.html	2021-12-01 17:04:27 UTC (rev 286365)
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Selector Invalidation: :has() with sibling combinator argument</title>
+<link rel="author" title="Antti Koivisto" href=""
+<script src=""
+<script src=""
+<link rel="help" href=""
+<style>
+div, main { color: grey }
+#subject:has(~ .test) { color: red }
+#subject:has(+ .test) { color: green }
+#subject:has(~ div .test) { color: blue }
+#subject:has(~ div > .test) { color: purple }
+#subject:has(+ div .test) { color: yellow }
+#subject:has(+ div > .test) { color: pink }
+</style>
+
+<main id=main>
+    <div id=subject></div>
+    <div id=first_sibling>
+        <div id=first_sibling_child>
+            <div id=first_sibling_descendant></div>
+        </div>
+    </div>
+    <div id=second_sibling></div>
+    <div id=third_sibling>
+        <div id=third_sibling_child>
+            <div id=third_sibling_descendant></div>
+        </div>
+    </div>
+</main>
+<script>
+
+const grey = 'rgb(128, 128, 128)';
+const red = 'rgb(255, 0, 0)';
+const green = 'rgb(0, 128, 0)';
+const blue = 'rgb(0, 0, 255)';
+const yellow = 'rgb(255, 255, 0)';
+const purple = 'rgb(128, 0, 128)';
+const pink = 'rgb(255, 192, 203)';
+
+function testColor(test_name, color) {
+    test(function() {
+        assert_equals(getComputedStyle(subject).color, color);
+    }, test_name);
+}
+
+function testClassChange(element, expectedColor)
+{
+    element.classList.add('test');
+    testColor(`add .test to ${element.id}`, expectedColor);
+    element.classList.remove('test');
+    testColor(`remove .test from ${element.id}`, grey);
+}
+
+function testElementInsertionBefore(beforeElement, expectedColor)
+{
+    const newElement = document.createElement('div');
+    newElement.classList.add('test')
+
+    beforeElement.before(newElement);
+    testColor(`insert element div.test before ${beforeElement.id}`, expectedColor);
+
+    newElement.remove();
+    testColor(`remove element div.test before ${beforeElement.id}`, grey);
+}
+
+function testElementInsertionAfter(afterElement, expectedColor)
+{
+    const newElement = document.createElement('div');
+    newElement.classList.add('test')
+
+    afterElement.after(newElement);
+    testColor(`insert element div.test after ${afterElement.id}`, expectedColor);
+
+    newElement.remove();
+    testColor(`remove element div.test after ${afterElement.id}`, grey);
+}
+
+function testTreeInsertionBefore(beforeElement, expectedColor)
+{
+    const newElement = document.createElement('div');
+    const newChild = document.createElement('div');
+    newChild.classList.add('test');
+    newElement.appendChild(newChild);
+
+    beforeElement.before(newElement);
+    testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColor);
+
+    newElement.remove();
+    testColor(`remove tree div>div.test before ${beforeElement.id}`, grey);
+}
+
+function testTreeInsertionAfter(afterElement, expectedColor)
+{
+    const newElement = document.createElement('div');
+    const newChild = document.createElement('div');
+    newChild.classList.add('test');
+    newElement.appendChild(newChild);
+
+    afterElement.after(newElement);
+    testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColor);
+
+    newElement.remove();
+    testColor(`remove tree div>div.test after ${afterElement.id}`, grey);
+}
+
+testColor('initial_color', grey);
+
+testClassChange(first_sibling, green);
+testClassChange(second_sibling, red);
+testClassChange(third_sibling, red);
+testClassChange(first_sibling_child, pink);
+testClassChange(first_sibling_descendant, yellow);
+testClassChange(third_sibling_child, purple);
+testClassChange(third_sibling_descendant, blue);
+
+testElementInsertionBefore(first_sibling, green);
+testElementInsertionBefore(second_sibling, red);
+testElementInsertionBefore(third_sibling, red);
+testElementInsertionBefore(first_sibling_child, pink);
+testElementInsertionBefore(first_sibling_descendant, yellow);
+testElementInsertionBefore(third_sibling_child, purple);
+testElementInsertionBefore(third_sibling_descendant, blue);
+
+testElementInsertionAfter(first_sibling, red);
+testElementInsertionAfter(second_sibling, red);
+testElementInsertionAfter(third_sibling, red);
+testElementInsertionAfter(first_sibling_child, pink);
+testElementInsertionAfter(first_sibling_descendant, yellow);
+testElementInsertionAfter(third_sibling_child, purple);
+testElementInsertionAfter(third_sibling_descendant, blue);
+
+testTreeInsertionBefore(first_sibling, pink);
+testTreeInsertionBefore(second_sibling, purple);
+testTreeInsertionBefore(third_sibling, purple);
+testTreeInsertionBefore(first_sibling_child, yellow);
+testTreeInsertionBefore(first_sibling_descendant, yellow);
+testTreeInsertionBefore(third_sibling_child, blue);
+testTreeInsertionBefore(third_sibling_descendant, blue);
+
+testTreeInsertionAfter(first_sibling, purple);
+testTreeInsertionAfter(second_sibling, purple);
+testTreeInsertionAfter(third_sibling, purple);
+testTreeInsertionAfter(first_sibling_child, yellow);
+testTreeInsertionAfter(first_sibling_descendant, yellow);
+testTreeInsertionAfter(third_sibling_child, blue);
+testTreeInsertionAfter(third_sibling_descendant, blue);
+</script>

Modified: trunk/Source/WebCore/ChangeLog (286364 => 286365)


--- trunk/Source/WebCore/ChangeLog	2021-12-01 16:50:06 UTC (rev 286364)
+++ trunk/Source/WebCore/ChangeLog	2021-12-01 17:04:27 UTC (rev 286365)
@@ -1,3 +1,36 @@
+2021-12-01  Antti Koivisto  <an...@apple.com>
+
+        [:has() pseudo-class] Sibling combinator invalidation
+        https://bugs.webkit.org/show_bug.cgi?id=233696
+
+        Reviewed by Simon Fraser.
+
+        Invalidate style correctly for sibling combinators in :has() arguments.
+
+        Also add new MatchElement::HasSiblingDescendant value used for :has() arguments that
+        match the subject elements siblings descendants, like ':has(~ div .descendant)'.
+        Use it to avoid unnecessary big tree traversals in simple cases.
+
+        Test: imported/w3c/web-platform-tests/css/selectors/invalidation/has-sibling.html
+
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::matchHasPseudoClass const):
+
+        Minimal traversals for HasSibling and HasSiblingDescendant.
+
+        * style/ChildChangeInvalidation.cpp:
+        (WebCore::Style::needsTraversal):
+        (WebCore::Style::needsDescendantTraversal):
+        * style/RuleFeature.cpp:
+        (WebCore::Style::isSiblingOrSubject):
+        (WebCore::Style::isHasPseudoClassMatchElement):
+        (WebCore::Style::computeHasPseudoClassMatchElement):
+        * style/RuleFeature.h:
+        * style/StyleInvalidator.cpp:
+        (WebCore::Style::Invalidator::invalidateStyleWithMatchElement):
+
+        Invalidation traversal.
+
 2021-12-01  Youenn Fablet  <you...@apple.com>
 
         Add a features.json entry to requestVideoFrameCallback

Modified: trunk/Source/WebCore/css/SelectorChecker.cpp (286364 => 286365)


--- trunk/Source/WebCore/css/SelectorChecker.cpp	2021-12-01 16:50:06 UTC (rev 286364)
+++ trunk/Source/WebCore/css/SelectorChecker.cpp	2021-12-01 17:04:27 UTC (rev 286365)
@@ -1315,10 +1315,15 @@
         for (auto* sibling = element.nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
             if (checkRelative(*sibling))
                 return true;
+        }
+        break;
+    case Style::MatchElement::HasSiblingDescendant:
+        for (auto* sibling = element.nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
             if (checkDescendants(*sibling))
                 return true;
         }
         break;
+
     default:
         ASSERT_NOT_REACHED();
         break;

Modified: trunk/Source/WebCore/style/ChildChangeInvalidation.cpp (286364 => 286365)


--- trunk/Source/WebCore/style/ChildChangeInvalidation.cpp	2021-12-01 16:50:06 UTC (rev 286364)
+++ trunk/Source/WebCore/style/ChildChangeInvalidation.cpp	2021-12-01 17:04:27 UTC (rev 286365)
@@ -111,12 +111,14 @@
         return true;
     if (features.usesMatchElement(MatchElement::HasDescendant))
         return true;
+    if (features.usesMatchElement(MatchElement::HasSiblingDescendant))
+        return true;
     return features.usesMatchElement(MatchElement::HasSibling) && childChange.previousSiblingElement;
 };
 
 static bool needsDescendantTraversal(const RuleFeatureSet& features)
 {
-    return features.usesMatchElement(MatchElement::HasDescendant);
+    return features.usesMatchElement(MatchElement::HasDescendant) || features.usesMatchElement(MatchElement::HasSiblingDescendant);
 };
 
 template<typename Function>

Modified: trunk/Source/WebCore/style/RuleFeature.cpp (286364 => 286365)


--- trunk/Source/WebCore/style/RuleFeature.cpp	2021-12-01 16:50:06 UTC (rev 286364)
+++ trunk/Source/WebCore/style/RuleFeature.cpp	2021-12-01 17:04:27 UTC (rev 286365)
@@ -52,6 +52,7 @@
     case MatchElement::AncestorSibling:
     case MatchElement::HasChild:
     case MatchElement::HasDescendant:
+    case MatchElement::HasSiblingDescendant:
         return false;
     }
     ASSERT_NOT_REACHED();
@@ -61,9 +62,10 @@
 bool isHasPseudoClassMatchElement(MatchElement matchElement)
 {
     switch (matchElement) {
-    case MatchElement::HasSibling:
     case MatchElement::HasChild:
     case MatchElement::HasDescendant:
+    case MatchElement::HasSibling:
+    case MatchElement::HasSiblingDescendant:
         return true;
     default:
         return false;
@@ -146,13 +148,15 @@
         return MatchElement::HasDescendant;
     case MatchElement::IndirectSibling:
     case MatchElement::DirectSibling:
+    case MatchElement::AnySibling:
+        return MatchElement::HasSibling;
     case MatchElement::ParentSibling:
     case MatchElement::AncestorSibling:
-    case MatchElement::AnySibling:
-        return MatchElement::HasSibling;
+        return MatchElement::HasSiblingDescendant;
     case MatchElement::HasChild:
     case MatchElement::HasDescendant:
     case MatchElement::HasSibling:
+    case MatchElement::HasSiblingDescendant:
     case MatchElement::Host:
         ASSERT_NOT_REACHED();
         break;

Modified: trunk/Source/WebCore/style/RuleFeature.h (286364 => 286365)


--- trunk/Source/WebCore/style/RuleFeature.h	2021-12-01 16:50:06 UTC (rev 286364)
+++ trunk/Source/WebCore/style/RuleFeature.h	2021-12-01 17:04:27 UTC (rev 286365)
@@ -36,7 +36,21 @@
 
 class RuleData;
 
-enum class MatchElement : uint8_t { Subject, Parent, Ancestor, DirectSibling, IndirectSibling, AnySibling, ParentSibling, AncestorSibling, HasChild, HasDescendant, HasSibling, Host };
+enum class MatchElement : uint8_t {
+    Subject,
+    Parent,
+    Ancestor,
+    DirectSibling,
+    IndirectSibling,
+    AnySibling,
+    ParentSibling,
+    AncestorSibling,
+    HasChild,
+    HasDescendant,
+    HasSibling,
+    HasSiblingDescendant,
+    Host
+};
 constexpr unsigned matchElementCount = static_cast<unsigned>(MatchElement::Host) + 1;
 
 struct RuleFeature {

Modified: trunk/Source/WebCore/style/StyleInvalidator.cpp (286364 => 286365)


--- trunk/Source/WebCore/style/StyleInvalidator.cpp	2021-12-01 16:50:06 UTC (rev 286364)
+++ trunk/Source/WebCore/style/StyleInvalidator.cpp	2021-12-01 17:04:27 UTC (rev 286365)
@@ -322,10 +322,27 @@
         break;
     }
     case MatchElement::HasSibling: {
+        if (auto* sibling = element.previousElementSibling()) {
+            SelectorMatchingState selectorMatchingState;
+            selectorMatchingState.selectorFilter.pushParentInitializingIfNeeded(*element.parentElement());
+
+            for (; sibling; sibling = sibling->previousElementSibling())
+                invalidateIfNeeded(*sibling, &selectorMatchingState);
+        }
+        break;
+    }
+    case MatchElement::HasSiblingDescendant: {
+        Vector<Element*, 16> elementAndAncestors;
+        elementAndAncestors.append(&element);
+        for (auto* parent = element.parentElement(); parent; parent = parent->parentElement())
+            elementAndAncestors.append(parent);
+
         SelectorMatchingState selectorMatchingState;
-        for (auto* sibling = element.previousElementSibling(); sibling; sibling = sibling->previousElementSibling()) {
-            selectorMatchingState.selectorFilter.popParentsUntil(element.parentElement());
-            invalidateStyleForDescendants(*sibling, &selectorMatchingState);
+        for (auto* elementOrAncestor : makeReversedRange(elementAndAncestors)) {
+            for (auto* sibling = elementOrAncestor->previousElementSibling(); sibling; sibling = sibling->previousElementSibling())
+                invalidateIfNeeded(*sibling, &selectorMatchingState);
+
+            selectorMatchingState.selectorFilter.pushParent(elementOrAncestor);
         }
         break;
     }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to