blob: d5731fff7f3a2ef13df6ce8b5fa77465808fcdc0 [file] [log] [blame]
// Copyright 2017 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/layout/ng/ng_block_child_iterator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
namespace blink {
namespace {
scoped_refptr<const NGBlockBreakToken> CreateBreakToken(
NGLayoutInputNode node,
const NGBreakTokenVector* child_break_tokens = nullptr,
bool has_seen_all_children = false) {
NGBoxFragmentBuilder builder(
node, &node.Style(), /* space */ nullptr,
WritingDirectionMode(WritingMode::kHorizontalTb, TextDirection::kLtr));
if (has_seen_all_children)
builder.SetHasSeenAllChildren();
if (child_break_tokens) {
for (scoped_refptr<const NGBreakToken> token : *child_break_tokens)
builder.AddBreakToken(token);
}
return NGBlockBreakToken::Create(builder);
}
using NGBlockChildIteratorTest = NGLayoutTest;
TEST_F(NGBlockChildIteratorTest, NullFirstChild) {
NGBlockChildIterator iterator(nullptr, nullptr);
ASSERT_EQ(NGBlockChildIterator::Entry(nullptr, nullptr),
iterator.NextChild());
}
TEST_F(NGBlockChildIteratorTest, NoBreakToken) {
SetBodyInnerHTML(R"HTML(
<div id='child1'></div>
<div id='child2'></div>
<div id='child3'></div>
)HTML");
NGLayoutInputNode node1 = NGBlockNode(GetLayoutBoxByElementId("child1"));
NGLayoutInputNode node2 = node1.NextSibling();
NGLayoutInputNode node3 = node2.NextSibling();
// The iterator should loop through three children.
NGBlockChildIterator iterator(node1, nullptr);
ASSERT_EQ(NGBlockChildIterator::Entry(node1, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node2, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node3, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(nullptr, nullptr),
iterator.NextChild());
}
TEST_F(NGBlockChildIteratorTest, BreakTokens) {
SetBodyInnerHTML(R"HTML(
<div id='container'>
<div id='child1'></div>
<div id='child2'></div>
<div id='child3'></div>
<div id='child4'></div>
</div>
)HTML");
NGBlockNode container = NGBlockNode(GetLayoutBoxByElementId("container"));
NGLayoutInputNode node1 = container.FirstChild();
NGLayoutInputNode node2 = node1.NextSibling();
NGLayoutInputNode node3 = node2.NextSibling();
NGLayoutInputNode node4 = node3.NextSibling();
NGBreakTokenVector empty_tokens_list;
scoped_refptr<const NGBreakToken> child_token1 = CreateBreakToken(node1);
scoped_refptr<const NGBreakToken> child_token2 = CreateBreakToken(node2);
scoped_refptr<const NGBreakToken> child_token3 = CreateBreakToken(node3);
NGBreakTokenVector child_break_tokens;
child_break_tokens.push_back(child_token1);
scoped_refptr<const NGBlockBreakToken> parent_token =
CreateBreakToken(container, &child_break_tokens);
NGBlockChildIterator iterator(node1, parent_token.get());
ASSERT_EQ(NGBlockChildIterator::Entry(node1, child_token1.get()),
iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node2, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node3, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node4, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(nullptr, nullptr),
iterator.NextChild());
child_break_tokens.clear();
child_break_tokens.push_back(child_token1);
child_break_tokens.push_back(child_token2);
parent_token = CreateBreakToken(container, &child_break_tokens);
iterator = NGBlockChildIterator(node1, parent_token.get());
ASSERT_EQ(NGBlockChildIterator::Entry(node1, child_token1.get()),
iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node2, child_token2.get()),
iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node3, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node4, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(nullptr, nullptr),
iterator.NextChild());
child_break_tokens.clear();
child_break_tokens.push_back(child_token2);
child_break_tokens.push_back(child_token3);
parent_token = CreateBreakToken(container, &child_break_tokens);
iterator = NGBlockChildIterator(node1, parent_token.get());
ASSERT_EQ(NGBlockChildIterator::Entry(node2, child_token2.get()),
iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node3, child_token3.get()),
iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node4, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(nullptr, nullptr),
iterator.NextChild());
child_break_tokens.clear();
child_break_tokens.push_back(child_token1);
child_break_tokens.push_back(child_token3);
parent_token = CreateBreakToken(container, &child_break_tokens);
iterator = NGBlockChildIterator(node1, parent_token.get());
ASSERT_EQ(NGBlockChildIterator::Entry(node1, child_token1.get()),
iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node3, child_token3.get()),
iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(node4, nullptr), iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(nullptr, nullptr),
iterator.NextChild());
}
TEST_F(NGBlockChildIteratorTest, SeenAllChildren) {
SetBodyInnerHTML(R"HTML(
<div id='container'>
<div id='child1'></div>
<div id='child2'></div>
</div>
)HTML");
NGBlockNode container = NGBlockNode(GetLayoutBoxByElementId("container"));
NGLayoutInputNode node1 = container.FirstChild();
scoped_refptr<const NGBlockBreakToken> child_token1 = CreateBreakToken(node1);
NGBreakTokenVector child_break_tokens;
child_break_tokens.push_back(child_token1);
scoped_refptr<const NGBlockBreakToken> parent_token = CreateBreakToken(
container, &child_break_tokens, /* has_seen_all_children*/ true);
// We have a break token for #child1, but have seen all children. This happens
// e.g. when #child1 has overflow into a new fragmentainer, while #child2 was
// finished in an earlier fragmentainer.
NGBlockChildIterator iterator(node1, parent_token.get());
ASSERT_EQ(NGBlockChildIterator::Entry(node1, child_token1.get()),
iterator.NextChild());
ASSERT_EQ(NGBlockChildIterator::Entry(nullptr, nullptr),
iterator.NextChild());
parent_token = CreateBreakToken(container, /* child_break_tokens */ nullptr,
/* has_seen_all_children*/ true);
// We have no break tokens, but have seen all children. This happens e.g. when
// we have a large container with fixed block-size, with empty space at the
// end, not occupied by any children.
iterator = NGBlockChildIterator(node1, parent_token.get());
ASSERT_EQ(NGBlockChildIterator::Entry(nullptr, nullptr),
iterator.NextChild());
}
} // namespace
} // namespace blink