blob: d712565fcf85337e12291346ab8cc68119a27d1b [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/dom/document_lifecycle.h"
#include "third_party/blink/renderer/core/html/shadow/shadow_element_utils.h"
#include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h"
#include "third_party/blink/renderer/core/layout/layout_block.h"
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.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_block_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
#include "third_party/blink/renderer/platform/wtf/size_assertions.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
namespace {
struct SameSizeAsNGPhysicalFragment
: RefCounted<const NGPhysicalFragment, NGPhysicalFragmentTraits> {
// |flags_for_free_maybe| is used to support an additional increase in size
// needed for DCHECK and 32-bit builds.
unsigned flags_for_free_maybe;
void* layout_object;
PhysicalSize size;
unsigned flags;
};
ASSERT_SIZE(NGPhysicalFragment, SameSizeAsNGPhysicalFragment);
String StringForBoxType(const NGPhysicalFragment& fragment) {
StringBuilder result;
switch (fragment.BoxType()) {
case NGPhysicalFragment::NGBoxType::kNormalBox:
break;
case NGPhysicalFragment::NGBoxType::kInlineBox:
result.Append("inline");
break;
case NGPhysicalFragment::NGBoxType::kColumnBox:
result.Append("column");
break;
case NGPhysicalFragment::NGBoxType::kAtomicInline:
result.Append("atomic-inline");
break;
case NGPhysicalFragment::NGBoxType::kFloating:
result.Append("floating");
break;
case NGPhysicalFragment::NGBoxType::kOutOfFlowPositioned:
result.Append("out-of-flow-positioned");
break;
case NGPhysicalFragment::NGBoxType::kBlockFlowRoot:
result.Append("block-flow-root");
break;
case NGPhysicalFragment::NGBoxType::kRenderedLegend:
result.Append("rendered-legend");
break;
}
if (fragment.IsLegacyLayoutRoot()) {
if (result.length())
result.Append(" ");
result.Append("legacy-layout-root");
}
if (fragment.IsBlockFlow()) {
if (result.length())
result.Append(" ");
result.Append("block-flow");
}
if (fragment.IsFieldsetContainer()) {
if (result.length())
result.Append(" ");
result.Append("fieldset-container");
}
if (fragment.IsBox() &&
To<NGPhysicalBoxFragment>(fragment).IsInlineFormattingContext()) {
if (result.length())
result.Append(" ");
result.Append("children-inline");
}
return result.ToString();
}
class FragmentTreeDumper {
STACK_ALLOCATED();
public:
FragmentTreeDumper(StringBuilder* builder,
NGPhysicalFragment::DumpFlags flags)
: builder_(builder), flags_(flags) {}
void Append(const NGPhysicalFragment* fragment,
base::Optional<PhysicalOffset> fragment_offset,
unsigned indent = 2) {
AppendIndentation(indent);
bool has_content = false;
if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(fragment)) {
const LayoutObject* layout_object = box->GetLayoutObject();
if (flags_ & NGPhysicalFragment::DumpType) {
builder_->Append("Box");
String box_type = StringForBoxType(*fragment);
has_content = true;
if (!box_type.IsEmpty()) {
builder_->Append(" (");
builder_->Append(box_type);
builder_->Append(")");
}
if (flags_ & NGPhysicalFragment::DumpSelfPainting &&
box->HasSelfPaintingLayer()) {
if (box_type.IsEmpty())
builder_->Append(" ");
builder_->Append("(self paint)");
}
}
has_content = AppendOffsetAndSize(fragment, fragment_offset, has_content);
if (flags_ & NGPhysicalFragment::DumpNodeName && layout_object) {
if (has_content)
builder_->Append(" ");
builder_->Append(layout_object->DebugName());
}
builder_->Append("\n");
bool has_fragment_items = false;
if (flags_ & NGPhysicalFragment::DumpItems) {
if (const NGFragmentItems* fragment_items = box->Items()) {
NGInlineCursor cursor(*box, *fragment_items);
Append(&cursor, indent + 2);
has_fragment_items = true;
}
}
if (flags_ & NGPhysicalFragment::DumpSubtree) {
if (flags_ & NGPhysicalFragment::DumpLegacyDescendants &&
layout_object && !layout_object->IsLayoutNGObject()) {
DCHECK(box->Children().empty());
AppendLegacySubtree(*layout_object, indent);
return;
}
for (auto& child : box->Children()) {
if (has_fragment_items && child->IsLineBox())
continue;
Append(child.get(), child.Offset(), indent + 2);
}
}
return;
}
if (const auto* line_box = DynamicTo<NGPhysicalLineBoxFragment>(fragment)) {
if (flags_ & NGPhysicalFragment::DumpType) {
builder_->Append("LineBox");
has_content = true;
}
has_content = AppendOffsetAndSize(fragment, fragment_offset, has_content);
builder_->Append("\n");
if (flags_ & NGPhysicalFragment::DumpSubtree) {
for (auto& child : line_box->Children()) {
Append(child.get(), child.Offset(), indent + 2);
}
return;
}
}
if (flags_ & NGPhysicalFragment::DumpType) {
builder_->Append("Unknown fragment type");
has_content = true;
}
has_content = AppendOffsetAndSize(fragment, fragment_offset, has_content);
builder_->Append("\n");
}
void AppendLegacySubtree(const LayoutObject& layout_object,
unsigned indent = 0) {
for (const LayoutObject* descendant = &layout_object; descendant;) {
if (!descendant->IsLayoutNGObject()) {
if (const auto* block = DynamicTo<LayoutBlock>(descendant)) {
if (const auto* positioned_descendants = block->PositionedObjects()) {
for (const auto* positioned_object : *positioned_descendants) {
if (positioned_object->IsLayoutNGObject())
AppendNGRootInLegacySubtree(*positioned_object, indent);
else
AppendLegacySubtree(*positioned_object, indent);
}
}
}
if (descendant->IsOutOfFlowPositioned() && descendant != &layout_object)
descendant = descendant->NextInPreOrderAfterChildren(&layout_object);
else
descendant = descendant->NextInPreOrder(&layout_object);
continue;
}
AppendNGRootInLegacySubtree(*descendant, indent);
descendant = descendant->NextInPreOrderAfterChildren(&layout_object);
}
}
void AppendNGRootInLegacySubtree(const LayoutObject& layout_object,
unsigned indent) {
if (flags_ & NGPhysicalFragment::DumpHeaderText) {
AppendIndentation(indent + 2);
builder_->Append("(NG fragment root inside legacy subtree:)\n");
}
const LayoutBox& box_descendant = To<LayoutBox>(layout_object);
DCHECK_EQ(box_descendant.PhysicalFragmentCount(), 1u);
Append(box_descendant.GetPhysicalFragment(0), base::nullopt, indent + 4);
}
private:
void Append(NGInlineCursor* cursor, unsigned indent) {
for (; *cursor; cursor->MoveToNextSkippingChildren()) {
const NGInlineCursorPosition& current = cursor->Current();
if (const NGPhysicalBoxFragment* box = current.BoxFragment()) {
if (!box->IsInlineBox()) {
Append(box, current.OffsetInContainerFragment(), indent);
continue;
}
}
AppendIndentation(indent);
// TODO(kojii): Use the same format as layout tree dump for now. We can
// make this more similar to |AppendFragmentToString| above.
builder_->Append(current->ToString());
if (flags_ & NGPhysicalFragment::DumpOffset) {
builder_->Append(" offset:");
builder_->Append(current.OffsetInContainerFragment().ToString());
}
if (flags_ & NGPhysicalFragment::DumpSize) {
builder_->Append(" size:");
builder_->Append(current.Size().ToString());
}
builder_->Append("\n");
if (flags_ & NGPhysicalFragment::DumpSubtree && current.HasChildren()) {
NGInlineCursor descendants = cursor->CursorForDescendants();
Append(&descendants, indent + 2);
}
}
}
bool AppendOffsetAndSize(const NGPhysicalFragment* fragment,
base::Optional<PhysicalOffset> fragment_offset,
bool has_content) {
if (flags_ & NGPhysicalFragment::DumpOffset) {
if (has_content)
builder_->Append(" ");
builder_->Append("offset:");
if (fragment_offset)
builder_->Append(fragment_offset->ToString());
else
builder_->Append("unplaced");
has_content = true;
}
if (flags_ & NGPhysicalFragment::DumpSize) {
if (has_content)
builder_->Append(" ");
builder_->Append("size:");
builder_->Append(fragment->Size().ToString());
has_content = true;
}
return has_content;
}
void AppendIndentation(unsigned indent) {
if (flags_ & NGPhysicalFragment::DumpIndentation) {
for (unsigned i = 0; i < indent; i++)
builder_->Append(" ");
}
}
StringBuilder* builder_;
NGPhysicalFragment::DumpFlags flags_;
};
} // namespace
// static
void NGPhysicalFragmentTraits::Destruct(const NGPhysicalFragment* fragment) {
fragment->Destroy();
}
NGPhysicalFragment::NGPhysicalFragment(NGFragmentBuilder* builder,
NGFragmentType type,
unsigned sub_type)
: has_floating_descendants_for_paint_(false),
layout_object_(builder->layout_object_),
size_(ToPhysicalSize(builder->size_, builder->GetWritingMode())),
type_(type),
sub_type_(sub_type),
style_variant_((unsigned)builder->style_variant_),
is_hidden_for_paint_(builder->is_hidden_for_paint_),
is_fieldset_container_(false),
is_table_ng_part_(false),
is_legacy_layout_root_(false),
is_painted_atomically_(false),
has_collapsed_borders_(builder->has_collapsed_borders_),
has_baseline_(false) {
CHECK(builder->layout_object_);
}
NGPhysicalFragment::NGPhysicalFragment(LayoutObject* layout_object,
NGStyleVariant style_variant,
PhysicalSize size,
NGFragmentType type,
unsigned sub_type)
: has_floating_descendants_for_paint_(false),
has_layout_overflow_(false),
has_inflow_bounds_(false),
has_rare_data_(false),
layout_object_(layout_object),
size_(size),
type_(type),
sub_type_(sub_type),
style_variant_((unsigned)style_variant),
is_hidden_for_paint_(false),
is_fieldset_container_(false),
is_table_ng_part_(false),
is_legacy_layout_root_(false),
is_painted_atomically_(false),
has_collapsed_borders_(false),
has_baseline_(false),
has_last_baseline_(false) {
CHECK(layout_object);
}
// Even though the other constructors don't initialize many of these fields
// (instead set by their super-classes), the copy constructor does.
NGPhysicalFragment::NGPhysicalFragment(const NGPhysicalFragment& other)
: has_floating_descendants_for_paint_(
other.has_floating_descendants_for_paint_),
has_adjoining_object_descendants_(
other.has_adjoining_object_descendants_),
depends_on_percentage_block_size_(
other.depends_on_percentage_block_size_),
has_propagated_descendants_(other.has_propagated_descendants_),
has_hanging_(other.has_hanging_),
is_inline_formatting_context_(other.is_inline_formatting_context_),
has_fragment_items_(other.has_fragment_items_),
include_border_top_(other.include_border_top_),
include_border_right_(other.include_border_right_),
include_border_bottom_(other.include_border_bottom_),
include_border_left_(other.include_border_left_),
has_layout_overflow_(other.has_layout_overflow_),
has_borders_(other.has_borders_),
has_padding_(other.has_padding_),
has_inflow_bounds_(other.has_inflow_bounds_),
has_rare_data_(other.has_rare_data_),
is_first_for_node_(other.is_first_for_node_),
layout_object_(other.layout_object_),
size_(other.size_),
type_(other.type_),
sub_type_(other.sub_type_),
style_variant_(other.style_variant_),
is_hidden_for_paint_(other.is_hidden_for_paint_),
is_math_fraction_(other.is_math_fraction_),
is_math_operator_(other.is_math_operator_),
base_or_resolved_direction_(other.base_or_resolved_direction_),
may_have_descendant_above_block_start_(
other.may_have_descendant_above_block_start_),
is_fieldset_container_(other.is_fieldset_container_),
is_table_ng_part_(other.is_table_ng_part_),
is_legacy_layout_root_(other.is_legacy_layout_root_),
is_painted_atomically_(other.is_painted_atomically_),
has_collapsed_borders_(other.has_collapsed_borders_),
has_baseline_(other.has_baseline_),
has_last_baseline_(other.has_last_baseline_) {
CHECK(layout_object_);
}
// Keep the implementation of the destructor here, to avoid dependencies on
// ComputedStyle in the header file.
NGPhysicalFragment::~NGPhysicalFragment() = default;
void NGPhysicalFragment::Destroy() const {
switch (Type()) {
case kFragmentBox:
delete static_cast<const NGPhysicalBoxFragment*>(this);
break;
case kFragmentLineBox:
delete static_cast<const NGPhysicalLineBoxFragment*>(this);
break;
}
}
bool NGPhysicalFragment::IsBlockFlow() const {
return !IsLineBox() && layout_object_->IsLayoutBlockFlow();
}
bool NGPhysicalFragment::IsTextControlContainer() const {
return blink::IsTextControlContainer(layout_object_->GetNode());
}
bool NGPhysicalFragment::IsTextControlPlaceholder() const {
return blink::IsTextControlPlaceholder(layout_object_->GetNode());
}
bool NGPhysicalFragment::IsPlacedByLayoutNG() const {
// TODO(kojii): Move this to a flag for |LayoutNGBlockFlow::UpdateBlockLayout|
// to set.
if (IsLineBox())
return false;
if (IsFragmentainerBox())
return true;
const LayoutBlock* container = layout_object_->ContainingBlock();
if (!container)
return false;
return container->IsLayoutNGMixin();
}
const FragmentData* NGPhysicalFragment::GetFragmentData() const {
DCHECK(CanTraverse());
const LayoutBox* box = DynamicTo<LayoutBox>(GetLayoutObject());
if (!box) {
DCHECK(!GetLayoutObject());
return nullptr;
}
return box->FragmentDataFromPhysicalFragment(
To<NGPhysicalBoxFragment>(*this));
}
const NGPhysicalFragment* NGPhysicalFragment::PostLayout() const {
if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(this))
return box->PostLayout();
return this;
}
#if DCHECK_IS_ON()
void NGPhysicalFragment::CheckType() const {
switch (Type()) {
case kFragmentBox:
if (IsInlineBox()) {
DCHECK(layout_object_->IsLayoutInline());
} else {
DCHECK(layout_object_->IsBox());
}
if (IsColumnBox()) {
// Column fragments are associated with the same layout object as their
// multicol container. The fragments themselves are regular in-flow
// block container fragments for most purposes.
DCHECK(layout_object_->IsLayoutBlockFlow());
DCHECK(IsBox());
DCHECK(!IsFloating());
DCHECK(!IsOutOfFlowPositioned());
DCHECK(!IsAtomicInline());
DCHECK(!IsFormattingContextRoot());
break;
}
if (layout_object_->IsLayoutNGOutsideListMarker()) {
// List marker is an atomic inline if it appears in a line box, or a
// block box.
DCHECK(!IsFloating());
DCHECK(!IsOutOfFlowPositioned());
DCHECK(IsAtomicInline() || (IsBox() && BoxType() == kBlockFlowRoot));
break;
}
DCHECK_EQ(IsFloating(), layout_object_->IsFloating());
DCHECK_EQ(IsOutOfFlowPositioned(),
layout_object_->IsOutOfFlowPositioned());
DCHECK_EQ(IsAtomicInline(), layout_object_->IsInline() &&
layout_object_->IsAtomicInlineLevel());
break;
case kFragmentLineBox:
DCHECK(layout_object_->IsLayoutBlockFlow());
DCHECK(!IsFloating());
DCHECK(!IsOutOfFlowPositioned());
DCHECK(!IsInlineBox());
DCHECK(!IsAtomicInline());
break;
}
}
void NGPhysicalFragment::CheckCanUpdateInkOverflow() const {
if (!GetLayoutObject())
return;
const DocumentLifecycle& lifecycle = GetDocument().Lifecycle();
DCHECK(lifecycle.GetState() >= DocumentLifecycle::kLayoutClean &&
lifecycle.GetState() < DocumentLifecycle::kCompositingAssignmentsClean)
<< lifecycle.GetState();
}
#endif
PhysicalRect NGPhysicalFragment::ScrollableOverflow(
const NGPhysicalBoxFragment& container,
TextHeightType height_type) const {
switch (Type()) {
case kFragmentBox:
return To<NGPhysicalBoxFragment>(*this).ScrollableOverflow(height_type);
case kFragmentLineBox:
NOTREACHED()
<< "You must call NGLineBoxFragment::ScrollableOverflow explicitly.";
break;
}
NOTREACHED();
return {{}, Size()};
}
PhysicalRect NGPhysicalFragment::ScrollableOverflowForPropagation(
const NGPhysicalBoxFragment& container,
TextHeightType height_type) const {
PhysicalRect overflow = ScrollableOverflow(container, height_type);
AdjustScrollableOverflowForPropagation(container, height_type, &overflow);
return overflow;
}
void NGPhysicalFragment::AdjustScrollableOverflowForPropagation(
const NGPhysicalBoxFragment& container,
TextHeightType height_type,
PhysicalRect* overflow) const {
DCHECK(!IsLineBox());
if (!IsCSSBox())
return;
if (UNLIKELY(IsLayoutObjectDestroyedOrMoved())) {
NOTREACHED();
return;
}
if (height_type == TextHeightType::kNormalHeight && Type() == kFragmentBox)
overflow->Unite({{}, Size()});
const LayoutObject* layout_object = GetLayoutObject();
DCHECK(layout_object);
const LayoutObject* container_layout_object = container.GetLayoutObject();
DCHECK(container_layout_object);
if (layout_object->ShouldUseTransformFromContainer(container_layout_object)) {
TransformationMatrix transform;
layout_object->GetTransformFromContainer(container_layout_object,
PhysicalOffset(), transform);
*overflow =
PhysicalRect::EnclosingRect(transform.MapRect(FloatRect(*overflow)));
}
}
const Vector<NGInlineItem>& NGPhysicalFragment::InlineItemsOfContainingBlock()
const {
DCHECK(IsInline());
DCHECK(GetLayoutObject());
LayoutBlockFlow* block_flow = GetLayoutObject()->ContainingNGBlockFlow();
// TODO(xiaochengh): Code below is copied from ng_offset_mapping.cc with
// modification. Unify them.
DCHECK(block_flow);
NGBlockNode block_node = NGBlockNode(block_flow);
DCHECK(block_node.IsInlineFormattingContextRoot());
DCHECK(block_node.CanUseNewLayout());
NGLayoutInputNode node = block_node.FirstChild();
// TODO(xiaochengh): Handle ::first-line.
return To<NGInlineNode>(node).ItemsData(false).items;
}
TouchAction NGPhysicalFragment::EffectiveAllowedTouchAction() const {
DCHECK(layout_object_);
return layout_object_->EffectiveAllowedTouchAction();
}
bool NGPhysicalFragment::InsideBlockingWheelEventHandler() const {
DCHECK(layout_object_);
return layout_object_->InsideBlockingWheelEventHandler();
}
UBiDiLevel NGPhysicalFragment::BidiLevel() const {
switch (Type()) {
case kFragmentBox:
return To<NGPhysicalBoxFragment>(*this).BidiLevel();
case kFragmentLineBox:
break;
}
NOTREACHED();
return 0;
}
TextDirection NGPhysicalFragment::ResolvedDirection() const {
switch (Type()) {
case kFragmentBox:
DCHECK(IsInline() && IsAtomicInline());
// TODO(xiaochengh): Store direction in |base_direction_| flag.
return DirectionFromLevel(BidiLevel());
case kFragmentLineBox:
break;
}
NOTREACHED();
return TextDirection::kLtr;
}
bool NGPhysicalFragment::ShouldPaintCursorCaret() const {
// TODO(xiaochengh): Merge cursor caret painting functions from LayoutBlock to
// FrameSelection.
if (const auto* block = DynamicTo<LayoutBlock>(GetLayoutObject()))
return block->ShouldPaintCursorCaret();
return false;
}
bool NGPhysicalFragment::ShouldPaintDragCaret() const {
// TODO(xiaochengh): Merge drag caret painting functions from LayoutBlock to
// DragCaret.
if (const auto* block = DynamicTo<LayoutBlock>(GetLayoutObject()))
return block->ShouldPaintDragCaret();
return false;
}
LogicalRect NGPhysicalFragment::ConvertChildToLogical(
const PhysicalRect& physical_rect) const {
return WritingModeConverter(Style().GetWritingDirection(), Size())
.ToLogical(physical_rect);
}
PhysicalRect NGPhysicalFragment::ConvertChildToPhysical(
const LogicalRect& logical_rect) const {
return WritingModeConverter(Style().GetWritingDirection(), Size())
.ToPhysical(logical_rect);
}
String NGPhysicalFragment::ToString() const {
StringBuilder output;
output.AppendFormat("Type: '%d' Size: '%s'", Type(),
Size().ToString().Ascii().c_str());
switch (Type()) {
case kFragmentBox:
output.AppendFormat(", BoxType: '%s'",
StringForBoxType(*this).Ascii().c_str());
break;
case kFragmentLineBox:
break;
}
return output.ToString();
}
String NGPhysicalFragment::DumpFragmentTree(
DumpFlags flags,
base::Optional<PhysicalOffset> fragment_offset,
unsigned indent) const {
StringBuilder string_builder;
if (flags & DumpHeaderText)
string_builder.Append(".:: LayoutNG Physical Fragment Tree ::.\n");
FragmentTreeDumper(&string_builder, flags)
.Append(this, fragment_offset, indent);
return string_builder.ToString();
}
String NGPhysicalFragment::DumpFragmentTree(const LayoutObject& root,
DumpFlags flags) {
if (root.IsLayoutNGObject()) {
const LayoutBox& root_box = To<LayoutBox>(root);
DCHECK_EQ(root_box.PhysicalFragmentCount(), 1u);
return root_box.GetPhysicalFragment(0)->DumpFragmentTree(flags);
}
StringBuilder string_builder;
if (flags & DumpHeaderText) {
string_builder.Append(
".:: LayoutNG Physical Fragment Tree at legacy root ");
string_builder.Append(root.DebugName());
string_builder.Append(" ::.\n");
}
FragmentTreeDumper(&string_builder, flags).AppendLegacySubtree(root);
return string_builder.ToString();
}
#if DCHECK_IS_ON()
void NGPhysicalFragment::ShowFragmentTree() const {
DumpFlags dump_flags = DumpAll;
LOG(INFO) << "\n" << DumpFragmentTree(dump_flags).Utf8();
}
void NGPhysicalFragment::ShowFragmentTree(const LayoutObject& root) {
DumpFlags dump_flags = DumpAll;
LOG(INFO) << "\n" << DumpFragmentTree(root, dump_flags).Utf8();
}
#endif
PhysicalRect NGPhysicalFragmentWithOffset::RectInContainerBox() const {
return {offset_to_container_box, fragment->Size()};
}
std::ostream& operator<<(std::ostream& out,
const NGPhysicalFragment& fragment) {
return out << fragment.ToString();
}
std::ostream& operator<<(std::ostream& out,
const NGPhysicalFragment* fragment) {
if (!fragment)
return out << "<null>";
return out << *fragment;
}
} // namespace blink