Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(716)

Side by Side Diff: third_party/WebKit/Source/core/dom/SelectorQueryTest.cpp

Issue 2803103002: Add lots more tests for SelectorQuery fast paths. (Closed)
Patch Set: refs woops. Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/WebKit/Source/core/dom/SelectorQuery.cpp ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 <memory>
8 #include "core/css/parser/CSSParser.h" 8 #include "core/css/parser/CSSParser.h"
9 #include "core/css/parser/CSSParserContext.h" 9 #include "core/css/parser/CSSParserContext.h"
10 #include "core/dom/Document.h" 10 #include "core/dom/Document.h"
11 #include "core/dom/ElementTraversal.h"
11 #include "core/dom/StaticNodeList.h" 12 #include "core/dom/StaticNodeList.h"
13 #include "core/dom/shadow/ElementShadow.h"
12 #include "core/html/HTMLDocument.h" 14 #include "core/html/HTMLDocument.h"
13 #include "core/html/HTMLHtmlElement.h" 15 #include "core/html/HTMLHtmlElement.h"
14 #include "testing/gtest/include/gtest/gtest.h" 16 #include "testing/gtest/include/gtest/gtest.h"
15 17
16 namespace blink { 18 namespace blink {
17 19
18 namespace { 20 namespace {
19 struct QueryTest { 21 struct QueryTest {
20 const char* selector; 22 const char* selector;
21 bool query_all; 23 bool query_all;
22 unsigned matches; 24 unsigned matches;
23 // {totalCount, fastId, fastClass, fastTagName, fastScan, slowScan, 25 // {totalCount, fastId, fastClass, fastTagName, fastScan, slowScan,
24 // slowTraversingShadowTreeScan} 26 // slowTraversingShadowTreeScan}
25 SelectorQuery::QueryStats stats; 27 SelectorQuery::QueryStats stats;
26 }; 28 };
27 29
28 template <unsigned length> 30 template <unsigned length>
29 void RunTests(Document& document, const QueryTest (&test_cases)[length]) { 31 void RunTests(ContainerNode& scope, const QueryTest (&test_cases)[length]) {
30 for (const auto& test_case : test_cases) { 32 for (const auto& test_case : test_cases) {
31 const char* selector = test_case.selector; 33 const char* selector = test_case.selector;
32 SCOPED_TRACE(testing::Message() 34 SCOPED_TRACE(testing::Message()
33 << (test_case.query_all ? "querySelectorAll('" 35 << (test_case.query_all ? "querySelectorAll('"
34 : "querySelector('") 36 : "querySelector('")
35 << selector << "')"); 37 << selector << "')");
36 if (test_case.query_all) { 38 if (test_case.query_all) {
37 StaticElementList* match_all = document.QuerySelectorAll(selector); 39 StaticElementList* match_all = scope.QuerySelectorAll(selector);
38 EXPECT_EQ(test_case.matches, match_all->length()); 40 EXPECT_EQ(test_case.matches, match_all->length());
39 } else { 41 } else {
40 Element* match = document.QuerySelector(selector); 42 Element* match = scope.QuerySelector(selector);
41 EXPECT_EQ(test_case.matches, match ? 1u : 0u); 43 EXPECT_EQ(test_case.matches, match ? 1u : 0u);
42 } 44 }
43 #if DCHECK_IS_ON() 45 #if DCHECK_IS_ON()
44 SelectorQuery::QueryStats stats = SelectorQuery::LastQueryStats(); 46 SelectorQuery::QueryStats stats = SelectorQuery::LastQueryStats();
45 EXPECT_EQ(test_case.stats.total_count, stats.total_count); 47 EXPECT_EQ(test_case.stats.total_count, stats.total_count);
46 EXPECT_EQ(test_case.stats.fast_id, stats.fast_id); 48 EXPECT_EQ(test_case.stats.fast_id, stats.fast_id);
47 EXPECT_EQ(test_case.stats.fast_class, stats.fast_class); 49 EXPECT_EQ(test_case.stats.fast_class, stats.fast_class);
48 EXPECT_EQ(test_case.stats.fast_tag_name, stats.fast_tag_name); 50 EXPECT_EQ(test_case.stats.fast_tag_name, stats.fast_tag_name);
49 EXPECT_EQ(test_case.stats.fast_scan, stats.fast_scan); 51 EXPECT_EQ(test_case.stats.fast_scan, stats.fast_scan);
50 EXPECT_EQ(test_case.stats.slow_scan, stats.slow_scan); 52 EXPECT_EQ(test_case.stats.slow_scan, stats.slow_scan);
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
156 {"div .two", true, 2, {14, 0, 14, 0, 0, 0, 0}}, 158 {"div .two", true, 2, {14, 0, 14, 0, 0, 0, 0}},
157 159
158 // TODO: querySelector disables the class fast path to favor the id, but 160 // TODO: querySelector disables the class fast path to favor the id, but
159 // this means some selectors always end up doing fastScan. 161 // this means some selectors always end up doing fastScan.
160 {"#second .two", false, 1, {2, 0, 0, 0, 2, 0, 0}}, 162 {"#second .two", false, 1, {2, 0, 0, 0, 2, 0, 0}},
161 163
162 // TODO(esprehn): This should have used getElementById instead of doing 164 // TODO(esprehn): This should have used getElementById instead of doing
163 // a fastClass scan. It could have looked at 4 elements instead. 165 // a fastClass scan. It could have looked at 4 elements instead.
164 {"#second .two", true, 2, {14, 0, 14, 0, 0, 0, 0}}, 166 {"#second .two", true, 2, {14, 0, 14, 0, 0, 0, 0}},
165 167
168 // We combine the class fast path with the fast scan mode when possible.
169 {".B span", false, 1, {11, 0, 0, 0, 11, 0, 0}},
170 {".B span", true, 4, {14, 0, 10, 0, 4, 0, 0}},
171
172 // We expand the scope of id selectors when affected by an adjectent
173 // combinator.
174 {"#c + :last-child", false, 1, {4, 0, 0, 0, 4, 0, 0}},
175 {"#a ~ :last-child", false, 1, {4, 0, 0, 0, 4, 0, 0}},
176 {"#c + div", true, 1, {4, 0, 0, 0, 4, 0, 0}},
177 {"#a ~ span", true, 2, {4, 0, 0, 0, 4, 0, 0}},
178
179 // We only expand the scope for id selectors if they're directly affected
180 // the adjacent combinator.
181 {"#first span + span", false, 1, {2, 0, 0, 0, 2, 0, 0}},
182 {"#first span ~ span", false, 1, {2, 0, 0, 0, 2, 0, 0}},
183 {"#second span + span", true, 3, {4, 0, 0, 0, 4, 0, 0}},
184 {"#second span ~ span", true, 3, {4, 0, 0, 0, 4, 0, 0}},
185
186 // We disable the fast path for class selectors when affected by adjacent
187 // combinator.
188 {".one + :last-child", false, 1, {8, 0, 0, 0, 8, 0, 0}},
189 {".A ~ :last-child", false, 1, {9, 0, 0, 0, 9, 0, 0}},
190 {".A + div", true, 1, {14, 0, 0, 0, 14, 0, 0}},
191 {".one ~ span", true, 5, {14, 0, 0, 0, 14, 0, 0}},
192
193 // We re-enable the fast path for classes once past the selector directly
194 // affected by the adjacent combinator.
195 {".B span + span", true, 3, {14, 0, 10, 0, 4, 0, 0}},
196 {".B span ~ span", true, 3, {14, 0, 10, 0, 4, 0, 0}},
197
166 // Selectors with no classes or ids use the fast scan. 198 // Selectors with no classes or ids use the fast scan.
167 {":scope", false, 1, {1, 0, 0, 0, 1, 0, 0}}, 199 {":scope", false, 1, {1, 0, 0, 0, 1, 0, 0}},
168 {":scope", true, 1, {14, 0, 0, 0, 14, 0, 0}}, 200 {":scope", true, 1, {14, 0, 0, 0, 14, 0, 0}},
169 {"foo bar", false, 0, {14, 0, 0, 0, 14, 0, 0}}, 201 {"foo bar", false, 0, {14, 0, 0, 0, 14, 0, 0}},
170 202
171 // Multiple selectors always uses the slow path. 203 // Multiple selectors always uses the slow path.
172 // TODO(esprehn): We could make this fast if we sorted the output, not 204 // TODO(esprehn): We could make this fast if we sorted the output, not
173 // sure it's worth it unless we're dealing with ids. 205 // sure it's worth it unless we're dealing with ids.
174 {"#a, #b", false, 1, {5, 0, 0, 0, 0, 5, 0}}, 206 {"#a, #b", false, 1, {5, 0, 0, 0, 0, 5, 0}},
175 {"#a, #b", true, 2, {14, 0, 0, 0, 0, 14, 0}}, 207 {"#a, #b", true, 2, {14, 0, 0, 0, 0, 14, 0}},
176 208
177 // Anything that crosses shadow boundaries is slow path. 209 // Anything that crosses shadow boundaries is slow path.
178 {"#foo /deep/ .a", false, 0, {14, 0, 0, 0, 0, 0, 14}}, 210 {"#foo /deep/ .a", false, 0, {14, 0, 0, 0, 0, 0, 14}},
179 {"#foo::shadow .a", false, 0, {14, 0, 0, 0, 0, 0, 14}}, 211 {"#foo::shadow .a", false, 0, {14, 0, 0, 0, 0, 0, 14}},
180 {"::content .a", false, 0, {14, 0, 0, 0, 0, 14, 0}}, 212 {"::content .a", false, 0, {14, 0, 0, 0, 0, 14, 0}},
181 {"#foo /deep/ .a", true, 0, {14, 0, 0, 0, 0, 0, 14}}, 213 {"#foo /deep/ .a", true, 0, {14, 0, 0, 0, 0, 0, 14}},
182 {"#foo::shadow .a", true, 0, {14, 0, 0, 0, 0, 0, 14}}, 214 {"#foo::shadow .a", true, 0, {14, 0, 0, 0, 0, 0, 14}},
183 {"::content .a", true, 0, {14, 0, 0, 0, 0, 14, 0}}, 215 {"::content .a", true, 0, {14, 0, 0, 0, 0, 14, 0}},
184 }; 216 };
185 RunTests(*document, kTestCases); 217 RunTests(*document, kTestCases);
186 } 218 }
187 219
220 TEST(SelectorQueryTest, FastPathScoped) {
221 Document* document = HTMLDocument::Create();
222 document->write(
223 "<!DOCTYPE html>"
224 "<html id=root-id class=root-class>"
225 " <head></head>"
226 " <body>"
227 " <span id=first>"
228 " <span id=A class='a child'></span>"
229 " <span id=B class='a child'>"
230 " <a class=first></a>"
231 " <a class=second></a>"
232 " <a class=third></a>"
233 " </span>"
234 " <span id=multiple class='b child'></span>"
235 " <span id=multiple class='c child'></span>"
236 " </span>"
237 " </body>"
238 "</html>");
239 // TODO(esprehn): Element::attachShadow() should not require a ScriptState,
240 // it should handle the use counting in the bindings layer instead of in the
241 // C++.
242 Element* scope = document->getElementById("first");
243 ASSERT_NE(nullptr, scope);
244 ShadowRoot& shadowRoot =
245 scope->EnsureShadow().AddShadowRoot(*scope, ShadowRootType::kOpen);
246 // Make the inside the shadow root be identical to that of the outer document.
247 shadowRoot.appendChild(
248 document->documentElement()->CloneElementWithChildren());
249 static const struct QueryTest kTestCases[] = {
250 // Id in the right most selector.
251 {"#first", false, 0, {0, 0, 0, 0, 0, 0, 0}},
252
253 {"#B", false, 1, {1, 1, 0, 0, 0, 0, 0}},
254 {"#multiple", false, 1, {1, 1, 0, 0, 0, 0, 0}},
255 {"#multiple.c", false, 1, {2, 2, 0, 0, 0, 0, 0}},
256
257 // Class in the right most selector.
258 {".child", false, 1, {1, 0, 1, 0, 0, 0, 0}},
259 {".child", true, 4, {7, 0, 7, 0, 0, 0, 0}},
260
261 // If an ancestor has the class name we fast scan all the descendants of
262 // the scope.
263 {".root-class span", true, 4, {7, 0, 0, 0, 7, 0, 0}},
264
265 // If an ancestor has the class name in the middle of the selector we fast
266 // scan all the descendants of the scope.
267 {".root-class span:nth-child(2)", false, 1, {2, 0, 0, 0, 2, 0, 0}},
268 {".root-class span:nth-child(2)", true, 1, {7, 0, 0, 0, 7, 0, 0}},
269
270 // If the id is an ancestor we scan all the descendants.
271 {"#root-id span", true, 4, {7, 0, 0, 0, 7, 0, 0}},
272 };
273
274 {
275 SCOPED_TRACE("Inside document");
276 RunTests(*scope, kTestCases);
277 }
278
279 {
280 // Run all the tests a second time but with a scope inside a shadow root,
281 // all the fast paths should behave the same.
282 SCOPED_TRACE("Inside shadow root");
283 scope = shadowRoot.getElementById("first");
284 ASSERT_NE(nullptr, scope);
285 RunTests(*scope, kTestCases);
286 }
287 }
288
188 TEST(SelectorQueryTest, QuirksModeSlowPath) { 289 TEST(SelectorQueryTest, QuirksModeSlowPath) {
189 Document* document = HTMLDocument::Create(); 290 Document* document = HTMLDocument::Create();
190 document->write( 291 document->write(
191 "<html>" 292 "<html>"
192 " <head></head>" 293 " <head></head>"
193 " <body>" 294 " <body>"
194 " <span id=first>" 295 " <span id=first>"
195 " <span id=One class=Two></span>" 296 " <span id=One class=Two></span>"
196 " <span id=one class=tWo></span>" 297 " <span id=one class=tWo></span>"
197 " </span>" 298 " </span>"
198 " </body>" 299 " </body>"
199 "</html>"); 300 "</html>");
200 static const struct QueryTest kTestCases[] = { 301 static const struct QueryTest kTestCases[] = {
201 // Quirks mode always uses the slow path. 302 // Quirks mode always uses the slow path.
202 {"#one", false, 1, {5, 0, 0, 0, 0, 5, 0}}, 303 {"#one", false, 1, {5, 0, 0, 0, 0, 5, 0}},
203 {"#One", false, 1, {5, 0, 0, 0, 0, 5, 0}}, 304 {"#One", false, 1, {5, 0, 0, 0, 0, 5, 0}},
204 {"#ONE", false, 1, {5, 0, 0, 0, 0, 5, 0}}, 305 {"#ONE", false, 1, {5, 0, 0, 0, 0, 5, 0}},
205 {"#ONE", true, 2, {6, 0, 0, 0, 0, 6, 0}}, 306 {"#ONE", true, 2, {6, 0, 0, 0, 0, 6, 0}},
206 {"span", false, 1, {4, 0, 0, 0, 0, 4, 0}}, 307 {"span", false, 1, {4, 0, 0, 0, 0, 4, 0}},
207 {"span", true, 3, {6, 0, 0, 0, 0, 6, 0}}, 308 {"span", true, 3, {6, 0, 0, 0, 0, 6, 0}},
208 {".two", false, 1, {5, 0, 0, 0, 0, 5, 0}}, 309 {".two", false, 1, {5, 0, 0, 0, 0, 5, 0}},
209 {".two", true, 2, {6, 0, 0, 0, 0, 6, 0}}, 310 {".two", true, 2, {6, 0, 0, 0, 0, 6, 0}},
210 {"body #first", false, 1, {4, 0, 0, 0, 0, 4, 0}}, 311 {"body #first", false, 1, {4, 0, 0, 0, 0, 4, 0}},
211 {"body #one", true, 2, {6, 0, 0, 0, 0, 6, 0}}, 312 {"body #one", true, 2, {6, 0, 0, 0, 0, 6, 0}},
212 }; 313 };
213 RunTests(*document, kTestCases); 314 RunTests(*document, kTestCases);
214 } 315 }
215 316
317 TEST(SelectorQueryTest, DisconnectedSubtreeSlowPath) {
318 Document* document = HTMLDocument::Create();
319 Element* scope = document->createElement("div");
320 scope->setInnerHTML(
321 "<section>"
322 " <span id=first>"
323 " <span id=A class=A></span>"
324 " <span id=B class=child></span>"
325 " <span id=multiple class=child></span>"
326 " <span id=multiple class=B></span>"
327 " </span>"
328 "</section>");
329 ShadowRoot& shadowRoot =
330 scope->EnsureShadow().AddShadowRoot(*scope, ShadowRootType::kOpen);
331 // Make the inside the ShadowRoot look identical to the outer document.
332 shadowRoot.appendChild(
333 ElementTraversal::FirstChild(*scope)->CloneElementWithChildren());
334 static const struct QueryTest kTestCases[] = {
335 // TODO(esprehn): Disconnected subtrees always uses the slow path, but
336 // we can actually use it in a number of cases, for example using the id
337 // map for things inside a tree scope, or using the fast class scanning
338 // always.
339 {"#A", false, 1, {3, 0, 0, 0, 0, 3, 0}},
340 {"#B", false, 1, {4, 0, 0, 0, 0, 4, 0}},
341 {"#B", true, 1, {6, 0, 0, 0, 0, 6, 0}},
342 {"#multiple", true, 2, {6, 0, 0, 0, 0, 6, 0}},
343 {".child", false, 1, {4, 0, 0, 0, 0, 4, 0}},
344 {".child", true, 2, {6, 0, 0, 0, 0, 6, 0}},
345 {"#first span", false, 1, {3, 0, 0, 0, 0, 3, 0}},
346 {"#first span", true, 4, {6, 0, 0, 0, 0, 6, 0}},
347 };
348
349 {
350 SCOPED_TRACE("Inside disconnected subtree");
351 RunTests(*scope, kTestCases);
352 }
353
354 {
355 // Run all the tests a second time but with a scope inside a shadow root,
356 // this tests for cases where we could have used the id map the ShadowRoot
357 // is keeping track of.
358 SCOPED_TRACE("Inside disconnected shadow root subtree");
359 RunTests(shadowRoot, kTestCases);
360 }
361 }
362
216 } // namespace blink 363 } // namespace blink
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/core/dom/SelectorQuery.cpp ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698