blob: 2bb3a9831158849076a25b4140cfde1d113d48ae [file] [log] [blame]
// Copyright 2017 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_container_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.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/style/computed_style.h"
#include "third_party/blink/renderer/platform/text/writing_mode.h"
namespace blink {
namespace {
bool IsInlineContainerForNode(const NGBlockNode& node,
const LayoutObject* inline_container) {
return inline_container && inline_container->IsLayoutInline() &&
inline_container->CanContainOutOfFlowPositionedElement(
node.Style().GetPosition());
}
} // namespace
void NGContainerFragmentBuilder::ReplaceChild(
wtf_size_t index,
const NGPhysicalContainerFragment& new_child,
const LogicalOffset offset) {
DCHECK_LT(index, children_.size());
children_[index] = ChildWithOffset(offset, std::move(&new_child));
}
// Propagate data in |child| to this fragment. The |child| will then be added as
// a child fragment or a child fragment item.
void NGContainerFragmentBuilder::PropagateChildData(
const NGPhysicalContainerFragment& child,
const LogicalOffset& child_offset,
const LayoutInline* inline_container) {
// Collect the child's out of flow descendants.
const WritingModeConverter converter(GetWritingDirection(), child.Size());
for (const auto& descendant : child.OutOfFlowPositionedDescendants()) {
NGLogicalStaticPosition static_position =
descendant.static_position.ConvertToLogical(converter);
static_position.offset += child_offset;
const LayoutInline* new_inline_container = descendant.inline_container;
if (!new_inline_container &&
IsInlineContainerForNode(descendant.node, inline_container))
new_inline_container = inline_container;
// |oof_positioned_candidates_| should not have duplicated entries.
DCHECK(std::none_of(
oof_positioned_candidates_.begin(), oof_positioned_candidates_.end(),
[&descendant](const NGLogicalOutOfFlowPositionedNode& node) {
return node.node == descendant.node;
}));
oof_positioned_candidates_.emplace_back(descendant.node, static_position,
new_inline_container);
}
PropagateOOFPositionedInfo(child, child_offset);
// We only need to report if inflow or floating elements depend on the
// percentage resolution block-size. OOF-positioned children resolve their
// percentages against the "final" size of their parent.
if (!has_descendant_that_depends_on_percentage_block_size_) {
if (child.DependsOnPercentageBlockSize() && !child.IsOutOfFlowPositioned())
has_descendant_that_depends_on_percentage_block_size_ = true;
// We may have a child which has the following style:
// <div style="position: relative; top: 50%;"></div>
// We need to mark this as depending on our %-block-size for the its offset
// to be correctly calculated. This is *slightly* too broad as it only
// depends on the available block-size, rather than the %-block-size.
const auto& child_style = child.Style();
if (child.IsCSSBox() && child_style.GetPosition() == EPosition::kRelative) {
if (IsHorizontalWritingMode(Style().GetWritingMode())) {
if (child_style.Top().IsPercentOrCalc() ||
child_style.Bottom().IsPercentOrCalc())
has_descendant_that_depends_on_percentage_block_size_ = true;
} else {
if (child_style.Left().IsPercentOrCalc() ||
child_style.Right().IsPercentOrCalc())
has_descendant_that_depends_on_percentage_block_size_ = true;
}
}
}
// Compute |has_floating_descendants_for_paint_| to optimize tree traversal
// in paint.
if (!has_floating_descendants_for_paint_) {
if (child.IsFloating() || child.IsLegacyLayoutRoot() ||
(child.HasFloatingDescendantsForPaint() &&
!child.IsPaintedAtomically()))
has_floating_descendants_for_paint_ = true;
}
// The |has_adjoining_object_descendants_| is used to determine if a fragment
// can be re-used when preceding floats are present.
// If a fragment doesn't have any adjoining object descendants, and is
// self-collapsing, it can be "shifted" anywhere.
if (!has_adjoining_object_descendants_) {
if (!child.IsFormattingContextRoot() &&
child.HasAdjoiningObjectDescendants())
has_adjoining_object_descendants_ = true;
}
// Collect any (block) break tokens, but skip break tokens for fragmentainers,
// as they should only escape a fragmentation context at the discretion of the
// fragmentation context.
if (has_block_fragmentation_ && !child.IsFragmentainerBox()) {
const NGBreakToken* child_break_token = child.BreakToken();
switch (child.Type()) {
case NGPhysicalFragment::kFragmentBox:
if (child_break_token)
child_break_tokens_.push_back(child_break_token);
break;
case NGPhysicalFragment::kFragmentLineBox:
// We only care about the break token from the last line box added. This
// is where we'll resume if we decide to block-fragment. Note that
// child_break_token is nullptr if this is the last line to be generated
// from the node.
last_inline_break_token_ = To<NGInlineBreakToken>(child_break_token);
line_count_++;
break;
}
}
// Always store the consumed block size of the previous fragmentainer so the
// out-of-flow positioned-nodes in the next fragmentainers can use it to
// compute its start position.
if (child.BreakToken() && child.IsFragmentainerBox()) {
DCHECK(IsA<NGBlockBreakToken>(child.BreakToken()));
fragmentainer_consumed_block_size_ =
To<NGBlockBreakToken>(child.BreakToken())->ConsumedBlockSize();
}
}
void NGContainerFragmentBuilder::AddChildInternal(
scoped_refptr<const NGPhysicalFragment> child,
const LogicalOffset& child_offset) {
// In order to know where list-markers are within the children list (for the
// |NGSimplifiedLayoutAlgorithm|) we always place them as the first child.
if (child->IsListMarker()) {
children_.push_front(ChildWithOffset(child_offset, std::move(child)));
return;
}
if (child->IsTextControlPlaceholder()) {
// ::placeholder should be followed by another block in order to paint
// ::placeholder earlier.
const wtf_size_t size = children_.size();
if (size > 0) {
children_.insert(size - 1,
ChildWithOffset(child_offset, std::move(child)));
return;
}
}
children_.emplace_back(child_offset, std::move(child));
}
void NGContainerFragmentBuilder::AddOutOfFlowChildCandidate(
NGBlockNode child,
const LogicalOffset& child_offset,
NGLogicalStaticPosition::InlineEdge inline_edge,
NGLogicalStaticPosition::BlockEdge block_edge,
bool needs_block_offset_adjustment,
const base::Optional<LogicalRect> containing_block_rect) {
DCHECK(child);
// If an OOF-positioned candidate has a static-position which uses a
// non-block-start edge, we may need to adjust its static-position when the
// final block-size is known.
needs_block_offset_adjustment &=
block_edge != NGLogicalStaticPosition::BlockEdge::kBlockStart;
has_oof_candidate_that_needs_block_offset_adjustment_ |=
needs_block_offset_adjustment;
oof_positioned_candidates_.emplace_back(
child, NGLogicalStaticPosition{child_offset, inline_edge, block_edge},
/* inline_container */ nullptr, needs_block_offset_adjustment,
/* containing_block_offset */ LogicalOffset(),
/* containing_block_fragment */ nullptr, containing_block_rect);
}
void NGContainerFragmentBuilder::AddOutOfFlowInlineChildCandidate(
NGBlockNode child,
const LogicalOffset& child_offset,
TextDirection inline_container_direction) {
DCHECK(node_.IsInline() || layout_object_->IsLayoutInline());
// As all inline-level fragments are built in the line-logical coordinate
// system (Direction() is kLtr), we need to know the direction of the
// parent element to correctly determine an OOF childs static position.
AddOutOfFlowChildCandidate(child, child_offset,
IsLtr(inline_container_direction)
? NGLogicalStaticPosition::kInlineStart
: NGLogicalStaticPosition::kInlineEnd,
NGLogicalStaticPosition::kBlockStart);
}
void NGContainerFragmentBuilder::AddOutOfFlowFragmentainerDescendant(
const NGLogicalOutOfFlowPositionedNode& descendant) {
oof_positioned_fragmentainer_descendants_.push_back(descendant);
}
void NGContainerFragmentBuilder::AddOutOfFlowDescendant(
const NGLogicalOutOfFlowPositionedNode& descendant) {
oof_positioned_descendants_.push_back(descendant);
}
void NGContainerFragmentBuilder::SwapOutOfFlowPositionedCandidates(
Vector<NGLogicalOutOfFlowPositionedNode>* candidates) {
DCHECK(candidates->IsEmpty());
std::swap(oof_positioned_candidates_, *candidates);
if (!has_oof_candidate_that_needs_block_offset_adjustment_)
return;
using BlockEdge = NGLogicalStaticPosition::BlockEdge;
// We might have an OOF-positioned candidate whose static-position depends on
// the final block-size of this fragment.
DCHECK_NE(BlockSize(), kIndefiniteSize);
for (auto& candidate : *candidates) {
if (!candidate.needs_block_offset_adjustment)
continue;
if (candidate.static_position.block_edge == BlockEdge::kBlockCenter)
candidate.static_position.offset.block_offset += BlockSize() / 2;
else if (candidate.static_position.block_edge == BlockEdge::kBlockEnd)
candidate.static_position.offset.block_offset += BlockSize();
candidate.needs_block_offset_adjustment = false;
}
has_oof_candidate_that_needs_block_offset_adjustment_ = false;
}
void NGContainerFragmentBuilder::AddMulticolWithPendingOOFs(
const NGBlockNode& multicol) {
DCHECK(To<LayoutBlockFlow>(multicol.GetLayoutBox())->MultiColumnFlowThread());
multicols_with_pending_oofs_.insert(multicol.GetLayoutBox());
}
void NGContainerFragmentBuilder::SwapMulticolsWithPendingOOFs(
MulticolCollection* multicols_with_pending_oofs) {
DCHECK(multicols_with_pending_oofs->IsEmpty());
std::swap(multicols_with_pending_oofs_, *multicols_with_pending_oofs);
}
void NGContainerFragmentBuilder::SwapOutOfFlowFragmentainerDescendants(
Vector<NGLogicalOutOfFlowPositionedNode>* descendants) {
DCHECK(descendants->IsEmpty());
DCHECK(!has_oof_candidate_that_needs_block_offset_adjustment_);
std::swap(oof_positioned_fragmentainer_descendants_, *descendants);
}
void NGContainerFragmentBuilder::ClearOutOfFlowFragmentainerDescendants() {
if (!HasOutOfFlowFragmentainerDescendants())
return;
oof_positioned_fragmentainer_descendants_.clear();
}
void NGContainerFragmentBuilder::
MoveOutOfFlowDescendantCandidatesToDescendants() {
DCHECK(oof_positioned_descendants_.IsEmpty());
std::swap(oof_positioned_candidates_, oof_positioned_descendants_);
if (!layout_object_->IsInline())
return;
for (auto& candidate : oof_positioned_descendants_) {
// If we are inside the inline algorithm, (and creating a fragment for a
// <span> or similar), we may add a child (e.g. an atomic-inline) which has
// OOF descandants.
//
// This checks if the object creating this box will be the container for
// the given descendant.
if (!candidate.inline_container &&
IsInlineContainerForNode(candidate.node, layout_object_))
candidate.inline_container = To<LayoutInline>(layout_object_);
// Ensure that the inline_container is a continuation root.
if (candidate.inline_container) {
candidate.inline_container =
To<LayoutInline>(candidate.inline_container->ContinuationRoot());
}
}
}
void NGContainerFragmentBuilder::PropagateOOFPositionedInfo(
const NGPhysicalContainerFragment& fragment,
LogicalOffset offset) {
const NGPhysicalBoxFragment* box_fragment =
DynamicTo<NGPhysicalBoxFragment>(&fragment);
if (!box_fragment)
return;
if (box_fragment->HasMulticolsWithPendingOOFs()) {
const auto& multicols_with_pending_oofs =
box_fragment->MulticolsWithPendingOOFs();
for (LayoutBox* multicol : multicols_with_pending_oofs)
AddMulticolWithPendingOOFs(NGBlockNode(multicol));
}
// If we find a multicol with OOF positioned fragmentainer descendants,
// then that multicol is an inner multicol with pending OOFs. Those OOFs
// will be laid out inside the inner multicol when we reach the outermost
// fragmentation context, so we should not propagate those OOFs up the tree
// any further.
if (!box_fragment->HasOutOfFlowPositionedFragmentainerDescendants() ||
box_fragment->IsFragmentationContextRoot()) {
return;
}
const WritingModeConverter converter(GetWritingDirection(), fragment.Size());
const auto& out_of_flow_fragmentainer_descendants =
box_fragment->OutOfFlowPositionedFragmentainerDescendants();
for (const auto& descendant : out_of_flow_fragmentainer_descendants) {
const NGPhysicalContainerFragment* containing_block_fragment =
descendant.containing_block_fragment.get();
if (!containing_block_fragment)
containing_block_fragment = box_fragment;
LogicalOffset containing_block_offset = converter.ToLogical(
descendant.containing_block_offset, containing_block_fragment->Size());
if (!fragment.IsFragmentainerBox())
containing_block_offset.block_offset += offset.block_offset;
if (IsBlockFragmentationContextRoot()) {
containing_block_offset.block_offset +=
fragmentainer_consumed_block_size_;
}
// The static position should remain relative to its containing block
// fragment.
const WritingModeConverter containing_block_converter(
GetWritingDirection(), containing_block_fragment->Size());
NGLogicalStaticPosition static_position =
descendant.static_position.ConvertToLogical(containing_block_converter);
AddOutOfFlowFragmentainerDescendant(
{descendant.node, static_position, descendant.inline_container,
/* needs_block_offset_adjustment */ false, containing_block_offset,
containing_block_fragment});
}
}
#if DCHECK_IS_ON()
String NGContainerFragmentBuilder::ToString() const {
StringBuilder builder;
builder.AppendFormat("ContainerFragment %.2fx%.2f, Children %u\n",
InlineSize().ToFloat(), BlockSize().ToFloat(),
children_.size());
for (auto& child : children_) {
builder.Append(child.fragment->DumpFragmentTree(
NGPhysicalFragment::DumpAll & ~NGPhysicalFragment::DumpHeaderText));
}
return builder.ToString();
}
#endif
} // namespace blink