blob: f3aeb4d96075ebd4fe77994b60f81c5b5c7c178c [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/LayoutMultiColumnFlowThread.h"
#include "core/layout/LayoutMultiColumnSet.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_break_token.h"
#include "core/layout/ng/ng_block_layout_algorithm.h"
#include "core/layout/ng/ng_box_fragment.h"
#include "core/layout/ng/ng_column_layout_algorithm.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_min_max_content_size.h"
#include "core/layout/ng/ng_writing_mode.h"
#include "core/paint/PaintLayer.h"
#include "platform/RuntimeEnabledFeatures.h"
namespace blink {
namespace {
RefPtr<NGLayoutResult> LayoutWithAlgorithm(const ComputedStyle& style,
NGBlockNode* node,
NGConstraintSpace* space,
NGBreakToken* break_token) {
if (style.specifiesColumns())
return NGColumnLayoutAlgorithm(node, space,
toNGBlockBreakToken(break_token))
.Layout();
return NGBlockLayoutAlgorithm(node, space, toNGBlockBreakToken(break_token))
.Layout();
}
// 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.";
// LegacyLayout flips vertical-rl horizontal coordinates before paint.
// NGLayout flips X location for LegacyLayout compatibility.
LayoutBlock* containing_block = layout_box->containingBlock();
if (containing_block->styleRef().isFlippedBlocksWritingMode()) {
LayoutUnit container_width = containing_block->size().width();
layout_box->setX(container_width - fragment.LeftOffset() -
fragment.Width());
} else {
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->setIsInPlacedTree(false);
floating_object->setX(ng_floating_object->left_offset);
floating_object->setY(box_fragment->TopOffset());
floating_object->setIsPlaced(true);
floating_object->setIsInPlacedTree(true);
}
}
void UpdateLegacyMultiColumnFlowThread(LayoutBox* layout_box,
const NGPhysicalBoxFragment* fragment) {
LayoutBlockFlow* multicol = toLayoutBlockFlow(layout_box);
LayoutMultiColumnFlowThread* flow_thread = multicol->multiColumnFlowThread();
if (!flow_thread)
return;
if (LayoutMultiColumnSet* column_set = flow_thread->firstMultiColumnSet()) {
column_set->setWidth(fragment->Width());
column_set->setHeight(fragment->Height());
// TODO(mstensho): This value has next to nothing to do with the flow thread
// portion size, but at least it's usually better than zero.
column_set->endFlow(fragment->Height());
column_set->clearNeedsLayout();
}
// TODO(mstensho): Fix the relatively nonsensical values here (the content box
// size of the multicol container has very little to do with the price of
// eggs).
flow_thread->setWidth(fragment->Width());
flow_thread->setHeight(fragment->Height());
flow_thread->validateColumnSets();
flow_thread->clearNeedsLayout();
}
} // namespace
NGBlockNode::NGBlockNode(LayoutObject* layout_object)
: NGLayoutInputNode(NGLayoutInputNodeType::kLegacyBlock),
layout_box_(toLayoutBox(layout_object)) {
DCHECK(layout_box_);
}
// 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,
NGBreakToken* break_token) {
// Use the old layout code and synthesize a fragment.
if (!CanUseNewLayout()) {
return RunOldLayout(*constraint_space);
}
RefPtr<NGLayoutResult> layout_result =
LayoutWithAlgorithm(Style(), this, constraint_space, break_token);
CopyFragmentDataToLayoutBox(*constraint_space, layout_result.get());
return layout_result;
}
MinMaxContentSize NGBlockNode::ComputeMinMaxContentSize() {
MinMaxContentSize sizes;
if (!CanUseNewLayout()) {
// 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;
}
RefPtr<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.get());
Optional<MinMaxContentSize> maybe_sizes =
minmax_algorithm.ComputeMinMaxContentSize();
if (maybe_sizes.has_value())
return *maybe_sizes;
// Have to synthesize this value.
RefPtr<NGLayoutResult> layout_result = Layout(constraint_space.get());
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.get());
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 {
return layout_box_->styleRef();
}
NGLayoutInputNode* NGBlockNode::NextSibling() {
if (!next_sibling_) {
LayoutObject* next_sibling = layout_box_->nextSibling();
if (next_sibling) {
if (next_sibling->isInline()) {
next_sibling_ = new NGInlineNode(
next_sibling, toLayoutBlockFlow(layout_box_->parent()));
} else {
next_sibling_ = new NGBlockNode(next_sibling);
}
}
}
return next_sibling_;
}
LayoutObject* NGBlockNode::GetLayoutObject() {
return layout_box_;
}
NGLayoutInputNode* NGBlockNode::FirstChild() {
if (!first_child_) {
LayoutObject* child = layout_box_->slowFirstChild();
if (child) {
if (child->isInline()) {
first_child_ = new NGInlineNode(child, toLayoutBlockFlow(layout_box_));
} else {
first_child_ = new NGBlockNode(child);
}
}
}
return first_child_;
}
DEFINE_TRACE(NGBlockNode) {
visitor->trace(next_sibling_);
visitor->trace(first_child_);
NGLayoutInputNode::trace(visitor);
}
bool NGBlockNode::CanUseNewLayout() {
// [Multicol]: for the 1st phase of LayoutNG's multicol implementation we want
// to utilize the existing ColumnBalancer class. That's why a multicol block
// should be processed by Legacy Layout engine.
if (Style().specifiesColumns())
return false;
if (!layout_box_->isLayoutBlockFlow())
return false;
return RuntimeEnabledFeatures::layoutNGEnabled() || !HasInlineChildren();
}
bool NGBlockNode::HasInlineChildren() {
if (!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,
NGLayoutResult* layout_result) {
NGPhysicalBoxFragment* fragment =
toNGPhysicalBoxFragment(layout_result->PhysicalFragment().get());
if (layout_box_->style()->specifiesColumns())
UpdateLegacyMultiColumnFlowThread(layout_box_, fragment);
layout_box_->setWidth(fragment->Width());
layout_box_->setHeight(fragment->Height());
NGBoxStrut border_and_padding = ComputeBorders(constraint_space, 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 RefPtr<NGFloatingObject>& floating_object :
fragment->PositionedFloats()) {
FloatingObjectPositionedUpdated(floating_object.get(), layout_box_);
}
}
for (const auto& child_fragment : fragment->Children()) {
if (child_fragment->IsPlaced())
FragmentPositionUpdated(toNGPhysicalBoxFragment(*child_fragment));
for (const RefPtr<NGFloatingObject>& floating_object :
toNGPhysicalBoxFragment(child_fragment.get())->PositionedFloats()) {
FloatingObjectPositionedUpdated(
floating_object.get(),
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_->isOutOfFlowPositioned());
layout_box_->containingBlock()->insertPositionedObject(layout_box_);
}
// Save static position for legacy AbsPos layout.
void NGBlockNode::SaveStaticOffsetForLegacy(const NGLogicalOffset& offset) {
DCHECK(layout_box_->isOutOfFlowPositioned());
DCHECK(layout_box_->layer());
layout_box_->layer()->setStaticBlockPosition(offset.block_offset);
layout_box_->layer()->setStaticInlinePosition(offset.inline_offset);
}
} // namespace blink