:first-child, :last-child, :nth-child, and :nth-of-type don't work on shadow root...
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 11 Sep 2018 23:00:31 +0000 (23:00 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 11 Sep 2018 23:00:31 +0000 (23:00 +0000)
https://bugs.webkit.org/show_bug.cgi?id=166748
<rdar://problem/29649177>

Reviewed by Yusuke Suzuki.

Source/WebCore:

Added the support for matching positional pseudo classes. For now, we invalidate whenever a child node
of a non-UA ShadowRoot is mutated instead of a fine-grained style invalidation as done for regular elements.

Tests: fast/shadow-dom/nth-node-on-shadow-child-invalidation.html
       fast/shadow-dom/nth-node-on-shadow-child-no-jit.html
       fast/shadow-dom/nth-node-on-shadow-child.html

* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::checkOne const):
* cssjit/SelectorCompiler.cpp:
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateWalkToParentElementOrShadowRoot):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsFirstChild):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsLastChild):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsOnlyChild):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateNthChildParentCheckAndRelationUpdate):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsNthChild):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsNthChildOf):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateNthLastChildParentCheckAndRelationUpdate):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsNthLastChild):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsNthLastChildOf):
* dom/ShadowRoot.cpp:
(WebCore::ShadowRoot::childrenChanged): Invalidate the subtree whenever a child node is mutated.
* dom/ShadowRoot.h:
* domjit/DOMJITHelpers.h:
(WebCore::DOMJIT::branchTestIsShadowRootFlagOnNode): Added.
(WebCore::DOMJIT::branchTestIsElementOrShadowRootFlagOnNode): Added.

LayoutTests:

Added ref tests for matching positional pseudo classes on direct child of shadow roots
during style resolutions and DOM API matching with C++ selector checker and selector compilers.

Also added a test for invalidating these selectors.

* fast/shadow-dom/nth-node-on-shadow-child-expected.html: Added.
* fast/shadow-dom/nth-node-on-shadow-child-invalidation-expected.html: Added.
* fast/shadow-dom/nth-node-on-shadow-child-invalidation.html: Added.
* fast/shadow-dom/nth-node-on-shadow-child-no-jit-expected.html: Added.
* fast/shadow-dom/nth-node-on-shadow-child-no-jit.html: Added.
* fast/shadow-dom/nth-node-on-shadow-child.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@235917 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-expected.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-invalidation-expected.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-invalidation.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-no-jit-expected.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-no-jit.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/nth-node-on-shadow-child.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/css/SelectorChecker.cpp
Source/WebCore/cssjit/SelectorCompiler.cpp
Source/WebCore/dom/ShadowRoot.cpp
Source/WebCore/dom/ShadowRoot.h
Source/WebCore/domjit/DOMJITHelpers.h

index a3b974a..fb32d91 100644 (file)
@@ -1,3 +1,23 @@
+2018-09-08  Ryosuke Niwa  <rniwa@webkit.org>
+
+        :first-child, :last-child, :nth-child, and :nth-of-type don't work on shadow root's children
+        https://bugs.webkit.org/show_bug.cgi?id=166748
+        <rdar://problem/29649177>
+
+        Reviewed by Yusuke Suzuki.
+
+        Added ref tests for matching positional pseudo classes on direct child of shadow roots
+        during style resolutions and DOM API matching with C++ selector checker and selector compilers.
+
+        Also added a test for invalidating these selectors.
+
+        * fast/shadow-dom/nth-node-on-shadow-child-expected.html: Added.
+        * fast/shadow-dom/nth-node-on-shadow-child-invalidation-expected.html: Added.
+        * fast/shadow-dom/nth-node-on-shadow-child-invalidation.html: Added.
+        * fast/shadow-dom/nth-node-on-shadow-child-no-jit-expected.html: Added.
+        * fast/shadow-dom/nth-node-on-shadow-child-no-jit.html: Added.
+        * fast/shadow-dom/nth-node-on-shadow-child.html: Added.
+
 2018-09-11  Ryosuke Niwa  <rniwa@webkit.org>
 
         Updated the test expectation as this test also causes a crash in release builds.
diff --git a/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-expected.html b/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-expected.html
new file mode 100644 (file)
index 0000000..e704d24
--- /dev/null
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+    <p>Test passes if you see a single 100px by 100px green box below.</p>
+    <div style="width: 100px; height: 100px; background: green;"></div>
+</body>
+</html>
diff --git a/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-invalidation-expected.html b/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-invalidation-expected.html
new file mode 100644 (file)
index 0000000..2775ece
--- /dev/null
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+    <p>Test passes if you see a single 200px by 200px green box below.</p>
+    <div style="width: 200px; height: 200px; background: green;"></div>
+</body>
+</html>
diff --git a/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-invalidation.html b/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-invalidation.html
new file mode 100644 (file)
index 0000000..291d6d8
--- /dev/null
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>Test passes if you see a single 200px by 200px green box below.</p>
+<div id="container" style="width: 200px; height: 200px; background: green;"></div>
+<script>
+
+function style(pseudoClasses) {
+    return `<style>
+    .match, .not-match { display: block; float: left; width: 100px; height: 15px; color: green; font-size: 12px; overflow: hidden; }
+    .match { background: red; }
+    .not-match { background: green; }
+    .match${pseudoClasses} { background: green; }
+    .not-match${pseudoClasses} { background: red; }
+</style>`;
+}
+
+function test(markup, action) {
+    const host = document.createElement('div');
+    const shadowRoot = host.attachShadow({mode: 'closed'});
+    document.getElementById('container').appendChild(host);
+    shadowRoot.innerHTML = markup;
+    return () => action(shadowRoot);
+}
+
+const tests = [
+    test(`<old-first></old-first><new-first class="match">newly first ${style(':first-child')}</new-first>`,
+        shadowRoot => shadowRoot.querySelector('old-first').remove()),
+    test(`<old-first class="not-match">old first ${style(':first-child')}</old-first>`,
+        shadowRoot => shadowRoot.prepend(document.createElement('div'))),
+
+    test(`<new-last class="match">newly last ${style(':last-child')}</new-last><old-last></old-last>`,
+        shadowRoot => shadowRoot.querySelector('old-last').remove()),
+    test(`<old-last class="not-match">old last ${style(':last-child')}</old-last>`,
+        shadowRoot => shadowRoot.append(document.createElement('div'))),
+
+    test(`<div></div><div class="match">only child 1 ${style(':only-child')}</div>`,
+        shadowRoot => shadowRoot.firstChild.remove()),
+    test(`<div class="match">only child 2 ${style(':only-child')}</div><div></div>`,
+        shadowRoot => shadowRoot.lastChild.remove()),
+    test(`<div class="not-match">only child 3 ${style(':only-child')}</div>`,
+        shadowRoot => shadowRoot.prepend(document.createElement('div'))),
+    test(`<div class="not-match">only child 4 ${style(':only-child')}</div>`,
+        shadowRoot => shadowRoot.append(document.createElement('div'))),
+
+    test(`<div></div><div class="match">1st child ${style(':nth-child(1)')}</div>`,
+        shadowRoot => shadowRoot.firstChild.remove()),
+    test(`<div></div><div class="not-match">2nd child ${style(':nth-child(2)')}</div>`,
+        shadowRoot => shadowRoot.prepend(document.createElement('div'))),
+
+    test(`<div class="match">3rd last child ${style(':nth-last-child(3)')}</div><div></div><div></div><div></div>`,
+        shadowRoot => shadowRoot.lastChild.remove()),
+    test(`<div></div><div class="not-match">4th last child ${style(':nth-last-child(4)')}</div><div></div><div></div><div></div>`,
+        shadowRoot => shadowRoot.append(document.createElement('div'))),
+
+    test(`<new-first></new-first><div></div><new-first class="match">first type 1 ${style(':first-of-type')}</new-first>`,
+        shadowRoot => shadowRoot.querySelector('new-first').remove()),
+    test(`<div></div><old-first class="not-match">first type 2 ${style(':first-of-type')}</old-first>`,
+        shadowRoot => shadowRoot.prepend(document.createElement('old-first'))),
+
+    test(`<new-last class="match">last of type 1 ${style(':last-of-type')}</new-last><div></div><new-last></new-last>`,
+        shadowRoot => shadowRoot.lastChild.remove()),
+    test(`<old-last class="not-match">last of type 2 ${style(':last-of-type')}</old-last><div></div>`,
+        shadowRoot => shadowRoot.append(document.createElement('old-last'))),
+
+    test(`<div></div><section></section><div class="match">only of type 1 ${style(':only-of-type')}</div>`,
+        shadowRoot => shadowRoot.firstChild.remove()),
+    test(`<section></section><div class="match">only of type 2 ${style(':only-of-type')}</div><div></div>`,
+        shadowRoot => shadowRoot.lastChild.remove()),
+    test(`<a-b></a-b><div class="not-match">only of type 3 ${style(':only-of-type')}</div><section></section>`,
+        shadowRoot => shadowRoot.insertBefore(document.createElement('div'), shadowRoot.childNodes[2])),
+    test(`<div class="not-match">only of type 4 ${style(':only-of-type')}</div><c-d></c-d>`,
+        shadowRoot => shadowRoot.append(document.createElement('div'))),
+
+    test(`<div></div><section></section><div class="match">1st of type ${style(':nth-of-type(1)')}</div>`,
+        shadowRoot => shadowRoot.firstChild.remove()),
+    test(`<div></div><section></section><div class="not-match">2nd of type ${style(':nth-of-type(2)')}</div>`,
+        shadowRoot => shadowRoot.prepend(document.createElement('div'))),
+
+    test(`<div class="match">3rd last of type ${style(':nth-last-of-type(3)')}</div><div></div><div></div><section></section><div></div>`,
+        shadowRoot => shadowRoot.querySelector('section').previousSibling.remove()),
+    test(`<div></div><div class="not-match">4th last of type ${style(':nth-last-of-type(4)')}</div><div></div><div></div><section></section><div></div>`,
+        shadowRoot => shadowRoot.append(document.createElement('div'))),
+
+    test(`<div></div><div class="match">1st, 3rd last of type ${style(':first-child:nth-last-of-type(3)')}</div><div></div><div></div><section></section><div></div>`,
+        shadowRoot => { shadowRoot.querySelector('section').previousSibling.remove(); shadowRoot.firstChild.remove(); }),
+
+    test(`<div></div><div class="match">2nd, 2rd last of type ${style(':nth-child(3):nth-of-type(2)')}</div>`,
+        shadowRoot => { shadowRoot.prepend(document.createElement('a-b')); shadowRoot.append(document.createElement('div')); }),
+];
+
+if (window.internals)
+    internals.updateLayoutIgnorePendingStylesheetsAndRunPostLayoutTasks();
+else
+    window.rect = document.body.getBoundingClientRect();
+
+for (const test of tests)
+    test();
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-no-jit-expected.html b/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-no-jit-expected.html
new file mode 100644 (file)
index 0000000..e704d24
--- /dev/null
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+    <p>Test passes if you see a single 100px by 100px green box below.</p>
+    <div style="width: 100px; height: 100px; background: green;"></div>
+</body>
+</html>
diff --git a/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-no-jit.html b/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child-no-jit.html
new file mode 100644 (file)
index 0000000..ff4c7e1
--- /dev/null
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<script>
+const host = document.createElement('div');
+
+const shadowRoot = host.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = `
+<some-element class="item">
+    some-element:first-child
+    <style>
+    .item { display: block; width: 100px; height: 10px; background: red; color: green; font-size: 9px; overflow: hidden; }
+    :matches(noop, some-element:first-child) { background: green; }
+    :matches(noop, section:nth-of-type(1)) { background: green; }
+    :matches(noop, section:nth-child(3)) { background: green; }
+    :matches(noop, .item:nth-last-child(6)) { background: green; }
+    :matches(noop, a-element:first-of-type) { background: green; }
+    :matches(noop, b-element:only-of-type) { background: green; }
+    :matches(noop, a-element:last-of-type) { background: green; }
+    :matches(noop, c-element:nth-last-of-type(1)) { background: green; }
+    :matches(noop, other-element:last-child) { background: green; }
+    </style>
+</some-element>
+<section class="item">section:nth-of-type(1)</section>
+<section class="item">section:nth-child(3)</section>
+<section class="item">.item:nth-last-child(6)</section>
+<a-element class="item">a-element:first-of-type</a-element>
+<b-element class="item">b-element:only-of-type</b-element>
+<a-element class="item">a-element:last-of-type</a-element>
+<c-element class="item">c-element:nth-last-of-type(1)</c-element>
+<other-element class="item">other-element:last-child</other-element>`;
+document.body.appendChild(host);
+
+const loneHost = document.createElement('div');
+const loneShadowRoot = loneHost.attachShadow({mode: 'closed'});
+loneShadowRoot.innerHTML = `<div class="item">.item:only-child
+<style>
+.item { display: block; width: 100px; height: 10px; background: red; color: green; font-size: 9px; overflow: hidden; }
+:matches(noop, .item:only-child) { background: green; }
+</style>
+</div>`;
+document.body.appendChild(loneHost);
+
+for (const element of [...shadowRoot.querySelectorAll('.item'), loneShadowRoot.firstChild]) {
+    const selector = element.innerText;
+    if (!element.matches(`:matches(noop, ${selector}`))
+        logError(`${selector} did not match an element via element.matches`);
+    else {
+        const queryResult = element.getRootNode().querySelectorAll(`:matches(noop, ${selector}`);
+        if (queryResult.length != 1) {
+            console.log(queryResult);
+            logError(`${selector} matches ${queryResult.length} elements`);            
+        }
+    }
+}
+
+function logError(error) {
+    const container = document.createElement('p');
+    container.textContent = error;
+    document.body.append(container);
+}
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child.html b/LayoutTests/fast/shadow-dom/nth-node-on-shadow-child.html
new file mode 100644 (file)
index 0000000..82a7a17
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>Test passes if you see a single 100px by 100px green box below.</p>
+<script>
+const host = document.createElement('div');
+const shadowRoot = host.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = `
+<some-element class="item">
+    some-element:first-child
+    <style>
+    .item { display: block; width: 100px; height: 10px; background: red; color: green; font-size: 9px; overflow: hidden; }
+    some-element:first-child { background: green; }
+    section:nth-of-type(1) { background: green; }
+    section:nth-child(3) { background: green; }
+    .item:nth-last-child(6) { background: green; }
+    a-element:first-of-type { background: green; }
+    b-element:only-of-type { background: green; }
+    a-element:last-of-type { background: green; }
+    c-element:nth-last-of-type(1) { background: green; }
+    other-element:last-child { background: green; }
+    </style>
+</some-element>
+<section class="item">section:nth-of-type(1)</section>
+<section class="item">section:nth-child(3)</section>
+<section class="item">.item:nth-last-child(6)</section>
+<a-element class="item">a-element:first-of-type</a-element>
+<b-element class="item">b-element:only-of-type</b-element>
+<a-element class="item">a-element:last-of-type</a-element>
+<c-element class="item">c-element:nth-last-of-type(1)</c-element>
+<other-element class="item">other-element:last-child</other-element>`;
+document.body.appendChild(host);
+
+const loneHost = document.createElement('div');
+const loneShadowRoot = loneHost.attachShadow({mode: 'closed'});
+loneShadowRoot.innerHTML = `<div class="item">.item:only-child
+<style>
+.item { display: block; width: 100px; height: 10px; background: red; color: green; font-size: 9px; overflow: hidden; }
+.item:only-child { background: green; }
+</style>
+</div>`;
+document.body.appendChild(loneHost);
+
+for (const element of [...shadowRoot.querySelectorAll('.item'), loneShadowRoot.firstChild]) {
+    const selector = element.innerText;
+    if (!element.matches(selector))
+        logError(`${selector} did not match an element via element.matches`);
+    else {
+        const queryResult = element.getRootNode().querySelectorAll(selector);
+        if (queryResult.length != 1) {
+            console.log(queryResult);
+            logError(`${selector} matches ${queryResult.length} elements`);            
+        }
+    }
+}
+
+function logError(error) {
+    const container = document.createElement('p');
+    container.textContent = error;
+    document.body.append(container);
+}
+
+</script>
+</body>
+</html>
index cdfcf15..8a0864f 100644 (file)
@@ -1,3 +1,38 @@
+2018-09-08  Ryosuke Niwa  <rniwa@webkit.org>
+
+        :first-child, :last-child, :nth-child, and :nth-of-type don't work on shadow root's children
+        https://bugs.webkit.org/show_bug.cgi?id=166748
+        <rdar://problem/29649177>
+
+        Reviewed by Yusuke Suzuki.
+
+        Added the support for matching positional pseudo classes. For now, we invalidate whenever a child node
+        of a non-UA ShadowRoot is mutated instead of a fine-grained style invalidation as done for regular elements.
+
+        Tests: fast/shadow-dom/nth-node-on-shadow-child-invalidation.html
+               fast/shadow-dom/nth-node-on-shadow-child-no-jit.html
+               fast/shadow-dom/nth-node-on-shadow-child.html
+
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::checkOne const):
+        * cssjit/SelectorCompiler.cpp:
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateWalkToParentElementOrShadowRoot):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsFirstChild):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsLastChild):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsOnlyChild):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateNthChildParentCheckAndRelationUpdate):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsNthChild):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsNthChildOf):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateNthLastChildParentCheckAndRelationUpdate):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsNthLastChild):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsNthLastChildOf):
+        * dom/ShadowRoot.cpp:
+        (WebCore::ShadowRoot::childrenChanged): Invalidate the subtree whenever a child node is mutated.
+        * dom/ShadowRoot.h:
+        * domjit/DOMJITHelpers.h:
+        (WebCore::DOMJIT::branchTestIsShadowRootFlagOnNode): Added.
+        (WebCore::DOMJIT::branchTestIsElementOrShadowRootFlagOnNode): Added.
+
 2018-09-11  Per Arne Vollan  <pvollan@apple.com>
 
         Addressing post-review feedback on r235619.
index 0fbb0ee..ebb1510 100644 (file)
@@ -736,69 +736,92 @@ bool SelectorChecker::checkOne(CheckingContext& checkingContext, const LocalCont
 
                 return result;
             }
-        case CSSSelector::PseudoClassFirstChild:
+        case CSSSelector::PseudoClassFirstChild: {
             // first-child matches the first child that is an element
-            if (const Element* parentElement = element.parentElement()) {
-                bool isFirstChild = isFirstChildElement(element);
-                if (isFirstChild)
-                    addStyleRelation(checkingContext, element, Style::Relation::FirstChild);
-                addStyleRelation(checkingContext, *parentElement, Style::Relation::ChildrenAffectedByFirstChildRules);
-                return isFirstChild;
-            }
-            break;
-        case CSSSelector::PseudoClassFirstOfType:
+            bool isFirstChild = isFirstChildElement(element);
+            auto* parent = element.parentNode();
+            if (is<Element>(parent))
+                addStyleRelation(checkingContext, downcast<Element>(*parent), Style::Relation::ChildrenAffectedByFirstChildRules);
+            else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
+            if (!isFirstChild)
+                break;
+            addStyleRelation(checkingContext, element, Style::Relation::FirstChild);
+            return true;
+        }
+        case CSSSelector::PseudoClassFirstOfType: {
             // first-of-type matches the first element of its type
-            if (auto* parentElement = element.parentElement()) {
+            auto* parent = element.parentNode();
+            if (is<Element>(parent)) {
                 auto relation = context.isSubjectOrAdjacentElement ? Style::Relation::ChildrenAffectedByForwardPositionalRules : Style::Relation::DescendantsAffectedByForwardPositionalRules;
-                addStyleRelation(checkingContext, *parentElement, relation);
-                return isFirstOfType(element, element.tagQName());
-            }
-            break;
-        case CSSSelector::PseudoClassLastChild:
+                addStyleRelation(checkingContext, downcast<Element>(*parent), relation);
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
+            return isFirstOfType(element, element.tagQName());
+        }
+        case CSSSelector::PseudoClassLastChild: {
             // last-child matches the last child that is an element
-            if (const Element* parentElement = element.parentElement()) {
-                bool isLastChild = parentElement->isFinishedParsingChildren() && isLastChildElement(element);
-                if (isLastChild)
-                    addStyleRelation(checkingContext, element, Style::Relation::LastChild);
-                addStyleRelation(checkingContext, *parentElement, Style::Relation::ChildrenAffectedByLastChildRules);
-                return isLastChild;
-            }
-            break;
-        case CSSSelector::PseudoClassLastOfType:
+            auto* parent = element.parentNode();
+            bool isLastChild = isLastChildElement(element);
+            if (is<Element>(parent)) {
+                auto& parentElement = downcast<Element>(*parent);
+                if (!parentElement.isFinishedParsingChildren())
+                    isLastChild = false;
+                addStyleRelation(checkingContext, parentElement, Style::Relation::ChildrenAffectedByLastChildRules);
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
+            if (!isLastChild)
+                break;
+            addStyleRelation(checkingContext, element, Style::Relation::LastChild);
+            return true;
+        }
+        case CSSSelector::PseudoClassLastOfType: {
             // last-of-type matches the last element of its type
-            if (Element* parentElement = element.parentElement()) {
+            auto* parent = element.parentNode();
+            if (is<Element>(parent)) {
+                auto& parentElement = downcast<Element>(*parent);
                 auto relation = context.isSubjectOrAdjacentElement ? Style::Relation::ChildrenAffectedByBackwardPositionalRules : Style::Relation::DescendantsAffectedByBackwardPositionalRules;
-                addStyleRelation(checkingContext, *parentElement, relation);
-                if (!parentElement->isFinishedParsingChildren())
+                addStyleRelation(checkingContext, parentElement, relation);
+                if (!parentElement.isFinishedParsingChildren())
                     return false;
-                return isLastOfType(element, element.tagQName());
-            }
-            break;
-        case CSSSelector::PseudoClassOnlyChild:
-            if (Element* parentElement = element.parentElement()) {
-                bool firstChild = isFirstChildElement(element);
-                bool onlyChild = firstChild && parentElement->isFinishedParsingChildren() && isLastChildElement(element);
-                addStyleRelation(checkingContext, *parentElement, Style::Relation::ChildrenAffectedByFirstChildRules);
-                addStyleRelation(checkingContext, *parentElement, Style::Relation::ChildrenAffectedByLastChildRules);
-                if (firstChild)
-                    addStyleRelation(checkingContext, element, Style::Relation::FirstChild);
-                if (onlyChild)
-                    addStyleRelation(checkingContext, element, Style::Relation::LastChild);
-                return onlyChild;
-            }
-            break;
-        case CSSSelector::PseudoClassOnlyOfType:
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
+            return isLastOfType(element, element.tagQName());
+        }
+        case CSSSelector::PseudoClassOnlyChild: {
+            auto* parent = element.parentNode();
+            bool firstChild = isFirstChildElement(element);
+            bool onlyChild = firstChild && isLastChildElement(element);
+            if (is<Element>(parent)) {
+                auto& parentElement = downcast<Element>(*parent);
+                addStyleRelation(checkingContext, parentElement, Style::Relation::ChildrenAffectedByFirstChildRules);
+                addStyleRelation(checkingContext, parentElement, Style::Relation::ChildrenAffectedByLastChildRules);
+                if (!parentElement.isFinishedParsingChildren())
+                    onlyChild = false;
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
+            if (firstChild)
+                addStyleRelation(checkingContext, element, Style::Relation::FirstChild);
+            if (onlyChild)
+                addStyleRelation(checkingContext, element, Style::Relation::LastChild);
+            return onlyChild;
+        }
+        case CSSSelector::PseudoClassOnlyOfType: {
             // FIXME: This selector is very slow.
-            if (Element* parentElement = element.parentElement()) {
+            auto* parent = element.parentNode();
+            if (is<Element>(parent)) {
+                auto& parentElement = downcast<Element>(*parent);
                 auto forwardRelation = context.isSubjectOrAdjacentElement ? Style::Relation::ChildrenAffectedByForwardPositionalRules : Style::Relation::DescendantsAffectedByForwardPositionalRules;
-                addStyleRelation(checkingContext, *parentElement, forwardRelation);
+                addStyleRelation(checkingContext, parentElement, forwardRelation);
                 auto backwardRelation = context.isSubjectOrAdjacentElement ? Style::Relation::ChildrenAffectedByBackwardPositionalRules : Style::Relation::DescendantsAffectedByBackwardPositionalRules;
-                addStyleRelation(checkingContext, *parentElement, backwardRelation);
-                if (!parentElement->isFinishedParsingChildren())
+                addStyleRelation(checkingContext, parentElement, backwardRelation);
+
+                if (!parentElement.isFinishedParsingChildren())
                     return false;
-                return isFirstOfType(element, element.tagQName()) && isLastOfType(element, element.tagQName());
-            }
-            break;
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
+            return isFirstOfType(element, element.tagQName()) && isLastOfType(element, element.tagQName());
+        }
         case CSSSelector::PseudoClassMatches:
             {
                 bool hasMatchedAnything = false;
@@ -836,88 +859,96 @@ bool SelectorChecker::checkOne(CheckingContext& checkingContext, const LocalCont
                 return downcast<HTMLTextFormControlElement>(element).isPlaceholderVisible();
             }
             return false;
-        case CSSSelector::PseudoClassNthChild:
-            if (auto* parentElement = element.parentElement()) {
+        case CSSSelector::PseudoClassNthChild: {
+            auto* parent = element.parentNode();
+            if (is<Element>(parent)) {
                 auto relation = context.isSubjectOrAdjacentElement ? Style::Relation::ChildrenAffectedByForwardPositionalRules : Style::Relation::DescendantsAffectedByForwardPositionalRules;
-                addStyleRelation(checkingContext, *parentElement, relation);
+                addStyleRelation(checkingContext, downcast<Element>(*parent), relation);
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
 
-                if (const CSSSelectorList* selectorList = selector.selectorList()) {
-                    unsigned selectorListSpecificity;
-                    if (!matchSelectorList(checkingContext, context, element, *selectorList, selectorListSpecificity))
-                        return false;
-                    specificity = CSSSelector::addSpecificities(specificity, selectorListSpecificity);
-                }
+            if (const CSSSelectorList* selectorList = selector.selectorList()) {
+                unsigned selectorListSpecificity;
+                if (!matchSelectorList(checkingContext, context, element, *selectorList, selectorListSpecificity))
+                    return false;
+                specificity = CSSSelector::addSpecificities(specificity, selectorListSpecificity);
+            }
 
-                int count = 1;
-                if (const CSSSelectorList* selectorList = selector.selectorList()) {
-                    for (Element* sibling = ElementTraversal::previousSibling(element); sibling; sibling = ElementTraversal::previousSibling(*sibling)) {
-                        unsigned ignoredSpecificity;
-                        if (matchSelectorList(checkingContext, context, *sibling, *selectorList, ignoredSpecificity))
-                            ++count;
-                    }
-                } else {
-                    count += countElementsBefore(element);
-                    addStyleRelation(checkingContext, element, Style::Relation::NthChildIndex, count);
+            int count = 1;
+            if (const CSSSelectorList* selectorList = selector.selectorList()) {
+                for (Element* sibling = ElementTraversal::previousSibling(element); sibling; sibling = ElementTraversal::previousSibling(*sibling)) {
+                    unsigned ignoredSpecificity;
+                    if (matchSelectorList(checkingContext, context, *sibling, *selectorList, ignoredSpecificity))
+                        ++count;
                 }
-
-                if (selector.matchNth(count))
-                    return true;
+            } else {
+                count += countElementsBefore(element);
+                addStyleRelation(checkingContext, element, Style::Relation::NthChildIndex, count);
             }
+
+            if (selector.matchNth(count))
+                return true;
             break;
-        case CSSSelector::PseudoClassNthOfType:
-            if (auto* parentElement = element.parentElement()) {
+        }
+        case CSSSelector::PseudoClassNthOfType: {
+            auto* parent = element.parentNode();
+            if (is<Element>(parent)) {
                 auto relation = context.isSubjectOrAdjacentElement ? Style::Relation::ChildrenAffectedByForwardPositionalRules : Style::Relation::DescendantsAffectedByForwardPositionalRules;
-                addStyleRelation(checkingContext, *parentElement, relation);
+                addStyleRelation(checkingContext, downcast<Element>(*parent), relation);
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
 
-                int count = 1 + countElementsOfTypeBefore(element, element.tagQName());
-                if (selector.matchNth(count))
-                    return true;
-            }
+            int count = 1 + countElementsOfTypeBefore(element, element.tagQName());
+            if (selector.matchNth(count))
+                return true;
             break;
-        case CSSSelector::PseudoClassNthLastChild:
-            if (Element* parentElement = element.parentElement()) {
+        }
+        case CSSSelector::PseudoClassNthLastChild: {
+            auto* parent = element.parentNode();
+            if (is<Element>(parent)) {
+                auto& parentElement = downcast<Element>(*parent);
                 if (const CSSSelectorList* selectorList = selector.selectorList()) {
                     unsigned selectorListSpecificity;
                     if (!matchSelectorList(checkingContext, context, element, *selectorList, selectorListSpecificity))
                         return false;
                     specificity = CSSSelector::addSpecificities(specificity, selectorListSpecificity);
 
-                    addStyleRelation(checkingContext, *parentElement, Style::Relation::ChildrenAffectedByPropertyBasedBackwardPositionalRules);
+                    addStyleRelation(checkingContext, parentElement, Style::Relation::ChildrenAffectedByPropertyBasedBackwardPositionalRules);
                 } else {
                     auto relation = context.isSubjectOrAdjacentElement ? Style::Relation::ChildrenAffectedByBackwardPositionalRules : Style::Relation::DescendantsAffectedByBackwardPositionalRules;
-                    addStyleRelation(checkingContext, *parentElement, relation);
+                    addStyleRelation(checkingContext, parentElement, relation);
                 }
-
-                if (!parentElement->isFinishedParsingChildren())
+                if (!parentElement.isFinishedParsingChildren())
                     return false;
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
+
+            int count = 1;
+            if (const CSSSelectorList* selectorList = selector.selectorList()) {
+                for (Element* sibling = ElementTraversal::nextSibling(element); sibling; sibling = ElementTraversal::nextSibling(*sibling)) {
+                    unsigned ignoredSpecificity;
+                    if (matchSelectorList(checkingContext, context, *sibling, *selectorList, ignoredSpecificity))
+                        ++count;
+                }
+            } else
+                count += countElementsAfter(element);
 
-                int count = 1;
-                if (const CSSSelectorList* selectorList = selector.selectorList()) {
-                    for (Element* sibling = ElementTraversal::nextSibling(element); sibling; sibling = ElementTraversal::nextSibling(*sibling)) {
-                        unsigned ignoredSpecificity;
-                        if (matchSelectorList(checkingContext, context, *sibling, *selectorList, ignoredSpecificity))
-                            ++count;
-                    }
-                } else
-                    count += countElementsAfter(element);
-
-                if (selector.matchNth(count))
-                    return true;
-            }
-            break;
-        case CSSSelector::PseudoClassNthLastOfType:
-            if (Element* parentElement = element.parentElement()) {
+            return selector.matchNth(count);
+        }
+        case CSSSelector::PseudoClassNthLastOfType: {
+            auto* parent = element.parentNode();
+            if (is<Element>(parent)) {
+                auto& parentElement = downcast<Element>(*parent);
                 auto relation = context.isSubjectOrAdjacentElement ? Style::Relation::ChildrenAffectedByBackwardPositionalRules : Style::Relation::DescendantsAffectedByBackwardPositionalRules;
-                addStyleRelation(checkingContext, *parentElement, relation);
+                addStyleRelation(checkingContext, parentElement, relation);
 
-                if (!parentElement->isFinishedParsingChildren())
+                if (!parentElement.isFinishedParsingChildren())
                     return false;
-
-                int count = 1 + countElementsOfTypeAfter(element, element.tagQName());
-                if (selector.matchNth(count))
-                    return true;
-            }
-            break;
+            } else if (!is<ShadowRoot>(parent))
+                break; // FIXME: Add the support for specifying relations on ShadowRoot.
+            int count = 1 + countElementsOfTypeAfter(element, element.tagQName());
+            return selector.matchNth(count);
+        }
         case CSSSelector::PseudoClassTarget:
             if (&element == element.document().cssTarget())
                 return true;
index 71cfa1b..fee16ac 100644 (file)
@@ -274,6 +274,7 @@ private:
     void generateRightmostTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateWalkToParentNode(Assembler::RegisterID targetRegister);
     void generateWalkToParentElement(Assembler::JumpList& failureCases, Assembler::RegisterID targetRegister);
+    void generateWalkToParentElementOrShadowRoot(Assembler::JumpList& failureCases, Assembler::RegisterID targetRegister);
     void generateParentElementTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateAncestorTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment&);
 
@@ -345,6 +346,9 @@ private:
     Assembler::Jump modulo(JSC::MacroAssembler::ResultCondition, Assembler::RegisterID inputDividend, int divisor);
     void moduloIsZero(Assembler::JumpList& failureCases, Assembler::RegisterID inputDividend, int divisor);
 
+    void generateNthChildParentCheckAndRelationUpdate(Assembler::JumpList& failureCases, const SelectorFragment&);
+    void generateNthLastChildParentCheckAndRelationUpdate(Assembler::JumpList& failureCases, const SelectorFragment&);
+
     void pushMacroAssemblerRegisters();
     void popMacroAssemblerRegisters(StackAllocator&);
     bool generatePrologue();
@@ -2056,6 +2060,16 @@ void SelectorCodeGenerator::generateWalkToParentElement(Assembler::JumpList& fai
     failureCases.append(DOMJIT::branchTestIsElementFlagOnNode(m_assembler, Assembler::Zero, targetRegister));
 }
 
+void SelectorCodeGenerator::generateWalkToParentElementOrShadowRoot(Assembler::JumpList& failureCases, Assembler::RegisterID targetRegister)
+{
+    //    ContainerNode* parent = parentNode()
+    //    if (!parent || !(parent->isElementNode() || parent->isShadowRoot()))
+    //         failure
+    generateWalkToParentNode(targetRegister);
+    failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, targetRegister));
+    failureCases.append(DOMJIT::branchTestIsElementOrShadowRootFlagOnNode(m_assembler, Assembler::Zero, targetRegister));
+}
+
 void SelectorCodeGenerator::generateParentElementTreeWalker(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
 {
     Assembler::JumpList traversalFailureCases;
@@ -3174,13 +3188,10 @@ void SelectorCodeGenerator::generateElementIsFirstChild(Assembler::JumpList& fai
         failureCases.append(m_assembler.jump());
         successCase.link(&m_assembler);
         LocalRegister parent(m_registerAllocator);
-        generateWalkToParentElement(failureCases, parent);
+        generateWalkToParentElementOrShadowRoot(failureCases, parent);
         return;
     }
 
-    Assembler::RegisterID parentElement = m_registerAllocator.allocateRegister();
-    generateWalkToParentElement(failureCases, parentElement);
-
     // Zero in isFirstChildRegister is the success case. The register is set to non-zero if a sibling if found.
     LocalRegister isFirstChildRegister(m_registerAllocator);
     m_assembler.move(Assembler::TrustedImm32(0), isFirstChildRegister);
@@ -3194,20 +3205,30 @@ void SelectorCodeGenerator::generateElementIsFirstChild(Assembler::JumpList& fai
         successCase.link(&m_assembler);
     }
 
+    LocalRegister parentNode(m_registerAllocator);
+    generateWalkToParentNode(parentNode);
+    failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, parentNode));
+    Assembler::Jump notElement = DOMJIT::branchTestIsElementFlagOnNode(m_assembler, Assembler::Zero, parentNode);
+
     LocalRegister checkingContext(m_registerAllocator);
     Assembler::Jump notResolvingStyle = jumpIfNotResolvingStyle(checkingContext);
 
-    generateAddStyleRelation(checkingContext, parentElement, Style::Relation::ChildrenAffectedByFirstChildRules);
-    m_registerAllocator.deallocateRegister(parentElement);
-
+    generateAddStyleRelation(checkingContext, parentNode, Style::Relation::ChildrenAffectedByFirstChildRules);
     // The parent marking is unconditional. If the matching is not a success, we can now fail.
     // Otherwise we need to apply setFirstChildState() on the RenderStyle.
+    Assembler::Label checkWithRelation(m_assembler.label());
     failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isFirstChildRegister));
-
     generateAddStyleRelation(checkingContext, elementAddressRegister, Style::Relation::FirstChild);
+    Assembler::Jump successCase = m_assembler.jump();
+
+    notElement.link(&m_assembler);
+    failureCases.append(DOMJIT::branchTestIsShadowRootFlagOnNode(m_assembler, Assembler::Zero, parentNode));
+    jumpIfNotResolvingStyle(checkingContext).linkTo(checkWithRelation, &m_assembler);
 
     notResolvingStyle.link(&m_assembler);
     failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isFirstChildRegister));
+
+    successCase.link(&m_assembler);
 }
 
 static bool elementIsHovered(const Element* element)
@@ -3265,22 +3286,23 @@ void SelectorCodeGenerator::generateElementIsLastChild(Assembler::JumpList& fail
 
         successCase.link(&m_assembler);
         LocalRegister parent(m_registerAllocator);
-        generateWalkToParentElement(failureCases, parent);
+        generateWalkToParentElementOrShadowRoot(failureCases, parent);
 
         failureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parent, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
 
         return;
     }
 
-    Assembler::RegisterID parentElement = m_registerAllocator.allocateRegister();
-    generateWalkToParentElement(failureCases, parentElement);
+    LocalRegister parentNode(m_registerAllocator);
+    generateWalkToParentNode(parentNode);
+    failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, parentNode));
 
     // Zero in isLastChildRegister is the success case. The register is set to non-zero if a sibling if found.
     LocalRegister isLastChildRegister(m_registerAllocator);
     m_assembler.move(Assembler::TrustedImm32(0), isLastChildRegister);
 
     {
-        Assembler::Jump notFinishedParsingChildren = m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentElement, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished()));
+        Assembler::Jump notFinishedParsingChildren = m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentNode, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished()));
 
         Assembler::JumpList successCase = jumpIfNoNextAdjacentElement();
 
@@ -3290,20 +3312,27 @@ void SelectorCodeGenerator::generateElementIsLastChild(Assembler::JumpList& fail
         successCase.link(&m_assembler);
     }
 
+    Assembler::Jump notElement = DOMJIT::branchTestIsElementFlagOnNode(m_assembler, Assembler::Zero, parentNode);
+
     LocalRegister checkingContext(m_registerAllocator);
     Assembler::Jump notResolvingStyle = jumpIfNotResolvingStyle(checkingContext);
 
-    generateAddStyleRelation(checkingContext, parentElement, Style::Relation::ChildrenAffectedByLastChildRules);
-    m_registerAllocator.deallocateRegister(parentElement);
-
+    generateAddStyleRelation(checkingContext, parentNode, Style::Relation::ChildrenAffectedByLastChildRules);
     // The parent marking is unconditional. If the matching is not a success, we can now fail.
     // Otherwise we need to apply setLastChildState() on the RenderStyle.
+    Assembler::Label checkWithRelation(m_assembler.label());
     failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isLastChildRegister));
-
     generateAddStyleRelation(checkingContext, elementAddressRegister, Style::Relation::LastChild);
+    Assembler::Jump successCase = m_assembler.jump();
+
+    notElement.link(&m_assembler);
+    failureCases.append(DOMJIT::branchTestIsShadowRootFlagOnNode(m_assembler, Assembler::Zero, parentNode));
+    jumpIfNotResolvingStyle(checkingContext).linkTo(checkWithRelation, &m_assembler);
 
     notResolvingStyle.link(&m_assembler);
     failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isLastChildRegister));
+
+    successCase.link(&m_assembler);
 }
 
 void SelectorCodeGenerator::generateElementIsOnlyChild(Assembler::JumpList& failureCases)
@@ -3319,15 +3348,16 @@ void SelectorCodeGenerator::generateElementIsOnlyChild(Assembler::JumpList& fail
         nextSuccessCase.link(&m_assembler);
 
         LocalRegister parent(m_registerAllocator);
-        generateWalkToParentElement(failureCases, parent);
+        generateWalkToParentElementOrShadowRoot(failureCases, parent);
 
         failureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parent, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
 
         return;
     }
 
-    Assembler::RegisterID parentElement = m_registerAllocator.allocateRegister();
-    generateWalkToParentElement(failureCases, parentElement);
+    LocalRegister parentNode(m_registerAllocator);
+    generateWalkToParentNode(parentNode);
+    failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, parentNode));
 
     // Zero in isOnlyChildRegister is the success case. The register is set to non-zero if a sibling if found.
     LocalRegister isOnlyChildRegister(m_registerAllocator);
@@ -3340,7 +3370,7 @@ void SelectorCodeGenerator::generateElementIsOnlyChild(Assembler::JumpList& fail
             localFailureCases.append(m_assembler.jump());
             successCase.link(&m_assembler);
         }
-        localFailureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentElement, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
+        localFailureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentNode, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
         Assembler::JumpList successCase = jumpIfNoNextAdjacentElement();
 
         localFailureCases.link(&m_assembler);
@@ -3349,23 +3379,29 @@ void SelectorCodeGenerator::generateElementIsOnlyChild(Assembler::JumpList& fail
         successCase.link(&m_assembler);
     }
 
+    Assembler::Jump notElement = DOMJIT::branchTestIsElementFlagOnNode(m_assembler, Assembler::Zero, parentNode);
+
     LocalRegister checkingContext(m_registerAllocator);
     Assembler::Jump notResolvingStyle = jumpIfNotResolvingStyle(checkingContext);
 
-    generateAddStyleRelation(checkingContext, parentElement, Style::Relation::ChildrenAffectedByFirstChildRules);
-    generateAddStyleRelation(checkingContext, parentElement, Style::Relation::ChildrenAffectedByLastChildRules);
-
-    m_registerAllocator.deallocateRegister(parentElement);
-
+    generateAddStyleRelation(checkingContext, parentNode, Style::Relation::ChildrenAffectedByFirstChildRules);
+    generateAddStyleRelation(checkingContext, parentNode, Style::Relation::ChildrenAffectedByLastChildRules);
     // The parent marking is unconditional. If the matching is not a success, we can now fail.
     // Otherwise we need to apply setLastChildState() on the RenderStyle.
+    Assembler::Label checkWithRelation(m_assembler.label());
     failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isOnlyChildRegister));
-
     generateAddStyleRelation(checkingContext, elementAddressRegister, Style::Relation::FirstChild);
     generateAddStyleRelation(checkingContext, elementAddressRegister, Style::Relation::LastChild);
+    Assembler::Jump successCase = m_assembler.jump();
+
+    notElement.link(&m_assembler);
+    failureCases.append(DOMJIT::branchTestIsShadowRootFlagOnNode(m_assembler, Assembler::Zero, parentNode));
+    jumpIfNotResolvingStyle(checkingContext).linkTo(checkWithRelation, &m_assembler);
 
     notResolvingStyle.link(&m_assembler);
     failureCases.append(m_assembler.branchTest32(Assembler::NonZero, isOnlyChildRegister));
+
+    successCase.link(&m_assembler);
 }
 
 static bool makeContextStyleUniqueIfNecessaryAndTestIsPlaceholderShown(const Element* element, SelectorChecker::CheckingContext* checkingContext)
@@ -3508,16 +3544,29 @@ static bool nthFilterIsAlwaysSatisified(int a, int b)
     return false;
 }
 
+void SelectorCodeGenerator::generateNthChildParentCheckAndRelationUpdate(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
+{
+    LocalRegister parentNode(m_registerAllocator);
+    generateWalkToParentNode(parentNode);
+    failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, parentNode));
+    Assembler::Jump notElement = DOMJIT::branchTestIsElementFlagOnNode(m_assembler, Assembler::Zero, parentNode);
+
+    generateWalkToParentElement(failureCases, parentNode);
+    auto relation = fragmentMatchesRightmostOrAdjacentElement(fragment)
+        ? Style::Relation::ChildrenAffectedByForwardPositionalRules
+        : Style::Relation::DescendantsAffectedByForwardPositionalRules;
+    generateAddStyleRelationIfResolvingStyle(parentNode, relation);
+    Assembler::Jump parentNodeCheckEnd = m_assembler.jump();
+
+    notElement.link(&m_assembler);
+    failureCases.append(DOMJIT::branchTestIsShadowRootFlagOnNode(m_assembler, Assembler::Zero, parentNode));
+
+    parentNodeCheckEnd.link(&m_assembler);
+}
+
 void SelectorCodeGenerator::generateElementIsNthChild(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
 {
-    {
-        LocalRegister parentElement(m_registerAllocator);
-        generateWalkToParentElement(failureCases, parentElement);
-        auto relation = fragmentMatchesRightmostOrAdjacentElement(fragment)
-            ? Style::Relation::ChildrenAffectedByForwardPositionalRules
-            : Style::Relation::DescendantsAffectedByForwardPositionalRules;
-        generateAddStyleRelationIfResolvingStyle(parentElement, relation);
-    }
+    generateNthChildParentCheckAndRelationUpdate(failureCases, fragment);
 
     Vector<std::pair<int, int>, 32> validSubsetFilters;
     validSubsetFilters.reserveInitialCapacity(fragment.nthChildFilters.size());
@@ -3573,14 +3622,7 @@ void SelectorCodeGenerator::generateElementIsNthChild(Assembler::JumpList& failu
 
 void SelectorCodeGenerator::generateElementIsNthChildOf(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
 {
-    {
-        LocalRegister parentElement(m_registerAllocator);
-        generateWalkToParentElement(failureCases, parentElement);
-        auto relation = fragmentMatchesRightmostOrAdjacentElement(fragment)
-            ? Style::Relation::ChildrenAffectedByForwardPositionalRules
-            : Style::Relation::DescendantsAffectedByForwardPositionalRules;
-        generateAddStyleRelationIfResolvingStyle(parentElement, relation);
-    }
+    generateNthChildParentCheckAndRelationUpdate(failureCases, fragment);
 
     // The initial element must match the selector list.
     for (const NthChildOfSelectorInfo& nthChildOfSelectorInfo : fragment.nthChildOfFilters)
@@ -3624,29 +3666,41 @@ void SelectorCodeGenerator::generateElementIsNthChildOf(Assembler::JumpList& fai
     }
 }
 
-void SelectorCodeGenerator::generateElementIsNthLastChild(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
+void SelectorCodeGenerator::generateNthLastChildParentCheckAndRelationUpdate(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
 {
-    Vector<std::pair<int, int>, 32> validSubsetFilters;
-    validSubsetFilters.reserveInitialCapacity(fragment.nthLastChildFilters.size());
-    { // :nth-last-child() must have a parent to match. If there is a parent, do the invalidation marking.
-        LocalRegister parentElement(m_registerAllocator);
-        generateWalkToParentElement(failureCases, parentElement);
+    LocalRegister parentNode(m_registerAllocator);
+    generateWalkToParentNode(parentNode);
+    failureCases.append(m_assembler.branchTestPtr(Assembler::Zero, parentNode));
+    Assembler::Jump notElement = DOMJIT::branchTestIsElementFlagOnNode(m_assembler, Assembler::Zero, parentNode);
 
-        auto relation = fragmentMatchesRightmostOrAdjacentElement(fragment)
-            ? Style::Relation::ChildrenAffectedByBackwardPositionalRules
-            : Style::Relation::DescendantsAffectedByBackwardPositionalRules;
-        generateAddStyleRelationIfResolvingStyle(parentElement, relation);
+    generateWalkToParentElement(failureCases, parentNode);
+    auto relation = fragmentMatchesRightmostOrAdjacentElement(fragment)
+        ? Style::Relation::ChildrenAffectedByBackwardPositionalRules
+        : Style::Relation::DescendantsAffectedByBackwardPositionalRules;
+    generateAddStyleRelationIfResolvingStyle(parentNode, relation);
+    failureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentNode, Node::nodeFlagsMemoryOffset()),
+        Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
+    Assembler::Jump parentNodeCheckEnd = m_assembler.jump();
 
-        failureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentElement, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
+    notElement.link(&m_assembler);
+    failureCases.append(DOMJIT::branchTestIsShadowRootFlagOnNode(m_assembler, Assembler::Zero, parentNode));
 
-        for (const auto& slot : fragment.nthLastChildFilters) {
-            if (nthFilterIsAlwaysSatisified(slot.first, slot.second))
-                continue;
-            validSubsetFilters.uncheckedAppend(slot);
-        }
-        if (validSubsetFilters.isEmpty())
-            return;
+    parentNodeCheckEnd.link(&m_assembler);
+}
+
+void SelectorCodeGenerator::generateElementIsNthLastChild(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
+{
+    generateNthLastChildParentCheckAndRelationUpdate(failureCases, fragment);
+
+    Vector<std::pair<int, int>, 32> validSubsetFilters;
+    validSubsetFilters.reserveInitialCapacity(fragment.nthLastChildFilters.size());
+    for (const auto& slot : fragment.nthLastChildFilters) {
+        if (nthFilterIsAlwaysSatisified(slot.first, slot.second))
+            continue;
+        validSubsetFilters.uncheckedAppend(slot);
     }
+    if (validSubsetFilters.isEmpty())
+        return;
 
     LocalRegister elementCounter(m_registerAllocator);
     { // Loop over the following sibling elements and increment the counter.
@@ -3672,28 +3726,22 @@ void SelectorCodeGenerator::generateElementIsNthLastChild(Assembler::JumpList& f
 
 void SelectorCodeGenerator::generateElementIsNthLastChildOf(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
 {
+    generateNthLastChildParentCheckAndRelationUpdate(failureCases, fragment);
+
     Vector<const NthChildOfSelectorInfo*> validSubsetFilters;
     validSubsetFilters.reserveInitialCapacity(fragment.nthLastChildOfFilters.size());
-    {
-        LocalRegister parentElement(m_registerAllocator);
-        generateWalkToParentElement(failureCases, parentElement);
-
-        generateAddStyleRelationIfResolvingStyle(parentElement, Style::Relation::ChildrenAffectedByPropertyBasedBackwardPositionalRules);
-
-        failureCases.append(m_assembler.branchTest32(Assembler::Zero, Assembler::Address(parentElement, Node::nodeFlagsMemoryOffset()), Assembler::TrustedImm32(Node::flagIsParsingChildrenFinished())));
 
-        // The initial element must match the selector list.
-        for (const NthChildOfSelectorInfo& nthLastChildOfSelectorInfo : fragment.nthLastChildOfFilters)
-            generateElementMatchesSelectorList(failureCases, elementAddressRegister, nthLastChildOfSelectorInfo.selectorList);
-
-        for (const NthChildOfSelectorInfo& nthLastChildOfSelectorInfo : fragment.nthLastChildOfFilters) {
-            if (nthFilterIsAlwaysSatisified(nthLastChildOfSelectorInfo.a, nthLastChildOfSelectorInfo.b))
-                continue;
-            validSubsetFilters.uncheckedAppend(&nthLastChildOfSelectorInfo);
-        }
-        if (validSubsetFilters.isEmpty())
-            return;
+    // The initial element must match the selector list.
+    for (const NthChildOfSelectorInfo& nthLastChildOfSelectorInfo : fragment.nthLastChildOfFilters)
+        generateElementMatchesSelectorList(failureCases, elementAddressRegister, nthLastChildOfSelectorInfo.selectorList);
+    
+    for (const NthChildOfSelectorInfo& nthLastChildOfSelectorInfo : fragment.nthLastChildOfFilters) {
+        if (nthFilterIsAlwaysSatisified(nthLastChildOfSelectorInfo.a, nthLastChildOfSelectorInfo.b))
+            continue;
+        validSubsetFilters.uncheckedAppend(&nthLastChildOfSelectorInfo);
     }
+    if (validSubsetFilters.isEmpty())
+        return;
 
     for (const NthChildOfSelectorInfo* nthLastChildOfSelectorInfo : validSubsetFilters) {
         // Setup the counter at 1.
index ff49cf5..b0af8fe 100644 (file)
@@ -106,6 +106,30 @@ void ShadowRoot::removedFromAncestor(RemovalType removalType, ContainerNode& old
         document().didRemoveInDocumentShadowRoot(*this);
 }
 
+void ShadowRoot::childrenChanged(const ChildChange& childChange)
+{
+    DocumentFragment::childrenChanged(childChange);
+
+    if (!m_host || m_type == ShadowRootMode::UserAgent)
+        return; // Don't support first-child, nth-of-type, etc... in UA shadow roots as an optimization.
+
+    // FIXME: Avoid always invalidating style just for first-child, etc... as done in Element::childrenChanged.
+    switch (childChange.type) {
+    case ElementInserted:
+    case ElementRemoved:
+        m_host->invalidateStyleForSubtreeInternal();
+        break;
+    case TextInserted:
+    case TextRemoved:
+    case TextChanged:
+    case AllChildrenRemoved:
+    case NonContentsChildRemoved:
+    case NonContentsChildInserted:
+    case AllChildrenReplaced:
+        break;
+    }
+}
+
 void ShadowRoot::moveShadowRootToNewParentScope(TreeScope& newScope, Document& newDocument)
 {
     setParentTreeScope(newScope);
index 6b9cc9f..5bd2372 100644 (file)
@@ -103,6 +103,8 @@ private:
     Node::InsertedIntoAncestorResult insertedIntoAncestor(InsertionType, ContainerNode&) override;
     void removedFromAncestor(RemovalType, ContainerNode& insertionPoint) override;
 
+    void childrenChanged(const ChildChange&) override;
+
     bool m_resetStyleInheritance { false };
     bool m_hasBegunDeletingDetachedChildren { false };
     ShadowRootMode m_type { ShadowRootMode::UserAgent };
index 6e8c239..b1c22de 100644 (file)
@@ -179,6 +179,17 @@ inline CCallHelpers::Jump branchTestIsElementFlagOnNode(MacroAssembler& jit, CCa
     return jit.branchTest32(condition, CCallHelpers::Address(nodeAddress, Node::nodeFlagsMemoryOffset()), CCallHelpers::TrustedImm32(Node::flagIsElement()));
 }
 
+inline CCallHelpers::Jump branchTestIsShadowRootFlagOnNode(MacroAssembler& jit, CCallHelpers::ResultCondition condition, GPRReg nodeAddress)
+{
+    return jit.branchTest32(condition, CCallHelpers::Address(nodeAddress, Node::nodeFlagsMemoryOffset()), CCallHelpers::TrustedImm32(Node::flagIsShadowRoot()));
+}
+
+inline CCallHelpers::Jump branchTestIsElementOrShadowRootFlagOnNode(MacroAssembler& jit, CCallHelpers::ResultCondition condition, GPRReg nodeAddress)
+{
+    return jit.branchTest32(condition, CCallHelpers::Address(nodeAddress, Node::nodeFlagsMemoryOffset()),
+        CCallHelpers::TrustedImm32(Node::flagIsShadowRoot() | Node::flagIsElement()));
+}
+
 inline CCallHelpers::Jump branchTestIsHTMLFlagOnNode(MacroAssembler& jit, CCallHelpers::ResultCondition condition, GPRReg nodeAddress)
 {
     return jit.branchTest32(condition, CCallHelpers::Address(nodeAddress, Node::nodeFlagsMemoryOffset()), CCallHelpers::TrustedImm32(Node::flagIsHTML()));