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