blob: 6aec1c797aa6df0ad472806dab604f84ae787530 [file] [log] [blame]
// Copyright 2018 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_layout_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.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"
namespace blink {
namespace {
// LengthResolveType indicates what type length the function is being passed
// based on its CSS property. E.g.
// kMinSize - min-width / min-height
// kMaxSize - max-width / max-height
// kMainSize - width / height
enum class LengthResolveType { kMinSize, kMaxSize, kMainSize };
inline bool InlineLengthMayChange(const ComputedStyle& style,
const Length& length,
LengthResolveType type,
const NGConstraintSpace& new_space,
const NGConstraintSpace& old_space,
const NGLayoutResult& layout_result) {
DCHECK_EQ(new_space.StretchInlineSizeIfAuto(),
old_space.StretchInlineSizeIfAuto());
bool is_unspecified =
(length.IsAuto() && type != LengthResolveType::kMinSize) ||
length.IsFitContent() || length.IsFillAvailable();
// Percentage inline margins will affect the size if the size is unspecified
// (auto and similar).
if (is_unspecified && style.MayHaveMargin() &&
(style.MarginStart().IsPercentOrCalc() ||
style.MarginEnd().IsPercentOrCalc()) &&
(new_space.PercentageResolutionInlineSize() !=
old_space.PercentageResolutionInlineSize()))
return true;
if (is_unspecified) {
if (new_space.AvailableSize().inline_size !=
old_space.AvailableSize().inline_size)
return true;
}
if (length.IsPercentOrCalc()) {
if (new_space.PercentageResolutionInlineSize() !=
old_space.PercentageResolutionInlineSize())
return true;
}
return false;
}
inline bool BlockLengthMayChange(const Length& length,
const NGConstraintSpace& new_space,
const NGConstraintSpace& old_space) {
DCHECK_EQ(new_space.StretchBlockSizeIfAuto(),
old_space.StretchBlockSizeIfAuto());
if (length.IsFillAvailable() ||
(length.IsAuto() && new_space.StretchBlockSizeIfAuto())) {
if (new_space.AvailableSize().block_size !=
old_space.AvailableSize().block_size)
return true;
}
return false;
}
bool BlockSizeMayChange(const NGBlockNode& node,
const NGConstraintSpace& new_space,
const NGConstraintSpace& old_space,
const NGLayoutResult& layout_result) {
DCHECK_EQ(new_space.IsFixedBlockSize(), old_space.IsFixedBlockSize());
DCHECK_EQ(new_space.IsFixedBlockSizeIndefinite(),
old_space.IsFixedBlockSizeIndefinite());
DCHECK_EQ(new_space.StretchBlockSizeIfAuto(),
old_space.StretchBlockSizeIfAuto());
DCHECK_EQ(new_space.TableCellChildLayoutMode(),
old_space.TableCellChildLayoutMode());
if (node.IsQuirkyAndFillsViewport())
return true;
if (new_space.IsFixedBlockSize()) {
if (new_space.AvailableSize().block_size !=
old_space.AvailableSize().block_size)
return true;
} else {
const ComputedStyle& style = node.Style();
if (BlockLengthMayChange(style.LogicalHeight(), new_space, old_space) ||
BlockLengthMayChange(style.LogicalMinHeight(), new_space, old_space) ||
BlockLengthMayChange(style.LogicalMaxHeight(), new_space, old_space))
return true;
// We only need to check if the PercentageResolutionBlockSizes match if the
// layout result has explicitly marked itself as dependent.
if (layout_result.PhysicalFragment().DependsOnPercentageBlockSize()) {
if (new_space.PercentageResolutionBlockSize() !=
old_space.PercentageResolutionBlockSize())
return true;
if (new_space.ReplacedPercentageResolutionBlockSize() !=
old_space.ReplacedPercentageResolutionBlockSize())
return true;
}
}
return false;
}
// Return true if it's possible (but not necessarily guaranteed) that the new
// constraint space will give a different size compared to the old one, when
// computed style and child content remain unchanged.
bool SizeMayChange(const NGBlockNode& node,
const NGConstraintSpace& new_space,
const NGConstraintSpace& old_space,
const NGLayoutResult& layout_result) {
DCHECK_EQ(new_space.IsFixedInlineSize(), old_space.IsFixedInlineSize());
DCHECK_EQ(new_space.StretchInlineSizeIfAuto(),
old_space.StretchInlineSizeIfAuto());
const ComputedStyle& style = node.Style();
// Go through all length properties, and, depending on length type
// (percentages, auto, etc.), check whether the constraint spaces differ in
// such a way that the resulting size *may* change. There are currently many
// possible false-positive situations here, as we don't rule out length
// changes that won't have any effect on the final size (e.g. if inline-size
// is 100px, max-inline-size is 50%, and percentage resolution inline size
// changes from 1000px to 500px). If the constraint space has "fixed" size in
// a dimension, we can skip checking properties in that dimension and just
// look for available size changes, since that's how a "fixed" constraint
// space works.
if (new_space.IsFixedInlineSize()) {
if (new_space.AvailableSize().inline_size !=
old_space.AvailableSize().inline_size)
return true;
} else {
if (InlineLengthMayChange(style, style.LogicalWidth(),
LengthResolveType::kMainSize, new_space,
old_space, layout_result) ||
InlineLengthMayChange(style, style.LogicalMinWidth(),
LengthResolveType::kMinSize, new_space, old_space,
layout_result) ||
InlineLengthMayChange(style, style.LogicalMaxWidth(),
LengthResolveType::kMaxSize, new_space, old_space,
layout_result))
return true;
}
if (style.MayHavePadding() &&
new_space.PercentageResolutionInlineSize() !=
old_space.PercentageResolutionInlineSize()) {
// Percentage-based padding is resolved against the inline content box size
// of the containing block.
if (style.PaddingTop().IsPercentOrCalc() ||
style.PaddingRight().IsPercentOrCalc() ||
style.PaddingBottom().IsPercentOrCalc() ||
style.PaddingLeft().IsPercentOrCalc())
return true;
}
return BlockSizeMayChange(node, new_space, old_space, layout_result);
}
// Given the pre-computed |fragment_geometry| calcuates the
// |NGLayoutCacheStatus| based on this sizing information. Returns:
// - |NGLayoutCacheStatus::kNeedsLayout| if the |new_space| will produce a
// different sized fragment, or if any %-block-size children will change
// size.
// - |NGLayoutCacheStatus::kNeedsSimplifiedLayout| if the block-size of the
// fragment will change, *without* affecting any descendants (no descendants
// have %-block-sizes).
// - |NGLayoutCacheStatus::kHit| otherwise.
NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry(
const NGBlockNode& node,
const NGFragmentGeometry& fragment_geometry,
const NGLayoutResult& layout_result,
const NGConstraintSpace& new_space,
const NGConstraintSpace& old_space) {
const ComputedStyle& style = node.Style();
const NGPhysicalBoxFragment& physical_fragment =
To<NGPhysicalBoxFragment>(layout_result.PhysicalFragment());
NGBoxFragment fragment(style.GetWritingDirection(), physical_fragment);
if (fragment_geometry.border_box_size.inline_size != fragment.InlineSize())
return NGLayoutCacheStatus::kNeedsLayout;
if (style.MayHavePadding() && fragment_geometry.padding != fragment.Padding())
return NGLayoutCacheStatus::kNeedsLayout;
// Tables are special - we can't determine the final block-size ahead of time
// (or based on the previous intrinsic size).
// Instead if the block-size *may* change, force a layout. If we definitely
// know the block-size won't change (the size constraints haven't changed) we
// can hit the cache.
//
// *NOTE* - any logic below this branch shouldn't apply to tables.
if (node.IsTable()) {
if (!new_space.AreBlockSizeConstraintsEqual(old_space) ||
BlockSizeMayChange(node, new_space, old_space, layout_result))
return NGLayoutCacheStatus::kNeedsLayout;
return NGLayoutCacheStatus::kHit;
}
LayoutUnit block_size = fragment_geometry.border_box_size.block_size;
bool is_initial_block_size_indefinite = block_size == kIndefiniteSize;
if (is_initial_block_size_indefinite) {
LayoutUnit intrinsic_block_size = layout_result.IntrinsicBlockSize();
if (node.IsFlexibleBox()) {
// Flex-boxes can have their children calculate their size based in their
// parent's final block-size. E.g.
// <div style="display: flex;">
// <div style="display: flex;">
// <!-- Child will stretch to the parent's fixed block-size -->
// <div></div>
// </div>
// </div>
// <div style="display: flex;">
// <div style="display: flex; flex-direction: column;">
// <!-- Child will grow to the parent's fixed block-size -->
// <div style="flex: 1;"></div>
// </div>
// </div>
//
// If the previous |layout_result| was produced by a space which had a
// fixed block-size we can't use |intrinsic_block_size| for determining
// the new block-size.
//
// TODO(ikilpatrick): Similar to %-block-size descendants we could store
// a bit on the |NGLayoutResult| which indicates if it had a child which
// sized itself based on the parent's block-size.
// We should consider this optimization if we are missing this cache
// often within this branch (and could have re-used the result).
// TODO(ikilaptrick): This may occur for other layout modes, e.g.
// grid/custom-layout/etc.
if (old_space.IsFixedBlockSize())
intrinsic_block_size = kIndefiniteSize;
// The intrinsic size of flex-boxes can depend on the %-block-size. This
// occurs when:
// - A column flex-box has "max-height: 100%" (or similar) on itself.
// - A row flex-box has "height: 100%" (or similar) and children which
// stretch to this size.
//
// Due to this we can't use the |intrinsic_block_size| value, as the
// following |block_size| calculation would be incorrect.
// TODO(dgrogan): We can hit the cache here for row flexboxes when they
// don't have stretchy children.
if (physical_fragment.DependsOnPercentageBlockSize() &&
new_space.PercentageResolutionBlockSize() !=
old_space.PercentageResolutionBlockSize())
intrinsic_block_size = kIndefiniteSize;
}
block_size = ComputeBlockSizeForFragment(
new_space, style, fragment_geometry.border + fragment_geometry.padding,
intrinsic_block_size, fragment_geometry.border_box_size.inline_size,
node.ShouldBeConsideredAsReplaced());
if (block_size == kIndefiniteSize)
return NGLayoutCacheStatus::kNeedsLayout;
}
bool is_block_size_equal = block_size == fragment.BlockSize();
if (!is_block_size_equal) {
// Only block-flow supports changing the block-size for simplified layout.
if (!node.IsBlockFlow() || node.IsLayoutNGCustom())
return NGLayoutCacheStatus::kNeedsLayout;
// Fieldsets stretch their content to the final block-size, which might
// affect scrollbars.
if (node.IsFieldsetContainer())
return NGLayoutCacheStatus::kNeedsLayout;
// Textfields are block-flow, but we can't apply simplified layout due to
// -internal-align-self-block.
// TODO(tkent): We could store a bit on the |NGLayoutResult| which
// indicates if we have a child with "-internal-align-self-block:center".
if (node.IsTextField())
return NGLayoutCacheStatus::kNeedsLayout;
// If we are the document or body element in quirks mode, changing our size
// means that a scrollbar was added/removed. Require full layout.
if (node.IsQuirkyAndFillsViewport())
return NGLayoutCacheStatus::kNeedsLayout;
// If a block (within a formatting-context) changes to/from an empty-block,
// margins may collapse through this node, requiring full layout. We
// approximate this check by checking if the block-size is/was zero.
if (!physical_fragment.IsFormattingContextRoot() &&
!block_size != !fragment.BlockSize())
return NGLayoutCacheStatus::kNeedsLayout;
}
if (layout_result.HasDescendantThatDependsOnPercentageBlockSize()) {
// %-block-size children of flex-items sometimes don't resolve their
// percentages against a fixed block-size.
// We miss the cache if the %-resolution block-size changes from indefinite
// to definite (or visa-versa).
bool is_old_initial_block_size_indefinite =
layout_result.IsInitialBlockSizeIndefinite();
if (is_old_initial_block_size_indefinite !=
is_initial_block_size_indefinite)
return NGLayoutCacheStatus::kNeedsLayout;
// %-block-size children of table-cells have different behaviour if they
// are in the "measure" or "layout" phase.
// Instead of trying to capture that logic here, we always miss the cache.
if (new_space.IsTableCell() &&
new_space.IsFixedBlockSize() != old_space.IsFixedBlockSize())
return NGLayoutCacheStatus::kNeedsLayout;
// If our initial block-size is definite, we know that if we change our
// block-size we'll affect any descendant that depends on the resulting
// percentage block-size.
if (!is_block_size_equal && !is_initial_block_size_indefinite)
return NGLayoutCacheStatus::kNeedsLayout;
DCHECK(is_block_size_equal || is_initial_block_size_indefinite);
// At this point we know that either we have the same block-size for our
// fragment, or our initial block-size was indefinite.
//
// The |NGPhysicalContainerFragment::DependsOnPercentageBlockSize| flag
// will returns true if we are in quirks mode, and have a descendant that
// depends on a percentage block-size, however it will also return true if
// the node itself depends on the %-block-size.
//
// As we only care about the quirks-mode %-block-size behaviour we remove
// this false-positive by checking if we have an initial indefinite
// block-size.
if (is_initial_block_size_indefinite &&
physical_fragment.DependsOnPercentageBlockSize()) {
DCHECK(is_old_initial_block_size_indefinite);
if (new_space.PercentageResolutionBlockSize() !=
old_space.PercentageResolutionBlockSize())
return NGLayoutCacheStatus::kNeedsLayout;
if (new_space.ReplacedPercentageResolutionBlockSize() !=
old_space.ReplacedPercentageResolutionBlockSize())
return NGLayoutCacheStatus::kNeedsLayout;
}
}
// Table-cells with vertical alignment might shift their contents if the
// block-size changes.
if (new_space.IsTableCell()) {
DCHECK(old_space.IsTableCell());
switch (style.VerticalAlign()) {
case EVerticalAlign::kTop:
// Do nothing special for 'top' vertical alignment.
break;
case EVerticalAlign::kBaselineMiddle:
case EVerticalAlign::kSub:
case EVerticalAlign::kSuper:
case EVerticalAlign::kTextTop:
case EVerticalAlign::kTextBottom:
case EVerticalAlign::kLength:
// All of the above are treated as 'baseline' for the purposes of
// table-cell vertical alignment.
case EVerticalAlign::kBaseline: {
auto new_alignment_baseline = new_space.TableCellAlignmentBaseline();
auto old_alignment_baseline = old_space.TableCellAlignmentBaseline();
// Do nothing if neither alignment baseline is set.
if (!new_alignment_baseline && !old_alignment_baseline)
break;
// If we only have an old alignment baseline set, we need layout, as we
// can't determine where the un-adjusted baseline is.
if (!new_alignment_baseline && old_alignment_baseline)
return NGLayoutCacheStatus::kNeedsLayout;
// We've been provided a new alignment baseline, just check that it
// matches the previously generated baseline.
if (!old_alignment_baseline) {
if (*new_alignment_baseline != physical_fragment.Baseline())
return NGLayoutCacheStatus::kNeedsLayout;
break;
}
// If the alignment baselines differ at this stage, we need layout.
if (*new_alignment_baseline != *old_alignment_baseline)
return NGLayoutCacheStatus::kNeedsLayout;
break;
}
case EVerticalAlign::kMiddle:
case EVerticalAlign::kBottom:
// 'middle', and 'bottom' vertical alignment depend on the block-size.
if (!is_block_size_equal)
return NGLayoutCacheStatus::kNeedsLayout;
break;
}
}
// If we've reached here we know that we can potentially "stretch"/"shrink"
// ourselves without affecting any of our children.
// In that case we may be able to perform "simplified" layout.
DCHECK(!node.IsTable());
return is_block_size_equal ? NGLayoutCacheStatus::kHit
: NGLayoutCacheStatus::kNeedsSimplifiedLayout;
}
bool IntrinsicSizeWillChange(
const NGBlockNode& node,
const NGLayoutResult& cached_layout_result,
const NGConstraintSpace& new_space,
base::Optional<NGFragmentGeometry>* fragment_geometry) {
const ComputedStyle& style = node.Style();
if (new_space.StretchInlineSizeIfAuto() && !NeedMinMaxSize(style))
return false;
if (!*fragment_geometry)
*fragment_geometry = CalculateInitialFragmentGeometry(new_space, node);
LayoutUnit inline_size = NGFragment(style.GetWritingDirection(),
cached_layout_result.PhysicalFragment())
.InlineSize();
if ((*fragment_geometry)->border_box_size.inline_size != inline_size)
return true;
return false;
}
} // namespace
NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatus(
const NGBlockNode& node,
const NGLayoutResult& cached_layout_result,
const NGConstraintSpace& new_space,
base::Optional<NGFragmentGeometry>* fragment_geometry) {
DCHECK_EQ(cached_layout_result.Status(), NGLayoutResult::kSuccess);
const NGConstraintSpace& old_space =
cached_layout_result.GetConstraintSpaceForCaching();
if (!new_space.MaySkipLayout(old_space))
return NGLayoutCacheStatus::kNeedsLayout;
if (new_space.AreInlineSizeConstraintsEqual(old_space) &&
new_space.AreBlockSizeConstraintsEqual(old_space)) {
// It is possible that our intrinsic size has changed, check for that here.
// TODO(cbiesinger): Investigate why this check doesn't apply to
// |MaySkipLegacyLayout|.
if (IntrinsicSizeWillChange(node, cached_layout_result, new_space,
fragment_geometry))
return NGLayoutCacheStatus::kNeedsLayout;
// We don't have to check our style if we know the constraint space sizes
// will remain the same.
if (new_space.AreSizesEqual(old_space))
return NGLayoutCacheStatus::kHit;
// TODO(ikilpatrick): Always miss the cache for tables whose block
// size-constraints change.
if (!SizeMayChange(node, new_space, old_space, cached_layout_result))
return NGLayoutCacheStatus::kHit;
}
if (!*fragment_geometry)
*fragment_geometry = CalculateInitialFragmentGeometry(new_space, node);
return CalculateSizeBasedLayoutCacheStatusWithGeometry(
node, **fragment_geometry, cached_layout_result, new_space, old_space);
}
bool MaySkipLegacyLayout(const NGBlockNode& node,
const NGLayoutResult& cached_layout_result,
const NGConstraintSpace& new_space) {
DCHECK_EQ(cached_layout_result.Status(), NGLayoutResult::kSuccess);
const NGConstraintSpace& old_space =
cached_layout_result.GetConstraintSpaceForCaching();
if (!new_space.MaySkipLayout(old_space))
return false;
if (!new_space.AreInlineSizeConstraintsEqual(old_space))
return false;
if (!new_space.AreBlockSizeConstraintsEqual(old_space))
return false;
if (new_space.AreSizesEqual(old_space))
return true;
if (SizeMayChange(node, new_space, old_space, cached_layout_result))
return false;
return true;
}
bool MaySkipLayoutWithinBlockFormattingContext(
const NGLayoutResult& cached_layout_result,
const NGConstraintSpace& new_space,
base::Optional<LayoutUnit>* bfc_block_offset,
LayoutUnit* block_offset_delta,
NGMarginStrut* end_margin_strut) {
DCHECK_EQ(cached_layout_result.Status(), NGLayoutResult::kSuccess);
DCHECK(bfc_block_offset);
DCHECK(block_offset_delta);
DCHECK(end_margin_strut);
const NGConstraintSpace& old_space =
cached_layout_result.GetConstraintSpaceForCaching();
bool is_margin_strut_equal =
old_space.MarginStrut() == new_space.MarginStrut();
LayoutUnit old_clearance_offset = old_space.ClearanceOffset();
LayoutUnit new_clearance_offset = new_space.ClearanceOffset();
// Determine if we can reuse a result if it was affected by clearance.
bool is_pushed_by_floats = cached_layout_result.IsPushedByFloats();
if (is_pushed_by_floats) {
DCHECK(old_space.HasFloats());
// We don't attempt to reuse the cached result if our margins have changed.
if (!is_margin_strut_equal)
return false;
// We don't attempt to reuse the cached result if the clearance offset
// differs from the final BFC-block-offset.
//
// The |is_pushed_by_floats| flag is also used by nodes who have a *child*
// which was pushed by floats. In this case the node may not have a
// BFC-block-offset or one equal to the clearance offset.
if (!cached_layout_result.BfcBlockOffset() ||
*cached_layout_result.BfcBlockOffset() != old_space.ClearanceOffset())
return false;
// We only reuse the cached result if the delta between the
// BFC-block-offset, and the clearance offset grows or remains the same. If
// it shrinks it may not be affected by clearance anymore as a margin may
// push the fragment below the clearance offset instead.
//
// TODO(layout-dev): If we track if any margins affected this calculation
// (with an additional bit on the layout result) we could potentially skip
// this check.
if (old_clearance_offset - old_space.BfcOffset().block_offset >
new_clearance_offset - new_space.BfcOffset().block_offset) {
return false;
}
}
// We can't reuse the layout result if the subtree modified its incoming
// margin-strut, and the incoming margin-strut has changed. E.g.
// <div style="margin-top: 5px;"> <!-- changes to 15px -->
// <div style="margin-top: 10px;"></div>
// text
// </div>
if (cached_layout_result.SubtreeModifiedMarginStrut() &&
!is_margin_strut_equal)
return false;
const auto& physical_fragment =
To<NGPhysicalBoxFragment>(cached_layout_result.PhysicalFragment());
// Check we have a descendant that *may* be positioned above the block-start
// edge. We abort if either the old or new space has floats, as we don't keep
// track of how far above the child could be. This case is relatively rare,
// and only occurs with negative margins.
if (physical_fragment.MayHaveDescendantAboveBlockStart() &&
(old_space.HasFloats() || new_space.HasFloats()))
return false;
// Self collapsing blocks have different "shifting" rules applied to them.
if (cached_layout_result.IsSelfCollapsing()) {
DCHECK(!is_pushed_by_floats);
// The "expected" BFC block-offset is where adjoining objects will be
// placed (which may be wrong due to adjoining margins).
LayoutUnit old_expected = old_space.ExpectedBfcBlockOffset();
LayoutUnit new_expected = new_space.ExpectedBfcBlockOffset();
// If we have any adjoining object descendants (floats), we need to ensure
// that their position wouldn't be impacted by any preceding floats.
if (physical_fragment.HasAdjoiningObjectDescendants()) {
// Check if the previous position intersects with any floats.
if (old_expected <
old_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
return false;
// Check if the new position intersects with any floats.
if (new_expected <
new_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
return false;
}
*block_offset_delta = new_expected - old_expected;
// Self-collapsing blocks with a "forced" BFC block-offset input receive a
// "resolved" BFC block-offset on their layout result.
*bfc_block_offset = new_space.ForcedBfcBlockOffset();
// If this sub-tree didn't append any margins to the incoming margin-strut,
// the new "start" margin-strut becomes the new "end" margin-strut (as we
// are self-collapsing).
if (!cached_layout_result.SubtreeModifiedMarginStrut()) {
*end_margin_strut = new_space.MarginStrut();
} else {
DCHECK(is_margin_strut_equal);
}
return true;
}
// We can now try to adjust the BFC block-offset for regular blocks.
DCHECK(*bfc_block_offset);
DCHECK_EQ(old_space.AncestorHasClearancePastAdjoiningFloats(),
new_space.AncestorHasClearancePastAdjoiningFloats());
bool ancestor_has_clearance_past_adjoining_floats =
new_space.AncestorHasClearancePastAdjoiningFloats();
if (ancestor_has_clearance_past_adjoining_floats) {
// The subsequent code will break if these invariants don't hold true.
DCHECK(old_space.ForcedBfcBlockOffset());
DCHECK(new_space.ForcedBfcBlockOffset());
DCHECK_EQ(*old_space.ForcedBfcBlockOffset(), old_clearance_offset);
DCHECK_EQ(*new_space.ForcedBfcBlockOffset(), new_clearance_offset);
} else {
// New formatting-contexts have (potentially) complex positioning logic. In
// some cases they will resolve a BFC block-offset twice (with their margins
// adjoining, and not adjoining), resulting in two different "forced" BFC
// block-offsets. We don't allow caching as we can't determine which pass a
// layout result belongs to for this case.
if (old_space.ForcedBfcBlockOffset() != new_space.ForcedBfcBlockOffset())
return false;
}
// Check if the previous position intersects with any floats.
if (**bfc_block_offset <
old_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
return false;
if (is_pushed_by_floats || ancestor_has_clearance_past_adjoining_floats) {
// If we've been pushed by floats, we assume the new clearance offset.
DCHECK_EQ(**bfc_block_offset, old_clearance_offset);
*block_offset_delta = new_clearance_offset - old_clearance_offset;
*bfc_block_offset = new_clearance_offset;
} else if (is_margin_strut_equal) {
// If our incoming margin-strut is equal, we are just shifted by the BFC
// block-offset amount.
*block_offset_delta =
new_space.BfcOffset().block_offset - old_space.BfcOffset().block_offset;
*bfc_block_offset = **bfc_block_offset + *block_offset_delta;
} else {
// If our incoming margin-strut isn't equal, we need to account for the
// difference in the incoming margin-struts.
#if DCHECK_IS_ON()
DCHECK(!cached_layout_result.SubtreeModifiedMarginStrut());
LayoutUnit old_bfc_block_offset =
old_space.BfcOffset().block_offset + old_space.MarginStrut().Sum();
DCHECK_EQ(old_bfc_block_offset, **bfc_block_offset);
#endif
LayoutUnit new_bfc_block_offset =
new_space.BfcOffset().block_offset + new_space.MarginStrut().Sum();
*block_offset_delta = new_bfc_block_offset - **bfc_block_offset;
*bfc_block_offset = **bfc_block_offset + *block_offset_delta;
}
// Check if the new position intersects with any floats.
if (**bfc_block_offset <
new_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
return false;
return true;
}
} // namespace blink