blob: d70553f10a1b852539b8289295ca1d181a5ed328 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/optional.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h"
#include "third_party/blink/renderer/core/layout/ng/list/ng_unpositioned_list_marker.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_early_break.h"
#include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.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_out_of_flow_layout_part.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
namespace {
bool HasLineEvenIfEmpty(LayoutBox* box) {
LayoutBlockFlow* const block_flow = DynamicTo<LayoutBlockFlow>(box);
if (!block_flow)
return false;
// Note: |block_flow->NeedsCollectInline()| is true after removing all
// children from block[1].
// [1] editing/inserting/insert_after_delete.html
LayoutObject* const child = GetLayoutObjectForFirstChildNode(block_flow);
if (!child) {
// Note: |block_flow->ChildrenInline()| can be both true or false:
// - true: just after construction, <div></div>
// - true: one of child is inline them remove all, <div>abc</div>
// - false: all children are block then remove all, <div><p></p></div>
return block_flow->HasLineIfEmpty();
}
if (!AreNGBlockFlowChildrenInline(block_flow))
return false;
return NGInlineNode(block_flow).HasLineEvenIfEmpty();
}
LogicalOffset CenterBlockChild(LogicalOffset offset,
LayoutUnit available_block_size,
LayoutUnit child_block_size) {
if (available_block_size == child_block_size)
return offset;
// We don't clamp a negative difference to zero. We'd like to center the
// child even if its taller than the container.
LayoutUnit block_size_diff = available_block_size - child_block_size;
offset.block_offset += block_size_diff / 2 + LayoutMod(block_size_diff, 2);
return offset;
}
inline scoped_refptr<const NGLayoutResult> LayoutBlockChild(
const NGConstraintSpace& space,
const NGBreakToken* break_token,
const NGEarlyBreak* early_break,
NGBlockNode* node) {
const NGEarlyBreak* early_break_in_child = nullptr;
if (UNLIKELY(early_break && early_break->Type() == NGEarlyBreak::kBlock &&
early_break->BlockNode() == *node)) {
// We're entering a child that we know that we're going to break inside, and
// even where to break. Look inside, and pass the inner breakpoint to
// layout.
early_break_in_child = early_break->BreakInside();
// If there's no break inside, we should already have broken before this
// child.
DCHECK(early_break_in_child);
}
return node->Layout(space, To<NGBlockBreakToken>(break_token),
early_break_in_child);
}
inline scoped_refptr<const NGLayoutResult> LayoutInflow(
const NGConstraintSpace& space,
const NGBreakToken* break_token,
const NGEarlyBreak* early_break,
NGLayoutInputNode* node,
NGInlineChildLayoutContext* context) {
if (auto* inline_node = DynamicTo<NGInlineNode>(node))
return inline_node->Layout(space, break_token, context);
return LayoutBlockChild(space, break_token, early_break,
To<NGBlockNode>(node));
}
NGAdjoiningObjectTypes ToAdjoiningObjectTypes(EClear clear) {
switch (clear) {
default:
NOTREACHED();
FALLTHROUGH;
case EClear::kNone:
return kAdjoiningNone;
case EClear::kLeft:
return kAdjoiningFloatLeft;
case EClear::kRight:
return kAdjoiningFloatRight;
case EClear::kBoth:
return kAdjoiningFloatBoth;
};
}
// Return true if a child is to be cleared past adjoining floats. These are
// floats that would otherwise (if 'clear' were 'none') be pulled down by the
// BFC block offset of the child. If the child is to clear floats, though, we
// obviously need separate the child from the floats and move it past them,
// since that's what clearance is all about. This means that if we have any such
// floats to clear, we know for sure that we get clearance, even before layout.
inline bool HasClearancePastAdjoiningFloats(
NGAdjoiningObjectTypes adjoining_object_types,
const ComputedStyle& child_style,
const ComputedStyle& cb_style) {
return ToAdjoiningObjectTypes(child_style.Clear(cb_style)) &
adjoining_object_types;
}
// Adjust BFC block offset for clearance, if applicable. Return true of
// clearance was applied.
//
// Clearance applies either when the BFC block offset calculated simply isn't
// past all relevant floats, *or* when we have already determined that we're
// directly preceded by clearance.
//
// The latter is the case when we need to force ourselves past floats that would
// otherwise be adjoining, were it not for the predetermined clearance.
// Clearance inhibits margin collapsing and acts as spacing before the
// block-start margin of the child. It needs to be exactly what takes the
// block-start border edge of the cleared block adjacent to the block-end outer
// edge of the "bottommost" relevant float.
//
// We cannot reliably calculate the actual clearance amount at this point,
// because 1) this block right here may actually be a descendant of the block
// that is to be cleared, and 2) we may not yet have separated the margin before
// and after the clearance. None of this matters, though, because we know where
// to place this block if clearance applies: exactly at the ConstraintSpace's
// ClearanceOffset().
bool ApplyClearance(const NGConstraintSpace& constraint_space,
LayoutUnit* bfc_block_offset) {
if (constraint_space.HasClearanceOffset() &&
*bfc_block_offset < constraint_space.ClearanceOffset()) {
*bfc_block_offset = constraint_space.ClearanceOffset();
return true;
}
return false;
}
LayoutUnit LogicalFromBfcLineOffset(LayoutUnit child_bfc_line_offset,
LayoutUnit parent_bfc_line_offset,
LayoutUnit child_inline_size,
LayoutUnit parent_inline_size,
TextDirection direction) {
// We need to respect the current text direction to calculate the logical
// offset correctly.
LayoutUnit relative_line_offset =
child_bfc_line_offset - parent_bfc_line_offset;
LayoutUnit inline_offset =
direction == TextDirection::kLtr
? relative_line_offset
: parent_inline_size - relative_line_offset - child_inline_size;
return inline_offset;
}
LogicalOffset LogicalFromBfcOffsets(const NGBfcOffset& child_bfc_offset,
const NGBfcOffset& parent_bfc_offset,
LayoutUnit child_inline_size,
LayoutUnit parent_inline_size,
TextDirection direction) {
LayoutUnit inline_offset = LogicalFromBfcLineOffset(
child_bfc_offset.line_offset, parent_bfc_offset.line_offset,
child_inline_size, parent_inline_size, direction);
return {inline_offset,
child_bfc_offset.block_offset - parent_bfc_offset.block_offset};
}
inline bool IsEarlyBreakpoint(const NGEarlyBreak& breakpoint,
const NGBoxFragmentBuilder& builder,
NGLayoutInputNode child) {
if (breakpoint.Type() == NGEarlyBreak::kLine)
return child.IsInline() && breakpoint.LineNumber() == builder.LineCount();
if (breakpoint.IsBreakBefore())
return breakpoint.BlockNode() == child;
return false;
}
} // namespace
NGBlockLayoutAlgorithm::NGBlockLayoutAlgorithm(
const NGLayoutAlgorithmParams& params)
: NGLayoutAlgorithm(params),
previous_result_(params.previous_result),
fit_all_lines_(false),
is_resuming_(IsResumingLayout(params.break_token)),
abort_when_bfc_block_offset_updated_(false),
has_processed_first_child_(false),
ignore_line_clamp_(false),
is_line_clamp_context_(params.space.IsLineClampContext()),
lines_until_clamp_(params.space.LinesUntilClamp()),
exclusion_space_(params.space.ExclusionSpace()),
early_break_(params.early_break) {}
// Define the destructor here, so that we can forward-declare more in the
// header.
NGBlockLayoutAlgorithm::~NGBlockLayoutAlgorithm() = default;
void NGBlockLayoutAlgorithm::SetBoxType(NGPhysicalFragment::NGBoxType type) {
container_builder_.SetBoxType(type);
}
MinMaxSizesResult NGBlockLayoutAlgorithm::ComputeMinMaxSizes(
const MinMaxSizesInput& input) const {
if (auto result =
CalculateMinMaxSizesIgnoringChildren(node_, BorderScrollbarPadding()))
return *result;
MinMaxSizes sizes;
bool depends_on_percentage_block_size = false;
const TextDirection direction = Style().Direction();
LayoutUnit float_left_inline_size = input.float_left_inline_size;
LayoutUnit float_right_inline_size = input.float_right_inline_size;
for (NGLayoutInputNode child = Node().FirstChild(); child;
child = child.NextSibling()) {
// We don't check IsRubyText() here intentionally. RubyText width should
// affect this width.
if (child.IsOutOfFlowPositioned() ||
(child.IsColumnSpanAll() && ConstraintSpace().IsInColumnBfc()) ||
child.IsTextControlPlaceholder())
continue;
const ComputedStyle& child_style = child.Style();
const EClear child_clear = child_style.Clear(Style());
bool child_is_new_fc = child.CreatesNewFormattingContext();
// Conceptually floats and a single new-FC would just get positioned on a
// single "line". If there is a float/new-FC with clearance, this creates a
// new "line", resetting the appropriate float size trackers.
//
// Both of the float size trackers get reset for anything that isn't a float
// (inflow and new-FC) at the end of the loop, as this creates a new "line".
if (child.IsFloating() || child_is_new_fc) {
LayoutUnit float_inline_size =
float_left_inline_size + float_right_inline_size;
if (child_clear != EClear::kNone)
sizes.max_size = std::max(sizes.max_size, float_inline_size);
if (child_clear == EClear::kBoth || child_clear == EClear::kLeft)
float_left_inline_size = LayoutUnit();
if (child_clear == EClear::kBoth || child_clear == EClear::kRight)
float_right_inline_size = LayoutUnit();
}
MinMaxSizesInput child_input(input.percentage_resolution_block_size,
input.type);
if (child.IsInline() || child.IsAnonymousBlock()) {
child_input.float_left_inline_size = float_left_inline_size;
child_input.float_right_inline_size = float_right_inline_size;
}
MinMaxSizesResult child_result;
if (child.IsInline()) {
// From |NGBlockLayoutAlgorithm| perspective, we can handle |NGInlineNode|
// almost the same as |NGBlockNode|, because an |NGInlineNode| includes
// all inline nodes following |child| and their descendants, and produces
// an anonymous box that contains all line boxes.
// |NextSibling| returns the next block sibling, or nullptr, skipping all
// following inline siblings and descendants.
child_result =
child.ComputeMinMaxSizes(Style().GetWritingMode(), child_input);
} else {
child_result = ComputeMinAndMaxContentContribution(
Style(), To<NGBlockNode>(child), child_input);
}
DCHECK_LE(child_result.sizes.min_size, child_result.sizes.max_size)
<< child.ToString();
// Determine the max inline contribution of the child.
NGBoxStrut margins = ComputeMinMaxMargins(Style(), child);
LayoutUnit max_inline_contribution;
if (child.IsFloating()) {
// A float adds to its inline size to the current "line". The new max
// inline contribution is just the sum of all the floats on that "line".
LayoutUnit float_inline_size =
child_result.sizes.max_size + margins.InlineSum();
// float_inline_size is negative when the float is completely outside of
// the content area, by e.g., negative margins. Such floats do not affect
// the content size.
if (float_inline_size > 0) {
if (child_style.Floating(Style()) == EFloat::kLeft)
float_left_inline_size += float_inline_size;
else
float_right_inline_size += float_inline_size;
}
max_inline_contribution =
float_left_inline_size + float_right_inline_size;
} else if (child_is_new_fc) {
// As floats are line relative, we perform the margin calculations in the
// line relative coordinate system as well.
LayoutUnit margin_line_left = margins.LineLeft(direction);
LayoutUnit margin_line_right = margins.LineRight(direction);
// line_left_inset and line_right_inset are the "distance" from their
// respective edges of the parent that the new-FC would take. If the
// margin is positive the inset is just whichever of the floats inline
// size and margin is larger, and if negative it just subtracts from the
// float inline size.
LayoutUnit line_left_inset =
margin_line_left > LayoutUnit()
? std::max(float_left_inline_size, margin_line_left)
: float_left_inline_size + margin_line_left;
LayoutUnit line_right_inset =
margin_line_right > LayoutUnit()
? std::max(float_right_inline_size, margin_line_right)
: float_right_inline_size + margin_line_right;
// The order of operations is important here.
// If child_result.sizes.max_size is saturated, adding the insets
// sequentially can result in an DCHECK.
max_inline_contribution =
child_result.sizes.max_size + (line_left_inset + line_right_inset);
} else {
// This is just a standard inflow child.
max_inline_contribution =
child_result.sizes.max_size + margins.InlineSum();
}
sizes.max_size = std::max(sizes.max_size, max_inline_contribution);
// The min inline contribution just assumes that floats are all on their own
// "line".
LayoutUnit min_inline_contribution =
child_result.sizes.min_size + margins.InlineSum();
sizes.min_size = std::max(sizes.min_size, min_inline_contribution);
depends_on_percentage_block_size |=
child_result.depends_on_percentage_block_size;
// Anything that isn't a float will create a new "line" resetting the float
// size trackers.
if (!child.IsFloating()) {
float_left_inline_size = LayoutUnit();
float_right_inline_size = LayoutUnit();
}
}
DCHECK_GE(sizes.min_size, LayoutUnit());
DCHECK_LE(sizes.min_size, sizes.max_size) << Node().ToString();
sizes += BorderScrollbarPadding().InlineSum();
return MinMaxSizesResult(sizes, depends_on_percentage_block_size);
}
LogicalOffset NGBlockLayoutAlgorithm::CalculateLogicalOffset(
const NGFragment& fragment,
LayoutUnit child_bfc_line_offset,
const base::Optional<LayoutUnit>& child_bfc_block_offset) {
LayoutUnit inline_size = container_builder_.InlineSize();
TextDirection direction = ConstraintSpace().Direction();
if (child_bfc_block_offset && container_builder_.BfcBlockOffset()) {
return LogicalFromBfcOffsets(
{child_bfc_line_offset, *child_bfc_block_offset}, ContainerBfcOffset(),
fragment.InlineSize(), inline_size, direction);
}
LayoutUnit inline_offset = LogicalFromBfcLineOffset(
child_bfc_line_offset, container_builder_.BfcLineOffset(),
fragment.InlineSize(), inline_size, direction);
// 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()};
}
scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout() {
scoped_refptr<const NGLayoutResult> result;
// Inline children require an inline child layout context to be
// passed between siblings. We want to stack-allocate that one, but
// only on demand, as it's quite big.
NGLayoutInputNode first_child(nullptr);
if (Node().IsInlineFormattingContextRoot(&first_child))
result = LayoutWithInlineChildLayoutContext(first_child);
else
result = Layout(nullptr);
if (UNLIKELY(result->Status() == NGLayoutResult::kNeedsEarlierBreak)) {
// If we found a good break somewhere inside this block, re-layout and break
// at that location.
DCHECK(!early_break_);
DCHECK(result->GetEarlyBreak());
return RelayoutAndBreakEarlier(*result->GetEarlyBreak());
} else if (UNLIKELY(result->Status() ==
NGLayoutResult::
kNeedsRelayoutWithNoForcedTruncateAtLineClamp)) {
DCHECK(!ignore_line_clamp_);
return RelayoutIgnoringLineClamp();
}
return result;
}
NOINLINE scoped_refptr<const NGLayoutResult>
NGBlockLayoutAlgorithm::LayoutWithInlineChildLayoutContext(
const NGLayoutInputNode& first_child) {
NGInlineChildLayoutContext context;
return LayoutWithItemsBuilder(To<NGInlineNode>(first_child), &context);
}
NOINLINE scoped_refptr<const NGLayoutResult>
NGBlockLayoutAlgorithm::LayoutWithItemsBuilder(
const NGInlineNode& first_child,
NGInlineChildLayoutContext* context) {
NGFragmentItemsBuilder items_builder(
first_child, container_builder_.GetWritingDirection());
container_builder_.SetItemsBuilder(&items_builder);
context->SetItemsBuilder(&items_builder);
scoped_refptr<const NGLayoutResult> result = Layout(context);
// Ensure stack-allocated |NGFragmentItemsBuilder| is not used anymore.
// TODO(kojii): Revisit when the storage of |NGFragmentItemsBuilder| is
// finalized.
container_builder_.SetItemsBuilder(nullptr);
context->SetItemsBuilder(nullptr);
return result;
}
NOINLINE scoped_refptr<const NGLayoutResult>
NGBlockLayoutAlgorithm::RelayoutAndBreakEarlier(
const NGEarlyBreak& breakpoint) {
NGLayoutAlgorithmParams params(Node(),
container_builder_.InitialFragmentGeometry(),
ConstraintSpace(), BreakToken(), &breakpoint);
NGBlockLayoutAlgorithm algorithm_with_break(params);
NGBoxFragmentBuilder& new_builder = algorithm_with_break.container_builder_;
new_builder.SetBoxType(container_builder_.BoxType());
// We're not going to run out of space in the next layout pass, since we're
// breaking earlier, so no space shortage will be detected. Repeat what we
// found in this pass.
new_builder.PropagateSpaceShortage(container_builder_.MinimalSpaceShortage());
return algorithm_with_break.Layout();
}
NOINLINE scoped_refptr<const NGLayoutResult>
NGBlockLayoutAlgorithm::RelayoutIgnoringLineClamp() {
NGLayoutAlgorithmParams params(Node(),
container_builder_.InitialFragmentGeometry(),
ConstraintSpace(), BreakToken(), nullptr);
NGBlockLayoutAlgorithm algorithm_ignoring_line_clamp(params);
algorithm_ignoring_line_clamp.ignore_line_clamp_ = true;
NGBoxFragmentBuilder& new_builder =
algorithm_ignoring_line_clamp.container_builder_;
new_builder.SetBoxType(container_builder_.BoxType());
return algorithm_ignoring_line_clamp.Layout();
}
inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout(
NGInlineChildLayoutContext* inline_child_layout_context) {
child_percentage_size_ = CalculateChildPercentageSize(
ConstraintSpace(), Node(), ChildAvailableSize());
replaced_child_percentage_size_ = CalculateReplacedChildPercentageSize(
ConstraintSpace(), Node(), ChildAvailableSize(), BorderScrollbarPadding(),
BorderPadding());
if (ConstraintSpace().IsLegacyTableCell())
container_builder_.AdjustBorderScrollbarPaddingForTableCell();
DCHECK_EQ(!!inline_child_layout_context,
Node().IsInlineFormattingContextRoot());
container_builder_.SetIsInlineFormattingContext(inline_child_layout_context);
container_builder_.SetBfcLineOffset(
ConstraintSpace().BfcOffset().line_offset);
if (NGAdjoiningObjectTypes adjoining_object_types =
ConstraintSpace().AdjoiningObjectTypes()) {
DCHECK(!ConstraintSpace().IsNewFormattingContext());
DCHECK(!container_builder_.BfcBlockOffset());
// If there were preceding adjoining objects, they will be affected when the
// BFC block-offset gets resolved or updated. We then need to roll back and
// re-layout those objects with the new BFC block-offset, once the BFC
// block-offset is updated.
abort_when_bfc_block_offset_updated_ = true;
container_builder_.SetAdjoiningObjectTypes(adjoining_object_types);
}
if (Style().IsDeprecatedWebkitBoxWithVerticalLineClamp()) {
is_line_clamp_context_ = true;
if (!ignore_line_clamp_)
lines_until_clamp_ = Style().LineClamp();
} else if (Style().HasLineClamp()) {
UseCounter::Count(Node().GetDocument(),
WebFeature::kWebkitLineClampWithoutWebkitBox);
}
LayoutUnit content_edge = BorderScrollbarPadding().block_start;
NGPreviousInflowPosition previous_inflow_position = {
LayoutUnit(), ConstraintSpace().MarginStrut(),
is_resuming_ ? LayoutUnit() : container_builder_.Padding().block_start,
/* self_collapsing_child_had_clearance */ false};
// Do not collapse margins between parent and its child if:
//
// A: There is border/padding between them.
// B: This is a new formatting context
// C: We're resuming layout from a break token. Margin struts cannot pass from
// one fragment to another if they are generated by the same block; they
// must be dealt with at the first fragment.
// D: We're forced to stop margin collapsing by a CSS property
//
// In all those cases we can and must resolve the BFC block offset now.
if (content_edge || is_resuming_ ||
ConstraintSpace().IsNewFormattingContext()) {
bool discard_subsequent_margins =
previous_inflow_position.margin_strut.discard_margins && !content_edge;
if (!ResolveBfcBlockOffset(&previous_inflow_position)) {
// There should be no preceding content that depends on the BFC block
// offset of a new formatting context block, and likewise when resuming
// from a break token.
DCHECK(!ConstraintSpace().IsNewFormattingContext());
DCHECK(!is_resuming_);
return container_builder_.Abort(NGLayoutResult::kBfcBlockOffsetResolved);
}
// Move to the content edge. This is where the first child should be placed.
previous_inflow_position.logical_block_offset = content_edge;
// If we resolved the BFC block offset now, the margin strut has been
// reset. If margins are to be discarded, and this box would otherwise have
// adjoining margins between its own margin and those subsequent content,
// we need to make sure subsequent content discard theirs.
if (discard_subsequent_margins)
previous_inflow_position.margin_strut.discard_margins = true;
}
#if DCHECK_IS_ON()
// If this is a new formatting context, we should definitely be at the origin
// here. If we're resuming from a break token (for a block that doesn't
// establish a new formatting context), that may not be the case,
// though. There may e.g. be clearance involved, or inline-start margins.
if (ConstraintSpace().IsNewFormattingContext())
DCHECK_EQ(*container_builder_.BfcBlockOffset(), LayoutUnit());
// If this is a new formatting context, or if we're resuming from a break
// token, no margin strut must be lingering around at this point.
if (ConstraintSpace().IsNewFormattingContext() || is_resuming_)
DCHECK(ConstraintSpace().MarginStrut().IsEmpty());
if (!container_builder_.BfcBlockOffset()) {
// New formatting-contexts, and when we have a self-collapsing child
// affected by clearance must already have their BFC block-offset resolved.
DCHECK(!previous_inflow_position.self_collapsing_child_had_clearance);
DCHECK(!ConstraintSpace().IsNewFormattingContext());
}
#endif
// If this node is a quirky container, (we are in quirks mode and either a
// table cell or body), we set our margin strut to a mode where it only
// considers non-quirky margins. E.g.
// <body>
// <p></p>
// <div style="margin-top: 10px"></div>
// <h1>Hello</h1>
// </body>
// In the above example <p>'s & <h1>'s margins are ignored as they are
// quirky, and we only consider <div>'s 10px margin.
if (node_.IsQuirkyContainer())
previous_inflow_position.margin_strut.is_quirky_container_start = true;
// Try to reuse line box fragments from cached fragments if possible.
// When possible, this adds fragments to |container_builder_| and update
// |previous_inflow_position| and |BreakToken()|.
scoped_refptr<const NGInlineBreakToken> previous_inline_break_token;
NGBlockChildIterator child_iterator(Node().FirstChild(), BreakToken());
// If this layout is blocked by a display-lock, then we pretend this node has
// no children and that there are no break tokens. Due to this, we skip layout
// on these children.
if (Node().ChildLayoutBlockedByDisplayLock())
child_iterator = NGBlockChildIterator(NGBlockNode(nullptr), nullptr);
NGBlockNode ruby_text_child(nullptr);
NGBlockNode placeholder_child(nullptr);
for (auto entry = child_iterator.NextChild();
NGLayoutInputNode child = entry.node;
entry = child_iterator.NextChild(previous_inline_break_token.get())) {
const NGBreakToken* child_break_token = entry.token;
if (child.IsOutOfFlowPositioned()) {
// We don't support fragmentation inside out-of-flow positioned boxes yet,
// but breaking before is fine. This may happen when a column spanner is
// directly followed by an OOF.
DCHECK(!child_break_token ||
(child_break_token->IsBlockType() &&
To<NGBlockBreakToken>(child_break_token)->IsBreakBefore()));
HandleOutOfFlowPositioned(previous_inflow_position,
To<NGBlockNode>(child));
} else if (child.IsFloating()) {
HandleFloat(previous_inflow_position, To<NGBlockNode>(child),
To<NGBlockBreakToken>(child_break_token));
} else if (child.IsListMarker() && !child.ListMarkerOccupiesWholeLine()) {
container_builder_.SetUnpositionedListMarker(
NGUnpositionedListMarker(To<NGBlockNode>(child)));
} else if (child.IsColumnSpanAll() && ConstraintSpace().IsInColumnBfc()) {
// The child is a column spanner. We now need to finish this
// fragmentainer, then abort and let the column layout algorithm handle
// the spanner as a child.
DCHECK(!container_builder_.DidBreakSelf());
DCHECK(!container_builder_.FoundColumnSpanner());
DCHECK(!child_break_token);
container_builder_.SetColumnSpanner(To<NGBlockNode>(child));
// After the spanner(s), we are going to resume inside this block. If
// there's a subsequent sibling that's not a spanner, we're resume right
// in front of that one. Otherwise we'll just resume after all the
// children.
for (entry = child_iterator.NextChild();
NGLayoutInputNode sibling = entry.node;
entry = child_iterator.NextChild()) {
DCHECK(!entry.token);
if (sibling.IsColumnSpanAll())
continue;
container_builder_.AddBreakBeforeChild(sibling, kBreakAppealPerfect,
/* is_forced_break */ true);
break;
}
break;
} else if (IsRubyText(child)) {
ruby_text_child = To<NGBlockNode>(child);
} else if (child.IsTextControlPlaceholder()) {
placeholder_child = To<NGBlockNode>(child);
} else {
// If this is the child we had previously determined to break before, do
// so now and finish layout.
if (UNLIKELY(
early_break_ &&
IsEarlyBreakpoint(*early_break_, container_builder_, child))) {
if (!ResolveBfcBlockOffset(&previous_inflow_position)) {
// However, the predetermined breakpoint may be exactly where the BFC
// block-offset gets resolved. If that hasn't yet happened, we need to
// do that first and re-layout at the right BFC block-offset, and THEN
// break.
return container_builder_.Abort(
NGLayoutResult::kBfcBlockOffsetResolved);
}
container_builder_.AddBreakBeforeChild(child, kBreakAppealPerfect,
/* is_forced_break */ false);
ConsumeRemainingFragmentainerSpace(&previous_inflow_position);
break;
}
NGLayoutResult::EStatus status;
if (child.CreatesNewFormattingContext()) {
status = HandleNewFormattingContext(child, child_break_token,
&previous_inflow_position);
previous_inline_break_token = nullptr;
} else {
status = HandleInflow(
child, child_break_token, &previous_inflow_position,
inline_child_layout_context, &previous_inline_break_token);
}
if (status != NGLayoutResult::kSuccess) {
// We need to abort the layout. No fragment will be generated.
return container_builder_.Abort(status);
}
if (ConstraintSpace().HasBlockFragmentation()) {
// A child break in a parallel flow doesn't affect whether we should
// break here or not.
if (container_builder_.HasInflowChildBreakInside()) {
// But if the break happened in the same flow, we'll now just finish
// layout of the fragment. No more siblings should be processed.
break;
}
// We need to propagate the initial break-before value up our container
// chain, until we reach a container that's not a first child. If we get
// all the way to the root of the fragmentation context without finding
// any such container, we have no valid class A break point, and if a
// forced break was requested, none will be inserted.
if (!has_processed_first_child_ && !child.IsInline())
container_builder_.SetInitialBreakBefore(child.Style().BreakBefore());
has_processed_first_child_ = true;
}
}
}
if (ruby_text_child)
HandleRubyText(ruby_text_child);
if (placeholder_child)
HandleTextControlPlaceholder(placeholder_child, previous_inflow_position);
if (UNLIKELY(ConstraintSpace().IsNewFormattingContext() &&
!ignore_line_clamp_ && lines_until_clamp_ == 0 &&
intrinsic_block_size_when_clamped_)) {
// Truncation of the last line was forced, but there are no lines after the
// truncated line. Rerun layout without forcing truncation. This is only
// done if line-clamp was specified on the element as the element containing
// the node may have subsequent lines. If there aren't, the containing
// element will relayout.
return container_builder_.Abort(
NGLayoutResult::kNeedsRelayoutWithNoForcedTruncateAtLineClamp);
}
if (!child_iterator.NextChild(previous_inline_break_token.get()).node) {
// We've gone through all the children. This doesn't necessarily mean that
// we're done fragmenting, as there may be parallel flows [1] (visible
// overflow) still needing more space than what the current fragmentainer
// can provide. It does mean, though, that, for any future fragmentainers,
// we'll just be looking at the break tokens, if any, and *not* start laying
// out any nodes from scratch, since we have started/finished all the
// children, or at least created break tokens for them.
//
// [1] https://drafts.csswg.org/css-break/#parallel-flows
container_builder_.SetHasSeenAllChildren();
}
// The intrinsic block size is not allowed to be less than the content edge
// offset, as that could give us a negative content box size.
intrinsic_block_size_ = content_edge;
// To save space of the stack when we recurse into children, the rest of this
// function is continued within |FinishLayout|. However it should be read as
// one function.
return FinishLayout(&previous_inflow_position, inline_child_layout_context);
}
scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::FinishLayout(
NGPreviousInflowPosition* previous_inflow_position,
NGInlineChildLayoutContext* inline_child_layout_context) {
LogicalSize border_box_size = container_builder_.InitialBorderBoxSize();
NGMarginStrut end_margin_strut = previous_inflow_position->margin_strut;
// Add line height for empty content editable or button with empty label, e.g.
// <div contenteditable></div>, <input type="button" value="">
if (container_builder_.HasSeenAllChildren() &&
HasLineEvenIfEmpty(Node().GetLayoutBox())) {
intrinsic_block_size_ +=
std::max(intrinsic_block_size_,
Node().GetLayoutBox()->LogicalHeightForEmptyLine());
// Test [1][2] require baseline offset for empty editable.
// [1] css3/flexbox/baseline-for-empty-line.html
// [2] inline-block/contenteditable-baseline.html
const LayoutBlock* const layout_block =
To<LayoutBlock>(Node().GetLayoutBox());
if (auto baseline_offset = layout_block->BaselineForEmptyLine(
layout_block->IsHorizontalWritingMode() ? kHorizontalLine
: kVerticalLine))
container_builder_.SetBaseline(*baseline_offset);
}
// Collapse annotation overflow and padding.
// logical_block_offset already contains block-end annotation overflow.
// However, if the container has non-zero block-end padding, the annotation
// can extend on the padding. So we decrease logical_block_offset by
// shareable part of the annotation overflow and the padding.
if (previous_inflow_position->block_end_annotation_space < LayoutUnit()) {
const LayoutUnit annotation_overflow =
-previous_inflow_position->block_end_annotation_space;
previous_inflow_position->logical_block_offset -=
std::min(container_builder_.Padding().block_end, annotation_overflow);
}
// If the current layout is a new formatting context, we need to encapsulate
// all of our floats.
if (ConstraintSpace().IsNewFormattingContext()) {
intrinsic_block_size_ = std::max(
intrinsic_block_size_, exclusion_space_.ClearanceOffset(EClear::kBoth));
}
// If line clamping occurred, the intrinsic block-size comes from the
// intrinsic block-size at the time of the clamp.
if (intrinsic_block_size_when_clamped_) {
DCHECK(container_builder_.BfcBlockOffset());
intrinsic_block_size_ = *intrinsic_block_size_when_clamped_ +
BorderScrollbarPadding().block_end;
end_margin_strut = NGMarginStrut();
} else if (BorderScrollbarPadding().block_end ||
previous_inflow_position->self_collapsing_child_had_clearance ||
ConstraintSpace().IsNewFormattingContext()) {
// The end margin strut of an in-flow fragment contributes to the size of
// the current fragment if:
// - There is block-end border/scrollbar/padding.
// - There was a self-collapsing child affected by clearance.
// - We are a new formatting context.
// Additionally this fragment produces no end margin strut.
if (!container_builder_.BfcBlockOffset()) {
// If we have collapsed through the block start and all children (if any),
// now is the time to determine the BFC block offset, because finally we
// have found something solid to hang on to (like clearance or a bottom
// border, for instance). If we're a new formatting context, though, we
// shouldn't be here, because then the offset should already have been
// determined.
DCHECK(!ConstraintSpace().IsNewFormattingContext());
if (!ResolveBfcBlockOffset(previous_inflow_position)) {
return container_builder_.Abort(
NGLayoutResult::kBfcBlockOffsetResolved);
}
DCHECK(container_builder_.BfcBlockOffset());
} else {
// If we are a quirky container, we ignore any quirky margins and just
// consider normal margins to extend our size. Other UAs perform this
// calculation differently, e.g. by just ignoring the *last* quirky
// margin.
LayoutUnit margin_strut_sum = node_.IsQuirkyContainer()
? end_margin_strut.QuirkyContainerSum()
: end_margin_strut.Sum();
if (ConstraintSpace().HasKnownFragmentainerBlockSize()) {
LayoutUnit bfc_block_offset =
*container_builder_.BfcBlockOffset() +
previous_inflow_position->logical_block_offset;
margin_strut_sum = AdjustedMarginAfterFinalChildFragment(
ConstraintSpace(), bfc_block_offset, margin_strut_sum);
}
// The trailing margin strut will be part of our intrinsic block size, but
// only if there is something that separates the end margin strut from the
// input margin strut (typically child content, block start
// border/padding, or this being a new BFC). If the margin strut from a
// previous sibling or ancestor managed to collapse through all our
// children (if any at all, that is), it means that the resulting end
// margin strut actually pushes us down, and it should obviously not be
// doubly accounted for as our block size.
intrinsic_block_size_ = std::max(
intrinsic_block_size_,
previous_inflow_position->logical_block_offset + margin_strut_sum);
}
intrinsic_block_size_ += BorderScrollbarPadding().block_end;
end_margin_strut = NGMarginStrut();
} else {
// Update our intrinsic block size to be just past the block-end border edge
// of the last in-flow child. The pending margin is to be propagated to our
// container, so ignore it.
intrinsic_block_size_ = std::max(
intrinsic_block_size_, previous_inflow_position->logical_block_offset);
}
// Save the unconstrained intrinsic size on the builder before clamping it.
LayoutUnit unconstrained_intrinsic_block_size = intrinsic_block_size_;
container_builder_.SetOverflowBlockSize(intrinsic_block_size_);
intrinsic_block_size_ = ClampIntrinsicBlockSize(
ConstraintSpace(), Node(), BorderScrollbarPadding(),
intrinsic_block_size_,
CalculateQuirkyBodyMarginBlockSum(end_margin_strut));
LayoutUnit previously_consumed_block_size;
if (UNLIKELY(BreakToken()))
previously_consumed_block_size = BreakToken()->ConsumedBlockSize();
// Recompute the block-axis size now that we know our content size.
border_box_size.block_size = ComputeBlockSizeForFragment(
ConstraintSpace(), Style(), BorderPadding(),
previously_consumed_block_size + intrinsic_block_size_,
border_box_size.inline_size, Node().ShouldBeConsideredAsReplaced());
container_builder_.SetFragmentsTotalBlockSize(border_box_size.block_size);
// If our BFC block-offset is still unknown, we check:
// - If we have a non-zero block-size (margins don't collapse through us).
// - If we have a break token. (Even if we are self-collapsing we position
// ourselves at the very start of the fragmentainer).
// - We got interrupted by a column spanner.
if (!container_builder_.BfcBlockOffset() &&
(border_box_size.block_size || BreakToken() ||
container_builder_.FoundColumnSpanner())) {
if (!ResolveBfcBlockOffset(previous_inflow_position))
return container_builder_.Abort(NGLayoutResult::kBfcBlockOffsetResolved);
DCHECK(container_builder_.BfcBlockOffset());
}
if (container_builder_.BfcBlockOffset()) {
// Do not collapse margins between the last in-flow child and bottom margin
// of its parent if:
// - The block-size differs from the intrinsic size.
// - The parent has computed block-size != auto.
if (border_box_size.block_size != intrinsic_block_size_ ||
!BlockLengthUnresolvable(ConstraintSpace(), Style().LogicalHeight())) {
end_margin_strut = NGMarginStrut();
}
}
// List markers should have been positioned if we had line boxes, or boxes
// that have line boxes. If there were no line boxes, position without line
// boxes.
if (container_builder_.UnpositionedListMarker() && node_.IsListItem()) {
if (!PositionListMarkerWithoutLineBoxes(previous_inflow_position))
return container_builder_.Abort(NGLayoutResult::kBfcBlockOffsetResolved);
}
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 objects, and shouldn't propagate this information to siblings.
container_builder_.ResetAdjoiningObjectTypes();
} else {
// If we don't know our BFC block-offset yet, we know that for
// margin-collapsing purposes we are self-collapsing.
container_builder_.SetIsSelfCollapsing();
// 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()) {
container_builder_.SetBfcBlockOffset(
*ConstraintSpace().ForcedBfcBlockOffset());
}
}
// At this point, perform any final table-cell adjustments needed.
if (ConstraintSpace().IsTableCell())
FinalizeForTableCell(unconstrained_intrinsic_block_size);
// 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.
if (container_builder_.BfcBlockOffset() &&
ConstraintSpace().HasBlockFragmentation()) {
if (!FinalizeForFragmentation())
return container_builder_.Abort(NGLayoutResult::kNeedsEarlierBreak);
}
NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), &container_builder_).Run();
#if DCHECK_IS_ON()
// If we're not participating in a fragmentation context, no block
// fragmentation related fields should have been set.
if (!ConstraintSpace().HasBlockFragmentation())
container_builder_.CheckNoBlockFragmentation();
#endif
// Adjust the position of the final baseline if needed.
container_builder_.SetLastBaselineToBlockEndMarginEdgeIfNeeded();
// An exclusion space is confined to nodes within the same formatting context.
if (!ConstraintSpace().IsNewFormattingContext()) {
container_builder_.SetExclusionSpace(std::move(exclusion_space_));
container_builder_.SetLinesUntilClamp(lines_until_clamp_);
}
if (ConstraintSpace().UseFirstLineStyle())
container_builder_.SetStyleVariant(NGStyleVariant::kFirstLine);
return container_builder_.ToBoxFragment();
}
bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache(
NGInlineNode inline_node,
NGPreviousInflowPosition* previous_inflow_position,
scoped_refptr<const NGInlineBreakToken>* inline_break_token_out) {
DCHECK(previous_result_);
DCHECK(!inline_node.IsEmptyInline());
DCHECK(container_builder_.BfcBlockOffset());
DCHECK(previous_inflow_position->margin_strut.IsEmpty());
DCHECK(!previous_inflow_position->self_collapsing_child_had_clearance);
const auto& previous_fragment =
To<NGPhysicalBoxFragment>(previous_result_->PhysicalFragment());
const NGFragmentItems* previous_items = previous_fragment.Items();
DCHECK(previous_items);
// Find reusable lines. Fail if no items are reusable.
// TODO(kojii): |DirtyLinesFromNeedsLayout| is needed only once for a
// |LayoutBlockFlow|, not for every fragment.
NGFragmentItems::DirtyLinesFromNeedsLayout(*inline_node.GetLayoutBlockFlow());
const NGFragmentItem* end_item =
previous_items->EndOfReusableItems(previous_fragment);
DCHECK(end_item);
if (!end_item || end_item == &previous_items->front())
return false;
wtf_size_t max_lines = 0;
if (lines_until_clamp_) {
// There is an additional logic for the last clamped line. Reuse only up to
// before that to use the same logic.
if (*lines_until_clamp_ <= 1)
return false;
max_lines = *lines_until_clamp_ - 1;
}
const auto& children = container_builder_.Children();
const wtf_size_t children_before = children.size();
NGFragmentItemsBuilder* items_builder = container_builder_.ItemsBuilder();
const NGConstraintSpace& space = ConstraintSpace();
DCHECK_EQ(items_builder->GetWritingDirection(), space.GetWritingDirection());
const auto result =
items_builder->AddPreviousItems(previous_fragment, *previous_items,
&container_builder_, end_item, max_lines);
if (UNLIKELY(!result.succeeded)) {
DCHECK_EQ(children.size(), children_before);
DCHECK(!result.used_block_size);
DCHECK(!result.inline_break_token);
return false;
}
DCHECK_GT(result.line_count, 0u);
DCHECK(!max_lines || result.line_count <= max_lines);
if (lines_until_clamp_) {
DCHECK_GT(*lines_until_clamp_, static_cast<int>(result.line_count));
lines_until_clamp_ = *lines_until_clamp_ - result.line_count;
}
// |AddPreviousItems| may have added more than one lines. Propagate baselines
// from them.
for (const auto& child : base::make_span(children).subspan(children_before)) {
DCHECK(child.fragment->IsLineBox());
PropagateBaselineFromChild(To<NGPhysicalContainerFragment>(*child.fragment),
child.offset.block_offset);
}
previous_inflow_position->logical_block_offset += result.used_block_size;
*inline_break_token_out = result.inline_break_token;
return true;
}
void NGBlockLayoutAlgorithm::HandleOutOfFlowPositioned(
const NGPreviousInflowPosition& previous_inflow_position,
NGBlockNode child) {
if (ConstraintSpace().HasBlockFragmentation()) {
// Forced breaks cannot be specified directly on out-of-flow positioned
// elements, but if the preceding block has a forced break after, we need to
// break before it. Note that we really only need to do this if block-start
// offset is auto (but it's harmless to do it also when it's non-auto).
EBreakBetween break_between =
container_builder_.JoinedBreakBetweenValue(EBreakBetween::kAuto);
if (IsForcedBreakValue(ConstraintSpace(), break_between)) {
container_builder_.AddBreakBeforeChild(child, kBreakAppealPerfect,
/* is_forced_break*/ true);
return;
}
}
DCHECK(child.IsOutOfFlowPositioned());
LogicalOffset static_offset = {BorderScrollbarPadding().inline_start,
previous_inflow_position.logical_block_offset};
// We only include the margin strut in the OOF static-position if we know we
// aren't going to be a zero-block-size fragment.
if (container_builder_.BfcBlockOffset())
static_offset.block_offset += previous_inflow_position.margin_strut.Sum();
if (child.Style().IsOriginalDisplayInlineType()) {
// The static-position of inline-level OOF-positioned nodes depends on
// previous floats (if any).
//
// Due to this we need to mark this node as having adjoining objects, and
// perform a re-layout if our position shifts.
if (!container_builder_.BfcBlockOffset()) {
container_builder_.AddAdjoiningObjectTypes(kAdjoiningInlineOutOfFlow);
abort_when_bfc_block_offset_updated_ = true;
}
LayoutUnit origin_bfc_block_offset =
container_builder_.BfcBlockOffset().value_or(
ConstraintSpace().ExpectedBfcBlockOffset()) +
static_offset.block_offset;
NGBfcOffset origin_bfc_offset = {
ConstraintSpace().BfcOffset().line_offset +
BorderScrollbarPadding().LineLeft(Style().Direction()),
origin_bfc_block_offset};
static_offset.inline_offset += CalculateOutOfFlowStaticInlineLevelOffset(
Style(), origin_bfc_offset, exclusion_space_,
ChildAvailableSize().inline_size);
}
container_builder_.AddOutOfFlowChildCandidate(child, static_offset);
}
void NGBlockLayoutAlgorithm::HandleFloat(
const NGPreviousInflowPosition& previous_inflow_position,
NGBlockNode child,
const NGBlockBreakToken* child_break_token) {
// If we're resuming layout, we must always know our position in the BFC.
DCHECK(!IsResumingLayout(child_break_token) ||
container_builder_.BfcBlockOffset());
// If we don't have a BFC block-offset yet, the "expected" BFC block-offset
// is used to optimistically place floats.
NGBfcOffset origin_bfc_offset = {
ConstraintSpace().BfcOffset().line_offset +
BorderScrollbarPadding().LineLeft(ConstraintSpace().Direction()),
container_builder_.BfcBlockOffset()
? NextBorderEdge(previous_inflow_position)
: ConstraintSpace().ExpectedBfcBlockOffset()};
if (ConstraintSpace().HasBlockFragmentation()) {
// Forced breaks cannot be specified directly on floats, but if the
// preceding block has a forced break after, we need to break before this
// float.
EBreakBetween break_between =
container_builder_.JoinedBreakBetweenValue(EBreakBetween::kAuto);
if (IsForcedBreakValue(ConstraintSpace(), break_between)) {
container_builder_.AddBreakBeforeChild(child, kBreakAppealPerfect,
/* is_forced_break*/ true);
return;
}
}
NGUnpositionedFloat unpositioned_float(
child, child_break_token, ChildAvailableSize(), child_percentage_size_,
replaced_child_percentage_size_, origin_bfc_offset, ConstraintSpace(),
Style());
if (!container_builder_.BfcBlockOffset()) {
container_builder_.AddAdjoiningObjectTypes(
unpositioned_float.IsLineLeft(ConstraintSpace().Direction())
? kAdjoiningFloatLeft
: kAdjoiningFloatRight);
// If we don't have a forced BFC block-offset yet, we'll optimistically
// place floats at the "expected" BFC block-offset. If this differs from
// our final BFC block-offset we'll need to re-layout.
if (!ConstraintSpace().ForcedBfcBlockOffset())
abort_when_bfc_block_offset_updated_ = true;
}
NGPositionedFloat positioned_float =
PositionFloat(&unpositioned_float, &exclusion_space_);
if (positioned_float.need_break_before) {
DCHECK(ConstraintSpace().HasBlockFragmentation());
LayoutUnit fragmentainer_block_offset =
ConstraintSpace().FragmentainerOffsetAtBfc() +
positioned_float.bfc_offset.block_offset;
BreakBeforeChild(ConstraintSpace(), child, *positioned_float.layout_result,
fragmentainer_block_offset,
/* appeal */ base::nullopt,
/* is_forced_break */ false, &container_builder_);
// After breaking before the float, carry on with layout of this
// container. The float constitutes a parallel flow, and there may be
// siblings that could still fit in the current fragmentainer.
return;
}
DCHECK_EQ(positioned_float.layout_result->Status(), NGLayoutResult::kSuccess);
// TODO(mstensho): There should be a class A breakpoint between a float and
// another float, and also between a float and an in-flow block.
const NGPhysicalFragment& physical_fragment =
positioned_float.layout_result->PhysicalFragment();
LayoutUnit float_inline_size =
NGFragment(ConstraintSpace().GetWritingDirection(), physical_fragment)
.InlineSize();
NGBfcOffset bfc_offset = {ConstraintSpace().BfcOffset().line_offset,
container_builder_.BfcBlockOffset().value_or(
ConstraintSpace().ExpectedBfcBlockOffset())};
LogicalOffset logical_offset = LogicalFromBfcOffsets(
positioned_float.bfc_offset, bfc_offset, float_inline_size,
container_builder_.InlineSize(), ConstraintSpace().Direction());
container_builder_.AddResult(*positioned_float.layout_result, logical_offset);
}
NGLayoutResult::EStatus NGBlockLayoutAlgorithm::HandleNewFormattingContext(
NGLayoutInputNode child,
const NGBreakToken* child_break_token,
NGPreviousInflowPosition* previous_inflow_position) {
DCHECK(child);
DCHECK(!child.IsFloating());
DCHECK(!child.IsOutOfFlowPositioned());
DCHECK(child.CreatesNewFormattingContext());
DCHECK(child.IsBlock());
const ComputedStyle& child_style = child.Style();
const TextDirection direction = ConstraintSpace().Direction();
NGInflowChildData child_data =
ComputeChildData(*previous_inflow_position, child, child_break_token,
/* is_new_fc */ true);
LayoutUnit child_origin_line_offset =
ConstraintSpace().BfcOffset().line_offset +
BorderScrollbarPadding().LineLeft(direction);
// If the child has a block-start margin, and the BFC block offset is still
// unresolved, and we have preceding adjoining floats, things get complicated
// here. Depending on whether the child fits beside the floats, the margin may
// or may not be adjoining with the current margin strut. This affects the
// position of the preceding adjoining floats. We may have to resolve the BFC
// block offset once with the child's margin tentatively adjoining, then
// realize that the child isn't going to fit beside the floats at the current
// position, and therefore re-resolve the BFC block offset with the child's
// margin non-adjoining. This is akin to clearance.
NGMarginStrut adjoining_margin_strut(previous_inflow_position->margin_strut);
adjoining_margin_strut.Append(child_data.margins.block_start,
child_style.HasMarginBeforeQuirk());
LayoutUnit adjoining_bfc_offset_estimate =
child_data.bfc_offset_estimate.block_offset +
adjoining_margin_strut.Sum();
LayoutUnit non_adjoining_bfc_offset_estimate =
child_data.bfc_offset_estimate.block_offset +
previous_inflow_position->margin_strut.Sum();
LayoutUnit child_bfc_offset_estimate = adjoining_bfc_offset_estimate;
bool bfc_offset_already_resolved = false;
bool child_determined_bfc_offset = false;
bool child_margin_got_separated = false;
bool has_adjoining_floats = false;
if (!container_builder_.BfcBlockOffset()) {
has_adjoining_floats =
container_builder_.AdjoiningObjectTypes() & kAdjoiningFloatBoth;
// 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_.AdjoiningObjectTypes(), 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().ForcedBfcBlockOffset();
// We require that the BFC block offset be the one we'd get with margins
// adjoining, margins separated, or if clearance was applied to either of
// these. Anything else is a bug.
DCHECK(child_bfc_offset_estimate == adjoining_bfc_offset_estimate ||
child_bfc_offset_estimate == non_adjoining_bfc_offset_estimate ||
child_bfc_offset_estimate == ConstraintSpace().ClearanceOffset());
// Figure out if the child margin has already got separated from the
// margin strut or not.
child_margin_got_separated =
child_bfc_offset_estimate != adjoining_bfc_offset_estimate;
}
// The BFC block offset of this container gets resolved because of this
// child.
child_determined_bfc_offset = true;
// The block-start margin of the child will only affect the parent's
// position if it is adjoining.
if (!child_margin_got_separated) {
SetSubtreeModifiedMarginStrutIfNeeded(
&child_style.MarginBeforeUsing(Style()));
}
if (!ResolveBfcBlockOffset(previous_inflow_position,
child_bfc_offset_estimate)) {
// If we need to abort here, it means that we had preceding unpositioned
// floats. This is only expected if we're here for the first time.
DCHECK(!bfc_offset_already_resolved);
return NGLayoutResult::kBfcBlockOffsetResolved;
}
// We reset the block offset here as it may have been affected by clearance.
child_bfc_offset_estimate = ContainerBfcOffset().block_offset;
}
// If the child has a non-zero block-start margin, our initial estimate will
// be that any pending floats will be flush (block-start-wise) with this
// child, since they are affected by margin collapsing. Furthermore, this
// child's margin may also pull parent blocks downwards. However, this is only
// the case if the child fits beside the floats at the current block
// offset. If it doesn't (or if it gets clearance), the child needs to be
// pushed down. In this case, the child's margin no longer collapses with the
// previous margin strut, so the pending floats and parent blocks need to
// ignore this margin, which may cause them to end up at completely different
// positions than initially estimated. In other words, we'll need another
// layout pass if this happens.
bool abort_if_cleared = child_data.margins.block_start != LayoutUnit() &&
!child_margin_got_separated &&
child_determined_bfc_offset;
NGBfcOffset child_bfc_offset;
scoped_refptr<const NGLayoutResult> layout_result =
LayoutNewFormattingContext(
child, child_break_token, child_data,
{child_origin_line_offset, child_bfc_offset_estimate},
abort_if_cleared, &child_bfc_offset);
if (!layout_result) {
DCHECK(abort_if_cleared);
// Layout got aborted, because the child got pushed down by floats, and we
// may have had pending floats that we tentatively positioned incorrectly
// (since the child's margin shouldn't have affected them). Try again
// without the child's margin. So, we need another layout pass. Figure out
// if we can do it right away from here, or if we have to roll back and
// reposition floats first.
if (child_determined_bfc_offset) {
// The BFC block offset was calculated when we got to this child, with
// the child's margin adjoining. Since that turned out to be wrong,
// 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,
/* forced_bfc_block_offset */ base::nullopt);
if ((bfc_offset_already_resolved || has_adjoining_floats) &&
old_offset != *container_builder_.BfcBlockOffset()) {
// The first BFC block offset resolution turned out to be wrong, and we
// positioned preceding adjacent floats based on that. Now we have to
// roll back and position them at the correct offset. The only expected
// incorrect estimate is with the child's margin adjoining. Any other
// incorrect estimate will result in failed layout.
DCHECK_EQ(old_offset, adjoining_bfc_offset_estimate);
return NGLayoutResult::kBfcBlockOffsetResolved;
}
}
child_bfc_offset_estimate = non_adjoining_bfc_offset_estimate;
child_margin_got_separated = true;
// We can re-layout the child right away. This re-layout *must* produce a
// fragment which fits within the exclusion space.
layout_result = LayoutNewFormattingContext(
child, child_break_token, child_data,
{child_origin_line_offset, child_bfc_offset_estimate},
/* abort_if_cleared */ false, &child_bfc_offset);
}
if (ConstraintSpace().HasBlockFragmentation()) {
bool has_container_separation =
has_processed_first_child_ || child_margin_got_separated ||
child_bfc_offset.block_offset > child_bfc_offset_estimate ||
layout_result->IsPushedByFloats();
NGBreakStatus break_status = BreakBeforeChildIfNeeded(
child, *layout_result, previous_inflow_position,
child_bfc_offset.block_offset, has_container_separation);
if (break_status == NGBreakStatus::kBrokeBefore)
return NGLayoutResult::kSuccess;
if (break_status == NGBreakStatus::kNeedsEarlierBreak)
return NGLayoutResult::kNeedsEarlierBreak;
// If the child aborted layout, we cannot continue.
DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess);
EBreakBetween break_after = JoinFragmentainerBreakValues(
layout_result->FinalBreakAfter(), child.Style().BreakAfter());
container_builder_.SetPreviousBreakAfter(break_after);
}
const auto& physical_fragment = layout_result->PhysicalFragment();
NGFragment fragment(ConstraintSpace().GetWritingDirection(),
physical_fragment);
LogicalOffset logical_offset = LogicalFromBfcOffsets(
child_bfc_offset, ContainerBfcOffset(), fragment.InlineSize(),
container_builder_.InlineSize(), ConstraintSpace().Direction());
if (!PositionOrPropagateListMarker(*layout_result, &logical_offset,
previous_inflow_position))
return NGLayoutResult::kBfcBlockOffsetResolved;
if (UNLIKELY(child.Style().AlignSelfBlockCenter())) {
DCHECK(Node().IsTextField());
// The block-size of a textfield doesn't depend on its contents, so we can
// compute the block-size without passing the actual intrinsic block-size.
const LayoutUnit bsp_block_sum = BorderScrollbarPadding().BlockSum();
LayoutUnit block_size = ClampIntrinsicBlockSize(
ConstraintSpace(), Node(), BorderScrollbarPadding(), bsp_block_sum);
block_size = ComputeBlockSizeForFragment(
ConstraintSpace(), Style(), BorderPadding(), block_size,
container_builder_.InitialBorderBoxSize().inline_size,
Node().ShouldBeConsideredAsReplaced());
block_size -= bsp_block_sum;
logical_offset =
CenterBlockChild(logical_offset, block_size, fragment.BlockSize());
}
PropagateBaselineFromChild(physical_fragment, logical_offset.block_offset);
container_builder_.AddResult(*layout_result, logical_offset);
// The margins we store will be used by e.g. getComputedStyle().
// When calculating these values, ignore any floats that might have
// affected the child. This is what Edge does.
ResolveInlineMargins(child_style, Style(), ChildAvailableSize().inline_size,
fragment.InlineSize(), &child_data.margins);
To<NGBlockNode>(child).StoreMargins(ConstraintSpace(), child_data.margins);
*previous_inflow_position = ComputeInflowPosition(
*previous_inflow_position, child, child_data,
child_bfc_offset.block_offset, logical_offset, *layout_result, fragment,
/* self_collapsing_child_had_clearance */ false);
return NGLayoutResult::kSuccess;
}
scoped_refptr<const NGLayoutResult>
NGBlockLayoutAlgorithm::LayoutNewFormattingContext(
NGLayoutInputNode child,
const NGBreakToken* child_break_token,
const NGInflowChildData& child_data,
NGBfcOffset origin_offset,
bool abort_if_cleared,
NGBfcOffset* out_child_bfc_offset) {
const ComputedStyle& child_style = child.Style();
const TextDirection direction = ConstraintSpace().Direction();
const auto writing_direction = ConstraintSpace().GetWritingDirection();
// The origin offset is where we should start looking for layout
// opportunities. It needs to be adjusted by the child's clearance.
AdjustToClearance(
exclusion_space_.ClearanceOffset(child_style.Clear(Style())),
&origin_offset);
DCHECK(container_builder_.BfcBlockOffset());
LayoutOpportunityVector opportunities =
exclusion_space_.AllLayoutOpportunities(origin_offset,
ChildAvailableSize().inline_size);
// We should always have at least one opportunity.
DCHECK_GT(opportunities.size(), 0u);
// Now we lay out. This will give us a child fragment and thus its size, which
// means that we can find out if it's actually going to fit. If it doesn't
// fit where it was laid out, and is pushed downwards, we'll lay out over
// again, since a new BFC block offset could result in a new fragment size,
// e.g. when inline size is auto, or if we're block-fragmented.
for (const auto& opportunity : opportunities) {
if (abort_if_cleared &&
origin_offset.block_offset < opportunity.rect.BlockStartOffset()) {
// Abort if we got pushed downwards. We need to adjust
// origin_offset.block_offset, reposition any floats affected by that, and
// try again.
return nullptr;
}
// Find the available inline-size which should be given to the child.
LayoutUnit line_left_offset = opportunity.rect.start_offset.line_offset;
LayoutUnit line_right_offset = opportunity.rect.end_offset.line_offset;
LayoutUnit line_left_margin = child_data.margins.LineLeft(direction);
LayoutUnit line_right_margin = child_data.margins.LineRight(direction);
// When the inline dimensions of layout opportunity match the available
// inline-size, a new formatting context can expand outside of the
// opportunity if negative margins are present.
bool can_expand_outside_opportunity =
opportunity.rect.start_offset.line_offset ==
origin_offset.line_offset &&
opportunity.rect.InlineSize() == ChildAvailableSize().inline_size;
if (can_expand_outside_opportunity) {
// No floats have affected the available inline-size, adjust the
// available inline-size by the margins.
DCHECK_EQ(line_left_offset, origin_offset.line_offset);
DCHECK_EQ(line_right_offset,
origin_offset.line_offset + ChildAvailableSize().inline_size);
line_left_offset += line_left_margin;
line_right_offset -= line_right_margin;
} else {
// Margins are applied from the content-box, not the layout opportunity
// area. Instead of adjusting by the size of the margins, we "shrink" the
// available inline-size if required.
line_left_offset = std::max(
line_left_offset,
origin_offset.line_offset + line_left_margin.ClampNegativeToZero());
line_right_offset = std::min(line_right_offset,
origin_offset.line_offset +
ChildAvailableSize().inline_size -
line_right_margin.ClampNegativeToZero());
}
LayoutUnit opportunity_size =
(line_right_offset - line_left_offset).ClampNegativeToZero();
// The available inline size in the child constraint space needs to include
// inline margins, since layout algorithms (both legacy and NG) will resolve
// auto inline size by subtracting the inline margins from available inline
// size. We have calculated a layout opportunity without margins in mind,
// since they overlap with adjacent floats. Now we need to add them.
LayoutUnit child_available_inline_size =
(opportunity_size + child_data.margins.InlineSum())
.ClampNegativeToZero();
NGConstraintSpace child_space = CreateConstraintSpaceForChild(
child, child_data,
{child_available_inline_size, ChildAvailableSize().block_size},
/* is_new_fc */ true, opportunity.rect.start_offset.block_offset);
// All formatting context roots (like this child) should start with an empty
// exclusion space.
DCHECK(child_space.ExclusionSpace().IsEmpty());
scoped_refptr<const NGLayoutResult> layout_result = LayoutBlockChild(
child_space, child_break_token, early_break_, &To<NGBlockNode>(child));
// Since this child establishes a new formatting context, no exclusion space
// should be returned.
DCHECK(layout_result->ExclusionSpace().IsEmpty());
if (layout_result->Status() != NGLayoutResult::kSuccess) {
DCHECK_EQ(layout_result->Status(),
NGLayoutResult::kOutOfFragmentainerSpace);
return layout_result;
}
NGFragment fragment(writing_direction, layout_result->PhysicalFragment());
// Check if the fragment will fit in this layout opportunity, if not proceed
// to the next opportunity.
if ((fragment.InlineSize() > opportunity.rect.InlineSize() &&
!can_expand_outside_opportunity) ||
fragment.BlockSize() > opportunity.rect.BlockSize())
continue;
// Now find the fragment's (final) position calculating the auto margins.
NGBoxStrut auto_margins = child_data.margins;
if (child.IsListMarker()) {
// Deal with marker's margin. It happens only when marker needs to occupy
// the whole line.
DCHECK(child.ListMarkerOccupiesWholeLine());
// Because the marker is laid out as a normal block child, its inline
// size is extended to fill up the space. Compute the regular marker size
// from the first child.
const auto& marker_fragment = layout_result->PhysicalFragment();
LayoutUnit marker_inline_size;
if (!marker_fragment.Children().empty()) {
marker_inline_size =
NGFragment(writing_direction, *marker_fragment.Children().front())
.InlineSize();
}
auto_margins.inline_start =
NGUnpositionedListMarker(To<NGBlockNode>(child))
.InlineOffset(marker_inline_size);
auto_margins.inline_end = opportunity.rect.InlineSize() -
fragment.InlineSize() -
auto_margins.inline_start;
} else {
ResolveInlineMargins(child_style, Style(), child_available_inline_size,
fragment.InlineSize(), &auto_margins);
}
// |auto_margins| are initialized as a copy of the child's initial margins.
// To determine the effect of the auto-margins we only apply the difference.
LayoutUnit auto_margin_line_left =
auto_margins.LineLeft(direction) - line_left_margin;
*out_child_bfc_offset = {line_left_offset + auto_margin_line_left,
opportunity.rect.start_offset.block_offset};
return layout_result;
}
NOTREACHED();
return nullptr;
}
NGLayoutResult::EStatus NGBlockLayoutAlgorithm::HandleInflow(
NGLayoutInputNode child,
const NGBreakToken* child_break_token,
NGPreviousInflowPosition* previous_inflow_position,
NGInlineChildLayoutContext* inline_child_layout_context,
scoped_refptr<const NGInlineBreakToken>* previous_inline_break_token) {
DCHECK(child);
DCHECK(!child.IsFloating());
DCHECK(!child.IsOutOfFlowPositioned());
DCHECK(!child.CreatesNewFormattingContext());
bool is_non_empty_inline = false;
auto* child_inline_node = DynamicTo<NGInlineNode>(child);
if (child_inline_node) {
is_non_empty_inline = !child_inline_node->IsEmptyInline();
// Add reusable line boxes from |previous_result_| if any.
if (is_non_empty_inline && !child_break_token && previous_result_) {
if (!ResolveBfcBlockOffset(previous_inflow_position))
return NGLayoutResult::kBfcBlockOffsetResolved;
DCHECK(container_builder_.BfcBlockOffset());
DCHECK(!*previous_inline_break_token);
if (TryReuseFragmentsFromCache(*child_inline_node,
previous_inflow_position,
previous_inline_break_token))
return NGLayoutResult::kSuccess;
}
}
bool has_clearance_past_adjoining_floats =
!container_builder_.BfcBlockOffset() && child.IsBlock() &&
HasClearancePastAdjoiningFloats(container_builder_.AdjoiningObjectTypes(),
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:
// 1. If the child is to be cleared by adjoining floats.
// 2. If the child is a non-empty inline.
//
// Note this logic is copied to TryReuseFragmentsFromCache(), they need to
// keep in sync.
if (has_clearance_past_adjoining_floats || is_non_empty_inline) {
if (!ResolveBfcBlockOffset(previous_inflow_position))
return NGLayoutResult::kBfcBlockOffsetResolved;
// 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(child.Style().Clear(Style()));
}
}
// Perform layout on the child.
NGInflowChildData child_data =
ComputeChildData(*previous_inflow_position, child, child_break_token,
/* is_new_fc */ false);
NGConstraintSpace child_space = CreateConstraintSpaceForChild(
child, child_data, ChildAvailableSize(), /* is_new_fc */ false,
forced_bfc_block_offset, has_clearance_past_adjoining_floats,
previous_inflow_position->block_end_annotation_space);
scoped_refptr<const NGLayoutResult> layout_result =
LayoutInflow(child_space, child_break_token, early_break_, &child,
inline_child_layout_context);
// To save space of the stack when we recurse into |NGBlockNode::Layout|
// 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);
}
NGLayoutResult::EStatus NGBlockLayoutAlgorithm::FinishInflow(
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,
NGInlineChildLayoutContext* inline_child_layout_context,
scoped_refptr<const NGInlineBreakToken>* previous_inline_break_token) {
base::Optional<LayoutUnit> child_bfc_block_offset =
layout_result->BfcBlockOffset();
bool is_self_collapsing = layout_result->IsSelfCollapsing();
// Only non self-collapsing children (e.g. "normal children") can be pushed
// by floats in this way.
bool normal_child_had_clearance =
layout_result->IsPushedByFloats() && child.IsBlock();
DCHECK(!normal_child_had_clearance || !is_self_collapsing);
// 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
// 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).
DCHECK(child_bfc_block_offset);
abort_when_bfc_block_offset_updated_ = true;
LayoutUnit bfc_block_offset = *child_bfc_block_offset;
if (normal_child_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);
}
// A new formatting-context may have previously tried to resolve the BFC
// block-offset. In this case we'll have a "forced" BFC block-offset
// present, but we shouldn't apply it (instead preferring the child's new
// BFC block-offset).
DCHECK(!ConstraintSpace().AncestorHasClearancePastAdjoiningFloats());
if (!ResolveBfcBlockOffset(previous_inflow_position, bfc_block_offset,
/* forced_bfc_block_offset */ base::nullopt))
return NGLayoutResult::kBfcBlockOffsetResolved;
}
// We have special behaviour for a self-collapsing child which gets pushed
// down due to clearance, see comment inside |ComputeInflowPosition|.
bool self_collapsing_child_had_clearance =
is_self_collapsing && 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) {
DCHECK(is_self_collapsing);
if (child_space.HasClearanceOffset() && child.Style().HasClear()) {
// This is a self-collapsing child that we collapsed through, so we have
// to detect clearance manually. See if the child's hypothetical border
// edge is past the relevant floats. If it's not, we need to apply
// clearance before it.
LayoutUnit child_block_offset_estimate =
BfcBlockOffset() + layout_result->EndMarginStrut().Sum();
if (child_block_offset_estimate < child_space.ClearanceOffset())
self_collapsing_child_had_clearance = true;
}
}
bool child_had_clearance =
self_collapsing_child_had_clearance || normal_child_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
// do it now to separate the child's collapsed margin from this container.
if (!ResolveBfcBlockOffset(previous_inflow_position))
return NGLayoutResult::kBfcBlockOffsetResolved;
} else if (layout_result->SubtreeModifiedMarginStrut()) {
// The child doesn't have clearance, and modified its incoming
// margin-strut. Propagate this information up to our parent if needed.
SetSubtreeModifiedMarginStrutIfNeeded();
}
bool self_collapsing_child_needs_relayout = false;
if (!child_bfc_block_offset) {
// Layout wasn't able to determine the BFC block-offset of the child. This
// has to mean that the child is self-collapsing.
DCHECK(is_self_collapsing);
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 = PositionSelfCollapsingChildWithParentBfc(
child, child_space, *child_data, *layout_result);
// We may need to relayout this child if it had any (adjoining) objects
// which were positioned in the incorrect place.
if (layout_result->PhysicalFragment().HasAdjoiningObjectDescendants() &&
*child_bfc_block_offset != child_space.ExpectedBfcBlockOffset())
self_collapsing_child_needs_relayout = true;
}
} else if (!child_had_clearance && !is_self_collapsing) {
// Only non self-collapsing children are allowed resolve their parent's BFC
// block-offset. We check the BFC block-offset at the end of layout
// determine if this fragment is self-collapsing.
//
// 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 NGLayoutResult::kBfcBlockOffsetResolved;
}
// We need to re-layout a self-collapsing child if it was affected by
// clearance in order to produce a new margin strut. For example:
// <div style="margin-bottom: 50px;"></div>
// <div id="float" style="height: 50px;"></div>
// <div id="zero" style="clear: left; margin-top: -20px;">
// <div id="zero-inner" style="margin-top: 40px; margin-bottom: -30px;">
// </div>
//
// The end margin strut for #zero will be {50, -30}. #zero will be affected
// by clearance (as 50 > {50, -30}).
//
// As #zero doesn't touch the incoming margin strut now we need to perform a
// 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.
if (self_collapsing_child_had_clearance) {
NGMarginStrut margin_strut;
margin_strut.Append(child_data->margins.block_start,
child.Style().HasMarginBeforeQuirk());
// We only need to relayout if the new margin strut is different to the
// previous one.
if (child_data->margin_strut != margin_strut) {
child_data->margin_strut = margin_strut;
self_collapsing_child_needs_relayout = true;
}
}
// We need to layout a child if we know its BFC block offset and:
// - It aborted its layout as it resolved its BFC block offset.
// - It has some unpositioned floats.
// - It was affected by clearance.
if ((layout_result->Status() == NGLayoutResult::kBfcBlockOffsetResolved ||
self_collapsing_child_needs_relayout) &&
child_bfc_block_offset) {
NGConstraintSpace new_child_space = CreateConstraintSpaceForChild(
child, *child_data, ChildAvailableSize(), /* is_new_fc */ false,
child_bfc_block_offset);
layout_result =
LayoutInflow(new_child_space, child_break_token, early_break_, &child,
inline_child_layout_context);
if (layout_result->Status() == NGLayoutResult::kBfcBlockOffsetResolved) {
// Even a second layout pass may abort, if the BFC block offset initially
// calculated turned out to be wrong. This happens when we discover that
// an in-flow block-level descendant that establishes a new formatting
// context doesn't fit beside the floats at its initial position. Allow
// one more pass.
child_bfc_block_offset = layout_result->BfcBlockOffset();
DCHECK(child_bfc_block_offset);
new_child_space = CreateConstraintSpaceForChild(
child, *child_data, ChildAvailableSize(), /* is_new_fc */ false,
child_bfc_block_offset);
layout_result =
LayoutInflow(new_child_space, child_break_token, early_break_, &child,
inline_child_layout_context);
}
DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess);
}
// It is now safe to update our version of the exclusion space, and any
// propagated adjoining floats.
exclusion_space_ = layout_result->ExclusionSpace();
// Only self-collapsing children should have adjoining objects.
DCHECK(!layout_result->AdjoiningObjectTypes() || is_self_collapsing);
container_builder_.SetAdjoiningObjectTypes(
layout_result->AdjoiningObjectTypes());
// If we don't know our BFC block-offset yet, and the child stumbled into
// something that needs it (unable to position floats yet), we need abort
// layout, and trigger a re-layout once we manage to resolve it.
//
// NOTE: This check is performed after the optional second layout pass above,
// since we may have been able to resolve our BFC block-offset (e.g. due to
// clearance) and position any descendant floats in the second pass.
// In particular, when it comes to clearance of self-collapsing children, if
// we just applied it and resolved the BFC block-offset to separate the
// margins before and after clearance, we cannot abort and re-layout this
// child, or clearance would be lost.
//
// If we are a new formatting context, the child will get re-laid out once it
// has been positioned.
if (!container_builder_.BfcBlockOffset()) {
abort_when_bfc_block_offset_updated_ |=
layout_result->AdjoiningObjectTypes();
// If our BFC block offset is unknown, and the child got pushed down by
// floats, so will we.
if (layout_result->IsPushedByFloats())
container_builder_.SetIsPushedByFloats();
}
const auto& physical_fragment = layout_result->PhysicalFragment();
NGFragment fragment(ConstraintSpace().GetWritingDirection(),
physical_fragment);
LogicalOffset logical_offset = CalculateLogicalOffset(
fragment, layout_result->BfcLineOffset(), child_bfc_block_offset);
if (UNLIKELY(child.IsSliderThumb()))
logical_offset = AdjustSliderThumbInlineOffset(fragment, logical_offset);
if (ConstraintSpace().HasBlockFragmentation() &&
container_builder_.BfcBlockOffset() && child_bfc_block_offset) {
// Floats only cause container separation for the outermost block child that
// gets pushed down (the container and the child may have adjoining
// block-start margins).
bool has_container_separation =
has_processed_first_child_ || (layout_result->IsPushedByFloats() &&
!container_builder_.IsPushedByFloats());
NGBreakStatus break_status = BreakBeforeChildIfNeeded(
child, *layout_result, previous_inflow_position,
*child_bfc_block_offset, has_container_separation);
if (break_status == NGBreakStatus::kBrokeBefore)
return NGLayoutResult::kSuccess;
if (break_status == NGBreakStatus::kNeedsEarlierBreak)
return NGLayoutResult::kNeedsEarlierBreak;
EBreakBetween break_after = JoinFragmentainerBreakValues(
layout_result->FinalBreakAfter(), child.Style().BreakAfter());
container_builder_.SetPreviousBreakAfter(break_after);
if (inline_child_layout_context) {
for (auto token : inline_child_layout_context->PropagatedBreakTokens()) {
container_builder_.AddBreakToken(std::move(token),
/* is_in_parallel_flow */ true);
}
inline_child_layout_context->ClearPropagatedBreakTokens();
}
}
if (!PositionOrPropagateListMarker(*layout_result, &logical_offset,
previous_inflow_position))
return NGLayoutResult::kBfcBlockOffsetResolved;
// The box with -internal-align-self:center should create new
// formatting context.
DCHECK(child.IsInline() || !child.Style().AlignSelfBlockCenter());
PropagateBaselineFromChild(physical_fragment, logical_offset.block_offset);
container_builder_.AddResult(*layout_result, logical_offset);
if (auto* block_child = DynamicTo<NGBlockNode>(child)) {
// We haven't yet resolved margins wrt. overconstrainedness, unless that was
// also required to calculate line-left offset (due to block alignment)
// before layout. Do so now, so that we store the correct values (which is
// required by e.g. getComputedStyle()).
if (!child_data->margins_fully_resolved) {
ResolveInlineMargins(child.Style(), Style(),
ChildAvailableSize().inline_size,
fragment.InlineSize(), &child_data->margins);
child_data->margins_fully_resolved = true;
}
block_child->StoreMargins(ConstraintSpace(), child_data->margins);
}
*previous_inflow_position = ComputeInflowPosition(
*previous_inflow_position, child, *child_data, child_bfc_block_offset,
logical_offset, *layout_result, fragment,
self_collapsing_child_had_clearance);
*previous_inline_break_token =
child.IsInline() ? To<NGInlineBreakToken>(physical_fragment.BreakToken())
: nullptr;
// If a spanner was found inside the child, we need to finish up and propagate
// the spanner to the column layout algorithm, so that it can take care of it.
if (UNLIKELY(ConstraintSpace().IsInColumnBfc())) {
if (NGBlockNode spanner_node = layout_result->ColumnSpanner()) {
DCHECK(container_builder_.HasInflowChildBreakInside());
container_builder_.SetColumnSpanner(spanner_node);
}
}
// Update |lines_until_clamp_| from the LayoutResult.
if (lines_until_clamp_) {
if (const auto* line_box =
DynamicTo<NGPhysicalLineBoxFragment>(physical_fragment)) {
if (!line_box->IsEmptyLineBox())
lines_until_clamp_ = *lines_until_clamp_ - 1;
} else {
lines_until_clamp_ = layout_result->LinesUntilClamp();
}
if (lines_until_clamp_ <= 0 &&
!intrinsic_block_size_when_clamped_.has_value()) {
// If line-clamping occurred save the intrinsic block-size, as this
// becomes the final intrinsic block-size.
intrinsic_block_size_when_clamped_ =
previous_inflow_position->logical_block_offset;
}
}
return NGLayoutResult::kSuccess;
}
NGInflowChildData NGBlockLayoutAlgorithm::ComputeChildData(
const NGPreviousInflowPosition& previous_inflow_position,
NGLayoutInputNode child,
const NGBreakToken* child_break_token,
bool is_new_fc) {
DCHECK(child);
DCHECK(!child.IsFloating());
DCHECK_EQ(is_new_fc, child.CreatesNewFormattingContext());
// Calculate margins in parent's writing mode.
bool margins_fully_resolved;
NGBoxStrut margins =
CalculateMargins(child, is_new_fc, &margins_fully_resolved);
// Append the current margin strut with child's block start margin.
// Non empty border/padding, and new formatting-context use cases are handled
// inside of the child's layout
NGMarginStrut margin_strut = previous_inflow_position.margin_strut;
const auto* child_block_break_token =
DynamicTo<NGBlockBreakToken>(child_break_token);
if (UNLIKELY(child_block_break_token)) {
AdjustMarginsForFragmentation(child_block_break_token, &margins);
if (child_block_break_token->IsForcedBreak()) {
// After a forced fragmentainer break we need to reset the margin strut,
// in case it was set to discard all margins (which is the default at
// breaks). Margins after a forced break should be retained.
margin_strut = NGMarginStrut();
}
}
LayoutUnit logical_block_offset =
previous_inflow_position.logical_block_offset;
margin_strut.Append(margins.block_start,
child.Style().HasMarginBeforeQuirk());
SetSubtreeModifiedMarginStrutIfNeeded(&child.Style().MarginBefore());
NGBfcOffset child_bfc_offset = {
ConstraintSpace().BfcOffset().line_offset +
BorderScrollbarPadding().LineLeft(ConstraintSpace().Direction()) +
margins.LineLeft(ConstraintSpace().Direction()),
BfcBlockOffset() + logical_block_offset};
return {child_bfc_offset, margin_strut, margins, margins_fully_resolved,
IsResumingLayout(child_block_break_token)};
}
NGPreviousInflowPosition NGBlockLayoutAlgorithm::ComputeInflowPosition(
const NGPreviousInflowPosition& previous_inflow_position,
const NGLayoutInputNode child,
const NGInflowChildData& child_data,
const base::Optional<LayoutUnit>& child_bfc_block_offset,
const LogicalOffset& logical_offset,
const NGLayoutResult& layout_result,
const NGFragment& fragment,
bool self_collapsing_child_had_clearance) {
// Determine the child's end logical offset, for the next child to use.
LayoutUnit logical_block_offset;
bool is_self_collapsing = layout_result.IsSelfCollapsing();
if (is_self_collapsing) {
// The default behaviour for self-collapsing children is they just pass
// through the previous inflow position.
logical_block_offset = previous_inflow_position.logical_block_offset;
if (self_collapsing_child_had_clearance) {
// If there's clearance, we must have applied that by now and thus
// resolved our BFC block-offset.
DCHECK(container_builder_.BfcBlockOffset());
DCHECK(child_bfc_block_offset.has_value());
// If a self-collapsing child was affected by clearance (that is it got
// pushed down past a float), we need to do something slightly bizarre.
//
// Instead of just passing through the previous inflow position, we make
// the inflow position our new position (which was affected by the
// float), minus what the margin strut which the self-collapsing child
// produced.
//
// Another way of thinking about this is that when you *add* back the
// margin strut, you end up with the same position as you started with.
//
// This is essentially what the spec refers to as clearance [1], and,
// while we normally don't have to calculate it directly, in the case of
// a self-collapsing cleared child like here, we actually have to.
//
// We have to calculate clearance for self-collapsing cleared children,
// because we need the margin that's between the clearance and this block
// to collapse correctly with subsequent content. This is something that
// needs to take place after the margin strut preceding and following the
// clearance have been separated. Clearance may be positive, negative or
// zero, depending on what it takes to (hypothetically) place this child
// just below the last relevant float. Since the margins before and after
// the clearance have been separated, we may have to pull the child back,
// and that's an example of negative clearance.
//
// (In the other case, when a cleared child is non self-collapsing (i.e.
// when we don't end up here), we don't need to explicitly calculate
// clearance, because then we just place its border edge where it should
// be and we're done with it.)
//
// [1] https://www.w3.org/TR/CSS22/visuren.html#flow-control
// First move past the margin that is to precede the clearance. It will
// not participate in any subsequent margin collapsing.
LayoutUnit margin_before_clearance =
previous_inflow_position.margin_strut.Sum();
logical_block_offset += margin_before_clearance;
// Calculate and apply actual clearance.
LayoutUnit clearance = *child_bfc_block_offset -
layout_result.EndMarginStrut().Sum() -
NextBorderEdge(previous_inflow_position);
logical_block_offset += clearance;
}
if (!container_builder_.BfcBlockOffset())
DCHECK_EQ(logical_block_offset, LayoutUnit());
} else {
// We add AnnotationOverflow unconditionally here. Then, we cancel it if
// - The next line box has block-start annotation space, or
// - There are no following child boxes and this container has block-end
// padding.
//
// See NGInlineLayoutAlgorithm::CreateLine() and
// BlockLayoutAlgorithm::Layout().
logical_block_offset = logical_offset.block_offset + fragment.BlockSize() +
layout_result.AnnotationOverflow();
}
NGMarginStrut margin_strut = layout_result.EndMarginStrut();
// Self collapsing child's end margin can "inherit" quirkiness from its start
// margin. E.g.
// <ol style="margin-bottom: 20px"></ol>
bool is_quirky =
(is_self_collapsing && child.Style().HasMarginBeforeQuirk()) ||
child.Style().HasMarginAfterQuirk();
margin_strut.Append(child_data.margins.block_end, is_quirky);
SetSubtreeModifiedMarginStrutIfNeeded(&child.Style().MarginAfter());
if (UNLIKELY(ConstraintSpace().HasBlockFragmentation())) {
// If the child broke inside, don't apply any trailing margin, since it's
// only to be applied to the last fragment that's not in a parallel flow
// (due to overflow). While trailing margins are normally truncated at
// fragmentainer boundaries, so that whether or not we add such margins
// doesn't really make much of a difference, this isn't the case in the
// initial column balancing pass.
if (const auto* physical_fragment = DynamicTo<NGPhysicalBoxFragment>(
&layout_result.PhysicalFragment())) {
if (const auto* token =
To<NGBlockBreakToken>(physical_fragment->BreakToken())) {
// TODO(mstensho): Don't apply the margin to all overflowing fragments
// (if any). It should only be applied after the fragment where we
// reached the block-end of the node.
if (!token->IsAtBlockEnd())
margin_strut = NGMarginStrut();
}
}
}
// This flag is subtle, but in order to determine our size correctly we need
// to check if our last child is self-collapsing, and it was affected by
// clearance *or* an adjoining self-collapsing sibling was affected by
// clearance. E.g.
// <div id="container">
// <div id="float"></div>
// <div id="zero-with-clearance"></div>
// <div id="another-zero"></div>
// </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 self_or_sibling_self_collapsing_child_had_clearance =
self_collapsing_child_had_clearance ||
(previous_inflow_position.self_collapsing_child_had_clearance &&
is_self_collapsing);
LayoutUnit annotation_space = layout_result.BlockEndAnnotationSpace();
if (layout_result.AnnotationOverflow() > LayoutUnit()) {
DCHECK(!annotation_space);
annotation_space = -layout_result.AnnotationOverflow();
}
return {logical_block_offset, margin_strut, annotation_space,
self_or_sibling_self_collapsing_child_had_clearance};
}
LayoutUnit NGBlockLayoutAlgorithm::PositionSelfCollapsingChildWithParentBfc(
const NGLayoutInputNode& child,
const NGConstraintSpace& child_space,
const NGInflowChildData& child_data,
const NGLayoutResult& layout_result) const {
DCHECK(layout_result.IsSelfCollapsing());
// The child must be an in-flow zero-block-size fragment, use its end margin
// strut for positioning.
LayoutUnit child_bfc_block_offset =
child_data.bfc_offset_estimate.block_offset +
layout_result.EndMarginStrut().Sum();
ApplyClearance(child_space, &child_bfc_block_offset);
return child_bfc_block_offset;
}
void NGBlockLayoutAlgorithm::FinalizeForTableCell(
LayoutUnit unconstrained_intrinsic_block_size) {
// Hide table-cells if:
// - They are within a collapsed column(s).
// - They have "empty-cells: hide", non-collapsed borders, and no children.
container_builder_.SetIsHiddenForPaint(
ConstraintSpace().IsTableCellHiddenForPaint() ||
(ConstraintSpace().HideTableCellIfEmpty() &&
container_builder_.Children().IsEmpty()));
container_builder_.SetHasCollapsedBorders(
ConstraintSpace().IsTableCellWithCollapsedBorders());
// Everything else within this function only applies to new table-cells.
if (ConstraintSpace().IsLegacyTableCell())
return;
container_builder_.SetIsTableNGPart();
container_builder_.SetTableCellColumnIndex(
ConstraintSpace().TableCellColumnIndex());
switch (Style().VerticalAlign()) {
case EVerticalAlign::kTop:
// Do nothing 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:
// Table-cells (with baseline vertical alignment) always produce a
// baseline of their end-content edge (even if the content doesn't have
// any baselines).
if (!container_builder_.Baseline() ||
Node().ShouldApplyLayoutContainment()) {
container_builder_.SetBaseline(unconstrained_intrinsic_block_size -
BorderScrollbarPadding().block_end);
}
if (auto alignment_baseline =
ConstraintSpace().TableCellAlignmentBaseline()) {
container_builder_.MoveChildrenInBlockDirection(
*alignment_baseline - *container_builder_.Baseline());
}
break;
case EVerticalAlign::kMiddle:
container_builder_.MoveChildrenInBlockDirection(
(container_builder_.FragmentBlockSize() -
unconstrained_intrinsic_block_size) /
2);
break;
case EVerticalAlign::kBottom:
container_builder_.MoveChildrenInBlockDirection(
container_builder_.FragmentBlockSize() -
unconstrained_intrinsic_block_size);
break;
};
}
LayoutUnit NGBlockLayoutAlgorithm::FragmentainerSpaceAvailable() const {
DCHECK(container_builder_.BfcBlockOffset());
return FragmentainerSpaceAtBfcStart(ConstraintSpace()) -
*container_builder_.BfcBlockOffset();
}
void NGBlockLayoutAlgorithm::ConsumeRemainingFragmentainerSpace(
NGPreviousInflowPosition* previous_inflow_position) {
if (ConstraintSpace().HasKnownFragmentainerBlockSize()) {
// The remaining part of the fragmentainer (the unusable space for child
// content, due to the break) should still be occupied by this container.
previous_inflow_position->logical_block_offset =
FragmentainerSpaceAvailable();
}
}
bool NGBlockLayoutAlgorithm::FinalizeForFragmentation() {
if (Node().IsInlineFormattingContextRoot() && !early_break_) {
if (container_builder_.HasInflowChildBreakInside() ||
first_overflowing_line_) {
if (first_overflowing_line_ &&
first_overflowing_line_ < container_builder_.LineCount()) {
int line_number;
if (fit_all_lines_) {
line_number = first_overflowing_line_;
} else {
// We managed to finish layout of all the lines for the node, which
// means that we won't have enough widows, unless we break earlier
// than where we overflowed.
int line_count = container_builder_.LineCount();
line_number = std::max(line_count - Style().Widows(),
std::min(line_count, int(Style().Orphans())));
}
// We need to layout again, and stop at the right line number.
scoped_refptr<const NGEarlyBreak> breakpoint =
base::AdoptRef(new NGEarlyBreak(line_number));
container_builder_.SetEarlyBreak(breakpoint, kBreakAppealPerfect);
return false;
}
} else {
// Everything could fit in the current fragmentainer, but, depending on
// what comes after, the best location to break at may be between two of
// our lines.
UpdateEarlyBreakBetweenLines();
}
}
if (container_builder_.IsFragmentainerBoxType()) {
// We're building fragmentainers. Finish fragmentation on our own, since
// special-rules apply.
LayoutUnit consumed_block_size =
BreakToken() ? BreakToken()->ConsumedBlockSize() : LayoutUnit();
if (ConstraintSpace().HasKnownFragmentainerBlockSize()) {
// Just copy the block-size from the constraint space. Calculating the
// size the regular way would cause some problems with overflow. For one,
// we don't want to produce a break token if there's no child content that
// requires it. When we lay out, we use FragmentainerCapacity(), so this
// is what we need to add to consumed block-size for the next break
// token. The fragment block-size itself will be based directly on the
// fragmentainer size from the constraint space, though.
container_builder_.SetFragmentBlockSize(
ConstraintSpace().FragmentainerBlockSize());
container_builder_.SetConsumedBlockSize(
consumed_block_size + FragmentainerCapacity(ConstraintSpace()));
} else {
// When we are in the initial column balancing pass, use the block-size
// calculated by the algorithm. Since any previously consumed block-size
// is already baked in (in order to correctly honor specified block-size
// (which makes sense to everyone but fragmentainers)), we need to extract
// it again now.
LayoutUnit fragments_total_block_size =
container_builder_.FragmentsTotalBlockSize();
container_builder_.SetFragmentBlockSize(fragments_total_block_size -
consumed_block_size);
container_builder_.SetConsumedBlockSize(fragments_total_block_size);
}
return true;
}
LayoutUnit space_left = kIndefiniteSize;
if (ConstraintSpace().HasKnownFragmentainerBlockSize())
space_left = FragmentainerSpaceAvailable();
return FinishFragmentation(Node(), ConstraintSpace(),
BorderPadding().block_end, space_left,
&container_builder_);
}
NGBreakStatus NGBlockLayoutAlgorithm::BreakBeforeChildIfNeeded(
NGLayoutInputNode child,
const NGLayoutResult& layout_result,
NGPreviousInflowPosition* previous_inflow_position,
LayoutUnit bfc_block_offset,
bool has_container_separation) {
DCHECK(ConstraintSpace().HasBlockFragmentation());
// If the BFC offset is unknown, there's nowhere to break, since there's no
// non-empty child content yet (as that would have resolved the BFC offset).
DCHECK(container_builder_.BfcBlockOffset());
// If we already know where to insert the break, we already know that it's not
// going to be here, since that's something we check before entering layout of
// a child.
if (early_break_)
return NGBreakStatus::kContinue;
LayoutUnit fragmentainer_block_offset =
ConstraintSpace().FragmentainerOffsetAtBfc() + bfc_block_offset;
if (has_container_separation) {
EBreakBetween break_between =
CalculateBreakBetweenValue(child, layout_result, container_builder_);
if (IsForcedBreakValue(ConstraintSpace(), break_between)) {
BreakBeforeChild(ConstraintSpace(), child, layout_result,
fragmentainer_block_offset, kBreakAppealPerfect,
/* is_forced_break */ true, &container_builder_);
ConsumeRemainingFragmentainerSpace(previous_inflow_position);
return NGBreakStatus::kBrokeBefore;
}
}
NGBreakAppeal appeal_before =
CalculateBreakAppealBefore(ConstraintSpace(), child, layout_result,
container_builder_, has_container_separation);
// Attempt to move past the break point, and if we can do that, also assess
// the appeal of breaking there, even if we didn't.
if (MovePastBreakpoint(ConstraintSpace(), child, layout_result,
fragmentainer_block_offset, appeal_before,
&container_builder_))
return NGBreakStatus::kContinue;
// Figure out where to insert a soft break. It will either be before this
// child, or before an earlier sibling, if there's a more appealing breakpoint
// there.
// If we decided to insert a soft break, we have to know the fragmentainer
// block-size.
DCHECK(ConstraintSpace().HasKnownFragmentainerBlockSize());
if (child.IsInline()) {
if (!first_overflowing_line_) {
// We're at the first overflowing line. This is the space shortage that
// we are going to report. We do this in spite of not yet knowing
// whether breaking here would violate orphans and widows requests. This
// approach may result in a lower space shortage than what's actually
// true, which leads to more layout passes than we'd otherwise
// need. However, getting this optimal for orphans and widows would
// require an additional piece of machinery. This case should be rare
// enough (to worry about performance), so let's focus on code
// simplicity instead.
PropagateSpaceShortage(ConstraintSpace(), layout_result,
fragmentainer_block_offset, &container_builder_);
}
// Attempt to honor orphans and widows requests.
if (int line_count = container_builder_.LineCount()) {
if (!first_overflowing_line_)
first_overflowing_line_ = line_count;
bool is_first_fragment = !BreakToken();
// Figure out how many lines we need before the break. That entails to
// attempt to honor the orphans request.
int minimum_line_count = Style().Orphans();
if (!is_first_fragment) {
// If this isn't the first fragment, it means that there's a break both
// before and after this fragment. So what was seen as trailing widows
// in the previous fragment is essentially orphans for us now.
minimum_line_count =
std::max(minimum_line_count, static_cast<int>(Style().Widows()));
}
if (line_count < minimum_line_count) {
// Not enough orphans. Our only hope is if we can break before the start
// of this block to improve on the situation. That's not something we
// can determine at this point though. Permit the break, but mark it as
// undesirable.
if (appeal_before > kBreakAppealViolatingOrphansAndWidows)
appeal_before = kBreakAppealViolatingOrphansAndWidows;
} else {
// There are enough lines before the break. Try to make sure that
// there'll be enough lines after the break as well. Attempt to honor
// the widows request.
DCHECK_GE(line_count, first_overflowing_line_);
int widows_found = line_count - first_overflowing_line_ + 1;
if (widows_found < Style().Widows()) {
// Although we're out of space, we have to continue layout to figure
// out exactly where to break in order to honor the widows
// request. We'll make sure that we're going to leave at least as many
// lines as specified by the 'widows' property for the next fragment
// (if at all possible), which means that lines that could fit in the
// current fragment (that we have already laid out) may have to be
// saved for the next fragment.
return NGBreakStatus::kContinue;
}
// We have determined that there are plenty of lines for the next
// fragment, so we can just break exactly where we ran out of space,
// rather than pushing some of the line boxes over to the next fragment.
}
fit_all_lines_ = true;
}
}
if (!AttemptSoftBreak(ConstraintSpace(), child, layout_result,
fragmentainer_block_offset, appeal_before,
&container_builder_))
return NGBreakStatus::kNeedsEarlierBreak;
ConsumeRemainingFragmentainerSpace(previous_inflow_position);
return NGBreakStatus::kBrokeBefore;
}
void NGBlockLayoutAlgorithm::UpdateEarlyBreakBetweenLines() {
// We shouldn't be here if we already know where to break.
DCHECK(!early_break_);
// If something in this flow already broke, it's a little too late to look for
// breakpoints.
DCHECK(!container_builder_.HasInflowChildBreakInside());
int line_count = container_builder_.LineCount();
if (line_count < 2)
return;
// We can break between two of the lines if we have to. Calculate the best
// line number to break before, and the appeal of such a breakpoint.
int line_number =
std::max(line_count - Style().Widows(),
std::min(line_count - 1, static_cast<int>(Style().Orphans())));
NGBreakAppeal appeal = kBreakAppealPerfect;
if (line_number < Style().Orphans() ||
line_count - line_number < Style().Widows()) {
// Not enough lines in this container to satisfy the orphans and/or widows
// requirement. If we break before the last line (i.e. the last possible
// class B breakpoint), we'll fit as much as possible, and that's the best
// we can do.
line_number = line_count - 1;
appeal = kBreakAppealViolatingOrphansAndWidows;
}
if (container_builder_.BreakAppeal() <= appeal) {
scoped_refptr<const NGEarlyBreak> breakpoint =
base::AdoptRef(new NGEarlyBreak(line_number));
container_builder_.SetEarlyBreak(breakpoint, appeal);
}
}
NGBoxStrut NGBlockLayoutAlgorithm::CalculateMargins(
NGLayoutInputNode child,
bool is_new_fc,
bool* margins_fully_resolved) {
// We need to at least partially resolve margins before creating a constraint
// space for layout. Layout needs to know the line-left offset before
// starting. If the line-left offset cannot be calculated without fully
// resolving the margins (because of block alignment), we have to create a
// temporary constraint space now to figure out the inline size first. In all
// other cases we'll postpone full resolution until after child layout, when
// we actually have a child constraint space to use (and know the inline
// size).
*margins_fully_resolved = false;
DCHECK(child);
if (child.IsInline())
return {};
const ComputedStyle& child_style = child.Style();
bool needs_inline_size =
NeedsInlineSizeToResolveLineLeft(child_style, Style());
if (!needs_inline_size && !child_style.MayHaveMargin())
return {};
NGBoxStrut margins =
ComputeMarginsFor(child_style, child_percentage_size_.inline_size,
ConstraintSpace().GetWritingDirection());
// As long as the child isn't establishing a new formatting context, we need
// to know its line-left offset before layout, to be able to position child
// floats correctly. If we need to resolve auto margins or other alignment
// properties to calculate the line-left offset, we also need to calculate its
// inline size first.
if (!is_new_fc && needs_inline_size) {
NGConstraintSpaceBuilder builder(ConstraintSpace(),
child_style.GetWritingDirection(),
/* is_new_fc */ false);
builder.SetAvailableSize(ChildAvailableSize());
builder.SetPercentageResolutionSize(child_percentage_size_);
builder.SetStretchInlineSizeIfAuto(true);
NGConstraintSpace space = builder.ToConstraintSpace();
NGBoxStrut child_border_padding =
ComputeBorders(space, To<NGBlockNode>(child)) +
ComputePadding(space, child_style);
LayoutUnit child_inline_size =
ComputeInlineSizeForFragment(space, child, child_border_padding);
ResolveInlineMargins(child_style, Style(),
space.AvailableSize().inline_size, child_inline_size,
&margins);
*margins_fully_resolved = true;
}
return margins;
}
NGConstraintSpace NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild(
const NGLayoutInputNode child,
const NGInflowChildData& child_data,
const LogicalSize child_available_size,
bool is_new_fc,
const base::Optional<LayoutUnit> child_bfc_block_offset,
bool has_clearance_past_adjoining_floats,
LayoutUnit block_start_annotation_space) {
const ComputedStyle& style = Style();
const ComputedStyle& child_style = child.Style();
const auto child_writing_direction = child.IsInline()
? style.GetWritingDirection()
: child_style.GetWritingDirection();
NGConstraintSpaceBuilder builder(ConstraintSpace(), child_writing_direction,
is_new_fc);
SetOrthogonalFallbackInlineSizeIfNeeded(Style(), child, &builder);
if (IsParallelWritingMode(ConstraintSpace().GetWritingMode(),
child_writing_direction.GetWritingMode())) {
if (!child.GetLayoutBox()->AutoWidthShouldFitContent() &&
!child.IsReplaced() && !child.IsTable())
builder.SetStretchInlineSizeIfAuto(true);
}
builder.SetAvailableSize(child_available_size);
builder.SetPercentageResolutionSize(child_percentage_size_);
builder.SetReplacedPercentageResolutionSize(replaced_child_percentage_size_);
if (ConstraintSpace().IsTableCell()) {
// If we have a fixed block-size we are in the "layout" mode.
NGTableCellChildLayoutMode mode;
if (ConstraintSpace().IsFixedBlockSize())
mode = NGTableCellChildLayoutMode::kLayout;
else if (ConstraintSpace().IsRestrictedBlockSizeTableCell())
mode = NGTableCellChildLayoutMode::kMeasureRestricted;
else
mode = NGTableCellChildLayoutMode::kMeasure;
builder.SetTableCellChildLayoutMode(mode);
}
bool has_bfc_block_offset = container_builder_.BfcBlockOffset().has_value();
// Propagate the |NGConstraintSpace::ForcedBfcBlockOffset| down to our
// children.
if (!has_bfc_block_offset && ConstraintSpace().ForcedBfcBlockOffset())
builder.SetForcedBfcBlockOffset(*ConstraintSpace().ForcedBfcBlockOffset());
if (child_bfc_block_offset && !is_new_fc)
builder.SetForcedBfcBlockOffset(*child_bfc_block_offset);
if (has_bfc_block_offset && child.IsBlock()) {
// Typically we aren't allowed to look at the previous layout result within
// a layout algorithm. However this is fine (honest), as it is just a hint
// to the child algorithm for where floats should be placed. If it doesn't
// have this flag, or gets this estimate wrong, it'll relayout with the
// appropriate "forced" BFC block-offset.
if (const NGLayoutResult* previous_result =
child.GetLayoutBox()->GetCachedLayoutResult()) {
const NGConstraintSpace& prev_space =
previous_result->GetConstraintSpaceForCaching();
// To increase the hit-rate we adjust the previous "optimistic"/"forced"
// BFC block-offset by how much the child has shifted from the previous
// layout.
LayoutUnit bfc_block_delta = child_data.bfc_offset_estimate.block_offset -
prev_space.BfcOffset().block_offset;
if (prev_space.ForcedBfcBlockOffset()) {
builder.SetOptimisticBfcBlockOffset(*prev_space.ForcedBfcBlockOffset() +
bfc_block_delta);
} else if (prev_space.OptimisticBfcBlockOffset()) {
builder.SetOptimisticBfcBlockOffset(
*prev_space.OptimisticBfcBlockOffset() + bfc_block_delta);
}
}
} else if (ConstraintSpace().OptimisticBfcBlockOffset()) {
// Propagate the |NGConstraintSpace::OptimisticBfcBlockOffset| down to our
// children.
builder.SetOptimisticBfcBlockOffset(
*ConstraintSpace().OptimisticBfcBlockOffset());
}
// 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()
: ConstraintSpace().ClearanceOffset();
if (child.IsBlock()) {
LayoutUnit child_clearance_offset =
exclusion_space_.ClearanceOffset(child_style.Clear(Style()));
clearance_offset = std::max(clearance_offset, child_clearance_offset);
// |PositionListMarker()| requires a baseline.
builder.SetBaselineAlgorithmType(ConstraintSpace().BaselineAlgorithmType());
}
builder.SetClearanceOffset(clearance_offset);
if (!is_new_fc) {
builder.SetMarginStrut(child_data.margin_strut);
builder.SetBfcOffset(child_data.bfc_offset_estimate);
builder.SetExclusionSpace(exclusion_space_);
if (!has_bfc_block_offset) {
builder.SetAdjoiningObjectTypes(
container_builder_.AdjoiningObjectTypes());
}
builder.SetIsLineClampContext(is_line_clamp_context_);
builder.SetLinesUntilClamp(lines_until_clamp_);
} else if (child_data.allow_discard_start_margin) {
// If the child is being resumed after a break, margins inside the child may
// be adjoining with the fragmentainer boundary, regardless of whether the
// child establishes a new formatting context or not.
builder.SetDiscardingMarginStrut();
}
builder.SetBlockStartAnnotationSpace(block_start_annotation_space);
if (ConstraintSpace().HasBlockFragmentation()) {
LayoutUnit fragmentainer_offset_delta;
// If a block establishes a new formatting context, we must know our
// position in the formatting context, to be able to adjust the
// fragmentation line.
if (is_new_fc)
fragmentainer_offset_delta = *child_bfc_block_offset;
SetupSpaceBuilderForFragmentation(ConstraintSpace(), child,
fragmentainer_offset_delta, &builder,
is_new_fc);
builder.SetEarlyBreakAppeal(container_builder_.BreakAppeal());
}
return builder.ToConstraintSpace();
}
void NGBlockLayoutAlgorithm::PropagateBaselineFromChild(
const NGPhysicalContainerFragment& child,
LayoutUnit block_offset) {
// Check if we've already found an appropriate baseline.
if (container_builder_.Baseline() &&
ConstraintSpace().BaselineAlgorithmType() ==
NGBaselineAlgorithmType::kFirstLine)
return;
if (child.IsLineBox()) {
const auto& line_box = To<NGPhysicalLineBoxFragment>(child);
// Skip over a line-box which is empty. These don't have any baselines
// which should be added.
if (line_box.IsEmptyLineBox())
return;
FontHeight metrics = line_box.BaselineMetrics();
DCHECK(!metrics.IsEmpty());
LayoutUnit baseline =
block_offset + (Style().IsFlippedLinesWritingMode() ? metrics.descent
: metrics.ascent);
if (!container_builder_.Baseline())
container_builder_.SetBaseline(baseline);
// Set the last baseline only if required.
if (ConstraintSpace().BaselineAlgorithmType() !=
NGBaselineAlgorithmType::kFirstLine)
container_builder_.SetLastBaseline(baseline);
return;
}
// When computing the baseline for an inline-block, table's don't contribute
// to any baselines.
if (child.IsTableNG() && ConstraintSpace().BaselineAlgorithmType() !=
NGBaselineAlgorithmType::kFirstLine) {
return;
}
NGBoxFragment fragment(ConstraintSpace().GetWritingDirection(),
To<NGPhysicalBoxFragment>(child));
if (!container_builder_.Baseline()) {
if (auto baseline = fragment.FirstBaseline())
container_builder_.SetBaseline(block_offset + *baseline);
}
// Set the last baseline only if required.
if (ConstraintSpace().BaselineAlgorithmType() !=
NGBaselineAlgorithmType::kFirstLine) {
if (auto last_baseline = fragment.Baseline())
container_builder_.SetLastBaseline(block_offset + *last_baseline);
}
}
bool NGBlockLayoutAlgorithm::ResolveBfcBlockOffset(
NGPreviousInflowPosition* previous_inflow_position,
LayoutUnit bfc_block_offset,
base::Optional<LayoutUnit> forced_bfc_block_offset) {
if (container_builder_.BfcBlockOffset())
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);
if (NeedsAbortOnBfcBlockOffsetChange())
return false;
// Set the offset to our block-start border edge. We'll now end up at the
// block-start border edge. If the BFC block offset was resolved due to a
// block-start border or padding, that must be added by the caller, for
// subsequent layout to continue at the right position. Whether we need to add
// border+padding or not isn't something we should determine here, so it must
// be dealt with as part of initializing the layout algorithm.
previous_inflow_position->logical_block_offset = LayoutUnit();
// Resolving the BFC offset normally means that we have finished collapsing
// adjoining margins, so that we can reset the margin strut. One exception
// here is if we're resuming after a break, in which case we know that we can
// resolve the BFC offset to the block-start of the fragmentainer
// (block-offset 0). But keep the margin strut, since we're essentially still
// collapsing with the fragmentainer boundary, which will eat / discard all
// adjoining margins - unless this is at a forced break. DCHECK that the strut
// is empty (note that a strut that's set up to eat all margins will also be
// considered to be empty).
if (!is_resuming_)
previous_inflow_position->margin_strut = NGMarginStrut();
else
DCHECK(previous_inflow_position->margin_strut.IsEmpty());
return true;
}
bool NGBlockLayoutAlgorithm::NeedsAbortOnBfcBlockOffsetChange() const {
DCHECK(container_builder_.BfcBlockOffset());
if (!abort_when_bfc_block_offset_updated_)
return false;
// If our position differs from our (potentially optimistic) estimate, abort.
return *container_builder_.BfcBlockOffset() !=
ConstraintSpace().ExpectedBfcBlockOffset();
}
base::Optional<LayoutUnit>
NGBlockLayoutAlgorithm::CalculateQuirkyBodyMarginBlockSum(
const NGMarginStrut& end_margin_strut) {
if (!Node().IsQuirkyAndFillsViewport())
return base::nullopt;
if (!Style().LogicalHeight().IsAuto())
return base::nullopt;
if (ConstraintSpace().IsNewFormattingContext())
return base::nullopt;
DCHECK(Node().IsBody());
LayoutUnit block_end_margin =
ComputeMarginsForSelf(ConstraintSpace(), Style()).block_end;
// The |end_margin_strut| is the block-start margin if the body doesn't have
// a resolved BFC block-offset.
if (!container_builder_.BfcBlockOffset())
return end_margin_strut.Sum() + block_end_margin;
NGMarginStrut body_strut = end_margin_strut;
body_strut.Append(block_end_margin, Style().HasMarginAfterQuirk());
return *container_builder_.BfcBlockOffset() -
ConstraintSpace().BfcOffset().block_offset + body_strut.Sum();
}
bool NGBlockLayoutAlgorithm::PositionOrPropagateListMarker(
const NGLayoutResult& layout_result,
LogicalOffset* content_offset,
NGPreviousInflowPosition* previous_inflow_position) {
// If this is not a list-item, propagate unpositioned list markers to
// ancestors.
if (!node_.IsListItem()) {
if (layout_result.UnpositionedListMarker()) {
DCHECK(!container_builder_.UnpositionedListMarker());
container_builder_.SetUnpositionedListMarker(
layout_result.UnpositionedListMarker());
}
return true;
}
// If this is a list item, add the unpositioned list marker as a child.
NGUnpositionedListMarker list_marker = layout_result.UnpositionedListMarker();
if (!list_marker) {
list_marker = container_builder_.UnpositionedListMarker();
if (!list_marker)
return true;
container_builder_.SetUnpositionedListMarker(NGUnpositionedListMarker());
}
const NGConstraintSpace& space = ConstraintSpace();
const NGPhysicalFragment& content = layout_result.PhysicalFragment();
FontBaseline baseline_type = Style().GetFontBaseline();
if (auto content_baseline =
list_marker.ContentAlignmentBaseline(space, baseline_type, content)) {
// TODO: We are reusing the ConstraintSpace for LI here. It works well for
// now because authors cannot style list-markers currently. If we want to
// support `::marker` pseudo, we need to create ConstraintSpace for marker
// separately.
scoped_refptr<const NGLayoutResult> marker_layout_result =
list_marker.Layout(space, container_builder_.Style(), baseline_type);
DCHECK(marker_layout_result);
// If the BFC block-offset of li is still not resolved, resolved it now.
if (!container_builder_.BfcBlockOffset() &&
marker_layout_result->BfcBlockOffset()) {
// TODO: Currently the margin-top of marker is always zero. To support
// `::marker` pseudo, we should count marker's margin-top in.
#if DCHECK_IS_ON()
list_marker.CheckMargin();
#endif
if (!ResolveBfcBlockOffset(previous_inflow_position))
return false;
}
list_marker.AddToBox(space, baseline_type, content,
BorderScrollbarPadding(), *marker_layout_result,
*content_baseline, content_offset,
&container_builder_);
return true;
}
// If the list marker could not be positioned against this child because it
// does not have the baseline to align to, keep it as unpositioned and try
// the next child.
container_builder_.SetUnpositionedListMarker(list_marker);
return true;
}
bool NGBlockLayoutAlgorithm::PositionListMarkerWithoutLineBoxes(
NGPreviousInflowPosition* previous_inflow_position) {
DCHECK(node_.IsListItem());
DCHECK(container_builder_.UnpositionedListMarker());
NGUnpositionedListMarker list_marker =
container_builder_.UnpositionedListMarker();
const NGConstraintSpace& space = ConstraintSpace();
FontBaseline baseline_type = Style().GetFontBaseline();
// Layout the list marker.
scoped_refptr<const NGLayoutResult> marker_layout_result =
list_marker.Layout(space, container_builder_.Style(), baseline_type);
DCHECK(marker_layout_result);
// If the BFC block-offset of li is still not resolved, resolve it now.
if (!container_builder_.BfcBlockOffset() &&
marker_layout_result->BfcBlockOffset()) {
// TODO: Currently the margin-top of marker is always zero. To support
// `::marker` pseudo, we should count marker's margin-top in.
#if DCHECK_IS_ON()
list_marker.CheckMargin();
#endif
if (!ResolveBfcBlockOffset(previous_inflow_position))
return false;
}
// Position the list marker without aligning to line boxes.
LayoutUnit marker_block_size = list_marker.AddToBoxWithoutLineBoxes(
space, baseline_type, *marker_layout_result, &container_builder_);
container_builder_.SetUnpositionedListMarker(NGUnpositionedListMarker());
// Whether the list marker should affect the block size or not is not
// well-defined, but 3 out of 4 impls do.
// https://github.com/w3c/csswg-drafts/issues/2418
//
// The BFC block-offset has been resolved after layout marker. We'll always
// include the marker into the block-size.
if (container_builder_.BfcBlockOffset()) {
intrinsic_block_size_ = std::max(marker_block_size, intrinsic_block_size_);
container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_);
container_builder_.SetFragmentsTotalBlockSize(
std::max(marker_block_size, container_builder_.Size().block_size));
}
return true;
}
bool NGBlockLayoutAlgorithm::IsRubyText(const NGLayoutInputNode& child) const {
return Node().IsRubyRun() && child.IsRubyText();
}
void NGBlockLayoutAlgorithm::HandleRubyText(NGBlockNode ruby_text_child) {
DCHECK(Node().IsRubyRun());
scoped_refptr<const NGBlockBreakToken> break_token;
if (const auto* token = BreakToken()) {
for (const auto* child_token : token->ChildBreakTokens()) {
if (child_token->InputNode() == ruby_text_child) {
break_token = To<NGBlockBreakToken>(child_token);
break;
}
}
}
const ComputedStyle& rt_style = ruby_text_child.Style();
NGConstraintSpaceBuilder builder(ConstraintSpace(),
rt_style.GetWritingDirection(), true);
SetOrthogonalFallbackInlineSizeIfNeeded(Style(), ruby_text_child, &builder);
builder.SetAvailableSize(ChildAvailableSize());
if (IsParallelWritingMode(ConstraintSpace().GetWritingMode(),
rt_style.GetWritingMode()))
builder.SetStretchInlineSizeIfAuto(true);
scoped_refptr<const NGLayoutResult> result =
ruby_text_child.Layout(builder.ToConstraintSpace(), break_token.get());
LayoutUnit ruby_text_box_top;
const NGPhysicalBoxFragment& ruby_text_fragment =
To<NGPhysicalBoxFragment>(result->PhysicalFragment());
const LogicalRect ruby_text_box = ruby_text_fragment.ConvertChildToLogical(
ruby_text_fragment.ScrollableOverflow(NGPhysicalFragment::kEmHeight));
RubyPosition block_start_position = Style().IsFlippedLinesWritingMode()
? RubyPosition::kAfter
: RubyPosition::kBefore;
if (Style().GetRubyPosition() == block_start_position) {
LayoutUnit last_line_ruby_text_bottom = ruby_text_box.BlockEndOffset();
// Find a fragment for RubyBase, and get the top of text in it.
LayoutUnit first_line_top;
for (const auto& child : container_builder_.Children()) {
if (const auto* layout_object = child.fragment->GetLayoutObject()) {
if (layout_object->IsRubyBase()) {
const auto& ruby_base_fragment =
To<NGPhysicalBoxFragment>(*child.fragment);
first_line_top =
ruby_base_fragment
.ConvertChildToLogical(ruby_base_fragment.ScrollableOverflow(
NGPhysicalFragment::kEmHeight))
.offset.block_offset;
first_line_top += child.offset.block_offset;
break;
}
}
}
ruby_text_box_top = first_line_top - last_line_ruby_text_bottom;
const LayoutUnit ruby_text_top =
ruby_text_box_top + ruby_text_box.offset.block_offset;
if (ruby_text_top < LayoutUnit())
container_builder_.SetAnnotationOverflow(ruby_text_top);
} else {
LayoutUnit first_line_ruby_text_top = ruby_text_box.offset.block_offset;
// Find a fragment for RubyBase, and get the bottom of text in it.
LayoutUnit last_line_bottom;
LayoutUnit base_logical_bottom;
for (const auto& child : container_builder_.Children()) {
if (const auto* layout_object = child.fragment->GetLayoutObject()) {
if (layout_object->IsRubyBase()) {
LayoutUnit base_block_size =
child.fragment->Size()
.ConvertToLogical(Style().GetWritingMode())
.block_size;
const auto& ruby_base_fragment =
To<NGPhysicalBoxFragment>(*child.fragment);
last_line_bottom =
ruby_base_fragment
.ConvertChildToLogical(ruby_base_fragment.ScrollableOverflow(
NGPhysicalFragment::kEmHeight))
.BlockEndOffset();
last_line_bottom += child.offset.block_offset;
base_logical_bottom = child.offset.block_offset + base_block_size;
break;
}
}
}
ruby_text_box_top = last_line_bottom - first_line_ruby_text_top;
LayoutUnit logical_bottom_overflow = ruby_text_box_top +
ruby_text_box.BlockEndOffset() -
base_logical_bottom;
if (logical_bottom_overflow > LayoutUnit())
container_builder_.SetAnnotationOverflow(logical_bottom_overflow);
}
container_builder_.AddResult(*result,
LogicalOffset(LayoutUnit(), ruby_text_box_top));
// RubyText provides baseline if RubyBase didn't.
// This behavior doesn't make much sense, but it's compatible with the legacy
// layout.
if (!container_builder_.Baseline())
PropagateBaselineFromChild(ruby_text_fragment, ruby_text_box_top);
}
void NGBlockLayoutAlgorithm::HandleTextControlPlaceholder(
NGBlockNode placeholder,
const NGPreviousInflowPosition& previous_inflow_position) {
DCHECK(Node().IsTextControl()) << Node().GetLayoutBox();
LogicalSize available_size = ChildAvailableSize();
// The placeholder should have the width same as "editing-view-port" element,
// which is the first grandchild of the text control.
if (container_builder_.Children().size() > 0) {
const NGPhysicalFragment& child =
*container_builder_.Children()[0].fragment;
if (child.IsTextControlContainer()) {
const auto& grand_children =
To<NGPhysicalContainerFragment>(child).PostLayoutChildren();
const auto begin = grand_children.begin();
if (begin != grand_children.end()) {
NGFragment grand_child_fragment(ConstraintSpace().GetWritingDirection(),
*begin->fragment);
available_size.inline_size = grand_child_fragment.InlineSize();
}
}
}
const bool is_new_fc = placeholder.CreatesNewFormattingContext();
const NGConstraintSpace space = CreateConstraintSpaceForChild(
placeholder,
ComputeChildData(previous_inflow_position, placeholder,
/* child_break_token */ nullptr, is_new_fc),
available_size, is_new_fc);
scoped_refptr<const NGLayoutResult> result = placeholder.Layout(space);
LogicalOffset offset = BorderScrollbarPadding().StartOffset();
if (Node().IsTextArea()) {
container_builder_.AddResult(*result, offset);
return;
}
// Another child should provide the baseline.
DCHECK(container_builder_.Baseline());
NGBoxFragment fragment(ConstraintSpace().GetWritingDirection(),
To<NGPhysicalBoxFragment>(result->PhysicalFragment()));
// We should apply FirstBaseline() of the placeholder fragment because the
// placeholder might have the 'overflow' property, and its LastBaseline()
// might be the block-end margin.
// |fragment| has no FirstBaseline() if it consists of only white-spaces.
if (fragment.FirstBaseline().has_value()) {
offset.block_offset =
*container_builder_.Baseline() - *fragment.FirstBaseline();
}
container_builder_.AddResult(*result, offset);
// This function doesn't update previous_inflow_position. Other children in
// this container should ignore |placeholder|.
}
LogicalOffset NGBlockLayoutAlgorithm::AdjustSliderThumbInlineOffset(
const NGFragment& fragment,
const LogicalOffset& logical_offset) {
// See LayoutSliderTrack::UpdateLayout().
const LayoutUnit available_extent =
ChildAvailableSize().inline_size - fragment.InlineSize();
const auto* input =
To<HTMLInputElement>(Node().GetDOMNode()->OwnerShadowHost());
LayoutUnit offset(input->RatioValue().ToDouble() * available_extent);
return {logical_offset.inline_offset + offset, logical_offset.block_offset};
}
} // namespace blink