1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/css/style_traversal_root.h"
6 
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/blink/renderer/core/dom/document.h"
9 #include "third_party/blink/renderer/core/dom/element.h"
10 #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
11 #include "third_party/blink/renderer/platform/heap/heap.h"
12 #include "third_party/blink/renderer/platform/heap/persistent.h"
13 
14 namespace blink {
15 
16 class StyleTraversalRootTestImpl : public StyleTraversalRoot {
17   STACK_ALLOCATED();
18 
19  public:
20   StyleTraversalRootTestImpl() = default;
MarkDirty(const Node * node)21   void MarkDirty(const Node* node) { dirty_nodes_.insert(node); }
IsSingleRoot() const22   bool IsSingleRoot() const { return root_type_ == RootType::kSingleRoot; }
IsCommonRoot() const23   bool IsCommonRoot() const { return root_type_ == RootType::kCommonRoot; }
24 
25  private:
ParentInternal(const Node & node) const26   virtual ContainerNode* ParentInternal(const Node& node) const {
27     return node.parentNode();
28   }
29 #if DCHECK_IS_ON()
Parent(const Node & node) const30   ContainerNode* Parent(const Node& node) const override {
31     return ParentInternal(node);
32   }
33 #endif  // DCHECK_IS_ON()
IsDirty(const Node & node) const34   bool IsDirty(const Node& node) const final {
35     return dirty_nodes_.Contains(&node);
36   }
RootRemoved(ContainerNode & parent)37   void RootRemoved(ContainerNode& parent) override {
38     Clear();
39   }
40 
41   HeapHashSet<Member<const Node>> dirty_nodes_;
42 };
43 
44 class StyleTraversalRootTest : public testing::Test {
45  protected:
46   enum ElementIndex { kA, kB, kC, kD, kE, kF, kG, kElementCount };
SetUp()47   void SetUp() final {
48     document_ = Document::CreateForTest();
49     elements_ = MakeGarbageCollected<HeapVector<Member<Element>, 7>>();
50     for (size_t i = 0; i < kElementCount; i++) {
51       elements_->push_back(GetDocument().CreateRawElement(html_names::kDivTag));
52     }
53     GetDocument().appendChild(DivElement(kA));
54     DivElement(kA)->appendChild(DivElement(kB));
55     DivElement(kA)->appendChild(DivElement(kC));
56     DivElement(kB)->appendChild(DivElement(kD));
57     DivElement(kB)->appendChild(DivElement(kE));
58     DivElement(kC)->appendChild(DivElement(kF));
59     DivElement(kC)->appendChild(DivElement(kG));
60 
61     // Tree Looks like this:
62     // div#a
63     // |-- div#b
64     // |   |-- div#d
65     // |   `-- div#e
66     // `-- div#c
67     //     |-- div#f
68     //     `-- div#g
69   }
GetDocument()70   Document& GetDocument() { return *document_; }
DivElement(ElementIndex index)71   Element* DivElement(ElementIndex index) { return elements_->at(index); }
72 
73  private:
74   Persistent<Document> document_;
75   Persistent<HeapVector<Member<Element>, 7>> elements_;
76 };
77 
TEST_F(StyleTraversalRootTest,Update_SingleRoot)78 TEST_F(StyleTraversalRootTest, Update_SingleRoot) {
79   StyleTraversalRootTestImpl root;
80   root.MarkDirty(DivElement(kA));
81 
82   // A single dirty node becomes a single root.
83   root.Update(nullptr, DivElement(kA));
84   EXPECT_EQ(DivElement(kA), root.GetRootNode());
85   EXPECT_TRUE(root.IsSingleRoot());
86 }
87 
TEST_F(StyleTraversalRootTest,Update_CommonRoot)88 TEST_F(StyleTraversalRootTest, Update_CommonRoot) {
89   StyleTraversalRootTestImpl root;
90   root.MarkDirty(DivElement(kC));
91 
92   // Initially make B a single root.
93   root.Update(nullptr, DivElement(kB));
94   EXPECT_EQ(DivElement(kB), root.GetRootNode());
95   EXPECT_TRUE(root.IsSingleRoot());
96 
97   // Adding C makes A a common root.
98   root.MarkDirty(DivElement(kB));
99   root.Update(DivElement(kA), DivElement(kC));
100   EXPECT_EQ(DivElement(kA), root.GetRootNode());
101   EXPECT_FALSE(root.IsSingleRoot());
102   EXPECT_TRUE(root.IsCommonRoot());
103 }
104 
TEST_F(StyleTraversalRootTest,Update_CommonRootDirtySubtree)105 TEST_F(StyleTraversalRootTest, Update_CommonRootDirtySubtree) {
106   StyleTraversalRootTestImpl root;
107   root.MarkDirty(DivElement(kA));
108   root.Update(nullptr, DivElement(kA));
109 
110   // Marking descendants of a single dirty root makes the single root a common
111   // root as long as the new common ancestor is the current root.
112   root.MarkDirty(DivElement(kD));
113   root.Update(DivElement(kA), DivElement(kD));
114   EXPECT_EQ(DivElement(kA), root.GetRootNode());
115   EXPECT_FALSE(root.IsSingleRoot());
116   EXPECT_TRUE(root.IsCommonRoot());
117 }
118 
TEST_F(StyleTraversalRootTest,Update_CommonRootDocumentFallback)119 TEST_F(StyleTraversalRootTest, Update_CommonRootDocumentFallback) {
120   StyleTraversalRootTestImpl root;
121 
122   // Initially make B a common root for D and E.
123   root.MarkDirty(DivElement(kD));
124   root.Update(nullptr, DivElement(kD));
125   root.MarkDirty(DivElement(kE));
126   root.Update(DivElement(kB), DivElement(kE));
127   EXPECT_EQ(DivElement(kB), root.GetRootNode());
128   EXPECT_FALSE(root.IsSingleRoot());
129   EXPECT_TRUE(root.IsCommonRoot());
130 
131   // Adding C falls back to using the document as the root because we don't know
132   // if A is above or below the current common root B.
133   root.MarkDirty(DivElement(kC));
134   root.Update(DivElement(kA), DivElement(kC));
135   EXPECT_EQ(&GetDocument(), root.GetRootNode());
136   EXPECT_FALSE(root.IsSingleRoot());
137   EXPECT_TRUE(root.IsCommonRoot());
138 }
139 
TEST_F(StyleTraversalRootTest,ChildrenRemoved)140 TEST_F(StyleTraversalRootTest, ChildrenRemoved) {
141   StyleTraversalRootTestImpl root;
142   // Initially make E a single root.
143   root.MarkDirty(DivElement(kE));
144   root.Update(nullptr, DivElement(kE));
145   EXPECT_EQ(DivElement(kE), root.GetRootNode());
146   EXPECT_TRUE(root.IsSingleRoot());
147 
148   // Removing D not affecting E.
149   DivElement(kD)->remove();
150   root.ChildrenRemoved(*DivElement(kB));
151   EXPECT_EQ(DivElement(kE), root.GetRootNode());
152   EXPECT_TRUE(root.IsSingleRoot());
153 
154   // Removing B
155   DivElement(kB)->remove();
156   root.ChildrenRemoved(*DivElement(kA));
157   EXPECT_FALSE(root.GetRootNode());
158   EXPECT_TRUE(root.IsSingleRoot());
159 }
160 
161 class StyleTraversalRootFlatTreeTestImpl : public StyleTraversalRootTestImpl {
162  private:
ParentInternal(const Node & node) const163   ContainerNode* ParentInternal(const Node& node) const final {
164     // Flat tree does not include Document or ShadowRoot.
165     return FlatTreeTraversal::ParentElement(node);
166   }
167 };
168 
TEST_F(StyleTraversalRootTest,Update_CommonRoot_FlatTree)169 TEST_F(StyleTraversalRootTest, Update_CommonRoot_FlatTree) {
170   StyleTraversalRootFlatTreeTestImpl root;
171 
172   // The single dirty node D becomes a single root.
173   root.MarkDirty(DivElement(kD));
174   root.Update(nullptr, DivElement(kD));
175 
176   EXPECT_EQ(DivElement(kD), root.GetRootNode());
177   EXPECT_TRUE(root.IsSingleRoot());
178 
179   // A becomes a common root.
180   root.MarkDirty(DivElement(kA));
181   root.Update(nullptr, DivElement(kA));
182 
183   EXPECT_EQ(DivElement(kA), root.GetRootNode());
184   EXPECT_TRUE(root.IsCommonRoot());
185 
186   // Making E dirty and the document becomes the common root.
187   root.MarkDirty(DivElement(kE));
188   root.Update(DivElement(kB), DivElement(kE));
189 
190   EXPECT_EQ(&GetDocument(), root.GetRootNode());
191   EXPECT_TRUE(root.IsCommonRoot());
192 }
193 
194 }  // namespace blink
195