// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "third_party/blink/renderer/core/css/css_selector_watch.h" #include #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/css/style_engine.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/testing/page_test_base.h" namespace blink { class CSSSelectorWatchTest : public PageTestBase { protected: StyleEngine& GetStyleEngine() { return GetDocument().GetStyleEngine(); } static const HashSet AddedSelectors(const CSSSelectorWatch& watch) { return watch.added_selectors_; } static const HashSet RemovedSelectors(const CSSSelectorWatch& watch) { return watch.removed_selectors_; } static void ClearAddedRemoved(CSSSelectorWatch&); }; void CSSSelectorWatchTest::ClearAddedRemoved(CSSSelectorWatch& watch) { watch.added_selectors_.clear(); watch.removed_selectors_.clear(); } TEST_F(CSSSelectorWatchTest, RecalcOnDocumentChange) { GetDocument().body()->setInnerHTML(R"HTML(
)HTML"); CSSSelectorWatch& watch = CSSSelectorWatch::From(GetDocument()); Vector selectors; selectors.push_back(".a"); watch.WatchCSSSelectors(selectors); UpdateAllLifecyclePhasesForTest(); selectors.clear(); selectors.push_back(".b"); selectors.push_back(".c"); selectors.push_back("#nomatch"); watch.WatchCSSSelectors(selectors); UpdateAllLifecyclePhasesForTest(); Element* x = GetDocument().getElementById("x"); Element* y = GetDocument().getElementById("y"); Element* z = GetDocument().getElementById("z"); ASSERT_TRUE(x); ASSERT_TRUE(y); ASSERT_TRUE(z); x->removeAttribute(html_names::kClassAttr); y->removeAttribute(html_names::kClassAttr); z->setAttribute(html_names::kClassAttr, "c"); ClearAddedRemoved(watch); unsigned before_count = GetStyleEngine().StyleForElementCount(); UpdateAllLifecyclePhasesForTest(); unsigned after_count = GetStyleEngine().StyleForElementCount(); EXPECT_EQ(2u, after_count - before_count); EXPECT_EQ(1u, AddedSelectors(watch).size()); EXPECT_TRUE(AddedSelectors(watch).Contains(".c")); EXPECT_EQ(1u, RemovedSelectors(watch).size()); EXPECT_TRUE(RemovedSelectors(watch).Contains(".b")); } class CSSSelectorWatchCQTest : public CSSSelectorWatchTest, private ScopedCSSContainerQueriesForTest { protected: CSSSelectorWatchCQTest() : ScopedCSSContainerQueriesForTest(true) {} }; TEST_F(CSSSelectorWatchCQTest, ContainerQueryDisplayNone) { CSSSelectorWatch& watch = CSSSelectorWatch::From(GetDocument()); GetDocument().body()->setInnerHTML(R"HTML(
)HTML"); Vector selectors; selectors.push_back("#inner"); watch.WatchCSSSelectors(selectors); UpdateAllLifecyclePhasesForTest(); EXPECT_EQ(1u, AddedSelectors(watch).size()); EXPECT_TRUE(AddedSelectors(watch).Contains("#inner")); EXPECT_EQ(0u, RemovedSelectors(watch).size()); // Setting the class 'c' on body will make #inner display:none, but also make // #container a container 'c1' which is flipping the span back to // display:inline. ClearAddedRemoved(watch); GetDocument().body()->setAttribute(html_names::kClassAttr, "c"); UpdateAllLifecyclePhasesForTest(); // Element::UpdateCallbackSelectors() will both remove and add #inner in the // two passes. First without the CQ matching, and then in an interleaved style // and layout pass. The accounting in CSSSelectorWatch::UpdateSelectorMatches // will make sure we up with a zero balance. EXPECT_EQ(0u, AddedSelectors(watch).size()); EXPECT_EQ(0u, RemovedSelectors(watch).size()); } } // namespace blink