blob: 0d365228ab5da6a1ed7311aac8cb83b1866d7882 [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 "core/layout/ng/ng_block_node.h"
#include "core/layout/LayoutBlockFlow.h"
#include "core/layout/api/LineLayoutAPIShim.h"
#include "core/layout/line/InlineIterator.h"
#include "core/layout/ng/layout_ng_block_flow.h"
#include "core/layout/ng/ng_block_layout_algorithm.h"
#include "core/layout/ng/ng_box_fragment.h"
#include "core/layout/ng/ng_constraint_space.h"
#include "core/layout/ng/ng_constraint_space_builder.h"
#include "core/layout/ng/ng_fragment_builder.h"
#include "core/layout/ng/ng_inline_node.h"
#include "core/layout/ng/ng_layout_result.h"
#include "core/layout/ng/ng_length_utils.h"
#include "core/layout/ng/ng_writing_mode.h"
#include "core/paint/PaintLayer.h"
#include "platform/RuntimeEnabledFeatures.h"
namespace blink {
namespace {
// Copies data back to the legacy layout tree for a given child fragment.
void FragmentPositionUpdated(const NGPhysicalFragment& fragment) {
LayoutBox* layout_box = toLayoutBox(fragment.GetLayoutObject());
if (!layout_box)
return;
DCHECK(layout_box->parent()) << "Should be called on children only.";
layout_box->setX(fragment.LeftOffset());
layout_box->setY(fragment.TopOffset());
}
// Similar to FragmentPositionUpdated but for floats.
// - Updates layout object's geometric information.
// - Creates legacy FloatingObject and attached it to the provided parent.
void FloatingObjectPositionedUpdated(NGFloatingObject* ng_floating_object,
LayoutBox* parent) {
NGPhysicalBoxFragment* box_fragment =
toNGPhysicalBoxFragment(ng_floating_object->fragment.get());
FragmentPositionUpdated(*box_fragment);
LayoutBox* layout_box = toLayoutBox(box_fragment->GetLayoutObject());
DCHECK(layout_box->isFloating());
if (parent && parent->isLayoutBlockFlow()) {
FloatingObject* floating_object =
toLayoutBlockFlow(parent)->insertFloatingObject(*layout_box);
floating_object->setX(ng_floating_object->left_offset);
floating_object->setY(box_fragment->TopOffset());
floating_object->setIsPlaced(true);
}
}
} // namespace
NGBlockNode::NGBlockNode(LayoutObject* layout_object)
: NGLayoutInputNode(NGLayoutInputNodeType::kLegacyBlock),
layout_box_(toLayoutBox(layout_object)) {
DCHECK(layout_box_);
}
NGBlockNode::NGBlockNode(ComputedStyle* style)
: NGLayoutInputNode(NGLayoutInputNodeType::kLegacyBlock),
layout_box_(nullptr),
style_(style) {
DCHECK(style_);
}
// Need an explicit destructor in the .cc file, or the MSWIN compiler will
// produce an error when attempting to generate a default one, if the .h file is
// included from a compilation unit that lacks the ComputedStyle definition.
NGBlockNode::~NGBlockNode() {}
RefPtr<NGLayoutResult> NGBlockNode::Layout(
NGConstraintSpace* constraint_space) {
// Use the old layout code and synthesize a fragment.
if (!CanUseNewLayout()) {
DCHECK(layout_box_);
layout_result_ = RunOldLayout(*constraint_space);
return layout_result_;
}
layout_result_ =
NGBlockLayoutAlgorithm(this, constraint_space, CurrentBreakToken())
.Layout();
CopyFragmentDataToLayoutBox(*constraint_space);
return layout_result_;
}
MinAndMaxContentSizes NGBlockNode::ComputeMinAndMaxContentSizes() {
MinAndMaxContentSizes sizes;
if (!CanUseNewLayout()) {
DCHECK(layout_box_);
// TODO(layout-ng): This could be somewhat optimized by directly calling
// computeIntrinsicLogicalWidths, but that function is currently private.
// Consider doing that if this becomes a performance issue.
LayoutUnit borderAndPadding = layout_box_->borderAndPaddingLogicalWidth();
sizes.min_content = layout_box_->computeLogicalWidthUsing(
MainOrPreferredSize, Length(MinContent),
LayoutUnit(), layout_box_->containingBlock()) -
borderAndPadding;
sizes.max_content = layout_box_->computeLogicalWidthUsing(
MainOrPreferredSize, Length(MaxContent),
LayoutUnit(), layout_box_->containingBlock()) -
borderAndPadding;
return sizes;
}
NGConstraintSpace* constraint_space =
NGConstraintSpaceBuilder(
FromPlatformWritingMode(Style().getWritingMode()))
.SetTextDirection(Style().direction())
.ToConstraintSpace(FromPlatformWritingMode(Style().getWritingMode()));
// TODO(cbiesinger): For orthogonal children, we need to always synthesize.
NGBlockLayoutAlgorithm minmax_algorithm(this, constraint_space);
Optional<MinAndMaxContentSizes> maybe_sizes =
minmax_algorithm.ComputeMinAndMaxContentSizes();
if (maybe_sizes.has_value())
return *maybe_sizes;
// Have to synthesize this value.
RefPtr<NGLayoutResult> layout_result = Layout(constraint_space);
NGPhysicalFragment* physical_fragment =
layout_result->PhysicalFragment().get();
NGBoxFragment min_fragment(FromPlatformWritingMode(Style().getWritingMode()),
toNGPhysicalBoxFragment(physical_fragment));
sizes.min_content = min_fragment.InlineOverflow();
// Now, redo with infinite space for max_content
constraint_space =
NGConstraintSpaceBuilder(
FromPlatformWritingMode(Style().getWritingMode()))
.SetTextDirection(Style().direction())
.SetAvailableSize({LayoutUnit::max(), LayoutUnit()})
.SetPercentageResolutionSize({LayoutUnit(), LayoutUnit()})
.ToConstraintSpace(FromPlatformWritingMode(Style().getWritingMode()));
layout_result = Layout(constraint_space);
physical_fragment = layout_result->PhysicalFragment().get();
NGBoxFragment max_fragment(FromPlatformWritingMode(Style().getWritingMode()),
toNGPhysicalBoxFragment(physical_fragment));
sizes.max_content = max_fragment.InlineOverflow();
return sizes;
}
const ComputedStyle& NGBlockNode::Style() const {
if (style_)
return *style_.get();
DCHECK(layout_box_);
return layout_box_->styleRef();
}
NGLayoutInputNode* NGBlockNode::NextSibling() {
if (!next_sibling_) {
LayoutObject* next_sibling =
layout_box_ ? layout_box_->nextSibling() : nullptr;
if (next_sibling) {
if (next_sibling->isInline())
SetNextSibling(new NGInlineNode(next_sibling, &Style()));
else
SetNextSibling(new NGBlockNode(next_sibling));
}
}
return next_sibling_;
}
LayoutObject* NGBlockNode::GetLayoutObject() {
return layout_box_;
}
NGLayoutInputNode* NGBlockNode::FirstChild() {
if (!first_child_) {
LayoutObject* child = layout_box_ ? layout_box_->slowFirstChild() : nullptr;
if (child) {
if (child->isInline()) {
SetFirstChild(new NGInlineNode(child, &Style()));
} else {
SetFirstChild(new NGBlockNode(child));
}
}
}
return first_child_;
}
void NGBlockNode::SetNextSibling(NGLayoutInputNode* sibling) {
next_sibling_ = sibling;
}
void NGBlockNode::SetFirstChild(NGLayoutInputNode* child) {
first_child_ = child;
}
NGBreakToken* NGBlockNode::CurrentBreakToken() const {
return layout_result_ ? layout_result_->PhysicalFragment()->BreakToken()
: nullptr;
}
DEFINE_TRACE(NGBlockNode) {
visitor->trace(next_sibling_);
visitor->trace(first_child_);
NGLayoutInputNode::trace(visitor);
}
bool NGBlockNode::CanUseNewLayout() {
if (!layout_box_)
return true;
if (!layout_box_->isLayoutBlockFlow())
return false;
return RuntimeEnabledFeatures::layoutNGInlineEnabled() ||
!HasInlineChildren();
}
bool NGBlockNode::HasInlineChildren() {
if (!layout_box_ || !layout_box_->isLayoutBlockFlow())
return false;
const LayoutBlockFlow* block_flow = toLayoutBlockFlow(layout_box_);
if (!block_flow->childrenInline())
return false;
LayoutObject* child = block_flow->firstChild();
while (child) {
if (child->isInline())
return true;
child = child->nextSibling();
}
return false;
}
void NGBlockNode::CopyFragmentDataToLayoutBox(
const NGConstraintSpace& constraint_space) {
// We may not have a layout_box_ during unit tests.
if (!layout_box_)
return;
NGPhysicalBoxFragment* fragment =
toNGPhysicalBoxFragment(layout_result_->PhysicalFragment().get());
layout_box_->setWidth(fragment->Width());
layout_box_->setHeight(fragment->Height());
NGBoxStrut border_and_padding =
ComputeBorders(Style()) + ComputePadding(constraint_space, Style());
LayoutUnit intrinsic_logical_height =
layout_box_->style()->isHorizontalWritingMode()
? fragment->HeightOverflow()
: fragment->WidthOverflow();
intrinsic_logical_height -= border_and_padding.BlockSum();
layout_box_->setIntrinsicContentLogicalHeight(intrinsic_logical_height);
// We may still have unpositioned floats when we reach the root box.
if (!layout_box_->parent()) {
for (const auto& floating_object : fragment->PositionedFloats()) {
FloatingObjectPositionedUpdated(floating_object, layout_box_);
}
}
// TODO(layout-dev): Currently we are not actually performing layout on
// inline children. For now just clear the needsLayout bit so that we can
// run unittests.
if (HasInlineChildren()) {
for (InlineWalker walker(
LineLayoutBlockFlow(toLayoutBlockFlow(layout_box_)));
!walker.atEnd(); walker.advance()) {
LayoutObject* o = LineLayoutAPIShim::layoutObjectFrom(walker.current());
o->clearNeedsLayout();
}
// Ensure the position of the children are copied across to the
// LayoutObject tree.
} else {
for (const auto& child_fragment : fragment->Children()) {
if (child_fragment->IsPlaced())
FragmentPositionUpdated(toNGPhysicalBoxFragment(*child_fragment));
for (const auto& floating_object :
toNGPhysicalBoxFragment(child_fragment.get())->PositionedFloats()) {
FloatingObjectPositionedUpdated(
floating_object, toLayoutBox(child_fragment->GetLayoutObject()));
}
}
}
if (layout_box_->isLayoutBlock())
toLayoutBlock(layout_box_)->layoutPositionedObjects(true);
layout_box_->clearNeedsLayout();
if (layout_box_->isLayoutBlockFlow()) {
toLayoutBlockFlow(layout_box_)->updateIsSelfCollapsing();
}
}
RefPtr<NGLayoutResult> NGBlockNode::RunOldLayout(
const NGConstraintSpace& constraint_space) {
NGLogicalSize available_size = constraint_space.PercentageResolutionSize();
LayoutObject* containing_block = layout_box_->containingBlock();
bool parallel_writing_mode;
if (!containing_block) {
parallel_writing_mode = true;
} else {
parallel_writing_mode = IsParallelWritingMode(
FromPlatformWritingMode(containing_block->styleRef().getWritingMode()),
FromPlatformWritingMode(Style().getWritingMode()));
}
if (parallel_writing_mode) {
layout_box_->setOverrideContainingBlockContentLogicalWidth(
available_size.inline_size);
layout_box_->setOverrideContainingBlockContentLogicalHeight(
available_size.block_size);
} else {
// OverrideContainingBlock should be in containing block writing mode.
layout_box_->setOverrideContainingBlockContentLogicalWidth(
available_size.block_size);
layout_box_->setOverrideContainingBlockContentLogicalHeight(
available_size.inline_size);
}
// TODO(layout-ng): Does this handle scrollbars correctly?
if (constraint_space.IsFixedSizeInline()) {
layout_box_->setOverrideLogicalContentWidth(
constraint_space.AvailableSize().inline_size -
layout_box_->borderAndPaddingLogicalWidth());
}
if (constraint_space.IsFixedSizeBlock()) {
layout_box_->setOverrideLogicalContentHeight(
constraint_space.AvailableSize().block_size -
layout_box_->borderAndPaddingLogicalHeight());
}
if (layout_box_->isLayoutNGBlockFlow() && layout_box_->needsLayout()) {
toLayoutNGBlockFlow(layout_box_)->LayoutBlockFlow::layoutBlock(true);
} else {
layout_box_->forceLayout();
}
LayoutRect overflow = layout_box_->layoutOverflowRect();
// TODO(layout-ng): This does not handle writing modes correctly (for
// overflow)
NGFragmentBuilder builder(NGPhysicalFragment::kFragmentBox, this);
builder.SetInlineSize(layout_box_->logicalWidth())
.SetBlockSize(layout_box_->logicalHeight())
.SetDirection(layout_box_->styleRef().direction())
.SetWritingMode(
FromPlatformWritingMode(layout_box_->styleRef().getWritingMode()))
.SetInlineOverflow(overflow.width())
.SetBlockOverflow(overflow.height());
return builder.ToBoxFragment();
}
void NGBlockNode::UseOldOutOfFlowPositioning() {
DCHECK(layout_box_);
DCHECK(layout_box_->isOutOfFlowPositioned());
layout_box_->containingBlock()->insertPositionedObject(layout_box_);
}
// Save static position for legacy AbsPos layout.
void NGBlockNode::SaveStaticOffsetForLegacy(const NGLogicalOffset& offset) {
DCHECK(layout_box_);
DCHECK(layout_box_->isOutOfFlowPositioned());
DCHECK(layout_box_->layer());
layout_box_->layer()->setStaticBlockPosition(offset.block_offset);
layout_box_->layer()->setStaticInlinePosition(offset.inline_offset);
}
} // namespace blink