blob: 442a448d9ad62e97ea3799674a3ea79fcafe25e7 [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_out_of_flow_layout_part.h"
#include <math.h>
#include "third_party/blink/renderer/core/layout/layout_block.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_flexible_box.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/layout_box_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.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_builder.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_positioned_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.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/core/style/computed_style.h"
namespace blink {
namespace {
bool IsAnonymousContainer(const LayoutObject* layout_object) {
return layout_object->IsAnonymousBlock() &&
layout_object->CanContainAbsolutePositionObjects();
}
// This saves the static-position for an OOF-positioned object into its
// paint-layer.
void SaveStaticPositionOnPaintLayer(const LayoutBox* layout_box,
const LayoutObject* container,
const NGLogicalStaticPosition& position) {
const LayoutObject* parent = layout_box->Parent();
if (parent == container ||
(parent->IsLayoutInline() && parent->ContainingBlock() == container)) {
DCHECK(layout_box->Layer());
layout_box->Layer()->SetStaticPositionFromNG(position);
}
}
// When the containing block is a split inline, Legacy and NG use different
// containers to place the OOF-positioned nodes:
// - Legacy uses the anonymous block generated by inline.
// - NG uses the anonymous' parent block, that contains all the anonymous
// continuations.
// This function finds the correct anonymous parent block.
const LayoutInline* GetOOFContainingBlockFromAnonymous(
const LayoutObject* anonymous_block,
EPosition child_position) {
DCHECK(IsAnonymousContainer(anonymous_block));
DCHECK(anonymous_block->IsBox());
// Comments and code copied from
// LayoutBox::ContainingBlockLogicalWidthForPositioned.
// Ensure we compute our width based on the width of our rel-pos inline
// container rather than any anonymous block created to manage a block-flow
// ancestor of ours in the rel-pos inline's inline flow.
LayoutBoxModelObject* absolute_containing_block =
To<LayoutBox>(anonymous_block)->Continuation();
// There may be nested parallel inline continuations. We have now found the
// innermost inline (which may not be relatively positioned). Locate the
// inline that serves as the containing block of this box.
while (!absolute_containing_block->CanContainOutOfFlowPositionedElement(
child_position)) {
absolute_containing_block =
To<LayoutBoxModelObject>(absolute_containing_block->Container());
}
// Make absolute_containing_block continuation root.
return To<LayoutInline>(absolute_containing_block->ContinuationRoot());
}
} // namespace
NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart(
const NGBlockNode& container_node,
const NGConstraintSpace& container_space,
NGBoxFragmentBuilder* container_builder)
: NGOutOfFlowLayoutPart(container_node.IsAbsoluteContainer(),
container_node.IsFixedContainer(),
container_node.Style(),
container_space,
container_builder) {
can_traverse_fragments_ = container_node.CanTraversePhysicalFragments();
}
NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart(
bool is_absolute_container,
bool is_fixed_container,
const ComputedStyle& container_style,
const NGConstraintSpace& container_space,
NGBoxFragmentBuilder* container_builder,
base::Optional<LogicalSize> initial_containing_block_fixed_size)
: container_builder_(container_builder),
writing_mode_(container_style.GetWritingMode()),
default_writing_direction_(container_style.GetWritingDirection()),
is_absolute_container_(is_absolute_container),
is_fixed_container_(is_fixed_container),
has_block_fragmentation_(container_space.HasBlockFragmentation()) {
if (!container_builder->HasOutOfFlowPositionedCandidates() &&
!To<LayoutBlock>(container_builder_->GetLayoutObject())
->HasPositionedObjects())
return;
default_containing_block_info_for_absolute_.writing_direction =
default_writing_direction_;
default_containing_block_info_for_fixed_.writing_direction =
default_writing_direction_;
const NGBoxStrut border_scrollbar =
container_builder->Borders() + container_builder->Scrollbar();
allow_first_tier_oof_cache_ = border_scrollbar.IsEmpty();
default_containing_block_info_for_absolute_.rect.size =
ShrinkLogicalSize(container_builder_->Size(), border_scrollbar);
default_containing_block_info_for_fixed_.rect.size =
initial_containing_block_fixed_size
? *initial_containing_block_fixed_size
: default_containing_block_info_for_absolute_.rect.size;
LogicalOffset container_offset = {border_scrollbar.inline_start,
border_scrollbar.block_start};
default_containing_block_info_for_absolute_.rect.offset = container_offset;
default_containing_block_info_for_fixed_.rect.offset = container_offset;
}
void NGOutOfFlowLayoutPart::Run(const LayoutBox* only_layout) {
if (container_builder_->IsBlockFragmentationContextRoot() &&
!has_block_fragmentation_) {
if (container_builder_->HasOutOfFlowFragmentainerDescendants()) {
Vector<NGLogicalOutOfFlowPositionedNode> fragmentainer_descendants;
container_builder_->SwapOutOfFlowFragmentainerDescendants(
&fragmentainer_descendants);
DCHECK(!fragmentainer_descendants.IsEmpty());
LayoutUnit column_inline_progression = ColumnInlineProgression(
container_builder_->ChildAvailableSize().inline_size,
container_builder_->Style());
LayoutFragmentainerDescendants(&fragmentainer_descendants,
column_inline_progression);
}
if (container_builder_->HasMulticolsWithPendingOOFs()) {
NGContainerFragmentBuilder::MulticolCollection
multicols_with_pending_oofs;
container_builder_->SwapMulticolsWithPendingOOFs(
&multicols_with_pending_oofs);
DCHECK(!multicols_with_pending_oofs.IsEmpty());
for (LayoutBox* multicol : multicols_with_pending_oofs)
LayoutOOFsInMulticol(NGBlockNode(multicol));
}
}
const LayoutObject* current_container = container_builder_->GetLayoutObject();
if (!container_builder_->HasOutOfFlowPositionedCandidates() &&
!To<LayoutBlock>(current_container)->HasPositionedObjects())
return;
// If the container is display-locked, then we skip the layout of descendants,
// so we can early out immediately.
if (current_container->ChildLayoutBlockedByDisplayLock())
return;
Vector<NGLogicalOutOfFlowPositionedNode> candidates;
container_builder_->SwapOutOfFlowPositionedCandidates(&candidates);
// Special case: containing block is a split inline.
// If current container was generated by a split inline, do not position
// OOF-positioned nodes inside this container. Let its non-anonymous parent
// handle it. Only the parent has geometry information needed to compute
// containing block geometry.
// See "Special case: oof css container" comment for detailed description.
if (candidates.size() > 0 && current_container && !only_layout &&
IsAnonymousContainer(current_container)) {
const LayoutInline* absolute_containing_block =
is_absolute_container_ ? GetOOFContainingBlockFromAnonymous(
current_container, EPosition::kAbsolute)
: nullptr;
const LayoutInline* fixed_containing_block =
is_fixed_container_ ? GetOOFContainingBlockFromAnonymous(
current_container, EPosition::kFixed)
: nullptr;
for (auto& candidate : candidates) {
if (absolute_containing_block &&
absolute_containing_block->CanContainOutOfFlowPositionedElement(
candidate.node.Style().GetPosition())) {
candidate.inline_container = absolute_containing_block;
} else if (fixed_containing_block &&
fixed_containing_block->CanContainOutOfFlowPositionedElement(
candidate.node.Style().GetPosition())) {
candidate.inline_container = fixed_containing_block;
}
container_builder_->AddOutOfFlowDescendant(candidate);
}
return;
}
HashSet<const LayoutObject*> placed_objects;
LayoutCandidates(&candidates, only_layout, &placed_objects);
if (only_layout)
return;
// If we're in a block fragmentation context, we've already ruled out the
// possibility of having legacy objects in here. The code below would pick up
// every OOF candidate not in placed_objects, and treat them as a legacy
// object (even if they aren't one), while in fact it could be an NG object
// that we have finished laying out in an earlier fragmentainer. Just bail.
if (has_block_fragmentation_)
return;
wtf_size_t prev_placed_objects_size = placed_objects.size();
while (SweepLegacyCandidates(&placed_objects)) {
container_builder_->SwapOutOfFlowPositionedCandidates(&candidates);
// We must have at least one new candidate, otherwise we shouldn't have
// entered this branch.
DCHECK_GT(candidates.size(), 0u);
LayoutCandidates(&candidates, only_layout, &placed_objects);
// Legacy currently has a bug where an OOF-positioned node is present
// within the current node's |LayoutBlock::PositionedObjects|, however it
// is not the containing-block for this node.
//
// This results in |LayoutDescendantCandidates| never performing layout on
// any additional objects.
wtf_size_t placed_objects_size = placed_objects.size();
if (prev_placed_objects_size == placed_objects_size) {
NOTREACHED();
break;
}
prev_placed_objects_size = placed_objects_size;
}
}
// Gather candidates that weren't present in the OOF candidates list.
// This occurs when a candidate is separated from container by a legacy node.
// E.g.
// <div style="position: relative;">
// <div style="display: flex;">
// <div style="position: absolute;"></div>
// </div>
// </div>
// Returns false if no new candidates were found.
bool NGOutOfFlowLayoutPart::SweepLegacyCandidates(
HashSet<const LayoutObject*>* placed_objects) {
const auto* container_block =
DynamicTo<LayoutBlock>(container_builder_->GetLayoutObject());
if (!container_block)
return false;
TrackedLayoutBoxListHashSet* legacy_objects =
container_block->PositionedObjects();
if (!legacy_objects || legacy_objects->size() == placed_objects->size())
return false;
for (LayoutObject* legacy_object : *legacy_objects) {
if (placed_objects->Contains(legacy_object))
continue;
// Flex OOF children may have center alignment or similar, and in order
// to determine their static position correctly need to have a valid
// size first.
// We perform a pre-layout to correctly determine the static position.
// Copied from LayoutBlock::LayoutPositionedObject
// TODO(layout-dev): Remove this once LayoutFlexibleBox is removed.
LayoutBox* layout_box = To<LayoutBox>(legacy_object);
if (layout_box->Parent()->IsFlexibleBox()) {
auto* parent = To<LayoutFlexibleBox>(layout_box->Parent());
if (parent->SetStaticPositionForPositionedLayout(*layout_box)) {
NGLogicalOutOfFlowPositionedNode candidate((NGBlockNode(layout_box)),
NGLogicalStaticPosition());
LayoutCandidate(candidate, /* only_layout */ nullptr);
parent->SetStaticPositionForPositionedLayout(*layout_box);
}
}
NGLogicalStaticPosition static_position =
LayoutBoxUtils::ComputeStaticPositionFromLegacy(
*layout_box,
container_builder_->Borders() + container_builder_->Scrollbar(),
container_builder_);
const LayoutObject* css_container = layout_box->Container();
if (IsAnonymousContainer(css_container)) {
css_container = GetOOFContainingBlockFromAnonymous(
css_container, layout_box->Style()->GetPosition());
}
container_builder_->AddOutOfFlowLegacyCandidate(
NGBlockNode(layout_box), static_position,
DynamicTo<LayoutInline>(css_container));
}
return true;
}
// Retrieve the stored ContainingBlockInfo needed for placing positioned nodes.
// When fragmenting, the ContainingBlockInfo is not stored ahead of time and
// must be generated on demand. The reason being that during fragmentation, we
// wait to place positioned nodes until they've reached the fragmentation
// context root. In such cases, we cannot use default |ContainingBlockInfo|
// since the fragmentation root is not the containing block of the positioned
// nodes. Rather, we must generate their ContainingBlockInfo based on the
// provided |containing_block_fragment|.
const NGOutOfFlowLayoutPart::ContainingBlockInfo
NGOutOfFlowLayoutPart::GetContainingBlockInfo(
const NGLogicalOutOfFlowPositionedNode& candidate,
const NGPhysicalContainerFragment* containing_block_fragment) {
if (candidate.containing_block_rect)
return {default_writing_direction_, *candidate.containing_block_rect};
if (candidate.inline_container) {
const auto it = containing_blocks_map_.find(candidate.inline_container);
DCHECK(it != containing_blocks_map_.end());
return it->value;
}
if (containing_block_fragment) {
DCHECK(container_builder_->IsBlockFragmentationContextRoot());
const LayoutObject* containing_block =
containing_block_fragment->GetLayoutObject();
DCHECK(containing_block);
auto it = containing_blocks_map_.find(containing_block);
if (it != containing_blocks_map_.end())
return it->value;
const auto writing_direction =
containing_block->StyleRef().GetWritingDirection();
LogicalSize size = containing_block_fragment->Size().ConvertToLogical(
writing_direction.GetWritingMode());
size.block_size =
LayoutBoxUtils::TotalBlockSize(*To<LayoutBox>(containing_block));
const NGPhysicalBoxFragment* fragment =
To<NGPhysicalBoxFragment>(containing_block_fragment);
// TODO(1079031): This should eventually include scrollbar and border.
NGBoxStrut border = fragment->Borders().ConvertToLogical(writing_direction);
LogicalSize content_size = ShrinkLogicalSize(size, border);
LogicalOffset container_offset =
LogicalOffset(border.inline_start, border.block_start);
container_offset += candidate.containing_block_offset;
ContainingBlockInfo containing_block_info{
writing_direction, LogicalRect(container_offset, content_size)};
return containing_blocks_map_
.insert(containing_block, containing_block_info)
.stored_value->value;
}
return candidate.node.Style().GetPosition() == EPosition::kAbsolute
? default_containing_block_info_for_absolute_
: default_containing_block_info_for_fixed_;
}
void NGOutOfFlowLayoutPart::ComputeInlineContainingBlocks(
const Vector<NGLogicalOutOfFlowPositionedNode>& candidates) {
NGBoxFragmentBuilder::InlineContainingBlockMap inline_container_fragments;
for (auto& candidate : candidates) {
if (candidate.inline_container &&
!inline_container_fragments.Contains(candidate.inline_container)) {
NGBoxFragmentBuilder::InlineContainingBlockGeometry inline_geometry = {};
inline_container_fragments.insert(candidate.inline_container,
inline_geometry);
}
}
// Fetch the inline start/end fragment geometry.
container_builder_->ComputeInlineContainerGeometry(
&inline_container_fragments);
LogicalSize container_builder_size = container_builder_->Size();
PhysicalSize container_builder_physical_size =
ToPhysicalSize(container_builder_size, writing_mode_);
// Transform the start/end fragments into a ContainingBlockInfo.
for (auto& block_info : inline_container_fragments) {
DCHECK(block_info.value.has_value());
// The calculation below determines the size of the inline containing block
// rect.
//
// To perform this calculation we:
// 1. Determine the start_offset "^", this is at the logical-start (wrt.
// default containing block), of the start fragment rect.
// 2. Determine the end_offset "$", this is at the logical-end (wrt.
// default containing block), of the end fragment rect.
// 3. Determine the logical rectangle defined by these two offsets.
//
// Case 1a: Same direction, overlapping fragments.
// +---------------
// ---> |^*****-------->
// +*----*---------
// * *
// ------*----*+
// ----> *****$| --->
// ------------+
//
// Case 1b: Different direction, overlapping fragments.
// +---------------
// ---> ^******* <-----|
// *------*--------
// * *
// -----*------*
// |<-- *******$ --->
// ------------+
//
// Case 2a: Same direction, non-overlapping fragments.
// +--------
// ---------> |^ ----->
// +*-------
// *
// --------+ *
// ------->| $ --->
// --------+
//
// Case 2b: Same direction, non-overlapping fragments.
// +--------
// ---------> ^ <-----|
// *--------
// *
// --------+ *
// | <------ $ --->
// --------+
//
// Note in cases [1a, 2a] we need to account for the inline borders of the
// rectangles, where-as in [1b, 2b] we do not. This is handled by the
// is_same_direction check(s).
//
// Note in cases [2a, 2b] we don't allow a "negative" containing block size,
// we clamp negative sizes to zero.
const ComputedStyle* inline_cb_style = block_info.key->Style();
DCHECK(inline_cb_style);
const auto container_writing_direction =
default_containing_block_info_for_absolute_.writing_direction;
const auto inline_writing_direction =
inline_cb_style->GetWritingDirection();
NGBoxStrut inline_cb_borders = ComputeBordersForInline(*inline_cb_style);
DCHECK_EQ(container_writing_direction.GetWritingMode(),
inline_writing_direction.GetWritingMode());
bool is_same_direction =
container_writing_direction == inline_writing_direction;
// Step 1 - determine the start_offset.
const PhysicalRect& start_rect =
block_info.value->start_fragment_union_rect;
LogicalOffset start_offset = start_rect.offset.ConvertToLogical(
container_writing_direction, container_builder_physical_size,
start_rect.size);
// Make sure we add the inline borders, we don't need to do this in the
// inline direction if the blocks are in opposite directions.
start_offset.block_offset += inline_cb_borders.block_start;
if (is_same_direction)
start_offset.inline_offset += inline_cb_borders.inline_start;
// Step 2 - determine the end_offset.
const PhysicalRect& end_rect = block_info.value->end_fragment_union_rect;
LogicalOffset end_offset = end_rect.offset.ConvertToLogical(
container_writing_direction, container_builder_physical_size,
end_rect.size);
// Add in the size of the fragment to get the logical end of the fragment.
end_offset += end_rect.size.ConvertToLogical(
container_writing_direction.GetWritingMode());
// Make sure we subtract the inline borders, we don't need to do this in the
// inline direction if the blocks are in opposite directions.
end_offset.block_offset -= inline_cb_borders.block_end;
if (is_same_direction)
end_offset.inline_offset -= inline_cb_borders.inline_end;
// Make sure we don't end up with a rectangle with "negative" size.
end_offset.inline_offset =
std::max(end_offset.inline_offset, start_offset.inline_offset);
end_offset.block_offset =
std::max(end_offset.block_offset, start_offset.block_offset);
// Step 3 - determine the logical rectangle.
// Determine the logical size of the containing block.
LogicalSize inline_cb_size = {
end_offset.inline_offset - start_offset.inline_offset,
end_offset.block_offset - start_offset.block_offset};
DCHECK_GE(inline_cb_size.inline_size, LayoutUnit());
DCHECK_GE(inline_cb_size.block_size, LayoutUnit());
// Set the container padding-box offset.
LogicalOffset container_offset = start_offset;
containing_blocks_map_.insert(
block_info.key,
ContainingBlockInfo{inline_writing_direction,
LogicalRect(container_offset, inline_cb_size)});
}
}
void NGOutOfFlowLayoutPart::LayoutCandidates(
Vector<NGLogicalOutOfFlowPositionedNode>* candidates,
const LayoutBox* only_layout,
HashSet<const LayoutObject*>* placed_objects) {
while (candidates->size() > 0) {
ComputeInlineContainingBlocks(*candidates);
for (auto& candidate : *candidates) {
const LayoutBox* layout_box = candidate.node.GetLayoutBox();
SaveStaticPositionOnPaintLayer(layout_box,
container_builder_->GetLayoutObject(),
candidate.static_position);
if (IsContainingBlockForCandidate(candidate) &&
(!only_layout || layout_box == only_layout)) {
if (has_block_fragmentation_) {
// If the containing block is fragmented, adjust the offset to be from
// the first containing block fragment to the fragmentation context
// root. Also, adjust the static position to be relative to the
// adjusted containing block offset.
if (const auto* previous_break_token =
container_builder_->PreviousBreakToken()) {
LayoutUnit previous_consumed_block_size =
previous_break_token->ConsumedBlockSize();
candidate.containing_block_offset.block_offset -=
previous_consumed_block_size;
candidate.static_position.offset.block_offset +=
previous_consumed_block_size;
}
container_builder_->AddOutOfFlowFragmentainerDescendant(candidate);
continue;
}
scoped_refptr<const NGLayoutResult> result =
LayoutCandidate(candidate, only_layout);
container_builder_->AddChild(result->PhysicalFragment(),
result->OutOfFlowPositionedOffset(),
candidate.inline_container);
placed_objects->insert(candidate.node.GetLayoutBox());
if (layout_box != only_layout)
candidate.node.UseLegacyOutOfFlowPositioning();
} else {
container_builder_->AddOutOfFlowDescendant(candidate);
}
}
// Sweep any candidates that might have been added.
// This happens when an absolute container has a fixed child.
candidates->Shrink(0);
container_builder_->SwapOutOfFlowPositionedCandidates(candidates);
}
}
scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::LayoutCandidate(
const NGLogicalOutOfFlowPositionedNode& candidate,
const LayoutBox* only_layout) {
NGBlockNode node = candidate.node;
// "NGOutOfFlowLayoutPart container is ContainingBlock" invariant cannot
// be enforced for tables. Tables are special, in that the ContainingBlock is
// TABLE, but constraint space is generated by TBODY/TR/. This happens
// because TBODY/TR are not LayoutBlocks, but LayoutBoxModelObjects.
DCHECK((container_builder_->GetLayoutObject() ==
node.GetLayoutBox()->ContainingBlock()) ||
node.GetLayoutBox()->ContainingBlock()->IsTable());
const ContainingBlockInfo container_info = GetContainingBlockInfo(candidate);
const ComputedStyle& candidate_style = node.Style();
const auto candidate_writing_direction =
candidate_style.GetWritingDirection();
LogicalSize container_content_size = container_info.rect.size;
PhysicalSize container_physical_content_size =
ToPhysicalSize(container_content_size, writing_mode_);
// Determine if we need to actually run the full OOF-positioned sizing, and
// positioning algorithm.
//
// The first-tier cache compares the given available-size. However we can't
// reuse the result if the |ContainingBlockInfo::container_offset| may change.
// This can occur when:
// - The default containing-block has borders and/or scrollbars.
// - The candidate has an inline container (instead of the default
// containing-block).
if (allow_first_tier_oof_cache_ && !candidate.inline_container) {
LogicalSize container_content_size_in_candidate_writing_mode =
container_physical_content_size.ConvertToLogical(
candidate_writing_direction.GetWritingMode());
if (scoped_refptr<const NGLayoutResult> cached_result =
node.CachedLayoutResultForOutOfFlowPositioned(
container_content_size_in_candidate_writing_mode))
return cached_result;
}
// Adjust the |static_position| (which is currently relative to the default
// container's border-box). ng_absolute_utils expects the static position to
// be relative to the container's padding-box.
NGLogicalStaticPosition static_position = candidate.static_position;
static_position.offset -= container_info.rect.offset;
NGLogicalStaticPosition candidate_static_position =
static_position
.ConvertToPhysical(
{default_writing_direction_, container_physical_content_size})
.ConvertToLogical(
{candidate_writing_direction, container_physical_content_size});
// Need a constraint space to resolve offsets.
NGConstraintSpaceBuilder builder(writing_mode_, candidate_writing_direction,
/* is_new_fc */ true);
builder.SetAvailableSize(container_content_size);
builder.SetPercentageResolutionSize(container_content_size);
NGConstraintSpace candidate_constraint_space = builder.ToConstraintSpace();
base::Optional<PaintLayerScrollableArea::FreezeScrollbarsScope>
freeze_scrollbars;
do {
scoped_refptr<const NGLayoutResult> layout_result =
Layout(node, candidate_constraint_space, candidate_static_position,
container_physical_content_size, container_info,
default_writing_direction_, only_layout);
if (!freeze_scrollbars.has_value()) {
// Since out-of-flow positioning sets up a constraint space with fixed
// inline-size, the regular layout code (|NGBlockNode::Layout()|) cannot
// re-layout if it discovers that a scrollbar was added or removed. Handle
// that situation here. The assumption is that if intrinsic logical widths
// are dirty after layout, AND its inline-size depends on the intrinsic
// logical widths, it means that scrollbars appeared or disappeared. We
// have the same logic in legacy layout in
// |LayoutBlockFlow::UpdateBlockLayout()|.
if (node.GetLayoutBox()->IntrinsicLogicalWidthsDirty() &&
AbsoluteNeedsChildInlineSize(node)) {
// Freeze the scrollbars for this layout pass. We don't want them to
// change *again*.
freeze_scrollbars.emplace();
continue;
}
}
return layout_result;
} while (true);
}
// TODO(almaher): Look into moving this to NGColumnLayoutAlgorithm instead.
void NGOutOfFlowLayoutPart::LayoutOOFsInMulticol(const NGBlockNode& multicol) {
Vector<NGLogicalOutOfFlowPositionedNode> oof_nodes_to_layout;
Vector<MulticolChildInfo> multicol_children;
const NGBlockBreakToken* previous_column_break_token = nullptr;
LayoutUnit column_inline_progression = kIndefiniteSize;
NGConstraintSpace multicol_constraint_space =
CreateConstraintSpaceForMulticol(multicol);
NGFragmentGeometry fragment_geometry =
CalculateInitialFragmentGeometry(multicol_constraint_space, multicol);
NGBoxFragmentBuilder multicol_container_builder =
CreateContainerBuilderForMulticol(multicol, multicol_constraint_space,
fragment_geometry);
// Accumulate all of the pending OOF positioned nodes that are stored inside
// |multicol|.
for (auto& multicol_fragment : multicol.GetLayoutBox()->PhysicalFragments()) {
const NGPhysicalBoxFragment* multicol_box_fragment =
To<NGPhysicalBoxFragment>(&multicol_fragment);
const ComputedStyle& style = multicol_box_fragment->Style();
WritingDirectionMode writing_direction = style.GetWritingDirection();
const WritingModeConverter converter(writing_direction,
multicol_box_fragment->Size());
const NGBlockBreakToken* current_column_break_token =
previous_column_break_token;
wtf_size_t current_column_index = 0;
if (column_inline_progression == kIndefiniteSize) {
// TODO(almaher): This should eventually include scrollbar, as well.
NGBoxStrut border_padding =
multicol_box_fragment->Borders().ConvertToLogical(writing_direction) +
multicol_box_fragment->Padding().ConvertToLogical(writing_direction);
LayoutUnit available_inline_size =
multicol_box_fragment->Size()
.ConvertToLogical(writing_direction.GetWritingMode())
.inline_size -
border_padding.InlineSum();
column_inline_progression =
ColumnInlineProgression(available_inline_size, style);
}
// Collect the children of the multicol fragments.
for (auto& child :
multicol_box_fragment->GetMutableChildrenForOutOfFlow().Children()) {
const auto* fragment = To<NGPhysicalContainerFragment>(child.get());
LogicalOffset offset =
converter.ToLogical(child.Offset(), fragment->Size());
if (fragment->IsFragmentainerBox()) {
current_column_break_token =
To<NGBlockBreakToken>(fragment->BreakToken());
current_column_index = multicol_children.size();
}
multicol_container_builder.AddChild(*fragment, offset);
multicol_children.emplace_back(MulticolChildInfo(&child));
}
// If a column fragment is updated with OOF children, we may need to update
// the reference to its break token in its parent's break token. There
// should be at most one column break token per parent break token
// (representing the last column laid out in that fragment). Thus, search
// for |current_column_break_token| in |multicol_box_fragment|'s list of
// child break tokens and update the stored MulticolChildInfo if found.
const NGBlockBreakToken* break_token =
To<NGBlockBreakToken>(multicol_box_fragment->BreakToken());
if (break_token && break_token->ChildBreakTokens().size()) {
// If there is a column break token, it will be the last item in its
// parent's list of break tokens.
const auto children = break_token->ChildBreakTokens();
const NGBlockBreakToken* child_token =
To<NGBlockBreakToken>(children[children.size() - 1]);
if (child_token == current_column_break_token) {
MulticolChildInfo& child_info = multicol_children[current_column_index];
child_info.parent_break_token = break_token;
}
}
// Convert the OOF fragmentainer descendants to the logical coordinate space
// and store the resulting nodes inside |oof_nodes_to_layout|.
for (const auto& descendant :
multicol_box_fragment->OutOfFlowPositionedFragmentainerDescendants()) {
const NGPhysicalContainerFragment* containing_block_fragment =
descendant.containing_block_fragment.get();
LogicalOffset containing_block_offset =
converter.ToLogical(descendant.containing_block_offset,
containing_block_fragment->Size());
// The containing block offset should be the offset from the top of the
// inner multicol to the start of the containing block (as if all of the
// columns are placed one on top of the other). When propagating OOFs
// in a nested fragmentation context, we miss the block contribution
// from columns in previous outer fragmentainers. Add the block size
// for such columns here to account for this.
if (previous_column_break_token) {
containing_block_offset.block_offset +=
previous_column_break_token->ConsumedBlockSize();
}
// The static position should remain relative to its containing block
// fragment.
const WritingModeConverter containing_block_converter(
writing_direction, containing_block_fragment->Size());
NGLogicalStaticPosition static_position =
descendant.static_position.ConvertToLogical(
containing_block_converter);
NGLogicalOutOfFlowPositionedNode node = {
descendant.node,
static_position,
descendant.inline_container,
/* needs_block_offset_adjustment */ false,
containing_block_offset,
containing_block_fragment};
oof_nodes_to_layout.push_back(node);
}
previous_column_break_token = current_column_break_token;
}
DCHECK(!oof_nodes_to_layout.IsEmpty());
// Clear out any OOF fragmentainer descendants that had been re-propagated
// when setting up |multicol_container_builder|.
// TODO(almaher): Avoid adding the descendants again to begin with.
multicol_container_builder.ClearOutOfFlowFragmentainerDescendants();
// Layout the OOF positioned elements inside the inner multicol.
NGOutOfFlowLayoutPart(multicol, multicol_constraint_space,
&multicol_container_builder)
.LayoutFragmentainerDescendants(
&oof_nodes_to_layout, column_inline_progression, &multicol_children);
}
void NGOutOfFlowLayoutPart::LayoutFragmentainerDescendants(
Vector<NGLogicalOutOfFlowPositionedNode>* descendants,
LayoutUnit column_inline_progression,
Vector<MulticolChildInfo>* multicol_children) {
nested_fragmentation_context_ = multicol_children;
original_column_block_size_ =
ShrinkLogicalSize(container_builder_->InitialBorderBoxSize(),
container_builder_->BorderScrollbarPadding())
.block_size;
while (descendants->size() > 0) {
for (auto& descendant : *descendants) {
scoped_refptr<const NGLayoutResult> result =
LayoutFragmentainerDescendant(descendant);
// TODO(almaher): Handle nested OOFs and inner multicols with pending OOFs
// in the case of nested fragmentation.
container_builder_->PropagateOOFPositionedInfo(
result->PhysicalFragment(), result->OutOfFlowPositionedOffset());
}
// Sweep any descendants that might have been bubbled up from the fragment
// to the |container_builder_|. This happens when we have nested absolute
// position elements.
descendants->Shrink(0);
container_builder_->SwapOutOfFlowFragmentainerDescendants(descendants);
}
// Add all of the descendant layout results as children to the fragment at
// the associated index.
wtf_size_t index = 0;
while (!fragmentainer_descendant_results_.IsEmpty()) {
// We don't allow keys of 0, so shift the index by 1.
auto it = fragmentainer_descendant_results_.find(index + 1);
if (it != fragmentainer_descendant_results_.end()) {
Vector<scoped_refptr<const NGLayoutResult>>& results = it->value;
AddOOFResultsToFragmentainer(results, index, column_inline_progression,
multicol_children);
fragmentainer_descendant_results_.erase(it);
}
index++;
}
}
scoped_refptr<const NGLayoutResult>
NGOutOfFlowLayoutPart::LayoutFragmentainerDescendant(
const NGLogicalOutOfFlowPositionedNode& descendant) {
NGBlockNode node = descendant.node;
const NGPhysicalContainerFragment* containing_block_fragment =
descendant.containing_block_fragment.get();
DCHECK(containing_block_fragment &&
containing_block_fragment->GetLayoutObject() ==
node.GetLayoutBox()->ContainingBlock());
const ContainingBlockInfo container_info =
GetContainingBlockInfo(descendant, containing_block_fragment);
const auto default_writing_direction =
containing_block_fragment->Style().GetWritingDirection();
const ComputedStyle& descendant_style = node.Style();
const auto descendant_writing_direction =
descendant_style.GetWritingDirection();
LogicalSize container_content_size = container_info.rect.size;
PhysicalSize container_physical_content_size = ToPhysicalSize(
container_content_size, default_writing_direction.GetWritingMode());
// Adjust the |static_position| (which is currently relative to the default
// container's border-box). ng_absolute_utils expects the static position to
// be relative to the container's padding-box. Since
// |container_info.rect.offset| is relative to its fragmentainer in this
// case, we also need to adjust the offset to account for this.
NGLogicalStaticPosition static_position = descendant.static_position;
static_position.offset -=
container_info.rect.offset - descendant.containing_block_offset;
NGLogicalStaticPosition descendant_static_position =
static_position
.ConvertToPhysical(
{default_writing_direction, container_physical_content_size})
.ConvertToLogical(
{descendant_writing_direction, container_physical_content_size});
// Need a constraint space to resolve offsets.
NGConstraintSpaceBuilder builder(default_writing_direction.GetWritingMode(),
descendant_writing_direction,
/* is_new_fc */ true);
builder.SetAvailableSize(container_content_size);
builder.SetPercentageResolutionSize(container_content_size);
NGConstraintSpace descendant_constraint_space = builder.ToConstraintSpace();
return Layout(node, descendant_constraint_space, descendant_static_position,
container_physical_content_size, container_info,
default_writing_direction, /* only_layout */ nullptr,
/* is_fragmentainer_descendant */ true);
}
scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::Layout(
NGBlockNode node,
const NGConstraintSpace& candidate_constraint_space,
const NGLogicalStaticPosition& candidate_static_position,
PhysicalSize container_physical_content_size,
const ContainingBlockInfo& container_info,
const WritingDirectionMode default_writing_direction,
const LayoutBox* only_layout,
bool is_fragmentainer_descendant) {
const ComputedStyle& candidate_style = node.Style();
const WritingDirectionMode candidate_writing_direction =
candidate_style.GetWritingDirection();
const auto container_writing_direction = container_info.writing_direction;
LogicalSize container_content_size_in_candidate_writing_mode =
container_physical_content_size.ConvertToLogical(
candidate_writing_direction.GetWritingMode());
NGBoxStrut border_padding =
ComputeBorders(candidate_constraint_space, node) +
ComputePadding(candidate_constraint_space, candidate_style);
// The |block_estimate| is wrt. the candidate's writing mode.
base::Optional<LayoutUnit> block_estimate;
base::Optional<MinMaxSizes> min_max_sizes;
scoped_refptr<const NGLayoutResult> layout_result = nullptr;
// In order to calculate the offsets, we may need to know the size.
// In some cases we will need the fragment size in order to calculate the
// offset. We may have to lay out to get the fragment size. For block
// fragmentation, we *need* to know the block-offset before layout. In other
// words, in that case, we may have to lay out, calculate the offset, and
// then lay out again at the correct block-offset.
NGLogicalOutOfFlowDimensions node_dimensions;
bool has_computed_block_dimensions = false;
bool is_replaced = node.IsReplaced();
bool should_be_considered_as_replaced = node.ShouldBeConsideredAsReplaced();
bool absolute_needs_child_block_size = AbsoluteNeedsChildBlockSize(node);
base::Optional<MinMaxSizes> minmax_intrinsic_sizes_for_ar;
// We also include items with aspect ratio here, because if the inline size
// is auto and we have a definite block size, we want to use that for the
// inline size calculation.
bool compute_inline_from_ar =
IsInlineSizeComputableFromBlockSize(node) && !is_replaced;
if (AbsoluteNeedsChildInlineSize(node) || NeedMinMaxSize(candidate_style) ||
should_be_considered_as_replaced || compute_inline_from_ar) {
MinMaxSizesInput input(kIndefiniteSize, MinMaxSizesType::kContent);
if (is_replaced) {
input.percentage_resolution_block_size =
container_content_size_in_candidate_writing_mode.block_size;
} else if (!absolute_needs_child_block_size) {
// If we can determine our block-size ahead of time (it doesn't depend on
// our content), we use this for our %-block-size.
ComputeOutOfFlowBlockDimensions(
node, candidate_constraint_space, border_padding,
candidate_static_position, base::nullopt, base::nullopt,
container_writing_direction, &node_dimensions);
has_computed_block_dimensions = true;
input.percentage_resolution_block_size = node_dimensions.size.block_size;
}
if (compute_inline_from_ar &&
candidate_style.OverflowInlineDirection() == EOverflow::kVisible) {
MinMaxSizesInput intrinsic_input(input);
intrinsic_input.type = MinMaxSizesType::kIntrinsic;
minmax_intrinsic_sizes_for_ar =
node.ComputeMinMaxSizes(candidate_writing_direction.GetWritingMode(),
intrinsic_input, &candidate_constraint_space)
.sizes;
}
min_max_sizes =
node.ComputeMinMaxSizes(candidate_writing_direction.GetWritingMode(),
input, &candidate_constraint_space)
.sizes;
}
base::Optional<LogicalSize> replaced_size;
base::Optional<LogicalSize> aspect_ratio;
bool has_aspect_ratio_without_intrinsic_size = false;
if (is_replaced) {
ComputeReplacedSize(node, candidate_constraint_space, min_max_sizes,
&replaced_size, &aspect_ratio);
DCHECK(replaced_size.has_value() != aspect_ratio.has_value());
has_aspect_ratio_without_intrinsic_size = aspect_ratio.has_value();
// If we only have aspect ratio, and no replaced size, intrinsic size
// defaults to 300x150. min_max_sizes gets computed from the intrinsic size.
// We reset the min_max_sizes because spec says that OOF-positioned size
// should not be constrained by intrinsic size in this case.
// https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width
if (has_aspect_ratio_without_intrinsic_size) {
min_max_sizes = MinMaxSizes{LayoutUnit(), LayoutUnit::NearlyMax()};
DCHECK(!aspect_ratio->IsEmpty()) << *aspect_ratio;
}
} else if (!candidate_style.AspectRatio().IsAuto()) {
has_aspect_ratio_without_intrinsic_size = true;
aspect_ratio = node.GetAspectRatio();
} else if (should_be_considered_as_replaced) {
replaced_size =
LogicalSize{min_max_sizes->ShrinkToFit(
candidate_constraint_space.AvailableSize().inline_size),
kIndefiniteSize};
}
ComputeOutOfFlowInlineDimensions(
node, candidate_constraint_space, border_padding,
candidate_static_position, min_max_sizes, minmax_intrinsic_sizes_for_ar,
replaced_size, container_writing_direction, &node_dimensions);
// |should_be_considered_as_replaced| sets the inline-size.
// It does not set the block-size. This is a compatibility quirk.
if (!is_replaced && should_be_considered_as_replaced)
replaced_size.reset();
// Elements with only aspect ratio compute their block size from
// inline size and aspect ratio.
// https://www.w3.org/TR/css-sizing-3/#intrinsic-sizes
if (has_aspect_ratio_without_intrinsic_size) {
// If this came from an aspect-ratio property, we need to respect
// box-sizing.
EAspectRatioType ar_type = candidate_style.AspectRatio().GetType();
EBoxSizing sizing =
(ar_type == EAspectRatioType::kRatio ||
(ar_type == EAspectRatioType::kAutoAndRatio && !is_replaced))
? candidate_style.BoxSizing()
: EBoxSizing::kContentBox;
replaced_size = LogicalSize(
node_dimensions.size.inline_size,
BlockSizeFromAspectRatio(border_padding, *aspect_ratio, sizing,
node_dimensions.size.inline_size));
}
if (absolute_needs_child_block_size) {
DCHECK(!has_computed_block_dimensions);
layout_result = GenerateFragment(
node, container_content_size_in_candidate_writing_mode, block_estimate,
node_dimensions, /* block_offset */ LayoutUnit(),
/* break_token */ nullptr,
/* fragmentainer_constraint_space */ nullptr,
/* should_use_fixed_block_size */ false);
// TODO(layout-dev): Handle abortions caused by block fragmentation.
DCHECK(layout_result->Status() != NGLayoutResult::kOutOfFragmentainerSpace);
NGFragment fragment(candidate_writing_direction,
layout_result->PhysicalFragment());
block_estimate = fragment.BlockSize();
}
// We may have already pre-computed our block-dimensions when determining
// our |min_max_sizes|, only run if needed.
if (!has_computed_block_dimensions) {
ComputeOutOfFlowBlockDimensions(
node, candidate_constraint_space, border_padding,
candidate_static_position, block_estimate, replaced_size,
container_writing_direction, &node_dimensions);
has_computed_block_dimensions = true;
}
block_estimate = node_dimensions.size.block_size;
// Calculate the offsets.
NGBoxStrut inset =
node_dimensions.inset.ConvertToPhysical(candidate_writing_direction)
.ConvertToLogical(default_writing_direction);
// |inset| is relative to the container's padding-box. Convert this to being
// relative to the default container's border-box.
LogicalOffset offset = container_info.rect.offset;
offset.inline_offset += inset.inline_start;
offset.block_offset += inset.block_start;
// Determine in which fragmentainer this OOF element will start its layout and
// adjust the offset to be relative to that fragmentainer.
wtf_size_t start_index = 0;
wtf_size_t num_children = container_builder_->Children().size();
if (is_fragmentainer_descendant) {
DCHECK_GT(num_children, 0u);
ComputeStartFragmentIndexAndRelativeOffset(
container_info, default_writing_direction.GetWritingMode(),
*block_estimate, &start_index, &offset);
}
if (!only_layout && !can_traverse_fragments_) {
// Special case: oof css container is a split inline.
// When css container spans multiple anonymous blocks, its dimensions can
// only be computed by a block that is an ancestor of all fragments
// generated by css container. That block is parent of anonymous
// containing block. That is why instead of OOF being placed by its
// anonymous container, they get placed by anonymous container's parent.
// This is different from all other OOF blocks, and requires special
// handling in several places in the OOF code.
// There is an exception to special case: if anonymous block is Legacy, we
// cannot do the fancy multiple anonymous block traversal, and we handle
// it like regular blocks.
//
// Detailed example:
//
// If Layout tree looks like this:
// LayoutNGBlockFlow#container
// LayoutNGBlockFlow (anonymous#1)
// LayoutInline#1 (relative)
// LayoutNGBlockFlow (anonymous#2 relative)
// LayoutNGBlockFlow#oof (positioned)
// LayoutNGBlockFlow (anonymous#3)
// LayoutInline#3 (continuation)
//
// The containing block geometry is defined by split inlines,
// LayoutInline#1, LayoutInline#3.
// Css container anonymous#2 does not have information needed
// to compute containing block geometry.
// Therefore, #oof cannot be placed by anonymous#2. NG handles this case
// by placing #oof in parent of anonymous (#container).
//
// But, PaintPropertyTreeBuilder expects #oof.Location() to be wrt css
// container, #anonymous2. This is why the code below adjusts the legacy
// offset from being wrt #container to being wrt #anonymous2.
const LayoutObject* container = node.GetLayoutBox()->Container();
if (container->IsAnonymousBlock()) {
LogicalOffset container_offset =
container_builder_->GetChildOffset(container);
offset -= container_offset;
} else if (container->IsLayoutInline() &&
container->ContainingBlock()->IsAnonymousBlock()) {
// Location of OOF with inline container, and anonymous containing block
// is wrt container.
LogicalOffset container_offset =
container_builder_->GetChildOffset(container->ContainingBlock());
offset -= container_offset;
}
}
// Reset the |layout_result| computed earlier to allow fragmentation in the
// next layout pass, if needed.
if (is_fragmentainer_descendant)
layout_result = nullptr;
const NGBlockBreakToken* break_token = nullptr;
do {
if (break_token) {
layout_result = nullptr;
start_index++;
// Skip over spanning fragments when finding the next fragmentainer to add
// an OOF result to.
while (start_index < num_children &&
!container_builder_->Children()[start_index]
.fragment->IsFragmentainerBox()) {
start_index++;
}
offset.block_offset = LayoutUnit();
}
// Skip this step if we produced a fragment that can be reused when
// estimating the block-size.
if (!layout_result) {
const NGConstraintSpace* fragmentainer_constraint_space = nullptr;
bool should_use_fixed_block_size = !!block_estimate;
if (is_fragmentainer_descendant) {
fragmentainer_constraint_space =
&GetFragmentainerConstraintSpace(start_index);
should_use_fixed_block_size &= !absolute_needs_child_block_size;
}
layout_result = GenerateFragment(
node, container_content_size_in_candidate_writing_mode,
block_estimate, node_dimensions, offset.block_offset, break_token,
fragmentainer_constraint_space, should_use_fixed_block_size);
}
// TODO(layout-dev): Handle abortions caused by block fragmentation.
DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess);
if (node.GetLayoutBox()->IsLayoutNGObject()) {
To<LayoutBlock>(node.GetLayoutBox())
->SetIsLegacyInitiatedOutOfFlowLayout(false);
}
// Legacy grid and flexbox handle OOF-positioned margins on their own, and
// break if we set them here.
if (!container_builder_->GetLayoutObject()
->Style()
->IsDisplayFlexibleOrGridBox()) {
node.GetLayoutBox()->SetMargin(node_dimensions.margins.ConvertToPhysical(
candidate_writing_direction));
}
layout_result->GetMutableForOutOfFlow().SetOutOfFlowPositionedOffset(
offset, allow_first_tier_oof_cache_);
if (is_fragmentainer_descendant) {
AddOOFResultToFragmentainerResults(layout_result, start_index);
const auto& physical_fragment =
To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment());
break_token = To<NGBlockBreakToken>(physical_fragment.BreakToken());
}
} while (break_token);
return layout_result;
}
bool NGOutOfFlowLayoutPart::IsContainingBlockForCandidate(
const NGLogicalOutOfFlowPositionedNode& candidate) {
// Column boxes are not allowed to be containing blocks.
if (container_builder_->IsFragmentainerBoxType())
return false;
EPosition position = candidate.node.Style().GetPosition();
// Candidates whose containing block is inline are always positioned inside
// closest parent block flow.
if (candidate.inline_container) {
DCHECK(candidate.inline_container->CanContainOutOfFlowPositionedElement(
candidate.node.Style().GetPosition()));
return container_builder_->GetLayoutObject() ==
candidate.node.GetLayoutBox()->ContainingBlock();
}
return (is_absolute_container_ && position == EPosition::kAbsolute) ||
(is_fixed_container_ && position == EPosition::kFixed);
}
// The fragment is generated in one of these two scenarios:
// 1. To estimate candidate's block size, in this case block_size is
// container's available size.
// 2. To compute final fragment, when block size is known from the absolute
// position calculation.
scoped_refptr<const NGLayoutResult> NGOutOfFlowLayoutPart::GenerateFragment(
NGBlockNode node,
const LogicalSize& container_content_size_in_candidate_writing_mode,
const base::Optional<LayoutUnit>& block_estimate,
const NGLogicalOutOfFlowDimensions& node_dimensions,
const LayoutUnit block_offset,
const NGBlockBreakToken* break_token,
const NGConstraintSpace* fragmentainer_constraint_space,
bool should_use_fixed_block_size) {
const auto& style = node.Style();
LayoutUnit inline_size = node_dimensions.size.inline_size;
LayoutUnit block_size = block_estimate.value_or(
container_content_size_in_candidate_writing_mode.block_size);
LogicalSize available_size(inline_size, block_size);
// As the |block_estimate| is always in the node's writing mode, we build the
// constraint space in the node's writing mode.
NGConstraintSpaceBuilder builder(style.GetWritingMode(),
style.GetWritingDirection(),
/* is_new_fc */ true);
builder.SetAvailableSize(available_size);
builder.SetPercentageResolutionSize(
container_content_size_in_candidate_writing_mode);
builder.SetIsFixedInlineSize(true);
if (should_use_fixed_block_size)
builder.SetIsFixedBlockSize(true);
if (fragmentainer_constraint_space) {
SetupSpaceBuilderForFragmentation(*fragmentainer_constraint_space, node,
block_offset, &builder,
/* is_new_fc */ true);
}
NGConstraintSpace space = builder.ToConstraintSpace();
return node.Layout(space, break_token);
}
void NGOutOfFlowLayoutPart::AddOOFResultsToFragmentainer(
const Vector<scoped_refptr<const NGLayoutResult>>& results,
wtf_size_t index,
LayoutUnit column_inline_progression,
Vector<MulticolChildInfo>* multicol_children) {
wtf_size_t num_children = container_builder_->Children().size();
bool is_new_fragment = index >= num_children;
// If an OOF positioned element is in a nested context, and it fragments
// beyond the last fragmentainer, we don't create a new column for it.
// Rather, we will add it to the last existing fragmentainter at the
// correct inline offset.
bool create_new_fragment = is_new_fragment && !nested_fragmentation_context_;
bool add_to_last_fragment = is_new_fragment && nested_fragmentation_context_;
wtf_size_t num_new_fragmentainers;
if (is_new_fragment)
num_new_fragmentainers = index - num_children + 1;
// If |index| is greater than the number of current children, we need to add
// empty column fragments at all of the indexes leading up to |index|.
if (create_new_fragment) {
const Vector<scoped_refptr<const NGLayoutResult>> empty_results;
while (index > num_children) {
AddOOFResultsToFragmentainer(empty_results, /*index */ num_children,
column_inline_progression);
num_children++;
}
DCHECK_EQ(index, container_builder_->Children().size());
}
const NGConstraintSpace& space = GetFragmentainerConstraintSpace(index);
// If we are a new fragment, find a non-spanner fragmentainer as a basis.
while (
index >= num_children ||
!container_builder_->Children()[index].fragment->IsFragmentainerBox()) {
DCHECK_GT(num_children, 0u);
index--;
}
const auto& fragmentainer = container_builder_->Children()[index];
DCHECK(fragmentainer.fragment->IsFragmentainerBox());
const NGBlockNode& node = container_builder_->Node();
const auto& fragment =
To<NGPhysicalBoxFragment>(*fragmentainer.fragment.get());
// Grab the previous fragment's break token. We can't just use the current
// fragment's break token sequence, since there might not be one if we're
// adding a new fragment.
const NGBreakToken* previous_break_token =
(index == 0)
? nullptr
: To<NGPhysicalBoxFragment>(
container_builder_->Children()[index - 1].fragment.get())
->BreakToken();
NGFragmentGeometry fragment_geometry =
CalculateInitialFragmentGeometry(space, node);
NGLayoutAlgorithmParams params(node, fragment_geometry, space,
To<NGBlockBreakToken>(previous_break_token),
/* early_break */ nullptr);
// |algorithm| corresponds to the "mutable copy" of our original
// fragmentainer. As long as this "copy" hasn't been laid out via
// NGSimplifiedOOFLayoutAlgorithm::Layout, we can append new items to it.
NGSimplifiedOOFLayoutAlgorithm algorithm(params, fragment,
create_new_fragment);
for (const auto& result : results) {
// If we are adding the result to the last fragmentainer rather than
// creating a new fragmentainer to hold it, adjust the inline offset as if
// we had created a new fragmentainer.
if (add_to_last_fragment) {
LogicalOffset oof_offset = result->OutOfFlowPositionedOffset();
oof_offset.inline_offset +=
column_inline_progression * num_new_fragmentainers;
result->GetMutableForOutOfFlow().SetOutOfFlowPositionedOffset(
oof_offset, allow_first_tier_oof_cache_);
}
algorithm.AppendOutOfFlowResult(result);
}
if (create_new_fragment) {
LogicalOffset offset;
if (index != num_children - 1 && !container_builder_->Children()[index + 1]
.fragment->IsFragmentainerBox()) {
// If we are a new fragment and are separated from other columns by a
// spanner, compute the correct column offset to use.
const auto& spanner = container_builder_->Children()[index + 1];
DCHECK(spanner.fragment->IsColumnSpanAll());
offset = spanner.offset;
LogicalSize spanner_size = spanner.fragment->Size().ConvertToLogical(
container_builder_->Style().GetWritingMode());
// TODO(almaher): Include trailing spanner margin.
offset.block_offset += spanner_size.block_size;
} else {
offset = fragmentainer.offset;
offset.inline_offset += column_inline_progression;
}
scoped_refptr<const NGLayoutResult> new_result = algorithm.Layout();
node.AddColumnResult(new_result);
container_builder_->AddChild(new_result->PhysicalFragment(), offset);
} else {
scoped_refptr<const NGLayoutResult> new_result = algorithm.Layout();
node.ReplaceColumnResult(new_result, fragment);
const NGPhysicalContainerFragment* new_fragment =
&new_result->PhysicalFragment();
container_builder_->ReplaceChild(index, *new_fragment,
fragmentainer.offset);
if (nested_fragmentation_context_) {
// We are in a nested fragmentation context. Replace the column entry
// and break token directly in the existing multicol fragment.
DCHECK(multicol_children);
MulticolChildInfo& column_info = (*multicol_children)[index];
if (auto* parent_break_token = column_info.parent_break_token) {
DCHECK_GT(parent_break_token->ChildBreakTokens().size(), 0u);
parent_break_token->GetMutableForOutOfFlow().ReplaceChildBreakToken(
new_fragment->BreakToken(),
parent_break_token->ChildBreakTokens().size() - 1);
}
column_info.mutable_link->fragment->Release();
new (&column_info.mutable_link->fragment)
scoped_refptr<const NGPhysicalFragment>(std::move(new_fragment));
}
}
}
const NGConstraintSpace& NGOutOfFlowLayoutPart::GetFragmentainerConstraintSpace(
wtf_size_t index) {
// Increase the index by 1 to avoid a key of 0.
wtf_size_t stored_index = index + 1;
auto it = fragmentainer_constraint_space_map_.find(stored_index);
if (it != fragmentainer_constraint_space_map_.end())
return it->value;
wtf_size_t num_children = container_builder_->Children().size();
bool is_new_fragment = index >= num_children;
// Allow margins to be discarded if this is not the first column in the
// multicol container, and we're not right after a spanner.
//
// TODO(layout-dev): This check is incorrect in nested multicol. If the
// previous outer fragmentainer ended with regular column content (i.e. not a
// spanner), and this is the first column in the next outer fragmentainer, we
// should still discard margins, since there is no explicit break involved.
bool allow_discard_start_margin =
is_new_fragment || (index > 0 && container_builder_->Children()[index - 1]
.fragment->IsFragmentainerBox());
// If we are a new fragment, find a non-spanner fragmentainer to base our
// constraint space off of.
while (
index >= num_children ||
!container_builder_->Children()[index].fragment->IsFragmentainerBox()) {
DCHECK_GT(num_children, 0u);
index--;
}
const auto& fragmentainer = container_builder_->Children()[index];
DCHECK(fragmentainer.fragment->IsFragmentainerBox());
const auto& fragment =
To<NGPhysicalBoxFragment>(*fragmentainer.fragment.get());
const WritingMode container_writing_mode =
container_builder_->Style().GetWritingMode();
LogicalSize column_size =
fragment.Size().ConvertToLogical(container_writing_mode);
// If we are a new fragment and are separated from other columns by a
// spanner, compute the correct column block size to use.
if (is_new_fragment && !nested_fragmentation_context_ &&
index != num_children - 1 &&
original_column_block_size_ != kIndefiniteSize &&
!container_builder_->Children()[index + 1]
.fragment->IsFragmentainerBox()) {
column_size.block_size =
original_column_block_size_ -
container_builder_->BlockOffsetForAdditionalColumns();
column_size.block_size = column_size.block_size.ClampNegativeToZero();
}
LogicalSize percentage_resolution_size =
LogicalSize(column_size.inline_size,
container_builder_->ChildAvailableSize().block_size);
// TODO(bebeaudr): Need to handle different fragmentation types. It won't
// always be multi-column.
NGConstraintSpace fragmentainer_constraint_space =
CreateConstraintSpaceForColumns(*container_builder_->ConstraintSpace(),
column_size, percentage_resolution_size,
allow_discard_start_margin,
/* balance_columns */ false);
return fragmentainer_constraint_space_map_
.insert(stored_index, fragmentainer_constraint_space)
.stored_value->value;
}
void NGOutOfFlowLayoutPart::AddOOFResultToFragmentainerResults(
const scoped_refptr<const NGLayoutResult> result,
wtf_size_t index) {
// Increase the index by 1 to avoid a key of 0.
wtf_size_t stored_index = index + 1;
Vector<scoped_refptr<const NGLayoutResult>> results;
auto it = fragmentainer_descendant_results_.find(stored_index);
if (it != fragmentainer_descendant_results_.end())
results = it->value;
results.emplace_back(result);
fragmentainer_descendant_results_.Set(stored_index, results);
}
// Compute in which fragmentainer the OOF element will start its layout and
// position the offset relative to that fragmentainer.
void NGOutOfFlowLayoutPart::ComputeStartFragmentIndexAndRelativeOffset(
const ContainingBlockInfo& container_info,
WritingMode default_writing_mode,
LayoutUnit block_estimate,
wtf_size_t* start_index,
LogicalOffset* offset) const {
wtf_size_t child_index = 0;
// The sum of all previous fragmentainers' block size.
LayoutUnit used_block_size;
// The sum of all previous fragmentainers' block size + the current one.
LayoutUnit current_max_block_size;
// The block size for the last fragmentainer we encountered.
LayoutUnit fragmentainer_block_size;
// TODO(bebeaudr): There is a possible performance improvement here as we'll
// repeat this for each abspos in a same fragmentainer.
for (auto& child : container_builder_->Children()) {
if (child.fragment->IsFragmentainerBox()) {
fragmentainer_block_size = child.fragment->Size()
.ConvertToLogical(default_writing_mode)
.block_size;
fragmentainer_block_size =
ClampedToValidFragmentainerCapacity(fragmentainer_block_size);
current_max_block_size += fragmentainer_block_size;
// Edge case: an abspos with an height of 0 positioned exactly at the
// |current_max_block_size| won't be fragmented, so no break token will be
// produced - as we'd expect. However, the break token is used to compute
// the |fragmentainer_consumed_block_size_| stored on the
// |container_builder_| when we have a nested abspos. Because we use that
// value to position the nested abspos, its start offset would be off by
// exactly one fragmentainer block size.
if (offset->block_offset < current_max_block_size ||
(offset->block_offset == current_max_block_size &&
block_estimate == 0)) {
*start_index = child_index;
offset->block_offset -= used_block_size;
return;
}
used_block_size = current_max_block_size;
}
child_index++;
}
// If the right fragmentainer hasn't been found yet, the OOF element will
// start its layout in a proxy fragment.
LayoutUnit remaining_block_offset = offset->block_offset - used_block_size;
// If we are a new fragment and are separated from other columns by a
// spanner, compute the correct fragmentainer_block_size.
if (!nested_fragmentation_context_ &&
original_column_block_size_ != kIndefiniteSize &&
!container_builder_->Children()[child_index - 1]
.fragment->IsFragmentainerBox()) {
fragmentainer_block_size =
original_column_block_size_ -
container_builder_->BlockOffsetForAdditionalColumns();
fragmentainer_block_size =
ClampedToValidFragmentainerCapacity(fragmentainer_block_size);
}
wtf_size_t additional_fragment_count =
int(floorf(remaining_block_offset / fragmentainer_block_size));
*start_index = child_index + additional_fragment_count;
offset->block_offset = remaining_block_offset -
additional_fragment_count * fragmentainer_block_size;
}
} // namespace blink