blob: 84be8e11a6283cb09e2c81c14c582847a97fd9f8 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/layout/geometry/fragment_geometry.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_utils.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
namespace blink {
namespace {
// These tests exercise the caching logic of |LayoutResult|s. They are
// rendering tests which contain two children: "test" and "src".
//
// Both have layout initially performed on them, however the "src" will have a
// different |ConstraintSpace| which is then used to test either a cache hit
// or miss.
class LayoutResultCachingTest : public RenderingTest {
protected:
LayoutResultCachingTest() {}
const LayoutResult* TestCachedLayoutResultWithBreakToken(
LayoutBox* box,
const ConstraintSpace& constraint_space,
const BlockBreakToken* break_token) {
absl::optional<FragmentGeometry> fragment_geometry;
LayoutCacheStatus cache_status;
return box->CachedLayoutResult(constraint_space, break_token, nullptr,
nullptr, &fragment_geometry, &cache_status);
}
const LayoutResult* TestCachedLayoutResult(
LayoutBox* box,
const ConstraintSpace& constraint_space,
LayoutCacheStatus* out_cache_status = nullptr) {
absl::optional<FragmentGeometry> fragment_geometry;
LayoutCacheStatus cache_status;
const LayoutResult* result =
box->CachedLayoutResult(constraint_space, nullptr, nullptr, nullptr,
&fragment_geometry, &cache_status);
if (out_cache_status) {
*out_cache_status = cache_status;
}
return result;
}
};
TEST_F(LayoutResultCachingTest, HitDifferentExclusionSpace) {
// Same BFC offset, different exclusion space.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="test" style="height: 20px;"></div>
</div>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 30px;"></div>
</div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
EXPECT_EQ(result->BfcBlockOffset().value(), LayoutUnit(50));
EXPECT_EQ(result->BfcLineOffset(), LayoutUnit());
}
TEST_F(LayoutResultCachingTest, HitDifferentBFCOffset) {
// Different BFC offset, same exclusion space.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="test" style="height: 20px; padding-top: 5px;">
<div class="float" style="height: 20px;"></div>
</div>
</div>
<div class="bfc">
<div style="height: 40px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="src" style="height: 20px; padding-top: 5px;">
<div class="float" style="height: 20px;"></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
EXPECT_EQ(result->BfcBlockOffset().value(), LayoutUnit(40));
EXPECT_EQ(result->BfcLineOffset(), LayoutUnit());
// Also check that the exclusion(s) got moved correctly.
LayoutOpportunityVector opportunities =
result->GetExclusionSpace().AllLayoutOpportunities(
/* offset */ {LayoutUnit(), LayoutUnit()},
/* available_inline_size */ LayoutUnit(100));
EXPECT_EQ(opportunities.size(), 3u);
EXPECT_EQ(opportunities[0].rect.start_offset,
BfcOffset(LayoutUnit(50), LayoutUnit()));
EXPECT_EQ(opportunities[0].rect.end_offset,
BfcOffset(LayoutUnit(100), LayoutUnit::Max()));
EXPECT_EQ(opportunities[1].rect.start_offset,
BfcOffset(LayoutUnit(), LayoutUnit(20)));
EXPECT_EQ(opportunities[1].rect.end_offset,
BfcOffset(LayoutUnit(100), LayoutUnit(45)));
EXPECT_EQ(opportunities[2].rect.start_offset,
BfcOffset(LayoutUnit(), LayoutUnit(65)));
EXPECT_EQ(opportunities[2].rect.end_offset,
BfcOffset(LayoutUnit(100), LayoutUnit::Max()));
}
TEST_F(LayoutResultCachingTest, HitDifferentBFCOffsetSameMarginStrut) {
// Different BFC offset, same margin-strut.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
</style>
<div class="bfc">
<div style="height: 50px; margin-bottom: 20px;"></div>
<div id="test" style="height: 20px;"></div>
</div>
<div class="bfc">
<div style="height: 40px; margin-bottom: 20px;"></div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissDescendantAboveBlockStart1) {
// Same BFC offset, different exclusion space, descendant above
// block start.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="test" style="height: 20px; padding-top: 5px;">
<div style="height: 10px; margin-top: -10px;"></div>
</div>
</div>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 30px;"></div>
</div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissDescendantAboveBlockStart2) {
// Different BFC offset, same exclusion space, descendant above
// block start.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="test" style="height: 20px; padding-top: 5px;">
<div style="height: 10px; margin-top: -10px;"></div>
</div>
</div>
<div class="bfc">
<div style="height: 40px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitOOFDescendantAboveBlockStart) {
// Different BFC offset, same exclusion space, OOF-descendant above
// block start.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="test" style="position: relative; height: 20px; padding-top: 5px;">
<div style="position: absolute; height: 10px; top: -10px;"></div>
</div>
</div>
<div class="bfc">
<div style="height: 40px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitLineBoxDescendantAboveBlockStart) {
// Different BFC offset, same exclusion space, line-box descendant above
// block start.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="test" style="font-size: 12px;">
text
<span style="margin: 0 1px;">
<span style="display: inline-block; vertical-align: text-bottom; width: 16px; height: 16px;"></span>
</span>
</div>
</div>
<div class="bfc">
<div style="height: 40px;">
<div class="float" style="height: 20px;"></div>
</div>
<div id="src" style="font-size: 12px;">
text
</div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissFloatInitiallyIntruding1) {
// Same BFC offset, different exclusion space, float initially
// intruding.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="test" style="height: 20px;"></div>
</div>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 30px;"></div>
</div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissFloatInitiallyIntruding2) {
// Different BFC offset, same exclusion space, float initially
// intruding.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="test" style="height: 60px;"></div>
</div>
<div class="bfc">
<div style="height: 70px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissFloatWillIntrude1) {
// Same BFC offset, different exclusion space, float will intrude.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 40px;"></div>
</div>
<div id="test" style="height: 20px;"></div>
</div>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissFloatWillIntrude2) {
// Different BFC offset, same exclusion space, float will intrude.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 40px;"></div>
</div>
<div id="test" style="height: 60px;"></div>
</div>
<div class="bfc">
<div style="height: 30px;">
<div class="float" style="height: 40px;"></div>
</div>
<div id="src" style="height: 20px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitPushedByFloats1) {
// Same BFC offset, different exclusion space, pushed by floats.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="test" style="height: 20px; clear: left;"></div>
</div>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 70px;"></div>
</div>
<div id="src" style="height: 20px; clear: left;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitPushedByFloats2) {
// Different BFC offset, same exclusion space, pushed by floats.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="test" style="height: 20px; clear: left;"></div>
</div>
<div class="bfc">
<div style="height: 30px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="src" style="height: 20px; clear: left;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissPushedByFloats1) {
// Same BFC offset, different exclusion space, pushed by floats.
// Miss due to shrinking offset.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 70px;"></div>
</div>
<div id="test" style="height: 20px; clear: left;"></div>
</div>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="src" style="height: 20px; clear: left;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissPushedByFloats2) {
// Different BFC offset, same exclusion space, pushed by floats.
// Miss due to shrinking offset.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 50px; }
</style>
<div class="bfc">
<div style="height: 30px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="test" style="height: 20px; clear: left;"></div>
</div>
<div class="bfc">
<div style="height: 50px;">
<div class="float" style="height: 60px;"></div>
</div>
<div id="src" style="height: 20px; clear: left;"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitDifferentRareData) {
// Same absolute fixed constraints.
SetBodyInnerHTML(R"HTML(
<style>
.container { position: relative; width: 100px; height: 100px; }
.abs { position: absolute; width: 100px; height: 100px; top: 0; left: 0; }
</style>
<div class="container">
<div id="test" class="abs"></div>
</div>
<div class="container" style="width: 200px; height: 200px;">
<div id="src" class="abs"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitPercentageMinWidth) {
// min-width calculates to different values, but doesn't change size.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.inflow { width: 100px; min-width: 25%; }
</style>
<div class="bfc">
<div id="test" class="inflow"></div>
</div>
<div class="bfc" style="width: 200px; height: 200px;">
<div id="src" class="inflow"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitFixedMinWidth) {
// min-width is always larger than the available size.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.inflow { min-width: 300px; }
</style>
<div class="bfc">
<div id="test" class="inflow"></div>
</div>
<div class="bfc" style="width: 200px; height: 200px;">
<div id="src" class="inflow"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitShrinkToFit) {
SetBodyInnerHTML(R"HTML(
<div style="display: flow-root; width: 300px; height: 100px;">
<div id="test1" style="float: left;">
<div style="display: inline-block; width: 150px;"></div>
<div style="display: inline-block; width: 50px;"></div>
</div>
<div id="test2" style="float: left;">
<div style="display: inline-block; width: 350px;"></div>
<div style="display: inline-block; width: 250px;"></div>
</div>
</div>
<div style="display: flow-root; width: 400px; height: 100px;">
<div id="src1" style="float: left;">
<div style="display: inline-block; width: 150px;"></div>
<div style="display: inline-block; width: 50px;"></div>
</div>
</div>
<div style="display: flow-root; width: 200px; height: 100px;">
<div id="src2" style="float: left;">
<div style="display: inline-block; width: 350px;"></div>
<div style="display: inline-block; width: 250px;"></div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// test1 was sized to its max-content size, passing an available size larger
// than the fragment should hit the cache.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test2, space, &cache_status);
// test2 was sized to its min-content size in, passing an available size
// smaller than the fragment should hit the cache.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissShrinkToFit) {
SetBodyInnerHTML(R"HTML(
<div style="display: flow-root; width: 300px; height: 100px;">
<div id="test1" style="float: left;">
<div style="display: inline-block; width: 150px;"></div>
<div style="display: inline-block; width: 50px;"></div>
</div>
<div id="test2" style="float: left;">
<div style="display: inline-block; width: 350px;"></div>
<div style="display: inline-block; width: 250px;"></div>
</div>
<div id="test3" style="float: left; min-width: 80%;">
<div style="display: inline-block; width: 150px;"></div>
<div style="display: inline-block; width: 250px;"></div>
</div>
<div id="test4" style="float: left; margin-left: 75px;">
<div style="display: inline-block; width: 150px;"></div>
<div style="display: inline-block; width: 50px;"></div>
</div>
</div>
<div style="display: flow-root; width: 100px; height: 100px;">
<div id="src1" style="float: left;">
<div style="display: inline-block; width: 150px;"></div>
<div style="display: inline-block; width: 50px;"></div>
</div>
</div>
<div style="display: flow-root; width: 400px; height: 100px;">
<div id="src2" style="float: left;">
<div style="display: inline-block; width: 350px;"></div>
<div style="display: inline-block; width: 250px;"></div>
</div>
<div id="src3" style="float: left; min-width: 80%;">
<div style="display: inline-block; width: 150px;"></div>
<div style="display: inline-block; width: 250px;"></div>
</div>
</div>
<div style="display: flow-root; width: 250px; height: 100px;">
<div id="src4" style="float: left; margin-left: 75px;">
<div style="display: inline-block; width: 150px;"></div>
<div style="display: inline-block; width: 50px;"></div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* test3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test3"));
auto* test4 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test4"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
auto* src3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src3"));
auto* src4 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src4"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// test1 was sized to its max-content size, passing an available size smaller
// than the fragment should miss the cache.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test2, space, &cache_status);
// test2 was sized to its min-content size, passing an available size
// larger than the fragment should miss the cache.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
space = src3->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test3, space, &cache_status);
// test3 was sized to its min-content size, however it should miss the cache
// as it has a %-min-size.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
space = src4->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test4, space, &cache_status);
// test4 was sized to its max-content size, however it should miss the cache
// due to its margin.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitShrinkToFitSameIntrinsicSizes) {
// We have a shrink-to-fit node, with the min, and max intrinsic sizes being
// equal (the available size doesn't affect the final size).
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.shrink { width: fit-content; }
.child { width: 250px; }
</style>
<div class="bfc">
<div id="test" class="shrink">
<div class="child"></div>
</div>
</div>
<div class="bfc" style="width: 200px; height: 200px;">
<div id="src" class="shrink">
<div class="child"></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitShrinkToFitDifferentParent) {
// The parent "bfc" node changes from shrink-to-fit, to a fixed width. But
// these calculate as the same available space to the "test" element.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; }
.child { width: 250px; }
</style>
<div class="bfc" style="width: fit-content; height: 100px;">
<div id="test">
<div class="child"></div>
</div>
</div>
<div class="bfc" style="width: 250px; height: 100px;">
<div id="src">
<div class="child"></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissQuirksModePercentageBasedChild) {
// Quirks-mode %-block-size child.
GetDocument().SetCompatibilityMode(Document::kQuirksMode);
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.child { height: 50%; }
</style>
<div class="bfc">
<div id="test">
<div class="child"></div>
</div>
</div>
<div class="bfc" style="height: 200px;">
<div id="src">
<div class="child"></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitQuirksModePercentageBasedParentAndChild) {
// Quirks-mode %-block-size parent *and* child. Here we mark the parent as
// depending on %-block-size changes, however itself doesn't change in
// height.
// We are able to hit the cache as we detect that the height for the child
// *isn't* indefinite, and results in the same height as before.
GetDocument().SetCompatibilityMode(Document::kQuirksMode);
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.parent { height: 50%; min-height: 200px; }
.child { height: 50%; }
</style>
<div class="bfc">
<div id="test" class="parent">
<div class="child"></div>
</div>
</div>
<div class="bfc" style="height: 200px;">
<div id="src" class="parent">
<div class="child"></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitStandardsModePercentageBasedChild) {
// Standards-mode %-block-size child.
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.child { height: 50%; }
</style>
<div class="bfc">
<div id="test">
<div class="child"></div>
</div>
</div>
<div class="bfc" style="height: 200px;">
<div id="src">
<div class="child"></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, ChangeTableCellBlockSizeConstrainedness) {
SetBodyInnerHTML(R"HTML(
<style>
.table { display: table; width: 300px; }
.cell { display: table-cell; }
.child1 { height: 100px; }
.child2, .child3 { overflow:auto; height:10%; }
</style>
<div class="table">
<div class="cell">
<div class="child1" id="test1"></div>
<div class="child2" id="test2">
<div style="height:30px;"></div>
</div>
<div class="child3" id="test3"></div>
</div>
</div>
<div class="table" style="height:300px;">
<div class="cell">
<div class="child1" id="src1"></div>
<div class="child2" id="src2">
<div style="height:30px;"></div>
</div>
<div class="child3" id="src3"></div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* test3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test3"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
auto* src3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src3"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// The first child has a fixed height, and shouldn't be affected by the cell
// height.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test2, space, &cache_status);
// The second child has overflow:auto and a percentage height, but its
// intrinsic height is identical to its extrinsic height (when the cell has a
// height). So it won't need layout, either.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
space = src3->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test3, space, &cache_status);
// The third child has overflow:auto and a percentage height, and its
// intrinsic height is 0 (no children), so it matters whether the cell has a
// height or not. We're only going to need simplified layout, though, since no
// children will be affected by its height change.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsSimplifiedLayout);
}
TEST_F(LayoutResultCachingTest, OptimisticFloatPlacementNoRelayout) {
SetBodyInnerHTML(R"HTML(
<style>
.root { display: flow-root; width: 300px; }
.float { float: left; width: 10px; height: 10px; }
</style>
<div class="root">
<div id="empty">
<div class="float"></div>
</div>
</div>
)HTML");
auto* empty = To<LayoutBlockFlow>(GetLayoutObjectByElementId("empty"));
ConstraintSpace space =
empty->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
// We shouldn't have a "forced" BFC block-offset, as the "empty"
// self-collapsing block should have its "expected" BFC block-offset at the
// correct place.
EXPECT_EQ(space.ForcedBfcBlockOffset(), absl::nullopt);
}
TEST_F(LayoutResultCachingTest, SelfCollapsingShifting) {
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float { float: left; width: 10px; height: 10px; }
.adjoining-oof { position: absolute; display: inline; }
</style>
<div class="bfc">
<div class="float"></div>
<div id="test1"></div>
</div>
<div class="bfc">
<div class="float" style="height; 20px;"></div>
<div id="src1"></div>
</div>
<div class="bfc">
<div class="float"></div>
<div id="test2">
<div class="adjoining-oof"></div>
</div>
</div>
<div class="bfc">
<div class="float" style="height; 20px;"></div>
<div id="src2">
<div class="adjoining-oof"></div>
</div>
</div>
<div class="bfc">
<div class="float"></div>
<div style="height: 30px;"></div>
<div id="test3">
<div class="adjoining-oof"></div>
</div>
</div>
<div class="bfc">
<div class="float" style="height; 20px;"></div>
<div style="height: 30px;"></div>
<div id="src3">
<div class="adjoining-oof"></div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* test3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test3"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
auto* src3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src3"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// Case 1: We have a different set of constraints, but as the child has no
// adjoining descendants it can be shifted anywhere.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test2, space, &cache_status);
// Case 2: We have a different set of constraints, but the child has an
// adjoining object and isn't "past" the floats - it can't be reused.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
space = src3->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test3, space, &cache_status);
// Case 3: We have a different set of constraints, and adjoining descendants,
// but have a position past where they might affect us.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, ClearancePastAdjoiningFloatsMovement) {
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
.float-left { float: left; width: 10px; height: 10px; }
.float-right { float: right; width: 10px; height: 20px; }
</style>
<div class="bfc">
<div>
<div class="float-left"></div>
<div class="float-right"></div>
<div id="test1" style="clear: both;">text</div>
</div>
</div>
<div class="bfc">
<div>
<div class="float-left" style="height; 20px;"></div>
<div class="float-right"></div>
<div id="src1" style="clear: both;">text</div>
</div>
</div>
<div class="bfc">
<div>
<div class="float-left"></div>
<div class="float-right"></div>
<div id="test2" style="clear: left;">text</div>
</div>
</div>
<div class="bfc">
<div>
<div class="float-left" style="height; 20px;"></div>
<div class="float-right"></div>
<div id="src2" style="clear: left;">text</div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// Case 1: We have forced clearance, but floats won't impact our children.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test2, space, &cache_status);
// Case 2: We have forced clearance, and floats will impact our children.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MarginStrutMovementSelfCollapsing) {
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
</style>
<div class="bfc">
<div style="margin-top: 10px;">
<div id="test1">
<div></div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 5px;">
<div id="src1">
<div></div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 10px;">
<div id="test2">
<div style="margin-bottom: 8px;"></div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 5px;">
<div id="src2">
<div style="margin-bottom: 8px;"></div>
</div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// Case 1: We can safely re-use this fragment as it doesn't append anything
// to the margin-strut within the sub-tree.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
// The "end" margin-strut should be updated.
MarginStrut expected_margin_strut;
expected_margin_strut.Append(LayoutUnit(5), false /* is_quirky */);
EXPECT_EQ(expected_margin_strut, result->EndMarginStrut());
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test2, space, &cache_status);
// Case 2: We can't re-use this fragment as it appended a non-zero value to
// the margin-strut within the sub-tree.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MarginStrutMovementInFlow) {
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
</style>
<div class="bfc">
<div style="margin-top: 10px;">
<div id="test1">
<div>text</div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 5px;">
<div id="src1">
<div>text</div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 10px;">
<div id="test2">
<div style="margin-top: 8px;">text</div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 5px;">
<div id="src2">
<div style="margin-top: 8px;">text</div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 10px;">
<div id="test3">
<div>
<div style="margin-top: 8px;"></div>
</div>
<div>text</div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 5px;">
<div id="src3">
<div>
<div style="margin-top: 8px;"></div>
</div>
<div>text</div>
</div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* test3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test3"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
auto* src3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src3"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// Case 1: We can safely re-use this fragment as it doesn't append anything
// to the margin-strut within the sub-tree.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test2, space, &cache_status);
// Case 2: We can't re-use this fragment as it appended a non-zero value to
// the margin-strut within the sub-tree.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
space = src3->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test3, space, &cache_status);
// Case 3: We can't re-use this fragment as a (inner) self-collapsing block
// appended a non-zero value to the margin-strut within the sub-tree.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MarginStrutMovementPercentage) {
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 300px; height: 300px; }
</style>
<div class="bfc">
<div style="margin-top: 10px;">
<div id="test1" style="width: 0px;">
<div style="margin-top: 50%;">text</div>
</div>
</div>
</div>
<div class="bfc">
<div style="margin-top: 5px;">
<div id="src1" style="width: 0px;">
<div style="margin-top: 50%;">text</div>
</div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// We can't re-use this fragment as it appended a non-zero value (50%) to the
// margin-strut within the sub-tree.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitIsFixedBlockSizeIndefinite) {
SetBodyInnerHTML(R"HTML(
<div style="display: flex; width: 100px; height: 100px;">
<div id="test1" style="flex-grow: 1; min-height: 100px;">
<div style="height: 50px;">text</div>
</div>
</div>
<div style="display: flex; width: 100px; height: 100px; align-items: stretch;">
<div id="src1" style="flex-grow: 1; min-height: 100px;">
<div style="height: 50px;">text</div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// Even though the "align-items: stretch" will make the final fixed
// block-size indefinite, we don't have any %-block-size children, so we can
// hit the cache.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissIsFixedBlockSizeIndefinite) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div style="display: flex; width: 100px; height: 100px; align-items: start;">
<div id="src1" style="flex-grow: 1; min-height: 100px;">
<div style="height: 50%;">text</div>
</div>
</div>
<div style="display: flex; width: 100px; height: 100px; align-items: stretch;">
<div id="test1" style="flex-grow: 1; min-height: 100px;">
<div style="height: 50%;">text</div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
// The "align-items: stretch" will make the final fixed block-size
// indefinite, and we have a %-block-size child, so we need to miss the
// cache.
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitColumnFlexBoxMeasureAndLayout) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<style>
.bfc { display: flex; flex-direction: column; width: 100px; height: 100px; }
</style>
<div class="bfc">
<div id="src1" style="flex-grow: 0;">
<div style="height: 50px;"></div>
</div>
</div>
<div class="bfc">
<div id="src2" style="flex-grow: 1;">
<div style="height: 50px;"></div>
</div>
</div>
<div class="bfc">
<div id="test1" style="flex-grow: 2;">
<div style="height: 50px;"></div>
</div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
LayoutCacheStatus cache_status;
// "src1" only had one "measure" pass performed, and should hit the "measure"
// cache-slot for "test1".
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
EXPECT_EQ(space.CacheSlot(), LayoutResultCacheSlot::kMeasure);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
// "src2" had both a "measure" and "layout" pass performed, and should hit
// the "layout" cache-slot for "test1".
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test1, space, &cache_status);
EXPECT_EQ(space.CacheSlot(), LayoutResultCacheSlot::kLayout);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitRowFlexBoxMeasureAndLayout) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<style>
.bfc { display: flex; width: 100px; }
</style>
<div class="bfc">
<div id="src1">
<div style="height: 50px;"></div>
</div>
</div>
<div class="bfc">
<div id="src2">
<div style="height: 70px;"></div>
</div>
<div style="width: 0px; height: 100px;"></div>
</div>
<div class="bfc">
<div id="test1">
<div style="height: 50px;"></div>
</div>
<div style="width: 0px; height: 100px;"></div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* src1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src1"));
auto* src2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src2"));
LayoutCacheStatus cache_status;
// "src1" only had one "measure" pass performed, and should hit the "measure"
// cache-slot for "test1".
ConstraintSpace space =
src1->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test1, space, &cache_status);
EXPECT_EQ(space.CacheSlot(), LayoutResultCacheSlot::kMeasure);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
// "src2" had both a "measure" and "layout" pass performed, and should hit
// the "layout" cache-slot for "test1".
space = src2->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
result = TestCachedLayoutResult(test1, space, &cache_status);
EXPECT_EQ(space.CacheSlot(), LayoutResultCacheSlot::kLayout);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitFlexLegacyImg) {
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flex; flex-direction: column; width: 300px; }
.bfc > * { display: flex; }
</style>
<div class="bfc">
<div id="test">
<img />
</div>
</div>
<div class="bfc" style="height: 200px;">
<div id="src">
<img />
</div>
</div>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitFlexLegacyGrid) {
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flex; flex-direction: column; width: 300px; }
.bfc > * { display: flex; }
.grid { display: grid; }
</style>
<div class="bfc">
<div id="test">
<div class="grid"></div>
</div>
</div>
<div class="bfc" style="height: 200px;">
<div id="src">
<div class="grid"></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitFlexDefiniteChange) {
SetBodyInnerHTML(R"HTML(
<div style="display: flex; flex-direction: column;">
<div style="height: 200px;" id=target1>
<div style="height: 100px"></div>
</div>
</div>
)HTML");
auto* target1 = To<LayoutBlock>(GetLayoutObjectByElementId("target1"));
const LayoutResult* result1 = target1->GetSingleCachedLayoutResult();
const LayoutResult* measure1 = target1->GetCachedMeasureResult();
EXPECT_EQ(measure1->IntrinsicBlockSize(), 100);
EXPECT_EQ(result1->GetPhysicalFragment().Size().height, 200);
EXPECT_EQ(result1->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kMeasure);
EXPECT_EQ(result1, measure1);
}
TEST_F(LayoutResultCachingTest, HitOrthogonalRoot) {
SetBodyInnerHTML(R"HTML(
<style>
span { display: inline-block; width: 20px; height: 250px }
</style>
<div id="target" style="display: flex;">
<div style="writing-mode: vertical-rl; line-height: 0;">
<span></span><span></span>
</div>
</div>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
target->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(target, space, &cache_status);
// We should hit the cache using the same constraint space.
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, SimpleTable) {
SetBodyInnerHTML(R"HTML(
<table>
<td id="target1">abc</td>
<td id="target2">abc</td>
</table>
)HTML");
auto* target1 = To<LayoutBlock>(GetLayoutObjectByElementId("target1"));
auto* target2 = To<LayoutBlock>(GetLayoutObjectByElementId("target2"));
// Both "target1", and "target1" should have only had one "measure" pass
// performed.
const LayoutResult* result1 = target1->GetSingleCachedLayoutResult();
const LayoutResult* measure1 = target1->GetCachedMeasureResult();
EXPECT_EQ(result1->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kMeasure);
EXPECT_NE(result1, nullptr);
EXPECT_EQ(result1, measure1);
const LayoutResult* result2 = target2->GetSingleCachedLayoutResult();
const LayoutResult* measure2 = target2->GetCachedMeasureResult();
EXPECT_EQ(result2->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kMeasure);
EXPECT_NE(result2, nullptr);
EXPECT_EQ(result2, measure2);
}
TEST_F(LayoutResultCachingTest, MissTableCellMiddleAlignment) {
SetBodyInnerHTML(R"HTML(
<table>
<td id="target" style="vertical-align: middle;">abc</td>
<td>abc<br>abc</td>
</table>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
// "target" should be stretched, and miss the measure cache.
const LayoutResult* result = target->GetSingleCachedLayoutResult();
const LayoutResult* measure = target->GetCachedMeasureResult();
EXPECT_NE(measure, nullptr);
EXPECT_NE(result, nullptr);
EXPECT_EQ(measure->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kMeasure);
EXPECT_EQ(result->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kLayout);
EXPECT_NE(result, measure);
}
TEST_F(LayoutResultCachingTest, MissTableCellBottomAlignment) {
SetBodyInnerHTML(R"HTML(
<table>
<td id="target" style="vertical-align: bottom;">abc</td>
<td>abc<br>abc</td>
</table>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
// "target" should be stretched, and miss the measure cache.
const LayoutResult* result = target->GetSingleCachedLayoutResult();
const LayoutResult* measure = target->GetCachedMeasureResult();
EXPECT_NE(measure, nullptr);
EXPECT_NE(result, nullptr);
EXPECT_EQ(measure->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kMeasure);
EXPECT_EQ(result->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kLayout);
EXPECT_NE(result, measure);
}
TEST_F(LayoutResultCachingTest, HitTableCellBaselineAlignment) {
SetBodyInnerHTML(R"HTML(
<style>
td { vertical-align: baseline; }
</style>
<table>
<td id="target">abc</td>
<td>def</td>
</table>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
// "target" should align to the baseline, but hit the cache.
const LayoutResult* result = target->GetSingleCachedLayoutResult();
const LayoutResult* measure = target->GetCachedMeasureResult();
EXPECT_EQ(result->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kMeasure);
EXPECT_NE(result, nullptr);
EXPECT_EQ(result, measure);
}
TEST_F(LayoutResultCachingTest, MissTableCellBaselineAlignment) {
SetBodyInnerHTML(R"HTML(
<style>
td { vertical-align: baseline; }
</style>
<table>
<td id="target">abc</td>
<td><span style="font-size: 32px">def</span></td>
</table>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
// "target" should align to the baseline, but miss the cache.
const LayoutResult* result = target->GetSingleCachedLayoutResult();
const LayoutResult* measure = target->GetCachedMeasureResult();
EXPECT_NE(measure, nullptr);
EXPECT_NE(result, nullptr);
EXPECT_EQ(measure->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kMeasure);
EXPECT_EQ(result->GetConstraintSpaceForCaching().CacheSlot(),
LayoutResultCacheSlot::kLayout);
EXPECT_NE(result, measure);
}
TEST_F(LayoutResultCachingTest, MissTablePercent) {
SetBodyInnerHTML(R"HTML(
<style>
.bfc { display: flow-root; width: 100px; }
table { height: 100%; }
caption { height: 50px; }
</style>
<div class="bfc" style="height: 50px;">
<table id="test">
<caption></caption>
<td></td>
</table>
</div>
<div class="bfc" style="height: 100px;">
<table id="src">
<caption></caption>
<td></td>
</table>
</div>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitTableRowAdd) {
SetBodyInnerHTML(R"HTML(
<table>
<tr><td>a</td><td>b</td></tr>
<tr id="test"><td>text</td><td>more text</td></tr>
</table>
<table>
<tr id="src"><td>text</td><td>more text</td></tr>
</table>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissTableRowAdd) {
SetBodyInnerHTML(R"HTML(
<table>
<tr><td>longwordhere</td><td>b</td></tr>
<tr id="test"><td>text</td><td>more text</td></tr>
</table>
<table>
<tr id="src"><td>text</td><td>more text</td></tr>
</table>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitTableRowRemove) {
SetBodyInnerHTML(R"HTML(
<table>
<tr id="test"><td>text</td><td>more text</td></tr>
</table>
<table>
<tr><td>a</td><td>b</td></tr>
<tr id="src"><td>text</td><td>more text</td></tr>
</table>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissTableRowRemove) {
SetBodyInnerHTML(R"HTML(
<table>
<tr id="test"><td>text</td><td>more text</td></tr>
</table>
<table>
<tr><td>longwordhere</td><td>b</td></tr>
<tr id="src"><td>text</td><td>more text</td></tr>
</table>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitTableSectionAdd) {
SetBodyInnerHTML(R"HTML(
<table>
<tbody><tr><td>a</td><td>b</td></tr></tbody>
<tbody id="test"><tr><td>text</td><td>more text</td></tr></tbody>
</table>
<table>
<tbody id="src"><tr><td>text</td><td>more text</td></tr></tbody>
</table>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitTableSectionRemove) {
SetBodyInnerHTML(R"HTML(
<table>
<tbody id="test"><tr><td>text</td><td>more text</td></tr></tbody>
</table>
<table>
<tbody><tr><td>a</td><td>b</td></tr></tbody>
<tbody id="src"><tr><td>text</td><td>more text</td></tr></tbody>
</table>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, FragmentainerSizeChange) {
SetBodyInnerHTML(R"HTML(
<style>
.multicol { columns:2; column-fill:auto; }
.child { height:120px; }
</style>
<div class="multicol" style="height:50px;">
<div id="test" class="child"></div>
</div>
<div class="multicol" style="height:51px;">
<div id="src" class="child"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
const LayoutResult* test_result1 = test->GetCachedLayoutResult(nullptr);
ASSERT_TRUE(test_result1);
const ConstraintSpace& test_space1 =
test_result1->GetConstraintSpaceForCaching();
const auto* test_break_token1 =
To<BlockBreakToken>(test_result1->GetPhysicalFragment().GetBreakToken());
ASSERT_TRUE(test_break_token1);
const LayoutResult* test_result2 =
test->GetCachedLayoutResult(test_break_token1);
ASSERT_TRUE(test_result2);
const ConstraintSpace& test_space2 =
test_result2->GetConstraintSpaceForCaching();
const auto* test_break_token2 =
To<BlockBreakToken>(test_result2->GetPhysicalFragment().GetBreakToken());
ASSERT_TRUE(test_break_token2);
const LayoutResult* test_result3 =
test->GetCachedLayoutResult(test_break_token2);
ASSERT_TRUE(test_result3);
const ConstraintSpace& test_space3 =
test_result3->GetConstraintSpaceForCaching();
EXPECT_FALSE(test_result3->GetPhysicalFragment().GetBreakToken());
const LayoutResult* src_result1 = src->GetCachedLayoutResult(nullptr);
ASSERT_TRUE(src_result1);
const ConstraintSpace& src_space1 =
src_result1->GetConstraintSpaceForCaching();
const auto* src_break_token1 =
To<BlockBreakToken>(src_result1->GetPhysicalFragment().GetBreakToken());
ASSERT_TRUE(src_break_token1);
const LayoutResult* src_result2 =
src->GetCachedLayoutResult(src_break_token1);
ASSERT_TRUE(src_result2);
const ConstraintSpace& src_space2 =
src_result2->GetConstraintSpaceForCaching();
const auto* src_break_token2 =
To<BlockBreakToken>(src_result2->GetPhysicalFragment().GetBreakToken());
ASSERT_TRUE(src_break_token2);
const LayoutResult* src_result3 =
src->GetCachedLayoutResult(src_break_token2);
ASSERT_TRUE(src_result3);
const ConstraintSpace& src_space3 =
src_result3->GetConstraintSpaceForCaching();
EXPECT_FALSE(src_result3->GetPhysicalFragment().GetBreakToken());
// If the extrinsic constraints are unchanged, hit the cache, even if
// fragmented:
EXPECT_TRUE(TestCachedLayoutResultWithBreakToken(src, src_space1, nullptr));
EXPECT_TRUE(
TestCachedLayoutResultWithBreakToken(src, src_space2, src_break_token1));
EXPECT_TRUE(
TestCachedLayoutResultWithBreakToken(src, src_space3, src_break_token2));
// If the fragmentainer size changes, though, miss the cache:
EXPECT_FALSE(TestCachedLayoutResultWithBreakToken(src, test_space1, nullptr));
EXPECT_FALSE(TestCachedLayoutResultWithBreakToken(src, test_space2,
test_break_token1));
EXPECT_FALSE(TestCachedLayoutResultWithBreakToken(src, test_space3,
test_break_token2));
}
TEST_F(LayoutResultCachingTest, BlockOffsetChangeInFragmentainer) {
SetBodyInnerHTML(R"HTML(
<style>
.multicol { columns:2; column-fill:auto; height:100px; }
.second { height:80px; }
</style>
<div class="multicol">
<div style="height:19px;"></div>
<div id="test1" class="second"></div>
</div>
<div class="multicol">
<div style="height:20px;"></div>
<div id="test2" class="second"></div>
</div>
<div class="multicol">
<div style="height:21px;"></div>
<div id="test3" class="second"></div>
</div>
<div class="multicol">
<div style="height:10px;"></div>
<div id="src" class="second"></div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* test3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test3"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
const ConstraintSpace& test1_space =
test1->GetCachedLayoutResult(nullptr)->GetConstraintSpaceForCaching();
const ConstraintSpace& test2_space =
test2->GetCachedLayoutResult(nullptr)->GetConstraintSpaceForCaching();
const ConstraintSpace& test3_space =
test3->GetCachedLayoutResult(nullptr)->GetConstraintSpaceForCaching();
// The element is one pixel above the fragmentation line. Still unbroken. We
// can hit the cache.
EXPECT_TRUE(TestCachedLayoutResult(src, test1_space));
// The element ends exactly at the fragmentation line. Still unbroken. We can
// hit the cache.
EXPECT_TRUE(TestCachedLayoutResult(src, test2_space));
// The element crosses the fragmentation line by one pixel, so it needs to
// break. We need to miss the cache.
EXPECT_FALSE(TestCachedLayoutResult(src, test3_space));
}
TEST_F(LayoutResultCachingTest, BfcRootBlockOffsetChangeInFragmentainer) {
SetBodyInnerHTML(R"HTML(
<style>
.multicol { columns:2; column-fill:auto; height:100px; }
.second { display: flow-root; height:80px; }
</style>
<div class="multicol">
<div style="height:19px;"></div>
<div id="test1" class="second"></div>
</div>
<div class="multicol">
<div style="height:20px;"></div>
<div id="test2" class="second"></div>
</div>
<div class="multicol">
<div style="height:21px;"></div>
<div id="test3" class="second"></div>
</div>
<div class="multicol">
<div style="height:10px;"></div>
<div id="src" class="second"></div>
</div>
)HTML");
auto* test1 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test1"));
auto* test2 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test2"));
auto* test3 = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test3"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
const ConstraintSpace& test1_space =
test1->GetCachedLayoutResult(nullptr)->GetConstraintSpaceForCaching();
const ConstraintSpace& test2_space =
test2->GetCachedLayoutResult(nullptr)->GetConstraintSpaceForCaching();
const ConstraintSpace& test3_space =
test3->GetCachedLayoutResult(nullptr)->GetConstraintSpaceForCaching();
// The element is one pixel above the fragmentation line. Still unbroken. We
// can hit the cache.
EXPECT_TRUE(TestCachedLayoutResult(src, test1_space));
// The element ends exactly at the fragmentation line. Still unbroken. We can
// hit the cache.
EXPECT_TRUE(TestCachedLayoutResult(src, test2_space));
// The element crosses the fragmentation line by one pixel, so it needs to
// break. We need to miss the cache.
EXPECT_FALSE(TestCachedLayoutResult(src, test3_space));
}
TEST_F(LayoutResultCachingTest, HitBlockOffsetUnchangedInFragmentainer) {
SetBodyInnerHTML(R"HTML(
<style>
.multicol { columns:2; column-fill:auto; height:100px; }
.third { height:50px; }
</style>
<div class="multicol">
<div height="10px;"></div>
<div height="20px;"></div>
<div id="test" class="third"></div>
</div>
<div class="multicol">
<div height="20px;"></div>
<div height="10px;"></div>
<div id="src" class="third"></div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
ASSERT_NE(src->GetSingleCachedLayoutResult(), nullptr);
ASSERT_NE(test->GetSingleCachedLayoutResult(), nullptr);
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, HitNewFormattingContextInFragmentainer) {
SetBodyInnerHTML(R"HTML(
<style>
.multicol { columns:2; }
.newfc { display: flow-root; height:50px; }
</style>
<div class="multicol">
<div id="test" class="newfc"></div>
<div style="height: 100px;"></div>
</div>
<div class="multicol">
<div id="src" class="newfc"></div>
<div style="height: 90px;"></div>
</div>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
ASSERT_NE(src->GetSingleCachedLayoutResult(), nullptr);
ASSERT_NE(test->GetSingleCachedLayoutResult(), nullptr);
const ConstraintSpace& space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
EXPECT_TRUE(space.IsInitialColumnBalancingPass());
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kHit);
EXPECT_NE(result, nullptr);
}
TEST_F(LayoutResultCachingTest, MissMonolithicChangeInFragmentainer) {
SetBodyInnerHTML(R"HTML(
<style>
.multicol { columns:2; column-fill:auto; height:100px; }
.container { height:150px; }
.child { height:150px; }
</style>
<div class="multicol">
<div class="container">
<div id="test" class="child"></div>
</div>
</div>
<div class="multicol">
<div class="container" style="contain:size;">
<div id="src" class="child"></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlockFlow>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlockFlow>(GetLayoutObjectByElementId("src"));
const ConstraintSpace& src_space =
src->GetCachedLayoutResult(nullptr)->GetConstraintSpaceForCaching();
const ConstraintSpace& test_space =
test->GetCachedLayoutResult(nullptr)->GetConstraintSpaceForCaching();
EXPECT_FALSE(TestCachedLayoutResult(src, test_space));
EXPECT_FALSE(TestCachedLayoutResult(test, src_space));
}
TEST_F(LayoutResultCachingTest, MissGridIncorrectIntrinsicSize) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div style="display: flex; width: 100px; height: 200px; align-items: stretch;">
<div id="test" style="flex-grow: 1; min-height: 100px; display: grid;">
<div></div>
</div>
</div>
<div style="display: flex; width: 100px; height: 200px; align-items: start;">
<div id="src" style="flex-grow: 1; min-height: 100px; display: grid;">
<div></div>
</div>
</div>
)HTML");
auto* test = To<LayoutBlock>(GetLayoutObjectByElementId("test"));
auto* src = To<LayoutBlock>(GetLayoutObjectByElementId("src"));
LayoutCacheStatus cache_status;
ConstraintSpace space =
src->GetSingleCachedLayoutResult()->GetConstraintSpaceForCaching();
const LayoutResult* result =
TestCachedLayoutResult(test, space, &cache_status);
EXPECT_EQ(cache_status, LayoutCacheStatus::kNeedsLayout);
EXPECT_EQ(result, nullptr);
}
} // namespace
} // namespace blink