blob: 318c43f771e76d2d922d814fb65465c419941f42 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// 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/dom/shadow_including_tree_order_traversal.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
namespace blink {
namespace {
using ShadowIncludingTreeOrderTraversalTest = PageTestBase;
using ::testing::ElementsAre;
// Traverses `traversal_range` and collects the element ids of the `Element`s it
// discovers. If a node is a shadow root, it collects the string "shadow"
// instead.
Vector<String> GatherElementIdsFromTraversalRange(auto traversal_range) {
Vector<String> ids;
for (Node& node : traversal_range) {
if (auto* el = DynamicTo<Element>(node)) {
ids.push_back(el->GetIdAttribute());
} else if (node.IsShadowRoot()) {
ids.push_back("shadow");
}
}
return ids;
}
void RemoveWhiteSpaceOnlyTextNodes(ContainerNode& container) {
HeapVector<Member<Text>> to_remove;
for (Node& descendant : NodeTraversal::DescendantsOf(container)) {
if (auto* text = DynamicTo<Text>(&descendant);
text && text->ContainsOnlyWhitespaceOrEmpty()) {
to_remove.push_back(text);
}
}
for (Text* text : to_remove)
text->remove();
}
TEST_F(ShadowIncludingTreeOrderTraversalTest, Next) {
GetDocument().body()->setHTMLUnsafe(R"HTML(
<div id="c0">
<div id="c00">
<template shadowrootmode="open"></template>
</div>
<div id="c01">
<template shadowrootmode="open">
<div id="s0"></div>
<div id="s1">
<div id="s10"></div>
</div>
</template>
</div>
<div id="c02">
<div id="c020"></div>
<div id="c021" slot="t01"></div>
<template shadowrootmode="open">
<div id="t0">
<slot id="t00"></slot>
<slot id="t01"></slot>
</div>
</template>
</div>
</div>
<div id="c1"></div>
)HTML");
RemoveWhiteSpaceOnlyTextNodes(*GetDocument().body());
auto* c0 = GetElementById("c0");
auto* c1 = GetElementById("c1");
auto* c00 = GetElementById("c00");
auto* c01 = GetElementById("c01");
auto* c02 = GetElementById("c02");
auto* c020 = GetElementById("c020");
auto* c021 = GetElementById("c021");
ShadowRoot* shadow_root_0 = c00->GetShadowRoot();
ASSERT_TRUE(shadow_root_0);
ShadowRoot* shadow_root_1 = c01->GetShadowRoot();
ASSERT_TRUE(shadow_root_1);
RemoveWhiteSpaceOnlyTextNodes(*shadow_root_1);
auto* s0 = shadow_root_1->getElementById(AtomicString("s0"));
auto* s1 = shadow_root_1->getElementById(AtomicString("s1"));
auto* s10 = shadow_root_1->getElementById(AtomicString("s10"));
ShadowRoot* shadow_root_2 = c02->GetShadowRoot();
ASSERT_TRUE(shadow_root_2);
RemoveWhiteSpaceOnlyTextNodes(*shadow_root_2);
auto* t0 = shadow_root_2->getElementById(AtomicString("t0"));
auto* t00 = shadow_root_2->getElementById(AtomicString("t00"));
auto* t01 = shadow_root_2->getElementById(AtomicString("t01"));
// Test iteration order using Next.
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*GetDocument().body(),
&GetDocument()),
c0);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*c0, &GetDocument()), c00);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*c00, &GetDocument()),
shadow_root_0);
EXPECT_EQ(
ShadowIncludingTreeOrderTraversal::Next(*shadow_root_0, &GetDocument()),
c01);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*c01, &GetDocument()),
shadow_root_1);
EXPECT_EQ(
ShadowIncludingTreeOrderTraversal::Next(*shadow_root_1, &GetDocument()),
s0);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*s0, &GetDocument()), s1);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*s1, &GetDocument()), s10);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*s10, &GetDocument()), c02);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*c02, &GetDocument()),
shadow_root_2);
EXPECT_EQ(
ShadowIncludingTreeOrderTraversal::Next(*shadow_root_2, &GetDocument()),
t0);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*t0, &GetDocument()), t00);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*t00, &GetDocument()), t01);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*t01, &GetDocument()),
c020);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*c020, &GetDocument()),
c021);
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*c021, &GetDocument()), c1);
// c1 is not in c0's tree, so this returns nullptr.
EXPECT_EQ(ShadowIncludingTreeOrderTraversal::Next(*c021, c0), nullptr);
}
TEST_F(ShadowIncludingTreeOrderTraversalTest, DescendantsOf) {
GetDocument().body()->setHTMLUnsafe(R"HTML(
<div id="a0">
<div id="a00"></div>
<div id="a01"></div>
</div>
<div id="a1">
<template shadowrootmode="open" id="sr1">
<div id="b0">
<div id="b00"></div>
</div>
</template>
<div id="a10"></div>
</div>
<div id="a2"></div>
)HTML");
GetDocument().body()->SetIdAttribute(AtomicString("body"));
EXPECT_THAT(GatherElementIdsFromTraversalRange(
ShadowIncludingTreeOrderTraversal::DescendantsOf(
*GetDocument().body())),
ElementsAre("a0", "a00", "a01", "a1", "shadow", "b0", "b00",
"a10", "a2"));
EXPECT_THAT(GatherElementIdsFromTraversalRange(
ShadowIncludingTreeOrderTraversal::InclusiveDescendantsOf(
*GetDocument().body())),
ElementsAre("body", "a0", "a00", "a01", "a1", "shadow", "b0",
"b00", "a10", "a2"));
}
TEST_F(ShadowIncludingTreeOrderTraversalTest, ChildrenOf) {
GetDocument().body()->setHTMLUnsafe(R"HTML(
<div id="a0">
<div id="a00"></div>
<div id="a01"></div>
</div>
<div id="a1">
<template shadowrootmode="open">
<div id="b0">
<div id="b00"></div>
</div>
</template>
<div id="a10"></div>
</div>
<div id="a2">
<template shadowrootmode="open">
<slot></slot>
</template>
<div id="a20"></div>
</div>
)HTML");
EXPECT_THAT(
GatherElementIdsFromTraversalRange(
ShadowIncludingTreeOrderTraversal::ChildrenOf(*GetDocument().body())),
ElementsAre("a0", "a1", "a2"));
EXPECT_THAT(
GatherElementIdsFromTraversalRange(
ShadowIncludingTreeOrderTraversal::ChildrenOf(*GetElementById("a1"))),
ElementsAre("shadow", "a10"));
EXPECT_THAT(
GatherElementIdsFromTraversalRange(
ShadowIncludingTreeOrderTraversal::ChildrenOf(*GetElementById("a2"))),
ElementsAre("shadow", "a20"));
}
} // namespace
} // namespace blink