blob: 898d05f4b27de18705ecf45dfe3d710ed955a6fd [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_physical_box_fragment.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.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/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_object_inlines.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.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_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_outline_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h"
namespace blink {
namespace {
struct SameSizeAsNGPhysicalBoxFragment : NGPhysicalContainerFragment {
NGBaselineList baselines;
NGLink children[];
};
static_assert(sizeof(NGPhysicalBoxFragment) ==
sizeof(SameSizeAsNGPhysicalBoxFragment),
"NGPhysicalBoxFragment should stay small");
bool HasControlClip(const NGPhysicalBoxFragment& self) {
const LayoutBox* box = ToLayoutBoxOrNull(self.GetLayoutObject());
return box && box->HasControlClip();
}
LayoutUnit BorderWidth(unsigned edges, unsigned edge, float border_width) {
return (edges & edge) ? LayoutUnit(border_width) : LayoutUnit();
}
} // namespace
scoped_refptr<const NGPhysicalBoxFragment> NGPhysicalBoxFragment::Create(
NGBoxFragmentBuilder* builder,
WritingMode block_or_line_writing_mode) {
const NGPhysicalBoxStrut borders =
builder->initial_fragment_geometry_->border.ConvertToPhysical(
builder->GetWritingMode(), builder->Direction());
const NGPhysicalBoxStrut padding =
builder->initial_fragment_geometry_->padding.ConvertToPhysical(
builder->GetWritingMode(), builder->Direction());
size_t byte_size = sizeof(NGPhysicalBoxFragment) +
sizeof(NGLink) * builder->children_.size() +
(borders.IsZero() ? 0 : sizeof(borders)) +
(padding.IsZero() ? 0 : sizeof(padding));
if (builder->ItemsBuilder())
byte_size += sizeof(NGFragmentItems);
// We store the children list inline in the fragment as a flexible
// array. Therefore, we need to make sure to allocate enough space for
// that array here, which requires a manual allocation + placement new.
// The initialization of the array is done by NGPhysicalContainerFragment;
// we pass the buffer as a constructor argument.
void* data = ::WTF::Partitions::FastMalloc(
byte_size, ::WTF::GetStringWithTypeName<NGPhysicalBoxFragment>());
new (data) NGPhysicalBoxFragment(builder, borders, padding,
block_or_line_writing_mode);
return base::AdoptRef(static_cast<NGPhysicalBoxFragment*>(data));
}
NGPhysicalBoxFragment::NGPhysicalBoxFragment(
NGBoxFragmentBuilder* builder,
const NGPhysicalBoxStrut& borders,
const NGPhysicalBoxStrut& padding,
WritingMode block_or_line_writing_mode)
: NGPhysicalContainerFragment(
builder,
block_or_line_writing_mode,
children_,
(builder->node_ && builder->node_.IsRenderedLegend())
? kFragmentRenderedLegend
: kFragmentBox,
builder->BoxType()),
baselines_(builder->baselines_) {
DCHECK(GetLayoutObject() && GetLayoutObject()->IsBoxModelObject());
if (NGFragmentItemsBuilder* items_builder = builder->ItemsBuilder()) {
has_fragment_items_ = true;
NGFragmentItems* items =
const_cast<NGFragmentItems*>(ComputeItemsAddress());
items_builder->ToFragmentItems(block_or_line_writing_mode,
builder->Direction(), Size(), items);
} else {
has_fragment_items_ = false;
}
has_borders_ = !borders.IsZero();
if (has_borders_)
*const_cast<NGPhysicalBoxStrut*>(ComputeBordersAddress()) = borders;
has_padding_ = !padding.IsZero();
if (has_padding_)
*const_cast<NGPhysicalBoxStrut*>(ComputePaddingAddress()) = padding;
// consumed_block_size_ is only updated if we're in block
// fragmentation. Otherwise it will always be 0.
is_first_for_node_ =
builder->consumed_block_size_ <= builder->size_.block_size;
is_fieldset_container_ = builder->is_fieldset_container_;
is_legacy_layout_root_ = builder->is_legacy_layout_root_;
border_edge_ = builder->border_edges_.ToPhysical(builder->GetWritingMode());
children_inline_ =
builder->layout_object_ && builder->layout_object_->ChildrenInline();
}
scoped_refptr<const NGLayoutResult>
NGPhysicalBoxFragment::CloneAsHiddenForPaint() const {
const ComputedStyle& style = Style();
NGBoxFragmentBuilder builder(GetMutableLayoutObject(), &style,
style.GetWritingMode(), style.Direction());
builder.SetBoxType(BoxType());
NGFragmentGeometry initial_fragment_geometry{
Size().ConvertToLogical(style.GetWritingMode())};
builder.SetInitialFragmentGeometry(initial_fragment_geometry);
builder.SetIsHiddenForPaint(true);
return builder.ToBoxFragment();
}
bool NGPhysicalBoxFragment::HasSelfPaintingLayer() const {
SECURITY_DCHECK(GetLayoutObject() && GetLayoutObject()->IsBoxModelObject());
return (static_cast<const LayoutBoxModelObject*>(GetLayoutObject()))
->HasSelfPaintingLayer();
}
PhysicalRect NGPhysicalBoxFragment::OverflowClipRect(
const PhysicalOffset& location,
OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const {
DCHECK(GetLayoutObject() && GetLayoutObject()->IsBox());
const LayoutBox* box = ToLayoutBox(GetLayoutObject());
return box->OverflowClipRect(location, overlay_scrollbar_clip_behavior);
}
PhysicalRect NGPhysicalBoxFragment::ScrollableOverflow() const {
DCHECK(GetLayoutObject());
const LayoutObject* layout_object = GetLayoutObject();
if (layout_object->IsBox()) {
if (HasOverflowClip())
return PhysicalRect({}, Size());
// Legacy is the source of truth for overflow
return PhysicalRect(ToLayoutBox(layout_object)->LayoutOverflowRect());
} else if (layout_object->IsLayoutInline()) {
// Inline overflow is a union of child overflows.
PhysicalRect overflow({}, Size());
WritingMode container_writing_mode = Style().GetWritingMode();
TextDirection container_direction = Style().Direction();
for (const auto& child_fragment : Children()) {
PhysicalRect child_overflow =
child_fragment->ScrollableOverflowForPropagation(layout_object);
if (child_fragment->Style() != Style()) {
PhysicalOffset relative_offset = ComputeRelativeOffset(
child_fragment->Style(), container_writing_mode,
container_direction, Size());
child_overflow.offset += relative_offset;
}
child_overflow.offset += child_fragment.Offset();
overflow.Unite(child_overflow);
}
return overflow;
} else {
NOTREACHED();
}
return PhysicalRect({}, Size());
}
LayoutSize NGPhysicalBoxFragment::ScrolledContentOffset() const {
DCHECK(GetLayoutObject() && GetLayoutObject()->IsBox());
const LayoutBox* box = ToLayoutBox(GetLayoutObject());
return box->ScrolledContentOffset();
}
PhysicalSize NGPhysicalBoxFragment::ScrollSize() const {
DCHECK(GetLayoutObject() && GetLayoutObject()->IsBox());
const LayoutBox* box = ToLayoutBox(GetLayoutObject());
return {box->ScrollWidth(), box->ScrollHeight()};
}
PhysicalRect NGPhysicalBoxFragment::ComputeSelfInkOverflow() const {
CheckCanUpdateInkOverflow();
const ComputedStyle& style = Style();
PhysicalRect ink_overflow({}, Size().ToLayoutSize());
DCHECK(GetLayoutObject());
if (style.HasVisualOverflowingEffect()) {
ink_overflow.Expand(style.BoxDecorationOutsets());
if (NGOutlineUtils::HasPaintedOutline(style,
GetLayoutObject()->GetNode()) &&
!NGOutlineUtils::IsInlineOutlineNonpaintingFragment(*this)) {
Vector<PhysicalRect> outline_rects;
// The result rects are in coordinates of this object's border box.
AddSelfOutlineRects(
&outline_rects, PhysicalOffset(),
GetLayoutObject()->OutlineRectsShouldIncludeBlockVisualOverflow());
PhysicalRect rect = UnionRectEvenIfEmpty(outline_rects);
rect.Inflate(LayoutUnit(style.OutlineOutsetExtent()));
ink_overflow.Unite(rect);
}
}
return ink_overflow;
}
void NGPhysicalBoxFragment::AddSelfOutlineRects(
Vector<PhysicalRect>* outline_rects,
const PhysicalOffset& additional_offset,
NGOutlineType outline_type) const {
// TODO(kojii): Needs inline_element_continuation logic from
// LayoutBlockFlow::AddOutlineRects?
const LayoutObject* layout_object = GetLayoutObject();
DCHECK(layout_object);
if (layout_object->IsLayoutInline()) {
Vector<PhysicalRect> blockflow_outline_rects =
layout_object->OutlineRects(PhysicalOffset(), outline_type);
// The rectangles returned are offset from the containing block. We need the
// offset from this fragment.
if (blockflow_outline_rects.size() > 0) {
PhysicalOffset first_fragment_offset = blockflow_outline_rects[0].offset;
PhysicalOffset corrected_offset =
additional_offset - first_fragment_offset;
for (auto& outline : blockflow_outline_rects) {
// Skip if both width and height are zero. Containing blocks in empty
// linebox is one such case.
if (outline.size.IsZero())
continue;
outline.Move(corrected_offset);
outline_rects->push_back(outline);
}
}
return;
}
DCHECK(layout_object->IsBox());
// For anonymous blocks, the children add outline rects.
if (!layout_object->IsAnonymous()) {
outline_rects->emplace_back(additional_offset, Size().ToLayoutSize());
}
if (outline_type == NGOutlineType::kIncludeBlockVisualOverflow &&
!HasOverflowClip() && !HasControlClip(*this)) {
// Tricky code ahead: we pass a 0,0 additional_offset to
// AddOutlineRectsForNormalChildren, and add it in after the call.
// This is necessary because AddOutlineRectsForNormalChildren expects
// additional_offset to be an offset from containing_block.
// Since containing_block is our layout object, offset must be 0,0.
// https://crbug.com/968019
Vector<PhysicalRect> children_rects;
AddOutlineRectsForNormalChildren(&children_rects, PhysicalOffset(),
outline_type,
ToLayoutBoxModelObject(GetLayoutObject()));
if (!additional_offset.IsZero()) {
for (auto& rect : children_rects)
rect.offset += additional_offset;
}
outline_rects->AppendVector(children_rects);
// LayoutBlock::AddOutlineRects also adds out of flow objects here.
// In LayoutNG out of flow objects are not part of the outline.
}
// TODO(kojii): Needs inline_element_continuation logic from
// LayoutBlockFlow::AddOutlineRects?
}
UBiDiLevel NGPhysicalBoxFragment::BidiLevel() const {
// TODO(xiaochengh): Make the implementation more efficient.
DCHECK(IsInline());
DCHECK(IsAtomicInline());
const auto& inline_items = InlineItemsOfContainingBlock();
const NGInlineItem* self_item =
std::find_if(inline_items.begin(), inline_items.end(),
[this](const NGInlineItem& item) {
return GetLayoutObject() == item.GetLayoutObject();
});
DCHECK(self_item);
DCHECK_NE(self_item, inline_items.end());
return self_item->BidiLevel();
}
NGPixelSnappedPhysicalBoxStrut NGPhysicalBoxFragment::BorderWidths() const {
unsigned edges = BorderEdges();
NGPhysicalBoxStrut box_strut(
BorderWidth(edges, NGBorderEdges::kTop, Style().BorderTopWidth()),
BorderWidth(edges, NGBorderEdges::kRight, Style().BorderRightWidth()),
BorderWidth(edges, NGBorderEdges::kBottom, Style().BorderBottomWidth()),
BorderWidth(edges, NGBorderEdges::kLeft, Style().BorderLeftWidth()));
return box_strut.SnapToDevicePixels();
}
#if DCHECK_IS_ON()
void NGPhysicalBoxFragment::CheckSameForSimplifiedLayout(
const NGPhysicalBoxFragment& other,
bool check_same_block_size) const {
DCHECK_EQ(layout_object_, other.layout_object_);
LogicalSize size = size_.ConvertToLogical(Style().GetWritingMode());
LogicalSize other_size =
other.size_.ConvertToLogical(Style().GetWritingMode());
DCHECK_EQ(size.inline_size, other_size.inline_size);
if (check_same_block_size)
DCHECK_EQ(size.block_size, other_size.block_size);
// "simplified" layout doesn't work within a fragmentation context.
DCHECK(!break_token_ && !other.break_token_);
DCHECK_EQ(type_, other.type_);
DCHECK_EQ(sub_type_, other.sub_type_);
DCHECK_EQ(style_variant_, other.style_variant_);
DCHECK_EQ(has_floating_descendants_, other.has_floating_descendants_);
DCHECK_EQ(has_orthogonal_flow_roots_, other.has_orthogonal_flow_roots_);
DCHECK_EQ(may_have_descendant_above_block_start_,
other.may_have_descendant_above_block_start_);
DCHECK_EQ(depends_on_percentage_block_size_,
other.depends_on_percentage_block_size_);
DCHECK_EQ(children_inline_, other.children_inline_);
DCHECK_EQ(is_fieldset_container_, other.is_fieldset_container_);
DCHECK_EQ(is_legacy_layout_root_, other.is_legacy_layout_root_);
DCHECK_EQ(border_edge_, other.border_edge_);
// The oof_positioned_descendants_ vector can change during "simplified"
// layout. This occurs when an OOF-descendant changes from "fixed" to
// "absolute" (or visa versa) changing its containing block.
// Legacy layout can (incorrectly) shift baseline position(s) during
// "simplified" layout.
DCHECK(IsLegacyLayoutRoot() || baselines_ == other.baselines_);
DCHECK(Borders() == other.Borders());
DCHECK(Padding() == other.Padding());
}
#endif
} // namespace blink