blob: 4d2cd1b383e7d5ae7684b9f2608faca3d2d10ef9 [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_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::AddChild(
const NGPhysicalContainerFragment& child,
const LogicalOffset& child_offset,
const LayoutInline* inline_container) {
PropagateChildData(child, child_offset, inline_container);
AddChildInternal(&child, child_offset);
}
// 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.
// child_offset is offset of inline_start/block_start vertex.
// Candidates need offset of top/left vertex.
if (child.HasOutOfFlowPositionedDescendants()) {
const auto& out_of_flow_descendants =
child.OutOfFlowPositionedDescendants();
PhysicalSize child_size = child.Size();
// We can end up in a case where we need to account for the relative
// position of an element to correctly determine the static position of a
// descendant. E.g.
// <div id="fixed_container">
// <div style="position: relative; top: 10px;">
// <div style="position: fixed;"></div>
// </div>
// </div>
// TODO(layout-dev): This code should eventually be removed once we handle
// relative positioned objects directly in the fragment tree.
LogicalOffset offset = child_offset;
if (const LayoutBox* child_box =
ToLayoutBoxOrNull(child.GetLayoutObject())) {
offset += PhysicalOffset(child_box->OffsetForInFlowPosition())
.ConvertToLogical(GetWritingMode(), Direction(),
PhysicalSize(), PhysicalSize());
}
for (const auto& descendant : out_of_flow_descendants) {
NGLogicalStaticPosition static_position =
descendant.static_position.ConvertToLogical(GetWritingMode(),
Direction(), child_size);
static_position.offset += offset;
const LayoutInline* new_inline_container = descendant.inline_container;
if (!descendant.inline_container &&
IsInlineContainerForNode(descendant.node, inline_container))
new_inline_container = inline_container;
oof_positioned_candidates_.emplace_back(descendant.node, static_position,
new_inline_container);
}
}
// For the |has_orthogonal_flow_roots_| flag, we don't care about the type of
// child (OOF-positioned, etc), it is for *any* descendant.
if (child.HasOrthogonalFlowRoots() ||
!IsParallelWritingMode(child.Style().GetWritingMode(),
Style().GetWritingMode()))
has_orthogonal_flow_roots_ = true;
// 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 (child.DependsOnPercentageBlockSize() && !child.IsOutOfFlowPositioned())
has_descendant_that_depends_on_percentage_block_size_ = true;
// The |may_have_descendant_above_block_start_| flag is used to determine if
// a fragment can be re-used when preceding floats are present. This is
// relatively rare, and is true if:
// - An inflow child is positioned above our block-start edge.
// - Any inflow descendants (within the same formatting-context) which *may*
// have a child positioned above our block-start edge.
if ((child_offset.block_offset < LayoutUnit() &&
!child.IsOutOfFlowPositioned()) ||
(!child.IsBlockFormattingContextRoot() && !child.IsLineBox() &&
child.MayHaveDescendantAboveBlockStart()))
may_have_descendant_above_block_start_ = true;
// Compute |has_floating_descendants_for_paint_| to optimize tree traversal
// in paint.
if (!has_floating_descendants_for_paint_) {
// TODO(layout-dev): The |NGPhysicalFragment::IsAtomicInline| check should
// be checking for any children which paint all phases atomically.
if (child.IsFloating() || child.IsLegacyLayoutRoot() ||
(child.HasFloatingDescendantsForPaint() && !child.IsAtomicInline()))
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.IsBlockFormattingContextRoot() &&
child.HasAdjoiningObjectDescendants())
has_adjoining_object_descendants_ = true;
}
// Collect any (block) break tokens, unless this is a fragmentation context
// root. Break tokens should only escape a fragmentation context at the
// discretion of the fragmentation context.
if (has_block_fragmentation_ && !is_fragmentation_context_root_) {
if (const NGBreakToken* child_break_token = child.BreakToken()) {
switch (child.Type()) {
case NGPhysicalFragment::kFragmentBox:
child_break_tokens_.push_back(child_break_token);
break;
case NGPhysicalFragment::kFragmentLineBox:
// NGInlineNode produces multiple line boxes in an anonymous box. We
// won't know up front which line box to insert a fragment break
// before (due to widows), so keep them all until we know.
inline_break_tokens_.push_back(child_break_token);
break;
case NGPhysicalFragment::kFragmentText:
default:
NOTREACHED();
break;
}
}
}
}
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;
}
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) {
DCHECK(child);
// If an OOF-positioned candidate has a static-position which uses a
// non-block-start edge, we need to adjust its static-position when the final
// block-size is known.
bool 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);
}
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::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::
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 = ToLayoutInline(layout_object_);
// Ensure that the inline_container is a continuation root.
if (candidate.inline_container) {
candidate.inline_container =
ToLayoutInline(candidate.inline_container->ContinuationRoot());
}
}
}
#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