blob: 57095760c0353c14f8015364e5bdb1d7425eddc7 [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_physical_container_fragment.h"
#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_overflow_calculator.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"
#include "third_party/blink/renderer/platform/wtf/size_assertions.h"
namespace blink {
namespace {
struct SameSizeAsNGPhysicalContainerFragment : NGPhysicalFragment {
wtf_size_t size;
void* break_token;
std::unique_ptr<Vector<NGPhysicalOutOfFlowPositionedNode>>
oof_positioned_descendants_;
void* pointer;
};
ASSERT_SIZE(NGPhysicalContainerFragment, SameSizeAsNGPhysicalContainerFragment);
} // namespace
NGPhysicalContainerFragment::NGPhysicalContainerFragment(
NGContainerFragmentBuilder* builder,
WritingMode block_or_line_writing_mode,
NGLink* buffer,
NGFragmentType type,
unsigned sub_type)
: NGPhysicalFragment(builder, type, sub_type),
num_children_(builder->children_.size()),
break_token_(std::move(builder->break_token_)),
oof_positioned_descendants_(
builder->oof_positioned_descendants_.IsEmpty()
? nullptr
: new Vector<NGPhysicalOutOfFlowPositionedNode>()),
buffer_(buffer) {
has_floating_descendants_for_paint_ =
builder->has_floating_descendants_for_paint_;
has_adjoining_object_descendants_ =
builder->has_adjoining_object_descendants_;
depends_on_percentage_block_size_ = DependsOnPercentageBlockSize(*builder);
PhysicalSize size = Size();
if (oof_positioned_descendants_) {
oof_positioned_descendants_->ReserveCapacity(
builder->oof_positioned_descendants_.size());
const WritingModeConverter converter(
{builder->Style().GetWritingMode(), builder->Direction()}, size);
for (const auto& descendant : builder->oof_positioned_descendants_) {
oof_positioned_descendants_->emplace_back(
descendant.node,
descendant.static_position.ConvertToPhysical(converter),
descendant.inline_container);
}
}
// Because flexible arrays need to be the last member in a class, we need to
// have the buffer passed as a constructor argument and have the actual
// storage be part of the subclass.
const WritingModeConverter converter(
{block_or_line_writing_mode, builder->Direction()}, size);
wtf_size_t i = 0;
for (auto& child : builder->children_) {
buffer[i].offset =
converter.ToPhysical(child.offset, child.fragment->Size());
// Call the move constructor to move without |AddRef|. Fragments in
// |builder| are not used after |this| was constructed.
static_assert(
sizeof(buffer[0].fragment) ==
sizeof(scoped_refptr<const NGPhysicalFragment>),
"scoped_refptr must be the size of a pointer for this to work");
new (&buffer[i].fragment)
scoped_refptr<const NGPhysicalFragment>(std::move(child.fragment));
DCHECK(!child.fragment); // Ensure it was moved.
++i;
}
}
NGPhysicalContainerFragment::NGPhysicalContainerFragment(
const NGPhysicalContainerFragment& other,
bool recalculate_layout_overflow,
NGLink* buffer)
: NGPhysicalFragment(other),
num_children_(other.num_children_),
break_token_(other.break_token_),
oof_positioned_descendants_(
other.oof_positioned_descendants_
? new Vector<NGPhysicalOutOfFlowPositionedNode>(
*other.oof_positioned_descendants_)
: nullptr),
buffer_(buffer) {
// To ensure the fragment tree is consistent, use the post-layout fragment.
for (wtf_size_t i = 0; i < num_children_; ++i) {
buffer[i].offset = other.buffer_[i].offset;
scoped_refptr<const NGPhysicalFragment> post_layout =
other.buffer_[i]->PostLayout();
// While making the fragment tree consistent, we need to also clone any
// fragmentainer fragments, as they don't nessecerily have their result
// stored on the layout-object tree.
if (post_layout->IsFragmentainerBox()) {
const auto& box_fragment = To<NGPhysicalBoxFragment>(*post_layout);
base::Optional<PhysicalRect> layout_overflow;
if (recalculate_layout_overflow) {
layout_overflow =
NGLayoutOverflowCalculator::RecalculateLayoutOverflowForFragment(
box_fragment);
}
post_layout = NGPhysicalBoxFragment::CloneWithPostLayoutFragments(
box_fragment, layout_overflow);
}
new (&buffer[i].fragment)
scoped_refptr<const NGPhysicalFragment>(std::move(post_layout));
}
}
NGPhysicalContainerFragment::~NGPhysicalContainerFragment() = default;
// additional_offset must be offset from the containing_block.
void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren(
Vector<PhysicalRect>* outline_rects,
const PhysicalOffset& additional_offset,
NGOutlineType outline_type,
const LayoutBoxModelObject* containing_block) const {
if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(this)) {
DCHECK_EQ(box->PostLayout(), box);
if (const NGFragmentItems* items = box->Items()) {
NGInlineCursor cursor(*box, *items);
AddOutlineRectsForCursor(outline_rects, additional_offset, outline_type,
containing_block, &cursor);
// Don't add |Children()|. If |this| has |NGFragmentItems|, children are
// either line box, which we already handled in items, or OOF, which we
// should ignore.
DCHECK(std::all_of(PostLayoutChildren().begin(),
PostLayoutChildren().end(), [](const NGLink& child) {
return child->IsLineBox() ||
child->IsOutOfFlowPositioned();
}));
return;
}
}
for (const auto& child : PostLayoutChildren()) {
// Outlines of out-of-flow positioned descendants are handled in
// NGPhysicalBoxFragment::AddSelfOutlineRects().
if (child->IsOutOfFlowPositioned())
continue;
// Outline of an element continuation or anonymous block continuation is
// added when we iterate the continuation chain.
// See NGPhysicalBoxFragment::AddSelfOutlineRects().
if (!child->IsLineBox()) {
const LayoutObject* child_layout_object = child->GetLayoutObject();
if (auto* child_layout_block_flow =
DynamicTo<LayoutBlockFlow>(child_layout_object)) {
if (child_layout_object->IsElementContinuation() ||
child_layout_block_flow->IsAnonymousBlockContinuation())
continue;
}
}
AddOutlineRectsForDescendant(child, outline_rects, additional_offset,
outline_type, containing_block);
}
}
void NGPhysicalContainerFragment::AddOutlineRectsForCursor(
Vector<PhysicalRect>* outline_rects,
const PhysicalOffset& additional_offset,
NGOutlineType outline_type,
const LayoutBoxModelObject* containing_block,
NGInlineCursor* cursor) const {
for (; *cursor; cursor->MoveToNext()) {
DCHECK(cursor->Current().Item());
const NGFragmentItem& item = *cursor->Current().Item();
if (UNLIKELY(item.IsLayoutObjectDestroyedOrMoved()))
continue;
if (item.Type() == NGFragmentItem::kLine) {
AddOutlineRectsForDescendant(
{item.LineBoxFragment(), item.OffsetInContainerFragment()},
outline_rects, additional_offset, outline_type, containing_block);
continue;
}
if (item.IsText()) {
if (outline_type == NGOutlineType::kDontIncludeBlockVisualOverflow)
continue;
outline_rects->push_back(
PhysicalRect(additional_offset + item.OffsetInContainerFragment(),
item.Size().ToLayoutSize()));
continue;
}
if (item.Type() == NGFragmentItem::kBox) {
if (const NGPhysicalBoxFragment* child_box =
item.PostLayoutBoxFragment()) {
DCHECK(!child_box->IsOutOfFlowPositioned());
AddOutlineRectsForDescendant(
{child_box, item.OffsetInContainerFragment()}, outline_rects,
additional_offset, outline_type, containing_block);
}
continue;
}
}
}
void NGPhysicalContainerFragment::AddScrollableOverflowForInlineChild(
const NGPhysicalBoxFragment& container,
const ComputedStyle& container_style,
const NGFragmentItem& line,
bool has_hanging,
const NGInlineCursor& cursor,
TextHeightType height_type,
PhysicalRect* overflow) const {
DCHECK(IsLineBox() || IsInlineBox());
DCHECK(cursor.Current().Item() &&
(cursor.Current().Item()->BoxFragment() == this ||
cursor.Current().Item()->LineBoxFragment() == this));
const WritingMode container_writing_mode = container_style.GetWritingMode();
for (NGInlineCursor descendants = cursor.CursorForDescendants();
descendants;) {
const NGFragmentItem* item = descendants.CurrentItem();
DCHECK(item);
if (UNLIKELY(item->IsLayoutObjectDestroyedOrMoved())) {
NOTREACHED();
descendants.MoveToNextSkippingChildren();
continue;
}
if (item->IsText()) {
PhysicalRect child_scroll_overflow = item->RectInContainerFragment();
if (height_type == TextHeightType::kEmHeight) {
child_scroll_overflow = AdjustTextRectForEmHeight(
child_scroll_overflow, item->Style(), item->TextShapeResult(),
container_writing_mode);
}
if (UNLIKELY(has_hanging)) {
AdjustScrollableOverflowForHanging(line.RectInContainerFragment(),
container_writing_mode,
&child_scroll_overflow);
}
overflow->Unite(child_scroll_overflow);
descendants.MoveToNextSkippingChildren();
continue;
}
if (const NGPhysicalBoxFragment* child_box =
item->PostLayoutBoxFragment()) {
PhysicalRect child_scroll_overflow;
if (height_type == TextHeightType::kNormalHeight ||
(child_box->BoxType() != kInlineBox && !IsRubyBox()))
child_scroll_overflow = item->RectInContainerFragment();
if (child_box->IsInlineBox()) {
child_box->AddScrollableOverflowForInlineChild(
container, container_style, line, has_hanging, descendants,
height_type, &child_scroll_overflow);
child_box->AdjustScrollableOverflowForPropagation(
container, height_type, &child_scroll_overflow);
if (UNLIKELY(has_hanging)) {
AdjustScrollableOverflowForHanging(line.RectInContainerFragment(),
container_writing_mode,
&child_scroll_overflow);
}
} else {
child_scroll_overflow =
child_box->ScrollableOverflowForPropagation(container, height_type);
child_scroll_overflow.offset += item->OffsetInContainerFragment();
}
overflow->Unite(child_scroll_overflow);
descendants.MoveToNextSkippingChildren();
continue;
}
// Add all children of a culled inline box; i.e., an inline box without
// margin/border/padding etc.
DCHECK_EQ(item->Type(), NGFragmentItem::kBox);
descendants.MoveToNext();
}
}
// Chop the hanging part from scrollable overflow. Children overflow in inline
// direction should hang, which should not cause scroll.
// TODO(kojii): Should move to text fragment to make this more accurate.
void NGPhysicalContainerFragment::AdjustScrollableOverflowForHanging(
const PhysicalRect& rect,
const WritingMode container_writing_mode,
PhysicalRect* overflow) {
if (IsHorizontalWritingMode(container_writing_mode)) {
if (overflow->offset.left < rect.offset.left)
overflow->offset.left = rect.offset.left;
if (overflow->Right() > rect.Right())
overflow->ShiftRightEdgeTo(rect.Right());
} else {
if (overflow->offset.top < rect.offset.top)
overflow->offset.top = rect.offset.top;
if (overflow->Bottom() > rect.Bottom())
overflow->ShiftBottomEdgeTo(rect.Bottom());
}
}
// additional_offset must be offset from the containing_block because
// LocalToAncestorRect returns rects wrt containing_block.
void NGPhysicalContainerFragment::AddOutlineRectsForDescendant(
const NGLink& descendant,
Vector<PhysicalRect>* outline_rects,
const PhysicalOffset& additional_offset,
NGOutlineType outline_type,
const LayoutBoxModelObject* containing_block) const {
DCHECK(!descendant->IsLayoutObjectDestroyedOrMoved());
if (descendant->IsListMarker())
return;
if (const auto* descendant_box =
DynamicTo<NGPhysicalBoxFragment>(descendant.get())) {
DCHECK_EQ(descendant_box->PostLayout(), descendant_box);
const LayoutObject* descendant_layout_object =
descendant_box->GetLayoutObject();
// TODO(layoutng): Explain this check. I assume we need it because layers
// may have transforms and so we have to go through LocalToAncestorRects?
if (descendant_box->HasLayer()) {
DCHECK(descendant_layout_object);
Vector<PhysicalRect> layer_outline_rects;
descendant_box->AddOutlineRects(PhysicalOffset(), outline_type,
&layer_outline_rects);
// Don't pass additional_offset because LocalToAncestorRects will itself
// apply it.
descendant_layout_object->LocalToAncestorRects(
layer_outline_rects, containing_block, PhysicalOffset(),
PhysicalOffset());
outline_rects->AppendVector(layer_outline_rects);
return;
}
if (!descendant_box->IsInlineBox()) {
descendant_box->AddSelfOutlineRects(
additional_offset + descendant.Offset(), outline_type, outline_rects);
return;
}
DCHECK(descendant_layout_object);
const auto* descendant_layout_inline =
To<LayoutInline>(descendant_layout_object);
// As an optimization, an ancestor has added rects for its line boxes
// covering descendants' line boxes, so descendants don't need to add line
// boxes again. For example, if the parent is a LayoutBlock, it adds rects
// for its line box which cover the line boxes of this LayoutInline. So
// the LayoutInline needs to add rects for children and continuations
// only.
if (descendant_box->IsOutlineOwner()) {
// We don't pass additional_offset here because the function requires
// additional_offset to be the offset from the containing block.
descendant_layout_inline->AddOutlineRectsForChildrenAndContinuations(
*outline_rects, PhysicalOffset(), outline_type);
}
return;
}
if (const auto* descendant_line_box =
DynamicTo<NGPhysicalLineBoxFragment>(descendant.get())) {
descendant_line_box->AddOutlineRectsForNormalChildren(
outline_rects, additional_offset + descendant.Offset(), outline_type,
containing_block);
if (!descendant_line_box->Size().IsEmpty()) {
outline_rects->emplace_back(additional_offset + descendant.Offset(),
descendant_line_box->Size().ToLayoutSize());
}
}
}
bool NGPhysicalContainerFragment::DependsOnPercentageBlockSize(
const NGContainerFragmentBuilder& builder) {
NGLayoutInputNode node = builder.node_;
if (!node || node.IsInline())
return builder.has_descendant_that_depends_on_percentage_block_size_;
// For the below if-stmt we only want to consider legacy *containers* as
// potentially having %-dependent children - i.e. an image doesn't have any
// children.
bool is_legacy_container_with_percent_height_descendants =
builder.is_legacy_layout_root_ && !node.IsReplaced() &&
node.GetLayoutBox()->MaybeHasPercentHeightDescendant();
// NOTE: If an element is OOF positioned, and has top/bottom constraints
// which are percentage based, this function will return false.
//
// This is fine as the top/bottom constraints are computed *before* layout,
// and the result is set as a fixed-block-size constraint. (And the caching
// logic will never check the result of this function).
//
// The result of this function still may be used for an OOF positioned
// element if it has a percentage block-size however, but this will return
// the correct result from below.
// There are two conditions where we need to know about an (arbitrary)
// descendant which depends on a %-block-size.
// - In quirks mode, the arbitrary descendant may depend the percentage
// resolution block-size given (to this node), and need to relayout if
// this size changes.
// - A flex-item may have its "definiteness" change, (e.g. if itself is a
// flex item which is being stretched). This definiteness change will
// affect any %-block-size children.
//
// NOTE(ikilpatrick): For the flex-item case this is potentially too general.
// We only need to know about if this flex-item has a %-block-size child if
// the "definiteness" changes, not if the percentage resolution size changes.
if ((builder.has_descendant_that_depends_on_percentage_block_size_ ||
is_legacy_container_with_percent_height_descendants) &&
(node.UseParentPercentageResolutionBlockSizeForChildren() ||
node.IsFlexItem()))
return true;
const ComputedStyle& style = builder.Style();
if (style.LogicalHeight().IsPercentOrCalc() ||
style.LogicalMinHeight().IsPercentOrCalc() ||
style.LogicalMaxHeight().IsPercentOrCalc())
return true;
return false;
}
} // namespace blink