Commit a62cd235 authored by Ian Kilpatrick's avatar Ian Kilpatrick Committed by Commit Bot

[LayoutNG] Disable first-tier OOF-cache when borders/scrollbars are present.

The first-tier OOF-cache attempts to skip all the OOF sizing and positioning
work required, as the other cache simply compares the constraint spaces
which can be (relatively) expensive to determine.

This first-tier cache works by comparing the available-size given to the
OOF-positioned descendant. If equal we can skip the OOF-positioned sizing
and positioning steps.

This mostly worked, however had an issue where the containing-block may have
added/removed a borders/scrollbars, but the available-size given remained
the same.

This resulted in an incorrectly positioned OOF-positioned node.

This fix disallows the first-tier cache when borders/scrollbars are present.
This is sufficiently rare that this cache still has a relatively high
hit-rate and is still relative cheap to calculate.

Bug: 1013866
Change-Id: I71d41d2c4b47bcff6412f395db2deabf2f6f3a8f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1891768
Commit-Queue: Ian Kilpatrick <ikilpatrick@chromium.org>
Reviewed-by: default avatarAleks Totic <atotic@chromium.org>
Reviewed-by: default avatarMorten Stenshorne <mstensho@chromium.org>
Cr-Commit-Position: refs/heads/master@{#714394}
parent f09edbca
...@@ -439,6 +439,11 @@ NGBlockNode::CachedLayoutResultForOutOfFlowPositioned( ...@@ -439,6 +439,11 @@ NGBlockNode::CachedLayoutResultForOutOfFlowPositioned(
if (!cached_layout_result) if (!cached_layout_result)
return nullptr; return nullptr;
// The containing-block may have borders/scrollbars which might change
// between passes affecting the final position.
if (!cached_layout_result->CanUseOutOfFlowPositionedFirstTierCache())
return nullptr;
// TODO(layout-dev): There are potentially more cases where we can reuse this // TODO(layout-dev): There are potentially more cases where we can reuse this
// layout result. // layout result.
// E.g. when we have a fixed-length top position constraint (top: 5px), we // E.g. when we have a fixed-length top position constraint (top: 5px), we
......
...@@ -53,7 +53,11 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode { ...@@ -53,7 +53,11 @@ class CORE_EXPORT NGBlockNode final : public NGLayoutInputNode {
// //
// As OOF-positioned objects have their position, and size computed // As OOF-positioned objects have their position, and size computed
// pre-layout, we need a way to quickly determine if we need to perform this // pre-layout, we need a way to quickly determine if we need to perform this
// work. This method compares the containing-block size to determine this. // work.
//
// We have this "first-tier" cache explicitly for this purpose.
// This method compares the containing-block size to determine if we can skip
// the position, and size calculation.
// //
// If the containing-block size hasn't changed, and we are layout-clean we // If the containing-block size hasn't changed, and we are layout-clean we
// can reuse the previous layout result. // can reuse the previous layout result.
......
...@@ -68,6 +68,12 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { ...@@ -68,6 +68,12 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> {
: oof_positioned_offset_; : oof_positioned_offset_;
} }
// Returns if we can use the first-tier OOF-positioned cache.
bool CanUseOutOfFlowPositionedFirstTierCache() const {
DCHECK(physical_fragment_->IsOutOfFlowPositioned());
return bitfields_.can_use_out_of_flow_positioned_first_tier_cache;
}
const NGUnpositionedListMarker UnpositionedListMarker() const { const NGUnpositionedListMarker UnpositionedListMarker() const {
return HasRareData() ? rare_data_->unpositioned_list_marker return HasRareData() ? rare_data_->unpositioned_list_marker
: NGUnpositionedListMarker(); : NGUnpositionedListMarker();
...@@ -243,13 +249,18 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { ...@@ -243,13 +249,18 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> {
protected: protected:
friend class NGOutOfFlowLayoutPart; friend class NGOutOfFlowLayoutPart;
void SetOutOfFlowPositionedOffset(const LogicalOffset& offset) { void SetOutOfFlowPositionedOffset(
const LogicalOffset& offset,
bool can_use_out_of_flow_positioned_first_tier_cache) {
// OOF-positioned nodes *must* always have an initial BFC-offset. // OOF-positioned nodes *must* always have an initial BFC-offset.
DCHECK(layout_result_->physical_fragment_->IsOutOfFlowPositioned()); DCHECK(layout_result_->physical_fragment_->IsOutOfFlowPositioned());
DCHECK_EQ(layout_result_->BfcLineOffset(), LayoutUnit()); DCHECK_EQ(layout_result_->BfcLineOffset(), LayoutUnit());
DCHECK_EQ(layout_result_->BfcBlockOffset().value_or(LayoutUnit()), DCHECK_EQ(layout_result_->BfcBlockOffset().value_or(LayoutUnit()),
LayoutUnit()); LayoutUnit());
layout_result_->bitfields_
.can_use_out_of_flow_positioned_first_tier_cache =
can_use_out_of_flow_positioned_first_tier_cache;
layout_result_->bitfields_.has_oof_positioned_offset = true; layout_result_->bitfields_.has_oof_positioned_offset = true;
if (layout_result_->HasRareData()) if (layout_result_->HasRareData())
layout_result_->rare_data_->oof_positioned_offset = offset; layout_result_->rare_data_->oof_positioned_offset = offset;
...@@ -376,6 +387,7 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { ...@@ -376,6 +387,7 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> {
: has_rare_data(false), : has_rare_data(false),
has_rare_data_exclusion_space(false), has_rare_data_exclusion_space(false),
has_oof_positioned_offset(false), has_oof_positioned_offset(false),
can_use_out_of_flow_positioned_first_tier_cache(false),
is_bfc_block_offset_nullopt(false), is_bfc_block_offset_nullopt(false),
has_forced_break(false), has_forced_break(false),
is_self_collapsing(is_self_collapsing), is_self_collapsing(is_self_collapsing),
...@@ -392,6 +404,7 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> { ...@@ -392,6 +404,7 @@ class CORE_EXPORT NGLayoutResult : public RefCounted<NGLayoutResult> {
unsigned has_rare_data : 1; unsigned has_rare_data : 1;
unsigned has_rare_data_exclusion_space : 1; unsigned has_rare_data_exclusion_space : 1;
unsigned has_oof_positioned_offset : 1; unsigned has_oof_positioned_offset : 1;
unsigned can_use_out_of_flow_positioned_first_tier_cache : 1;
unsigned is_bfc_block_offset_nullopt : 1; unsigned is_bfc_block_offset_nullopt : 1;
unsigned has_forced_break : 1; unsigned has_forced_break : 1;
......
...@@ -90,7 +90,8 @@ NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart( ...@@ -90,7 +90,8 @@ NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart(
container_builder_(container_builder), container_builder_(container_builder),
writing_mode_(container_style.GetWritingMode()), writing_mode_(container_style.GetWritingMode()),
is_absolute_container_(is_absolute_container), is_absolute_container_(is_absolute_container),
is_fixed_container_(is_fixed_container) { is_fixed_container_(is_fixed_container),
allow_first_tier_oof_cache_(border_scrollbar.IsEmpty()) {
if (!container_builder->HasOutOfFlowPositionedCandidates() && if (!container_builder->HasOutOfFlowPositionedCandidates() &&
!To<LayoutBlock>(container_builder_->GetLayoutObject()) !To<LayoutBlock>(container_builder_->GetLayoutObject())
->HasPositionedObjects()) ->HasPositionedObjects())
...@@ -460,10 +461,13 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutCandidate( ...@@ -460,10 +461,13 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutCandidate(
// Determine if we need to actually run the full OOF-positioned sizing, and // Determine if we need to actually run the full OOF-positioned sizing, and
// positioning algorithm. // positioning algorithm.
// //
// When this candidate has an inline container, the container may move without // The first-tier cache compares the given available-size. However we can't
// setting |NeedsLayout()| to the candidate and that there are cases where the // reuse the result if the |ContainingBlockInfo::container_offset| may change.
// cache validity cannot be determined. // This can occur when:
if (!candidate.inline_container) { // - The default containing-block has borders and/or scrollbars.
// - The candidate has an inline container (instead of the default
// containing-block).
if (allow_first_tier_oof_cache_ && !candidate.inline_container) {
LogicalSize container_content_size_in_candidate_writing_mode = LogicalSize container_content_size_in_candidate_writing_mode =
container_physical_content_size.ConvertToLogical( container_physical_content_size.ConvertToLogical(
candidate_writing_mode); candidate_writing_mode);
...@@ -732,7 +736,8 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout( ...@@ -732,7 +736,8 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout(
offset.inline_offset = *y; offset.inline_offset = *y;
} }
layout_result->GetMutableForOutOfFlow().SetOutOfFlowPositionedOffset(offset); layout_result->GetMutableForOutOfFlow().SetOutOfFlowPositionedOffset(
offset, allow_first_tier_oof_cache_);
return layout_result; return layout_result;
} }
...@@ -740,8 +745,8 @@ bool NGOutOfFlowLayoutPart::IsContainingBlockForCandidate( ...@@ -740,8 +745,8 @@ bool NGOutOfFlowLayoutPart::IsContainingBlockForCandidate(
const NGLogicalOutOfFlowPositionedNode& candidate) { const NGLogicalOutOfFlowPositionedNode& candidate) {
EPosition position = candidate.node.Style().GetPosition(); EPosition position = candidate.node.Style().GetPosition();
// Candidates whose containing block is inline are always positioned // Candidates whose containing block is inline are always positioned inside
// inside closest parent block flow. // closest parent block flow.
if (candidate.inline_container) { if (candidate.inline_container) {
DCHECK( DCHECK(
candidate.node.Style().GetPosition() == EPosition::kAbsolute && candidate.node.Style().GetPosition() == EPosition::kAbsolute &&
...@@ -765,15 +770,13 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::GenerateFragment( ...@@ -765,15 +770,13 @@ scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::GenerateFragment(
const LogicalSize& container_content_size_in_candidate_writing_mode, const LogicalSize& container_content_size_in_candidate_writing_mode,
const base::Optional<LayoutUnit>& block_estimate, const base::Optional<LayoutUnit>& block_estimate,
const NGLogicalOutOfFlowPosition& node_position) { const NGLogicalOutOfFlowPosition& node_position) {
// As the |block_estimate| is always in the node's writing mode, we // As the |block_estimate| is always in the node's writing mode, we build the
// build the constraint space in the node's writing mode. // constraint space in the node's writing mode.
WritingMode writing_mode = node.Style().GetWritingMode(); WritingMode writing_mode = node.Style().GetWritingMode();
LayoutUnit inline_size = node_position.size.inline_size; LayoutUnit inline_size = node_position.size.inline_size;
LayoutUnit block_size = LayoutUnit block_size = block_estimate.value_or(
block_estimate container_content_size_in_candidate_writing_mode.block_size);
? *block_estimate
: container_content_size_in_candidate_writing_mode.block_size;
LogicalSize available_size(inline_size, block_size); LogicalSize available_size(inline_size, block_size);
......
...@@ -131,6 +131,7 @@ class CORE_EXPORT NGOutOfFlowLayoutPart { ...@@ -131,6 +131,7 @@ class CORE_EXPORT NGOutOfFlowLayoutPart {
const WritingMode writing_mode_; const WritingMode writing_mode_;
bool is_absolute_container_; bool is_absolute_container_;
bool is_fixed_container_; bool is_fixed_container_;
bool allow_first_tier_oof_cache_;
}; };
} // namespace blink } // namespace blink
......
<!DOCTYPE html>
<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
<link rel="help" href="https://www.w3.org/TR/css-position-3/" />
<meta name="assert" content="This test checks that a dynamic change padding-box location, but no change in available-size positions correctly an absolute child correctly."/>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="target" style="position: relative; background: red; margin: 25px; width: 50px; height: 50px;">
<div style="position: absolute; top: 0; left: 0; width: 50px; height: 50px; background: green;"></div>
</div>
<script>
document.body.offsetTop;
const target = document.getElementById('target');
target.style.border = '25px solid green';
target.style.margin = '0';
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment