blob: 814ac9d0f607330d0c53d6817554429fddcef784 [file] [log] [blame]
// 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/page/slot_scoped_traversal.h"
#include <memory>
#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/node.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_slot_element.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/geometry/int_size.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
class SlotScopedTraversalTest : public testing::Test {
protected:
Document& GetDocument() const;
void SetupSampleHTML(const char* main_html,
const char* shadow_html,
unsigned);
void AttachOpenShadowRoot(Element& shadow_host,
const char* shadow_inner_html);
void TestExpectedSequence(const Vector<Element*>& list);
private:
void SetUp() override;
Persistent<Document> document_;
std::unique_ptr<DummyPageHolder> dummy_page_holder_;
};
void SlotScopedTraversalTest::SetUp() {
dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
document_ = &dummy_page_holder_->GetDocument();
DCHECK(document_);
}
Document& SlotScopedTraversalTest::GetDocument() const {
return *document_;
}
void SlotScopedTraversalTest::SetupSampleHTML(const char* main_html,
const char* shadow_html,
unsigned index) {
Element* body = GetDocument().body();
body->SetInnerHTMLFromString(String::FromUTF8(main_html));
if (shadow_html) {
Element* shadow_host = ToElement(NodeTraversal::ChildAt(*body, index));
AttachOpenShadowRoot(*shadow_host, shadow_html);
}
}
void SlotScopedTraversalTest::AttachOpenShadowRoot(
Element& shadow_host,
const char* shadow_inner_html) {
ShadowRoot& shadow_root =
shadow_host.AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.SetInnerHTMLFromString(String::FromUTF8(shadow_inner_html));
GetDocument().body()->UpdateDistributionForFlatTreeTraversal();
}
TEST_F(SlotScopedTraversalTest, emptySlot) {
const char* main_html = "<div id='host'></div>";
const char* shadow_html = "<slot></slot>";
SetupSampleHTML(main_html, shadow_html, 0);
Element* host = GetDocument().QuerySelector("#host");
ShadowRoot* shadow_root = host->OpenShadowRoot();
auto* slot = ToHTMLSlotElement(shadow_root->QuerySelector("slot"));
EXPECT_EQ(nullptr, SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(nullptr, SlotScopedTraversal::LastAssignedToSlot(*slot));
}
void SlotScopedTraversalTest::TestExpectedSequence(
const Vector<Element*>& list) {
for (wtf_size_t i = 0; i < list.size(); ++i) {
const Element* expected = i == list.size() - 1 ? nullptr : list[i + 1];
EXPECT_EQ(expected, SlotScopedTraversal::Next(*list[i]));
}
for (wtf_size_t i = list.size(); i > 0; --i) {
const Element* expected = i == 1 ? nullptr : list[i - 2];
EXPECT_EQ(expected, SlotScopedTraversal::Previous(*list[i - 1]));
}
}
TEST_F(SlotScopedTraversalTest, simpleSlot) {
const char* main_html =
"<div id='host'>"
"<div id='inner1'></div>"
"<div id='inner2'></div>"
"</div>";
const char* shadow_html = "<slot></slot>";
SetupSampleHTML(main_html, shadow_html, 0);
Element* host = GetDocument().QuerySelector("#host");
Element* inner1 = GetDocument().QuerySelector("#inner1");
Element* inner2 = GetDocument().QuerySelector("#inner2");
ShadowRoot* shadow_root = host->OpenShadowRoot();
auto* slot = ToHTMLSlotElement(shadow_root->QuerySelector("slot"));
EXPECT_EQ(inner1, SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(inner2, SlotScopedTraversal::LastAssignedToSlot(*slot));
EXPECT_FALSE(SlotScopedTraversal::IsSlotScoped(*host));
EXPECT_FALSE(SlotScopedTraversal::IsSlotScoped(*slot));
EXPECT_TRUE(SlotScopedTraversal::IsSlotScoped(*inner1));
EXPECT_TRUE(SlotScopedTraversal::IsSlotScoped(*inner2));
EXPECT_EQ(inner1, SlotScopedTraversal::NearestInclusiveAncestorAssignedToSlot(
*inner1));
EXPECT_EQ(inner2, SlotScopedTraversal::NearestInclusiveAncestorAssignedToSlot(
*inner2));
EXPECT_EQ(slot, SlotScopedTraversal::FindScopeOwnerSlot(*inner1));
EXPECT_EQ(slot, SlotScopedTraversal::FindScopeOwnerSlot(*inner2));
Vector<Element*> expected_sequence({inner1, inner2});
TestExpectedSequence(expected_sequence);
}
TEST_F(SlotScopedTraversalTest, multipleSlots) {
const char* main_html =
"<div id='host'>"
"<div id='inner0' slot='slot0'></div>"
"<div id='inner1' slot='slot1'></div>"
"<div id='inner2'></div>"
"<div id='inner3' slot='slot1'></div>"
"<div id='inner4' slot='slot0'></div>"
"<div id='inner5'></div>"
"</div>";
const char* shadow_html =
"<slot id='unnamedslot'></slot>"
"<slot id='slot0' name='slot0'></slot>"
"<slot id='slot1' name='slot1'></slot>";
SetupSampleHTML(main_html, shadow_html, 0);
Element* host = GetDocument().QuerySelector("#host");
Element* inner[6];
inner[0] = GetDocument().QuerySelector("#inner0");
inner[1] = GetDocument().QuerySelector("#inner1");
inner[2] = GetDocument().QuerySelector("#inner2");
inner[3] = GetDocument().QuerySelector("#inner3");
inner[4] = GetDocument().QuerySelector("#inner4");
inner[5] = GetDocument().QuerySelector("#inner5");
ShadowRoot* shadow_root = host->OpenShadowRoot();
Element* slot_element[3];
slot_element[0] = shadow_root->QuerySelector("#slot0");
slot_element[1] = shadow_root->QuerySelector("#slot1");
slot_element[2] = shadow_root->QuerySelector("#unnamedslot");
HTMLSlotElement* slot[3];
slot[0] = ToHTMLSlotElement(slot_element[0]);
slot[1] = ToHTMLSlotElement(slot_element[1]);
slot[2] = ToHTMLSlotElement(slot_element[2]);
{
// <slot id='slot0'> : Expected assigned nodes: inner0, inner4
EXPECT_EQ(inner[0], SlotScopedTraversal::FirstAssignedToSlot(*slot[0]));
EXPECT_EQ(inner[4], SlotScopedTraversal::LastAssignedToSlot(*slot[0]));
Vector<Element*> expected_sequence({inner[0], inner[4]});
TestExpectedSequence(expected_sequence);
}
{
// <slot name='slot1'> : Expected assigned nodes: inner1, inner3
EXPECT_EQ(inner[1], SlotScopedTraversal::FirstAssignedToSlot(*slot[1]));
EXPECT_EQ(inner[3], SlotScopedTraversal::LastAssignedToSlot(*slot[1]));
Vector<Element*> expected_sequence({inner[1], inner[3]});
TestExpectedSequence(expected_sequence);
}
{
// <slot id='unnamedslot'> : Expected assigned nodes: inner2, inner5
EXPECT_EQ(inner[2], SlotScopedTraversal::FirstAssignedToSlot(*slot[2]));
EXPECT_EQ(inner[5], SlotScopedTraversal::LastAssignedToSlot(*slot[2]));
Vector<Element*> expected_sequence({inner[2], inner[5]});
TestExpectedSequence(expected_sequence);
}
}
TEST_F(SlotScopedTraversalTest, shadowHostAtTopLevel) {
// This covers a shadow host is directly assigned to a slot.
//
// We build the following tree:
// host
// |
// ShadowRoot
// |
// ____<slot>____
// | | |
// inner0 inner1 inner2
// | | |
// child0 child1 child2
//
// And iterate on inner0, inner1, and inner2 to attach shadow on each of
// them, and check if elements that are slotted to another slot are skipped
// in traversal.
const char* main_html =
"<div id='host'>"
"<div id='inner0'><div id='child0'></div></div>"
"<div id='inner1'><div id='child1'></div></div>"
"<div id='inner2'><div id='child2'></div></div>"
"</div>";
const char* shadow_html = "<slot></slot>";
for (int i = 0; i < 3; ++i) {
SetupSampleHTML(main_html, shadow_html, 0);
Element* host = GetDocument().QuerySelector("#host");
Element* inner[3];
inner[0] = GetDocument().QuerySelector("#inner0");
inner[1] = GetDocument().QuerySelector("#inner1");
inner[2] = GetDocument().QuerySelector("#inner2");
Element* child[3];
child[0] = GetDocument().QuerySelector("#child0");
child[1] = GetDocument().QuerySelector("#child1");
child[2] = GetDocument().QuerySelector("#child2");
AttachOpenShadowRoot(*inner[i], shadow_html);
ShadowRoot* shadow_root = host->OpenShadowRoot();
auto* slot = ToHTMLSlotElement(shadow_root->QuerySelector("slot"));
switch (i) {
case 0: {
// inner0 is a shadow host.
EXPECT_EQ(inner[0], SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(child[2], SlotScopedTraversal::LastAssignedToSlot(*slot));
EXPECT_EQ(nullptr, SlotScopedTraversal::Next(*child[0]));
EXPECT_EQ(nullptr, SlotScopedTraversal::Previous(*child[0]));
Vector<Element*> expected_sequence(
{inner[0], inner[1], child[1], inner[2], child[2]});
TestExpectedSequence(expected_sequence);
} break;
case 1: {
// inner1 is a shadow host.
EXPECT_EQ(inner[0], SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(child[2], SlotScopedTraversal::LastAssignedToSlot(*slot));
EXPECT_EQ(nullptr, SlotScopedTraversal::Next(*child[1]));
EXPECT_EQ(nullptr, SlotScopedTraversal::Previous(*child[1]));
Vector<Element*> expected_sequence(
{inner[0], child[0], inner[1], inner[2], child[2]});
TestExpectedSequence(expected_sequence);
} break;
case 2: {
// inner2 is a shadow host.
EXPECT_EQ(inner[0], SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(inner[2], SlotScopedTraversal::LastAssignedToSlot(*slot));
EXPECT_EQ(nullptr, SlotScopedTraversal::Next(*child[2]));
EXPECT_EQ(nullptr, SlotScopedTraversal::Previous(*child[2]));
Vector<Element*> expected_sequence(
{inner[0], child[0], inner[1], child[1], inner[2]});
TestExpectedSequence(expected_sequence);
} break;
}
}
}
TEST_F(SlotScopedTraversalTest, shadowHostAtSecondLevel) {
// This covers cases where a shadow host exists in a child of a slotted
// element.
//
// We build the following tree:
// host
// |
// ShadowRoot
// |
// ____<slot>____
// | |
// _inner0_ _inner1_
// | | | |
// child0 child1 child2 child3
// | | | |
// span0 span1 span2 span3
//
// And iterate on child0, child1, child2, and child3 to attach shadow on each
// of them, and check if elements that are slotted to another slot are skipped
// in traversal.
const char* main_html =
"<div id='host'>"
"<div id='inner0'>"
"<div id='child0'><span id='span0'></span></div>"
"<div id='child1'><span id='span1'></span></div>"
"</div>"
"<div id='inner1'>"
"<div id='child2'><span id='span2'></span></div>"
"<div id='child3'><span id='span3'></span></div>"
"</div>"
"</div>";
const char* shadow_html = "<slot></slot>";
for (int i = 0; i < 4; ++i) {
SetupSampleHTML(main_html, shadow_html, 0);
Element* host = GetDocument().QuerySelector("#host");
Element* inner[2];
inner[0] = GetDocument().QuerySelector("#inner0");
inner[1] = GetDocument().QuerySelector("#inner1");
Element* child[4];
child[0] = GetDocument().QuerySelector("#child0");
child[1] = GetDocument().QuerySelector("#child1");
child[2] = GetDocument().QuerySelector("#child2");
child[3] = GetDocument().QuerySelector("#child3");
Element* span[4];
span[0] = GetDocument().QuerySelector("#span0");
span[1] = GetDocument().QuerySelector("#span1");
span[2] = GetDocument().QuerySelector("#span2");
span[3] = GetDocument().QuerySelector("#span3");
AttachOpenShadowRoot(*child[i], shadow_html);
for (int j = 0; j < 4; ++j) {
DCHECK(child[i]);
DCHECK(span[i]);
}
ShadowRoot* shadow_root = host->OpenShadowRoot();
auto* slot = ToHTMLSlotElement(shadow_root->QuerySelector("slot"));
switch (i) {
case 0: {
// child0 is a shadow host.
EXPECT_EQ(inner[0], SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(span[3], SlotScopedTraversal::LastAssignedToSlot(*slot));
EXPECT_EQ(nullptr, SlotScopedTraversal::Next(*span[0]));
EXPECT_EQ(nullptr, SlotScopedTraversal::Previous(*span[0]));
const Vector<Element*> expected_sequence({inner[0], child[0], child[1],
span[1], inner[1], child[2],
span[2], child[3], span[3]});
TestExpectedSequence(expected_sequence);
} break;
case 1: {
// child1 is a shadow host.
EXPECT_EQ(inner[0], SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(span[3], SlotScopedTraversal::LastAssignedToSlot(*slot));
EXPECT_EQ(nullptr, SlotScopedTraversal::Next(*span[1]));
EXPECT_EQ(nullptr, SlotScopedTraversal::Previous(*span[1]));
const Vector<Element*> expected_sequence({inner[0], child[0], span[0],
child[1], inner[1], child[2],
span[2], child[3], span[3]});
TestExpectedSequence(expected_sequence);
} break;
case 2: {
// child2 is a shadow host.
EXPECT_EQ(inner[0], SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(span[3], SlotScopedTraversal::LastAssignedToSlot(*slot));
EXPECT_EQ(nullptr, SlotScopedTraversal::Next(*span[2]));
EXPECT_EQ(nullptr, SlotScopedTraversal::Previous(*span[2]));
const Vector<Element*> expected_sequence({inner[0], child[0], span[0],
child[1], span[1], inner[1],
child[2], child[3], span[3]});
TestExpectedSequence(expected_sequence);
} break;
case 3: {
// child3 is a shadow host.
EXPECT_EQ(inner[0], SlotScopedTraversal::FirstAssignedToSlot(*slot));
EXPECT_EQ(child[3], SlotScopedTraversal::LastAssignedToSlot(*slot));
EXPECT_EQ(nullptr, SlotScopedTraversal::Next(*span[3]));
EXPECT_EQ(nullptr, SlotScopedTraversal::Previous(*span[3]));
const Vector<Element*> expected_sequence({inner[0], child[0], span[0],
child[1], span[1], inner[1],
child[2], span[2], child[3]});
TestExpectedSequence(expected_sequence);
} break;
}
}
}
} // namespace blink