blob: bad31c5eb1458d70e020fa8247eeaf39c077e9b1 [file] [log] [blame]
// Copyright 2020 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_base_layout_algorithm_test.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
namespace {
class NGFragmentationTest : public NGBaseLayoutAlgorithmTest,
private ScopedLayoutNGBlockFragmentationForTest {
protected:
NGFragmentationTest() : ScopedLayoutNGBlockFragmentationForTest(true) {}
scoped_refptr<const NGPhysicalBoxFragment> RunBlockLayoutAlgorithm(
Element* element) {
NGBlockNode container(element->GetLayoutBox());
NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace(
{WritingMode::kHorizontalTb, TextDirection::kLtr},
LogicalSize(LayoutUnit(1000), kIndefiniteSize));
return NGBaseLayoutAlgorithmTest::RunBlockLayoutAlgorithm(container, space);
}
};
TEST_F(NGFragmentationTest, MultipleFragments) {
SetBodyInnerHTML(R"HTML(
<div id="container">
<div style="columns:3; width:620px; column-fill:auto; height:100px; column-gap:10px;">
<div id="outer1" style="height:150px;">
<div id="inner1" style="height:250px;"></div>
<div id="inner2" style="height:10px;"></div>
</div>
<div id="outer2" style="height:90px;"></div>
</div>
</div>
)HTML");
RunBlockLayoutAlgorithm(GetElementById("container"));
const LayoutBox* outer1 = GetLayoutBoxByElementId("outer1");
const LayoutBox* outer2 = GetLayoutBoxByElementId("outer2");
const LayoutBox* inner1 = GetLayoutBoxByElementId("inner1");
const LayoutBox* inner2 = GetLayoutBoxByElementId("inner2");
EXPECT_EQ(outer1->PhysicalFragmentCount(), 3u);
EXPECT_EQ(outer2->PhysicalFragmentCount(), 2u);
EXPECT_EQ(inner1->PhysicalFragmentCount(), 3u);
EXPECT_EQ(inner2->PhysicalFragmentCount(), 1u);
// While the #outer1 box itself only needs two fragments, we need to create a
// third fragment to hold the overflowing children in the third column.
EXPECT_EQ(outer1->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 100));
EXPECT_EQ(outer1->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 50));
EXPECT_EQ(outer1->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 0));
// #inner1 overflows its parent and uses three columns.
EXPECT_EQ(inner1->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 100));
EXPECT_EQ(inner1->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 100));
EXPECT_EQ(inner1->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 50));
// #inner2 is tiny, and only needs some space in one column (the third one).
EXPECT_EQ(inner2->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 10));
// #outer2 starts in the second column and ends in the third.
EXPECT_EQ(outer2->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 50));
EXPECT_EQ(outer2->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 40));
}
TEST_F(NGFragmentationTest, MultipleFragmentsAndColumnSpanner) {
SetBodyInnerHTML(R"HTML(
<div id="container">
<div id="multicol" style="columns:3; width:620px; column-gap:10px; orphans:1; widows:1; line-height:20px;">
<div id="outer">
<div id="inner1"><br><br><br><br></div>
<div id="spanner1" style="column-span:all;"></div>
<div id="inner2"><br><br><br><br><br></div>
<div id="spanner2" style="column-span:all;"></div>
<div id="inner3"><br><br><br><br><br><br><br></div>
</div>
</div>
</div>
)HTML");
RunBlockLayoutAlgorithm(GetElementById("container"));
const LayoutBox* multicol = GetLayoutBoxByElementId("multicol");
const LayoutBox* outer = GetLayoutBoxByElementId("outer");
const LayoutBox* inner1 = GetLayoutBoxByElementId("inner1");
const LayoutBox* inner2 = GetLayoutBoxByElementId("inner2");
const LayoutBox* inner3 = GetLayoutBoxByElementId("inner3");
const LayoutBox* spanner1 = GetLayoutBoxByElementId("spanner1");
const LayoutBox* spanner2 = GetLayoutBoxByElementId("spanner2");
EXPECT_EQ(multicol->PhysicalFragmentCount(), 1u);
// #outer will create 8 fragments: 2 for the 2 columns before the first
// spanner, 3 for the 3 columns between the two spanners, and 3 for the 3
// columns after the last spanner.
EXPECT_EQ(outer->PhysicalFragmentCount(), 8u);
// #inner1 has 4 lines split into 2 columns.
EXPECT_EQ(inner1->PhysicalFragmentCount(), 2u);
// #inner2 has 5 lines split into 3 columns.
EXPECT_EQ(inner2->PhysicalFragmentCount(), 3u);
// #inner3 has 8 lines split into 3 columns.
EXPECT_EQ(inner3->PhysicalFragmentCount(), 3u);
EXPECT_EQ(spanner1->PhysicalFragmentCount(), 1u);
EXPECT_EQ(spanner2->PhysicalFragmentCount(), 1u);
EXPECT_EQ(multicol->GetPhysicalFragment(0)->Size(), PhysicalSize(620, 140));
EXPECT_EQ(outer->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 40));
EXPECT_EQ(outer->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 40));
EXPECT_EQ(outer->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 40));
EXPECT_EQ(outer->GetPhysicalFragment(3)->Size(), PhysicalSize(200, 40));
EXPECT_EQ(outer->GetPhysicalFragment(4)->Size(), PhysicalSize(200, 20));
EXPECT_EQ(outer->GetPhysicalFragment(5)->Size(), PhysicalSize(200, 60));
EXPECT_EQ(outer->GetPhysicalFragment(6)->Size(), PhysicalSize(200, 60));
EXPECT_EQ(outer->GetPhysicalFragment(7)->Size(), PhysicalSize(200, 20));
EXPECT_EQ(inner1->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 40));
EXPECT_EQ(inner1->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 40));
EXPECT_EQ(inner2->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 40));
EXPECT_EQ(inner2->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 40));
EXPECT_EQ(inner2->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 20));
EXPECT_EQ(inner3->GetPhysicalFragment(0)->Size(), PhysicalSize(200, 60));
EXPECT_EQ(inner3->GetPhysicalFragment(1)->Size(), PhysicalSize(200, 60));
EXPECT_EQ(inner3->GetPhysicalFragment(2)->Size(), PhysicalSize(200, 20));
EXPECT_EQ(spanner1->GetPhysicalFragment(0)->Size(), PhysicalSize(620, 0));
EXPECT_EQ(spanner2->GetPhysicalFragment(0)->Size(), PhysicalSize(620, 0));
}
TEST_F(NGFragmentationTest, MultipleFragmentsNestedMulticol) {
SetBodyInnerHTML(R"HTML(
<div id="container">
<div id="outer_multicol" style="columns:3; column-fill:auto; height:100px; width:620px; column-gap:10px;">
<div id="inner_multicol" style="columns:2; column-fill:auto;">
<div id="child1" style="width:11px; height:350px;"></div>
<div id="child2" style="width:22px; height:350px;"></div>
</div>
</div>
</div>
)HTML");
RunBlockLayoutAlgorithm(GetElementById("container"));
const LayoutBox* outer_multicol = GetLayoutBoxByElementId("outer_multicol");
const LayoutBox* inner_multicol = GetLayoutBoxByElementId("inner_multicol");
const LayoutBox* child1 = GetLayoutBoxByElementId("child1");
const LayoutBox* child2 = GetLayoutBoxByElementId("child2");
EXPECT_EQ(outer_multicol->PhysicalFragmentCount(), 1u);
// The content is too tall (350px + 350px, column height 100px, 2*3 columns =
// 600px) and will use one more column than we have specified.
EXPECT_EQ(inner_multicol->PhysicalFragmentCount(), 4u);
// 350px tall content with a column height of 100px will require 4 fragments.
EXPECT_EQ(child1->PhysicalFragmentCount(), 4u);
EXPECT_EQ(child2->PhysicalFragmentCount(), 4u);
EXPECT_EQ(outer_multicol->GetPhysicalFragment(0)->Size(),
PhysicalSize(620, 100));
EXPECT_EQ(inner_multicol->GetPhysicalFragment(0)->Size(),
PhysicalSize(200, 100));
EXPECT_EQ(inner_multicol->GetPhysicalFragment(1)->Size(),
PhysicalSize(200, 100));
EXPECT_EQ(inner_multicol->GetPhysicalFragment(2)->Size(),
PhysicalSize(200, 100));
EXPECT_EQ(inner_multicol->GetPhysicalFragment(3)->Size(),
PhysicalSize(200, 100));
// #child1 starts at the beginning of a column, so the last fragment will be
// shorter than the rest.
EXPECT_EQ(child1->GetPhysicalFragment(0)->Size(), PhysicalSize(11, 100));
EXPECT_EQ(child1->GetPhysicalFragment(1)->Size(), PhysicalSize(11, 100));
EXPECT_EQ(child1->GetPhysicalFragment(2)->Size(), PhysicalSize(11, 100));
EXPECT_EQ(child1->GetPhysicalFragment(3)->Size(), PhysicalSize(11, 50));
// #child2 starts in the middle of a column, so the first fragment will be
// shorter than the rest.
EXPECT_EQ(child2->GetPhysicalFragment(0)->Size(), PhysicalSize(22, 50));
EXPECT_EQ(child2->GetPhysicalFragment(1)->Size(), PhysicalSize(22, 100));
EXPECT_EQ(child2->GetPhysicalFragment(2)->Size(), PhysicalSize(22, 100));
EXPECT_EQ(child2->GetPhysicalFragment(3)->Size(), PhysicalSize(22, 100));
}
TEST_F(NGFragmentationTest, HasSeenAllChildrenIfc) {
SetBodyInnerHTML(R"HTML(
<div id="container">
<div style="columns:3; column-fill:auto; height:50px; line-height:20px; orphans:1; widows:1;">
<div id="ifc" style="height:300px;">
<br><br>
<br><br>
<br><br>
<br>
</div>
</div>
</div>
)HTML");
RunBlockLayoutAlgorithm(GetElementById("container"));
const LayoutBox* ifc = GetLayoutBoxByElementId("ifc");
ASSERT_EQ(ifc->PhysicalFragmentCount(), 6u);
const NGPhysicalBoxFragment* fragment = ifc->GetPhysicalFragment(0);
const NGBlockBreakToken* break_token =
DynamicTo<NGBlockBreakToken>(fragment->BreakToken());
ASSERT_TRUE(break_token);
EXPECT_FALSE(break_token->HasSeenAllChildren());
fragment = ifc->GetPhysicalFragment(1);
break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken());
ASSERT_TRUE(break_token);
EXPECT_FALSE(break_token->HasSeenAllChildren());
fragment = ifc->GetPhysicalFragment(2);
break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken());
ASSERT_TRUE(break_token);
EXPECT_FALSE(break_token->HasSeenAllChildren());
fragment = ifc->GetPhysicalFragment(3);
break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken());
ASSERT_TRUE(break_token);
EXPECT_TRUE(break_token->HasSeenAllChildren());
fragment = ifc->GetPhysicalFragment(4);
break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken());
ASSERT_TRUE(break_token);
EXPECT_TRUE(break_token->HasSeenAllChildren());
fragment = ifc->GetPhysicalFragment(5);
break_token = DynamicTo<NGBlockBreakToken>(fragment->BreakToken());
EXPECT_FALSE(break_token);
}
TEST_F(NGFragmentationTest, InkOverflowInline) {
SetBodyInnerHTML(R"HTML(
<style>
#container {
font-size: 10px;
column-width: 100px;
column-gap: 10px;
width: 210px;
line-height: 15px;
height: 15px;
}
atomic {
display: inline-block;
width: 100px;
height: 10px;
background: blue;
}
.w15 {
width: 150px;
background: orange;
}
</style>
<div id="container">
<div>
<!-- 1st column does not have ink overflow. -->
<atomic></atomic>
<!-- 2nd column has 50px ink overflow to right. -->
<atomic><atomic class="w15"></atomic></atomic>
</div>
</div>
)HTML");
const auto* container =
To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"));
const auto* flow_thread = To<LayoutBlockFlow>(container->FirstChild());
DCHECK(flow_thread->IsLayoutFlowThread());
// |flow_thread| is in the stitched coordinate system.
EXPECT_EQ(flow_thread->PhysicalVisualOverflowRect(),
PhysicalRect(0, 0, 150, 30));
// TOOD(crbug.com/1144203): This should be (0, 0, 260, 15).
EXPECT_EQ(container->PhysicalVisualOverflowRect(),
PhysicalRect(0, 0, 210, 15));
}
TEST_F(NGFragmentationTest, OffsetFromOwnerLayoutBoxColumnBox) {
SetBodyInnerHTML(R"HTML(
<style>
#columns {
column-width: 100px;
column-gap: 10px;
column-fill: auto;
width: 320px;
height: 500px;
}
</style>
<div id="columns" style="background: blue">
<div id="block" style="height: 1500px"></div>
</div>
)HTML");
const auto* columns = GetLayoutBoxByElementId("columns");
const auto* flow_thread = To<LayoutBox>(columns->SlowFirstChild());
EXPECT_EQ(flow_thread->PhysicalFragmentCount(), 3u);
const NGPhysicalBoxFragment* fragment0 = flow_thread->GetPhysicalFragment(0);
EXPECT_EQ(fragment0->OffsetFromOwnerLayoutBox(), PhysicalOffset());
const NGPhysicalBoxFragment* fragment1 = flow_thread->GetPhysicalFragment(1);
EXPECT_EQ(fragment1->OffsetFromOwnerLayoutBox(), PhysicalOffset(110, 0));
const NGPhysicalBoxFragment* fragment2 = flow_thread->GetPhysicalFragment(2);
EXPECT_EQ(fragment2->OffsetFromOwnerLayoutBox(), PhysicalOffset(220, 0));
// Check running another layout does not crash.
GetElementById("block")->appendChild(GetDocument().createTextNode("a"));
RunDocumentLifecycle();
}
TEST_F(NGFragmentationTest, OffsetFromOwnerLayoutBoxFloat) {
SetBodyInnerHTML(R"HTML(
<style>
#columns {
column-width: 100px;
column-gap: 10px;
column-fill: auto;
width: 320px;
height: 500px;
}
#float {
float: left;
width: 50px;
height: 500px;
background: orange;
}
</style>
<div id="columns" style="background: blue">
<!-- A spacer to make `target` start at 2nd column. -->
<div style="height: 800px"></div>
<div id="float"></div>
Text
</div>
)HTML");
const auto* target = GetLayoutBoxByElementId("float");
EXPECT_EQ(target->PhysicalFragmentCount(), 2u);
const NGPhysicalBoxFragment* fragment0 = target->GetPhysicalFragment(0);
EXPECT_EQ(fragment0->OffsetFromOwnerLayoutBox(), PhysicalOffset());
const NGPhysicalBoxFragment* fragment1 = target->GetPhysicalFragment(1);
EXPECT_EQ(fragment1->OffsetFromOwnerLayoutBox(), PhysicalOffset(110, -300));
}
TEST_F(NGFragmentationTest, OffsetFromOwnerLayoutBoxNested) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0;
}
#outer-columns {
column-width: 100px;
column-gap: 10px;
column-fill: auto;
width: 320px;
height: 500px;
}
#inner-columns {
column-width: 45px;
column-gap: 10px;
column-fill: auto;
width: 100px;
height: 800px;
}
</style>
<div id="outer-columns" style="background: blue">
<!-- A spacer to make `inner-columns` start at 2nd column. -->
<div style="height: 700px"></div>
<div id="inner-columns" style="height: 800px; background: purple">
<!-- A spacer to make `target` start at 2nd column. -->
<div style="height: 400px"></div>
<div id="target" style="background: orange; height: 1000px"></div>
</div>
</div>
)HTML");
const auto* target = GetLayoutBoxByElementId("target");
EXPECT_EQ(target->PhysicalFragmentCount(), 3u);
const NGPhysicalBoxFragment* fragment0 = target->GetPhysicalFragment(0);
EXPECT_EQ(fragment0->OffsetFromOwnerLayoutBox(), PhysicalOffset());
const NGPhysicalBoxFragment* fragment1 = target->GetPhysicalFragment(1);
EXPECT_EQ(fragment1->OffsetFromOwnerLayoutBox(), PhysicalOffset(55, -300));
const NGPhysicalBoxFragment* fragment2 = target->GetPhysicalFragment(2);
EXPECT_EQ(fragment2->OffsetFromOwnerLayoutBox(), PhysicalOffset(110, -300));
}
} // anonymous namespace
} // namespace blink