blob: 38d74b7a4572443d908a7508b9174132cc466712 [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/MinMaxSize.h"
#include "core/layout/ng/inline/ng_inline_node.h"
#include "core/layout/ng/layout_ng_block_flow.h"
#include "core/layout/ng/legacy_layout_tree_walking.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_layout_input_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 {
RefPtr<NGLayoutResult> LayoutWithAlgorithm(const ComputedStyle& style,
NGBlockNode node,
const NGConstraintSpace& space,
NGBreakToken* break_token) {
if (style.SpecifiesColumns())
return NGColumnLayoutAlgorithm(node, space,
ToNGBlockBreakToken(break_token))
.Layout();
return NGBlockLayoutAlgorithm(node, space, ToNGBlockBreakToken(break_token))
.Layout();
}
bool IsFloatFragment(const NGPhysicalFragment& fragment) {
const LayoutObject* layout_object = fragment.GetLayoutObject();
return layout_object && layout_object->IsFloating() && fragment.IsBox();
}
void UpdateLegacyMultiColumnFlowThread(
LayoutBox* layout_box,
const NGConstraintSpace& constraint_space,
const NGPhysicalBoxFragment& fragment) {
LayoutBlockFlow* multicol = ToLayoutBlockFlow(layout_box);
LayoutMultiColumnFlowThread* flow_thread = multicol->MultiColumnFlowThread();
if (!flow_thread)
return;
NGWritingMode writing_mode = constraint_space.WritingMode();
LayoutUnit column_inline_size;
LayoutUnit flow_end;
// Stitch the columns together.
for (const RefPtr<NGPhysicalFragment> child : fragment.Children()) {
NGFragment child_fragment(writing_mode, *child);
flow_end += child_fragment.BlockSize();
column_inline_size = child_fragment.InlineSize();
}
if (LayoutMultiColumnSet* column_set = flow_thread->FirstMultiColumnSet()) {
NGFragment logical_fragment(writing_mode, fragment);
column_set->SetLogicalWidth(logical_fragment.InlineSize());
column_set->SetLogicalHeight(logical_fragment.BlockSize());
column_set->EndFlow(flow_end);
column_set->UpdateFromNG();
}
// TODO(mstensho): Update all column boxes, not just the first column set
// (like we do above). This is needed to support column-span:all.
for (LayoutBox* column_box = flow_thread->FirstMultiColumnBox(); column_box;
column_box = column_box->NextSiblingMultiColumnBox())
column_box->ClearNeedsLayout();
flow_thread->ValidateColumnSets();
flow_thread->SetLogicalWidth(column_inline_size);
flow_thread->SetLogicalHeight(flow_end);
flow_thread->ClearNeedsLayout();
}
// Return true if the specified fragment is the first generated fragment of
// some node.
bool IsFirstFragment(const NGConstraintSpace& constraint_space,
const NGPhysicalBoxFragment& fragment) {
const auto* break_token = ToNGBlockBreakToken(fragment.BreakToken());
if (!break_token)
return true;
NGFragment logical_fragment(constraint_space.WritingMode(), fragment);
return break_token->UsedBlockSize() <= logical_fragment.BlockSize();
}
// Return true if the specified fragment is the final fragment of some node.
bool IsLastFragment(const NGPhysicalBoxFragment& fragment) {
const auto* break_token = fragment.BreakToken();
return !break_token || break_token->IsFinished();
}
} // namespace
NGBlockNode::NGBlockNode(LayoutBox* box) : NGLayoutInputNode(box, kBlock) {}
RefPtr<NGLayoutResult> NGBlockNode::Layout(
const 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;
if (box_->IsLayoutNGBlockFlow()) {
layout_result = ToLayoutNGBlockFlow(box_)->CachedLayoutResult(
constraint_space, break_token);
if (layout_result)
return layout_result;
}
layout_result =
LayoutWithAlgorithm(Style(), *this, constraint_space, break_token);
if (box_->IsLayoutNGBlockFlow()) {
ToLayoutNGBlockFlow(box_)->SetCachedLayoutResult(
constraint_space, break_token, layout_result);
}
if (layout_result->Status() == NGLayoutResult::kSuccess &&
layout_result->UnpositionedFloats().IsEmpty())
CopyFragmentDataToLayoutBox(constraint_space, *layout_result);
return layout_result;
}
MinMaxSize NGBlockNode::ComputeMinMaxSize() {
MinMaxSize 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 border_and_padding = box_->BorderAndPaddingLogicalWidth();
sizes.min_size = box_->ComputeLogicalWidthUsing(
kMainOrPreferredSize, Length(kMinContent),
LayoutUnit(), box_->ContainingBlock()) -
border_and_padding;
sizes.max_size = box_->ComputeLogicalWidthUsing(
kMainOrPreferredSize, Length(kMaxContent),
LayoutUnit(), box_->ContainingBlock()) -
border_and_padding;
return sizes;
}
RefPtr<NGConstraintSpace> constraint_space =
NGConstraintSpaceBuilder(
FromPlatformWritingMode(Style().GetWritingMode()),
/* icb_size */ {NGSizeIndefinite, NGSizeIndefinite})
.SetTextDirection(Style().Direction())
.ToConstraintSpace(FromPlatformWritingMode(Style().GetWritingMode()));
// TODO(cbiesinger): For orthogonal children, we need to always synthesize.
NGBlockLayoutAlgorithm minmax_algorithm(*this, *constraint_space);
Optional<MinMaxSize> maybe_sizes = minmax_algorithm.ComputeMinMaxSize();
if (maybe_sizes.has_value())
return *maybe_sizes;
// Have to synthesize this value.
RefPtr<NGLayoutResult> layout_result = Layout(*constraint_space);
NGBoxFragment min_fragment(
FromPlatformWritingMode(Style().GetWritingMode()),
ToNGPhysicalBoxFragment(*layout_result->PhysicalFragment()));
sizes.min_size = min_fragment.OverflowSize().inline_size;
// Now, redo with infinite space for max_content
constraint_space =
NGConstraintSpaceBuilder(
FromPlatformWritingMode(Style().GetWritingMode()),
/* icb_size */ {NGSizeIndefinite, NGSizeIndefinite})
.SetTextDirection(Style().Direction())
.SetAvailableSize({LayoutUnit::Max(), LayoutUnit()})
.SetPercentageResolutionSize({LayoutUnit(), LayoutUnit()})
.ToConstraintSpace(FromPlatformWritingMode(Style().GetWritingMode()));
layout_result = Layout(*constraint_space);
NGBoxFragment max_fragment(
FromPlatformWritingMode(Style().GetWritingMode()),
ToNGPhysicalBoxFragment(*layout_result->PhysicalFragment()));
sizes.max_size = max_fragment.OverflowSize().inline_size;
return sizes;
}
NGLayoutInputNode NGBlockNode::NextSibling() const {
LayoutObject* next_sibling = box_->NextSibling();
if (next_sibling) {
DCHECK(!next_sibling->IsInline());
return NGBlockNode(ToLayoutBox(next_sibling));
}
return nullptr;
}
NGLayoutInputNode NGBlockNode::FirstChild() {
auto* block = ToLayoutNGBlockFlow(box_);
auto* child = GetLayoutObjectForFirstChildNode(block);
if (!child)
return nullptr;
if (AreNGBlockFlowChildrenInline(block))
return NGInlineNode(block);
return NGBlockNode(ToLayoutBox(child));
}
bool NGBlockNode::CanUseNewLayout() const {
if (!box_->IsLayoutNGBlockFlow())
return false;
return RuntimeEnabledFeatures::LayoutNGEnabled();
}
String NGBlockNode::ToString() const {
return String::Format("NGBlockNode: '%s'",
GetLayoutObject()->DebugName().Ascii().data());
}
void NGBlockNode::CopyFragmentDataToLayoutBox(
const NGConstraintSpace& constraint_space,
const NGLayoutResult& layout_result) {
DCHECK(layout_result.PhysicalFragment());
const NGPhysicalBoxFragment& physical_fragment =
ToNGPhysicalBoxFragment(*layout_result.PhysicalFragment());
if (box_->Style()->SpecifiesColumns()) {
UpdateLegacyMultiColumnFlowThread(box_, constraint_space,
physical_fragment);
}
NGBoxFragment fragment(constraint_space.WritingMode(), physical_fragment);
// For each fragment we process, we'll accumulate the logical height and
// logical intrinsic content box height. We reset it at the first fragment,
// and accumulate at each method call for fragments belonging to the same
// layout object. Logical width will only be set at the first fragment and is
// expected to remain the same throughout all subsequent fragments, since
// legacy layout doesn't support non-uniform fragmentainer widths.
LayoutUnit logical_height;
LayoutUnit intrinsic_content_logical_height;
if (IsFirstFragment(constraint_space, physical_fragment)) {
box_->SetLogicalWidth(fragment.InlineSize());
} else {
DCHECK_EQ(box_->LogicalWidth(), fragment.InlineSize())
<< "Variable fragment inline size not supported";
logical_height = box_->LogicalHeight();
intrinsic_content_logical_height = box_->IntrinsicContentLogicalHeight();
}
logical_height += fragment.BlockSize();
intrinsic_content_logical_height += fragment.OverflowSize().block_size;
NGBoxStrut border_scrollbar_padding =
ComputeBorders(constraint_space, Style()) +
ComputePadding(constraint_space, Style()) + GetScrollbarSizes(box_);
if (IsLastFragment(physical_fragment))
intrinsic_content_logical_height -= border_scrollbar_padding.BlockSum();
box_->SetLogicalHeight(logical_height);
box_->SetIntrinsicContentLogicalHeight(intrinsic_content_logical_height);
// LayoutBox::Margin*() should be used value, while we set computed value
// here. This is not entirely correct, but these values are not used for
// layout purpose.
// BaselinePosition() relies on margins set to the box, and computed value is
// good enough for it to work correctly.
// Set this only for atomic inlines, or we end up adding margins twice.
if (box_->IsAtomicInlineLevel()) {
NGBoxStrut margins =
ComputeMargins(constraint_space, Style(),
constraint_space.WritingMode(), Style().Direction());
box_->SetMarginBefore(margins.block_start);
box_->SetMarginAfter(margins.block_end);
box_->SetMarginStart(margins.inline_start);
box_->SetMarginEnd(margins.inline_end);
}
for (const auto& child_fragment : physical_fragment.Children()) {
DCHECK(child_fragment->IsPlaced());
// At the moment "anonymous" fragments for inline layout will have the same
// layout object as ourselves, we need to copy its floats across.
if (child_fragment->GetLayoutObject() == box_) {
for (const auto& maybe_float_fragment :
ToNGPhysicalBoxFragment(child_fragment.Get())->Children()) {
// The child of the anonymous fragment might be just a line-box
// fragment - ignore.
if (IsFloatFragment(*maybe_float_fragment)) {
// We need to include the anonymous fragments offset here for the
// correct position.
CopyChildFragmentPosition(
ToNGPhysicalBoxFragment(*maybe_float_fragment),
child_fragment->Offset());
}
}
} else {
const auto& box_fragment = *ToNGPhysicalBoxFragment(child_fragment.Get());
if (IsFirstFragment(constraint_space, box_fragment))
CopyChildFragmentPosition(box_fragment);
if (child_fragment->GetLayoutObject()->IsLayoutBlockFlow())
ToLayoutBlockFlow(child_fragment->GetLayoutObject())
->AddOverflowFromFloats();
}
}
if (box_->IsLayoutBlock()) {
ToLayoutBlock(box_)->LayoutPositionedObjects(true);
NGWritingMode writing_mode =
FromPlatformWritingMode(Style().GetWritingMode());
NGBoxFragment fragment(writing_mode, physical_fragment);
ToLayoutBlock(box_)->ComputeOverflow(fragment.OverflowSize().block_size -
border_scrollbar_padding.block_end);
}
box_->UpdateAfterLayout();
box_->ClearNeedsLayout();
if (box_->IsLayoutBlockFlow()) {
LayoutBlockFlow* block_flow = ToLayoutBlockFlow(box_);
block_flow->UpdateIsSelfCollapsing();
if (block_flow->CreatesNewFormattingContext())
block_flow->AddOverflowFromFloats();
}
}
// Copies data back to the legacy layout tree for a given child fragment.
void NGBlockNode::CopyChildFragmentPosition(
const NGPhysicalFragment& fragment,
const NGPhysicalOffset& additional_offset) {
LayoutBox* layout_box = ToLayoutBox(fragment.GetLayoutObject());
if (!layout_box)
return;
DCHECK(layout_box->Parent()) << "Should be called on children only.";
// We should only be positioning children which are relative to ourselves.
DCHECK_EQ(box_, layout_box->ContainingBlock());
// LegacyLayout flips vertical-rl horizontal coordinates before paint.
// NGLayout flips X location for LegacyLayout compatibility.
if (box_->StyleRef().IsFlippedBlocksWritingMode()) {
LayoutUnit container_width = box_->Size().Width();
layout_box->SetX(container_width - fragment.Offset().left -
additional_offset.left - fragment.Size().width);
} else {
layout_box->SetX(fragment.Offset().left + additional_offset.left);
}
layout_box->SetY(fragment.Offset().top + additional_offset.top);
// Floats need an associated FloatingObject for painting.
if (IsFloatFragment(fragment) && box_->IsLayoutBlockFlow()) {
FloatingObject* floating_object =
ToLayoutBlockFlow(box_)->InsertFloatingObject(*layout_box);
floating_object->SetIsInPlacedTree(false);
floating_object->SetX(fragment.Offset().left + additional_offset.left);
floating_object->SetY(fragment.Offset().top + additional_offset.top);
floating_object->SetIsPlaced(true);
floating_object->SetIsInPlacedTree(true);
}
}
RefPtr<NGLayoutResult> NGBlockNode::RunOldLayout(
const NGConstraintSpace& constraint_space) {
NGLogicalSize available_size = constraint_space.PercentageResolutionSize();
LayoutObject* containing_block = box_->ContainingBlock();
NGWritingMode writing_mode =
FromPlatformWritingMode(Style().GetWritingMode());
bool parallel_writing_mode;
if (!containing_block) {
parallel_writing_mode = true;
} else {
parallel_writing_mode = IsParallelWritingMode(
FromPlatformWritingMode(containing_block->StyleRef().GetWritingMode()),
writing_mode);
}
if (parallel_writing_mode) {
box_->SetOverrideContainingBlockContentLogicalWidth(
available_size.inline_size);
box_->SetOverrideContainingBlockContentLogicalHeight(
available_size.block_size);
} else {
// OverrideContainingBlock should be in containing block writing mode.
box_->SetOverrideContainingBlockContentLogicalWidth(
available_size.block_size);
box_->SetOverrideContainingBlockContentLogicalHeight(
available_size.inline_size);
}
// TODO(layout-ng): Does this handle scrollbars correctly?
if (constraint_space.IsFixedSizeInline()) {
box_->SetOverrideLogicalContentWidth(
constraint_space.AvailableSize().inline_size -
box_->BorderAndPaddingLogicalWidth());
}
if (constraint_space.IsFixedSizeBlock()) {
box_->SetOverrideLogicalContentHeight(
constraint_space.AvailableSize().block_size -
box_->BorderAndPaddingLogicalHeight());
}
if (box_->IsLayoutNGBlockFlow() && box_->NeedsLayout()) {
ToLayoutNGBlockFlow(box_)->LayoutBlockFlow::UpdateBlockLayout(true);
} else {
box_->ForceLayout();
}
NGLogicalSize box_size(box_->LogicalWidth(), box_->LogicalHeight());
NGPhysicalSize overflow_physical_size(box_->LayoutOverflowRect().Width(),
box_->LayoutOverflowRect().Height());
NGLogicalSize overflow_size =
overflow_physical_size.ConvertToLogical(writing_mode);
// TODO(kojii): Implement use_first_line_style.
NGFragmentBuilder builder(*this, box_->Style(), writing_mode,
box_->StyleRef().Direction());
builder.SetSize(box_size)
.SetOverflowSize(overflow_size);
// For now we copy the exclusion space straight through, this is incorrect
// but needed as not all elements which participate in a BFC are switched
// over to LayoutNG yet.
// TODO(ikilpatrick): Remove this once the above isn't true.
builder.SetExclusionSpace(
WTF::WrapUnique(new NGExclusionSpace(constraint_space.ExclusionSpace())));
CopyBaselinesFromOldLayout(constraint_space, &builder);
return builder.ToBoxFragment();
}
void NGBlockNode::CopyBaselinesFromOldLayout(
const NGConstraintSpace& constraint_space,
NGFragmentBuilder* builder) {
const Vector<NGBaselineRequest>& requests =
constraint_space.BaselineRequests();
if (requests.IsEmpty())
return;
for (const auto& request : requests) {
switch (request.algorithm_type) {
case NGBaselineAlgorithmType::kAtomicInline:
AddAtomicInlineBaselineFromOldLayout(request, false, builder);
break;
case NGBaselineAlgorithmType::kAtomicInlineForFirstLine:
AddAtomicInlineBaselineFromOldLayout(request, true, builder);
break;
case NGBaselineAlgorithmType::kFirstLine: {
LayoutUnit position = box_->FirstLineBoxBaseline();
if (position != -1) {
builder->AddBaseline(request, position);
}
break;
}
}
}
}
void NGBlockNode::AddAtomicInlineBaselineFromOldLayout(
const NGBaselineRequest& request,
bool is_first_line,
NGFragmentBuilder* builder) {
LineDirectionMode line_direction =
IsHorizontalWritingMode(builder->WritingMode())
? LineDirectionMode::kHorizontalLine
: LineDirectionMode::kVerticalLine;
LayoutUnit position = LayoutUnit(box_->BaselinePosition(
request.baseline_type, is_first_line, line_direction));
// Some form controls return 0 for BaselinePosition() if 'display:block'.
// Blocks without line boxes should not produce baselines.
if (!position && !box_->IsAtomicInlineLevel() &&
!box_->IsLayoutNGBlockFlow() &&
box_->InlineBlockBaseline(line_direction) == -1) {
return;
}
// BaselinePosition() uses margin edge for atomic inlines.
if (box_->IsAtomicInlineLevel())
position -= box_->MarginOver();
builder->AddBaseline(request, position);
}
void NGBlockNode::UseOldOutOfFlowPositioning() {
DCHECK(box_->IsOutOfFlowPositioned());
box_->ContainingBlock()->InsertPositionedObject(box_);
}
// Save static position for legacy AbsPos layout.
void NGBlockNode::SaveStaticOffsetForLegacy(const NGLogicalOffset& offset) {
DCHECK(box_->IsOutOfFlowPositioned());
DCHECK(box_->Layer());
box_->Layer()->SetStaticBlockPosition(offset.block_offset);
box_->Layer()->SetStaticInlinePosition(offset.inline_offset);
}
} // namespace blink