| // 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/layout/ng/ng_block_layout_algorithm.h" |
| |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/renderer/core/dom/dom_token_list.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/dom/tag_collection.h" |
| #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_base_layout_algorithm_test.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_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_result.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/core/layout/ng/ng_physical_fragment.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" |
| #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" |
| |
| namespace blink { |
| namespace { |
| |
| using testing::ElementsAre; |
| using testing::Pointee; |
| |
| class NGBlockLayoutAlgorithmTest : public NGBaseLayoutAlgorithmTest { |
| protected: |
| void SetUp() override { |
| NGBaseLayoutAlgorithmTest::SetUp(); |
| } |
| |
| MinMaxSizes RunComputeMinMaxSizes(NGBlockNode node) { |
| // The constraint space is not used for min/max computation, but we need |
| // it to create the algorithm. |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(), LayoutUnit())); |
| NGFragmentGeometry fragment_geometry = |
| CalculateInitialMinMaxFragmentGeometry(space, node); |
| |
| NGBlockLayoutAlgorithm algorithm({node, fragment_geometry, space}); |
| MinMaxSizesInput input( |
| /* percentage_resolution_block_size */ LayoutUnit(), |
| MinMaxSizesType::kContent); |
| return algorithm.ComputeMinMaxSizes(input).sizes; |
| } |
| |
| scoped_refptr<const NGLayoutResult> RunCachedLayoutResult( |
| const NGConstraintSpace& space, |
| const NGBlockNode& node) { |
| NGLayoutCacheStatus cache_status; |
| base::Optional<NGFragmentGeometry> initial_fragment_geometry; |
| return To<LayoutBlockFlow>(node.GetLayoutBox()) |
| ->CachedLayoutResult(space, nullptr, nullptr, |
| &initial_fragment_geometry, &cache_status); |
| } |
| |
| String DumpFragmentTree(const NGPhysicalBoxFragment* fragment) { |
| NGPhysicalFragment::DumpFlags flags = |
| NGPhysicalFragment::DumpHeaderText | NGPhysicalFragment::DumpSubtree | |
| NGPhysicalFragment::DumpIndentation | NGPhysicalFragment::DumpOffset | |
| NGPhysicalFragment::DumpSize; |
| |
| return fragment->DumpFragmentTree(flags); |
| } |
| |
| scoped_refptr<ComputedStyle> MutableStyleForElement(Element* element) { |
| DCHECK(element->GetLayoutObject()); |
| scoped_refptr<ComputedStyle> mutable_style = |
| ComputedStyle::Clone(element->GetLayoutObject()->StyleRef()); |
| element->GetLayoutObject()->SetModifiedStyleOutsideStyleRecalc( |
| mutable_style, LayoutObject::ApplyStyleChanges::kNo); |
| return mutable_style; |
| } |
| }; |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, FixedSize) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="box" style="width:30px; height:40px"></div> |
| )HTML"); |
| |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), kIndefiniteSize)); |
| |
| NGBlockNode box(GetLayoutBoxByElementId("box")); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(box, space); |
| |
| EXPECT_EQ(PhysicalSize(30, 40), fragment->Size()); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, Caching) { |
| // The inner element exists so that "simplified" layout logic isn't invoked. |
| SetBodyInnerHTML(R"HTML( |
| <div id="box" style="width:30px; height:40%;"> |
| <div style="height: 100%;"></div> |
| </div> |
| )HTML"); |
| |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), LayoutUnit(100))); |
| |
| auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box")); |
| NGBlockNode node(block_flow); |
| |
| scoped_refptr<const NGLayoutResult> result(node.Layout(space, nullptr)); |
| EXPECT_EQ(PhysicalSize(30, 40), result->PhysicalFragment().Size()); |
| |
| // Test pointer-equal constraint space. |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_NE(result.get(), nullptr); |
| |
| // Test identical, but not pointer-equal, constraint space. |
| space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), LayoutUnit(100))); |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_NE(result.get(), nullptr); |
| |
| // Test different constraint space. |
| space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(200), LayoutUnit(100))); |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_NE(result.get(), nullptr); |
| |
| // Test a different constraint space that will actually result in a different |
| // sized fragment. |
| space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(200), LayoutUnit(200))); |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_EQ(result.get(), nullptr); |
| |
| // Test layout invalidation |
| block_flow->SetNeedsLayout(""); |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_EQ(result.get(), nullptr); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, MinInlineSizeCaching) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="box" style="min-width:30%; width: 10px; height:40px;"></div> |
| )HTML"); |
| |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), LayoutUnit(100))); |
| |
| auto* block_flow = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box")); |
| NGBlockNode node(block_flow); |
| |
| scoped_refptr<const NGLayoutResult> result(node.Layout(space, nullptr)); |
| EXPECT_EQ(PhysicalSize(30, 40), result->PhysicalFragment().Size()); |
| |
| // Test pointer-equal constraint space. |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_NE(result.get(), nullptr); |
| |
| // Test identical, but not pointer-equal, constraint space. |
| space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), LayoutUnit(100))); |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_NE(result.get(), nullptr); |
| |
| // Test different constraint space. |
| space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), LayoutUnit(200))); |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_NE(result.get(), nullptr); |
| |
| // Test a different constraint space that will actually result in a different |
| // size. |
| space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(200), LayoutUnit(100))); |
| result = RunCachedLayoutResult(space, node); |
| EXPECT_EQ(result.get(), nullptr); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, PercentageBlockSizeQuirkDescendantsCaching) { |
| // Quirks mode triggers the interesting parent-child %-resolution behaviour. |
| GetDocument().SetCompatibilityMode(Document::kQuirksMode); |
| |
| SetBodyInnerHTML(R"HTML( |
| <div id="container" style="display: flow-root; width: 100px; height: 100px;"> |
| <div id="box1"></div> |
| <div id="box2"> |
| <div style="height: 20px;"></div> |
| <div style="height: 20px;"></div> |
| </div> |
| <div id="box3"> |
| <div style="height: 20px;"></div> |
| <div style="height: 50%;"></div> |
| </div> |
| <div id="box4"> |
| <div style="height: 20px;"></div> |
| <div style="display: flex;"></div> |
| </div> |
| <div id="box5"> |
| <div style="height: 20px;"></div> |
| <div style="display: flex; height: 50%;"></div> |
| </div> |
| <div id="box6" style="position: relative;"> |
| <div style="position: absolute; width: 10px; height: 100%;"></div> |
| </div> |
| <div id="box7"> |
| <img /> |
| </div> |
| <div id="box8"> |
| <img style="height: 100%;" /> |
| </div> |
| </div> |
| )HTML"); |
| |
| auto create_space = [&](auto size) -> NGConstraintSpace { |
| NGConstraintSpaceBuilder builder( |
| WritingMode::kHorizontalTb, |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| /* is_new_formatting_context */ false); |
| builder.SetAvailableSize(size); |
| builder.SetPercentageResolutionSize(size); |
| builder.SetStretchInlineSizeIfAuto(true); |
| return builder.ToConstraintSpace(); |
| }; |
| |
| NGConstraintSpace space100 = |
| create_space(LogicalSize(LayoutUnit(100), LayoutUnit(100))); |
| NGConstraintSpace space200 = |
| create_space(LogicalSize(LayoutUnit(100), LayoutUnit(200))); |
| |
| auto run_test = [&](auto id) -> scoped_refptr<const NGLayoutResult> { |
| // Grab the box under test. |
| auto* box = To<LayoutBlockFlow>(GetLayoutObjectByElementId(id)); |
| NGBlockNode node(box); |
| |
| // Check that we have a cache hit with space100. |
| scoped_refptr<const NGLayoutResult> result = |
| RunCachedLayoutResult(space100, node); |
| EXPECT_NE(result.get(), nullptr); |
| |
| // Return the result of the cache with space200. |
| return RunCachedLayoutResult(space200, node); |
| }; |
| |
| // Test 1: No descendants. |
| EXPECT_NE(run_test("box1"), nullptr); |
| |
| // Test 2: No %-height descendants. |
| EXPECT_NE(run_test("box2"), nullptr); |
| |
| // Test 3: A %-height descendant. |
| EXPECT_EQ(run_test("box3"), nullptr); |
| |
| // Test 4: A flexbox (legacy descendant), which doesn't use the quirks mode |
| // behaviour. |
| EXPECT_NE(run_test("box4"), nullptr); |
| |
| // Test 5: A flexbox (legacy descendant), which doesn't use the quirks mode |
| // behaviour, but is %-sized. |
| EXPECT_EQ(run_test("box5"), nullptr); |
| |
| // Test 6: An OOF positioned descentant which has a %-height, should not |
| // count as a percentage descendant. |
| EXPECT_NE(run_test("box6"), nullptr); |
| |
| // Test 7: A replaced element (legacy descendant), shouldn't use the quirks |
| // mode behaviour. |
| EXPECT_NE(run_test("box7"), nullptr); |
| |
| // Test 8: A replaced element (legacy descendant), shouldn't use the quirks |
| // mode behaviour, but is %-sized. |
| EXPECT_EQ(run_test("box8"), nullptr); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, LineOffsetCaching) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="container" style="display: flow-root; width: 300px; height: 100px;"> |
| <div id="box1" style="width: 100px; margin: 0 auto 0 auto;"></div> |
| </div> |
| )HTML"); |
| |
| auto create_space = [&](auto size, auto bfc_offset) -> NGConstraintSpace { |
| NGConstraintSpaceBuilder builder( |
| WritingMode::kHorizontalTb, |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| /* is_new_formatting_context */ false); |
| builder.SetAvailableSize(size); |
| builder.SetPercentageResolutionSize(size); |
| builder.SetBfcOffset(bfc_offset); |
| return builder.ToConstraintSpace(); |
| }; |
| |
| NGConstraintSpace space200 = |
| create_space(LogicalSize(LayoutUnit(300), LayoutUnit(100)), |
| NGBfcOffset(LayoutUnit(50), LayoutUnit())); |
| |
| scoped_refptr<const NGLayoutResult> result; |
| auto* box1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("box1")); |
| |
| // Ensure we get a cached layout result, even if our BFC line-offset changed. |
| result = RunCachedLayoutResult(space200, NGBlockNode(box1)); |
| EXPECT_NE(result.get(), nullptr); |
| } |
| |
| // Verifies that two children are laid out with the correct size and position. |
| TEST_F(NGBlockLayoutAlgorithmTest, LayoutBlockChildren) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="container" style="width: 30px"> |
| <div style="height: 20px"> |
| </div> |
| <div style="height: 30px; margin-top: 5px; margin-bottom: 20px"> |
| </div> |
| </div> |
| )HTML"); |
| const int kWidth = 30; |
| const int kHeight1 = 20; |
| const int kHeight2 = 30; |
| const int kMarginTop = 5; |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), kIndefiniteSize)); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(container, space); |
| |
| EXPECT_EQ(LayoutUnit(kWidth), fragment->Size().width); |
| EXPECT_EQ(LayoutUnit(kHeight1 + kHeight2 + kMarginTop), |
| fragment->Size().height); |
| EXPECT_EQ(NGPhysicalFragment::kFragmentBox, fragment->Type()); |
| ASSERT_EQ(fragment->Children().size(), 2UL); |
| |
| const NGLink& first_child = fragment->Children()[0]; |
| EXPECT_EQ(kHeight1, first_child->Size().height); |
| EXPECT_EQ(0, first_child.Offset().top); |
| |
| const NGLink& second_child = fragment->Children()[1]; |
| EXPECT_EQ(kHeight2, second_child->Size().height); |
| EXPECT_EQ(kHeight1 + kMarginTop, second_child.Offset().top); |
| } |
| |
| // Verifies that a child is laid out correctly if it's writing mode is different |
| // from the parent's one. |
| TEST_F(NGBlockLayoutAlgorithmTest, LayoutBlockChildrenWithWritingMode) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #div2 { |
| width: 50px; |
| height: 50px; |
| margin-left: 100px; |
| writing-mode: horizontal-tb; |
| } |
| </style> |
| <div id="container"> |
| <div id="div1" style="writing-mode: vertical-lr;"> |
| <div id="div2"> |
| </div> |
| </div> |
| </div> |
| )HTML"); |
| const int kHeight = 50; |
| const int kMarginLeft = 100; |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(500), LayoutUnit(500))); |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(container, space); |
| |
| const NGLink& child = fragment->Children()[0]; |
| const NGLink& child2 = |
| static_cast<const NGPhysicalBoxFragment*>(child.get())->Children()[0]; |
| |
| EXPECT_EQ(kHeight, child2->Size().height); |
| EXPECT_EQ(0, child2.Offset().top); |
| EXPECT_EQ(kMarginLeft, child2.Offset().left); |
| } |
| |
| // Verifies that floats are positioned at the top of the first child that can |
| // determine its position after margins collapsed. |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase1WithFloats) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #container { |
| height: 200px; |
| width: 200px; |
| margin-top: 10px; |
| padding: 0 7px; |
| background-color: red; |
| } |
| #first-child { |
| margin-top: 20px; |
| height: 10px; |
| background-color: blue; |
| } |
| #float-child-left { |
| float: left; |
| height: 10px; |
| width: 10px; |
| padding: 10px; |
| margin: 10px; |
| background-color: green; |
| } |
| #float-child-right { |
| float: right; |
| height: 30px; |
| width: 30px; |
| background-color: pink; |
| } |
| </style> |
| <div id='container'> |
| <div id='float-child-left'></div> |
| <div id='float-child-right'></div> |
| <div id='first-child'></div> |
| </div> |
| )HTML"); |
| |
| // ** Run LayoutNG algorithm ** |
| NGConstraintSpace space; |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| std::tie(fragment, space) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| ASSERT_EQ(fragment->Children().size(), 1UL); |
| PhysicalOffset body_offset = fragment->Children()[0].Offset(); |
| auto* body_fragment = |
| To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| // 20 = max(first child's margin top, containers's margin top) |
| int body_top_offset = 20; |
| EXPECT_THAT(LayoutUnit(body_top_offset), body_offset.top); |
| // 8 = body's margin |
| int body_left_offset = 8; |
| EXPECT_THAT(LayoutUnit(body_left_offset), body_offset.left); |
| ASSERT_EQ(1UL, body_fragment->Children().size()); |
| |
| auto* container_fragment = |
| To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); |
| PhysicalOffset container_offset = body_fragment->Children()[0].Offset(); |
| |
| // 0 = collapsed with body's margin |
| EXPECT_THAT(LayoutUnit(0), container_offset.top); |
| ASSERT_EQ(3UL, container_fragment->Children().size()); |
| |
| PhysicalOffset child_offset = container_fragment->Children()[2].Offset(); |
| |
| // 0 = collapsed with container's margin |
| EXPECT_THAT(LayoutUnit(0), child_offset.top); |
| } |
| |
| // Verifies the collapsing margins case for the next pairs: |
| // - bottom margin of box and top margin of its next in-flow following sibling. |
| // - top and bottom margins of a box that does not establish a new block |
| // formatting context and that has zero computed 'min-height', zero or 'auto' |
| // computed 'height', and no in-flow children |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase2WithFloats) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #first-child { |
| background-color: red; |
| height: 50px; |
| margin-bottom: 20px; |
| } |
| #float-between-empties { |
| background-color: green; |
| float: left; |
| height: 30px; |
| width: 30px; |
| } |
| #float-between-nonempties { |
| background-color: lightgreen; |
| float: left; |
| height: 40px; |
| width: 40px; |
| } |
| #float-top-align { |
| background-color: seagreen; |
| float: left; |
| height: 50px; |
| width: 50px; |
| } |
| #second-child { |
| background-color: blue; |
| height: 50px; |
| margin-top: 10px; |
| } |
| </style> |
| <div id='first-child'> |
| <div id='empty1' style='margin-bottom: -15px'></div> |
| <div id='float-between-empties'></div> |
| <div id='empty2'></div> |
| </div> |
| <div id='float-between-nonempties'></div> |
| <div id='second-child'> |
| <div id='float-top-align'></div> |
| <div id='empty3'></div> |
| <div id='empty4' style='margin-top: -30px'></div> |
| </div> |
| <div id='empty5'></div> |
| )HTML"); |
| |
| // ** Run LayoutNG algorithm ** |
| NGConstraintSpace space; |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| std::tie(fragment, space) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| |
| auto* body_fragment = |
| To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| PhysicalOffset body_offset = fragment->Children()[0].Offset(); |
| // -7 = empty1's margin(-15) + body's margin(8) |
| EXPECT_THAT(LayoutUnit(-7), body_offset.top); |
| ASSERT_EQ(4UL, body_fragment->Children().size()); |
| |
| FragmentChildIterator iterator(body_fragment); |
| PhysicalOffset offset; |
| iterator.NextChild(&offset); |
| EXPECT_THAT(LayoutUnit(), offset.top); |
| |
| iterator.NextChild(&offset); |
| // 70 = first_child's height(50) + first child's margin-bottom(20) |
| EXPECT_THAT(offset.top, LayoutUnit(70)); |
| EXPECT_THAT(offset.left, LayoutUnit(0)); |
| |
| iterator.NextChild(&offset); |
| // 40 = first_child's height(50) - margin's collapsing result(10) |
| EXPECT_THAT(LayoutUnit(40), offset.top); |
| |
| iterator.NextChild(&offset); |
| // 90 = first_child's height(50) + collapsed margins(-10) + |
| // second child's height(50) |
| EXPECT_THAT(LayoutUnit(90), offset.top); |
| |
| // ** Verify layout tree ** |
| Element* first_child = GetDocument().getElementById("first-child"); |
| // -7 = body_top_offset |
| EXPECT_EQ(-7, first_child->OffsetTop()); |
| } |
| |
| // Verifies the collapsing margins case for the next pair: |
| // - bottom margin of a last in-flow child and bottom margin of its parent if |
| // the parent has 'auto' computed height |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase3) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #container { |
| margin-bottom: 20px; |
| } |
| #child { |
| margin-bottom: 200px; |
| height: 50px; |
| } |
| </style> |
| <div id='container'> |
| <div id='child'></div> |
| </div> |
| )HTML"); |
| |
| const NGPhysicalBoxFragment* body_fragment; |
| const NGPhysicalBoxFragment* container_fragment; |
| const NGPhysicalBoxFragment* child_fragment; |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| auto run_test = [&](const Length& container_height) { |
| Element* container = GetDocument().getElementById("container"); |
| MutableStyleForElement(container)->SetHeight(container_height); |
| container->GetLayoutObject()->SetNeedsLayout(""); |
| std::tie(fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| ASSERT_EQ(1UL, fragment->Children().size()); |
| body_fragment = To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| container_fragment = |
| To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); |
| ASSERT_EQ(1UL, container_fragment->Children().size()); |
| child_fragment = |
| To<NGPhysicalBoxFragment>(container_fragment->Children()[0].get()); |
| }; |
| |
| // height == auto |
| run_test(Length::Auto()); |
| // Margins are collapsed with the result 200 = std::max(20, 200) |
| // The fragment size 258 == body's margin 8 + child's height 50 + 200 |
| EXPECT_EQ(PhysicalSize(800, 258), fragment->Size()); |
| |
| // height == fixed |
| run_test(Length::Fixed(50)); |
| // Margins are not collapsed, so fragment still has margins == 20. |
| // The fragment size 78 == body's margin 8 + child's height 50 + 20 |
| EXPECT_EQ(PhysicalSize(800, 78), fragment->Size()); |
| } |
| |
| // Verifies that 2 adjoining margins are not collapsed if there is padding or |
| // border that separates them. |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase4) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #container { |
| margin: 30px 0px; |
| width: 200px; |
| } |
| #child { |
| margin: 200px 0px; |
| height: 50px; |
| background-color: blue; |
| } |
| </style> |
| <div id='container'> |
| <div id='child'></div> |
| </div> |
| )HTML"); |
| |
| PhysicalOffset body_offset; |
| PhysicalOffset container_offset; |
| PhysicalOffset child_offset; |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| auto run_test = [&](const Length& container_padding_top) { |
| Element* container = GetDocument().getElementById("container"); |
| MutableStyleForElement(container)->SetPaddingTop(container_padding_top); |
| container->GetLayoutObject()->SetNeedsLayout(""); |
| std::tie(fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| ASSERT_EQ(1UL, fragment->Children().size()); |
| const auto* body_fragment = |
| To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| body_offset = fragment->Children()[0].Offset(); |
| const auto* container_fragment = |
| To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); |
| container_offset = body_fragment->Children()[0].Offset(); |
| ASSERT_EQ(1UL, container_fragment->Children().size()); |
| child_offset = container_fragment->Children()[0].Offset(); |
| }; |
| |
| // with padding |
| run_test(Length::Fixed(20)); |
| // 500 = child's height 50 + 2xmargin 400 + paddint-top 20 + |
| // container's margin 30 |
| EXPECT_EQ(PhysicalSize(800, 500), fragment->Size()); |
| // 30 = max(body's margin 8, container margin 30) |
| EXPECT_EQ(LayoutUnit(30), body_offset.top); |
| // 220 = container's padding top 20 + child's margin |
| EXPECT_EQ(LayoutUnit(220), child_offset.top); |
| |
| // without padding |
| run_test(Length::Fixed(0)); |
| // 450 = 2xmax(body's margin 8, container's margin 30, child's margin 200) + |
| // child's height 50 |
| EXPECT_EQ(PhysicalSize(800, 450), fragment->Size()); |
| // 200 = (body's margin 8, container's margin 30, child's margin 200) |
| EXPECT_EQ(LayoutUnit(200), body_offset.top); |
| // 0 = collapsed margins |
| EXPECT_EQ(LayoutUnit(0), child_offset.top); |
| } |
| |
| // Verifies that margins of 2 adjoining blocks with different writing modes |
| // get collapsed. |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase5) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #container { |
| margin-top: 10px; |
| writing-mode: vertical-lr; |
| } |
| #vertical { |
| margin-right: 90px; |
| background-color: red; |
| height: 70px; |
| width: 30px; |
| } |
| #horizontal { |
| background-color: blue; |
| margin-left: 100px; |
| writing-mode: horizontal-tb; |
| height: 60px; |
| width: 30px; |
| } |
| </style> |
| <div id='container'> |
| <div id='vertical'></div> |
| <div id='horizontal'></div> |
| </div> |
| )HTML"); |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| std::tie(fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| |
| // body |
| auto* body_fragment = |
| To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| PhysicalOffset body_offset = fragment->Children()[0].Offset(); |
| // 10 = std::max(body's margin 8, container's margin top) |
| int body_top_offset = 10; |
| EXPECT_THAT(body_offset.top, LayoutUnit(body_top_offset)); |
| int body_left_offset = 8; |
| EXPECT_THAT(body_offset.left, LayoutUnit(body_left_offset)); |
| |
| // height = 70. std::max(vertical height's 70, horizontal's height's 60) |
| ASSERT_EQ(PhysicalSize(784, 70), body_fragment->Size()); |
| ASSERT_EQ(1UL, body_fragment->Children().size()); |
| |
| // container |
| auto* container_fragment = |
| To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); |
| PhysicalOffset container_offset = body_fragment->Children()[0].Offset(); |
| // Container's margins are collapsed with body's fragment. |
| EXPECT_THAT(container_offset.top, LayoutUnit()); |
| EXPECT_THAT(container_offset.left, LayoutUnit()); |
| ASSERT_EQ(2UL, container_fragment->Children().size()); |
| |
| // vertical |
| PhysicalOffset vertical_offset = container_fragment->Children()[0].Offset(); |
| EXPECT_THAT(vertical_offset.top, LayoutUnit()); |
| EXPECT_THAT(vertical_offset.left, LayoutUnit()); |
| |
| // horizontal |
| PhysicalOffset orizontal_offset = container_fragment->Children()[1].Offset(); |
| EXPECT_THAT(orizontal_offset.top, LayoutUnit()); |
| // 130 = vertical's width 30 + |
| // std::max(vertical's margin right 90, horizontal's margin-left 100) |
| EXPECT_THAT(orizontal_offset.left, LayoutUnit(130)); |
| } |
| |
| // Verifies that margins collapsing logic works with Layout Inline. |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsWithText) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| body { |
| margin: 10px; |
| } |
| p { |
| margin: 20px; |
| } |
| </style> |
| <p>Some text</p> |
| )HTML"); |
| scoped_refptr<const NGPhysicalBoxFragment> html_fragment; |
| std::tie(html_fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| |
| const NGPhysicalBoxFragment* body_fragment = |
| To<NGPhysicalBoxFragment>(html_fragment->Children()[0].get()); |
| PhysicalOffset body_offset = html_fragment->Children()[0].Offset(); |
| // 20 = std::max(body's margin, p's margin) |
| EXPECT_THAT(body_offset, PhysicalOffset(10, 20)); |
| |
| PhysicalOffset p_offset = body_fragment->Children()[0].Offset(); |
| // Collapsed margins with result = 0. |
| EXPECT_THAT(p_offset, PhysicalOffset(20, 0)); |
| } |
| |
| // Verifies that the margin strut of a child with a different writing mode does |
| // not get used in the collapsing margins calculation. |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase6) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #div1 { |
| margin-bottom: 10px; |
| width: 10px; |
| height: 60px; |
| writing-mode: vertical-rl; |
| } |
| #div2 { margin-left: -20px; width: 10px; } |
| #div3 { margin-top: 40px; height: 60px; } |
| </style> |
| <div id="container" style="width:500px;height:500px"> |
| <div id="div1"> |
| <div id="div2">vertical</div> |
| </div> |
| <div id="div3"></div> |
| </div> |
| )HTML"); |
| const int kHeight = 60; |
| const int kMarginBottom = 10; |
| const int kMarginTop = 40; |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(500), LayoutUnit(500))); |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(container, space); |
| |
| ASSERT_EQ(fragment->Children().size(), 2UL); |
| |
| const NGPhysicalFragment* child1 = fragment->Children()[0].get(); |
| PhysicalOffset child1_offset = fragment->Children()[0].Offset(); |
| EXPECT_EQ(0, child1_offset.top); |
| EXPECT_EQ(kHeight, child1->Size().height); |
| |
| PhysicalOffset child2_offset = fragment->Children()[1].Offset(); |
| EXPECT_EQ(kHeight + std::max(kMarginBottom, kMarginTop), child2_offset.top); |
| } |
| |
| // Verifies that a child with clearance - which does nothing - still shifts its |
| // parent's offset. |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase7) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| body { |
| outline: solid purple 1px; |
| width: 200px; |
| } |
| #zero { |
| outline: solid red 1px; |
| margin-top: 10px; |
| } |
| #float { |
| background: yellow; |
| float: right; |
| width: 20px; |
| height: 20px; |
| } |
| #inflow { |
| background: blue; |
| clear: left; |
| height: 20px; |
| margin-top: 20px; |
| } |
| </style> |
| <div id="zero"> |
| <div id="float"></div> |
| </div> |
| <div id="inflow"></div> |
| )HTML"); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| std::tie(fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| |
| FragmentChildIterator iterator(fragment.get()); |
| |
| // body |
| PhysicalOffset offset; |
| const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(200, 20), child->Size()); |
| EXPECT_EQ(PhysicalOffset(8, 20), offset); |
| |
| // #zero |
| iterator.SetParent(child); |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(200, 0), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 0), offset); |
| |
| // #inflow |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(200, 20), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 0), offset); |
| } |
| |
| // An empty block level element (with margins collapsing through it) has |
| // non-trivial behaviour with margins collapsing. |
| TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsEmptyBlockWithClearance) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| body { |
| position: relative; |
| outline: solid purple 1px; |
| display: flow-root; |
| width: 200px; |
| } |
| #float { |
| background: orange; |
| float: left; |
| width: 50px; |
| height: 50px; |
| } |
| #zero { |
| outline: solid red 1px; |
| clear: left; |
| } |
| #abs { |
| background: cyan; |
| position: absolute; |
| width: 20px; |
| height: 20px; |
| } |
| #inflow { |
| background: green; |
| height: 20px; |
| } |
| </style> |
| <div id="float"></div> |
| <div id="zero-top"></div> |
| <div id="zero"> |
| <!-- This exists to produce complex margin struts. --> |
| <div id="zero-inner"></div> |
| </div> |
| <div id="abs"></div> |
| <div id="inflow"></div> |
| )HTML"); |
| |
| const LayoutNGBlockFlow* zero; |
| const LayoutNGBlockFlow* abs; |
| const LayoutNGBlockFlow* inflow; |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| auto run_test = [&](const Length& zero_top_margin_bottom, |
| const Length& zero_inner_margin_top, |
| const Length& zero_inner_margin_bottom, |
| const Length& zero_margin_bottom, |
| const Length& inflow_margin_top) { |
| // Set the style of the elements we care about. |
| Element* zero_top = GetDocument().getElementById("zero-top"); |
| MutableStyleForElement(zero_top)->SetMarginBottom(zero_top_margin_bottom); |
| zero_top->GetLayoutObject()->SetNeedsLayout(""); |
| Element* zero_inner = GetDocument().getElementById("zero-inner"); |
| scoped_refptr<ComputedStyle> zero_inner_style = |
| MutableStyleForElement(zero_inner); |
| zero_inner_style->SetMarginTop(zero_inner_margin_top); |
| zero_inner_style->SetMarginBottom(zero_inner_margin_bottom); |
| zero_inner->GetLayoutObject()->SetNeedsLayout(""); |
| Element* zero_element = GetDocument().getElementById("zero"); |
| MutableStyleForElement(zero_element)->SetMarginBottom(zero_margin_bottom); |
| zero_element->GetLayoutObject()->SetNeedsLayout(""); |
| |
| Element* inflow_element = GetDocument().getElementById("inflow"); |
| MutableStyleForElement(inflow_element)->SetMarginTop(inflow_margin_top); |
| inflow_element->GetLayoutObject()->SetNeedsLayout(""); |
| |
| UpdateAllLifecyclePhasesForTest(); |
| |
| LayoutNGBlockFlow* child; |
| // #float |
| child = To<LayoutNGBlockFlow>(GetLayoutObjectByElementId("float")); |
| EXPECT_EQ(LayoutSize(LayoutUnit(50), LayoutUnit(50)), child->Size()); |
| EXPECT_EQ(LayoutPoint(LayoutUnit(0), LayoutUnit(0)), child->Location()); |
| |
| // We need to manually test the position of #zero, #abs, #inflow. |
| zero = To<LayoutNGBlockFlow>(GetLayoutObjectByElementId("zero")); |
| inflow = To<LayoutNGBlockFlow>(GetLayoutObjectByElementId("inflow")); |
| abs = To<LayoutNGBlockFlow>(GetLayoutObjectByElementId("abs")); |
| }; |
| |
| // Base case of no margins. |
| run_test( |
| /* #zero-top margin-bottom */ Length::Fixed(0), |
| /* #zero-inner margin-top */ Length::Fixed(0), |
| /* #zero-inner margin-bottom */ Length::Fixed(0), |
| /* #zero margin-bottom */ Length::Fixed(0), |
| /* #inflow margin-top */ Length::Fixed(0)); |
| |
| // #zero, #abs, #inflow should all be positioned at the float. |
| EXPECT_EQ(LayoutUnit(50), zero->Location().Y()); |
| EXPECT_EQ(LayoutUnit(50), abs->Location().Y()); |
| EXPECT_EQ(LayoutUnit(50), inflow->Location().Y()); |
| |
| // A margin strut which resolves to -50 (-70 + 20) adjusts the position of |
| // #zero to the float clearance. |
| run_test( |
| /* #zero-top margin-bottom */ Length::Fixed(0), |
| /* #zero-inner margin-top */ Length::Fixed(-60), |
| /* #zero-inner margin-bottom */ Length::Fixed(20), |
| /* #zero margin-bottom */ Length::Fixed(-70), |
| /* #inflow margin-top */ Length::Fixed(50)); |
| |
| // #zero is placed at the float, the margin strut is at: |
| // 90 = (50 - (-60 + 20)). |
| EXPECT_EQ(LayoutUnit(50), zero->Location().Y()); |
| |
| // #abs estimates its position with the margin strut: |
| // 40 = (90 + (-70 + 20)). |
| EXPECT_EQ(LayoutUnit(40), abs->Location().Y()); |
| |
| // #inflow has similar behaviour to #abs, but includes its margin. |
| // 70 = (90 + (-70 + 50)) |
| EXPECT_EQ(LayoutUnit(70), inflow->Location().Y()); |
| |
| // A margin strut which resolves to 60 (-10 + 70) means that #zero doesn't |
| // get adjusted to clear the float, and we have normal behaviour. |
| // |
| // NOTE: This case below has wildly different results on different browsers, |
| // we may have to change the behaviour here in the future for web compat. |
| run_test( |
| /* #zero-top margin-bottom */ Length::Fixed(0), |
| /* #zero-inner margin-top */ Length::Fixed(70), |
| /* #zero-inner margin-bottom */ Length::Fixed(-10), |
| /* #zero margin-bottom */ Length::Fixed(-20), |
| /* #inflow margin-top */ Length::Fixed(80)); |
| |
| // #zero is placed at 60 (-10 + 70). |
| EXPECT_EQ(LayoutUnit(60), zero->Location().Y()); |
| |
| // #abs estimates its position with the margin strut: |
| // 50 = (0 + (-20 + 70)). |
| EXPECT_EQ(LayoutUnit(50), abs->Location().Y()); |
| |
| // #inflow has similar behaviour to #abs, but includes its margin. |
| // 60 = (0 + (-20 + 80)) |
| EXPECT_EQ(LayoutUnit(60), inflow->Location().Y()); |
| |
| // #zero-top produces a margin which needs to be ignored, as #zero is |
| // affected by clearance, it needs to have layout performed again, starting |
| // with an empty margin strut. |
| run_test( |
| /* #zero-top margin-bottom */ Length::Fixed(30), |
| /* #zero-inner margin-top */ Length::Fixed(20), |
| /* #zero-inner margin-bottom */ Length::Fixed(-10), |
| /* #zero margin-bottom */ Length::Fixed(0), |
| /* #inflow margin-top */ Length::Fixed(25)); |
| |
| // #zero is placed at the float, the margin strut is at: |
| // 40 = (50 - (-10 + 20)). |
| EXPECT_EQ(LayoutUnit(50), zero->Location().Y()); |
| |
| // The margin strut is now disjoint, this is placed at: |
| // 55 = (40 + (-10 + 25)) |
| EXPECT_EQ(LayoutUnit(55), inflow->Location().Y()); |
| } |
| |
| // Tests that when auto margins are applied to a new formatting context, they |
| // are applied within the layout opportunity. |
| TEST_F(NGBlockLayoutAlgorithmTest, NewFormattingContextAutoMargins) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { width: 200px; direction: rtl; display: flow-root; } |
| #float { width: 100px; height: 60px; background: hotpink; float: left; } |
| #newfc { direction: rtl; width: 50px; height: 20px; background: green; overflow: hidden; } |
| </style> |
| <div id="container"> |
| <div id="float"></div> |
| <div id="newfc" style="margin-right: auto;"></div> |
| <div id="newfc" style="margin-left: auto; margin-right: auto;"></div> |
| <div id="newfc" style="margin-left: auto;"></div> |
| </div> |
| )HTML"); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| std::tie(fragment, std::ignore) = |
| RunBlockLayoutAlgorithmForElement(GetElementById("container")); |
| |
| String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::. |
| offset:unplaced size:200x60 |
| offset:0,0 size:100x60 |
| offset:100,0 size:50x20 |
| offset:125,20 size:50x20 |
| offset:150,40 size:50x20 |
| )DUMP"; |
| EXPECT_EQ(expectation, DumpFragmentTree(fragment.get())); |
| } |
| |
| // Verifies that a box's size includes its borders and padding, and that |
| // children are positioned inside the content box. |
| TEST_F(NGBlockLayoutAlgorithmTest, BorderAndPadding) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #div1 { |
| width: 100px; |
| height: 100px; |
| border-style: solid; |
| border-width: 1px 2px 3px 4px; |
| padding: 5px 6px 7px 8px; |
| } |
| </style> |
| <div id="container"> |
| <div id="div1"> |
| <div id="div2"></div> |
| </div> |
| </div> |
| )HTML"); |
| const int kWidth = 100; |
| const int kHeight = 100; |
| const int kBorderTop = 1; |
| const int kBorderRight = 2; |
| const int kBorderBottom = 3; |
| const int kBorderLeft = 4; |
| const int kPaddingTop = 5; |
| const int kPaddingRight = 6; |
| const int kPaddingBottom = 7; |
| const int kPaddingLeft = 8; |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize)); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(container, space); |
| |
| ASSERT_EQ(fragment->Children().size(), 1UL); |
| |
| // div1 |
| const NGPhysicalFragment* child = fragment->Children()[0].get(); |
| EXPECT_EQ(kBorderLeft + kPaddingLeft + kWidth + kPaddingRight + kBorderRight, |
| child->Size().width); |
| EXPECT_EQ(kBorderTop + kPaddingTop + kHeight + kPaddingBottom + kBorderBottom, |
| child->Size().height); |
| |
| ASSERT_TRUE(child->IsBox()); |
| ASSERT_EQ(static_cast<const NGPhysicalBoxFragment*>(child)->Children().size(), |
| 1UL); |
| |
| PhysicalOffset div2_offset = |
| static_cast<const NGPhysicalBoxFragment*>(child)->Children()[0].Offset(); |
| EXPECT_EQ(kBorderTop + kPaddingTop, div2_offset.top); |
| EXPECT_EQ(kBorderLeft + kPaddingLeft, div2_offset.left); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, PercentageResolutionSize) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="container" style="width: 30px; padding-left: 10px"> |
| <div id="div1" style="width: 40%"></div> |
| </div> |
| )HTML"); |
| const int kPaddingLeft = 10; |
| const int kWidth = 30; |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), kIndefiniteSize)); |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(container, space); |
| |
| EXPECT_EQ(LayoutUnit(kWidth + kPaddingLeft), fragment->Size().width); |
| EXPECT_EQ(NGPhysicalFragment::kFragmentBox, fragment->Type()); |
| ASSERT_EQ(fragment->Children().size(), 1UL); |
| |
| const NGPhysicalFragment* child = fragment->Children()[0].get(); |
| EXPECT_EQ(LayoutUnit(12), child->Size().width); |
| } |
| |
| // A very simple auto margin case. We rely on the tests in ng_length_utils_test |
| // for the more complex cases; just make sure we handle auto at all here. |
| TEST_F(NGBlockLayoutAlgorithmTest, AutoMargin) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #first { width: 10px; margin-left: auto; margin-right: auto; } |
| </style> |
| <div id="container" style="width: 30px; padding-left: 10px"> |
| <div id="first"> |
| </div> |
| </div> |
| )HTML"); |
| const int kPaddingLeft = 10; |
| const int kWidth = 30; |
| const int kChildWidth = 10; |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), kIndefiniteSize)); |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(container, space); |
| |
| EXPECT_EQ(LayoutUnit(kWidth + kPaddingLeft), fragment->Size().width); |
| EXPECT_EQ(NGPhysicalFragment::kFragmentBox, fragment->Type()); |
| ASSERT_EQ(1UL, fragment->Children().size()); |
| |
| const NGPhysicalFragment* child = fragment->Children()[0].get(); |
| PhysicalOffset child_offset = fragment->Children()[0].Offset(); |
| EXPECT_EQ(LayoutUnit(kChildWidth), child->Size().width); |
| EXPECT_EQ(LayoutUnit(kPaddingLeft + 10), child_offset.left); |
| EXPECT_EQ(LayoutUnit(0), child_offset.top); |
| } |
| |
| // Verifies that floats can be correctly positioned if they are inside of nested |
| // empty blocks. |
| TEST_F(NGBlockLayoutAlgorithmTest, PositionFloatInsideEmptyBlocks) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #container { |
| height: 300px; |
| width: 300px; |
| outline: blue solid; |
| } |
| #empty1 { |
| margin: 20px; |
| padding: 0 20px; |
| } |
| #empty2 { |
| margin: 15px; |
| padding: 0 15px; |
| } |
| #left-float { |
| float: left; |
| height: 5px; |
| width: 5px; |
| padding: 10px; |
| margin: 10px; |
| background-color: green; |
| } |
| #right-float { |
| float: right; |
| height: 15px; |
| width: 15px; |
| margin: 15px 10px; |
| background-color: red; |
| } |
| </style> |
| <div id='container'> |
| <div id='empty1'> |
| <div id='empty2'> |
| <div id='left-float'></div> |
| <div id='right-float'></div> |
| </div> |
| </div> |
| </div> |
| )HTML"); |
| |
| // ** Run LayoutNG algorithm ** |
| NGConstraintSpace space; |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| std::tie(fragment, space) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| |
| const auto* body_fragment = |
| To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| PhysicalOffset body_offset = fragment->Children()[0].Offset(); |
| FragmentChildIterator iterator(body_fragment); |
| // 20 = std::max(empty1's margin, empty2's margin, body's margin) |
| int body_top_offset = 20; |
| EXPECT_THAT(body_offset.top, LayoutUnit(body_top_offset)); |
| ASSERT_EQ(1UL, body_fragment->Children().size()); |
| |
| const auto* container_fragment = iterator.NextChild(); |
| ASSERT_EQ(1UL, container_fragment->Children().size()); |
| |
| iterator.SetParent(container_fragment); |
| PhysicalOffset offset; |
| const auto* empty1_fragment = iterator.NextChild(&offset); |
| // 0, vertical margins got collapsed |
| EXPECT_THAT(offset.top, LayoutUnit()); |
| // 20 empty1's margin |
| EXPECT_THAT(offset.left, LayoutUnit(20)); |
| ASSERT_EQ(empty1_fragment->Children().size(), 1UL); |
| |
| iterator.SetParent(empty1_fragment); |
| const auto* empty2_fragment = iterator.NextChild(&offset); |
| // 0, vertical margins got collapsed |
| EXPECT_THAT(LayoutUnit(), offset.top); |
| // 35 = empty1's padding(20) + empty2's padding(15) |
| EXPECT_THAT(offset.left, LayoutUnit(35)); |
| |
| offset = empty2_fragment->Children()[0].offset; |
| // inline 25 = left float's margin(10) + empty2's padding(15). |
| // block 10 = left float's margin |
| EXPECT_THAT(offset, PhysicalOffset(25, 10)); |
| |
| offset = empty2_fragment->Children()[1].offset; |
| // inline offset 140 = right float's margin(10) + right float offset(140) |
| // block offset 15 = right float's margin |
| LayoutUnit right_float_offset = LayoutUnit(140); |
| EXPECT_THAT(offset, PhysicalOffset(LayoutUnit(10) + right_float_offset, |
| LayoutUnit(15))); |
| |
| // ** Verify layout tree ** |
| Element* left_float = GetDocument().getElementById("left-float"); |
| // 88 = body's margin(8) + |
| // empty1's padding and margin + empty2's padding and margins + float's |
| // padding |
| EXPECT_THAT(left_float->OffsetLeft(), 88); |
| // 30 = body_top_offset(collapsed margins result) + float's padding |
| EXPECT_THAT(left_float->OffsetTop(), body_top_offset + 10); |
| } |
| |
| // Verifies that left/right floating and regular blocks can be positioned |
| // correctly by the algorithm. |
| TEST_F(NGBlockLayoutAlgorithmTest, PositionFloatFragments) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #container { |
| height: 200px; |
| width: 200px; |
| } |
| #left-float { |
| background-color: red; |
| float: left; |
| height: 30px; |
| width: 30px; |
| } |
| #left-wide-float { |
| background-color: greenyellow; |
| float: left; |
| height: 30px; |
| width: 180px; |
| } |
| #regular { |
| width: 40px; |
| height: 40px; |
| background-color: green; |
| } |
| #right-float { |
| background-color: cyan; |
| float: right; |
| width: 50px; |
| height: 50px; |
| } |
| #left-float-with-margin { |
| background-color: black; |
| float: left; |
| height: 120px; |
| margin: 10px; |
| width: 120px; |
| } |
| </style> |
| <div id='container'> |
| <div id='left-float'></div> |
| <div id='left-wide-float'></div> |
| <div id='regular'></div> |
| <div id='right-float'></div> |
| <div id='left-float-with-margin'></div> |
| </div> |
| )HTML"); |
| |
| // ** Run LayoutNG algorithm ** |
| NGConstraintSpace space; |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| std::tie(fragment, space) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| |
| // ** Verify LayoutNG fragments and the list of positioned floats ** |
| ASSERT_EQ(1UL, fragment->Children().size()); |
| const auto* body_fragment = |
| To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| PhysicalOffset body_offset = fragment->Children()[0].Offset(); |
| EXPECT_THAT(LayoutUnit(8), body_offset.top); |
| |
| FragmentChildIterator iterator(body_fragment); |
| const auto* container_fragment = iterator.NextChild(); |
| ASSERT_EQ(5UL, container_fragment->Children().size()); |
| |
| // ** Verify layout tree ** |
| Element* left_float = GetDocument().getElementById("left-float"); |
| // 8 = body's margin-top |
| EXPECT_EQ(8, left_float->OffsetTop()); |
| |
| iterator.SetParent(container_fragment); |
| PhysicalOffset offset; |
| iterator.NextChild(&offset); |
| EXPECT_THAT(LayoutUnit(), offset.top); |
| |
| Element* left_wide_float = GetDocument().getElementById("left-wide-float"); |
| // left-wide-float is positioned right below left-float as it's too wide. |
| // 38 = left_float_block_offset 8 + |
| // left-float's height 30 |
| EXPECT_EQ(38, left_wide_float->OffsetTop()); |
| |
| iterator.NextChild(&offset); |
| // 30 = left-float's height. |
| EXPECT_THAT(LayoutUnit(30), offset.top); |
| |
| Element* regular = GetDocument().getElementById("regular"); |
| // regular_block_offset = body's margin-top 8 |
| EXPECT_EQ(8, regular->OffsetTop()); |
| |
| iterator.NextChild(&offset); |
| EXPECT_THAT(LayoutUnit(), offset.top); |
| |
| Element* right_float = GetDocument().getElementById("right-float"); |
| // 158 = body's margin-left 8 + container's width 200 - right_float's width 50 |
| // it's positioned right after our left_wide_float |
| // 68 = left_wide_float_block_offset 38 + left-wide-float's height 30 |
| EXPECT_EQ(158, right_float->OffsetLeft()); |
| EXPECT_EQ(68, right_float->OffsetTop()); |
| |
| iterator.NextChild(&offset); |
| // 60 = right_float_block_offset(68) - body's margin(8) |
| EXPECT_THAT(LayoutUnit(60), offset.top); |
| // 150 = right_float_inline_offset(158) - body's margin(8) |
| EXPECT_THAT(LayoutUnit(150), offset.left); |
| |
| Element* left_float_with_margin = |
| GetDocument().getElementById("left-float-with-margin"); |
| // 18 = body's margin(8) + left-float-with-margin's margin(10) |
| EXPECT_EQ(18, left_float_with_margin->OffsetLeft()); |
| // 78 = left_wide_float_block_offset 38 + left-wide-float's height 30 + |
| // left-float-with-margin's margin(10) |
| EXPECT_EQ(78, left_float_with_margin->OffsetTop()); |
| |
| iterator.NextChild(&offset); |
| // 70 = left_float_with_margin_block_offset(78) - body's margin(8) |
| EXPECT_THAT(LayoutUnit(70), offset.top); |
| // 10 = left_float_with_margin_inline_offset(18) - body's margin(8) |
| EXPECT_THAT(LayoutUnit(10), offset.left); |
| } |
| |
| // Verifies that NG block layout algorithm respects "clear" CSS property. |
| TEST_F(NGBlockLayoutAlgorithmTest, PositionFragmentsWithClear) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #container { |
| height: 200px; |
| width: 200px; |
| } |
| #float-left { |
| background-color: red; |
| float: left; |
| height: 30px; |
| width: 30px; |
| } |
| #float-right { |
| background-color: blue; |
| float: right; |
| height: 170px; |
| width: 40px; |
| } |
| #clearance { |
| background-color: yellow; |
| height: 60px; |
| width: 60px; |
| margin: 20px; |
| } |
| #block { |
| margin: 40px; |
| background-color: black; |
| height: 60px; |
| width: 60px; |
| } |
| #adjoining-clearance { |
| background-color: green; |
| clear: left; |
| height: 20px; |
| width: 20px; |
| margin: 30px; |
| } |
| </style> |
| <div id='container'> |
| <div id='float-left'></div> |
| <div id='float-right'></div> |
| <div id='clearance'></div> |
| <div id='block'></div> |
| <div id='adjoining-clearance'></div> |
| </div> |
| )HTML"); |
| |
| PhysicalOffset clerance_offset; |
| PhysicalOffset body_offset; |
| PhysicalOffset container_offset; |
| PhysicalOffset block_offset; |
| PhysicalOffset adjoining_clearance_offset; |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| auto run_with_clearance = [&](EClear clear_value) { |
| Element* el_with_clear = GetDocument().getElementById("clearance"); |
| MutableStyleForElement(el_with_clear)->SetClear(clear_value); |
| el_with_clear->GetLayoutObject()->SetNeedsLayout(""); |
| std::tie(fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| ASSERT_EQ(1UL, fragment->Children().size()); |
| const NGPhysicalBoxFragment* body_fragment = |
| To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| body_offset = fragment->Children()[0].Offset(); |
| const NGPhysicalBoxFragment* container_fragment = |
| To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); |
| ASSERT_EQ(5UL, container_fragment->Children().size()); |
| container_offset = body_fragment->Children()[0].Offset(); |
| clerance_offset = container_fragment->Children()[2].Offset(); |
| block_offset = container_fragment->Children()[3].Offset(); |
| adjoining_clearance_offset = container_fragment->Children()[4].Offset(); |
| }; |
| |
| // clear: none |
| run_with_clearance(EClear::kNone); |
| // 20 = std::max(body's margin 8, clearance's margins 20) |
| EXPECT_EQ(LayoutUnit(20), body_offset.top); |
| EXPECT_EQ(LayoutUnit(0), container_offset.top); |
| // 0 = collapsed margins |
| EXPECT_EQ(LayoutUnit(0), clerance_offset.top); |
| // 100 = clearance's height 60 + |
| // std::max(clearance's margins 20, block's margins 40) |
| EXPECT_EQ(LayoutUnit(100), block_offset.top); |
| // 200 = 100 + block's height 60 + max(adjoining_clearance's margins 30, |
| // block's margins 40) |
| EXPECT_EQ(LayoutUnit(200), adjoining_clearance_offset.top); |
| |
| // clear: right |
| run_with_clearance(EClear::kRight); |
| // 8 = body's margin. This doesn't collapse its margins with 'clearance' block |
| // as it's not an adjoining block to body. |
| EXPECT_EQ(LayoutUnit(8), body_offset.top); |
| EXPECT_EQ(LayoutUnit(0), container_offset.top); |
| // 170 = float-right's height |
| EXPECT_EQ(LayoutUnit(170), clerance_offset.top); |
| // 270 = float-right's height + clearance's height 60 + |
| // max(clearance's margin 20, block margin 40) |
| EXPECT_EQ(LayoutUnit(270), block_offset.top); |
| // 370 = block's offset 270 + block's height 60 + |
| // std::max(block's margin 40, adjoining_clearance's margin 30) |
| EXPECT_EQ(LayoutUnit(370), adjoining_clearance_offset.top); |
| |
| // clear: left |
| run_with_clearance(EClear::kLeft); |
| // 8 = body's margin. This doesn't collapse its margins with 'clearance' block |
| // as it's not an adjoining block to body. |
| EXPECT_EQ(LayoutUnit(8), body_offset.top); |
| EXPECT_EQ(LayoutUnit(0), container_offset.top); |
| // 30 = float_left's height |
| EXPECT_EQ(LayoutUnit(30), clerance_offset.top); |
| // 130 = float_left's height + clearance's height 60 + |
| // max(clearance's margin 20, block margin 40) |
| EXPECT_EQ(LayoutUnit(130), block_offset.top); |
| // 230 = block's offset 130 + block's height 60 + |
| // std::max(block's margin 40, adjoining_clearance's margin 30) |
| EXPECT_EQ(LayoutUnit(230), adjoining_clearance_offset.top); |
| |
| // clear: both |
| // same as clear: right |
| run_with_clearance(EClear::kBoth); |
| EXPECT_EQ(LayoutUnit(8), body_offset.top); |
| EXPECT_EQ(LayoutUnit(0), container_offset.top); |
| EXPECT_EQ(LayoutUnit(170), clerance_offset.top); |
| EXPECT_EQ(LayoutUnit(270), block_offset.top); |
| EXPECT_EQ(LayoutUnit(370), adjoining_clearance_offset.top); |
| } |
| |
| // Verifies that we compute the right min and max-content size. |
| TEST_F(NGBlockLayoutAlgorithmTest, ComputeMinMaxContent) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="container"> |
| <div id="first-child" style="width: 20px"></div> |
| <div id="second-child" style="width: 30px"></div> |
| </div> |
| )HTML"); |
| |
| const int kSecondChildWidth = 30; |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| MinMaxSizes sizes = RunComputeMinMaxSizes(container); |
| EXPECT_EQ(kSecondChildWidth, sizes.min_size); |
| EXPECT_EQ(kSecondChildWidth, sizes.max_size); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, ComputeMinMaxContentFloats) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #f1 { float: left; width: 20px; } |
| #f2 { float: left; width: 30px; } |
| #f3 { float: right; width: 40px; } |
| </style> |
| <div id="container"> |
| <div id="f1"></div> |
| <div id="f2"></div> |
| <div id="f3"></div> |
| </div> |
| )HTML"); |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| MinMaxSizes sizes = RunComputeMinMaxSizes(container); |
| EXPECT_EQ(LayoutUnit(40), sizes.min_size); |
| EXPECT_EQ(LayoutUnit(90), sizes.max_size); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, ComputeMinMaxContentFloatsClearance) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #f1 { float: left; width: 20px; } |
| #f2 { float: left; width: 30px; } |
| #f3 { float: right; width: 40px; clear: left; } |
| </style> |
| <div id="container"> |
| <div id="f1"></div> |
| <div id="f2"></div> |
| <div id="f3"></div> |
| </div> |
| )HTML"); |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| MinMaxSizes sizes = RunComputeMinMaxSizes(container); |
| EXPECT_EQ(LayoutUnit(40), sizes.min_size); |
| EXPECT_EQ(LayoutUnit(50), sizes.max_size); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, ComputeMinMaxContentNewFormattingContext) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #f1 { float: left; width: 20px; } |
| #f2 { float: left; width: 30px; } |
| #fc { display: flex; width: 40px; margin-left: 60px; } |
| </style> |
| <div id="container"> |
| <div id="f1"></div> |
| <div id="f2"></div> |
| <div id="fc"></div> |
| </div> |
| )HTML"); |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| MinMaxSizes sizes = RunComputeMinMaxSizes(container); |
| EXPECT_EQ(LayoutUnit(100), sizes.min_size); |
| EXPECT_EQ(LayoutUnit(100), sizes.max_size); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, |
| ComputeMinMaxContentNewFormattingContextNegativeMargins) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #f1 { float: left; width: 20px; } |
| #f2 { float: left; width: 30px; } |
| #fc { display: flex; width: 40px; margin-left: -20px; } |
| </style> |
| <div id="container"> |
| <div id="f1"></div> |
| <div id="f2"></div> |
| <div id="fc"></div> |
| </div> |
| )HTML"); |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| MinMaxSizes sizes = RunComputeMinMaxSizes(container); |
| EXPECT_EQ(LayoutUnit(30), sizes.min_size); |
| EXPECT_EQ(LayoutUnit(70), sizes.max_size); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, |
| ComputeMinMaxContentSingleNewFormattingContextNegativeMargins) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #fc { display: flex; width: 20px; margin-left: -40px; } |
| </style> |
| <div id="container"> |
| <div id="fc"></div> |
| </div> |
| )HTML"); |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| MinMaxSizes sizes = RunComputeMinMaxSizes(container); |
| EXPECT_EQ(LayoutUnit(), sizes.min_size); |
| EXPECT_EQ(LayoutUnit(), sizes.max_size); |
| } |
| |
| // Tests that we correctly handle shrink-to-fit |
| TEST_F(NGBlockLayoutAlgorithmTest, ShrinkToFit) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="container"> |
| <div id="first-child" style="width: 20px"></div> |
| <div id="second-child" style="width: 30px"></div> |
| </div> |
| )HTML"); |
| const int kWidthChild2 = 30; |
| |
| NGBlockNode container(GetLayoutBoxByElementId("container")); |
| |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(100), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ false); |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(container, space); |
| |
| EXPECT_EQ(LayoutUnit(kWidthChild2), fragment->Size().width); |
| } |
| |
| // Verifies that we position empty blocks and floats correctly inside of the |
| // block that establishes new BFC. |
| TEST_F(NGBlockLayoutAlgorithmTest, PositionEmptyBlocksInNewBfc) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #container { |
| overflow: hidden; |
| } |
| #empty-block1 { |
| margin: 8px; |
| } |
| #left-float { |
| float: left; |
| background: red; |
| height: 20px; |
| width: 10px; |
| margin: 15px; |
| } |
| #empty-block2 { |
| margin-top: 50px; |
| } |
| </style> |
| <div id="container"> |
| <div id="left-float"></div> |
| <div id="empty-block1"></div> |
| <div id="empty-block2"></div> |
| </div> |
| )HTML"); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> html_fragment; |
| std::tie(html_fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| auto* body_fragment = |
| To<NGPhysicalBoxFragment>(html_fragment->Children()[0].get()); |
| auto* container_fragment = |
| To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); |
| PhysicalOffset empty_block1_offset = |
| container_fragment->Children()[1].Offset(); |
| // empty-block1's margin == 8 |
| EXPECT_THAT(empty_block1_offset, PhysicalOffset(8, 8)); |
| |
| PhysicalOffset empty_block2_offset = |
| container_fragment->Children()[2].Offset(); |
| // empty-block2's margin == 50 |
| EXPECT_THAT(empty_block2_offset, PhysicalOffset(0, 50)); |
| } |
| |
| // Verifies that we can correctly position blocks with clearance and |
| // intruding floats. |
| TEST_F(NGBlockLayoutAlgorithmTest, |
| PositionBlocksWithClearanceAndIntrudingFloats) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| body { margin: 80px; } |
| #left-float { |
| background: green; |
| float: left; |
| width: 50px; |
| height: 50px; |
| } |
| #right-float { |
| background: red; |
| float: right; |
| margin: 0 80px 0 10px; |
| width: 50px; |
| height: 80px; |
| } |
| #block1 { |
| outline: purple solid; |
| height: 30px; |
| margin: 130px 0 20px 0; |
| } |
| #zero { |
| margin-top: 30px; |
| } |
| #container-clear { |
| clear: left; |
| outline: orange solid; |
| } |
| #clears-right { |
| clear: right; |
| height: 20px; |
| background: lightblue; |
| } |
| </style> |
| |
| <div id="left-float"></div> |
| <div id="right-float"></div> |
| <div id="block1"></div> |
| <div id="container-clear"> |
| <div id="zero"></div> |
| <div id="clears-right"></div> |
| </div> |
| )HTML"); |
| |
| // Run LayoutNG algorithm. |
| scoped_refptr<const NGPhysicalBoxFragment> html_fragment; |
| std::tie(html_fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| auto* body_fragment = |
| To<NGPhysicalBoxFragment>(html_fragment->Children()[0].get()); |
| ASSERT_EQ(4UL, body_fragment->Children().size()); |
| |
| // Verify #container-clear block |
| auto* container_clear_fragment = |
| To<NGPhysicalBoxFragment>(body_fragment->Children()[3].get()); |
| PhysicalOffset container_clear_offset = body_fragment->Children()[3].Offset(); |
| // 60 = block1's height 30 + std::max(block1's margin 20, zero's margin 30) |
| EXPECT_THAT(PhysicalOffset(0, 60), container_clear_offset); |
| Element* container_clear = GetDocument().getElementById("container-clear"); |
| // 190 = block1's margin 130 + block1's height 30 + |
| // std::max(block1's margin 20, zero's margin 30) |
| EXPECT_THAT(container_clear->OffsetTop(), 190); |
| |
| // Verify #clears-right block |
| ASSERT_EQ(2UL, container_clear_fragment->Children().size()); |
| PhysicalOffset clears_right_offset = |
| container_clear_fragment->Children()[1].Offset(); |
| // 20 = right-float's block end offset (130 + 80) - |
| // container_clear->offsetTop() 190 |
| EXPECT_THAT(PhysicalOffset(0, 20), clears_right_offset); |
| } |
| |
| // Tests that a block won't fragment if it doesn't reach the fragmentation line. |
| TEST_F(NGBlockLayoutAlgorithmTest, NoFragmentation) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 150px; |
| height: 200px; |
| } |
| </style> |
| <div id='container'></div> |
| )HTML"); |
| |
| LayoutUnit kFragmentainerSpaceAvailable(200); |
| |
| NGBlockNode node(GetLayoutBoxByElementId("container")); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| // We should only have one 150x200 fragment with no fragmentation. |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(150, 200), fragment->Size()); |
| ASSERT_FALSE(fragment->BreakToken()); |
| } |
| |
| // Tests that a block will fragment if it reaches the fragmentation line. |
| TEST_F(NGBlockLayoutAlgorithmTest, SimpleFragmentation) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 150px; |
| height: 300px; |
| } |
| </style> |
| <div id='container'></div> |
| )HTML"); |
| |
| LayoutUnit kFragmentainerSpaceAvailable(200); |
| |
| NGBlockNode node(GetLayoutBoxByElementId("container")); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(150, 200), fragment->Size()); |
| EXPECT_TRUE(fragment->BreakToken()); |
| |
| fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); |
| EXPECT_EQ(PhysicalSize(150, 100), fragment->Size()); |
| ASSERT_FALSE(fragment->BreakToken()); |
| } |
| |
| // Tests that children inside the same block formatting context fragment when |
| // reaching a fragmentation line. |
| TEST_F(NGBlockLayoutAlgorithmTest, InnerChildrenFragmentation) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 150px; |
| padding-top: 20px; |
| } |
| #child1 { |
| height: 200px; |
| margin-bottom: 20px; |
| } |
| #child2 { |
| height: 100px; |
| margin-top: 20px; |
| } |
| </style> |
| <div id='container'> |
| <div id='child1'></div> |
| <div id='child2'></div> |
| </div> |
| )HTML"); |
| |
| LayoutUnit kFragmentainerSpaceAvailable(200); |
| |
| NGBlockNode node(GetLayoutBoxByElementId("container")); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(150, 200), fragment->Size()); |
| EXPECT_TRUE(fragment->BreakToken()); |
| |
| FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); |
| PhysicalOffset offset; |
| const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 180), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 20), offset); |
| |
| EXPECT_FALSE(iterator.NextChild()); |
| |
| fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); |
| EXPECT_EQ(PhysicalSize(150, 140), fragment->Size()); |
| ASSERT_FALSE(fragment->BreakToken()); |
| |
| iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 20), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 0), offset); |
| |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 100), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 40), offset); |
| |
| EXPECT_FALSE(iterator.NextChild()); |
| } |
| |
| // Tests that children which establish new formatting contexts fragment |
| // correctly. |
| TEST_F(NGBlockLayoutAlgorithmTest, |
| InnerFormattingContextChildrenFragmentation) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 150px; |
| padding-top: 20px; |
| } |
| #child1 { |
| height: 200px; |
| margin-bottom: 20px; |
| contain: paint; |
| } |
| #child2 { |
| height: 100px; |
| margin-top: 20px; |
| contain: paint; |
| } |
| </style> |
| <div id='container'> |
| <div id='child1'></div> |
| <div id='child2'></div> |
| </div> |
| )HTML"); |
| |
| LayoutUnit kFragmentainerSpaceAvailable(200); |
| |
| NGBlockNode node(GetLayoutBoxByElementId("container")); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(150, 200), fragment->Size()); |
| EXPECT_TRUE(fragment->BreakToken()); |
| |
| FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); |
| PhysicalOffset offset; |
| const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 180), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 20), offset); |
| |
| EXPECT_FALSE(iterator.NextChild()); |
| |
| fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); |
| EXPECT_EQ(PhysicalSize(150, 140), fragment->Size()); |
| ASSERT_FALSE(fragment->BreakToken()); |
| |
| iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 20), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 0), offset); |
| |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 100), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 40), offset); |
| |
| EXPECT_FALSE(iterator.NextChild()); |
| } |
| |
| // Tests that children inside a block container will fragment if the container |
| // doesn't reach the fragmentation line. |
| TEST_F(NGBlockLayoutAlgorithmTest, InnerChildrenFragmentationSmallHeight) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 150px; |
| padding-top: 20px; |
| height: 50px; |
| } |
| #child1 { |
| height: 200px; |
| margin-bottom: 20px; |
| } |
| #child2 { |
| height: 100px; |
| margin-top: 20px; |
| } |
| </style> |
| <div id='container'> |
| <div id='child1'></div> |
| <div id='child2'></div> |
| </div> |
| )HTML"); |
| |
| LayoutUnit kFragmentainerSpaceAvailable(200); |
| |
| NGBlockNode node(GetLayoutBoxByElementId("container")); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(150, 70), fragment->Size()); |
| EXPECT_TRUE(fragment->BreakToken()); |
| |
| FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); |
| PhysicalOffset offset; |
| const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 180), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 20), offset); |
| |
| EXPECT_FALSE(iterator.NextChild()); |
| |
| fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); |
| EXPECT_EQ(PhysicalSize(150, 0), fragment->Size()); |
| ASSERT_FALSE(fragment->BreakToken()); |
| |
| iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 20), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 0), offset); |
| |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 100), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 40), offset); |
| |
| EXPECT_FALSE(iterator.NextChild()); |
| } |
| |
| // Tests that float children fragment correctly inside a parallel flow. |
| TEST_F(NGBlockLayoutAlgorithmTest, DISABLED_FloatFragmentationParallelFlows) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 150px; |
| height: 50px; |
| display: flow-root; |
| } |
| #float1 { |
| width: 50px; |
| height: 200px; |
| float: left; |
| } |
| #float2 { |
| width: 75px; |
| height: 250px; |
| float: right; |
| margin: 10px; |
| } |
| </style> |
| <div id='container'> |
| <div id='float1'></div> |
| <div id='float2'></div> |
| </div> |
| )HTML"); |
| |
| LayoutUnit kFragmentainerSpaceAvailable(150); |
| |
| NGBlockNode node( |
| To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(150, 50), fragment->Size()); |
| EXPECT_TRUE(fragment->BreakToken()); |
| |
| FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); |
| |
| // First fragment of float1. |
| PhysicalOffset offset; |
| const auto* child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(50, 150), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 0), offset); |
| |
| // First fragment of float2. |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(75, 150), child->Size()); |
| EXPECT_EQ(PhysicalOffset(65, 10), offset); |
| |
| space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); |
| EXPECT_EQ(PhysicalSize(150, 0), fragment->Size()); |
| ASSERT_FALSE(fragment->BreakToken()); |
| |
| iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); |
| |
| // Second fragment of float1. |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(50, 50), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 0), offset); |
| |
| // Second fragment of float2. |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(75, 100), child->Size()); |
| EXPECT_EQ(PhysicalOffset(65, 0), offset); |
| } |
| |
| // Tests that float children don't fragment if they aren't in the same writing |
| // mode as their parent. |
| TEST_F(NGBlockLayoutAlgorithmTest, FloatFragmentationOrthogonalFlows) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 150px; |
| height: 60px; |
| overflow: hidden; |
| } |
| #float1 { |
| width: 100px; |
| height: 50px; |
| float: left; |
| } |
| #float2 { |
| width: 60px; |
| height: 200px; |
| float: right; |
| writing-mode: vertical-rl; |
| } |
| </style> |
| <div id='container'> |
| <div id='float1'></div> |
| <div id='float2'></div> |
| </div> |
| )HTML"); |
| |
| LayoutUnit kFragmentainerSpaceAvailable(150); |
| |
| NGBlockNode node( |
| To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| /* is_new_formatting_context */ true, kFragmentainerSpaceAvailable); |
| |
| AdvanceToLayoutPhase(); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(150, 60), fragment->Size()); |
| ASSERT_FALSE(fragment->BreakToken()); |
| |
| const auto* float2 = fragment->Children()[1].fragment; |
| |
| // float2 should only have one fragment. |
| EXPECT_EQ(PhysicalSize(60, 200), float2->Size()); |
| ASSERT_TRUE(float2->IsBox()); |
| const NGBreakToken* break_token = |
| To<NGPhysicalBoxFragment>(float2)->BreakToken(); |
| EXPECT_FALSE(break_token); |
| } |
| |
| // Tests that a float child inside a zero height block fragments correctly. |
| TEST_F(NGBlockLayoutAlgorithmTest, DISABLED_FloatFragmentationZeroHeight) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 150px; |
| height: 50px; |
| display: flow-root; |
| } |
| #float { |
| width: 75px; |
| height: 200px; |
| float: left; |
| margin: 10px; |
| } |
| </style> |
| <div id='container'> |
| <div id='zero'> |
| <div id='float'></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutUnit kFragmentainerSpaceAvailable(150); |
| |
| NGBlockNode node( |
| To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(150, 50), fragment->Size()); |
| EXPECT_TRUE(fragment->BreakToken()); |
| |
| FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); |
| const auto* child = iterator.NextChild(); |
| |
| // First fragment of float. |
| iterator.SetParent(child); |
| PhysicalOffset offset; |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(75, 150), child->Size()); |
| EXPECT_EQ(PhysicalOffset(10, 10), offset); |
| |
| space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| node.CreatesNewFormattingContext(), kFragmentainerSpaceAvailable); |
| |
| fragment = RunBlockLayoutAlgorithm(node, space, fragment->BreakToken()); |
| EXPECT_EQ(PhysicalSize(150, 0), fragment->Size()); |
| ASSERT_FALSE(fragment->BreakToken()); |
| |
| iterator.SetParent(To<NGPhysicalBoxFragment>(fragment.get())); |
| child = iterator.NextChild(); |
| |
| // Second fragment of float. |
| iterator.SetParent(child); |
| child = iterator.NextChild(); |
| EXPECT_EQ(PhysicalSize(75, 50), child->Size()); |
| // TODO(ikilpatrick): Don't include the block-start margin of a float which |
| // has fragmented. |
| // EXPECT_EQ(PhysicalOffset(10, 0), |
| // child->Offset()); |
| } |
| |
| // Verifies that we correctly position a new FC block with the Layout |
| // Opportunity iterator. |
| TEST_F(NGBlockLayoutAlgorithmTest, |
| NewFcBlockWithAdjoiningFloatCollapsesMargins) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 200px; outline: solid purple 1px; |
| } |
| #float { |
| float: left; width: 100px; height: 30px; background: red; |
| } |
| #new-fc { |
| contain: paint; margin-top: 20px; background: purple; |
| height: 50px; |
| } |
| </style> |
| <div id="container"> |
| <div id="float"></div> |
| <div id="new-fc"></div> |
| </div> |
| )HTML"); |
| |
| PhysicalOffset body_offset; |
| PhysicalOffset new_fc_offset; |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment; |
| auto run_test = [&](const Length& block_width) { |
| Element* new_fc_block = GetDocument().getElementById("new-fc"); |
| MutableStyleForElement(new_fc_block)->SetWidth(block_width); |
| new_fc_block->GetLayoutObject()->SetNeedsLayout(""); |
| std::tie(fragment, std::ignore) = RunBlockLayoutAlgorithmForElement( |
| GetDocument().getElementsByTagName("html")->item(0)); |
| ASSERT_EQ(1UL, fragment->Children().size()); |
| const auto* body_fragment = |
| To<NGPhysicalBoxFragment>(fragment->Children()[0].get()); |
| const auto* container_fragment = |
| To<NGPhysicalBoxFragment>(body_fragment->Children()[0].get()); |
| ASSERT_EQ(2UL, container_fragment->Children().size()); |
| body_offset = fragment->Children()[0].Offset(); |
| new_fc_offset = container_fragment->Children()[1].Offset(); |
| }; |
| |
| // #new-fc is small enough to fit on the same line with #float. |
| run_test(Length::Fixed(80)); |
| // 100 = float's width, 0 = no margin collapsing |
| EXPECT_THAT(new_fc_offset, PhysicalOffset(100, 0)); |
| // 8 = body's margins, 20 = new-fc's margin top(20) collapses with |
| // body's margin(8) |
| EXPECT_THAT(body_offset, PhysicalOffset(8, 20)); |
| |
| // #new-fc is too wide to be positioned on the same line with #float |
| run_test(Length::Fixed(120)); |
| // 30 = #float's height |
| EXPECT_THAT(new_fc_offset, PhysicalOffset(0, 30)); |
| // 8 = body's margins, no margin collapsing |
| EXPECT_THAT(body_offset, PhysicalOffset(8, 8)); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, NewFcAvoidsFloats) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 200px; |
| } |
| #float { |
| float: left; width: 100px; height: 30px; background: red; |
| } |
| #fc { |
| width: 150px; height: 120px; display: flow-root; |
| } |
| </style> |
| <div id="container"> |
| <div id="float"></div> |
| <div id="fc"></div> |
| </div> |
| )HTML"); |
| |
| NGBlockNode node( |
| To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize)); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(200, 150), fragment->Size()); |
| |
| FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); |
| |
| PhysicalOffset offset; |
| const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(100, 30), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 0), offset); |
| |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(150, 120), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, 30), offset); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, ZeroBlockSizeAboveEdge) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { width: 200px; display: flow-root; } |
| #inflow { width: 50px; height: 50px; background: red; margin-top: -70px; } |
| #zero { width: 70px; margin: 10px 0 30px 0; } |
| </style> |
| <div id="container"> |
| <div id="inflow"></div> |
| <div id="zero"></div> |
| </div> |
| )HTML"); |
| |
| NGBlockNode node( |
| To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| /* is_new_formatting_context */ true); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(200, 10), fragment->Size()); |
| |
| FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); |
| |
| PhysicalOffset offset; |
| const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(50, 50), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, -70), offset); |
| |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(70, 0), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, -10), offset); |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, NewFcFirstChildIsZeroBlockSize) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { width: 200px; display: flow-root; } |
| #zero1 { width: 50px; margin-top: -30px; margin-bottom: 10px; } |
| #zero2 { width: 70px; margin-top: 20px; margin-bottom: -40px; } |
| #inflow { width: 90px; height: 20px; margin-top: 30px; } |
| </style> |
| <div id="container"> |
| <div id="zero1"></div> |
| <div id="zero2"></div> |
| <div id="inflow"></div> |
| </div> |
| )HTML"); |
| |
| NGBlockNode node( |
| To<LayoutBlockFlow>(GetLayoutObjectByElementId("container"))); |
| NGConstraintSpace space = ConstructBlockLayoutTestConstraintSpace( |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| LogicalSize(LayoutUnit(1000), kIndefiniteSize), |
| /* stretch_inline_size_if_auto */ true, |
| /* is_new_formatting_context */ true); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> fragment = |
| RunBlockLayoutAlgorithm(node, space); |
| EXPECT_EQ(PhysicalSize(200, 10), fragment->Size()); |
| |
| FragmentChildIterator iterator(To<NGPhysicalBoxFragment>(fragment.get())); |
| |
| PhysicalOffset offset; |
| const NGPhysicalBoxFragment* child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(50, 0), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, -30), offset); |
| |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(70, 0), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, -10), offset); |
| |
| child = iterator.NextChild(&offset); |
| EXPECT_EQ(PhysicalSize(90, 20), child->Size()); |
| EXPECT_EQ(PhysicalOffset(0, -10), offset); |
| } |
| |
| // This test assumes that tables are not yet implemented in LayoutNG. |
| TEST_F(NGBlockLayoutAlgorithmTest, RootFragmentOffsetInsideLegacy) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <div style="display:table-cell;"> |
| <div id="innerNGRoot" style="margin-top:10px; margin-left:20px;"></div> |
| </div> |
| )HTML"); |
| |
| UpdateAllLifecyclePhasesForTest(); |
| const LayoutObject* innerNGRoot = GetLayoutObjectByElementId("innerNGRoot"); |
| |
| ASSERT_TRUE(innerNGRoot->IsLayoutNGMixin()); |
| const NGPhysicalBoxFragment* fragment = |
| CurrentFragmentFor(To<LayoutNGBlockFlow>(innerNGRoot)); |
| |
| ASSERT_TRUE(fragment); |
| // TODO(crbug.com/781241: Re-enable when we calculate inline offset at |
| // the right time. |
| // EXPECT_EQ(PhysicalOffset(20, 10), fragment->Offset()); |
| } |
| |
| // This test checks if the inline block baseline is computed correctly when it |
| // is from the logical bottom margin edge, even after the simplified layout. |
| TEST_F(NGBlockLayoutAlgorithmTest, |
| BaselineAtBlockEndMarginEdgeAfterSimplifiedLayout) { |
| SetBodyInnerHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #outer { |
| height: 200px; |
| } |
| #outer.after { |
| height: 400px; |
| } |
| #target { |
| display: inline-block; |
| overflow: hidden; |
| width: 300px; |
| height: 100%; |
| } |
| </style> |
| <div id="outer"> |
| <div id="target"> |
| </div> |
| </div> |
| )HTML"); |
| UpdateAllLifecyclePhasesForTest(); |
| |
| // #target uses the logical bottom margin edge for the inline block baseline. |
| auto* target_block_flow = |
| To<LayoutBlockFlow>(GetLayoutObjectByElementId("target")); |
| NGBlockNode target(target_block_flow); |
| ASSERT_TRUE(target.UseBlockEndMarginEdgeForInlineBlockBaseline()); |
| scoped_refptr<const NGPhysicalBoxFragment> before = |
| To<NGPhysicalBoxFragment>(target_block_flow->GetPhysicalFragment(0)); |
| EXPECT_EQ(*before->LastBaseline(), LayoutUnit(200)); |
| |
| // Change the height of the container. This should kick the simplified layout. |
| Element* outer_element = GetElementById("outer"); |
| outer_element->classList().Add("after"); |
| UpdateAllLifecyclePhasesForTest(); |
| |
| scoped_refptr<const NGPhysicalBoxFragment> after = |
| To<NGPhysicalBoxFragment>(target_block_flow->GetPhysicalFragment(0)); |
| EXPECT_EQ(*after->LastBaseline(), LayoutUnit(400)); |
| } |
| |
| // TODO(dgrogan): Move this to ng_flex_layout_algorithm_test.cc if there ever is |
| // one. |
| TEST_F(NGBlockLayoutAlgorithmTest, DetailsFlexDoesntCrash) { |
| SetBodyInnerHTML(R"HTML( |
| <details style="display:flex"></details> |
| )HTML"); |
| UpdateAllLifecyclePhasesForTest(); |
| // No crash is good. |
| } |
| |
| TEST_F(NGBlockLayoutAlgorithmTest, LayoutRubyTextCrash) { |
| // crbug.com/1102186. This test passes if no DCHECK failure. |
| SetBodyInnerHTML(R"HTML( |
| <ruby>base<rt style="writing-mode:vertical-rl">annotation</ruby> |
| )HTML"); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| |
| } // namespace |
| } // namespace blink |