| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "core/dom/SelectorQuery.h" | 5 #include "core/dom/SelectorQuery.h" |
| 6 | 6 |
| 7 #include <memory> |
| 7 #include "core/css/parser/CSSParser.h" | 8 #include "core/css/parser/CSSParser.h" |
| 8 #include "core/css/parser/CSSParserContext.h" | 9 #include "core/css/parser/CSSParserContext.h" |
| 9 #include "core/dom/Document.h" | 10 #include "core/dom/Document.h" |
| 11 #include "core/dom/StaticNodeList.h" |
| 10 #include "core/html/HTMLDocument.h" | 12 #include "core/html/HTMLDocument.h" |
| 11 #include "core/html/HTMLHtmlElement.h" | 13 #include "core/html/HTMLHtmlElement.h" |
| 12 #include "testing/gtest/include/gtest/gtest.h" | 14 #include "testing/gtest/include/gtest/gtest.h" |
| 13 #include <memory> | |
| 14 | 15 |
| 15 namespace blink { | 16 namespace blink { |
| 16 | 17 |
| 18 namespace { |
| 19 struct QueryTest { |
| 20 const char* selector; |
| 21 bool queryAll; |
| 22 unsigned matches; |
| 23 // {totalCount, fastId, fastClass, fastTagName, fastScan, slowScan, |
| 24 // slowTraversingShadowTreeScan} |
| 25 SelectorQuery::QueryStats stats; |
| 26 }; |
| 27 |
| 28 template <unsigned length> |
| 29 void runTests(Document& document, const QueryTest (&testCases)[length]) { |
| 30 for (const auto& testCase : testCases) { |
| 31 const char* selector = testCase.selector; |
| 32 SCOPED_TRACE(testing::Message() << (testCase.queryAll ? "querySelectorAll('" |
| 33 : "querySelector('") |
| 34 << selector << "')"); |
| 35 if (testCase.queryAll) { |
| 36 StaticElementList* matchAll = document.querySelectorAll(selector); |
| 37 EXPECT_EQ(testCase.matches, matchAll->length()); |
| 38 } else { |
| 39 Element* match = document.querySelector(selector); |
| 40 EXPECT_EQ(testCase.matches, match ? 1u : 0u); |
| 41 } |
| 42 #if DCHECK_IS_ON() |
| 43 SelectorQuery::QueryStats stats = SelectorQuery::lastQueryStats(); |
| 44 EXPECT_EQ(testCase.stats.totalCount, stats.totalCount); |
| 45 EXPECT_EQ(testCase.stats.fastId, stats.fastId); |
| 46 EXPECT_EQ(testCase.stats.fastClass, stats.fastClass); |
| 47 EXPECT_EQ(testCase.stats.fastTagName, stats.fastTagName); |
| 48 EXPECT_EQ(testCase.stats.fastScan, stats.fastScan); |
| 49 EXPECT_EQ(testCase.stats.slowScan, stats.slowScan); |
| 50 EXPECT_EQ(testCase.stats.slowTraversingShadowTreeScan, |
| 51 stats.slowTraversingShadowTreeScan); |
| 52 #endif |
| 53 } |
| 54 } |
| 55 }; |
| 56 |
| 17 TEST(SelectorQueryTest, NotMatchingPseudoElement) { | 57 TEST(SelectorQueryTest, NotMatchingPseudoElement) { |
| 18 Document* document = Document::create(); | 58 Document* document = Document::create(); |
| 19 HTMLHtmlElement* html = HTMLHtmlElement::create(*document); | 59 HTMLHtmlElement* html = HTMLHtmlElement::create(*document); |
| 20 document->appendChild(html); | 60 document->appendChild(html); |
| 21 document->documentElement()->setInnerHTML( | 61 document->documentElement()->setInnerHTML( |
| 22 "<body><style>span::before { content: 'X' }</style><span></span></body>"); | 62 "<body><style>span::before { content: 'X' }</style><span></span></body>"); |
| 23 | 63 |
| 24 CSSSelectorList selectorList = CSSParser::parseSelector( | 64 CSSSelectorList selectorList = CSSParser::parseSelector( |
| 25 CSSParserContext::create(*document, KURL(), ReferrerPolicyDefault, | 65 CSSParserContext::create(*document, KURL(), ReferrerPolicyDefault, |
| 26 emptyString, CSSParserContext::StaticProfile), | 66 emptyString, CSSParserContext::StaticProfile), |
| (...skipping 25 matching lines...) Expand all Loading... |
| 52 CSSParserContext::create(*document, KURL(), ReferrerPolicyDefault, | 92 CSSParserContext::create(*document, KURL(), ReferrerPolicyDefault, |
| 53 emptyString, CSSParserContext::StaticProfile), | 93 emptyString, CSSParserContext::StaticProfile), |
| 54 nullptr, "p:last-of-type"); | 94 nullptr, "p:last-of-type"); |
| 55 std::unique_ptr<SelectorQuery> query = | 95 std::unique_ptr<SelectorQuery> query = |
| 56 SelectorQuery::adopt(std::move(selectorList)); | 96 SelectorQuery::adopt(std::move(selectorList)); |
| 57 Element* elm = query->queryFirst(*document); | 97 Element* elm = query->queryFirst(*document); |
| 58 ASSERT_TRUE(elm); | 98 ASSERT_TRUE(elm); |
| 59 EXPECT_EQ("last", elm->idForStyleResolution()); | 99 EXPECT_EQ("last", elm->idForStyleResolution()); |
| 60 } | 100 } |
| 61 | 101 |
| 102 TEST(SelectorQueryTest, StandardsModeFastPaths) { |
| 103 Document* document = HTMLDocument::create(); |
| 104 document->write( |
| 105 "<!DOCTYPE html>" |
| 106 "<html>" |
| 107 " <head></head>" |
| 108 " <body>" |
| 109 " <span id=first class=A>" |
| 110 " <span id=a class=one></span>" |
| 111 " <span id=b class=two></span>" |
| 112 " <span id=c class=one></span>" |
| 113 " <div id=multiple class=two></div>" |
| 114 " </span>" |
| 115 " <div>" |
| 116 " <span id=second class=B>" |
| 117 " <span id=A class=one></span>" |
| 118 " <span id=B class=two></span>" |
| 119 " <span id=C class=one></span>" |
| 120 " <span id=multiple class=two></span>" |
| 121 " </span>" |
| 122 " </div>" |
| 123 " </body>" |
| 124 "</html>"); |
| 125 static const struct QueryTest kTestCases[] = { |
| 126 // Id in right most selector fast path. |
| 127 {"#A", false, 1, {1, 1, 0, 0, 0, 0, 0}}, |
| 128 {"#multiple", false, 1, {1, 1, 0, 0, 0, 0, 0}}, |
| 129 {"#multiple.two", false, 1, {1, 1, 0, 0, 0, 0, 0}}, |
| 130 {"#multiple", true, 2, {2, 2, 0, 0, 0, 0, 0}}, |
| 131 {"span#multiple", true, 1, {2, 2, 0, 0, 0, 0, 0}}, |
| 132 {"#multiple.two", true, 2, {2, 2, 0, 0, 0, 0, 0}}, |
| 133 {"body #multiple", false, 1, {1, 1, 0, 0, 0, 0, 0}}, |
| 134 {"body span#multiple", false, 1, {2, 2, 0, 0, 0, 0, 0}}, |
| 135 {"body #multiple", true, 2, {2, 2, 0, 0, 0, 0, 0}}, |
| 136 |
| 137 // Single selector tag fast path. |
| 138 {"span", false, 1, {4, 0, 0, 4, 0, 0, 0}}, |
| 139 {"span", true, 9, {14, 0, 0, 14, 0, 0, 0}}, |
| 140 |
| 141 // Single selector class fast path. |
| 142 {".two", false, 1, {6, 0, 6, 0, 0, 0, 0}}, |
| 143 {".two", true, 4, {14, 0, 14, 0, 0, 0, 0}}, |
| 144 |
| 145 // Class in the right most selector fast path. |
| 146 {"body .two", false, 1, {6, 0, 0, 0, 6, 0, 0}}, |
| 147 {"div .two", false, 1, {12, 0, 0, 0, 12, 0, 0}}, |
| 148 |
| 149 // Classes in the right most selector for querySelectorAll use a fast |
| 150 // path. |
| 151 {"body .two", true, 4, {14, 0, 14, 0, 0, 0, 0}}, |
| 152 {"div .two", true, 2, {14, 0, 14, 0, 0, 0, 0}}, |
| 153 |
| 154 // TODO: querySelector disables the class fast path to favor the id, but |
| 155 // this means some selectors always end up doing fastScan. |
| 156 {"#second .two", false, 1, {2, 0, 0, 0, 2, 0, 0}}, |
| 157 |
| 158 // TODO(esprehn): This should have used getElementById instead of doing |
| 159 // a fastClass scan. It could have looked at 4 elements instead. |
| 160 {"#second .two", true, 2, {14, 0, 14, 0, 0, 0, 0}}, |
| 161 |
| 162 // Selectors with no classes or ids use the fast scan. |
| 163 {":scope", false, 1, {1, 0, 0, 0, 1, 0, 0}}, |
| 164 {":scope", true, 1, {14, 0, 0, 0, 14, 0, 0}}, |
| 165 {"foo bar", false, 0, {14, 0, 0, 0, 14, 0, 0}}, |
| 166 |
| 167 // Multiple selectors always uses the slow path. |
| 168 // TODO(esprehn): We could make this fast if we sorted the output, not |
| 169 // sure it's worth it unless we're dealing with ids. |
| 170 {"#a, #b", false, 1, {5, 0, 0, 0, 0, 5, 0}}, |
| 171 {"#a, #b", true, 2, {14, 0, 0, 0, 0, 14, 0}}, |
| 172 |
| 173 // Anything that crosses shadow boundaries is slow path. |
| 174 {"#foo /deep/ .a", false, 0, {14, 0, 0, 0, 0, 0, 14}}, |
| 175 {"#foo::shadow .a", false, 0, {14, 0, 0, 0, 0, 0, 14}}, |
| 176 {"::content .a", false, 0, {14, 0, 0, 0, 0, 14, 0}}, |
| 177 {"#foo /deep/ .a", true, 0, {14, 0, 0, 0, 0, 0, 14}}, |
| 178 {"#foo::shadow .a", true, 0, {14, 0, 0, 0, 0, 0, 14}}, |
| 179 {"::content .a", true, 0, {14, 0, 0, 0, 0, 14, 0}}, |
| 180 }; |
| 181 runTests(*document, kTestCases); |
| 182 } |
| 183 |
| 184 TEST(SelectorQueryTest, QuirksModeSlowPath) { |
| 185 Document* document = HTMLDocument::create(); |
| 186 document->write( |
| 187 "<html>" |
| 188 " <head></head>" |
| 189 " <body>" |
| 190 " <span id=first>" |
| 191 " <span id=One class=Two></span>" |
| 192 " <span id=one class=tWo></span>" |
| 193 " </span>" |
| 194 " </body>" |
| 195 "</html>"); |
| 196 static const struct QueryTest kTestCases[] = { |
| 197 // Quirks mode always uses the slow path. |
| 198 {"#one", false, 1, {5, 0, 0, 0, 0, 5, 0}}, |
| 199 {"#One", false, 1, {5, 0, 0, 0, 0, 5, 0}}, |
| 200 {"#ONE", false, 1, {5, 0, 0, 0, 0, 5, 0}}, |
| 201 {"#ONE", true, 2, {6, 0, 0, 0, 0, 6, 0}}, |
| 202 {"span", false, 1, {4, 0, 0, 0, 0, 4, 0}}, |
| 203 {"span", true, 3, {6, 0, 0, 0, 0, 6, 0}}, |
| 204 {".two", false, 1, {5, 0, 0, 0, 0, 5, 0}}, |
| 205 {".two", true, 2, {6, 0, 0, 0, 0, 6, 0}}, |
| 206 {"body #first", false, 1, {4, 0, 0, 0, 0, 4, 0}}, |
| 207 {"body #one", true, 2, {6, 0, 0, 0, 0, 6, 0}}, |
| 208 }; |
| 209 runTests(*document, kTestCases); |
| 210 } |
| 211 |
| 62 } // namespace blink | 212 } // namespace blink |
| OLD | NEW |