| 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 <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/ElementTraversal.h" |
| 12 #include "core/dom/StaticNodeList.h" | 12 #include "core/dom/StaticNodeList.h" |
| 13 #include "core/dom/shadow/ElementShadow.h" | 13 #include "core/dom/shadow/ElementShadow.h" |
| 14 #include "core/html/HTMLDocument.h" | 14 #include "core/html/HTMLDocument.h" |
| 15 #include "core/html/HTMLHtmlElement.h" | 15 #include "core/html/HTMLHtmlElement.h" |
| 16 #include "testing/gtest/include/gtest/gtest.h" | 16 #include "testing/gtest/include/gtest/gtest.h" |
| 17 | 17 |
| 18 // Uncomment to run the SelectorQueryTests for stats in a release build. |
| 19 // #define RELEASE_QUERY_STATS |
| 20 |
| 18 namespace blink { | 21 namespace blink { |
| 19 | 22 |
| 20 namespace { | 23 namespace { |
| 21 struct QueryTest { | 24 struct QueryTest { |
| 22 const char* selector; | 25 const char* selector; |
| 23 bool query_all; | 26 bool query_all; |
| 24 unsigned matches; | 27 unsigned matches; |
| 25 // {totalCount, fastId, fastClass, fastTagName, fastScan, slowScan, | 28 // {totalCount, fastId, fastClass, fastTagName, fastScan, slowScan, |
| 26 // slowTraversingShadowTreeScan} | 29 // slowTraversingShadowTreeScan} |
| 27 SelectorQuery::QueryStats stats; | 30 SelectorQuery::QueryStats stats; |
| 28 }; | 31 }; |
| 29 | 32 |
| 30 template <unsigned length> | 33 template <unsigned length> |
| 31 void RunTests(ContainerNode& scope, const QueryTest (&test_cases)[length]) { | 34 void RunTests(ContainerNode& scope, const QueryTest (&test_cases)[length]) { |
| 32 for (const auto& test_case : test_cases) { | 35 for (const auto& test_case : test_cases) { |
| 33 const char* selector = test_case.selector; | 36 const char* selector = test_case.selector; |
| 34 SCOPED_TRACE(testing::Message() | 37 SCOPED_TRACE(testing::Message() |
| 35 << (test_case.query_all ? "querySelectorAll('" | 38 << (test_case.query_all ? "querySelectorAll('" |
| 36 : "querySelector('") | 39 : "querySelector('") |
| 37 << selector << "')"); | 40 << selector << "')"); |
| 38 if (test_case.query_all) { | 41 if (test_case.query_all) { |
| 39 StaticElementList* match_all = scope.QuerySelectorAll(selector); | 42 StaticElementList* match_all = scope.QuerySelectorAll(selector); |
| 40 EXPECT_EQ(test_case.matches, match_all->length()); | 43 EXPECT_EQ(test_case.matches, match_all->length()); |
| 41 } else { | 44 } else { |
| 42 Element* match = scope.QuerySelector(selector); | 45 Element* match = scope.QuerySelector(selector); |
| 43 EXPECT_EQ(test_case.matches, match ? 1u : 0u); | 46 EXPECT_EQ(test_case.matches, match ? 1u : 0u); |
| 44 } | 47 } |
| 45 #if DCHECK_IS_ON() | 48 #if DCHECK_IS_ON() || defined(RELEASE_QUERY_STATS) |
| 46 SelectorQuery::QueryStats stats = SelectorQuery::LastQueryStats(); | 49 SelectorQuery::QueryStats stats = SelectorQuery::LastQueryStats(); |
| 47 EXPECT_EQ(test_case.stats.total_count, stats.total_count); | 50 EXPECT_EQ(test_case.stats.total_count, stats.total_count); |
| 48 EXPECT_EQ(test_case.stats.fast_id, stats.fast_id); | 51 EXPECT_EQ(test_case.stats.fast_id, stats.fast_id); |
| 49 EXPECT_EQ(test_case.stats.fast_class, stats.fast_class); | 52 EXPECT_EQ(test_case.stats.fast_class, stats.fast_class); |
| 50 EXPECT_EQ(test_case.stats.fast_tag_name, stats.fast_tag_name); | 53 EXPECT_EQ(test_case.stats.fast_tag_name, stats.fast_tag_name); |
| 51 EXPECT_EQ(test_case.stats.fast_scan, stats.fast_scan); | 54 EXPECT_EQ(test_case.stats.fast_scan, stats.fast_scan); |
| 52 EXPECT_EQ(test_case.stats.slow_scan, stats.slow_scan); | 55 EXPECT_EQ(test_case.stats.slow_scan, stats.slow_scan); |
| 53 EXPECT_EQ(test_case.stats.slow_traversing_shadow_tree_scan, | 56 EXPECT_EQ(test_case.stats.slow_traversing_shadow_tree_scan, |
| 54 stats.slow_traversing_shadow_tree_scan); | 57 stats.slow_traversing_shadow_tree_scan); |
| 55 #endif | 58 #endif |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 291 "<html>" | 294 "<html>" |
| 292 " <head></head>" | 295 " <head></head>" |
| 293 " <body>" | 296 " <body>" |
| 294 " <span id=first>" | 297 " <span id=first>" |
| 295 " <span id=One class=Two></span>" | 298 " <span id=One class=Two></span>" |
| 296 " <span id=one class=tWo></span>" | 299 " <span id=one class=tWo></span>" |
| 297 " </span>" | 300 " </span>" |
| 298 " </body>" | 301 " </body>" |
| 299 "</html>"); | 302 "</html>"); |
| 300 static const struct QueryTest kTestCases[] = { | 303 static const struct QueryTest kTestCases[] = { |
| 301 // Quirks mode always uses the slow path. | 304 // Quirks mode can't use the id fast path due to being case-insensitive. |
| 302 {"#one", false, 1, {5, 0, 0, 0, 0, 5, 0}}, | 305 {"#one", false, 1, {5, 0, 0, 0, 5, 0, 0}}, |
| 303 {"#One", false, 1, {5, 0, 0, 0, 0, 5, 0}}, | 306 {"#One", false, 1, {5, 0, 0, 0, 5, 0, 0}}, |
| 304 {"#ONE", false, 1, {5, 0, 0, 0, 0, 5, 0}}, | 307 {"#ONE", false, 1, {5, 0, 0, 0, 5, 0, 0}}, |
| 305 {"#ONE", true, 2, {6, 0, 0, 0, 0, 6, 0}}, | 308 {"#ONE", true, 2, {6, 0, 0, 0, 6, 0, 0}}, |
| 306 {"[id=One]", false, 1, {5, 0, 0, 0, 0, 5, 0}}, | 309 {"[id=One]", false, 1, {5, 0, 0, 0, 5, 0, 0}}, |
| 307 {"[id=One]", true, 1, {6, 0, 0, 0, 0, 6, 0}}, | 310 {"[id=One]", true, 1, {6, 0, 0, 0, 6, 0, 0}}, |
| 308 {"span", false, 1, {4, 0, 0, 0, 0, 4, 0}}, | 311 {"body #first", false, 1, {4, 0, 0, 0, 4, 0, 0}}, |
| 309 {"span", true, 3, {6, 0, 0, 0, 0, 6, 0}}, | 312 {"body #one", true, 2, {6, 0, 0, 0, 6, 0, 0}}, |
| 310 {".two", false, 1, {5, 0, 0, 0, 0, 5, 0}}, | 313 // Quirks can use the class and tag name fast paths though. |
| 311 {".two", true, 2, {6, 0, 0, 0, 0, 6, 0}}, | 314 {"span", false, 1, {4, 0, 0, 4, 0, 0, 0}}, |
| 312 {"body #first", false, 1, {4, 0, 0, 0, 0, 4, 0}}, | 315 {"span", true, 3, {6, 0, 0, 6, 0, 0, 0}}, |
| 313 {"body #one", true, 2, {6, 0, 0, 0, 0, 6, 0}}, | 316 {".two", false, 1, {5, 0, 5, 0, 0, 0, 0}}, |
| 317 {".two", true, 2, {6, 0, 6, 0, 0, 0, 0}}, |
| 318 {"body span", false, 1, {4, 0, 0, 0, 4, 0, 0}}, |
| 319 {"body span", true, 3, {6, 0, 0, 0, 6, 0, 0}}, |
| 320 {"body .two", false, 1, {5, 0, 5, 0, 0, 0, 0}}, |
| 321 {"body .two", true, 2, {6, 0, 6, 0, 0, 0, 0}}, |
| 314 }; | 322 }; |
| 315 RunTests(*document, kTestCases); | 323 RunTests(*document, kTestCases); |
| 316 } | 324 } |
| 317 | 325 |
| 318 TEST(SelectorQueryTest, DisconnectedSubtreeSlowPath) { | 326 TEST(SelectorQueryTest, DisconnectedSubtree) { |
| 319 Document* document = HTMLDocument::Create(); | 327 Document* document = HTMLDocument::Create(); |
| 320 Element* scope = document->createElement("div"); | 328 Element* scope = document->createElement("div"); |
| 321 scope->setInnerHTML( | 329 scope->setInnerHTML( |
| 322 "<section>" | 330 "<section>" |
| 323 " <span id=first>" | 331 " <span id=first>" |
| 324 " <span id=A class=A></span>" | 332 " <span id=A class=A></span>" |
| 325 " <span id=B class=child></span>" | 333 " <span id=B class=child></span>" |
| 326 " <span id=multiple class=child></span>" | 334 " <span id=multiple class=child></span>" |
| 327 " <span id=multiple class=B></span>" | 335 " <span id=multiple class=B></span>" |
| 328 " </span>" | 336 " </span>" |
| 329 "</section>"); | 337 "</section>"); |
| 330 ShadowRoot& shadowRoot = | |
| 331 scope->EnsureShadow().AddShadowRoot(*scope, ShadowRootType::kOpen); | |
| 332 // Make the inside the ShadowRoot look identical to the outer document. | |
| 333 shadowRoot.appendChild( | |
| 334 ElementTraversal::FirstChild(*scope)->CloneElementWithChildren()); | |
| 335 static const struct QueryTest kTestCases[] = { | 338 static const struct QueryTest kTestCases[] = { |
| 336 // TODO(esprehn): Disconnected subtrees always uses the slow path, but | 339 {"#A", false, 1, {3, 0, 0, 0, 3, 0, 0}}, |
| 337 // we can actually use it in a number of cases, for example using the id | 340 {"#B", false, 1, {4, 0, 0, 0, 4, 0, 0}}, |
| 338 // map for things inside a tree scope, or using the fast class scanning | 341 {"#B", true, 1, {6, 0, 0, 0, 6, 0, 0}}, |
| 339 // always. | 342 {"#multiple", true, 2, {6, 0, 0, 0, 6, 0, 0}}, |
| 340 {"#A", false, 1, {3, 0, 0, 0, 0, 3, 0}}, | 343 {".child", false, 1, {4, 0, 4, 0, 0, 0, 0}}, |
| 341 {"#B", false, 1, {4, 0, 0, 0, 0, 4, 0}}, | 344 {".child", true, 2, {6, 0, 6, 0, 0, 0, 0}}, |
| 342 {"#B", true, 1, {6, 0, 0, 0, 0, 6, 0}}, | 345 {"#first span", false, 1, {3, 0, 0, 0, 3, 0, 0}}, |
| 343 {"#multiple", true, 2, {6, 0, 0, 0, 0, 6, 0}}, | 346 {"#first span", true, 4, {6, 0, 0, 0, 6, 0, 0}}, |
| 344 {".child", false, 1, {4, 0, 0, 0, 0, 4, 0}}, | |
| 345 {".child", true, 2, {6, 0, 0, 0, 0, 6, 0}}, | |
| 346 {"#first span", false, 1, {3, 0, 0, 0, 0, 3, 0}}, | |
| 347 {"#first span", true, 4, {6, 0, 0, 0, 0, 6, 0}}, | |
| 348 }; | 347 }; |
| 349 | 348 |
| 350 { | 349 RunTests(*scope, kTestCases); |
| 351 SCOPED_TRACE("Inside disconnected subtree"); | 350 } |
| 352 RunTests(*scope, kTestCases); | |
| 353 } | |
| 354 | 351 |
| 355 { | 352 TEST(SelectorQueryTest, DisconnectedTreeScope) { |
| 356 // Run all the tests a second time but with a scope inside a shadow root, | 353 Document* document = HTMLDocument::Create(); |
| 357 // this tests for cases where we could have used the id map the ShadowRoot | 354 Element* host = document->createElement("div"); |
| 358 // is keeping track of. | 355 // TODO(esprehn): Element::attachShadow() should not require a ScriptState, |
| 359 SCOPED_TRACE("Inside disconnected shadow root subtree"); | 356 // it should handle the use counting in the bindings layer instead of in the |
| 360 RunTests(shadowRoot, kTestCases); | 357 // C++. |
| 361 } | 358 ShadowRoot& shadowRoot = |
| 359 host->EnsureShadow().AddShadowRoot(*host, ShadowRootType::kOpen); |
| 360 shadowRoot.setInnerHTML( |
| 361 "<section>" |
| 362 " <span id=first>" |
| 363 " <span id=A class=A></span>" |
| 364 " <span id=B class=child></span>" |
| 365 " <span id=multiple class=child></span>" |
| 366 " <span id=multiple class=B></span>" |
| 367 " </span>" |
| 368 "</section>"); |
| 369 static const struct QueryTest kTestCases[] = { |
| 370 {"#A", false, 1, {1, 1, 0, 0, 0, 0, 0}}, |
| 371 {"#B", false, 1, {1, 1, 0, 0, 0, 0, 0}}, |
| 372 {"#B", true, 1, {1, 1, 0, 0, 0, 0, 0}}, |
| 373 {"#multiple", true, 2, {2, 2, 0, 0, 0, 0, 0}}, |
| 374 {".child", false, 1, {4, 0, 4, 0, 0, 0, 0}}, |
| 375 {".child", true, 2, {6, 0, 6, 0, 0, 0, 0}}, |
| 376 {"#first span", false, 1, {2, 1, 0, 0, 1, 0, 0}}, |
| 377 {"#first span", true, 4, {5, 1, 0, 0, 4, 0, 0}}, |
| 378 }; |
| 379 |
| 380 RunTests(shadowRoot, kTestCases); |
| 362 } | 381 } |
| 363 | 382 |
| 364 } // namespace blink | 383 } // namespace blink |
| OLD | NEW |