blob: fcf4ccc7cb1d90c40bde35e5486b78ed8e4705bd [file] [log] [blame]
// Copyright 2018 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/style_traversal_root.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
namespace blink {
class StyleTraversalRootTestImpl : public StyleTraversalRoot {
STACK_ALLOCATED();
public:
StyleTraversalRootTestImpl() = default;
void MarkDirty(const Node* node) { dirty_nodes_.insert(node); }
void MarkChildDirty(const Node* node) { child_dirty_nodes_.insert(node); }
bool IsSingleRoot() const { return root_type_ == RootType::kSingleRoot; }
bool IsCommonRoot() const { return root_type_ == RootType::kCommonRoot; }
private:
virtual ContainerNode* ParentInternal(const Node& node) const {
return node.parentNode();
}
#if DCHECK_IS_ON()
ContainerNode* Parent(const Node& node) const override {
return ParentInternal(node);
}
bool IsChildDirty(const ContainerNode& node) const final {
return child_dirty_nodes_.Contains(&node);
}
#endif // DCHECK_IS_ON()
bool IsDirty(const Node& node) const final {
return dirty_nodes_.Contains(&node);
}
void ClearChildDirtyForAncestors(ContainerNode& parent) const override {
for (ContainerNode* ancestor = &parent; ancestor;
ancestor = ParentInternal(*ancestor)) {
child_dirty_nodes_.erase(ancestor);
}
}
HeapHashSet<Member<const Node>> dirty_nodes_;
mutable HeapHashSet<Member<const Node>> child_dirty_nodes_;
};
class StyleTraversalRootTest : public testing::Test {
protected:
enum ElementIndex { kA, kB, kC, kD, kE, kF, kG, kElementCount };
void SetUp() final {
document_ = MakeGarbageCollected<Document>();
elements_ = MakeGarbageCollected<HeapVector<Member<Element>, 7>>();
for (size_t i = 0; i < kElementCount; i++) {
elements_->push_back(GetDocument().CreateRawElement(html_names::kDivTag));
}
GetDocument().appendChild(DivElement(kA));
DivElement(kA)->appendChild(DivElement(kB));
DivElement(kA)->appendChild(DivElement(kC));
DivElement(kB)->appendChild(DivElement(kD));
DivElement(kB)->appendChild(DivElement(kE));
DivElement(kC)->appendChild(DivElement(kF));
DivElement(kC)->appendChild(DivElement(kG));
// Tree Looks like this:
// div#a
// |-- div#b
// | |-- div#d
// | `-- div#e
// `-- div#c
// |-- div#f
// `-- div#g
}
Document& GetDocument() { return *document_; }
Element* DivElement(ElementIndex index) { return elements_->at(index); }
private:
Persistent<Document> document_;
Persistent<HeapVector<Member<Element>, 7>> elements_;
};
TEST_F(StyleTraversalRootTest, Update_SingleRoot) {
StyleTraversalRootTestImpl root;
root.MarkChildDirty(&GetDocument());
root.MarkDirty(DivElement(kA));
// A single dirty node becomes a single root.
root.Update(nullptr, DivElement(kA));
EXPECT_EQ(DivElement(kA), root.GetRootNode());
EXPECT_TRUE(root.IsSingleRoot());
}
TEST_F(StyleTraversalRootTest, Update_CommonRoot) {
StyleTraversalRootTestImpl root;
root.MarkChildDirty(&GetDocument());
root.MarkChildDirty(DivElement(kA));
root.MarkDirty(DivElement(kC));
// Initially make B a single root.
root.Update(nullptr, DivElement(kB));
EXPECT_EQ(DivElement(kB), root.GetRootNode());
EXPECT_TRUE(root.IsSingleRoot());
// Adding C makes A a common root.
root.MarkDirty(DivElement(kB));
root.Update(DivElement(kA), DivElement(kC));
EXPECT_EQ(DivElement(kA), root.GetRootNode());
EXPECT_FALSE(root.IsSingleRoot());
EXPECT_TRUE(root.IsCommonRoot());
}
TEST_F(StyleTraversalRootTest, Update_CommonRootDirtySubtree) {
StyleTraversalRootTestImpl root;
root.MarkChildDirty(&GetDocument());
root.MarkDirty(DivElement(kA));
root.Update(nullptr, DivElement(kA));
// Marking descendants of a single dirty root makes the single root a common
// root as long as the new common ancestor is the current root.
root.MarkChildDirty(DivElement(kA));
root.MarkChildDirty(DivElement(kB));
root.MarkDirty(DivElement(kD));
root.Update(DivElement(kA), DivElement(kD));
EXPECT_EQ(DivElement(kA), root.GetRootNode());
EXPECT_FALSE(root.IsSingleRoot());
EXPECT_TRUE(root.IsCommonRoot());
}
TEST_F(StyleTraversalRootTest, Update_CommonRootDocumentFallback) {
StyleTraversalRootTestImpl root;
// Initially make B a common root for D and E.
root.MarkChildDirty(&GetDocument());
root.MarkChildDirty(DivElement(kA));
root.MarkChildDirty(DivElement(kB));
root.MarkDirty(DivElement(kD));
root.Update(nullptr, DivElement(kD));
root.MarkDirty(DivElement(kE));
root.Update(DivElement(kB), DivElement(kE));
EXPECT_EQ(DivElement(kB), root.GetRootNode());
EXPECT_FALSE(root.IsSingleRoot());
EXPECT_TRUE(root.IsCommonRoot());
// Adding C falls back to using the document as the root because we don't know
// if A is above or below the current common root B.
root.MarkDirty(DivElement(kC));
root.Update(DivElement(kA), DivElement(kC));
EXPECT_EQ(&GetDocument(), root.GetRootNode());
EXPECT_FALSE(root.IsSingleRoot());
EXPECT_TRUE(root.IsCommonRoot());
}
TEST_F(StyleTraversalRootTest, ChildrenRemoved) {
StyleTraversalRootTestImpl root;
// Initially make E a single root.
root.MarkChildDirty(&GetDocument());
root.MarkChildDirty(DivElement(kA));
root.MarkChildDirty(DivElement(kB));
root.MarkDirty(DivElement(kE));
root.Update(nullptr, DivElement(kE));
EXPECT_EQ(DivElement(kE), root.GetRootNode());
EXPECT_TRUE(root.IsSingleRoot());
// Removing D not affecting E.
DivElement(kD)->remove();
root.ChildrenRemoved(*DivElement(kB));
EXPECT_EQ(DivElement(kE), root.GetRootNode());
EXPECT_TRUE(root.IsSingleRoot());
// Removing B
DivElement(kB)->remove();
root.ChildrenRemoved(*DivElement(kA));
EXPECT_FALSE(root.GetRootNode());
EXPECT_TRUE(root.IsSingleRoot());
}
class StyleTraversalRootFlatTreeTestImpl : public StyleTraversalRootTestImpl {
private:
ContainerNode* ParentInternal(const Node& node) const final {
// Flat tree does not include Document or ShadowRoot.
return FlatTreeTraversal::ParentElement(node);
}
};
TEST_F(StyleTraversalRootTest, Update_CommonRoot_FlatTree) {
StyleTraversalRootFlatTreeTestImpl root;
// The single dirty node D becomes a single root.
root.MarkChildDirty(DivElement(kA));
root.MarkChildDirty(DivElement(kB));
root.MarkDirty(DivElement(kD));
root.Update(nullptr, DivElement(kD));
EXPECT_EQ(DivElement(kD), root.GetRootNode());
EXPECT_TRUE(root.IsSingleRoot());
// A becomes a common root.
root.MarkDirty(DivElement(kA));
root.Update(nullptr, DivElement(kA));
EXPECT_EQ(DivElement(kA), root.GetRootNode());
EXPECT_TRUE(root.IsCommonRoot());
// Making E dirty and the document becomes the common root.
root.MarkDirty(DivElement(kE));
root.Update(DivElement(kB), DivElement(kE));
EXPECT_EQ(&GetDocument(), root.GetRootNode());
EXPECT_TRUE(root.IsCommonRoot());
}
} // namespace blink