blob: 537498c95df9461ad4b4ac593141f190b87113c8 [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_node.h"
#include <memory>
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/html_marquee_element.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/core/layout/box_layout_extra_input.h"
#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h"
#include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/layout_fieldset.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_set.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h"
#include "third_party/blink/renderer/core/layout/layout_table.h"
#include "third_party/blink/renderer/core/layout/layout_table_cell.h"
#include "third_party/blink/renderer/core/layout/layout_video.h"
#include "third_party/blink/renderer/core/layout/min_max_sizes.h"
#include "third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h"
#include "third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h"
#include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.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/legacy_layout_tree_walking.h"
#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_operator_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_padded_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_radical_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_scripts_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_under_over_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.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_column_layout_algorithm.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_fieldset_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_replaced_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
#include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_cell.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h"
#include "third_party/blink/renderer/core/layout/text_autosizer.h"
#include "third_party/blink/renderer/core/mathml/mathml_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_fraction_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_padded_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_radical_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_scripts_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_space_element.h"
#include "third_party/blink/renderer/core/mathml/mathml_under_over_element.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/text/writing_mode.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
namespace {
inline bool HasInlineChildren(LayoutBlockFlow* block_flow) {
auto* child = GetLayoutObjectForFirstChildNode(block_flow);
return child && AreNGBlockFlowChildrenInline(block_flow);
}
inline LayoutMultiColumnFlowThread* GetFlowThread(
const LayoutBlockFlow* block_flow) {
if (!block_flow)
return nullptr;
return block_flow->MultiColumnFlowThread();
}
inline LayoutMultiColumnFlowThread* GetFlowThread(const LayoutBox& box) {
return GetFlowThread(DynamicTo<LayoutBlockFlow>(box));
}
inline wtf_size_t FragmentIndex(const NGBlockBreakToken* incoming_break_token) {
if (incoming_break_token && !incoming_break_token->IsBreakBefore())
return incoming_break_token->SequenceNumber() + 1;
return 0;
}
// The entire purpose of this function is to avoid allocating space on the stack
// for all layout algorithms for each node we lay out. Therefore it must not be
// inline.
template <typename Algorithm, typename Callback>
NOINLINE void CreateAlgorithmAndRun(const NGLayoutAlgorithmParams& params,
const Callback& callback) {
Algorithm algorithm(params);
callback(&algorithm);
}
template <typename Callback>
NOINLINE void DetermineMathMLAlgorithmAndRun(
const LayoutBox& box,
const NGLayoutAlgorithmParams& params,
const Callback& callback) {
DCHECK(box.IsMathML());
// Currently math layout algorithms can only apply to MathML elements.
auto* element = box.GetNode();
if (element) {
if (IsA<MathMLSpaceElement>(element)) {
CreateAlgorithmAndRun<NGMathSpaceLayoutAlgorithm>(params, callback);
return;
} else if (IsA<MathMLFractionElement>(element) &&
IsValidMathMLFraction(params.node)) {
CreateAlgorithmAndRun<NGMathFractionLayoutAlgorithm>(params, callback);
return;
} else if (IsA<MathMLRadicalElement>(element) &&
IsValidMathMLRadical(params.node)) {
CreateAlgorithmAndRun<NGMathRadicalLayoutAlgorithm>(params, callback);
return;
} else if (IsA<MathMLPaddedElement>(element)) {
CreateAlgorithmAndRun<NGMathPaddedLayoutAlgorithm>(params, callback);
return;
} else if (IsA<MathMLElement>(element) &&
To<MathMLElement>(*element).IsTokenElement()) {
if (IsOperatorWithSpecialShaping(params.node))
CreateAlgorithmAndRun<NGMathOperatorLayoutAlgorithm>(params, callback);
else
CreateAlgorithmAndRun<NGBlockLayoutAlgorithm>(params, callback);
return;
} else if (IsA<MathMLScriptsElement>(element) &&
IsValidMathMLScript(params.node)) {
if (IsA<MathMLUnderOverElement>(element) &&
!IsUnderOverLaidOutAsSubSup(params.node)) {
CreateAlgorithmAndRun<NGMathUnderOverLayoutAlgorithm>(params, callback);
} else {
CreateAlgorithmAndRun<NGMathScriptsLayoutAlgorithm>(params, callback);
}
return;
}
}
CreateAlgorithmAndRun<NGMathRowLayoutAlgorithm>(params, callback);
}
template <typename Callback>
NOINLINE void DetermineAlgorithmAndRun(const NGLayoutAlgorithmParams& params,
const Callback& callback) {
const ComputedStyle& style = params.node.Style();
const LayoutBox& box = *params.node.GetLayoutBox();
if (box.IsLayoutNGFlexibleBox()) {
CreateAlgorithmAndRun<NGFlexLayoutAlgorithm>(params, callback);
} else if (box.IsTable()) {
CreateAlgorithmAndRun<NGTableLayoutAlgorithm>(params, callback);
} else if (box.IsTableRow()) {
CreateAlgorithmAndRun<NGTableRowLayoutAlgorithm>(params, callback);
} else if (box.IsTableSection()) {
CreateAlgorithmAndRun<NGTableSectionLayoutAlgorithm>(params, callback);
} else if (box.IsLayoutNGCustom()) {
CreateAlgorithmAndRun<NGCustomLayoutAlgorithm>(params, callback);
} else if (box.IsMathML()) {
DetermineMathMLAlgorithmAndRun(box, params, callback);
} else if (box.IsLayoutNGGrid() &&
RuntimeEnabledFeatures::LayoutNGGridEnabled()) {
CreateAlgorithmAndRun<NGGridLayoutAlgorithm>(params, callback);
} else if (box.IsLayoutImage()) {
DCHECK(RuntimeEnabledFeatures::LayoutNGReplacedEnabled());
CreateAlgorithmAndRun<NGReplacedLayoutAlgorithm>(params, callback);
} else if (box.IsLayoutNGFieldset()) {
CreateAlgorithmAndRun<NGFieldsetLayoutAlgorithm>(params, callback);
// If there's a legacy layout box, we can only do block fragmentation if
// we would have done block fragmentation with the legacy engine.
// Otherwise writing data back into the legacy tree will fail. Look for
// the flow thread.
} else if (GetFlowThread(box)) {
if (style.SpecifiesColumns())
CreateAlgorithmAndRun<NGColumnLayoutAlgorithm>(params, callback);
else
CreateAlgorithmAndRun<NGPageLayoutAlgorithm>(params, callback);
} else {
CreateAlgorithmAndRun<NGBlockLayoutAlgorithm>(params, callback);
}
}
inline scoped_refptr<const NGLayoutResult> LayoutWithAlgorithm(
const NGLayoutAlgorithmParams& params) {
scoped_refptr<const NGLayoutResult> result;
DetermineAlgorithmAndRun(params,
[&result](NGLayoutAlgorithmOperations* algorithm) {
result = algorithm->Layout();
});
return result;
}
inline MinMaxSizesResult ComputeMinMaxSizesWithAlgorithm(
const NGLayoutAlgorithmParams& params,
const MinMaxSizesInput& input) {
MinMaxSizesResult result;
DetermineAlgorithmAndRun(
params, [&result, &input](NGLayoutAlgorithmOperations* algorithm) {
result = algorithm->ComputeMinMaxSizes(input);
});
return result;
}
NGConstraintSpace CreateConstraintSpaceForMinMax(
const NGBlockNode& node,
const MinMaxSizesInput& input) {
NGConstraintSpaceBuilder builder(node.Style().GetWritingMode(),
node.Style().GetWritingDirection(),
node.CreatesNewFormattingContext());
builder.SetAvailableSize({kIndefiniteSize, kIndefiniteSize});
builder.SetPercentageResolutionSize(
{kIndefiniteSize, input.percentage_resolution_block_size});
return builder.ToConstraintSpace();
}
LayoutUnit CalculateAvailableInlineSizeForLegacy(
const LayoutBox& box,
const NGConstraintSpace& space) {
if (box.ShouldComputeSizeAsReplaced()) {
return space.ReplacedPercentageResolutionInlineSize()
.ClampIndefiniteToZero();
}
return space.PercentageResolutionInlineSize().ClampIndefiniteToZero();
}
LayoutUnit CalculateAvailableBlockSizeForLegacy(
const LayoutBox& box,
const NGConstraintSpace& space) {
if (box.ShouldComputeSizeAsReplaced())
return space.ReplacedPercentageResolutionBlockSize();
return space.PercentageResolutionBlockSize();
}
void SetupBoxLayoutExtraInput(const NGConstraintSpace& space,
const LayoutBox& box,
BoxLayoutExtraInput* input) {
input->containing_block_content_inline_size =
CalculateAvailableInlineSizeForLegacy(box, space);
input->containing_block_content_block_size =
CalculateAvailableBlockSizeForLegacy(box, space);
WritingMode writing_mode = box.StyleRef().GetWritingMode();
if (LayoutObject* containing_block = box.ContainingBlock()) {
if (!IsParallelWritingMode(containing_block->StyleRef().GetWritingMode(),
writing_mode)) {
// The sizes should be in the containing block writing mode.
std::swap(input->containing_block_content_block_size,
input->containing_block_content_inline_size);
// We cannot lay out without a definite containing block inline-size. We
// end up here if we're performing a measure pass (as part of resolving
// the intrinsic min/max inline-size of some ancestor, for instance).
// Legacy layout has a tendency of clamping negative sizes to 0 anyway,
// but this is missing when it comes to resolving percentage-based
// padding, for instance.
if (input->containing_block_content_inline_size == kIndefiniteSize)
input->containing_block_content_inline_size = LayoutUnit();
}
}
// We need a definite containing block inline-size, or we'd be unable to
// resolve percentages.
DCHECK_GE(input->containing_block_content_inline_size, LayoutUnit());
input->available_inline_size = space.AvailableSize().inline_size;
if (space.IsFixedInlineSize())
input->override_inline_size = space.AvailableSize().inline_size;
if (space.IsFixedBlockSize()) {
input->override_block_size = space.AvailableSize().block_size;
input->is_override_block_size_definite =
!space.IsFixedBlockSizeIndefinite();
}
input->stretch_inline_size_if_auto = space.StretchInlineSizeIfAuto();
input->stretch_block_size_if_auto =
space.StretchBlockSizeIfAuto() &&
space.AvailableSize().block_size != kIndefiniteSize;
}
bool CanUseCachedIntrinsicInlineSizes(const MinMaxSizesInput& input,
const NGConstraintSpace& constraint_space,
const NGBlockNode& node) {
// Obviously can't use the cache if our intrinsic logical widths are dirty.
if (node.GetLayoutBox()->IntrinsicLogicalWidthsDirty())
return false;
// We don't store the float inline sizes for comparison, always skip the
// cache in this case.
if (input.float_left_inline_size || input.float_right_inline_size)
return false;
// Check if we have any percentage inline padding.
const auto& style = node.Style();
if (style.MayHavePadding() && (style.PaddingStart().IsPercentOrCalc() ||
style.PaddingEnd().IsPercentOrCalc()))
return false;
if (!style.AspectRatio().IsAuto() &&
(style.LogicalMinHeight().IsPercentOrCalc() ||
style.LogicalMaxHeight().IsPercentOrCalc()) &&
input.percentage_resolution_block_size !=
node.GetLayoutBox()
->IntrinsicLogicalWidthsPercentageResolutionBlockSize())
return false;
if (node.IsNGTableCell() && To<LayoutNGTableCell>(node.GetLayoutBox())
->IntrinsicLogicalWidthsBorderSizes() !=
constraint_space.TableCellBorders())
return false;
// We may have something like:
// "grid-template-columns: repeat(auto-fill, 50px); min-width: 50%;"
// In this specific case our min/max sizes are now dependent on what
// "min-width" resolves to - which is unique to grid.
if (node.IsGrid() && (style.LogicalMinWidth().IsPercentOrCalc() ||
style.LogicalMaxWidth().IsPercentOrCalc()))
return false;
return true;
}
bool IsContentMinimumInlineSizeZero(const NGBlockNode& block_node) {
const auto* node = block_node.GetDOMNode();
const auto* marquee_element = DynamicTo<HTMLMarqueeElement>(node);
if (marquee_element && marquee_element->IsHorizontal())
return true;
if (!block_node.Style().LogicalWidth().IsPercentOrCalc())
return false;
if (block_node.IsTextControl())
return true;
if (IsA<HTMLSelectElement>(node))
return true;
if (const auto* input_element = DynamicTo<HTMLInputElement>(node)) {
const AtomicString& type = input_element->type();
if (type == input_type_names::kFile)
return true;
if (type == input_type_names::kRange)
return true;
}
return false;
}
// Convert a physical offset for an NG fragment to a physical legacy
// LayoutPoint, to be used in LayoutBox. There are special considerations for
// vertical-rl writing-mode, and also for block fragmentation (the block-offset
// should include consumed space in previous fragments).
inline LayoutPoint ToLayoutPoint(
const NGPhysicalBoxFragment& child_fragment,
PhysicalOffset offset,
const NGPhysicalBoxFragment& container_fragment,
const NGBlockBreakToken* previous_container_break_token) {
if (UNLIKELY(container_fragment.Style().IsFlippedBlocksWritingMode())) {
// Move the physical offset to the right side of the child fragment,
// relative to the right edge of the container fragment. This is the
// block-start offset in vertical-rl, and the legacy engine expects always
// expects the block offset to be relative to block-start.
offset.left = container_fragment.Size().width - offset.left -
child_fragment.Size().width;
}
if (UNLIKELY(previous_container_break_token)) {
// Add the amount of block-size previously (in previous fragmentainers)
// consumed by the container fragment. This will map the child's offset
// nicely into the flow thread coordinate system used by the legacy engine.
LayoutUnit consumed = previous_container_break_token->ConsumedBlockSize();
if (container_fragment.Style().IsHorizontalWritingMode())
offset.top += consumed;
else
offset.left += consumed;
}
return offset.ToLayoutPoint();
}
} // namespace
scoped_refptr<const NGLayoutResult> NGBlockNode::Layout(
const NGConstraintSpace& constraint_space,
const NGBlockBreakToken* break_token,
const NGEarlyBreak* early_break) const {
// Use the old layout code and synthesize a fragment.
if (!CanUseNewLayout())
return RunLegacyLayout(constraint_space);
// The exclusion space internally is a pointer to a shared vector, and
// equality of exclusion spaces is performed using pointer comparison on this
// internal shared vector.
// In order for the caching logic to work correctly we need to set the
// pointer to the value previous shared vector.
if (const NGLayoutResult* previous_result = box_->GetCachedLayoutResult()) {
constraint_space.ExclusionSpace().PreInitialize(
previous_result->GetConstraintSpaceForCaching().ExclusionSpace());
}
NGLayoutCacheStatus cache_status;
base::Optional<NGFragmentGeometry> fragment_geometry;
scoped_refptr<const NGLayoutResult> layout_result =
box_->CachedLayoutResult(constraint_space, break_token, early_break,
&fragment_geometry, &cache_status);
if (cache_status == NGLayoutCacheStatus::kHit) {
DCHECK(layout_result);
// We may have to update the margins on box_; we reuse the layout result
// even if a percentage margin may have changed.
if (UNLIKELY(Style().MayHaveMargin() && !constraint_space.IsTableCell()))
box_->SetMargin(ComputePhysicalMargins(constraint_space, Style()));
UpdateShapeOutsideInfoIfNeeded(
*layout_result, constraint_space.PercentageResolutionInlineSize());
// Even if we can reuse the result, we may still need to recalculate our
// overflow. TODO(crbug.com/919415): Explain why.
if (box_->NeedsLayoutOverflowRecalc()) {
if (RuntimeEnabledFeatures::LayoutNGLayoutOverflowEnabled())
box_->SetLayoutOverflowFromLayoutResults();
else
box_->RecalcLayoutOverflow();
}
// Return the cached result unless we're marked for layout. We may have
// added or removed scrollbars during overflow recalculation, which may have
// marked us for layout. In that case the cached result is unusable, and we
// need to re-lay out now.
if (!box_->NeedsLayout())
return layout_result;
}
if (!fragment_geometry) {
fragment_geometry =
CalculateInitialFragmentGeometry(constraint_space, *this);
}
if (RuntimeEnabledFeatures::CSSContainerQueriesEnabled() &&
IsContainerForContainerQueries()) {
if (auto* element = DynamicTo<Element>(GetDOMNode())) {
LogicalSize available_size = CalculateChildAvailableSize(
constraint_space, *this, fragment_geometry->border_box_size,
fragment_geometry->border + fragment_geometry->padding);
LogicalAxes contained_axes = ContainedAxes();
GetDocument().GetStyleEngine().UpdateStyleAndLayoutTreeForContainer(
*element, available_size, contained_axes);
}
}
TextAutosizer::NGLayoutScope text_autosizer_layout_scope(
box_, fragment_geometry->border_box_size.inline_size);
PrepareForLayout();
NGLayoutAlgorithmParams params(*this, *fragment_geometry, constraint_space,
break_token, early_break);
auto* block_flow = DynamicTo<LayoutBlockFlow>(box_);
// Try to perform "simplified" layout.
if (cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout &&
!GetFlowThread(block_flow)) {
DCHECK(layout_result);
#if DCHECK_IS_ON()
scoped_refptr<const NGLayoutResult> previous_result = layout_result;
#endif
// A child may have changed size while performing "simplified" layout (it
// may have gained or removed scrollbars, changing its size). In these
// cases "simplified" layout will return a null layout-result, indicating
// we need to perform a full layout.
layout_result = RunSimplifiedLayout(params, *layout_result);
#if DCHECK_IS_ON()
if (layout_result) {
layout_result->CheckSameForSimplifiedLayout(
*previous_result, /* check_same_block_size */ !block_flow);
}
#endif
} else if (cache_status == NGLayoutCacheStatus::kCanReuseLines) {
params.previous_result = layout_result.get();
layout_result = nullptr;
} else {
layout_result = nullptr;
}
// Fragment geometry scrollbars are potentially size constrained, and cannot
// be used for comparison with their after layout size.
NGBoxStrut before_layout_scrollbars =
ComputeScrollbars(constraint_space, *this);
bool before_layout_intrinsic_logical_widths_dirty =
box_->IntrinsicLogicalWidthsDirty();
if (!layout_result)
layout_result = LayoutWithAlgorithm(params);
FinishLayout(block_flow, constraint_space, break_token, layout_result);
// We may need to relayout if:
// - Our scrollbars have changed causing our size to change (shrink-to-fit)
// or the available space to our children changing.
// - A child changed scrollbars causing our size to change (shrink-to-fit).
//
// This mirrors legacy code in PaintLayerScrollableArea::UpdateAfterLayout.
if ((before_layout_scrollbars !=
ComputeScrollbars(constraint_space, *this)) ||
(!before_layout_intrinsic_logical_widths_dirty &&
box_->IntrinsicLogicalWidthsDirty())) {
PaintLayerScrollableArea::FreezeScrollbarsScope freeze_scrollbars;
// We need to clear any previous results when scrollbars change. For
// example - we may have stored a "measure" layout result which will be
// incorrect if we try and reuse it.
params.previous_result = nullptr;
box_->ClearLayoutResults();
#if DCHECK_IS_ON()
// Ensure turning on/off scrollbars only once at most, when we call
// |LayoutWithAlgorithm| recursively.
DEFINE_STATIC_LOCAL(HashSet<LayoutBox*>, scrollbar_changed, ());
DCHECK(scrollbar_changed.insert(box_).is_new_entry);
#endif
// Scrollbar changes are hard to detect. Make sure everyone gets the
// message.
box_->SetNeedsLayout(layout_invalidation_reason::kScrollbarChanged,
kMarkOnlyThis);
fragment_geometry =
CalculateInitialFragmentGeometry(constraint_space, *this);
layout_result = LayoutWithAlgorithm(params);
FinishLayout(block_flow, constraint_space, break_token, layout_result);
#if DCHECK_IS_ON()
scrollbar_changed.erase(box_);
#endif
}
// We always need to update the ShapeOutsideInfo even if the layout is
// intermediate (e.g. called during a min/max pass).
//
// If a shape-outside float is present in an orthogonal flow, when
// calculating the min/max-size (by performing an intermediate layout), we
// might calculate this incorrectly, as the layout won't take into account the
// shape-outside area.
//
// TODO(ikilpatrick): This should be fixed by moving the shape-outside data
// to the NGLayoutResult, removing this "side" data-structure.
UpdateShapeOutsideInfoIfNeeded(
*layout_result, constraint_space.PercentageResolutionInlineSize());
return layout_result;
}
scoped_refptr<const NGLayoutResult> NGBlockNode::SimplifiedLayout(
const NGPhysicalFragment& previous_fragment) const {
scoped_refptr<const NGLayoutResult> previous_result =
box_->GetCachedLayoutResult();
DCHECK(previous_result);
// We might be be trying to perform simplfied layout on a fragment in the
// "measure" cache slot, abort if this is the case.
if (&previous_result->PhysicalFragment() != &previous_fragment)
return nullptr;
if (!box_->NeedsLayout())
return previous_result;
DCHECK(box_->NeedsSimplifiedLayoutOnly() ||
box_->ChildLayoutBlockedByDisplayLock());
// Perform layout on ourselves using the previous constraint space.
const NGConstraintSpace space(
previous_result->GetConstraintSpaceForCaching());
scoped_refptr<const NGLayoutResult> result =
Layout(space, /* break_token */ nullptr);
const auto& old_fragment =
To<NGPhysicalBoxFragment>(previous_result->PhysicalFragment());
const auto& new_fragment =
To<NGPhysicalBoxFragment>(result->PhysicalFragment());
// Simplified layout has the ability to add/remove scrollbars, this can cause
// a couple (rare) edge-cases which will make the fragment different enough
// that the parent should perform a full layout.
// - The size has changed.
// - The alignment baseline has shifted.
// We return a nullptr in these cases indicating to our parent that it needs
// to perform a full layout.
if (old_fragment.Size() != new_fragment.Size())
return nullptr;
if (old_fragment.Baseline() != new_fragment.Baseline())
return nullptr;
#if DCHECK_IS_ON()
result->CheckSameForSimplifiedLayout(*previous_result);
#endif
return result;
}
scoped_refptr<const NGLayoutResult>
NGBlockNode::CachedLayoutResultForOutOfFlowPositioned(
LogicalSize container_content_size) const {
DCHECK(IsOutOfFlowPositioned());
if (box_->NeedsLayout())
return nullptr;
const NGLayoutResult* cached_layout_result = box_->GetCachedLayoutResult();
if (!cached_layout_result)
return nullptr;
// The containing-block may have borders/scrollbars which might change
// between passes affecting the final position.
if (!cached_layout_result->CanUseOutOfFlowPositionedFirstTierCache())
return nullptr;
// TODO(layout-dev): There are potentially more cases where we can reuse this
// layout result.
// E.g. when we have a fixed-length top position constraint (top: 5px), we
// are in the correct writing mode (htb-ltr), and we have a fixed width.
const NGConstraintSpace& space =
cached_layout_result->GetConstraintSpaceForCaching();
if (space.PercentageResolutionSize() != container_content_size)
return nullptr;
// We currently don't keep the static-position around to determine if it is
// the same as the previous layout pass. As such, only reuse the result when
// we know it doesn't depend on the static-position.
//
// TODO(layout-dev): We might be able to determine what the previous
// static-position was based on |NGLayoutResult::OutOfFlowPositionedOffset|.
bool depends_on_static_position =
(Style().Left().IsAuto() && Style().Right().IsAuto()) ||
(Style().Top().IsAuto() && Style().Bottom().IsAuto());
if (depends_on_static_position)
return nullptr;
return cached_layout_result;
}
void NGBlockNode::PrepareForLayout() const {
auto* block = DynamicTo<LayoutBlock>(box_);
if (block && block->IsScrollContainer()) {
DCHECK(block->GetScrollableArea());
if (block->GetScrollableArea()->ShouldPerformScrollAnchoring())
block->GetScrollableArea()->GetScrollAnchor()->NotifyBeforeLayout();
}
// TODO(layoutng) Can UpdateMarkerTextIfNeeded call be moved
// somewhere else? List items need up-to-date markers before layout.
if (IsListItem())
To<LayoutNGListItem>(box_)->UpdateMarkerTextIfNeeded();
}
void NGBlockNode::FinishLayout(
LayoutBlockFlow* block_flow,
const NGConstraintSpace& constraint_space,
const NGBlockBreakToken* break_token,
scoped_refptr<const NGLayoutResult> layout_result) const {
// If we abort layout and don't clear the cached layout-result, we can end
// up in a state where the layout-object tree doesn't match fragment tree
// referenced by this layout-result.
if (layout_result->Status() != NGLayoutResult::kSuccess) {
box_->ClearLayoutResults();
return;
}
// Add all layout results (and fragments) generated from a node to a list in
// the layout object. Some extra care is required to correctly overwrite
// intermediate layout results: The sequence number of an incoming break token
// corresponds with the fragment index in the layout object (off by 1,
// though). When writing back a layout result, we remove any fragments in the
// layout box at higher indices than that of the one we're writing back.
const auto& physical_fragment =
To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment());
if (layout_result->IsSingleUse())
box_->AddLayoutResult(layout_result, FragmentIndex(break_token));
else
box_->SetCachedLayoutResult(layout_result);
if (block_flow) {
const NGFragmentItems* items = physical_fragment.Items();
bool has_inline_children = items || HasInlineChildren(block_flow);
// Don't consider display-locked objects as having any children.
if (has_inline_children && box_->ChildLayoutBlockedByDisplayLock()) {
has_inline_children = false;
// It could be the case that our children are already clean at the time
// the lock was acquired. This means that |box_| self dirty bits might be
// set, and child dirty bits might not be. We clear the self bits since we
// want to treat the |box_| as layout clean, even when locked. However,
// here we also skip appending paint fragments for inline children. This
// means that we potentially can end up in a situation where |box_| is
// completely layout clean, but its inline children didn't append the
// paint fragments to it, which causes problems. In order to solve this,
// we set a child dirty bit on |box_| ensuring that when the lock
// is removed, or update is forced, we will visit this box again and
// properly create the paint fragments. See https://crbug.com/962614.
box_->SetChildNeedsLayout(kMarkOnlyThis);
}
if (has_inline_children) {
if (items)
CopyFragmentItemsToLayoutBox(physical_fragment, *items, break_token);
} else {
// We still need to clear |NGInlineNodeData| in case it had inline
// children.
block_flow->ClearNGInlineNodeData();
}
} else {
DCHECK(!physical_fragment.HasItems());
}
CopyFragmentDataToLayoutBox(constraint_space, *layout_result, break_token);
}
MinMaxSizesResult NGBlockNode::ComputeMinMaxSizes(
WritingMode container_writing_mode,
const MinMaxSizesInput& input,
const NGConstraintSpace* constraint_space) const {
// TODO(layoutng) Can UpdateMarkerTextIfNeeded call be moved
// somewhere else? List items need up-to-date markers before layout.
if (IsListItem())
To<LayoutNGListItem>(box_)->UpdateMarkerTextIfNeeded();
bool is_orthogonal_flow_root =
!IsParallelWritingMode(container_writing_mode, Style().GetWritingMode());
// If we're orthogonal, run layout to compute the sizes.
if (is_orthogonal_flow_root) {
// If we have an aspect ratio, we may be able to avoid laying out the
// child as an optimization, if performance testing shows this to be
// important.
MinMaxSizes sizes;
// Some other areas of the code can query the intrinsic-sizes while outside
// of the layout phase.
// TODO(ikilpatrick): Remove this check.
if (!box_->GetFrameView()->IsInPerformLayout()) {
sizes = ComputeMinMaxSizesFromLegacy(input, *constraint_space);
return MinMaxSizesResult(sizes,
/* depends_on_percentage_block_size */ false);
}
DCHECK(constraint_space);
scoped_refptr<const NGLayoutResult> layout_result =
Layout(*constraint_space);
DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess);
sizes = NGFragment({container_writing_mode, TextDirection::kLtr},
layout_result->PhysicalFragment())
.InlineSize();
return MinMaxSizesResult(sizes,
/* depends_on_percentage_block_size */ false);
}
// Synthesize a zero space if not provided.
auto zero_constraint_space = CreateConstraintSpaceForMinMax(*this, input);
if (!constraint_space)
constraint_space = &zero_constraint_space;
if (!Style().AspectRatio().IsAuto() && !IsReplaced() &&
input.type == MinMaxSizesType::kContent) {
LayoutUnit block_size(kIndefiniteSize);
if (IsOutOfFlowPositioned()) {
// For out-of-flow, the input percentage block size is actually our
// block size. We should use that for aspect-ratio purposes if known.
block_size = input.percentage_resolution_block_size;
}
const NGFragmentGeometry fragment_geometry =
CalculateInitialMinMaxFragmentGeometry(*constraint_space, *this);
const NGBoxStrut border_padding =
fragment_geometry.border + fragment_geometry.padding;
LayoutUnit size_from_ar = ComputeInlineSizeFromAspectRatio(
*constraint_space, Style(), border_padding,
ShouldBeConsideredAsReplaced(), block_size);
if (size_from_ar != kIndefiniteSize) {
return MinMaxSizesResult({size_from_ar, size_from_ar},
Style().LogicalHeight().IsPercentOrCalc());
}
}
bool can_use_cached_intrinsic_inline_sizes =
CanUseCachedIntrinsicInlineSizes(input, *constraint_space, *this);
// Use our cached sizes if either:
// - The %-block-sizes match.
// - We don't have a descendant which depends on the %-block-size.
if (can_use_cached_intrinsic_inline_sizes &&
(input.percentage_resolution_block_size ==
box_->IntrinsicLogicalWidthsPercentageResolutionBlockSize() ||
!box_->IntrinsicLogicalWidthsChildDependsOnPercentageBlockSize())) {
MinMaxSizes sizes = box_->IsTable() && !box_->IsLayoutNGMixin()
? box_->PreferredLogicalWidths()
: box_->IntrinsicLogicalWidths(input.type);
bool depends_on_percentage_block_size =
box_->IntrinsicLogicalWidthsDependsOnPercentageBlockSize();
return MinMaxSizesResult(sizes, depends_on_percentage_block_size);
}
const NGFragmentGeometry fragment_geometry =
CalculateInitialMinMaxFragmentGeometry(*constraint_space, *this);
// Calculate the %-block-size for our children up front. This allows us to
// determine if |input|'s %-block-size is used.
const NGBoxStrut border_padding =
fragment_geometry.border + fragment_geometry.padding;
bool uses_input_percentage_block_size = false;
LayoutUnit child_percentage_resolution_block_size =
CalculateChildPercentageBlockSizeForMinMax(
*constraint_space, *this, border_padding, fragment_geometry.scrollbar,
input.percentage_resolution_block_size,
&uses_input_percentage_block_size);
bool cache_depends_on_percentage_block_size =
uses_input_percentage_block_size &&
box_->IntrinsicLogicalWidthsChildDependsOnPercentageBlockSize();
// We might still be able to use the cached values if our children don't
// depend on the *input* %-block-size.
if (can_use_cached_intrinsic_inline_sizes &&
!cache_depends_on_percentage_block_size) {
MinMaxSizes sizes = box_->IsTable() && !box_->IsLayoutNGMixin()
? box_->PreferredLogicalWidths()
: box_->IntrinsicLogicalWidths(input.type);
return MinMaxSizesResult(sizes, cache_depends_on_percentage_block_size);
}
box_->SetIntrinsicLogicalWidthsDirty();
if (!CanUseNewLayout()) {
MinMaxSizes sizes = ComputeMinMaxSizesFromLegacy(input, *constraint_space);
// Update the cache bits for this legacy root (but not the intrinsic
// inline-sizes themselves).
box_->SetIntrinsicLogicalWidthsFromNG(
input.percentage_resolution_block_size,
/* depends_on_percentage_block_size */ uses_input_percentage_block_size,
/* child_depends_on_percentage_block_size */ true,
/* sizes */ nullptr);
return MinMaxSizesResult(sizes, uses_input_percentage_block_size);
}
// Copy the input, and set the new %-block-size.
MinMaxSizesInput adjusted_input = input;
adjusted_input.percentage_resolution_block_size =
child_percentage_resolution_block_size;
MinMaxSizesResult result = ComputeMinMaxSizesWithAlgorithm(
NGLayoutAlgorithmParams(*this, fragment_geometry, *constraint_space),
adjusted_input);
if (UNLIKELY(IsContentMinimumInlineSizeZero(*this)))
result.sizes.min_size = border_padding.InlineSum();
bool depends_on_percentage_block_size =
uses_input_percentage_block_size &&
result.depends_on_percentage_block_size;
if (!Style().AspectRatio().IsAuto() &&
BlockLengthUnresolvable(*constraint_space, Style().LogicalHeight())) {
// If the block size will be computed from the aspect ratio, we need
// to take the max-block-size into account.
// https://drafts.csswg.org/css-sizing-4/#aspect-ratio
MinMaxSizes min_max = ComputeMinMaxInlineSizesFromAspectRatio(
*constraint_space, Style(), border_padding);
result.sizes.min_size = min_max.ClampSizeToMinAndMax(result.sizes.min_size);
result.sizes.max_size = min_max.ClampSizeToMinAndMax(result.sizes.max_size);
depends_on_percentage_block_size =
depends_on_percentage_block_size ||
Style().LogicalMinHeight().IsPercentOrCalc() ||
Style().LogicalMaxHeight().IsPercentOrCalc();
}
box_->SetIntrinsicLogicalWidthsFromNG(
input.percentage_resolution_block_size, depends_on_percentage_block_size,
/* child_depends_on_percentage_block_size */
result.depends_on_percentage_block_size, &result.sizes);
if (IsNGTableCell()) {
To<LayoutNGTableCell>(box_)->SetIntrinsicLogicalWidthsBorderSizes(
constraint_space->TableCellBorders());
}
// We report to our parent if we depend on the %-block-size if we used the
// input %-block-size, or one of children said it depended on this.
result.depends_on_percentage_block_size = depends_on_percentage_block_size;
return result;
}
MinMaxSizes NGBlockNode::ComputeMinMaxSizesFromLegacy(
const MinMaxSizesInput& input,
const NGConstraintSpace& space) const {
BoxLayoutExtraInput extra_input(*box_);
SetupBoxLayoutExtraInput(space, *box_, &extra_input);
// Tables don't calculate their min/max content contribution the same way as
// other layout nodes. This is because width/min-width/etc have a different
// meaning for tables.
//
// Due to this the min/max content contribution is their min/max content size.
MinMaxSizes sizes = box_->IsTable()
? box_->PreferredLogicalWidths()
: box_->IntrinsicLogicalWidths(input.type);
return sizes;
}
NGLayoutInputNode NGBlockNode::NextSibling() const {
LayoutObject* next_sibling = GetLayoutObjectForNextSiblingNode(box_);
// We may have some LayoutInline(s) still within the tree (due to treating
// inline-level floats and/or OOF-positioned nodes as block-level), we need
// to skip them and clear layout.
while (next_sibling && next_sibling->IsInline()) {
// TODO(layout-dev): Clearing needs-layout within this accessor is an
// unexpected side-effect. There may be additional invalidations that need
// to be performed.
DCHECK(next_sibling->IsText());
next_sibling->ClearNeedsLayout();
next_sibling = next_sibling->NextSibling();
}
if (!next_sibling)
return nullptr;
return NGBlockNode(To<LayoutBox>(next_sibling));
}
NGLayoutInputNode NGBlockNode::FirstChild() const {
auto* block = DynamicTo<LayoutBlock>(box_);
if (UNLIKELY(!block))
return NGBlockNode(box_->FirstChildBox());
auto* child = GetLayoutObjectForFirstChildNode(block);
if (!child)
return nullptr;
if (!AreNGBlockFlowChildrenInline(block))
return NGBlockNode(To<LayoutBox>(child));
NGInlineNode inline_node(To<LayoutBlockFlow>(block));
if (!inline_node.IsBlockLevel())
return inline_node;
// At this point we have a node which is empty or only has floats and
// OOF-positioned nodes. We treat all children as block-level, even though
// they are within a inline-level LayoutBlockFlow.
// We may have some LayoutInline(s) still within the tree (due to treating
// inline-level floats and/or OOF-positioned nodes as block-level), we need
// to skip them and clear layout.
while (child && child->IsInline()) {
// TODO(layout-dev): Clearing needs-layout within this accessor is an
// unexpected side-effect. There may be additional invalidations that need
// to be performed.
DCHECK(child->IsText());
child->ClearNeedsLayout();
child = child->NextSibling();
}
if (!child)
return nullptr;
DCHECK(child->IsFloatingOrOutOfFlowPositioned());
return NGBlockNode(To<LayoutBox>(child));
}
NGBlockNode NGBlockNode::GetRenderedLegend() const {
if (!IsFieldsetContainer())
return nullptr;
return NGBlockNode(LayoutFieldset::FindInFlowLegend(*To<LayoutBlock>(box_)));
}
NGBlockNode NGBlockNode::GetFieldsetContent() const {
if (!IsFieldsetContainer())
return nullptr;
auto* child = GetLayoutObjectForFirstChildNode(To<LayoutBlock>(box_));
if (!child)
return nullptr;
return NGBlockNode(To<LayoutBox>(child));
}
bool NGBlockNode::CanUseNewLayout(const LayoutBox& box) {
DCHECK(RuntimeEnabledFeatures::LayoutNGEnabled());
if (box.ForceLegacyLayout())
return false;
return box.IsLayoutNGMixin() ||
(box.IsLayoutImage() && !box.IsMedia() &&
RuntimeEnabledFeatures::LayoutNGReplacedEnabled());
}
bool NGBlockNode::CanUseNewLayout() const {
return CanUseNewLayout(*box_);
}
String NGBlockNode::ToString() const {
return String::Format("NGBlockNode: '%s'",
GetLayoutBox()->DebugName().Ascii().c_str());
}
void NGBlockNode::CopyFragmentDataToLayoutBox(
const NGConstraintSpace& constraint_space,
const NGLayoutResult& layout_result,
const NGBlockBreakToken* previous_break_token) const {
const auto& physical_fragment =
To<NGPhysicalBoxFragment>(layout_result.PhysicalFragment());
NGBoxFragment fragment(constraint_space.GetWritingDirection(),
physical_fragment);
LogicalSize fragment_logical_size = fragment.Size();
NGBoxStrut borders = fragment.Borders();
NGBoxStrut scrollbars = ComputeScrollbars(constraint_space, *this);
NGBoxStrut padding = fragment.Padding();
NGBoxStrut border_scrollbar_padding = borders + scrollbars + padding;
bool is_last_fragment = !physical_fragment.BreakToken();
// For each fragment we process, we'll accumulate the logical height. We reset
// it at the first fragment, and accumulate at each method call for fragments
// belonging to the same layout object. Logical width will only be set at the
// first fragment and is expected to remain the same throughout all subsequent
// fragments, since legacy layout doesn't support non-uniform fragmentainer
// widths.
if (LIKELY(physical_fragment.IsFirstForNode())) {
box_->SetSize(LayoutSize(physical_fragment.Size().width,
physical_fragment.Size().height));
// If this is a fragment from a node that didn't break into multiple
// fragments, write back the intrinsic size. We skip this if the node has
// fragmented, since intrinsic block-size is rather meaningless in that
// case, because the block-size may have been affected by something on the
// outside (i.e. the fragmentainer).
//
// If we had a fixed block size, our children will have sized themselves
// relative to the fixed size, which would make our intrinsic size incorrect
// (too big). So skip the write-back in that case, too.
if (LIKELY(is_last_fragment && !constraint_space.IsFixedBlockSize())) {
box_->SetIntrinsicContentLogicalHeight(
layout_result.IntrinsicBlockSize() -
border_scrollbar_padding.BlockSum());
}
} else {
DCHECK_EQ(box_->LogicalWidth(), fragment_logical_size.inline_size)
<< "Variable fragment inline size not supported";
// Update logical height, unless this fragment is past the block-end of the
// generating node (happens with overflow).
if (previous_break_token && !previous_break_token->IsAtBlockEnd()) {
box_->SetLogicalHeight(fragment_logical_size.block_size +
previous_break_token->ConsumedBlockSize());
} else {
DCHECK_EQ(fragment_logical_size.block_size, LayoutUnit());
}
}
// TODO(mstensho): This should always be done by the parent algorithm, since
// we may have auto margins, which only the parent is able to resolve. Remove
// the following line when all layout modes do this properly.
if (UNLIKELY(box_->IsTableCell())) {
// Table-cell margins compute to zero.
box_->SetMargin(NGPhysicalBoxStrut());
} else {
box_->SetMargin(ComputePhysicalMargins(constraint_space, Style()));
}
auto* block_flow = DynamicTo<LayoutBlockFlow>(box_);
LayoutMultiColumnFlowThread* flow_thread = GetFlowThread(block_flow);
// Position the children inside the box. We skip this if display-lock prevents
// child layout.
if (!ChildLayoutBlockedByDisplayLock()) {
if (UNLIKELY(flow_thread)) {
// Hold off writing legacy data for the entire multicol container until
// done with the last fragment (we may have multiple if nested within
// another fragmentation context). This way we'll get everything in order.
// We'd otherwise mess up in complex cases of nested column balancing. The
// column layout algorithms may retry layout for a given fragment, which
// would confuse the code that writes back to legacy objects, so that we
// wouldn't always update column sets or establish fragmentainer groups
// correctly.
if (is_last_fragment) {
const NGBlockBreakToken* incoming_break_token = nullptr;
for (const NGPhysicalBoxFragment& multicol_fragment :
box_->PhysicalFragments()) {
PlaceChildrenInFlowThread(flow_thread, constraint_space,
multicol_fragment, incoming_break_token);
incoming_break_token =
To<NGBlockBreakToken>(multicol_fragment.BreakToken());
}
}
} else {
PlaceChildrenInLayoutBox(physical_fragment, previous_break_token);
}
}
if (UNLIKELY(!is_last_fragment))
return;
LayoutBlock* block = DynamicTo<LayoutBlock>(box_);
bool needs_full_invalidation = false;
if (LIKELY(block)) {
#if DCHECK_IS_ON()
block->CheckPositionedObjectsNeedLayout();
#endif
if (UNLIKELY(flow_thread && Style().HasColumnRule())) {
// Issue full invalidation, in case the number of column rules have
// changed.
needs_full_invalidation = true;
} else if (block->StyleForContinuationOutline()) {
// When this is a block-in-inline created by |SplineInlines|, we may need
// to paint outlines for this. See |NGBoxFragmentPainter|.
needs_full_invalidation = true;
}
block->SetNeedsOverflowRecalc(
LayoutObject::OverflowRecalcType::kOnlyVisualOverflowRecalc);
if (RuntimeEnabledFeatures::LayoutNGLayoutOverflowEnabled()) {
block->SetLayoutOverflowFromLayoutResults();
} else {
BoxLayoutExtraInput input(*block);
SetupBoxLayoutExtraInput(constraint_space, *block, &input);
LayoutUnit overflow_block_size = layout_result.OverflowBlockSize();
if (UNLIKELY(previous_break_token))
overflow_block_size += previous_break_token->ConsumedBlockSize();
block->ComputeLayoutOverflow(overflow_block_size - borders.block_end -
scrollbars.block_end);
}
}
box_->UpdateAfterLayout();
if (needs_full_invalidation)
box_->ClearNeedsLayoutWithFullPaintInvalidation();
else
box_->ClearNeedsLayout();
// Overflow computation depends on this being set.
if (LIKELY(block_flow))
block_flow->SetIsSelfCollapsingFromNG(layout_result.IsSelfCollapsing());
// We should notify the display lock that we've done layout on self, and if
// it's not blocked, on children.
if (auto* context = box_->GetDisplayLockContext()) {
if (!ChildLayoutBlockedByDisplayLock())
context->DidLayoutChildren();
}
}
void NGBlockNode::PlaceChildrenInLayoutBox(
const NGPhysicalBoxFragment& physical_fragment,
const NGBlockBreakToken* previous_break_token) const {
LayoutBox* rendered_legend = nullptr;
for (const auto& child_fragment : physical_fragment.Children()) {
// Skip any line-boxes we have as children, this is handled within
// NGInlineNode at the moment.
if (!child_fragment->IsBox())
continue;
const auto& box_fragment = *To<NGPhysicalBoxFragment>(child_fragment.get());
if (box_fragment.IsFirstForNode()) {
if (box_fragment.IsRenderedLegend())
rendered_legend = To<LayoutBox>(box_fragment.GetMutableLayoutObject());
CopyChildFragmentPosition(box_fragment, child_fragment.offset,
physical_fragment, previous_break_token);
}
}
}
void NGBlockNode::PlaceChildrenInFlowThread(
LayoutMultiColumnFlowThread* flow_thread,
const NGConstraintSpace& space,
const NGPhysicalBoxFragment& physical_fragment,
const NGBlockBreakToken* previous_container_break_token) const {
// Stitch the contents of the columns together in the legacy flow thread, and
// update the position and size of column sets, spanners and spanner
// placeholders. Create fragmentainer groups as needed. When in a nested
// fragmentation context, we need one fragmentainer group for each outer
// fragmentainer in which the column contents occur. All this ensures that the
// legacy layout tree is sufficiently set up, so that DOM position/size
// querying APIs (such as offsetTop and offsetLeft) work correctly. We still
// rely on the legacy engine for this.
//
// This rather complex piece of machinery is described to some extent in the
// design document for legacy multicol:
// https://www.chromium.org/developers/design-documents/multi-column-layout
NGBoxStrut border_scrollbar_padding = ComputeBorders(space, *this) +
ComputeScrollbars(space, *this) +
ComputePadding(space, Style());
WritingModeConverter converter(space.GetWritingDirection(),
physical_fragment.Size());
LayoutUnit column_row_inline_size =
converter.ToLogical(physical_fragment.Size()).inline_size -
border_scrollbar_padding.InlineSum();
const NGBlockBreakToken* previous_column_break_token = nullptr;
LayoutMultiColumnSet* pending_column_set = nullptr;
LayoutUnit flow_thread_offset;
bool has_processed_first_column_in_flow_thread = false;
bool should_append_fragmentainer_group = false;
if (IsResumingLayout(previous_container_break_token)) {
// This multicol container is nested inside another fragmentation context,
// and this isn't its first fragment. Locate the break token for the
// previous inner column contents, so that we include the correct amount of
// consumed block-size in the child offsets. If there's a break token for
// column contents, we'll find it at the back.
const auto& child_break_tokens =
previous_container_break_token->ChildBreakTokens();
if (!child_break_tokens.empty()) {
const auto* token = To<NGBlockBreakToken>(child_break_tokens.back());
// We also create break tokens for spanners, so we need to check.
if (token->InputNode() == *this) {
previous_column_break_token = token;
flow_thread_offset = previous_column_break_token->ConsumedBlockSize();
// We're usually resuming layout into a column set that has already been
// started in an earlier fragment, but in some cases the column set
// starts exactly at the outer fragmentainer boundary (right after a
// spanner that took up all remaining space in the earlier fragment),
// and when this happens, we need to initialize the set now.
pending_column_set = flow_thread->PendingColumnSetForNG();
// If we resume with column content (without being interrupted by a
// spanner) in this multicol fragment, we need to add another
// fragmentainer group to the column set that we're resuming.
should_append_fragmentainer_group = true;
}
}
} else {
// This is the first fragment generated for the multicol container (there
// may be multiple fragments if we're nested inside another fragmentation
// context).
int column_count =
ResolveUsedColumnCount(space.AvailableSize().inline_size, Style());
flow_thread->StartLayoutFromNG(column_count);
pending_column_set =
DynamicTo<LayoutMultiColumnSet>(flow_thread->FirstMultiColumnBox());
}
for (const auto& child : physical_fragment.Children()) {
const auto& child_fragment = To<NGPhysicalBoxFragment>(*child);
const auto* child_box = To<LayoutBox>(child_fragment.GetLayoutObject());
if (child_box && child_box != box_) {
if (!child_box->IsColumnSpanAll()) {
// TODO(almaher): In order for legacy tree operations to work properly,
// we need to CopyChildFragmentPosition(). We should probably also
// update the LayoutBox size at the last fragment of an OOF node.
// (See comments in CL:2597769).
DCHECK(child_box->IsOutOfFlowPositioned());
continue;
}
CopyChildFragmentPosition(child_fragment, child.offset,
physical_fragment);
LayoutBox* placeholder = child_box->SpannerPlaceholder();
if (!child_fragment.BreakToken()) {
// Last fragment for this spanner. Update its placeholder.
placeholder->SetLocation(child_box->Location());
placeholder->SetSize(child_box->Size());
}
flow_thread->SkipColumnSpanner(child_box, flow_thread_offset);
if (auto* previous_column_set = DynamicTo<LayoutMultiColumnSet>(
placeholder->PreviousSiblingMultiColumnBox()))
previous_column_set->FinishLayoutFromNG();
pending_column_set = DynamicTo<LayoutMultiColumnSet>(
placeholder->NextSiblingMultiColumnBox());
// If this multicol container was nested inside another fragmentation
// context, and we're resuming at a subsequent fragment, we'll normally
// append another fragmentainer group for column contents. But since we
// found a spanner first, we won't do that, since we'll move to another
// column set (if there's more column content at all).
should_append_fragmentainer_group = false;
continue;
}
DCHECK(!child_box);
LogicalSize logical_size = converter.ToLogical(child_fragment.Size());
logical_size.block_size =
ClampedToValidFragmentainerCapacity(logical_size.block_size);
if (has_processed_first_column_in_flow_thread) {
// Non-uniform fragmentainer widths not supported by legacy layout.
DCHECK_EQ(flow_thread->LogicalWidth(), logical_size.inline_size);
} else {
// The offset of the flow thread is the same as that of the first column.
LayoutPoint point =
ToLayoutPoint(child_fragment, child.offset, physical_fragment,
previous_container_break_token);
flow_thread->SetLocationAndUpdateOverflowControlsIfNeeded(point);
flow_thread->SetLogicalWidth(logical_size.inline_size);
has_processed_first_column_in_flow_thread = true;
}
if (pending_column_set) {
// We're visiting this column set for the first time in this layout pass.
// Set up what we can set up. That's everything except for the block-size.
// Set the inline-size to that of the content-box of the multicol
// container. The inline-offset will be the content-box edge of the
// multicol container, and the block-offset will be the block-offset of
// the column itself. It doesn't matter which column from the same row we
// use, since all columns have the same block-offset and block-size (so
// just use the first one).
LogicalOffset logical_offset(
border_scrollbar_padding.inline_start,
converter.ToLogical(child.offset, child_fragment.Size())
.block_offset);
LogicalSize column_set_logical_size(column_row_inline_size,
logical_size.block_size);
PhysicalOffset physical_offset = converter.ToPhysical(
logical_offset, converter.ToPhysical(column_set_logical_size));
// We have calculated the physical offset relative to the border edge of
// this multicol container fragment. We'll now convert it to a legacy
// engine LayoutPoint, which will also take care of converting it into the
// flow thread coordinate space, if we happen to be nested inside another
// fragmentation context.
LayoutPoint point =
ToLayoutPoint(child_fragment, physical_offset, physical_fragment,
previous_container_break_token);
pending_column_set->SetLocation(point);
pending_column_set->SetLogicalWidth(column_row_inline_size);
pending_column_set->ResetColumnHeight();
pending_column_set = nullptr;
} else if (should_append_fragmentainer_group) {
// Resuming column layout from the previous outer fragmentainer into the
// same column set as we used there.
flow_thread->AppendNewFragmentainerGroupFromNG();
should_append_fragmentainer_group = false;
}
flow_thread->SetCurrentColumnBlockSizeFromNG(logical_size.block_size);
// Each anonymous child of a multicol container constitutes one column.
// Position each child fragment in the first column that they occur,
// relatively to the block-start of the flow thread.
PlaceChildrenInLayoutBox(child_fragment, previous_column_break_token);
// If the multicol container has inline children, there may still be floats
// there, but they aren't stored as child fragments of |column| in that case
// (but rather inside fragment items). Make sure that they get positioned,
// too.
if (const NGFragmentItems* items = child_fragment.Items()) {
CopyFragmentItemsToLayoutBox(child_fragment, *items,
previous_column_break_token);
}
flow_thread_offset += logical_size.block_size;
previous_column_break_token =
To<NGBlockBreakToken>(child_fragment.BreakToken());
}
if (!physical_fragment.BreakToken())
flow_thread->FinishLayoutFromNG(flow_thread_offset);
}
// Copies data back to the legacy layout tree for a given child fragment.
void NGBlockNode::CopyChildFragmentPosition(
const NGPhysicalBoxFragment& child_fragment,
PhysicalOffset offset,
const NGPhysicalBoxFragment& container_fragment,
const NGBlockBreakToken* previous_container_break_token) const {
auto* layout_box = To<LayoutBox>(child_fragment.GetMutableLayoutObject());
if (!layout_box)
return;
DCHECK(layout_box->Parent()) << "Should be called on children only.";
LayoutPoint point = ToLayoutPoint(child_fragment, offset, container_fragment,
previous_container_break_token);
layout_box->SetLocationAndUpdateOverflowControlsIfNeeded(point);
}
void NGBlockNode::CopyFragmentItemsToLayoutBox(
const NGPhysicalBoxFragment& container,
const NGFragmentItems& items,
const NGBlockBreakToken* previous_break_token) const {
LayoutUnit previously_consumed_block_size;
if (previous_break_token)
previously_consumed_block_size = previous_break_token->ConsumedBlockSize();
bool initial_container_is_flipped = Style().IsFlippedBlocksWritingMode();
for (NGInlineCursor cursor(container, items); cursor; cursor.MoveToNext()) {
if (const NGPhysicalBoxFragment* child = cursor.Current().BoxFragment()) {
// Replaced elements and inline blocks need Location() set relative to
// their block container.
LayoutObject* layout_object = child->GetMutableLayoutObject();
if (!layout_object)
continue;
if (auto* layout_box = DynamicTo<LayoutBox>(layout_object)) {
PhysicalOffset maybe_flipped_offset =
cursor.Current().OffsetInContainerFragment();
if (initial_container_is_flipped) {
maybe_flipped_offset.left = container.Size().width -
child->Size().width -
maybe_flipped_offset.left;
}
if (container.Style().IsHorizontalWritingMode())
maybe_flipped_offset.top += previously_consumed_block_size;
else
maybe_flipped_offset.left += previously_consumed_block_size;
layout_box->SetLocationAndUpdateOverflowControlsIfNeeded(
maybe_flipped_offset.ToLayoutPoint());
if (UNLIKELY(layout_box->HasSelfPaintingLayer()))
layout_box->Layer()->SetNeedsVisualOverflowRecalc();
continue;
}
// Legacy compatibility. This flag is used in paint layer for
// invalidation.
if (auto* layout_inline = DynamicTo<LayoutInline>(layout_object)) {
if (layout_inline->StyleRef().HasOutline() &&
!layout_inline->IsElementContinuation() &&
layout_inline->Continuation()) {
box_->SetContainsInlineWithOutlineAndContinuation(true);
}
if (UNLIKELY(layout_inline->HasSelfPaintingLayer()))
layout_inline->Layer()->SetNeedsVisualOverflowRecalc();
}
}
}
}
bool NGBlockNode::IsInlineFormattingContextRoot(
NGLayoutInputNode* first_child_out) const {
if (const auto* block = DynamicTo<LayoutBlockFlow>(box_)) {
if (!AreNGBlockFlowChildrenInline(block))
return false;
NGLayoutInputNode first_child = FirstChild();
if (first_child.IsInline()) {
if (first_child_out)
*first_child_out = first_child;
return true;
}
}
return false;
}
bool NGBlockNode::IsInlineLevel() const {
return GetLayoutBox()->IsInline();
}
bool NGBlockNode::IsAtomicInlineLevel() const {
// LayoutObject::IsAtomicInlineLevel() returns true for e.g., <img
// style="display: block">. Check IsInline() as well.
return GetLayoutBox()->IsAtomicInlineLevel() && GetLayoutBox()->IsInline();
}
bool NGBlockNode::HasAspectRatio() const {
if (!Style().AspectRatio().IsAuto())
return true;
LayoutBox* layout_object = GetLayoutBox();
if (!layout_object->IsImage() && !IsA<LayoutVideo>(layout_object) &&
!layout_object->IsCanvas() && !layout_object->IsSVGRoot()) {
return false;
}
// Retrieving this and throwing it away is wasteful. We could make this method
// return Optional<LogicalSize> that returns the aspect_ratio if there is one.
return !GetAspectRatio().IsEmpty();
}
LogicalSize NGBlockNode::GetAspectRatio() const {
// The CSS parser will ensure that this will only be set if the feature
// is enabled.
const StyleAspectRatio& ratio = Style().AspectRatio();
if (ratio.GetType() == EAspectRatioType::kRatio ||
(ratio.GetType() == EAspectRatioType::kAutoAndRatio && !IsReplaced()))
return Style().LogicalAspectRatio();
base::Optional<LayoutUnit> computed_inline_size;
base::Optional<LayoutUnit> computed_block_size;
GetOverrideIntrinsicSize(&computed_inline_size, &computed_block_size);
if (computed_inline_size && computed_block_size)
return LogicalSize(*computed_inline_size, *computed_block_size);
IntrinsicSizingInfo legacy_sizing_info;
To<LayoutReplaced>(box_)->ComputeIntrinsicSizingInfo(legacy_sizing_info);
LogicalSize intrinsic_ar{
LayoutUnit(legacy_sizing_info.aspect_ratio.Width()),
LayoutUnit(legacy_sizing_info.aspect_ratio.Height())};
if (!intrinsic_ar.IsEmpty())
return intrinsic_ar;
if (ratio.GetType() == EAspectRatioType::kAutoAndRatio)
return Style().LogicalAspectRatio();
return LogicalSize();
}
base::Optional<TransformationMatrix> NGBlockNode::GetTransformForChildFragment(
const NGPhysicalBoxFragment& child_fragment,
PhysicalSize size) const {
const auto* child_layout_object = child_fragment.GetLayoutObject();
DCHECK(child_layout_object);
if (!child_layout_object->ShouldUseTransformFromContainer(box_))
return base::nullopt;
TransformationMatrix transform;
child_layout_object->GetTransformFromContainer(box_, PhysicalOffset(),
transform, &size);
return transform;
}
bool NGBlockNode::IsCustomLayoutLoaded() const {
DCHECK(box_->IsLayoutNGCustom());
return To<LayoutNGCustom>(box_)->IsLoaded();
}
MathScriptType NGBlockNode::ScriptType() const {
DCHECK(IsA<MathMLScriptsElement>(GetDOMNode()));
return To<MathMLScriptsElement>(GetDOMNode())->GetScriptType();
}
bool NGBlockNode::HasIndex() const {
DCHECK(IsA<MathMLRadicalElement>(GetDOMNode()));
return To<MathMLRadicalElement>(GetDOMNode())->HasIndex();
}
scoped_refptr<const NGLayoutResult> NGBlockNode::LayoutAtomicInline(
const NGConstraintSpace& parent_constraint_space,
const ComputedStyle& parent_style,
bool use_first_line_style,
NGBaselineAlgorithmType baseline_algorithm_type) {
NGConstraintSpaceBuilder builder(parent_constraint_space,
Style().GetWritingDirection(),
/* is_new_fc */ true);
SetOrthogonalFallbackInlineSizeIfNeeded(parent_style, *this, &builder);
builder.SetIsPaintedAtomically(true);
builder.SetUseFirstLineStyle(use_first_line_style);
builder.SetBaselineAlgorithmType(baseline_algorithm_type);
builder.SetAvailableSize(parent_constraint_space.AvailableSize());
builder.SetPercentageResolutionSize(
parent_constraint_space.PercentageResolutionSize());
builder.SetReplacedPercentageResolutionSize(
parent_constraint_space.ReplacedPercentageResolutionSize());
NGConstraintSpace constraint_space = builder.ToConstraintSpace();
scoped_refptr<const NGLayoutResult> result = Layout(constraint_space);
// TODO(kojii): Investigate why ClearNeedsLayout() isn't called automatically
// when it's being laid out.
GetLayoutBox()->ClearNeedsLayout();
return result;
}
scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout(
const NGConstraintSpace& constraint_space) const {
// This is an exit-point from LayoutNG to the legacy engine. This means that
// we need to be at a formatting context boundary, since NG and legacy don't
// cooperate on e.g. margin collapsing.
DCHECK(!box_->IsLayoutBlock() ||
To<LayoutBlock>(box_)->CreatesNewFormattingContext());
// We cannot enter legacy layout for something fragmentable if we're inside an
// NG block fragmentation context. LayoutNG and legacy block fragmentation
// cannot cooperate within the same fragmentation context.
DCHECK(!constraint_space.HasBlockFragmentation() ||
box_->GetNGPaginationBreakability() == LayoutBox::kForbidBreaks);
scoped_refptr<const NGLayoutResult> layout_result =
box_->GetCachedLayoutResult();
// We need to force a layout on the child if the constraint space given will
// change the layout.
bool needs_force_relayout =
layout_result &&
!MaySkipLegacyLayout(*this, *layout_result, constraint_space);
if (box_->NeedsLayout() || !layout_result || needs_force_relayout) {
BoxLayoutExtraInput input(*box_);
WritingMode writing_mode = Style().GetWritingMode();
SetupBoxLayoutExtraInput(constraint_space, *box_, &input);
box_->ComputeAndSetBlockDirectionMargins(box_->ContainingBlock());
// Using |LayoutObject::LayoutIfNeeded| save us a little bit of overhead,
// compared to |LayoutObject::ForceLayout|.
DCHECK(!box_->IsLayoutNGMixin());
bool needed_layout = box_->NeedsLayout();
if (box_->NeedsLayout() && !needs_force_relayout)
box_->LayoutIfNeeded();
else
box_->ForceLayout();
// Synthesize a new layout result.
NGFragmentGeometry fragment_geometry;
fragment_geometry.border_box_size = {box_->LogicalWidth(),
box_->LogicalHeight()};
fragment_geometry.border = {box_->BorderStart(), box_->BorderEnd(),
box_->BorderBefore(), box_->BorderAfter()};
fragment_geometry.scrollbar = ComputeScrollbars(constraint_space, *this);
fragment_geometry.padding = {box_->PaddingStart(), box_->PaddingEnd(),
box_->PaddingBefore(), box_->PaddingAfter()};
// TODO(kojii): Implement use_first_line_style.
NGBoxFragmentBuilder builder(*this, box_->Style(), &constraint_space,
{writing_mode, box_->StyleRef().Direction()});
builder.SetIsNewFormattingContext(
constraint_space.IsNewFormattingContext());
builder.SetInitialFragmentGeometry(fragment_geometry);
builder.SetIsLegacyLayoutRoot();
if (box_->ShouldComputeSizeAsReplaced()) {
builder.SetIntrinsicBlockSize(box_->LogicalHeight());
} else {
builder.SetIntrinsicBlockSize(
box_->IntrinsicContentLogicalHeight() +
box_->BorderAndPaddingLogicalHeight() +
box_->ComputeLogicalScrollbars().BlockSum());
}
// If we're block-fragmented, we can only handle monolithic content, since
// the two block fragmentation machineries (NG and legacy) cannot cooperate.
DCHECK(!constraint_space.HasBlockFragmentation() || IsMonolithic());
if (constraint_space.IsInitialColumnBalancingPass()) {
// In the initial column balancing pass we need to provide the tallest
// unbreakable block-size. However, since the content is monolithic,
// that's already handled by the parent algorithm (so we don't need to
// propagate anything here). We still have to tell the builder that we're
// in this layout pass, though, so that the layout result is set up
// correctly.
builder.SetIsInitialColumnBalancingPass();
}
CopyBaselinesFromLegacyLayout(constraint_space, &builder);
layout_result = builder.ToBoxFragment();
box_->SetCachedLayoutResult(layout_result);
// If |SetCachedLayoutResult| did not update cached |LayoutResult|,
// |NeedsLayout()| flag should not be cleared.
if (needed_layout) {
if (layout_result != box_->GetCachedLayoutResult()) {
// TODO(kojii): If we failed to update CachedLayoutResult for other
// reasons, we'd like to review it.
NOTREACHED();
box_->SetNeedsLayout(layout_invalidation_reason::kUnknown);
}
}
} else if (layout_result) {
// OOF-positioned nodes have a two-tier cache, and their layout results
// must always contain the correct percentage resolution size.
// See |NGBlockNode::CachedLayoutResultForOutOfFlowPositioned|.
const NGConstraintSpace& old_space =
layout_result->GetConstraintSpaceForCaching();
bool needs_cached_result_update =
IsOutOfFlowPositioned() &&
constraint_space.PercentageResolutionSize() !=
old_space.PercentageResolutionSize();
if (needs_cached_result_update) {
layout_result = base::AdoptRef(new NGLayoutResult(
*layout_result, constraint_space, layout_result->EndMarginStrut(),
layout_result->BfcLineOffset(), layout_result->BfcBlockOffset(),
LayoutUnit() /* block_offset_delta */));
box_->SetCachedLayoutResult(layout_result);
}
}
UpdateShapeOutsideInfoIfNeeded(
*layout_result, constraint_space.PercentageResolutionInlineSize());
return layout_result;
}
scoped_refptr<const NGLayoutResult> NGBlockNode::RunSimplifiedLayout(
const NGLayoutAlgorithmParams& params,
const NGLayoutResult& previous_result) const {
NGSimplifiedLayoutAlgorithm algorithm(params, previous_result);
if (const auto* previous_box_fragment = DynamicTo<NGPhysicalBoxFragment>(
&previous_result.PhysicalFragment())) {
if (previous_box_fragment->HasItems())
return algorithm.LayoutWithItemsBuilder();
}
return algorithm.Layout();
}
void NGBlockNode::CopyBaselinesFromLegacyLayout(
const NGConstraintSpace& constraint_space,
NGBoxFragmentBuilder* builder) const {
switch (constraint_space.BaselineAlgorithmType()) {
case NGBaselineAlgorithmType::kFirstLine: {
LayoutUnit position = box_->FirstLineBoxBaseline();
if (position != -1)
builder->SetBaseline(position);
break;
}
case NGBaselineAlgorithmType::kInlineBlock: {
LayoutUnit position =
AtomicInlineBaselineFromLegacyLayout(constraint_space);
if (position != -1)
builder->SetBaseline(position);
break;
}
}
}
LayoutUnit NGBlockNode::AtomicInlineBaselineFromLegacyLayout(
const NGConstraintSpace& constraint_space) const {
LineDirectionMode line_direction = box_->IsHorizontalWritingMode()
? LineDirectionMode::kHorizontalLine
: LineDirectionMode::kVerticalLine;
// If this is an inline box, use |BaselinePosition()|. Some LayoutObject
// classes override it assuming inline layout calls |BaselinePosition()|.
if (box_->IsInline()) {
LayoutUnit position = LayoutUnit(box_->BaselinePosition(
box_->Style()->GetFontBaseline(), constraint_space.UseFirstLineStyle(),
line_direction, kPositionOnContainingLine));
// BaselinePosition() uses margin edge for atomic inlines. Subtract
// margin-over so that the position is relative to the border box.
if (box_->IsAtomicInlineLevel())
position -= box_->MarginOver();
if (IsFlippedLinesWritingMode(constraint_space.GetWritingMode()))
return box_->Size().Width() - position;
return position;
}
// If this is a block box, use |InlineBlockBaseline()|. When an inline block
// has block children, their inline block baselines need to be propagated.
return box_->InlineBlockBaseline(line_direction);
}
// Floats can optionally have a shape area, specifed by "shape-outside". The
// current shape machinery requires setting the size of the float after layout
// in the parents writing mode.
void NGBlockNode::UpdateShapeOutsideInfoIfNeeded(
const NGLayoutResult& layout_result,
LayoutUnit percentage_resolution_inline_size) const {
if (!box_->IsFloating() || !box_->GetShapeOutsideInfo())
return;
// The box_ may not have a valid size yet (due to an intermediate layout),
// use the fragment's size instead.
LayoutSize box_size = layout_result.PhysicalFragment().Size().ToLayoutSize();
// TODO(ikilpatrick): Ideally this should be moved to a NGLayoutResult
// computing the shape area. There may be an issue with the new fragmentation
// model and computing the correct sizes of shapes.
ShapeOutsideInfo* shape_outside = box_->GetShapeOutsideInfo();
LayoutBlock* containing_block = box_->ContainingBlock();
shape_outside->SetReferenceBoxLogicalSize(
containing_block->IsHorizontalWritingMode() ? box_size
: box_size.TransposedSize());
shape_outside->SetPercentageResolutionInlineSize(
percentage_resolution_inline_size);
}
void NGBlockNode::UseLegacyOutOfFlowPositioning() const {
DCHECK(box_->IsOutOfFlowPositioned());
box_->ContainingBlock()->InsertPositionedObject(box_);
}
void NGBlockNode::StoreMargins(const NGConstraintSpace& constraint_space,
const NGBoxStrut& margins) {
NGPhysicalBoxStrut physical_margins =
margins.ConvertToPhysical(constraint_space.GetWritingDirection());
box_->SetMargin(physical_margins);
}
void NGBlockNode::StoreMargins(const NGPhysicalBoxStrut& physical_margins) {
box_->SetMargin(physical_margins);
}
void NGBlockNode::AddColumnResult(
scoped_refptr<const NGLayoutResult> result,
const NGBlockBreakToken* incoming_break_token) const {
wtf_size_t index = FragmentIndex(incoming_break_token);
GetFlowThread(To<LayoutBlockFlow>(box_))->AddLayoutResult(result, index);
}
void NGBlockNode::AddColumnResult(
scoped_refptr<const NGLayoutResult> result) const {
GetFlowThread(To<LayoutBlockFlow>(box_))->AddLayoutResult(std::move(result));
}
void NGBlockNode::ReplaceColumnResult(
scoped_refptr<const NGLayoutResult> result,
const NGPhysicalBoxFragment& old_fragment) const {
GetFlowThread(To<LayoutBlockFlow>(box_))
->ReplaceLayoutResult(std::move(result), old_fragment);
}
} // namespace blink