Title: [286433] trunk
Revision
286433
Author
[email protected]
Date
2021-12-02 09:36:50 -0800 (Thu, 02 Dec 2021)

Log Message

[:has() pseudo-class] Invalidation in non-subject position
https://bugs.webkit.org/show_bug.cgi?id=233758

Reviewed by Simon Fraser.

LayoutTests/imported/w3c:

* web-platform-tests/css/selectors/invalidation/has-in-adjacent-position-expected.txt: Added.
* web-platform-tests/css/selectors/invalidation/has-in-adjacent-position.html: Added.
* web-platform-tests/css/selectors/invalidation/has-in-ancestor-position-expected.txt: Added.
* web-platform-tests/css/selectors/invalidation/has-in-ancestor-position.html: Added.
* web-platform-tests/css/selectors/invalidation/has-in-parent-position-expected.txt: Added.
* web-platform-tests/css/selectors/invalidation/has-in-parent-position.html: Added.
* web-platform-tests/css/selectors/invalidation/has-in-sibling-position-expected.txt: Added.
* web-platform-tests/css/selectors/invalidation/has-in-sibling-position.html: Added.

Source/WebCore:

Invalidation for selectors like '.ancestor:has(.foo) #target'.

Tests: imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-adjacent-position.html
       imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-ancestor-position.html
       imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-parent-position.html
       imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-sibling-position.html

* style/ChildChangeInvalidation.cpp:
(WebCore::Style::ChildChangeInvalidation::invalidateForChangedElement):
(WebCore::Style::needsTraversal):
(WebCore::Style::needsDescendantTraversal):
* style/RuleFeature.cpp:
(WebCore::Style::isSiblingOrSubject):
(WebCore::Style::isHasPseudoClassMatchElement):
(WebCore::Style::computeHasPseudoClassMatchElement):
(WebCore::Style::computeSubSelectorMatchElement):

Use new MatchElement::HasNonSubject as a catch-all for cases where :has() is in a non-subject position.
In the future this can be optimized better by computing both the :has match element and the overall
match element separately.

* style/RuleFeature.h:
* style/StyleInvalidator.cpp:
(WebCore::Style::Invalidator::invalidateStyleWithMatchElement):

Worst-case invalidation.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (286432 => 286433)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2021-12-02 16:44:31 UTC (rev 286432)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2021-12-02 17:36:50 UTC (rev 286433)
@@ -1,3 +1,19 @@
+2021-12-02  Antti Koivisto  <[email protected]>
+
+        [:has() pseudo-class] Invalidation in non-subject position
+        https://bugs.webkit.org/show_bug.cgi?id=233758
+
+        Reviewed by Simon Fraser.
+
+        * web-platform-tests/css/selectors/invalidation/has-in-adjacent-position-expected.txt: Added.
+        * web-platform-tests/css/selectors/invalidation/has-in-adjacent-position.html: Added.
+        * web-platform-tests/css/selectors/invalidation/has-in-ancestor-position-expected.txt: Added.
+        * web-platform-tests/css/selectors/invalidation/has-in-ancestor-position.html: Added.
+        * web-platform-tests/css/selectors/invalidation/has-in-parent-position-expected.txt: Added.
+        * web-platform-tests/css/selectors/invalidation/has-in-parent-position.html: Added.
+        * web-platform-tests/css/selectors/invalidation/has-in-sibling-position-expected.txt: Added.
+        * web-platform-tests/css/selectors/invalidation/has-in-sibling-position.html: Added.
+
 2021-12-02  Andreu Botella  <[email protected]>
 
         File inputs in non-multipart form submissions show up as string values in the formdata event

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-adjacent-position-expected.txt (0 => 286433)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-adjacent-position-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-adjacent-position-expected.txt	2021-12-02 17:36:50 UTC (rev 286433)
@@ -0,0 +1,73 @@
+
+PASS Initial color
+PASS add .test to previous_sibling
+PASS remove .test from previous_sibling
+PASS add .test to previous_sibling_child
+PASS remove .test from previous_sibling_child
+PASS add .test to previous_sibling_descendant
+PASS remove .test from previous_sibling_descendant
+PASS add .test to subject
+PASS remove .test from subject
+PASS add .test to next_sibling
+PASS remove .test from next_sibling
+PASS add .test to next_sibling_child
+PASS remove .test from next_sibling_child
+PASS add .test to next_sibling_descendant
+PASS remove .test from next_sibling_descendant
+PASS insert element div.test before previous_sibling
+PASS remove element div.test before previous_sibling
+PASS insert element div.test before previous_sibling_child
+PASS remove element div.test before previous_sibling_child
+PASS insert element div.test before previous_sibling_descendant
+PASS remove element div.test before previous_sibling_descendant
+PASS insert element div.test before subject
+PASS remove element div.test before subject
+PASS insert element div.test before next_sibling
+PASS remove element div.test before next_sibling
+PASS insert element div.test before next_sibling_child
+PASS remove element div.test before next_sibling_child
+PASS insert element div.test before next_sibling_descendant
+PASS remove element div.test before next_sibling_descendant
+PASS insert element div.test after previous_sibling
+PASS remove element div.test after previous_sibling
+PASS insert element div.test after previous_sibling_child
+PASS remove element div.test after previous_sibling_child
+PASS insert element div.test after previous_sibling_descendant
+PASS remove element div.test after previous_sibling_descendant
+PASS insert element div.test after subject
+PASS remove element div.test after subject
+PASS insert element div.test after next_sibling
+PASS remove element div.test after next_sibling
+PASS insert element div.test after next_sibling_child
+PASS remove element div.test after next_sibling_child
+PASS insert element div.test after next_sibling_descendant
+PASS remove element div.test after next_sibling_descendant
+PASS insert tree div>div.test before previous_sibling
+PASS remove tree div>div.test before previous_sibling
+PASS insert tree div>div.test before previous_sibling_child
+PASS remove tree div>div.test before previous_sibling_child
+PASS insert tree div>div.test before previous_sibling_descendant
+PASS remove tree div>div.test before previous_sibling_descendant
+PASS insert tree div>div.test before subject
+PASS remove tree div>div.test before subject
+PASS insert tree div>div.test before next_sibling
+PASS remove tree div>div.test before next_sibling
+PASS insert tree div>div.test before next_sibling_child
+PASS remove tree div>div.test before next_sibling_child
+PASS insert tree div>div.test before next_sibling_descendant
+PASS remove tree div>div.test before next_sibling_descendant
+PASS insert tree div>div.test after previous_sibling
+PASS remove tree div>div.test after previous_sibling
+PASS insert tree div>div.test after previous_sibling_child
+PASS remove tree div>div.test after previous_sibling_child
+PASS insert tree div>div.test after previous_sibling_descendant
+PASS remove tree div>div.test after previous_sibling_descendant
+PASS insert tree div>div.test after subject
+PASS remove tree div>div.test after subject
+PASS insert tree div>div.test after next_sibling
+PASS remove tree div>div.test after next_sibling
+PASS insert tree div>div.test after next_sibling_child
+PASS remove tree div>div.test after next_sibling_child
+PASS insert tree div>div.test after next_sibling_descendant
+PASS remove tree div>div.test after next_sibling_descendant
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-adjacent-position.html (0 => 286433)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-adjacent-position.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-adjacent-position.html	2021-12-02 17:36:50 UTC (rev 286433)
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Selector Invalidation: :has() in adjacent position</title>
+<link rel="author" title="Antti Koivisto" href=""
+<script src=""
+<script src=""
+<link rel="help" href=""
+<style>
+div, main { color: grey }
+div:has(.test) + #subject { color: red }
+div:has(> .test) + #subject { color: green }
+div:has(~ .test) + #subject { color: yellow }
+div:has(+ .test) + #subject { color: blue }
+div:has(~ div .test) + #subject { color: purple }
+div:has(+ div .test) + #subject { color: pink }
+</style>
+
+<main id=main>
+    <div id=previous_sibling>
+        <div id=previous_sibling_child>
+            <div id=previous_sibling_descendant></div>
+        </div>
+    </div>
+    <div id=subject></div>
+    <div id=next_sibling>
+        <div id=next_sibling_child>
+            <div id=next_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(previous_sibling, grey);
+testClassChange(previous_sibling_child, green);
+testClassChange(previous_sibling_descendant, red);
+testClassChange(subject, blue);
+testClassChange(next_sibling, yellow);
+testClassChange(next_sibling_child, purple);
+testClassChange(next_sibling_descendant, purple);
+
+testElementInsertionBefore(previous_sibling, grey);
+testElementInsertionBefore(previous_sibling_child, green);
+testElementInsertionBefore(previous_sibling_descendant, red);
+testElementInsertionBefore(subject, grey);
+testElementInsertionBefore(next_sibling, yellow);
+testElementInsertionBefore(next_sibling_child, purple);
+testElementInsertionBefore(next_sibling_descendant, purple);
+
+testElementInsertionAfter(previous_sibling, grey);
+testElementInsertionAfter(previous_sibling_child, green);
+testElementInsertionAfter(previous_sibling_descendant, red);
+testElementInsertionAfter(subject, yellow);
+testElementInsertionAfter(next_sibling, yellow);
+testElementInsertionAfter(next_sibling_child, purple);
+testElementInsertionAfter(next_sibling_descendant, purple);
+
+testTreeInsertionBefore(previous_sibling, grey);
+testTreeInsertionBefore(previous_sibling_child, red);
+testTreeInsertionBefore(previous_sibling_descendant, red);
+testTreeInsertionBefore(subject, green);
+testTreeInsertionBefore(next_sibling, purple);
+testTreeInsertionBefore(next_sibling_child, purple);
+testTreeInsertionBefore(next_sibling_descendant, purple);
+
+testTreeInsertionAfter(previous_sibling, green);
+testTreeInsertionAfter(previous_sibling_child, red);
+testTreeInsertionAfter(previous_sibling_descendant, red);
+testTreeInsertionAfter(subject, purple);
+testTreeInsertionAfter(next_sibling, purple);
+testTreeInsertionAfter(next_sibling_child, purple);
+testTreeInsertionAfter(next_sibling_descendant, purple);
+
+</script>

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-ancestor-position-expected.txt (0 => 286433)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-ancestor-position-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-ancestor-position-expected.txt	2021-12-02 17:36:50 UTC (rev 286433)
@@ -0,0 +1,83 @@
+
+PASS Initial color
+PASS add .test to subject_ancestor
+PASS remove .test from subject_ancestor
+PASS add .test to subject_parent
+PASS remove .test from subject_parent
+PASS add .test to subject
+PASS remove .test from subject
+PASS add .test to subject_child
+PASS remove .test from subject_child
+PASS add .test to subject_descendant
+PASS remove .test from subject_descendant
+PASS add .test to next_sibling
+PASS remove .test from next_sibling
+PASS add .test to next_sibling_child
+PASS remove .test from next_sibling_child
+PASS add .test to next_sibling_descendant
+PASS remove .test from next_sibling_descendant
+PASS insert element div.test before subject_ancestor
+PASS remove element div.test before subject_ancestor
+PASS insert element div.test before subject_parent
+PASS remove element div.test before subject_parent
+PASS insert element div.test before subject
+PASS remove element div.test before subject
+PASS insert element div.test before subject_child
+PASS remove element div.test before subject_child
+PASS insert element div.test before subject_descendant
+PASS remove element div.test before subject_descendant
+PASS insert element div.test before next_sibling
+PASS remove element div.test before next_sibling
+PASS insert element div.test before next_sibling_child
+PASS remove element div.test before next_sibling_child
+PASS insert element div.test before next_sibling_descendant
+PASS remove element div.test before next_sibling_descendant
+PASS insert element div.test after subject_ancestor
+PASS remove element div.test after subject_ancestor
+PASS insert element div.test after subject_parent
+PASS remove element div.test after subject_parent
+PASS insert element div.test after subject
+PASS remove element div.test after subject
+PASS insert element div.test after subject_child
+PASS remove element div.test after subject_child
+PASS insert element div.test after subject_descendant
+PASS remove element div.test after subject_descendant
+PASS insert element div.test after next_sibling
+PASS remove element div.test after next_sibling
+PASS insert element div.test after next_sibling_child
+PASS remove element div.test after next_sibling_child
+PASS insert element div.test after next_sibling_descendant
+PASS remove element div.test after next_sibling_descendant
+PASS insert tree div>div.test before subject_ancestor
+PASS remove tree div>div.test before subject_ancestor
+PASS insert tree div>div.test before subject_parent
+PASS remove tree div>div.test before subject_parent
+PASS insert tree div>div.test before subject
+PASS remove tree div>div.test before subject
+PASS insert tree div>div.test before subject_child
+PASS remove tree div>div.test before subject_child
+PASS insert tree div>div.test before subject_descendant
+PASS remove tree div>div.test before subject_descendant
+PASS insert tree div>div.test before next_sibling
+PASS remove tree div>div.test before next_sibling
+PASS insert tree div>div.test before next_sibling_child
+PASS remove tree div>div.test before next_sibling_child
+PASS insert tree div>div.test before next_sibling_descendant
+PASS remove tree div>div.test before next_sibling_descendant
+PASS insert tree div>div.test after subject_ancestor
+PASS remove tree div>div.test after subject_ancestor
+PASS insert tree div>div.test after subject_parent
+PASS remove tree div>div.test after subject_parent
+PASS insert tree div>div.test after subject
+PASS remove tree div>div.test after subject
+PASS insert tree div>div.test after subject_child
+PASS remove tree div>div.test after subject_child
+PASS insert tree div>div.test after subject_descendant
+PASS remove tree div>div.test after subject_descendant
+PASS insert tree div>div.test after next_sibling
+PASS remove tree div>div.test after next_sibling
+PASS insert tree div>div.test after next_sibling_child
+PASS remove tree div>div.test after next_sibling_child
+PASS insert tree div>div.test after next_sibling_descendant
+PASS remove tree div>div.test after next_sibling_descendant
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-ancestor-position.html (0 => 286433)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-ancestor-position.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-ancestor-position.html	2021-12-02 17:36:50 UTC (rev 286433)
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Selector Invalidation: :has() in ancestor position</title>
+<link rel="author" title="Antti Koivisto" href=""
+<script src=""
+<script src=""
+<link rel="help" href=""
+<style>
+div, main { color: grey }
+div:has(.test) #subject { color: red }
+div:has(> .test) #subject { color: green }
+div:has(~ .test) #subject { color: yellow }
+div:has(+ .test) #subject { color: blue }
+div:has(~ div .test) #subject { color: purple }
+div:has(+ div .test) #subject { color: pink }
+</style>
+
+<main id=main>
+    <div id=subject_ancestor>
+        <div id=subject_parent>
+            <div id=subject>
+                <div id=subject_child>
+                    <div id=subject_descendant></div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div id=next_sibling>
+        <div id=next_sibling_child>
+            <div id=next_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(subject_ancestor, grey);
+testClassChange(subject_parent, green);
+testClassChange(subject, green);
+testClassChange(subject_child, red);
+testClassChange(subject_descendant, red);
+testClassChange(next_sibling, blue);
+testClassChange(next_sibling_child, pink);
+testClassChange(next_sibling_descendant, pink);
+
+testElementInsertionBefore(subject_ancestor, grey);
+testElementInsertionBefore(subject_parent, green);
+testElementInsertionBefore(subject, green);
+testElementInsertionBefore(subject_child, red);
+testElementInsertionBefore(subject_descendant, red);
+testElementInsertionBefore(next_sibling, blue);
+testElementInsertionBefore(next_sibling_child, pink);
+testElementInsertionBefore(next_sibling_descendant, pink);
+
+testElementInsertionAfter(subject_ancestor, blue);
+testElementInsertionAfter(subject_parent, blue);
+testElementInsertionAfter(subject, green);
+testElementInsertionAfter(subject_child, red);
+testElementInsertionAfter(subject_descendant, red);
+testElementInsertionAfter(next_sibling, yellow);
+testElementInsertionAfter(next_sibling_child, pink);
+testElementInsertionAfter(next_sibling_descendant, pink);
+
+testTreeInsertionBefore(subject_ancestor, grey);
+testTreeInsertionBefore(subject_parent, red);
+testTreeInsertionBefore(subject, red);
+testTreeInsertionBefore(subject_child, red);
+testTreeInsertionBefore(subject_descendant, red);
+testTreeInsertionBefore(next_sibling, pink);
+testTreeInsertionBefore(next_sibling_child, pink);
+testTreeInsertionBefore(next_sibling_descendant, pink);
+
+testTreeInsertionAfter(subject_ancestor, pink);
+testTreeInsertionAfter(subject_parent, pink);
+testTreeInsertionAfter(subject, red);
+testTreeInsertionAfter(subject_child, red);
+testTreeInsertionAfter(subject_descendant, red);
+testTreeInsertionAfter(next_sibling, purple);
+testTreeInsertionAfter(next_sibling_child, pink);
+testTreeInsertionAfter(next_sibling_descendant, pink);
+
+</script>

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-parent-position-expected.txt (0 => 286433)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-parent-position-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-parent-position-expected.txt	2021-12-02 17:36:50 UTC (rev 286433)
@@ -0,0 +1,53 @@
+
+PASS Initial color
+PASS add .test to subject_ancestor
+PASS remove .test from subject_ancestor
+PASS add .test to subject_parent
+PASS remove .test from subject_parent
+PASS add .test to subject
+PASS remove .test from subject
+PASS add .test to subject_child
+PASS remove .test from subject_child
+PASS add .test to subject_descendant
+PASS remove .test from subject_descendant
+PASS insert element div.test before subject_ancestor
+PASS remove element div.test before subject_ancestor
+PASS insert element div.test before subject_parent
+PASS remove element div.test before subject_parent
+PASS insert element div.test before subject
+PASS remove element div.test before subject
+PASS insert element div.test before subject_child
+PASS remove element div.test before subject_child
+PASS insert element div.test before subject_descendant
+PASS remove element div.test before subject_descendant
+PASS insert element div.test after subject_ancestor
+PASS remove element div.test after subject_ancestor
+PASS insert element div.test after subject_parent
+PASS remove element div.test after subject_parent
+PASS insert element div.test after subject
+PASS remove element div.test after subject
+PASS insert element div.test after subject_child
+PASS remove element div.test after subject_child
+PASS insert element div.test after subject_descendant
+PASS remove element div.test after subject_descendant
+PASS insert tree div>div.test before subject_ancestor
+PASS remove tree div>div.test before subject_ancestor
+PASS insert tree div>div.test before subject_parent
+PASS remove tree div>div.test before subject_parent
+PASS insert tree div>div.test before subject
+PASS remove tree div>div.test before subject
+PASS insert tree div>div.test before subject_child
+PASS remove tree div>div.test before subject_child
+PASS insert tree div>div.test before subject_descendant
+PASS remove tree div>div.test before subject_descendant
+PASS insert tree div>div.test after subject_ancestor
+PASS remove tree div>div.test after subject_ancestor
+PASS insert tree div>div.test after subject_parent
+PASS remove tree div>div.test after subject_parent
+PASS insert tree div>div.test after subject
+PASS remove tree div>div.test after subject
+PASS insert tree div>div.test after subject_child
+PASS remove tree div>div.test after subject_child
+PASS insert tree div>div.test after subject_descendant
+PASS remove tree div>div.test after subject_descendant
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-parent-position.html (0 => 286433)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-parent-position.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-parent-position.html	2021-12-02 17:36:50 UTC (rev 286433)
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Selector Invalidation: :has() in parent position</title>
+<link rel="author" title="Antti Koivisto" href=""
+<script src=""
+<script src=""
+<link rel="help" href=""
+<style>
+div, main { color: grey }
+div:has(.test) > #subject { color: red }
+div:has(> .test) > #subject { color: green }
+div:has(~ .test) >  #subject { color: yellow }
+div:has(+ .test) > #subject { color: blue }
+div:has(~ div .test) > #subject { color: purple }
+div:has(+ div .test) > #subject { color: pink }
+</style>
+
+<main id=main>
+    <div id=subject_ancestor>
+        <div id=subject_parent>
+            <div id=subject>
+                <div id=subject_child>
+                    <div id=subject_descendant></div>
+                </div>
+            </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(subject_ancestor, grey);
+testClassChange(subject_parent, grey);
+testClassChange(subject, green);
+testClassChange(subject_child, red);
+testClassChange(subject_descendant, red);
+
+testElementInsertionBefore(subject_ancestor, grey);
+testElementInsertionBefore(subject_parent, grey);
+testElementInsertionBefore(subject, green);
+testElementInsertionBefore(subject_child, red);
+testElementInsertionBefore(subject_descendant, red);
+
+testElementInsertionAfter(subject_ancestor, grey);
+testElementInsertionAfter(subject_parent, blue);
+testElementInsertionAfter(subject, green);
+testElementInsertionAfter(subject_child, red);
+testElementInsertionAfter(subject_descendant, red);
+
+testTreeInsertionBefore(subject_ancestor, grey);
+testTreeInsertionBefore(subject_parent, grey);
+testTreeInsertionBefore(subject, red);
+testTreeInsertionBefore(subject_child, red);
+testTreeInsertionBefore(subject_descendant, red);
+
+testTreeInsertionAfter(subject_ancestor, grey);
+testTreeInsertionAfter(subject_parent, pink);
+testTreeInsertionAfter(subject, red);
+testTreeInsertionAfter(subject_child, red);
+testTreeInsertionAfter(subject_descendant, red);
+
+</script>

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


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-sibling-position-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-sibling-position-expected.txt	2021-12-02 17:36:50 UTC (rev 286433)
@@ -0,0 +1,73 @@
+
+PASS Initial color
+PASS add .test to previous_sibling
+PASS remove .test from previous_sibling
+PASS add .test to previous_sibling_child
+PASS remove .test from previous_sibling_child
+PASS add .test to previous_sibling_descendant
+PASS remove .test from previous_sibling_descendant
+PASS add .test to subject
+PASS remove .test from subject
+PASS add .test to next_sibling
+PASS remove .test from next_sibling
+PASS add .test to next_sibling_child
+PASS remove .test from next_sibling_child
+PASS add .test to next_sibling_descendant
+PASS remove .test from next_sibling_descendant
+PASS insert element div.test before previous_sibling
+PASS remove element div.test before previous_sibling
+PASS insert element div.test before previous_sibling_child
+PASS remove element div.test before previous_sibling_child
+PASS insert element div.test before previous_sibling_descendant
+PASS remove element div.test before previous_sibling_descendant
+PASS insert element div.test before subject
+PASS remove element div.test before subject
+PASS insert element div.test before next_sibling
+PASS remove element div.test before next_sibling
+PASS insert element div.test before next_sibling_child
+PASS remove element div.test before next_sibling_child
+PASS insert element div.test before next_sibling_descendant
+PASS remove element div.test before next_sibling_descendant
+PASS insert element div.test after previous_sibling
+PASS remove element div.test after previous_sibling
+PASS insert element div.test after previous_sibling_child
+PASS remove element div.test after previous_sibling_child
+PASS insert element div.test after previous_sibling_descendant
+PASS remove element div.test after previous_sibling_descendant
+PASS insert element div.test after subject
+PASS remove element div.test after subject
+PASS insert element div.test after next_sibling
+PASS remove element div.test after next_sibling
+PASS insert element div.test after next_sibling_child
+PASS remove element div.test after next_sibling_child
+PASS insert element div.test after next_sibling_descendant
+PASS remove element div.test after next_sibling_descendant
+PASS insert tree div>div.test before previous_sibling
+PASS remove tree div>div.test before previous_sibling
+PASS insert tree div>div.test before previous_sibling_child
+PASS remove tree div>div.test before previous_sibling_child
+PASS insert tree div>div.test before previous_sibling_descendant
+PASS remove tree div>div.test before previous_sibling_descendant
+PASS insert tree div>div.test before subject
+PASS remove tree div>div.test before subject
+PASS insert tree div>div.test before next_sibling
+PASS remove tree div>div.test before next_sibling
+PASS insert tree div>div.test before next_sibling_child
+PASS remove tree div>div.test before next_sibling_child
+PASS insert tree div>div.test before next_sibling_descendant
+PASS remove tree div>div.test before next_sibling_descendant
+PASS insert tree div>div.test after previous_sibling
+PASS remove tree div>div.test after previous_sibling
+PASS insert tree div>div.test after previous_sibling_child
+PASS remove tree div>div.test after previous_sibling_child
+PASS insert tree div>div.test after previous_sibling_descendant
+PASS remove tree div>div.test after previous_sibling_descendant
+PASS insert tree div>div.test after subject
+PASS remove tree div>div.test after subject
+PASS insert tree div>div.test after next_sibling
+PASS remove tree div>div.test after next_sibling
+PASS insert tree div>div.test after next_sibling_child
+PASS remove tree div>div.test after next_sibling_child
+PASS insert tree div>div.test after next_sibling_descendant
+PASS remove tree div>div.test after next_sibling_descendant
+

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


--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-sibling-position.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-sibling-position.html	2021-12-02 17:36:50 UTC (rev 286433)
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Selector Invalidation: :has() in sibling position</title>
+<link rel="author" title="Antti Koivisto" href=""
+<script src=""
+<script src=""
+<link rel="help" href=""
+<style>
+div, main { color: grey }
+div:has(.test) ~ #subject { color: red }
+div:has(> .test) ~ #subject { color: green }
+div:has(~ .test) ~ #subject { color: yellow }
+div:has(+ .test) ~ #subject { color: blue }
+div:has(~ div .test) ~ #subject { color: purple }
+div:has(+ div .test) ~ #subject { color: pink }
+</style>
+
+<main id=main>
+    <div id=previous_sibling>
+        <div id=previous_sibling_child>
+            <div id=previous_sibling_descendant></div>
+        </div>
+    </div>
+    <div id=subject></div>
+    <div id=next_sibling>
+        <div id=next_sibling_child>
+            <div id=next_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(previous_sibling, grey);
+testClassChange(previous_sibling_child, green);
+testClassChange(previous_sibling_descendant, red);
+testClassChange(subject, blue);
+testClassChange(next_sibling, yellow);
+testClassChange(next_sibling_child, purple);
+testClassChange(next_sibling_descendant, purple);
+
+testElementInsertionBefore(previous_sibling, grey);
+testElementInsertionBefore(previous_sibling_child, green);
+testElementInsertionBefore(previous_sibling_descendant, red);
+testElementInsertionBefore(subject, blue);
+testElementInsertionBefore(next_sibling, yellow);
+testElementInsertionBefore(next_sibling_child, purple);
+testElementInsertionBefore(next_sibling_descendant, purple);
+
+testElementInsertionAfter(previous_sibling, blue);
+testElementInsertionAfter(previous_sibling_child, green);
+testElementInsertionAfter(previous_sibling_descendant, red);
+testElementInsertionAfter(subject, yellow);
+testElementInsertionAfter(next_sibling, yellow);
+testElementInsertionAfter(next_sibling_child, purple);
+testElementInsertionAfter(next_sibling_descendant, purple);
+
+testTreeInsertionBefore(previous_sibling, green);
+testTreeInsertionBefore(previous_sibling_child, red);
+testTreeInsertionBefore(previous_sibling_descendant, red);
+testTreeInsertionBefore(subject, pink);
+testTreeInsertionBefore(next_sibling, purple);
+testTreeInsertionBefore(next_sibling_child, purple);
+testTreeInsertionBefore(next_sibling_descendant, purple);
+
+testTreeInsertionAfter(previous_sibling, pink);
+testTreeInsertionAfter(previous_sibling_child, red);
+testTreeInsertionAfter(previous_sibling_descendant, red);
+testTreeInsertionAfter(subject, purple);
+testTreeInsertionAfter(next_sibling, purple);
+testTreeInsertionAfter(next_sibling_child, purple);
+testTreeInsertionAfter(next_sibling_descendant, purple);
+
+</script>

Modified: trunk/Source/WebCore/ChangeLog (286432 => 286433)


--- trunk/Source/WebCore/ChangeLog	2021-12-02 16:44:31 UTC (rev 286432)
+++ trunk/Source/WebCore/ChangeLog	2021-12-02 17:36:50 UTC (rev 286433)
@@ -1,3 +1,37 @@
+2021-12-02  Antti Koivisto  <[email protected]>
+
+        [:has() pseudo-class] Invalidation in non-subject position
+        https://bugs.webkit.org/show_bug.cgi?id=233758
+
+        Reviewed by Simon Fraser.
+
+        Invalidation for selectors like '.ancestor:has(.foo) #target'.
+
+        Tests: imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-adjacent-position.html
+               imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-ancestor-position.html
+               imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-parent-position.html
+               imported/w3c/web-platform-tests/css/selectors/invalidation/has-in-sibling-position.html
+
+        * style/ChildChangeInvalidation.cpp:
+        (WebCore::Style::ChildChangeInvalidation::invalidateForChangedElement):
+        (WebCore::Style::needsTraversal):
+        (WebCore::Style::needsDescendantTraversal):
+        * style/RuleFeature.cpp:
+        (WebCore::Style::isSiblingOrSubject):
+        (WebCore::Style::isHasPseudoClassMatchElement):
+        (WebCore::Style::computeHasPseudoClassMatchElement):
+        (WebCore::Style::computeSubSelectorMatchElement):
+
+        Use new MatchElement::HasNonSubject as a catch-all for cases where :has() is in a non-subject position.
+        In the future this can be optimized better by computing both the :has match element and the overall
+        match element separately.
+
+        * style/RuleFeature.h:
+        * style/StyleInvalidator.cpp:
+        (WebCore::Style::Invalidator::invalidateStyleWithMatchElement):
+
+        Worst-case invalidation.
+
 2021-12-02  Andreu Botella  <[email protected]>
 
         File inputs in non-multipart form submissions show up as string values in the formdata event

Modified: trunk/Source/WebCore/style/ChildChangeInvalidation.cpp (286432 => 286433)


--- trunk/Source/WebCore/style/ChildChangeInvalidation.cpp	2021-12-02 16:44:31 UTC (rev 286432)
+++ trunk/Source/WebCore/style/ChildChangeInvalidation.cpp	2021-12-02 17:36:50 UTC (rev 286433)
@@ -75,8 +75,11 @@
         for (auto& invalidationRuleSet : *invalidationRuleSets) {
             if (!isHasPseudoClassMatchElement(invalidationRuleSet.matchElement))
                 continue;
-            if (isDescendant && invalidationRuleSet.matchElement != MatchElement::HasDescendant)
-                continue;
+            if (isDescendant) {
+                // Elements deeper in the tree can't affect anything except when :has() selector uses descendant combinator.
+                if (invalidationRuleSet.matchElement != MatchElement::HasDescendant && invalidationRuleSet.matchElement != MatchElement::HasNonSubject)
+                    continue;
+            }
             Invalidator::addToMatchElementRuleSets(matchElementRuleSets, invalidationRuleSet);
         }
     };
@@ -113,11 +116,15 @@
         return true;
     if (features.usesMatchElement(MatchElement::HasSiblingDescendant))
         return true;
+    if (features.usesMatchElement(MatchElement::HasNonSubject))
+        return true;
     return features.usesMatchElement(MatchElement::HasSibling) && childChange.previousSiblingElement;
 };
 
 static bool needsDescendantTraversal(const RuleFeatureSet& features)
 {
+    if (features.usesMatchElement(MatchElement::HasNonSubject))
+        return true;
     return features.usesMatchElement(MatchElement::HasDescendant) || features.usesMatchElement(MatchElement::HasSiblingDescendant);
 };
 

Modified: trunk/Source/WebCore/style/RuleFeature.cpp (286432 => 286433)


--- trunk/Source/WebCore/style/RuleFeature.cpp	2021-12-02 16:44:31 UTC (rev 286432)
+++ trunk/Source/WebCore/style/RuleFeature.cpp	2021-12-02 17:36:50 UTC (rev 286433)
@@ -53,6 +53,7 @@
     case MatchElement::HasChild:
     case MatchElement::HasDescendant:
     case MatchElement::HasSiblingDescendant:
+    case MatchElement::HasNonSubject:
         return false;
     }
     ASSERT_NOT_REACHED();
@@ -66,6 +67,7 @@
     case MatchElement::HasDescendant:
     case MatchElement::HasSibling:
     case MatchElement::HasSiblingDescendant:
+    case MatchElement::HasNonSubject:
         return true;
     default:
         return false;
@@ -157,6 +159,7 @@
     case MatchElement::HasDescendant:
     case MatchElement::HasSibling:
     case MatchElement::HasSiblingDescendant:
+    case MatchElement::HasNonSubject:
     case MatchElement::Host:
         ASSERT_NOT_REACHED();
         break;
@@ -176,8 +179,11 @@
         if (type == CSSSelector::PseudoClassHost)
             return MatchElement::Host;
 
-        if (type == CSSSelector::PseudoClassHas)
+        if (type == CSSSelector::PseudoClassHas) {
+            if (matchElement != MatchElement::Subject)
+                return MatchElement::HasNonSubject;
             return computeHasPseudoClassMatchElement(childSelector);
+        }
     }
     if (selector.match() == CSSSelector::PseudoElement) {
         // Similarly for ::slotted().

Modified: trunk/Source/WebCore/style/RuleFeature.h (286432 => 286433)


--- trunk/Source/WebCore/style/RuleFeature.h	2021-12-02 16:44:31 UTC (rev 286432)
+++ trunk/Source/WebCore/style/RuleFeature.h	2021-12-02 17:36:50 UTC (rev 286433)
@@ -36,6 +36,7 @@
 
 class RuleData;
 
+// FIXME: Has* values should be separated so we could describe both the :has() argument and its position in the selector.
 enum class MatchElement : uint8_t {
     Subject,
     Parent,
@@ -49,6 +50,7 @@
     HasDescendant,
     HasSibling,
     HasSiblingDescendant,
+    HasNonSubject, // FIXME: This is a catch-all for cases where :has() is in non-subject position.
     Host
 };
 constexpr unsigned matchElementCount = static_cast<unsigned>(MatchElement::Host) + 1;

Modified: trunk/Source/WebCore/style/StyleInvalidator.cpp (286432 => 286433)


--- trunk/Source/WebCore/style/StyleInvalidator.cpp	2021-12-02 16:44:31 UTC (rev 286432)
+++ trunk/Source/WebCore/style/StyleInvalidator.cpp	2021-12-02 17:36:50 UTC (rev 286433)
@@ -346,6 +346,11 @@
         }
         break;
     }
+    case MatchElement::HasNonSubject: {
+        SelectorMatchingState selectorMatchingState;
+        invalidateStyleForDescendants(*element.document().documentElement(), &selectorMatchingState);
+        break;
+    }
     case MatchElement::Host:
         invalidateInShadowTreeIfNeeded(element);
         break;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to