blob: 6c884ab8d1f401f9f560070f96c373b78d6cac9c [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 =
ToLayoutBox(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 =
ToLayoutBoxModelObject(absolute_containing_block->Container());
}
DCHECK(absolute_containing_block->IsLayoutInline());
// Make absolute_containing_block continuation root.
return ToLayoutInline(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) {}
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_space_(container_space),
container_builder_(container_builder),
writing_mode_(container_style.GetWritingMode()),
is_absolute_container_(is_absolute_container),
is_fixed_container_(is_fixed_container) {
if (!container_builder->HasOutOfFlowPositionedCandidates() &&
!To<LayoutBlock>(container_builder_->GetLayoutObject())
->HasPositionedObjects())
return;
default_containing_block_.writing_direction =
container_style.GetWritingDirection();
const NGBoxStrut border_scrollbar =
container_builder->Borders() + container_builder->Scrollbar();
allow_first_tier_oof_cache_ = border_scrollbar.IsEmpty();
default_containing_block_.content_size_for_absolute =
ShrinkLogicalSize(container_builder_->Size(), border_scrollbar);
default_containing_block_.content_size_for_fixed =
initial_containing_block_fixed_size
? *initial_containing_block_fixed_size
: default_containing_block_.content_size_for_absolute;
default_containing_block_.container_offset = LogicalOffset(
border_scrollbar.inline_start, border_scrollbar.block_start);
}
void NGOutOfFlowLayoutPart::Run(const LayoutBox* only_layout) {
if (container_builder_->IsBlockFragmentationContextRoot() &&
!container_space_.HasBlockFragmentation() &&
container_builder_->HasOutOfFlowFragmentainerDescendants()) {
Vector<NGLogicalOutOfFlowPositionedNode> fragmentainer_descendants;
container_builder_->SwapOutOfFlowFragmentainerDescendants(
&fragmentainer_descendants);
if (!fragmentainer_descendants.IsEmpty())
LayoutFragmentainerDescendants(&fragmentainer_descendants);
}
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 (container_space_.HasBlockFragmentation())
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 = ToLayoutBox(legacy_object);
if (layout_box->Parent()->IsFlexibleBox()) {
LayoutFlexibleBox* parent = ToLayoutFlexibleBox(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,
ToLayoutInlineOrNull(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_containing_block_| 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.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(*ToLayoutBox(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, content_size,
content_size, container_offset};
return containing_blocks_map_
.insert(containing_block, containing_block_info)
.stored_value->value;
}
return default_containing_block_;
}
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.
if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) {
container_builder_->ComputeInlineContainerGeometry(
&inline_container_fragments);
} else {
container_builder_->ComputeInlineContainerGeometryFromFragmentTree(
&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_.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, inline_cb_size,
inline_cb_size, container_offset});
}
}
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 (container_space_.HasBlockFragmentation()) {
// 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 auto default_writing_direction =
default_containing_block_.writing_direction;
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.ContentSize(candidate_style.GetPosition());
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.container_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.GetWritingMode(),
/* is_new_fc */ true);
builder.SetTextDirection(candidate_writing_direction.Direction());
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_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(candidate_style)) {
// Freeze the scrollbars for this layout pass. We don't want them to
// change *again*.
freeze_scrollbars.emplace();
continue;
}
}
return layout_result;
} while (true);
}
void NGOutOfFlowLayoutPart::LayoutFragmentainerDescendants(
Vector<NGLogicalOutOfFlowPositionedNode>* descendants) {
original_column_block_size_ =
ShrinkLogicalSize(container_builder_->InitialBorderBoxSize(),
container_builder_->BorderScrollbarPadding())
.block_size;
while (descendants->size() > 0) {
for (auto& descendant : *descendants) {
LayoutFragmentainerDescendant(descendant);
}
// Sweep any descendants that might have been added.
// This happens when an absolute container has a fixed child.
descendants->Shrink(0);
container_builder_->SwapOutOfFlowFragmentainerDescendants(descendants);
}
// Add all of the descendant layout results as children to the fragment at
// the associated index.
for (const auto& descendant_result : fragmentainer_descendant_results_) {
// We don't allow keys of 0, so shift the index back by 1 when adding to the
// fragmentainer.
wtf_size_t index = descendant_result.key - 1;
const Vector<scoped_refptr<const NGLayoutResult>>& results =
descendant_result.value;
AddOOFResultsToFragmentainer(results, index);
}
}
void 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.ContentSize(descendant_style.GetPosition());
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.container_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.container_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.GetWritingMode(),
/* is_new_fc */ true);
builder.SetTextDirection(descendant_writing_direction.Direction());
builder.SetAvailableSize(container_content_size);
builder.SetPercentageResolutionSize(container_content_size);
NGConstraintSpace descendant_constraint_space = builder.ToConstraintSpace();
Layout(node, descendant_constraint_space, descendant_static_position,
container_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,
LogicalSize container_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;
PhysicalSize container_physical_content_size = ToPhysicalSize(
container_content_size, default_writing_direction.GetWritingMode());
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(candidate_style);
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(candidate_style);
if (AbsoluteNeedsChildInlineSize(candidate_style) ||
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(
candidate_constraint_space, candidate_style, border_padding,
candidate_static_position, base::nullopt, base::nullopt,
default_writing_direction.GetWritingMode(),
container_writing_direction.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);
has_aspect_ratio_without_intrinsic_size =
!replaced_size && aspect_ratio && !aspect_ratio->IsEmpty();
// 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()};
} 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(
candidate_constraint_space, candidate_style, border_padding,
candidate_static_position, min_max_sizes, minmax_intrinsic_sizes_for_ar,
replaced_size, default_writing_direction.GetWritingMode(),
container_writing_direction.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));
}
// TODO(almaher): Handle fragmentation separately for the case where
// |absolute_needs_child_block_size| is true.
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);
// 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(
candidate_constraint_space, candidate_style, border_padding,
candidate_static_position, block_estimate, replaced_size,
default_writing_direction.GetWritingMode(),
container_writing_direction.Direction(), &node_dimensions);
has_computed_block_dimensions = true;
}
// 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.container_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(),
&start_index, &offset);
}
if (!only_layout) {
// 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;
}
}
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 when estimating the
// block-size.
if (!layout_result) {
block_estimate = node_dimensions.size.block_size;
const NGConstraintSpace* fragmentainer_constraint_space =
is_fragmentainer_descendant
? &GetFragmentainerConstraintSpace(start_index)
: nullptr;
layout_result = GenerateFragment(
node, container_content_size_in_candidate_writing_mode,
block_estimate, node_dimensions, offset.block_offset, break_token,
fragmentainer_constraint_space);
}
// 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);
// Adjusting the offset for a dialog after layout is fine, since we cannot
// have dialogs needing alignment inside block fragmentation.
base::Optional<LayoutUnit> y = ComputeAbsoluteDialogYPosition(
*node.GetLayoutBox(), layout_result->PhysicalFragment().Size().height);
if (y.has_value()) {
DCHECK(!container_space_.HasBlockFragmentation());
if (default_writing_direction.IsHorizontal())
offset.block_offset = *y;
else
offset.inline_offset = *y;
layout_result->GetMutableForOutOfFlow().SetOutOfFlowPositionedOffset(
offset, allow_first_tier_oof_cache_);
}
return layout_result;
}
bool NGOutOfFlowLayoutPart::IsContainingBlockForCandidate(
const NGLogicalOutOfFlowPositionedNode& candidate) {
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.node.Style().GetPosition() == EPosition::kAbsolute &&
candidate.inline_container->CanContainAbsolutePositionObjects() ||
(candidate.node.Style().GetPosition() == EPosition::kFixed &&
candidate.inline_container->CanContainFixedPositionObjects()));
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) {
// As the |block_estimate| is always in the node's writing mode, we build the
// constraint space in the node's writing mode.
WritingMode writing_mode = node.Style().GetWritingMode();
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);
// TODO(atotic) will need to be adjusted for scrollbars.
NGConstraintSpaceBuilder builder(writing_mode, writing_mode,
/* is_new_fc */ true);
builder.SetAvailableSize(available_size);
builder.SetTextDirection(node.Style().Direction());
builder.SetPercentageResolutionSize(
container_content_size_in_candidate_writing_mode);
builder.SetIsFixedInlineSize(true);
if (block_estimate)
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) {
wtf_size_t num_children = container_builder_->Children().size();
bool is_new_fragment = index >= num_children;
// 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 (index > num_children) {
const Vector<scoped_refptr<const NGLayoutResult>> empty_results;
while (index > num_children) {
AddOOFResultsToFragmentainer(empty_results, /*index */ num_children);
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());
NGFragmentGeometry fragment_geometry =
CalculateInitialFragmentGeometry(space, node);
NGLayoutAlgorithmParams params(node, fragment_geometry, space,
/* break_token */ nullptr,
/* 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, is_new_fragment);
for (const auto& result : results) {
// TODO(bebeaudr): Is the offset returned by OutOfFlowPositionedOffset the
// one to use?
algorithm.AppendOutOfFlowResult(result,
result->OutOfFlowPositionedOffset());
}
if (is_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 {
// Calculate the column inline progression in order to calculate the
// inline offset of any newly added column fragments.
if (column_inline_progression_ == kIndefiniteSize) {
LayoutUnit available_size =
container_builder_->ChildAvailableSize().inline_size;
const ComputedStyle& style = container_builder_->Style();
LayoutUnit column_inline_size =
ResolveUsedColumnInlineSize(available_size, style);
column_inline_progression_ =
column_inline_size + ResolveUsedColumnGap(available_size, style);
}
offset = fragmentainer.offset;
offset.inline_offset += column_inline_progression_;
}
container_builder_->AddChild(algorithm.Layout()->PhysicalFragment(),
offset);
} else {
container_builder_->ReplaceChild(
index, algorithm.Layout()->PhysicalFragment(), fragmentainer.offset);
}
}
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 && 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,
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;
current_max_block_size += fragmentainer_block_size;
if (offset->block_offset < current_max_block_size) {
*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 (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 = fragmentainer_block_size.ClampNegativeToZero();
}
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