[LayoutNG] Fix stability issues with NGBlockLayoutAlgorithm.
...we had all the right concepts, but we're holding them the wrong way.
This patch does a few inter-related things to ensure we don't have
different parts of the block layout algorithm fighting each other.
Previously we could end up in a state where a child believed it should
be placed at a particular BFC-block-offset, and the parent disagreeing
with that. This caused us to perform three layouts, and fail the
DCHECK_EQ(layout_result->Status(), kSuccess) check.
(if we did the layouts in a loop, this would otherwise result in a
timeout).
This introduces/changes:
- NGLayoutResult::IsEmptyBlock This flag is determined at the very
end of the layout algorithm. If we didn't resolve our BFC
block-offset, or were an empty-inline line-box we get this flag.
This flag is more stable than checking if the
NGLayoutResult::BlockBfcOffset had a value as this would sometimes
be present for empty-blocks.
- Changes the NGConstraintSpace::FloatsBfcBlockOffset to
NGConstraintSpace::ForcedBfcBlockOffset. This is used in *almost*
an identical way, except that:
- Empty-blocks will always set their position to this.
- Non-empty-blocks will typically also set their position to this
(unless shifted by clearance).
- AdjoiningFloatsTypes are now "passed" parent->child->sibling much
like exclusion spaces are. Only at the end of an algorithm do we
determine if they should be "passed" up to a parent based on if it
is an empty block or not.
This helps in determining clearance past adjoining floats.
- Determining the BFC-block-offset when a child has aborted now has a
more complex check to determine if the current layout should be
considered as "clearing" floats also.
- Removes the "forced-clearance" flag, in favour of the "forced" BFC
block-offset, and adds the "ancestor has clearance past adjoining
floats" flag.
Bug: 962344, 962300
Change-Id: Ife4030847f2e2b97aa5726072fadf581696ab8e7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1611111
Commit-Queue: Ian Kilpatrick <ikilpatrick@chromium.org>
Commit-Queue: Emil A Eklund <eae@chromium.org>
Reviewed-by: Morten Stenshorne <mstensho@chromium.org>
Reviewed-by: Emil A Eklund <eae@chromium.org>
Reviewed-by: Koji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#661462}
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index 3f5c0cda..78ef986 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -349,6 +349,7 @@
// Even if we have something in-flow, it may just be empty items that
// shouldn't trigger creation of a line. Exit now if that's the case.
if (line_info->IsEmptyLine()) {
+ container_builder_.SetIsEmptyBlock();
container_builder_.SetIsEmptyLineBox();
container_builder_.SetBaseDirection(line_info->BaseDirection());
container_builder_.AddChildren(line_box_);
@@ -569,9 +570,8 @@
bool is_empty_inline = Node().IsEmptyInline();
LayoutUnit bfc_block_offset = line_info.BfcOffset().block_offset;
- if (is_empty_inline && ConstraintSpace().FloatsBfcBlockOffset()) {
- bfc_block_offset = *ConstraintSpace().FloatsBfcBlockOffset();
- }
+ if (is_empty_inline && ConstraintSpace().ForcedBfcBlockOffset())
+ bfc_block_offset = *ConstraintSpace().ForcedBfcBlockOffset();
LayoutUnit bfc_line_offset = container_builder_.BfcLineOffset();
@@ -917,6 +917,11 @@
// TODO(ikilpatrick): Move this into ng_block_layout_algorithm.
container_builder_.SetBlockSize(
ComputeContentSize(line_info, exclusion_space, line_height));
+
+ // As we aren't an empty inline we should have correctly placed all
+ // our adjoining floats, and shouldn't propagate this information
+ // to siblings.
+ container_builder_.ResetAdjoiningFloatTypes();
}
break;
}
@@ -957,13 +962,13 @@
? kFloatTypeLeft
: kFloatTypeRight);
- // If we are an empty inline, and don't have the special floats BFC
+ // If we are an empty inline, and don't have the special forced BFC
// block-offset yet, there is no way to position any floats.
- if (is_empty_inline && !ConstraintSpace().FloatsBfcBlockOffset())
+ if (is_empty_inline && !ConstraintSpace().ForcedBfcBlockOffset())
continue;
LayoutUnit origin_bfc_block_offset =
- is_empty_inline ? *ConstraintSpace().FloatsBfcBlockOffset()
+ is_empty_inline ? *ConstraintSpace().ForcedBfcBlockOffset()
: ConstraintSpace().BfcOffset().block_offset;
NGPositionedFloat positioned_float = PositionFloat(
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index 316199d..5d8417640 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -71,8 +71,7 @@
bool ApplyClearance(const NGConstraintSpace& constraint_space,
LayoutUnit* bfc_block_offset) {
if (constraint_space.HasClearanceOffset() &&
- (*bfc_block_offset < constraint_space.ClearanceOffset() ||
- constraint_space.ShouldForceClearance())) {
+ *bfc_block_offset < constraint_space.ClearanceOffset()) {
*bfc_block_offset = constraint_space.ClearanceOffset();
return true;
}
@@ -82,32 +81,21 @@
// Returns if the resulting fragment should be considered an "empty block".
// There is special casing for fragments like this, e.g. margins "collapse
// through", etc.
-inline bool IsEmptyBlock(bool is_new_fc, const NGLayoutResult& layout_result) {
- // TODO(ikilpatrick): This should be a DCHECK.
- if (is_new_fc)
- return false;
-
- if (layout_result.BfcBlockOffset())
- return false;
-
+inline bool IsEmptyBlock(const NGLayoutResult& layout_result) {
#if DCHECK_IS_ON()
- const NGPhysicalFragment& physical_fragment =
- layout_result.PhysicalFragment();
- // This just checks that the fragments block size is actually zero. We can
- // assume that its in the same writing mode as its parent, as a different
- // writing mode child will be caught by the is_new_fc check.
- NGFragment fragment(physical_fragment.Style().GetWritingMode(),
- physical_fragment);
- DCHECK_EQ(LayoutUnit(), fragment.BlockSize());
+ if (layout_result.IsEmptyBlock()) {
+ const NGPhysicalFragment& physical_fragment =
+ layout_result.PhysicalFragment();
+ // This just checks that the fragments block size is actually zero. We can
+ // assume that its in the same writing mode as its parent, as a different
+ // writing mode child will be caught by the is_new_fc check.
+ NGFragment fragment(physical_fragment.Style().GetWritingMode(),
+ physical_fragment);
+ DCHECK_EQ(LayoutUnit(), fragment.BlockSize());
+ }
#endif
- return true;
-}
-
-// As above; for convenience if you have a child_space.
-inline bool IsEmptyBlock(const NGConstraintSpace& child_space,
- const NGLayoutResult& layout_result) {
- return IsEmptyBlock(child_space.IsNewFormattingContext(), layout_result);
+ return layout_result.IsEmptyBlock();
}
LayoutUnit LogicalFromBfcLineOffset(LayoutUnit child_bfc_line_offset,
@@ -333,7 +321,7 @@
LayoutUnit inline_size = container_builder_.Size().inline_size;
TextDirection direction = ConstraintSpace().Direction();
- if (child_bfc_block_offset) {
+ if (child_bfc_block_offset && container_builder_.BfcBlockOffset()) {
return LogicalFromBfcOffsets(
{child_bfc_line_offset, *child_bfc_block_offset}, ContainerBfcOffset(),
fragment.InlineSize(), inline_size, direction);
@@ -343,10 +331,9 @@
child_bfc_line_offset, container_builder_.BfcLineOffset(),
fragment.InlineSize(), inline_size, direction);
- // If we've reached here, both the child and the current layout don't have a
- // BFC block offset yet. Children in this situation are always placed at a
- // logical block offset of 0.
- DCHECK(!container_builder_.BfcBlockOffset());
+ // If we've reached here, either the parent, or the child don't have a BFC
+ // block-offset yet. Children in this situation are always placed at a
+ // logical block-offset of zero.
return {inline_offset, LayoutUnit()};
}
@@ -397,7 +384,7 @@
// offset is updated.
abort_when_bfc_block_offset_updated_ = true;
- container_builder_.AddAdjoiningFloatTypes(float_types);
+ container_builder_.SetAdjoiningFloatTypes(float_types);
}
// If we are resuming from a break token our start border and padding is
@@ -407,7 +394,7 @@
NGPreviousInflowPosition previous_inflow_position = {
LayoutUnit(), ConstraintSpace().MarginStrut(),
- /* empty_block_affected_by_clearance */ false};
+ /* empty_block_had_clearance */ false};
// Do not collapse margins between parent and its child if:
//
@@ -459,7 +446,7 @@
if (!container_builder_.BfcBlockOffset()) {
// New formatting contexts, and where we have an empty block affected by
// clearance should already have their BFC block offset resolved.
- DCHECK(!previous_inflow_position.empty_block_affected_by_clearance);
+ DCHECK(!previous_inflow_position.empty_block_had_clearance);
DCHECK(!ConstraintSpace().IsNewFormattingContext());
}
#endif
@@ -569,7 +556,7 @@
// - We are a new formatting context.
// Additionally this fragment produces no end margin strut.
if (border_scrollbar_padding_.block_end ||
- previous_inflow_position->empty_block_affected_by_clearance ||
+ previous_inflow_position->empty_block_had_clearance ||
ConstraintSpace().IsNewFormattingContext()) {
// If we are a quirky container, we ignore any quirky margins and
// just consider normal margins to extend our size. Other UAs
@@ -665,6 +652,25 @@
container_builder_.SetEndMarginStrut(end_margin_strut);
container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_);
+ if (container_builder_.BfcBlockOffset()) {
+ // If we know our BFC block-offset we should have correctly placed all
+ // adjoining floats, and shouldn't propagate this information to siblings.
+ container_builder_.ResetAdjoiningFloatTypes();
+ } else {
+ // If we don't know our BFC block-offset yet, we know that for
+ // margin-collapsing purposes we are an empty-block.
+ container_builder_.SetIsEmptyBlock();
+
+ // If we've been forced at a particular BFC block-offset, (either from
+ // clearance past adjoining floats, or a re-layout), we can safely set our
+ // BFC block-offset now.
+ if (ConstraintSpace().ForcedBfcBlockOffset()) {
+ DCHECK(unpositioned_floats_.IsEmpty());
+ container_builder_.SetBfcBlockOffset(
+ *ConstraintSpace().ForcedBfcBlockOffset());
+ }
+ }
+
// We only finalize for fragmentation if the fragment has a BFC block offset.
// This may occur with a zero block size fragment. We need to know the BFC
// block offset to determine where the fragmentation line is relative to us.
@@ -833,14 +839,14 @@
// No need to postpone the positioning if we know the correct offset.
if (container_builder_.BfcBlockOffset() ||
- ConstraintSpace().FloatsBfcBlockOffset()) {
+ ConstraintSpace().ForcedBfcBlockOffset()) {
// Adjust origin point to the margins of the last child.
// Example: <div style="margin-bottom: 20px"><float></div>
// <div style="margin-bottom: 30px"></div>
LayoutUnit origin_block_offset =
container_builder_.BfcBlockOffset()
? NextBorderEdge(previous_inflow_position)
- : *ConstraintSpace().FloatsBfcBlockOffset();
+ : *ConstraintSpace().ForcedBfcBlockOffset();
PositionPendingFloats(origin_block_offset);
}
}
@@ -857,11 +863,9 @@
const ComputedStyle& child_style = child.Style();
const TextDirection direction = ConstraintSpace().Direction();
- bool has_clearance_past_adjoining_floats = HasClearancePastAdjoiningFloats(
- container_builder_.AdjoiningFloatTypes(), child_style, Style());
- NGInflowChildData child_data = ComputeChildData(
- *previous_inflow_position, child, child_break_token,
- has_clearance_past_adjoining_floats, /* is_new_fc */ true);
+ NGInflowChildData child_data =
+ ComputeChildData(*previous_inflow_position, child, child_break_token,
+ /* is_new_fc */ true);
LayoutUnit child_origin_line_offset =
ConstraintSpace().BfcOffset().line_offset +
@@ -895,11 +899,22 @@
if (!container_builder_.BfcBlockOffset()) {
had_pending_floats = !unpositioned_floats_.IsEmpty();
- if (ConstraintSpace().FloatsBfcBlockOffset()) {
+ // If this node, or an arbitrary ancestor had clearance past adjoining
+ // floats, we consider the margin "separated". We should *never* attempt to
+ // re-resolve the BFC block-offset in this case.
+ bool has_clearance_past_adjoining_floats =
+ ConstraintSpace().AncestorHasClearancePastAdjoiningFloats() ||
+ HasClearancePastAdjoiningFloats(
+ container_builder_.AdjoiningFloatTypes(), child_style, Style());
+
+ if (has_clearance_past_adjoining_floats) {
+ child_bfc_offset_estimate = NextBorderEdge(*previous_inflow_position);
+ child_margin_got_separated = true;
+ } else if (ConstraintSpace().ForcedBfcBlockOffset()) {
// This is not the first time we're here. We already have a suggested BFC
// block offset.
bfc_offset_already_resolved = true;
- child_bfc_offset_estimate = *ConstraintSpace().FloatsBfcBlockOffset();
+ child_bfc_offset_estimate = *ConstraintSpace().ForcedBfcBlockOffset();
// We require that the BFC block offset be the one we'd get with either
// margins adjoining or margins separated. Anything else is a bug.
DCHECK(child_bfc_offset_estimate == adjoining_bfc_offset_estimate ||
@@ -908,9 +923,6 @@
// margin strut or not.
child_margin_got_separated =
child_bfc_offset_estimate != adjoining_bfc_offset_estimate;
- } else if (has_clearance_past_adjoining_floats) {
- child_bfc_offset_estimate = NextBorderEdge(*previous_inflow_position);
- child_margin_got_separated = true;
}
// The BFC block offset of this container gets resolved because of this
@@ -962,8 +974,15 @@
// re-resolve the BFC block offset without the child's margin.
LayoutUnit old_offset = *container_builder_.BfcBlockOffset();
container_builder_.ResetBfcBlockOffset();
+
+ // Re-resolving the BFC block-offset with a different "forced" BFC
+ // block-offset is only safe if an ancestor *never* had clearance past
+ // adjoining floats.
+ DCHECK(!ConstraintSpace().AncestorHasClearancePastAdjoiningFloats());
ResolveBfcBlockOffset(previous_inflow_position,
- non_adjoining_bfc_offset_estimate);
+ non_adjoining_bfc_offset_estimate,
+ /* forced_bfc_block_offset */ base::nullopt);
+
if ((bfc_offset_already_resolved || had_pending_floats) &&
old_offset != *container_builder_.BfcBlockOffset()) {
// The first BFC block offset resolution turned out to be wrong, and we
@@ -1064,7 +1083,7 @@
*previous_inflow_position = ComputeInflowPosition(
*previous_inflow_position, child, child_data,
child_bfc_offset.block_offset, logical_offset, *layout_result, fragment,
- /* empty_block_affected_by_clearance */ false);
+ /* empty_block_had_clearance */ false);
return true;
}
@@ -1138,8 +1157,8 @@
(opportunity.rect.InlineSize() - inline_negative_margin + inline_margin)
.ClampNegativeToZero(),
child_available_size_.block_size};
- NGConstraintSpace child_space =
- CreateConstraintSpaceForChild(child, child_data, child_available_size);
+ NGConstraintSpace child_space = CreateConstraintSpaceForChild(
+ child, child_data, child_available_size, /* is_new_fc */ true);
// All formatting context roots (like this child) should start with an empty
// exclusion space.
@@ -1193,10 +1212,12 @@
bool is_non_empty_inline =
child_inline_node && !child_inline_node->IsEmptyInline();
bool has_clearance_past_adjoining_floats =
- child.IsBlock() &&
+ !container_builder_.BfcBlockOffset() && child.IsBlock() &&
HasClearancePastAdjoiningFloats(container_builder_.AdjoiningFloatTypes(),
child.Style(), Style());
+ base::Optional<LayoutUnit> forced_bfc_block_offset;
+
// If we can separate the previous margin strut from what is to follow, do
// that. Then we're able to resolve *our* BFC block offset and position any
// pending floats. There are two situations where this is necessary:
@@ -1208,14 +1229,25 @@
if (has_clearance_past_adjoining_floats || is_non_empty_inline) {
if (!ResolveBfcBlockOffset(previous_inflow_position))
return false;
+
+ // If we had clearance past any adjoining floats, we already know where the
+ // child is going to be (the child's margins won't have any effect).
+ //
+ // Set the forced BFC block-offset to the appropriate clearance offset to
+ // force this placement of this child.
+ if (has_clearance_past_adjoining_floats) {
+ forced_bfc_block_offset = exclusion_space_.ClearanceOffset(
+ ResolvedClear(child.Style(), Style()));
+ }
}
// Perform layout on the child.
- NGInflowChildData child_data = ComputeChildData(
- *previous_inflow_position, child, child_break_token,
- has_clearance_past_adjoining_floats, /* is_new_fc */ false);
- NGConstraintSpace child_space =
- CreateConstraintSpaceForChild(child, child_data, child_available_size_);
+ NGInflowChildData child_data =
+ ComputeChildData(*previous_inflow_position, child, child_break_token,
+ /* is_new_fc */ false);
+ NGConstraintSpace child_space = CreateConstraintSpaceForChild(
+ child, child_data, child_available_size_, /* is_new_fc */ false,
+ forced_bfc_block_offset, has_clearance_past_adjoining_floats);
scoped_refptr<const NGLayoutResult> layout_result =
child.Layout(child_space, child_break_token, inline_child_layout_context);
@@ -1223,6 +1255,7 @@
// above, the rest of this function is continued within |FinishInflow|.
// However it should be read as one function.
return FinishInflow(child, child_break_token, child_space,
+ has_clearance_past_adjoining_floats,
std::move(layout_result), &child_data,
previous_inflow_position, inline_child_layout_context,
previous_inline_break_token);
@@ -1232,6 +1265,7 @@
NGLayoutInputNode child,
const NGBreakToken* child_break_token,
const NGConstraintSpace& child_space,
+ bool has_clearance_past_adjoining_floats,
scoped_refptr<const NGLayoutResult> layout_result,
NGInflowChildData* child_data,
NGPreviousInflowPosition* previous_inflow_position,
@@ -1239,6 +1273,7 @@
scoped_refptr<const NGInlineBreakToken>* previous_inline_break_token) {
base::Optional<LayoutUnit> child_bfc_block_offset =
layout_result->BfcBlockOffset();
+
// TODO(layout-dev): A more optimal version of this is to set
// relayout_child_when_bfc_resolved only if the child tree itself _added_ any
// floats that it failed to position. Currently, we risk relaying out the
@@ -1246,39 +1281,55 @@
// distinction.
bool relayout_child_when_bfc_resolved =
layout_result->AdjoiningFloatTypes() && !child_bfc_block_offset &&
- !child_space.FloatsBfcBlockOffset();
- bool is_empty_block = IsEmptyBlock(child_space, *layout_result);
+ !child_space.ForcedBfcBlockOffset();
- bool has_clearance = layout_result->IsPushedByFloats();
+ bool is_empty_block = IsEmptyBlock(*layout_result);
- // A child may have aborted its layout if it resolved its BFC block offset.
- // If we don't have a BFC block offset yet, we need to propagate the abortion
- // up to our parent.
+ // Only non-empty children can be pushed by floats in this way.
+ bool non_empty_block_had_clearance = layout_result->IsPushedByFloats();
+ DCHECK(!non_empty_block_had_clearance || !is_empty_block);
+
+ // A child may have aborted its layout if it resolved its BFC block-offset.
+ // If we don't have a BFC block-offset yet, we need to propagate the abort
+ // signal up to our parent.
if (layout_result->Status() == NGLayoutResult::kBfcBlockOffsetResolved &&
!container_builder_.BfcBlockOffset()) {
- // There's no need to do anything apart from resolving the BFC block offset
+ // There's no need to do anything apart from resolving the BFC block-offset
// here, so make sure that it aborts before trying to position floats or
- // anything like that, which would just be waste of time. This is simply
- // propagating an abort up to a node which is able to restart the layout (a
- // node that has resolved its BFC block offset).
+ // anything like that, which would just be waste of time.
+ //
+ // This is simply propagating an abort up to a node which is able to
+ // restart the layout (a node that has resolved its BFC block-offset).
DCHECK(child_bfc_block_offset);
abort_when_bfc_block_offset_updated_ = true;
- ResolveBfcBlockOffset(previous_inflow_position,
- has_clearance
- ? NextBorderEdge(*previous_inflow_position)
- : *child_bfc_block_offset);
+
+ LayoutUnit bfc_block_offset = *child_bfc_block_offset;
+
+ if (non_empty_block_had_clearance) {
+ // If the child has the same clearance-offset as ourselves it means that
+ // we should *also* resolve ourselves at that offset, (and we also have
+ // been pushed by floats).
+ if (ConstraintSpace().ClearanceOffset() == child_space.ClearanceOffset())
+ container_builder_.SetIsPushedByFloats();
+ else
+ bfc_block_offset = NextBorderEdge(*previous_inflow_position);
+ }
+
+ ResolveBfcBlockOffset(previous_inflow_position, bfc_block_offset);
return false;
}
// We have special behaviour for an empty block which gets pushed down due to
- // clearance, see comment inside ComputeInflowPosition.
- bool empty_block_affected_by_clearance = false;
+ // clearance, see comment inside |ComputeInflowPosition|.
+ bool empty_block_had_clearance =
+ is_empty_block && has_clearance_past_adjoining_floats;
// We try and position the child within the block formatting context. This
// may cause our BFC block offset to be resolved, in which case we should
// abort our layout if needed.
if (!child_bfc_block_offset) {
- if (!has_clearance && child_space.HasClearanceOffset() &&
+ DCHECK(is_empty_block);
+ if (child_space.HasClearanceOffset() &&
child.Style().Clear() != EClear::kNone) {
// This is an empty block child that we collapsed through, so we have to
// detect clearance manually. See if the child's hypothetical border edge
@@ -1286,12 +1337,14 @@
// before it.
LayoutUnit child_block_offset_estimate =
BfcBlockOffset() + layout_result->EndMarginStrut().Sum();
- if (child_block_offset_estimate < child_space.ClearanceOffset() ||
- child_space.ShouldForceClearance())
- has_clearance = empty_block_affected_by_clearance = true;
+ if (child_block_offset_estimate < child_space.ClearanceOffset())
+ empty_block_had_clearance = true;
}
}
- if (has_clearance) {
+
+ bool child_had_clearance =
+ empty_block_had_clearance || non_empty_block_had_clearance;
+ if (child_had_clearance) {
// The child has clearance. Clearance inhibits margin collapsing and acts as
// spacing before the block-start margin of the child. Our BFC block offset
// is therefore resolvable, and if it hasn't already been resolved, we'll
@@ -1299,27 +1352,34 @@
if (!ResolveBfcBlockOffset(previous_inflow_position))
return false;
}
+
if (!child_bfc_block_offset) {
DCHECK(is_empty_block);
+
// Layout wasn't able to determine the BFC block offset of the child. This
// has to mean that the child is empty (block-size-wise).
if (container_builder_.BfcBlockOffset()) {
// Since we know our own BFC block offset, though, we can calculate that
// of the child as well.
- child_bfc_block_offset = PositionEmptyChildWithParentBfc(
+ child_bfc_block_offset = PositionEmptyBlockWithParentBfc(
child, child_space, *child_data, *layout_result);
}
- } else if (!has_clearance) {
+ } else if (!child_had_clearance) {
// We shouldn't have any pending floats here, since an in-flow child found
// its BFC block offset.
DCHECK(unpositioned_floats_.IsEmpty());
- // The child's BFC block offset is known, and since there's no clearance,
- // this container will get the same offset, unless it has already been
- // resolved.
- if (!ResolveBfcBlockOffset(previous_inflow_position,
- *child_bfc_block_offset))
- return false;
+ // Only non-empty children are allowed resolve the BFC block-offset. We
+ // check the BFC block-offset at the end of layout determine if this
+ // fragment is empty.
+ if (!is_empty_block) {
+ // The child's BFC block offset is known, and since there's no clearance,
+ // this container will get the same offset, unless it has already been
+ // resolved.
+ if (!ResolveBfcBlockOffset(previous_inflow_position,
+ *child_bfc_block_offset))
+ return false;
+ }
}
// We need to re-layout a child if it was affected by clearance in order to
@@ -1337,9 +1397,9 @@
// relayout with an empty incoming margin strut.
//
// The resulting margin strut in the above example will be {40, -30}. See
- // ComputeInflowPosition for how this end margin strut is used.
- bool empty_block_affected_by_clearance_needs_relayout = false;
- if (empty_block_affected_by_clearance) {
+ // |ComputeInflowPosition| for how this end margin strut is used.
+ bool empty_block_had_clearance_needs_relayout = false;
+ if (empty_block_had_clearance) {
NGMarginStrut margin_strut;
margin_strut.Append(child_data->margins.block_start,
child.Style().HasMarginBeforeQuirk());
@@ -1348,7 +1408,7 @@
// previous one.
if (child_data->margin_strut != margin_strut) {
child_data->margin_strut = margin_strut;
- empty_block_affected_by_clearance_needs_relayout = true;
+ empty_block_had_clearance_needs_relayout = true;
}
}
@@ -1358,10 +1418,11 @@
// - It was affected by clearance.
if ((layout_result->Status() == NGLayoutResult::kBfcBlockOffsetResolved ||
relayout_child_when_bfc_resolved ||
- empty_block_affected_by_clearance_needs_relayout) &&
+ empty_block_had_clearance_needs_relayout) &&
child_bfc_block_offset) {
NGConstraintSpace new_child_space = CreateConstraintSpaceForChild(
- child, *child_data, child_available_size_, child_bfc_block_offset);
+ child, *child_data, child_available_size_, /* is_new_fc */ false,
+ child_bfc_block_offset);
layout_result = child.Layout(new_child_space, child_break_token,
inline_child_layout_context);
@@ -1374,7 +1435,8 @@
child_bfc_block_offset = layout_result->BfcBlockOffset();
DCHECK(child_bfc_block_offset);
new_child_space = CreateConstraintSpaceForChild(
- child, *child_data, child_available_size_, child_bfc_block_offset);
+ child, *child_data, child_available_size_, /* is_new_fc */ false,
+ child_bfc_block_offset);
layout_result = child.Layout(new_child_space, child_break_token,
inline_child_layout_context);
}
@@ -1383,9 +1445,15 @@
relayout_child_when_bfc_resolved = false;
}
- // It is now safe to update our version of the exclusion space.
+ // It is now safe to update our version of the exclusion space, and any
+ // propagated adjoining floats.
exclusion_space_ = layout_result->ExclusionSpace();
+ // Only empty-blocks should have adjoining floats.
+ DCHECK(!layout_result->AdjoiningFloatTypes() || is_empty_block);
+ container_builder_.SetAdjoiningFloatTypes(
+ layout_result->AdjoiningFloatTypes());
+
// If we don't know our BFC block offset yet, and the child stumbled into
// something that needs it (unable to position floats when the BFC block
// offset is unknown), we need abort layout once we manage to resolve it, and
@@ -1425,11 +1493,6 @@
PositionOrPropagateListMarker(*layout_result, &logical_offset);
- if (is_empty_block && !container_builder_.BfcBlockOffset()) {
- container_builder_.AddAdjoiningFloatTypes(
- layout_result->AdjoiningFloatTypes());
- }
-
container_builder_.AddChild(physical_fragment, logical_offset);
if (child.IsBlock())
container_builder_.PropagateBreak(*layout_result);
@@ -1451,8 +1514,7 @@
*previous_inflow_position = ComputeInflowPosition(
*previous_inflow_position, child, *child_data, child_bfc_block_offset,
- logical_offset, *layout_result, fragment,
- empty_block_affected_by_clearance);
+ logical_offset, *layout_result, fragment, empty_block_had_clearance);
*previous_inline_break_token =
child.IsInline() ? To<NGInlineBreakToken>(physical_fragment.BreakToken())
@@ -1465,7 +1527,6 @@
const NGPreviousInflowPosition& previous_inflow_position,
NGLayoutInputNode child,
const NGBreakToken* child_break_token,
- bool force_clearance,
bool is_new_fc) {
DCHECK(child);
DCHECK(!child.IsFloating());
@@ -1500,8 +1561,7 @@
margins.LineLeft(ConstraintSpace().Direction()),
BfcBlockOffset() + logical_block_offset};
- return {child_bfc_offset, margin_strut, margins,
- margins_fully_resolved, force_clearance, is_new_fc};
+ return {child_bfc_offset, margin_strut, margins, margins_fully_resolved};
}
NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition(
@@ -1512,17 +1572,17 @@
const LogicalOffset& logical_offset,
const NGLayoutResult& layout_result,
const NGFragment& fragment,
- bool empty_block_affected_by_clearance) {
+ bool empty_block_had_clearance) {
// Determine the child's end logical offset, for the next child to use.
LayoutUnit logical_block_offset;
- bool is_empty_block = IsEmptyBlock(child_data.is_new_fc, layout_result);
+ bool is_empty_block = IsEmptyBlock(layout_result);
if (is_empty_block) {
// The default behaviour for empty blocks is they just pass through the
// previous inflow position.
logical_block_offset = previous_inflow_position.logical_block_offset;
- if (empty_block_affected_by_clearance) {
+ if (empty_block_had_clearance) {
// If there's clearance, we must have applied that by now and thus
// resolved our BFC block offset.
DCHECK(container_builder_.BfcBlockOffset());
@@ -1603,21 +1663,20 @@
// </div>
// In the above case #container's size will depend on the end margin strut of
// #another-zero, even though usually it wouldn't.
- bool empty_or_sibling_empty_affected_by_clearance =
- empty_block_affected_by_clearance ||
- (previous_inflow_position.empty_block_affected_by_clearance &&
- is_empty_block);
+ bool empty_or_sibling_empty_had_clearance =
+ empty_block_had_clearance ||
+ (previous_inflow_position.empty_block_had_clearance && is_empty_block);
return {logical_block_offset, margin_strut,
- empty_or_sibling_empty_affected_by_clearance};
+ empty_or_sibling_empty_had_clearance};
}
-LayoutUnit NGBlockLayoutAlgorithm::PositionEmptyChildWithParentBfc(
+LayoutUnit NGBlockLayoutAlgorithm::PositionEmptyBlockWithParentBfc(
const NGLayoutInputNode& child,
const NGConstraintSpace& child_space,
const NGInflowChildData& child_data,
const NGLayoutResult& layout_result) const {
- DCHECK(IsEmptyBlock(child_space, layout_result));
+ DCHECK(IsEmptyBlock(layout_result));
// The child must be an in-flow zero-block-size fragment, use its end margin
// strut for positioning.
@@ -1990,14 +2049,16 @@
const NGLayoutInputNode child,
const NGInflowChildData& child_data,
const LogicalSize child_available_size,
- const base::Optional<LayoutUnit> floats_bfc_block_offset) {
+ bool is_new_fc,
+ const base::Optional<LayoutUnit> forced_bfc_block_offset,
+ bool has_clearance_past_adjoining_floats) {
const ComputedStyle& style = Style();
const ComputedStyle& child_style = child.Style();
WritingMode child_writing_mode =
child.IsInline() ? style.GetWritingMode() : child_style.GetWritingMode();
NGConstraintSpaceBuilder builder(ConstraintSpace(), child_writing_mode,
- child_data.is_new_fc);
+ is_new_fc);
SetOrthogonalFallbackInlineSizeIfNeeded(Style(), child, &builder);
if (!IsParallelWritingMode(ConstraintSpace().GetWritingMode(),
@@ -2022,13 +2083,22 @@
builder.SetBfcOffset(child_data.bfc_offset_estimate)
.SetMarginStrut(child_data.margin_strut);
- if (!container_builder_.BfcBlockOffset() &&
- ConstraintSpace().FloatsBfcBlockOffset()) {
- builder.SetFloatsBfcBlockOffset(*ConstraintSpace().FloatsBfcBlockOffset());
- }
+ bool has_bfc_block_offset = container_builder_.BfcBlockOffset().has_value();
- if (floats_bfc_block_offset)
- builder.SetFloatsBfcBlockOffset(floats_bfc_block_offset);
+ // Propagate the |NGConstraintSpace::ForcedBfcBlockOffset| down to our
+ // children.
+ if (!has_bfc_block_offset && ConstraintSpace().ForcedBfcBlockOffset())
+ builder.SetForcedBfcBlockOffset(*ConstraintSpace().ForcedBfcBlockOffset());
+ if (forced_bfc_block_offset)
+ builder.SetForcedBfcBlockOffset(forced_bfc_block_offset);
+
+ // Propagate the |NGConstraintSpace::AncestorHasClearancePastAdjoiningFloats|
+ // flag down to our children.
+ if (!has_bfc_block_offset &&
+ ConstraintSpace().AncestorHasClearancePastAdjoiningFloats())
+ builder.SetAncestorHasClearancePastAdjoiningFloats();
+ if (has_clearance_past_adjoining_floats)
+ builder.SetAncestorHasClearancePastAdjoiningFloats();
LayoutUnit clearance_offset = ConstraintSpace().IsNewFormattingContext()
? LayoutUnit::Min()
@@ -2049,15 +2119,10 @@
}
builder.SetClearanceOffset(clearance_offset);
- // If we were told to force clearance, we need to propagate this down to our
- // children if we haven't resolved our BFC-block-offset yet.
- if (child_data.force_clearance || (!container_builder_.BfcBlockOffset() &&
- ConstraintSpace().ShouldForceClearance()))
- builder.SetShouldForceClearance(true);
-
- if (!child_data.is_new_fc) {
+ if (!is_new_fc) {
builder.SetExclusionSpace(exclusion_space_);
- builder.SetAdjoiningFloatTypes(container_builder_.AdjoiningFloatTypes());
+ if (!has_bfc_block_offset)
+ builder.SetAdjoiningFloatTypes(container_builder_.AdjoiningFloatTypes());
}
LayoutUnit space_available;
@@ -2066,9 +2131,9 @@
// If a block establishes a new formatting context we must know our
// position in the formatting context, and are able to adjust the
// fragmentation line.
- if (child_data.is_new_fc) {
+ if (is_new_fc)
space_available -= child_data.bfc_offset_estimate.block_offset;
- }
+
// The policy regarding collapsing block-start margin with the fragmentainer
// block-start is the same throughout the entire fragmentainer (although it
// really only matters at the beginning of each fragmentainer, we don't need
@@ -2177,17 +2242,19 @@
bool NGBlockLayoutAlgorithm::ResolveBfcBlockOffset(
NGPreviousInflowPosition* previous_inflow_position,
- LayoutUnit bfc_block_offset) {
+ LayoutUnit bfc_block_offset,
+ base::Optional<LayoutUnit> forced_bfc_block_offset) {
if (container_builder_.BfcBlockOffset()) {
DCHECK(unpositioned_floats_.IsEmpty());
return true;
}
+ bfc_block_offset = forced_bfc_block_offset.value_or(bfc_block_offset);
+
if (ApplyClearance(ConstraintSpace(), &bfc_block_offset))
container_builder_.SetIsPushedByFloats();
container_builder_.SetBfcBlockOffset(bfc_block_offset);
- container_builder_.ResetAdjoiningFloatTypes();
if (NeedsAbortOnBfcBlockOffsetChange())
return false;
@@ -2217,11 +2284,11 @@
if (!abort_when_bfc_block_offset_updated_)
return false;
// If no previous BFC block offset was set, we need to abort.
- if (!ConstraintSpace().FloatsBfcBlockOffset())
+ if (!ConstraintSpace().ForcedBfcBlockOffset())
return true;
// If the previous BFC block offset matches the new one, we can continue.
// Otherwise, we need to abort.
- LayoutUnit old_bfc_block_offset = *ConstraintSpace().FloatsBfcBlockOffset();
+ LayoutUnit old_bfc_block_offset = *ConstraintSpace().ForcedBfcBlockOffset();
// In order to determine if the two offsets are equal, we also need to adjust
// floats offset by the clearance offset.
@@ -2232,7 +2299,7 @@
void NGBlockLayoutAlgorithm::PositionPendingFloats(
LayoutUnit origin_block_offset) {
DCHECK(container_builder_.BfcBlockOffset() ||
- ConstraintSpace().FloatsBfcBlockOffset())
+ ConstraintSpace().ForcedBfcBlockOffset())
<< "The parent BFC block offset should be known here.";
NGBfcOffset origin_bfc_offset = {
@@ -2242,7 +2309,7 @@
LayoutUnit bfc_block_offset = container_builder_.BfcBlockOffset()
? *container_builder_.BfcBlockOffset()
- : *ConstraintSpace().FloatsBfcBlockOffset();
+ : *ConstraintSpace().ForcedBfcBlockOffset();
NGBfcOffset bfc_offset = {ConstraintSpace().BfcOffset().line_offset,
bfc_block_offset};
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
index 7351989..2f54670 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
@@ -29,7 +29,7 @@
struct NGPreviousInflowPosition {
LayoutUnit logical_block_offset;
NGMarginStrut margin_strut;
- bool empty_block_affected_by_clearance;
+ bool empty_block_had_clearance;
};
// This strut holds information for the current inflow child. The data is not
@@ -39,8 +39,6 @@
NGMarginStrut margin_strut;
NGBoxStrut margins;
bool margins_fully_resolved;
- bool force_clearance;
- bool is_new_fc;
};
// A class for general block layout (e.g. a <div> with no special style).
@@ -102,13 +100,14 @@
const NGLayoutInputNode child,
const NGInflowChildData& child_data,
const LogicalSize child_available_size,
- const base::Optional<LayoutUnit> floats_bfc_block_offset = base::nullopt);
+ bool is_new_fc,
+ const base::Optional<LayoutUnit> forced_bfc_block_offset = base::nullopt,
+ bool has_clearance_past_adjoining_floats = false);
// @return Estimated BFC block offset for the "to be layout" child.
NGInflowChildData ComputeChildData(const NGPreviousInflowPosition&,
NGLayoutInputNode,
const NGBreakToken* child_break_token,
- bool force_clearance,
bool is_new_fc);
NGPreviousInflowPosition ComputeInflowPosition(
@@ -119,16 +118,16 @@
const LogicalOffset&,
const NGLayoutResult&,
const NGFragment&,
- bool empty_block_affected_by_clearance);
+ bool empty_block_had_clearance);
- // Position an empty child using the parent BFC block offset.
+ // Position an empty-block using the parent BFC block offset.
// The fragment doesn't know its offset, but we can still calculate its BFC
// position because the parent fragment's BFC is known.
// Example:
// BFC Offset is known here because of the padding.
// <div style="padding: 1px">
// <div id="empty-div" style="margin: 1px"></div>
- LayoutUnit PositionEmptyChildWithParentBfc(
+ LayoutUnit PositionEmptyBlockWithParentBfc(
const NGLayoutInputNode& child,
const NGConstraintSpace& child_space,
const NGInflowChildData& child_data,
@@ -192,6 +191,7 @@
NGLayoutInputNode child,
const NGBreakToken* child_break_token,
const NGConstraintSpace&,
+ bool has_clearance_past_adjoining_floats,
scoped_refptr<const NGLayoutResult>,
NGInflowChildData*,
NGPreviousInflowPosition*,
@@ -246,18 +246,35 @@
// If still unresolved, resolve the fragment's BFC block offset.
//
- // This includes applying clearance, so the bfc_block_offset passed won't be
- // the final BFC block offset, if it wasn't large enough to get past all
- // relevant floats. The updated BFC block offset can be read out with
- // ContainerBfcBlockOffset().
+ // This includes applying clearance, so the |bfc_block_offset| passed won't
+ // be the final BFC block-offset, if it wasn't large enough to get past all
+ // relevant floats. The updated BFC block-offset can be read out with
+ // |ContainerBfcBlockOffset()|.
+ //
+ // If the |forced_bfc_block_offset| has a value, it will override the given
+ // |bfc_block_offset|. Typically this comes from the input constraints, when
+ // the current node has clearance past adjoining floats, or has a re-layout
+ // due to a child resolving the BFC block-offset.
//
// In addition to resolving our BFC block offset, this will also position
- // pending floats, and update our in-flow layout state. Returns false if
- // resolving the BFC block offset resulted in needing to abort layout. It
- // will always return true otherwise. If the BFC block offset was already
- // resolved, this method does nothing (and returns true).
- bool ResolveBfcBlockOffset(NGPreviousInflowPosition*,
- LayoutUnit bfc_block_offset);
+ // pending floats, and update our in-flow layout state.
+ //
+ // Returns false if resolving the BFC block-offset resulted in needing to
+ // abort layout. It will always return true otherwise. If the BFC
+ // block-offset was already resolved, this method does nothing (and returns
+ // true).
+ bool ResolveBfcBlockOffset(
+ NGPreviousInflowPosition*,
+ LayoutUnit bfc_block_offset,
+ const base::Optional<LayoutUnit> forced_bfc_block_offset);
+
+ // This passes in the |forced_bfc_block_offset| from the input constraints,
+ // which is almost always desired.
+ bool ResolveBfcBlockOffset(NGPreviousInflowPosition* previous_inflow_position,
+ LayoutUnit bfc_block_offset) {
+ return ResolveBfcBlockOffset(previous_inflow_position, bfc_block_offset,
+ ConstraintSpace().ForcedBfcBlockOffset());
+ }
// A very common way to resolve the BFC block offset is to simply commit the
// pending margin, so here's a convenience overload for that.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
index d8663099..875b903 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
@@ -240,6 +240,7 @@
bool did_break_;
bool has_forced_break_ = false;
bool is_new_fc_ = false;
+ bool is_empty_block_ = false;
LayoutUnit used_block_size_;
LayoutUnit minimal_space_shortage_ = LayoutUnit::Max();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h b/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
index 410c459..7937d6e 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_constraint_space.h
@@ -66,7 +66,7 @@
kNewFormattingContext = 1 << 4,
kAnonymous = 1 << 5,
kUseFirstLineStyle = 1 << 6,
- kForceClearance = 1 << 7,
+ kAncestorHasClearancePastAdjoiningFloats = 1 << 7,
// Size of bitfield used to store the flags.
kNumberOfConstraintSpaceFlags = 8
@@ -276,6 +276,16 @@
// Also note this is true only when the document has ':first-line' rules.
bool UseFirstLineStyle() const { return HasFlag(kUseFirstLineStyle); }
+ // Returns true if an ancestor had clearance past adjoining floats.
+ //
+ // Typically this can be detected by seeing if a |ForcedBfcBlockOffset| is
+ // set. However new formatting contexts may require additional passes (if
+ // margins are adjoining or not), and without this extra bit of information
+ // can get into a bad state.
+ bool AncestorHasClearancePastAdjoiningFloats() const {
+ return HasFlag(kAncestorHasClearancePastAdjoiningFloats);
+ }
+
// Some layout modes “stretch” their children to a fixed size (e.g. flex,
// grid). These flags represented whether a layout needs to produce a
// fragment that satisfies a fixed constraint in the inline and block
@@ -346,27 +356,28 @@
return HasRareData() ? rare_data_->bfc_offset : bfc_offset_;
}
- // If present, and the current layout hasn't resolved its BFC offset yet (see
- // BfcOffset), the layout should position all of its unpositioned floats at
- // this offset. This value is the BFC offset that we calculated in the
- // previous pass, a pass which aborted once the BFC offset got resolved,
- // because we had walked past content (i.e. floats) that depended on it being
- // resolved.
+ // If present, and the current layout hasn't resolved its BFC block-offset
+ // yet (see BfcOffset), the layout should position all of its unpositioned
+ // floats at this offset.
//
- // This value should be propogated to child layouts if the current layout
+ // This value is present if:
+ // - An ancestor had clearance past adjoining floats. In this case this
+ // value is calculated ahead of time.
+ // - A second layout pass is required as there were unpositioned floats
+ // within the tree, and an arbitrary sibling determined their BFC
+ // block-offset.
+ //
+ // This value should be propagated to child layouts if the current layout
// hasn't resolved its BFC offset yet.
- //
- // This value is calculated *after* an initial pass of the tree, and should
- // only be present during subsequent passes.
- base::Optional<LayoutUnit> FloatsBfcBlockOffset() const {
- return HasRareData() ? rare_data_->floats_bfc_block_offset : base::nullopt;
+ base::Optional<LayoutUnit> ForcedBfcBlockOffset() const {
+ return HasRareData() ? rare_data_->forced_bfc_block_offset : base::nullopt;
}
// Return the types (none, left, right, both) of preceding adjoining
// floats. These are floats that are added while the in-flow BFC offset is
// still unknown. The floats may or may not be unpositioned (pending). That
// depends on which layout pass we're in. They are typically positioned if
- // FloatsBfcOffset() is known. Adjoining floats should be treated differently
+ // ForcedBfcOffset() is known. Adjoining floats should be treated differently
// when calculating clearance on a block with adjoining block-start margin.
// (in such cases we will know up front that the block will need clearance,
// since, if it doesn't, the float will be pulled along with the block, and
@@ -386,28 +397,6 @@
return HasRareData() ? rare_data_->clearance_offset : LayoutUnit::Min();
}
- // Return true if the fragment needs to have clearance applied to it,
- // regardless of its hypothetical position. The fragment will then go exactly
- // below the relevant floats. This happens when a cleared child gets separated
- // from floats that would otherwise be adjoining; example:
- //
- // <div id="container">
- // <div id="float" style="float:left; width:100px; height:100px;"></div>
- // <div id="clearee" style="clear:left; margin-top:12345px;">text</div>
- // </div>
- //
- // Clearance separates #clearee from #container, and #float is positioned at
- // the block-start content edge of #container. Without clearance, margins
- // would have been adjoining and the large margin on #clearee would have
- // pulled both #container and #float along with it. No margin, no matter how
- // large, would ever be able to pull #clearee below the float then. But we
- // have clearance, the margins are separated, and in this case we know that we
- // have clearance even before we have laid out (because of the adjoining
- // float). So it would just be wrong to check for clearance when we position
- // #clearee. Nothing can prevent clearance here. A large margin on the cleared
- // child will be canceled out with negative clearance.
- bool ShouldForceClearance() const { return HasFlag(kForceClearance); }
-
const NGBaselineRequestList BaselineRequests() const {
return NGBaselineRequestList(bitfields_.baseline_requests);
}
@@ -517,7 +506,7 @@
NGBfcOffset bfc_offset;
NGMarginStrut margin_strut;
- base::Optional<LayoutUnit> floats_bfc_block_offset;
+ base::Optional<LayoutUnit> forced_bfc_block_offset;
LayoutUnit clearance_offset = LayoutUnit::Min();
LayoutUnit fragmentainer_block_size = kIndefiniteSize;
@@ -527,7 +516,7 @@
bool MaySkipLayout(const RareData& other) const {
return margin_strut == other.margin_strut &&
- floats_bfc_block_offset == other.floats_bfc_block_offset &&
+ forced_bfc_block_offset == other.forced_bfc_block_offset &&
fragmentainer_block_size == other.fragmentainer_block_size &&
fragmentainer_space_at_bfc_start ==
other.fragmentainer_space_at_bfc_start &&
@@ -538,7 +527,7 @@
// Must be kept in sync with members checked within |MaySkipLayout|.
bool IsInitialForMaySkipLayout() const {
return margin_strut == NGMarginStrut() &&
- floats_bfc_block_offset == base::nullopt &&
+ forced_bfc_block_offset == base::nullopt &&
fragmentainer_block_size == kIndefiniteSize &&
fragmentainer_space_at_bfc_start == kIndefiniteSize &&
block_direction_fragmentation_type == kFragmentNone;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h b/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h
index 8204ec1f..919cc16 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h
@@ -184,6 +184,11 @@
return *this;
}
+ NGConstraintSpaceBuilder& SetAncestorHasClearancePastAdjoiningFloats() {
+ SetFlag(NGConstraintSpace::kAncestorHasClearancePastAdjoiningFloats, true);
+ return *this;
+ }
+
NGConstraintSpaceBuilder& SetAdjoiningFloatTypes(NGFloatTypes floats) {
if (!is_new_fc_)
space_.bitfields_.adjoining_floats = static_cast<unsigned>(floats);
@@ -212,15 +217,15 @@
return *this;
}
- NGConstraintSpaceBuilder& SetFloatsBfcBlockOffset(
- const base::Optional<LayoutUnit>& floats_bfc_block_offset) {
+ NGConstraintSpaceBuilder& SetForcedBfcBlockOffset(
+ const base::Optional<LayoutUnit>& forced_bfc_block_offset) {
#if DCHECK_IS_ON()
- DCHECK(!is_floats_bfc_block_offset_set_);
- is_floats_bfc_block_offset_set_ = true;
+ DCHECK(!is_forced_bfc_block_offset_set_);
+ is_forced_bfc_block_offset_set_ = true;
#endif
- if (LIKELY(!is_new_fc_ && floats_bfc_block_offset != base::nullopt)) {
- space_.EnsureRareData()->floats_bfc_block_offset =
- floats_bfc_block_offset;
+ if (LIKELY(!is_new_fc_ && forced_bfc_block_offset != base::nullopt)) {
+ space_.EnsureRareData()->forced_bfc_block_offset =
+ forced_bfc_block_offset;
}
return *this;
@@ -237,11 +242,6 @@
return *this;
}
- NGConstraintSpaceBuilder& SetShouldForceClearance(bool b) {
- SetFlag(NGConstraintSpace::kForceClearance, b);
- return *this;
- }
-
NGConstraintSpaceBuilder& SetTableCellChildLayoutPhase(
NGTableCellChildLayoutPhase table_cell_child_layout_phase) {
space_.bitfields_.table_cell_child_layout_phase =
@@ -312,7 +312,7 @@
bool is_fragmentainer_space_at_bfc_start_set_ = false;
bool is_block_direction_fragmentation_type_set_ = false;
bool is_margin_strut_set_ = false;
- bool is_floats_bfc_block_offset_set_ = false;
+ bool is_forced_bfc_block_offset_set_ = false;
bool is_clearance_offset_set_ = false;
bool to_constraint_space_called_ = false;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
index 8410fe4..c3c91cf 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
@@ -148,6 +148,11 @@
nullptr);
}
+ NGContainerFragmentBuilder& SetIsEmptyBlock() {
+ is_empty_block_ = true;
+ return *this;
+ }
+
NGContainerFragmentBuilder& SetIsPushedByFloats() {
is_pushed_by_floats_ = true;
return *this;
@@ -162,6 +167,10 @@
adjoining_floats_ |= floats;
return *this;
}
+ NGContainerFragmentBuilder& SetAdjoiningFloatTypes(NGFloatTypes floats) {
+ adjoining_floats_ = floats;
+ return *this;
+ }
NGFloatTypes AdjoiningFloatTypes() const { return adjoining_floats_; }
NGContainerFragmentBuilder& SetHasBlockFragmentation() {
@@ -244,6 +253,7 @@
NGFloatTypes adjoining_floats_ = kFloatTypeNone;
+ bool is_empty_block_ = false;
bool is_pushed_by_floats_ = false;
bool is_legacy_layout_root_ = false;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc
index aed07c4..6b7b91e0 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_result.cc
@@ -67,6 +67,7 @@
final_break_after_(other.final_break_after_),
has_valid_space_(other.has_valid_space_),
has_forced_break_(other.has_forced_break_),
+ is_empty_block_(other.is_empty_block_),
is_pushed_by_floats_(other.is_pushed_by_floats_),
adjoining_floats_(other.adjoining_floats_),
is_initial_block_size_indefinite_(
@@ -87,6 +88,7 @@
end_margin_strut_(builder->end_margin_strut_),
has_valid_space_(cache_space && builder->space_),
has_forced_break_(false),
+ is_empty_block_(builder->is_empty_block_),
is_pushed_by_floats_(builder->is_pushed_by_floats_),
adjoining_floats_(builder->adjoining_floats_),
is_initial_block_size_indefinite_(false),
@@ -147,6 +149,7 @@
DCHECK_EQ(has_valid_space_, other.has_valid_space_);
DCHECK_EQ(has_forced_break_, other.has_forced_break_);
+ DCHECK_EQ(is_empty_block_, other.is_empty_block_);
DCHECK_EQ(is_pushed_by_floats_, other.is_pushed_by_floats_);
DCHECK_EQ(adjoining_floats_, other.adjoining_floats_);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_result.h b/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
index 3260354..9b91c7b 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
@@ -95,6 +95,10 @@
// Return true if the fragment broke because a forced break before a child.
bool HasForcedBreak() const { return has_forced_break_; }
+ // Returns true if the fragment should be considered empty for margin
+ // collapsing purposes (e.g. margins "collapse through").
+ bool IsEmptyBlock() const { return is_empty_block_; }
+
// Return true if this fragment got its block offset increased by the presence
// of floats.
bool IsPushedByFloats() const { return is_pushed_by_floats_; }
@@ -217,6 +221,7 @@
unsigned has_valid_space_ : 1;
unsigned has_forced_break_ : 1;
+ unsigned is_empty_block_ : 1;
unsigned is_pushed_by_floats_ : 1;
unsigned adjoining_floats_ : 2; // NGFloatTypes
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
index 6aa70c9..1ab4873 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
@@ -517,8 +517,7 @@
if (space.IsIntermediateLayout())
return false;
// Check that we're done positioning pending floats.
- return !result.AdjoiningFloatTypes() || result.BfcBlockOffset() ||
- space.FloatsBfcBlockOffset();
+ return !result.AdjoiningFloatTypes() || result.BfcBlockOffset();
}
} // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc
index b817b8d..4cb444c 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc
@@ -56,9 +56,11 @@
container_builder_.SetIntrinsicBlockSize(result.IntrinsicBlockSize());
container_builder_.SetUnpositionedListMarker(result.UnpositionedListMarker());
- container_builder_.AddAdjoiningFloatTypes(result.AdjoiningFloatTypes());
+ if (result.IsEmptyBlock())
+ container_builder_.SetIsEmptyBlock();
if (result.IsPushedByFloats())
container_builder_.SetIsPushedByFloats();
+ container_builder_.AddAdjoiningFloatTypes(result.AdjoiningFloatTypes());
for (const auto& request : ConstraintSpace().BaselineRequests()) {
base::Optional<LayoutUnit> baseline = physical_fragment.Baseline(request);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc
index a51ed9a..ba44dfd 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_space_utils.cc
@@ -39,7 +39,6 @@
.SetPercentageResolutionSize(indefinite_size)
.SetReplacedPercentageResolutionSize(indefinite_size)
.SetIsIntermediateLayout(true)
- .SetFloatsBfcBlockOffset(LayoutUnit())
.ToConstraintSpace();
}
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/floats-clear/adjoining-float-nested-forced-clearance-002.html b/third_party/blink/web_tests/external/wpt/css/CSS2/floats-clear/adjoining-float-nested-forced-clearance-002.html
new file mode 100644
index 0000000..ed8ffd2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/floats-clear/adjoining-float-nested-forced-clearance-002.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<style>
+#container {
+ width: 100px;
+ background: green;
+}
+#left {
+ float: left;
+ width: 25px;
+ height: 10px;
+}
+#right {
+ float: right;
+ width: 25px;
+ height: 20px;
+}
+#clears-left {
+ clear: left;
+}
+#zero {
+ margin-bottom: 40px;
+ margin-top: -20px;
+}
+#nested-float {
+ float: left;
+ width: 25px;
+ height: 20px;
+}
+#new-formatting-context {
+ overflow: hidden;
+ width: 60px;
+ height: 80px;
+ margin-top: -30px;
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div id=container>
+ <div id=left></div>
+ <div id=right></div>
+ <div>
+ <div id=clears-left>
+ <div>
+ <div id=zero></div>
+ <div id=nested-float></div>
+ <!--
+ The margins up to this new formatting context are chosen to hit an
+ edge condition. At this point there are two possible margins:
+ - (adjoining) {-30px, 40px} => 10px
+ - (non-adjoining) {-20px, 40px} => 20px
+
+ The logic for placing this new formatting context however shouldn't
+ check these margins, as there is an ancestor ("clears-left") which
+ has clearance past adjoining floats ("left", and "right").
+
+ And "nested-float" should get placed at "10px".
+
+ However if we didn't have this logic the following would occur.
+ 1. We'd try and place the formatting context using the "adjoining"
+ margins.
+ 2. The new formatting context doesn't "fit" on the same edge as the
+ floats, so it would trigger a retry using with a new position
+ calculated using the "non-adjoining" margins.
+ 3. During the next pass, it still doesn't think the margins have
+ been separated yet as the parent is still using the position
+ calculated by the forced clearance from above.
+ 4. It will trigger a retry again (and if an implementation does this
+ in a loop, will timeout).
+ -->
+ <div id=new-formatting-context></div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/floats-clear/adjoining-float-nested-forced-clearance-003.html b/third_party/blink/web_tests/external/wpt/css/CSS2/floats-clear/adjoining-float-nested-forced-clearance-003.html
new file mode 100644
index 0000000..4c080d2b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/floats-clear/adjoining-float-nested-forced-clearance-003.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
+<link rel="help" href="https://www.w3.org/TR/CSS22/visuren.html#flow-control" title="9.5.2 Controlling flow next to floats: the 'clear' property">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="overflow: hidden; width: 100px; background: red; position: relative;">
+ <div style="float: left; height: 50px; width: 50px; background: green"></div>
+ <div style="height: 40px; background: green;"></div>
+ <div style="margin-top: 15px; clear: both;">
+ <div style="width: 50px; height: 50px; background: green; float: left;"></div>
+ <div style="margin-top: -10px;">
+ <span style="display: inline-block; width: 50px; height: 40px; background: green;"></span>
+ </div>
+ <div style="position: absolute; width: 50px; height: 10px; right: 0; top: 40px; background: green;"></div>
+ <div style="position: absolute; width: 50px; height: 10px; right: 0; bottom: 0; background: green;"></div>
+ </div>
+</div>