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;
}