CSS JIT: Implement Pseudo Element
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Jul 2014 06:58:53 +0000 (06:58 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Jul 2014 06:58:53 +0000 (06:58 +0000)
https://bugs.webkit.org/show_bug.cgi?id=134835

Reviewed by Benjamin Poulain.

Implement Pseudo Element handling for CSS JIT SelectorCompiler.
At first, we start with the simple implementation. We handle limited number of pseudo element,
before, after, first-line, first-letter.

Source/WebCore:
Tests: fast/selectors/pseudo-element-inside-any.html
       fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any.html
       fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not.html
       fast/selectors/querySelector-pseudo-element.html

* css/ElementRuleCollector.cpp:
(WebCore::ElementRuleCollector::ruleMatches):
* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::matchRecursively):
* cssjit/SelectorCompiler.cpp:
(WebCore::SelectorCompiler::SelectorFragment::SelectorFragment):
(WebCore::SelectorCompiler::constructFragments):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateSelectorChecker):
(WebCore::SelectorCompiler::SelectorCodeGenerator::loadCheckingContext):
(WebCore::SelectorCompiler::SelectorCodeGenerator::branchOnResolvingModeWithCheckingContext):
(WebCore::SelectorCompiler::SelectorCodeGenerator::branchOnResolvingMode):
(WebCore::SelectorCompiler::SelectorCodeGenerator::jumpIfNotResolvingStyle):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementMatching):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsActive):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsHovered):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementHasPseudoElement):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateRequestedPseudoElementEqualsToSelectorPseudoElement):
(WebCore::SelectorCompiler::SelectorCodeGenerator::generateMarkPseudoStyleForPseudoElement):
* cssjit/SelectorCompiler.h:
* rendering/style/RenderStyle.h:
* rendering/style/RenderStyleConstants.h:

LayoutTests:
* fast/selectors/pseudo-element-inside-any-expected.html: Added.
* fast/selectors/pseudo-element-inside-any.html: Added.
Inside functional pseudo classes such as ":-webkit-any", when pseudo element comes (e.g. ":-webkit-any(::first-letter)"),
it produces a local failure. So if the other selectors are matched against the element, whole ":-webkit-any" succeeds.
For example, a selector ":-webkit-any(::first-letter, p)" matches against `p` elements.
* fast/selectors/querySelector-pseudo-element-expected.txt: Added.
* fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any-expected.txt: Added.
* fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any.html: Added.
* fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not-expected.txt: Added.
* fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not.html: Added.
* fast/selectors/querySelector-pseudo-element.html: Added.

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

16 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/selectors/pseudo-element-inside-any-expected.html [new file with mode: 0644]
LayoutTests/fast/selectors/pseudo-element-inside-any.html [new file with mode: 0644]
LayoutTests/fast/selectors/querySelector-pseudo-element-expected.txt [new file with mode: 0644]
LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any-expected.txt [new file with mode: 0644]
LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any.html [new file with mode: 0644]
LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not-expected.txt [new file with mode: 0644]
LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not.html [new file with mode: 0644]
LayoutTests/fast/selectors/querySelector-pseudo-element.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/css/ElementRuleCollector.cpp
Source/WebCore/css/SelectorChecker.cpp
Source/WebCore/cssjit/SelectorCompiler.cpp
Source/WebCore/cssjit/SelectorCompiler.h
Source/WebCore/rendering/style/RenderStyle.h
Source/WebCore/rendering/style/RenderStyleConstants.h

index c774742..77fb1c2 100644 (file)
@@ -1,3 +1,26 @@
+2014-07-24  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        CSS JIT: Implement Pseudo Element
+        https://bugs.webkit.org/show_bug.cgi?id=134835
+
+        Reviewed by Benjamin Poulain.
+
+        Implement Pseudo Element handling for CSS JIT SelectorCompiler.
+        At first, we start with the simple implementation. We handle limited number of pseudo element,
+        before, after, first-line, first-letter.
+
+        * fast/selectors/pseudo-element-inside-any-expected.html: Added.
+        * fast/selectors/pseudo-element-inside-any.html: Added.
+        Inside functional pseudo classes such as ":-webkit-any", when pseudo element comes (e.g. ":-webkit-any(::first-letter)"),
+        it produces a local failure. So if the other selectors are matched against the element, whole ":-webkit-any" succeeds.
+        For example, a selector ":-webkit-any(::first-letter, p)" matches against `p` elements.
+        * fast/selectors/querySelector-pseudo-element-expected.txt: Added.
+        * fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any-expected.txt: Added.
+        * fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any.html: Added.
+        * fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not-expected.txt: Added.
+        * fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not.html: Added.
+        * fast/selectors/querySelector-pseudo-element.html: Added.
+
 2014-07-24  Radu Stavila  <stavila@adobe.com>
 
         REGRESSION (r169105): Crash in selection
diff --git a/LayoutTests/fast/selectors/pseudo-element-inside-any-expected.html b/LayoutTests/fast/selectors/pseudo-element-inside-any-expected.html
new file mode 100644 (file)
index 0000000..064b64c
--- /dev/null
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+<head>
+</head>
+<body>
+<div>
+    <div id="test1">
+        <div>
+            <p class="target" id="target1">pseudo element inside :any has no effect.</p>
+        </div>
+    </div>
+
+    <div id="test2">
+        <div>
+            <p class="target" id="target2">pseudo element inside :any has no effect.</p>
+        </div>
+    </div>
+
+    <div id="test3">
+        <div>
+            <p class="target" id="target3" style="background-color:lime">pseudo element inside :any has no effect and the other selectors are matched.</p>
+        </div>
+    </div>
+
+    <div id="test4">
+        <div>
+            <p class="target" id="target4" style="background-color:lime">pseudo element inside :any has no effect and the other selectors are matched.</p>
+        </div>
+    </div>
+</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/selectors/pseudo-element-inside-any.html b/LayoutTests/fast/selectors/pseudo-element-inside-any.html
new file mode 100644 (file)
index 0000000..48bf720
--- /dev/null
@@ -0,0 +1,49 @@
+<!doctype html>
+<html>
+<head>
+<style>
+#test1 :-webkit-any(::first-letter) p.target {
+    background-color: lime;
+}
+
+#test2 p:-webkit-any(::first-letter) {
+    background-color: lime;
+}
+
+#test3 p:-webkit-any(p, ::first-letter) {
+    background-color: lime;
+}
+
+#test4 p:-webkit-any(p, :-webkit-any(p, ::first-letter)) {
+    background-color: lime;
+}
+</style>
+</head>
+<body>
+<div>
+    <div id="test1">
+        <div>
+            <p class="target" id="target1">pseudo element inside :any has no effect.</p>
+        </div>
+    </div>
+
+    <div id="test2">
+        <div>
+            <p class="target" id="target2">pseudo element inside :any has no effect.</p>
+        </div>
+    </div>
+
+    <div id="test3">
+        <div>
+            <p class="target" id="target3">pseudo element inside :any has no effect and the other selectors are matched.</p>
+        </div>
+    </div>
+
+    <div id="test4">
+        <div>
+            <p class="target" id="target4">pseudo element inside :any has no effect and the other selectors are matched.</p>
+        </div>
+    </div>
+</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/selectors/querySelector-pseudo-element-expected.txt b/LayoutTests/fast/selectors/querySelector-pseudo-element-expected.txt
new file mode 100644 (file)
index 0000000..1f9711b
--- /dev/null
@@ -0,0 +1,25 @@
+This test makes sure that querySelector with pseudo-element doesn't match anything.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.querySelectorAll("#test p").length is 1
+PASS document.querySelector("#test p") is target
+PASS document.querySelectorAll("#test p.ng").length is 1
+PASS document.querySelector("#test p.ng") is target
+PASS document.querySelectorAll("#test div p").length is 1
+PASS document.querySelector("#test div p") is target
+PASS document.querySelectorAll("#test div p.ng").length is 1
+PASS document.querySelector("#test div p.ng") is target
+PASS document.querySelectorAll("#test p:first-line").length is 0
+PASS document.querySelector("#test p:first-line") is null
+PASS document.querySelectorAll("#test p:first-line.ng").length is 0
+PASS document.querySelector("#test p:first-line.ng") is null
+PASS document.querySelectorAll("#test div:first-line p").length is 0
+PASS document.querySelector("#test div:first-line p") is null
+PASS document.querySelectorAll("#test div:first-line p.ng").length is 0
+PASS document.querySelector("#test div:first-line p.ng") is null
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any-expected.txt b/LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any-expected.txt
new file mode 100644 (file)
index 0000000..f97020b
--- /dev/null
@@ -0,0 +1,25 @@
+This test makes sure that querySelector with pseudo-element inside functional pseudo class :-webkit-any doesn't match anything.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.querySelectorAll("#test :-webkit-any(p)").length is 1
+PASS document.querySelector("#test :-webkit-any(p)") is target
+PASS document.querySelectorAll("#test :-webkit-any(p.ng)").length is 1
+PASS document.querySelector("#test :-webkit-any(p.ng)") is target
+PASS document.querySelectorAll("#test div :-webkit-any(p)").length is 1
+PASS document.querySelector("#test div :-webkit-any(p)") is target
+PASS document.querySelectorAll("#test div :-webkit-any(p.ng)").length is 1
+PASS document.querySelector("#test div :-webkit-any(p.ng)") is target
+PASS document.querySelectorAll("#test :-webkit-any(p:first-line)").length is 0
+PASS document.querySelector("#test :-webkit-any(p:first-line)") is null
+PASS document.querySelectorAll("#test :-webkit-any(p:first-line.ng)").length is 0
+PASS document.querySelector("#test :-webkit-any(p:first-line.ng)") is null
+PASS document.querySelectorAll("#test :-webkit-any(div:first-line) p").length is 0
+PASS document.querySelector("#test :-webkit-any(div:first-line) p") is null
+PASS document.querySelectorAll("#test :-webkit-any(div:first-line) p.ng").length is 0
+PASS document.querySelector("#test :-webkit-any(div:first-line) p.ng") is null
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any.html b/LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any.html
new file mode 100644 (file)
index 0000000..0a7598a
--- /dev/null
@@ -0,0 +1,37 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div style="display:none" id="test">
+<div>
+    <p class="ng" id="target">Cocoa and Cappuccino make us happy.</p>
+</div>
+</div>
+</body>
+<script>
+description('This test makes sure that querySelector with pseudo-element inside functional pseudo class :-webkit-any doesn\'t match anything.');
+var target = document.getElementById('target');
+shouldBe('document.querySelectorAll("#test :-webkit-any(p)").length', '1');
+shouldBe('document.querySelector("#test :-webkit-any(p)")', 'target');
+shouldBe('document.querySelectorAll("#test :-webkit-any(p.ng)").length', '1');
+shouldBe('document.querySelector("#test :-webkit-any(p.ng)")', 'target');
+
+shouldBe('document.querySelectorAll("#test div :-webkit-any(p)").length', '1');
+shouldBe('document.querySelector("#test div :-webkit-any(p)")', 'target');
+shouldBe('document.querySelectorAll("#test div :-webkit-any(p.ng)").length', '1');
+shouldBe('document.querySelector("#test div :-webkit-any(p.ng)")', 'target');
+
+shouldBe('document.querySelectorAll("#test :-webkit-any(p:first-line)").length', '0');
+shouldBeNull('document.querySelector("#test :-webkit-any(p:first-line)")');
+shouldBe('document.querySelectorAll("#test :-webkit-any(p:first-line.ng)").length', '0');
+shouldBeNull('document.querySelector("#test :-webkit-any(p:first-line.ng)")');
+
+shouldBe('document.querySelectorAll("#test :-webkit-any(div:first-line) p").length', '0');
+shouldBeNull('document.querySelector("#test :-webkit-any(div:first-line) p")');
+shouldBe('document.querySelectorAll("#test :-webkit-any(div:first-line) p.ng").length', '0');
+shouldBeNull('document.querySelector("#test :-webkit-any(div:first-line) p.ng")');
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</html>
diff --git a/LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not-expected.txt b/LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not-expected.txt
new file mode 100644 (file)
index 0000000..d694093
--- /dev/null
@@ -0,0 +1,10 @@
+This test makes sure that querySelector with pseudo-element inside functional pseudo class :not is syntax error.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.querySelectorAll("#test div :not(:first-line)") threw exception Error: SyntaxError: DOM Exception 12.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not.html b/LayoutTests/fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not.html
new file mode 100644 (file)
index 0000000..88671b8
--- /dev/null
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div style="display:none" id="test">
+<div>
+    <p class="ng" id="target">Cocoa and Cappuccino make us happy.</p>
+</div>
+</div>
+</body>
+<script>
+description('This test makes sure that querySelector with pseudo-element inside functional pseudo class :not is syntax error.');
+shouldThrow('document.querySelectorAll("#test div :not(:first-line)")');
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</html>
diff --git a/LayoutTests/fast/selectors/querySelector-pseudo-element.html b/LayoutTests/fast/selectors/querySelector-pseudo-element.html
new file mode 100644 (file)
index 0000000..fd1ffa4
--- /dev/null
@@ -0,0 +1,37 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div style="display:none" id="test">
+<div>
+    <p class="ng" id="target">Cocoa and Cappuccino make us happy.</p>
+</div>
+</div>
+</body>
+<script>
+description('This test makes sure that querySelector with pseudo-element doesn\'t match anything.');
+var target = document.getElementById('target');
+shouldBe('document.querySelectorAll("#test p").length', '1');
+shouldBe('document.querySelector("#test p")', 'target');
+shouldBe('document.querySelectorAll("#test p.ng").length', '1');
+shouldBe('document.querySelector("#test p.ng")', 'target');
+
+shouldBe('document.querySelectorAll("#test div p").length', '1');
+shouldBe('document.querySelector("#test div p")', 'target');
+shouldBe('document.querySelectorAll("#test div p.ng").length', '1');
+shouldBe('document.querySelector("#test div p.ng")', 'target');
+
+shouldBe('document.querySelectorAll("#test p:first-line").length', '0');
+shouldBeNull('document.querySelector("#test p:first-line")');
+shouldBe('document.querySelectorAll("#test p:first-line.ng").length', '0');
+shouldBeNull('document.querySelector("#test p:first-line.ng")');
+
+shouldBe('document.querySelectorAll("#test div:first-line p").length', '0');
+shouldBeNull('document.querySelector("#test div:first-line p")');
+shouldBe('document.querySelectorAll("#test div:first-line p.ng").length', '0');
+shouldBeNull('document.querySelector("#test div:first-line p.ng")');
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</html>
index 3c89846..1423836 100644 (file)
@@ -1,3 +1,41 @@
+2014-07-24  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        CSS JIT: Implement Pseudo Element
+        https://bugs.webkit.org/show_bug.cgi?id=134835
+
+        Reviewed by Benjamin Poulain.
+
+        Implement Pseudo Element handling for CSS JIT SelectorCompiler.
+        At first, we start with the simple implementation. We handle limited number of pseudo element,
+        before, after, first-line, first-letter.
+
+        Tests: fast/selectors/pseudo-element-inside-any.html
+               fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-any.html
+               fast/selectors/querySelector-pseudo-element-inside-functional-pseudo-class-not.html
+               fast/selectors/querySelector-pseudo-element.html
+
+        * css/ElementRuleCollector.cpp:
+        (WebCore::ElementRuleCollector::ruleMatches):
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::matchRecursively):
+        * cssjit/SelectorCompiler.cpp:
+        (WebCore::SelectorCompiler::SelectorFragment::SelectorFragment):
+        (WebCore::SelectorCompiler::constructFragments):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateSelectorChecker):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::loadCheckingContext):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::branchOnResolvingModeWithCheckingContext):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::branchOnResolvingMode):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::jumpIfNotResolvingStyle):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementMatching):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsActive):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementIsHovered):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateElementHasPseudoElement):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateRequestedPseudoElementEqualsToSelectorPseudoElement):
+        (WebCore::SelectorCompiler::SelectorCodeGenerator::generateMarkPseudoStyleForPseudoElement):
+        * cssjit/SelectorCompiler.h:
+        * rendering/style/RenderStyle.h:
+        * rendering/style/RenderStyleConstants.h:
+
 2014-07-24  Radu Stavila  <stavila@adobe.com>
 
         REGRESSION (r169105): Crash in selection
index ff35e38..89e4799 100644 (file)
@@ -290,11 +290,10 @@ inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData)
         ruleData.setCompiledSelector(compilationStatus, compiledSelectorCodeRef);
         compiledSelectorChecker = ruleData.compiledSelectorCodeRef().code().executableAddress();
     }
-    if (compiledSelectorChecker) {
-        if (m_pseudoStyleRequest.pseudoId != NOPSEUDO)
-            return false;
 
+    if (compiledSelectorChecker) {
         if (ruleData.compilationStatus() == SelectorCompilationStatus::SimpleSelectorChecker) {
+            ASSERT_WITH_MESSAGE(m_pseudoStyleRequest.pseudoId == NOPSEUDO, "When matching pseudo elements, we should never compile a selector checker without context. ElementRuleCollector::collectMatchingRulesForList() should filter out useless rules for pseudo elements.");
             SelectorCompiler::SimpleSelectorChecker selectorChecker = SelectorCompiler::simpleSelectorCheckerFunction(compiledSelectorChecker, ruleData.compilationStatus());
 #if CSS_SELECTOR_JIT_PROFILING
             ruleData.compiledSelectorUsed();
@@ -303,14 +302,18 @@ inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData)
         }
         ASSERT(ruleData.compilationStatus() == SelectorCompilationStatus::SelectorCheckerWithCheckingContext);
 
-        SelectorCompiler::SelectorCheckerWithCheckingContext selectorChecker = SelectorCompiler::selectorCheckerFunctionWithCheckingContext(compiledSelectorChecker, ruleData.compilationStatus());
-        SelectorCompiler::CheckingContext context;
-        context.elementStyle = m_style;
-        context.resolvingMode = m_mode;
+        // FIXME: Currently a compiled selector doesn't support scrollbar / selection's exceptional case.
+        if (!m_pseudoStyleRequest.scrollbar) {
+            SelectorCompiler::SelectorCheckerWithCheckingContext selectorChecker = SelectorCompiler::selectorCheckerFunctionWithCheckingContext(compiledSelectorChecker, ruleData.compilationStatus());
+            SelectorCompiler::CheckingContext context;
+            context.elementStyle = m_style;
+            context.resolvingMode = m_mode;
+            context.pseudoId = m_pseudoStyleRequest.pseudoId;
 #if CSS_SELECTOR_JIT_PROFILING
-        ruleData.compiledSelectorUsed();
+            ruleData.compiledSelectorUsed();
 #endif
-        return selectorChecker(&m_element, &context);
+            return selectorChecker(&m_element, &context);
+        }
     }
 #endif // ENABLE(CSS_SELECTOR_JIT)
 
index 970a03d..671e840 100644 (file)
@@ -168,6 +168,11 @@ SelectorChecker::Match SelectorChecker::matchRecursively(const SelectorCheckingC
         return SelectorFailsLocally;
 
     if (context.selector->m_match == CSSSelector::PseudoElement) {
+        // In Selectors Level 4, a pseudo element inside a functional pseudo class is undefined (issue 7).
+        // Make it as matching failure until the spec clarifies this case.
+        if (context.inFunctionalPseudoClass)
+            return SelectorFailsCompletely;
+
         if (context.selector->isCustomPseudoElement()) {
             if (ShadowRoot* root = context.element->containingShadowRoot()) {
                 if (context.element->shadowPseudoId() != context.selector->value())
index ba495ef..9635e3f 100644 (file)
@@ -138,6 +138,7 @@ struct SelectorFragment {
         , tagName(nullptr)
         , id(nullptr)
         , langFilter(nullptr)
+        , pseudoElementSelector(nullptr)
         , onlyMatchesLinksInQuirksMode(true)
     {
     }
@@ -166,6 +167,7 @@ struct SelectorFragment {
     Vector<std::pair<int, int>, 32> nthChildFilters;
     Vector<SelectorFragment> notFilters;
     Vector<Vector<SelectorFragment>> anyFilters;
+    const CSSSelector* pseudoElementSelector;
 
     // For quirks mode, follow this: http://quirks.spec.whatwg.org/#the-:active-and-:hover-quirk
     // In quirks mode, a compound selector 'selector' that matches the following conditions must not match elements that would not also match the ':any-link' selector.
@@ -251,15 +253,21 @@ private:
     void generateElementIsNthChild(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateElementMatchesNotPseudoClass(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateElementMatchesAnyPseudoClass(Assembler::JumpList& failureCases, const SelectorFragment&);
+    void generateElementHasPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment&);
     void generateElementIsRoot(Assembler::JumpList& failureCases);
     void generateElementIsTarget(Assembler::JumpList& failureCases);
 
     // Helpers.
     void addFlagsToElementStyleFromContext(Assembler::RegisterID checkingContext, int64_t);
+    Assembler::Jump branchOnResolvingModeWithCheckingContext(Assembler::RelationalCondition, SelectorChecker::Mode, Assembler::RegisterID checkingContext);
+    Assembler::Jump branchOnResolvingMode(Assembler::RelationalCondition, SelectorChecker::Mode, Assembler::RegisterID checkingContext);
+    void generateMarkPseudoStyleForPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment&);
+    void generateRequestedPseudoElementEqualsToSelectorPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment&, Assembler::RegisterID checkingContext);
+    void generateSpecialFailureInQuirksModeForActiveAndHoverIfNeeded(Assembler::JumpList& failureCases, const SelectorFragment&);
     Assembler::JumpList jumpIfNoPreviousAdjacentElement();
     Assembler::JumpList jumpIfNoNextAdjacentElement();
     Assembler::Jump jumpIfNotResolvingStyle(Assembler::RegisterID checkingContextRegister);
-    void generateSpecialFailureInQuirksModeForActiveAndHoverIfNeeded(Assembler::JumpList& failureCases, const SelectorFragment&);
+    void loadCheckingContext(Assembler::RegisterID checkingContext);
     Assembler::Jump modulo(JSC::MacroAssembler::ResultCondition, Assembler::RegisterID inputDividend, int divisor);
     void moduloIsZero(Assembler::JumpList& failureCases, Assembler::RegisterID inputDividend, int divisor);
 
@@ -610,6 +618,16 @@ static FunctionType constructFragments(const CSSSelector* rootSelector, Selector
     FragmentRelation relationToPreviousFragment = FragmentRelation::Rightmost;
     FunctionType functionType = FunctionType::SimpleSelectorChecker;
     for (const CSSSelector* selector = rootSelector; selector; selector = selector->tagHistory()) {
+        CSSSelector::Relation relation = selector->relation();
+
+        // A selector is invalid if something follows a pseudo-element.
+        // We make an exception for scrollbar pseudo elements and allow a set of pseudo classes (but nothing else)
+        // to follow the pseudo elements.
+        // FIXME: Currently, CSS JIT doesn't support scrollbar and selection's exceptional cases.
+        // So all selectors following a pseudo-element is treated as invalid.
+        if (relation == CSSSelector::SubSelector && fragment.pseudoElementSelector)
+            return FunctionType::CannotCompile;
+
         switch (selector->m_match) {
         case CSSSelector::Tag:
             ASSERT(!fragment.tagName);
@@ -643,7 +661,33 @@ static FunctionType constructFragments(const CSSSelector* rootSelector, Selector
                 return functionType;
             break;
         }
+        case CSSSelector::PseudoElement: {
+            fragment.onlyMatchesLinksInQuirksMode = false;
+
+            // In the QuerySelector context, PseudoElement selectors always fail.
+            if (selectorContext == SelectorContext::QuerySelector)
+                return FunctionType::CannotMatchAnything;
+
+            // In Selectors Level 4, a pseudo element inside a functional pseudo class is undefined (issue 7).
+            // Make it as matching failure until the spec clarifies this case.
+            if (fragmentLevel == FragmentsLevel::InFunctionalPseudoType)
+                return FunctionType::CannotMatchAnything;
 
+            switch (selector->pseudoElementType()) {
+            case CSSSelector::PseudoElementAfter:
+            case CSSSelector::PseudoElementBefore:
+            case CSSSelector::PseudoElementFirstLetter:
+            case CSSSelector::PseudoElementFirstLine:
+                fragment.pseudoElementSelector = selector;
+                break;
+            // FIXME: Support SCROLLBAR, RESIZER, SELECTION etc.
+            default:
+                return FunctionType::CannotCompile;
+            }
+
+            functionType = FunctionType::SelectorCheckerWithCheckingContext;
+            break;
+        }
         case CSSSelector::List:
             if (selector->value().contains(' '))
                 return FunctionType::CannotMatchAnything;
@@ -671,12 +715,8 @@ static FunctionType constructFragments(const CSSSelector* rootSelector, Selector
         case CSSSelector::Unknown:
             ASSERT_NOT_REACHED();
             return FunctionType::CannotMatchAnything;
-        case CSSSelector::PseudoElement:
-            fragment.onlyMatchesLinksInQuirksMode = false;
-            return FunctionType::CannotCompile;
         }
 
-        CSSSelector::Relation relation = selector->relation();
         if (relation == CSSSelector::SubSelector)
             continue;
 
@@ -1219,9 +1259,16 @@ void SelectorCodeGenerator::generateSelectorChecker()
 #if CSS_SELECTOR_JIT_DEBUGGING
     dataLogF("Compiling with minimum required register count %u\n", minimumRegisterCountForAttributes);
 #endif
-    
+
+    Assembler::JumpList failureOnFunctionEntry;
+    // Test selector's pseudo element equals to requested PseudoId.
+    if (m_functionType == FunctionType::SelectorCheckerWithCheckingContext) {
+        ASSERT_WITH_MESSAGE(shouldUseRenderStyleFromCheckingContext(m_selectorFragments.first()), "Matching pseudo elements only make sense for the rightmost fragment.");
+        generateRequestedPseudoElementEqualsToSelectorPseudoElement(failureOnFunctionEntry, m_selectorFragments.first(), checkingContextRegister);
+    }
+
     bool needsEpilogue = generatePrologue();
-    
+
     ASSERT(minimumRegisterCountForAttributes <= maximumRegisterCount);
     if (availableRegisterCount < minimumRegisterCountForAttributes) {
         reservedCalleeSavedRegisters = true;
@@ -1268,6 +1315,11 @@ void SelectorCodeGenerator::generateSelectorChecker()
         generateBacktrackingTailsIfNeeded(failureCases, fragment);
     }
 
+    if (m_functionType == FunctionType::SelectorCheckerWithCheckingContext) {
+        ASSERT(!m_selectorFragments.isEmpty());
+        generateMarkPseudoStyleForPseudoElement(failureCases, m_selectorFragments.first());
+    }
+
     m_registerAllocator.deallocateRegister(elementAddressRegister);
 
     if (m_functionType == FunctionType::SimpleSelectorChecker) {
@@ -1278,6 +1330,7 @@ void SelectorCodeGenerator::generateSelectorChecker()
             m_assembler.ret();
 
             // Failure.
+            ASSERT_WITH_MESSAGE(failureOnFunctionEntry.empty(), "Early failure on function entry is used for pseudo element. When early failure is used, function type is SelectorCheckerWithCheckingContext.");
             if (!failureCases.empty()) {
                 failureCases.link(&m_assembler);
                 m_assembler.move(Assembler::TrustedImm32(0), returnRegister);
@@ -1305,6 +1358,13 @@ void SelectorCodeGenerator::generateSelectorChecker()
     if (needsEpilogue)
         generateEpilogue();
     m_assembler.ret();
+
+    // Early failure on function entry case.
+    if (!failureOnFunctionEntry.empty()) {
+        failureOnFunctionEntry.link(&m_assembler);
+        m_assembler.move(Assembler::TrustedImm32(0), returnRegister);
+        m_assembler.ret();
+    }
 }
 
 static inline Assembler::Jump testIsElementFlagOnNode(Assembler::ResultCondition condition, Assembler& assembler, Assembler::RegisterID nodeAddress)
@@ -1493,17 +1553,33 @@ Assembler::JumpList SelectorCodeGenerator::jumpIfNoNextAdjacentElement()
     return successCase;
 }
 
-Assembler::Jump SelectorCodeGenerator::jumpIfNotResolvingStyle(Assembler::RegisterID checkingContext)
+
+void SelectorCodeGenerator::loadCheckingContext(Assembler::RegisterID checkingContext)
 {
     RELEASE_ASSERT(m_selectorContext == SelectorContext::RuleCollector);
 
     // Get the checking context.
     unsigned offsetToCheckingContext = m_stackAllocator.offsetToStackReference(m_checkingContextStackReference);
     m_assembler.loadPtr(Assembler::Address(Assembler::stackPointerRegister, offsetToCheckingContext), checkingContext);
+}
 
-    // If we not resolving style, skip the whole marking.
+Assembler::Jump SelectorCodeGenerator::branchOnResolvingModeWithCheckingContext(Assembler::RelationalCondition condition, SelectorChecker::Mode mode, Assembler::RegisterID checkingContext)
+{
+    // Depend on the specified resolving mode and our current mode, branch.
     static_assert(sizeof(SelectorChecker::Mode) == 1, "We generate a byte load/test for the SelectorChecker::Mode.");
-    return m_assembler.branch8(Assembler::NotEqual, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, resolvingMode)), Assembler::TrustedImm32(static_cast<std::underlying_type<SelectorChecker::Mode>::type>(SelectorChecker::Mode::ResolvingStyle)));
+    return m_assembler.branch8(condition, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, resolvingMode)), Assembler::TrustedImm32(static_cast<std::underlying_type<SelectorChecker::Mode>::type>(mode)));
+
+}
+
+Assembler::Jump SelectorCodeGenerator::branchOnResolvingMode(Assembler::RelationalCondition condition, SelectorChecker::Mode mode, Assembler::RegisterID checkingContext)
+{
+    loadCheckingContext(checkingContext);
+    return branchOnResolvingModeWithCheckingContext(condition, mode, checkingContext);
+}
+
+Assembler::Jump SelectorCodeGenerator::jumpIfNotResolvingStyle(Assembler::RegisterID checkingContext)
+{
+    return branchOnResolvingMode(Assembler::NotEqual, SelectorChecker::Mode::ResolvingStyle, checkingContext);
 }
 
 static void getDocument(Assembler& assembler, Assembler::RegisterID element, Assembler::RegisterID output)
@@ -1816,6 +1892,8 @@ void SelectorCodeGenerator::generateElementMatching(Assembler::JumpList& matchin
         generateElementMatchesAnyPseudoClass(matchingPostTagNameFailureCases, fragment);
     if (fragment.langFilter)
         generateElementIsInLanguage(matchingPostTagNameFailureCases, *fragment.langFilter);
+    if (fragment.pseudoElementSelector)
+        generateElementHasPseudoElement(matchingPostTagNameFailureCases, fragment);
 }
 
 void SelectorCodeGenerator::generateElementDataMatching(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
@@ -2269,9 +2347,8 @@ void SelectorCodeGenerator::generateElementIsActive(Assembler::JumpList& failure
             functionCall.setOneArgument(elementAddressRegister);
             failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero));
         } else {
-            unsigned offsetToCheckingContext = m_stackAllocator.offsetToStackReference(m_checkingContextStackReference);
             Assembler::RegisterID checkingContext = m_registerAllocator.allocateRegisterWithPreference(JSC::GPRInfo::argumentGPR1);
-            m_assembler.loadPtr(Assembler::Address(Assembler::stackPointerRegister, offsetToCheckingContext), checkingContext);
+            loadCheckingContext(checkingContext);
             m_registerAllocator.deallocateRegister(checkingContext);
 
             FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls);
@@ -2365,9 +2442,8 @@ void SelectorCodeGenerator::generateElementIsHovered(Assembler::JumpList& failur
             functionCall.setOneArgument(elementAddressRegister);
             failureCases.append(functionCall.callAndBranchOnBooleanReturnValue(Assembler::Zero));
         } else {
-            unsigned offsetToCheckingContext = m_stackAllocator.offsetToStackReference(m_checkingContextStackReference);
             Assembler::RegisterID checkingContext = m_registerAllocator.allocateRegisterWithPreference(JSC::GPRInfo::argumentGPR1);
-            m_assembler.loadPtr(Assembler::Address(Assembler::stackPointerRegister, offsetToCheckingContext), checkingContext);
+            loadCheckingContext(checkingContext);
             m_registerAllocator.deallocateRegister(checkingContext);
 
             FunctionCall functionCall(m_assembler, m_registerAllocator, m_stackAllocator, m_functionCalls);
@@ -2760,6 +2836,36 @@ void SelectorCodeGenerator::generateElementMatchesAnyPseudoClass(Assembler::Jump
     }
 }
 
+void SelectorCodeGenerator::generateElementHasPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
+{
+    if (!shouldUseRenderStyleFromCheckingContext(fragment)) {
+        LocalRegister checkingContext(m_registerAllocator);
+        loadCheckingContext(checkingContext);
+        failureCases.append(branchOnResolvingModeWithCheckingContext(Assembler::Equal, SelectorChecker::Mode::ResolvingStyle, checkingContext));
+    }
+}
+
+void SelectorCodeGenerator::generateRequestedPseudoElementEqualsToSelectorPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment& fragment, Assembler::RegisterID checkingContext)
+{
+    // Make sure that the requested pseudoId equals to the pseudo element of the rightmost fragment.
+    // If the rightmost fragment doesn't have a pseudo element, the requested pseudoId need to be NOPSEUDO to succeed the matching.
+    // Otherwise, if the requested pseudoId is not NOPSEUDO, the requested pseudoId need to equal to the pseudo element of the rightmost fragment.
+    if (shouldUseRenderStyleFromCheckingContext(fragment)) {
+        if (!fragment.pseudoElementSelector)
+            failureCases.append(m_assembler.branch8(Assembler::NotEqual, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, pseudoId)), Assembler::TrustedImm32(NOPSEUDO)));
+        else {
+            Assembler::Jump skip = m_assembler.branch8(Assembler::Equal, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, pseudoId)), Assembler::TrustedImm32(NOPSEUDO));
+
+            // When mode is StyleInvalidation, the pseudo element of the rightmost fragment is treated as NOPSEUDO.
+            // Since the requested pseudoId is not NOPSEUDO here, it becomes failure.
+            failureCases.append(branchOnResolvingModeWithCheckingContext(Assembler::Equal, SelectorChecker::Mode::StyleInvalidation, checkingContext));
+
+            failureCases.append(m_assembler.branch8(Assembler::NotEqual, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, pseudoId)), Assembler::TrustedImm32(CSSSelector::pseudoId(fragment.pseudoElementSelector->pseudoElementType()))));
+            skip.link(&m_assembler);
+        }
+    }
+}
+
 void SelectorCodeGenerator::generateElementIsRoot(Assembler::JumpList& failureCases)
 {
     LocalRegister document(m_registerAllocator);
@@ -2774,6 +2880,38 @@ void SelectorCodeGenerator::generateElementIsTarget(Assembler::JumpList& failure
     failureCases.append(m_assembler.branchPtr(Assembler::NotEqual, Assembler::Address(document, Document::cssTargetMemoryOffset()), elementAddressRegister));
 }
 
+void SelectorCodeGenerator::generateMarkPseudoStyleForPseudoElement(Assembler::JumpList& failureCases, const SelectorFragment& fragment)
+{
+    LocalRegister checkingContext(m_registerAllocator);
+    loadCheckingContext(checkingContext);
+
+    // When fragment doesn't have a pseudo element, there's no need to mark the pseudo element style.
+    if (!fragment.pseudoElementSelector)
+        return;
+
+    Assembler::JumpList successCases;
+
+    // When the requested pseudoId isn't NOPSEUDO, there's no need to mark the pseudo element style.
+    successCases.append(m_assembler.branch8(Assembler::NotEqual, Assembler::Address(checkingContext, OBJECT_OFFSETOF(CheckingContext, pseudoId)), Assembler::TrustedImm32(NOPSEUDO)));
+
+    // When resolving mode is SharingRules or StyleInvalidation, there's no need to mark the pseudo element style.
+    successCases.append(branchOnResolvingModeWithCheckingContext(Assembler::Equal, SelectorChecker::Mode::SharingRules, checkingContext));
+    successCases.append(branchOnResolvingModeWithCheckingContext(Assembler::Equal, SelectorChecker::Mode::StyleInvalidation, checkingContext));
+
+    // When resolving mode is ResolvingStyle, mark the pseudo style for pseudo element.
+    PseudoId dynamicPseudo = CSSSelector::pseudoId(fragment.pseudoElementSelector->pseudoElementType());
+    if (dynamicPseudo < FIRST_INTERNAL_PSEUDOID) {
+        failureCases.append(branchOnResolvingModeWithCheckingContext(Assembler::NotEqual, SelectorChecker::Mode::ResolvingStyle, checkingContext));
+        addFlagsToElementStyleFromContext(checkingContext, RenderStyle::NonInheritedFlags::flagPseudoStyle(dynamicPseudo));
+    }
+
+    // When resolving mode is not SharingRules or StyleInvalidation (In this case, ResolvingStyle or CollectingRules),
+    // the checker including pseudo elements needs to fail for the matching request.
+    failureCases.append(m_assembler.jump());
+
+    successCases.link(&m_assembler);
+}
+
 }; // namespace SelectorCompiler.
 }; // namespace WebCore.
 
index c9df585..4be800a 100644 (file)
@@ -72,6 +72,7 @@ namespace SelectorCompiler {
 struct CheckingContext {
     SelectorChecker::Mode resolvingMode;
     RenderStyle* elementStyle;
+    PseudoId pseudoId;
 };
 
 enum class SelectorContext {
index ac27f5b..539ff4c 100644 (file)
@@ -285,6 +285,7 @@ public:
         static uint64_t flagIsUnique() { return oneBitMask << isUniqueOffset; }
         static uint64_t flagIsaffectedByActive() { return oneBitMask << affectedByActiveOffset; }
         static uint64_t flagIsaffectedByHover() { return oneBitMask << affectedByHoverOffset; }
+        static uint64_t flagPseudoStyle(PseudoId pseudo) { return oneBitMask << (pseudoBitsOffset - 1 + pseudo); }
         static uint64_t setFirstChildStateFlags() { return flagFirstChildState() | flagIsUnique(); }
         static uint64_t setLastChildStateFlags() { return flagLastChildState() | flagIsUnique(); }
     private:
index 654c567..5aa63b4 100644 (file)
@@ -68,7 +68,7 @@ enum StyleDifferenceContextSensitiveProperty {
 };
 
 // Static pseudo styles. Dynamic ones are produced on the fly.
-enum PseudoId {
+enum PseudoId : unsigned char {
     // The order must be NOP ID, public IDs, and then internal IDs.
     NOPSEUDO, FIRST_LINE, FIRST_LETTER, BEFORE, AFTER, SELECTION, FIRST_LINE_INHERITED, SCROLLBAR,
     // Internal IDs follow: