blob: 0c822946703855585cf1e7d9da5b70c939113c26 [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_block_node.h"
#include <memory>
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/html_marquee_element.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/layout_fieldset.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_set.h"
#include "third_party/blink/renderer/core/layout/min_max_size.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h"
#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_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_column_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
#include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/text/writing_mode.h"
namespace blink {
namespace {
inline LayoutMultiColumnFlowThread* GetFlowThread(const LayoutBox& box) {
if (!box.IsLayoutBlockFlow())
return nullptr;
return ToLayoutBlockFlow(box).MultiColumnFlowThread();
}
#define WITH_ALGORITHM(ret, func, argdecl, args) \
ret func##WithAlgorithm(NGBlockNode node, const NGConstraintSpace& space, \
const NGBreakToken* break_token, argdecl) { \
const auto* token = ToNGBlockBreakToken(break_token); \
const ComputedStyle& style = node.Style(); \
if (node.GetLayoutBox()->IsLayoutNGFlexibleBox()) \
return NGFlexLayoutAlgorithm(node, space, token).func args; \
if (node.GetLayoutBox()->IsLayoutNGFieldset()) \
return NGFieldsetLayoutAlgorithm(node, space, token).func args; \
/* If there's a legacy layout box, we can only do block fragmentation if \
* we would have done block fragmentation with the legacy engine. \
* Otherwise writing data back into the legacy tree will fail. Look for \
* the flow thread. */ \
if (GetFlowThread(*node.GetLayoutBox())) { \
if (style.IsOverflowPaged()) \
return NGPageLayoutAlgorithm(node, space, token).func args; \
if (style.SpecifiesColumns()) \
return NGColumnLayoutAlgorithm(node, space, token).func args; \
NOTREACHED(); \
} \
return NGBlockLayoutAlgorithm(node, space, token).func args; \
}
WITH_ALGORITHM(scoped_refptr<NGLayoutResult>, Layout, void*, ())
WITH_ALGORITHM(base::Optional<MinMaxSize>,
ComputeMinMaxSize,
MinMaxSizeInput input,
(input))
#undef WITH_ALGORITHM
bool IsFloatFragment(const NGPhysicalFragment& fragment) {
const LayoutObject* layout_object = fragment.GetLayoutObject();
return layout_object && layout_object->IsFloating() && fragment.IsBox();
}
// Creates a blink::FloatingObject (if needed), and populates it with the
// position information needed by the existing layout tree.
void CopyFloatChildFragmentPosition(LayoutBox* floating_box,
const NGPhysicalOffset offset,
bool has_flipped_x_axis) {
DCHECK(floating_box->IsFloating());
LayoutBlock* containing_block = floating_box->ContainingBlock();
DCHECK(containing_block);
// Floats need an associated FloatingObject for painting.
FloatingObject* floating_object =
ToLayoutBlockFlow(containing_block)->InsertFloatingObject(*floating_box);
floating_object->SetShouldPaint(!floating_box->HasSelfPaintingLayer());
LayoutUnit horizontal_margin_edge_offset = offset.left;
if (has_flipped_x_axis)
horizontal_margin_edge_offset -= floating_box->MarginRight();
else
horizontal_margin_edge_offset -= floating_box->MarginLeft();
floating_object->SetX(horizontal_margin_edge_offset);
floating_object->SetY(offset.top - floating_box->MarginTop());
#if DCHECK_IS_ON()
// Being "placed" is a legacy thing. Make sure the flags remain unset in NG.
DCHECK(!floating_object->IsPlaced());
DCHECK(!floating_object->IsInPlacedTree());
// Set this flag to tell the float machinery that it's safe to read out
// position data.
floating_object->SetHasGeometry();
#endif
}
void UpdateLegacyMultiColumnFlowThread(
NGBlockNode node,
LayoutMultiColumnFlowThread* flow_thread,
const NGConstraintSpace& constraint_space,
const NGPhysicalBoxFragment& fragment) {
WritingMode writing_mode = constraint_space.GetWritingMode();
LayoutUnit flow_end;
LayoutUnit column_block_size;
bool has_processed_first_child = false;
// Stitch the columns together.
for (const auto& child : fragment.Children()) {
NGFragment child_fragment(writing_mode, *child);
flow_end += child_fragment.BlockSize();
// Non-uniform fragmentainer widths not supported by legacy layout.
DCHECK(!has_processed_first_child ||
flow_thread->LogicalWidth() == child_fragment.InlineSize());
if (!has_processed_first_child) {
// The offset of the flow thread should be the same as that of the first
// first column.
flow_thread->SetLocation(child.Offset().ToLayoutPoint());
flow_thread->SetLogicalWidth(child_fragment.InlineSize());
column_block_size = child_fragment.BlockSize();
has_processed_first_child = true;
}
}
if (LayoutMultiColumnSet* column_set = flow_thread->FirstMultiColumnSet()) {
NGFragment logical_fragment(writing_mode, fragment);
auto border_scrollbar_padding =
CalculateBorderScrollbarPadding(constraint_space, node);
column_set->SetLogicalLeft(border_scrollbar_padding.inline_start);
column_set->SetLogicalTop(border_scrollbar_padding.block_start);
column_set->SetLogicalWidth(logical_fragment.InlineSize() -
border_scrollbar_padding.InlineSum());
column_set->SetLogicalHeight(column_block_size);
column_set->EndFlow(flow_end);
}
// TODO(mstensho): Update all column boxes, not just the first column set
// (like we do above). This is needed to support column-span:all.
flow_thread->UpdateFromNG();
flow_thread->ValidateColumnSets();
flow_thread->SetLogicalHeight(flow_end);
flow_thread->UpdateAfterLayout();
flow_thread->ClearNeedsLayout();
}
NGConstraintSpaceBuilder CreateConstraintSpaceBuilderForMinMax(
NGBlockNode node) {
return NGConstraintSpaceBuilder(node.Style().GetWritingMode(),
node.Style().GetWritingMode(),
node.CreatesNewFormattingContext())
.SetTextDirection(node.Style().Direction())
.SetIsIntermediateLayout(true)
.SetFloatsBfcBlockOffset(LayoutUnit());
}
LayoutUnit CalculateAvailableInlineSizeForLegacy(
const LayoutBox& box,
const NGConstraintSpace& space) {
if (box.StyleRef().LogicalWidth().IsPercent()) {
if (box.ShouldComputeSizeAsReplaced())
return space.ReplacedPercentageResolutionInlineSize();
return space.PercentageResolutionInlineSize();
}
return space.AvailableSize().inline_size;
}
LayoutUnit CalculateAvailableBlockSizeForLegacy(
const LayoutBox& box,
const NGConstraintSpace& space) {
if (box.StyleRef().LogicalHeight().IsPercent()) {
if (box.ShouldComputeSizeAsReplaced())
return space.ReplacedPercentageResolutionBlockSize();
return space.PercentageResolutionBlockSize();
}
return space.AvailableSize().block_size;
}
} // namespace
scoped_refptr<NGLayoutResult> NGBlockNode::Layout(
const NGConstraintSpace& constraint_space,
const NGBreakToken* break_token) {
// Use the old layout code and synthesize a fragment.
if (!CanUseNewLayout()) {
return RunOldLayout(constraint_space);
}
LayoutBlockFlow* block_flow =
box_->IsLayoutNGMixin() ? ToLayoutBlockFlow(box_) : nullptr;
if (RuntimeEnabledFeatures::TrackLayoutPassesPerBlockEnabled() && block_flow)
block_flow->IncrementLayoutPassCount();
NGLayoutInputNode first_child = FirstChild();
if (block_flow && !first_child)
block_flow->ClearNGInlineNodeData();
scoped_refptr<NGLayoutResult> layout_result;
if (block_flow) {
layout_result =
block_flow->CachedLayoutResult(constraint_space, break_token);
if (layout_result) {
// TODO(layoutng): Figure out why these two call can't be inside the
// !constraint_space.IsIntermediateLayout() block below.
UpdateShapeOutsideInfoIfNeeded(
*layout_result, constraint_space.PercentageResolutionInlineSize());
// We may need paint invalidation even if we can reuse layout, as our
// paint offset/visual rect may have changed due to relative
// positioning changes. Otherwise we fail fast/css/
// fast/css/relative-positioned-block-with-inline-ancestor-and-parent
// -dynamic.html
// TODO(layoutng): See if we can optimize this. When we natively
// support relative positioning in NG we can probably remove this,
box_->SetSubtreeShouldCheckForPaintInvalidation();
// We have to re-set the cached result here, because it is used for
// LayoutNGMixin::CurrentFragment and therefore has to be up-to-date.
// In particular, that fragment would have an incorrect offset if we
// don't re-set the result here.
block_flow->SetCachedLayoutResult(constraint_space, break_token,
*layout_result);
if (!constraint_space.IsIntermediateLayout() && first_child &&
first_child.IsInline()) {
block_flow->UpdatePaintFragmentFromCachedLayoutResult(
ToNGBlockBreakToken(break_token), layout_result->PhysicalFragment(),
layout_result->Offset());
}
return layout_result;
}
}
// This follows the code from LayoutBox::UpdateLogicalWidth
if (box_->NeedsPreferredWidthsRecalculation() &&
!box_->PreferredLogicalWidthsDirty()) {
// Laying out this object means that its containing block is also being
// laid out. This object is special, in that its min/max widths depend on
// the ancestry (min/max width calculation should ideally be strictly
// bottom-up, but that's not always the case), so since the containing
// block size may have changed, we need to recalculate the min/max widths
// of this object, and every child that has the same issue, recursively.
box_->SetPreferredLogicalWidthsDirty(kMarkOnlyThis);
// Since all this takes place during actual layout, instead of being part
// of min/max the width calculation machinery, we need to enter said
// machinery here, to make sure that what was dirtied is actualy
// recalculated. Leaving things dirty would mean that any subsequent
// dirtying of descendants would fail.
box_->ComputePreferredLogicalWidths();
}
PrepareForLayout();
NGBoxStrut old_scrollbars = GetScrollbarSizes();
layout_result = LayoutWithAlgorithm(*this, constraint_space, break_token,
/* ignored */ nullptr);
FinishLayout(block_flow, constraint_space, break_token, layout_result);
if (old_scrollbars != GetScrollbarSizes()) {
// If our scrollbars have changed, we need to relayout because either:
// - Our size has changed (if shrinking to fit), or
// - Space available to our children has changed.
// This mirrors legacy code in PaintLayerScrollableArea::UpdateAfterLayout.
// TODO(cbiesinger): It seems that we should also check if
// PreferredLogicalWidthsDirty() has changed from false to true during
// layout, so that we correctly size ourselves when shrinking to fit
// and a child gained a vertical scrollbar. However, no test fails
// without that check.
PaintLayerScrollableArea::FreezeScrollbarsScope freeze_scrollbars;
// Scrollbar changes are hard to detect. Make sure everyone gets the
// message.
box_->SetNeedsLayout(layout_invalidation_reason::kScrollbarChanged,
kMarkOnlyThis);
layout_result = LayoutWithAlgorithm(*this, constraint_space, break_token,
/* ignored */ nullptr);
FinishLayout(block_flow, constraint_space, break_token, layout_result);
}
// We always need to update the ShapeOutsideInfo even if the layout is
// intermediate (e.g. called during a min/max pass).
//
// If a shape-outside float is present in an orthogonal flow, when
// calculating the min/max-size (by performing an intermediate layout), we
// might calculate this incorrectly, as the layout won't take into account the
// shape-outside area.
//
// TODO(ikilpatrick): This should be fixed by moving the shape-outside data
// to the NGLayoutResult, removing this "side" data-structure.
UpdateShapeOutsideInfoIfNeeded(
*layout_result, constraint_space.PercentageResolutionInlineSize());
return layout_result;
}
void NGBlockNode::PrepareForLayout() {
if (box_->IsLayoutBlock()) {
LayoutBlock* block = ToLayoutBlock(box_);
if (block->HasOverflowClip()) {
DCHECK(block->GetScrollableArea());
if (block->GetScrollableArea()->ShouldPerformScrollAnchoring())
block->GetScrollableArea()->GetScrollAnchor()->NotifyBeforeLayout();
}
}
if (IsListItem())
ToLayoutNGListItem(box_)->UpdateMarkerTextIfNeeded();
}
void NGBlockNode::FinishLayout(LayoutBlockFlow* block_flow,
const NGConstraintSpace& constraint_space,
const NGBreakToken* break_token,
scoped_refptr<NGLayoutResult> layout_result) {
if (!IsBlockLayoutComplete(constraint_space, *layout_result))
return;
DCHECK(layout_result->PhysicalFragment());
if (block_flow) {
block_flow->SetCachedLayoutResult(constraint_space, break_token,
*layout_result);
NGLayoutInputNode first_child = FirstChild();
bool has_inline_children = first_child && first_child.IsInline();
if (has_inline_children || box_->IsLayoutNGFieldset()) {
if (has_inline_children) {
CopyFragmentDataToLayoutBoxForInlineChildren(
ToNGPhysicalBoxFragment(*layout_result->PhysicalFragment()),
layout_result->PhysicalFragment()->Size().width,
Style().IsFlippedBlocksWritingMode());
}
block_flow->SetPaintFragment(ToNGBlockBreakToken(break_token),
layout_result->PhysicalFragment(),
layout_result->Offset());
} else {
// We still need to clear paint fragments in case it had inline children,
// and thus had NGPaintFragment.
block_flow->SetPaintFragment(ToNGBlockBreakToken(break_token), nullptr,
NGPhysicalOffset());
}
}
CopyFragmentDataToLayoutBox(constraint_space, *layout_result);
}
MinMaxSize NGBlockNode::ComputeMinMaxSize(
WritingMode container_writing_mode,
const MinMaxSizeInput& input,
const NGConstraintSpace* constraint_space) {
bool is_orthogonal_flow_root =
!IsParallelWritingMode(container_writing_mode, Style().GetWritingMode());
MinMaxSize sizes;
// If we're orthogonal, we have to run layout to compute the sizes. However,
// if we're outside of layout, we can't do that. This can happen on Mac.
if ((!CanUseNewLayout() && !is_orthogonal_flow_root) ||
(is_orthogonal_flow_root && !box_->GetFrameView()->IsInPerformLayout())) {
return ComputeMinMaxSizeFromLegacy(input.size_type);
}
NGConstraintSpace zero_constraint_space =
CreateConstraintSpaceBuilderForMinMax(*this).ToConstraintSpace();
if (!constraint_space) {
// Using the zero-sized constraint space when measuring for an orthogonal
// flow root isn't going to give the right result.
DCHECK(!is_orthogonal_flow_root);
constraint_space = &zero_constraint_space;
}
if (is_orthogonal_flow_root || !CanUseNewLayout()) {
scoped_refptr<NGLayoutResult> layout_result = Layout(*constraint_space);
DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess);
NGBoxFragment fragment(
container_writing_mode,
TextDirection::kLtr, // irrelevant here
ToNGPhysicalBoxFragment(*layout_result->PhysicalFragment()));
sizes.min_size = sizes.max_size = fragment.Size().inline_size;
if (input.size_type == NGMinMaxSizeType::kContentBoxSize) {
sizes -= fragment.Borders().InlineSum() + fragment.Padding().InlineSum() +
box_->ScrollbarLogicalWidth();
DCHECK_GE(sizes.min_size, LayoutUnit());
DCHECK_GE(sizes.max_size, LayoutUnit());
}
return sizes;
}
base::Optional<MinMaxSize> maybe_sizes =
ComputeMinMaxSizeWithAlgorithm(*this, *constraint_space,
/* break token */ nullptr, input);
if (maybe_sizes.has_value()) {
if (UNLIKELY(IsHTMLMarqueeElement(box_->GetNode()) &&
ToHTMLMarqueeElement(box_->GetNode())->IsHorizontal()))
maybe_sizes->min_size = LayoutUnit();
return *maybe_sizes;
}
if (!box_->GetFrameView()->IsInPerformLayout()) {
// We can't synthesize these using Layout() if we're not in PerformLayout.
// This situation can happen on mac. Fall back to legacy instead.
return ComputeMinMaxSizeFromLegacy(input.size_type);
}
// Have to synthesize this value.
scoped_refptr<NGLayoutResult> layout_result = Layout(zero_constraint_space);
NGBoxFragment min_fragment(
container_writing_mode,
TextDirection::kLtr, // irrelevant here
ToNGPhysicalBoxFragment(*layout_result->PhysicalFragment()));
sizes.min_size = min_fragment.Size().inline_size;
// Now, redo with infinite space for max_content
NGConstraintSpace infinite_constraint_space =
CreateConstraintSpaceBuilderForMinMax(*this)
.SetAvailableSize({LayoutUnit::Max(), LayoutUnit()})
.SetPercentageResolutionSize({LayoutUnit(), LayoutUnit()})
.ToConstraintSpace();
layout_result = Layout(infinite_constraint_space);
NGBoxFragment max_fragment(
container_writing_mode,
TextDirection::kLtr, // irrelevant here
ToNGPhysicalBoxFragment(*layout_result->PhysicalFragment()));
sizes.max_size = max_fragment.Size().inline_size;
if (input.size_type == NGMinMaxSizeType::kContentBoxSize) {
sizes -= max_fragment.Borders().InlineSum() +
max_fragment.Padding().InlineSum() + box_->ScrollbarLogicalWidth();
DCHECK_GE(sizes.min_size, LayoutUnit());
DCHECK_GE(sizes.max_size, LayoutUnit());
}
return sizes;
}
MinMaxSize NGBlockNode::ComputeMinMaxSizeFromLegacy(
NGMinMaxSizeType type) const {
MinMaxSize sizes;
// ComputeIntrinsicLogicalWidths returns content-box + scrollbar.
box_->ComputeIntrinsicLogicalWidths(sizes.min_size, sizes.max_size);
if (type == NGMinMaxSizeType::kContentBoxSize) {
sizes -= LayoutUnit(box_->ScrollbarLogicalWidth());
DCHECK_GE(sizes.min_size, LayoutUnit());
DCHECK_GE(sizes.max_size, LayoutUnit());
} else {
sizes += box_->BorderAndPaddingLogicalWidth();
}
return sizes;
}
NGBoxStrut NGBlockNode::GetScrollbarSizes() const {
NGPhysicalBoxStrut sizes;
const ComputedStyle& style = box_->StyleRef();
if (!style.IsOverflowVisible()) {
LayoutUnit vertical = LayoutUnit(box_->VerticalScrollbarWidth());
LayoutUnit horizontal = LayoutUnit(box_->HorizontalScrollbarHeight());
sizes.bottom = horizontal;
if (box_->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft())
sizes.left = vertical;
else
sizes.right = vertical;
}
return sizes.ConvertToLogical(style.GetWritingMode(), style.Direction());
}
NGLayoutInputNode NGBlockNode::NextSibling() const {
LayoutObject* next_sibling = GetLayoutObjectForNextSiblingNode(box_);
if (next_sibling) {
DCHECK(!next_sibling->IsInline());
return NGBlockNode(ToLayoutBox(next_sibling));
}
return nullptr;
}
NGLayoutInputNode NGBlockNode::FirstChild() const {
auto* block = ToLayoutBlock(box_);
auto* child = GetLayoutObjectForFirstChildNode(block);
if (!child)
return nullptr;
if (AreNGBlockFlowChildrenInline(block))
return NGInlineNode(ToLayoutBlockFlow(block));
return NGBlockNode(ToLayoutBox(child));
}
NGBlockNode NGBlockNode::GetRenderedLegend() const {
if (!IsFieldsetContainer())
return nullptr;
return NGBlockNode(LayoutFieldset::FindInFlowLegend(*ToLayoutBlock(box_)));
}
NGBlockNode NGBlockNode::GetFieldsetContent() const {
if (!IsFieldsetContainer())
return nullptr;
auto* child = GetLayoutObjectForFirstChildNode(ToLayoutBlock(box_));
if (!child)
return nullptr;
return NGBlockNode(ToLayoutBox(child));
}
bool NGBlockNode::CanUseNewLayout(const LayoutBox& box) {
DCHECK(RuntimeEnabledFeatures::LayoutNGEnabled());
if (box.StyleRef().ForceLegacyLayout())
return false;
// When the style has |ForceLegacyLayout|, it's usually not LayoutNGMixin,
// but anonymous block can be.
return box.IsLayoutNGMixin() || box.IsLayoutNGFlexibleBox();
}
bool NGBlockNode::CanUseNewLayout() const {
return CanUseNewLayout(*box_);
}
String NGBlockNode::ToString() const {
return String::Format("NGBlockNode: '%s'",
GetLayoutBox()->DebugName().Ascii().data());
}
void NGBlockNode::CopyFragmentDataToLayoutBox(
const NGConstraintSpace& constraint_space,
const NGLayoutResult& layout_result) {
DCHECK(layout_result.PhysicalFragment());
if (UNLIKELY(constraint_space.IsIntermediateLayout()))
return;
const NGPhysicalBoxFragment& physical_fragment =
ToNGPhysicalBoxFragment(*layout_result.PhysicalFragment());
NGBoxFragment fragment(constraint_space.GetWritingMode(),
constraint_space.Direction(), physical_fragment);
NGLogicalSize fragment_logical_size = fragment.Size();
// 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 (LIKELY(IsFirstFragment(constraint_space, physical_fragment))) {
box_->SetLogicalWidth(fragment_logical_size.inline_size);
} else {
DCHECK_EQ(box_->LogicalWidth(), fragment_logical_size.inline_size)
<< "Variable fragment inline size not supported";
logical_height =
PreviouslyUsedBlockSpace(constraint_space, physical_fragment);
// TODO(layout-ng): We should store this on the break token instead of
// relying on previously-stored data. Our relayout in NGBlockNode::Layout
// will otherwise lead to wrong data.
intrinsic_content_logical_height = box_->IntrinsicContentLogicalHeight();
}
logical_height += fragment_logical_size.block_size;
intrinsic_content_logical_height += layout_result.IntrinsicBlockSize();
NGBoxStrut borders = fragment.Borders();
NGBoxStrut scrollbars = GetScrollbarSizes();
NGBoxStrut padding = fragment.Padding();
NGBoxStrut border_scrollbar_padding = borders + scrollbars + padding;
if (LIKELY(IsLastFragment(physical_fragment)))
intrinsic_content_logical_height -= border_scrollbar_padding.BlockSum();
box_->SetLogicalHeight(logical_height);
box_->SetIntrinsicContentLogicalHeight(intrinsic_content_logical_height);
// TODO(mstensho): This should always be done by the parent algorithm, since
// we may have auto margins, which only the parent is able to resolve. Remove
// the following line when all layout modes do this properly.
box_->SetMargin(ComputePhysicalMargins(constraint_space, Style()));
LayoutMultiColumnFlowThread* flow_thread = GetFlowThread(*box_);
if (UNLIKELY(flow_thread)) {
PlaceChildrenInFlowThread(constraint_space, physical_fragment);
} else {
NGPhysicalOffset offset_from_start;
if (UNLIKELY(constraint_space.HasBlockFragmentation())) {
// Need to include any block space that this container has used in
// previous fragmentainers. The offset of children will be relative to
// the container, in flow thread coordinates, i.e. the model where
// everything is represented as one single strip, rather than being
// sliced and translated into columns.
// TODO(mstensho): writing modes
offset_from_start.top =
PreviouslyUsedBlockSpace(constraint_space, physical_fragment);
}
PlaceChildrenInLayoutBox(constraint_space, physical_fragment,
offset_from_start);
}
LayoutBlock* block = ToLayoutBlockOrNull(box_);
if (LIKELY(block && IsLastFragment(physical_fragment))) {
LayoutUnit intrinsic_block_size = layout_result.IntrinsicBlockSize();
if (UNLIKELY(constraint_space.HasBlockFragmentation())) {
intrinsic_block_size +=
PreviouslyUsedBlockSpace(constraint_space, physical_fragment);
}
if (UNLIKELY(block->HasPositionedObjects()))
block->LayoutPositionedObjects(/* relayout_children */ false);
if (UNLIKELY(flow_thread)) {
UpdateLegacyMultiColumnFlowThread(*this, flow_thread, constraint_space,
physical_fragment);
}
// |ComputeOverflow()| below calls |AddVisualOverflowFromChildren()|, which
// computes visual overflow from |RootInlineBox| if |ChildrenInline()|
block->SetNeedsOverflowRecalc();
block->ComputeLayoutOverflow(intrinsic_block_size - borders.block_end -
scrollbars.block_end);
}
box_->UpdateAfterLayout();
box_->ClearNeedsLayout();
// Overflow computation depends on this being set.
LayoutBlockFlow* block_flow = ToLayoutBlockFlowOrNull(box_);
if (LIKELY(block_flow))
block_flow->UpdateIsSelfCollapsing();
}
void NGBlockNode::PlaceChildrenInLayoutBox(
const NGConstraintSpace& constraint_space,
const NGPhysicalBoxFragment& physical_fragment,
const NGPhysicalOffset& offset_from_start) {
LayoutBox* rendered_legend = nullptr;
for (const auto& child_fragment : physical_fragment.Children()) {
auto* child_object = child_fragment->GetLayoutObject();
// Skip any line-boxes we have as children, this is handled within
// NGInlineNode at the moment.
if (!child_fragment->IsBox() && !child_fragment->IsRenderedLegend())
continue;
const auto& box_fragment = *ToNGPhysicalBoxFragment(child_fragment.get());
if (IsFirstFragment(constraint_space, box_fragment)) {
if (box_fragment.IsRenderedLegend())
rendered_legend = ToLayoutBox(box_fragment.GetLayoutObject());
CopyChildFragmentPosition(box_fragment, child_fragment.Offset(),
offset_from_start);
}
if (child_object->IsLayoutBlockFlow()) {
ToLayoutBlockFlow(child_object)->AddVisualOverflowFromFloats();
ToLayoutBlockFlow(child_object)->AddLayoutOverflowFromFloats();
}
}
if (rendered_legend) {
// The rendered legend is a child of the the anonymous fieldset content
// child wrapper object on the legacy side. LayoutNG, on the other hand,
// generates a fragment for the rendered legend as a direct child of the
// fieldset container fragment (as a *sibling* preceding the anonymous
// fieldset content wrapper). Now that we have positioned the anonymous
// wrapper, we're ready to compensate for this discrepancy. See
// LayoutNGFieldset for more details.
LayoutBlock* content_wrapper = rendered_legend->ContainingBlock();
DCHECK(content_wrapper->IsAnonymous());
DCHECK(IsHTMLFieldSetElement(content_wrapper->Parent()->GetNode()));
LayoutPoint location = rendered_legend->Location();
location -= content_wrapper->Location();
rendered_legend->SetLocation(location);
}
}
void NGBlockNode::PlaceChildrenInFlowThread(
const NGConstraintSpace& constraint_space,
const NGPhysicalBoxFragment& physical_fragment) {
LayoutUnit flowthread_offset;
for (const auto& child : physical_fragment.Children()) {
// Each anonymous child of a multicol container constitutes one column.
DCHECK(child->GetLayoutObject() == box_);
// TODO(mstensho): writing modes
NGPhysicalOffset offset(LayoutUnit(), flowthread_offset);
// Position each child node in the first column that they occur, relatively
// to the block-start of the flow thread.
const auto* column = ToNGPhysicalBoxFragment(child.get());
PlaceChildrenInLayoutBox(constraint_space, *column, offset);
const auto* token = ToNGBlockBreakToken(column->BreakToken());
flowthread_offset = token->UsedBlockSize();
}
}
// Copies data back to the legacy layout tree for a given child fragment.
void NGBlockNode::CopyChildFragmentPosition(
const NGPhysicalFragment& fragment,
const NGPhysicalOffset fragment_offset,
const NGPhysicalOffset additional_offset) {
LayoutBox* layout_box = ToLayoutBox(fragment.GetLayoutObject());
if (!layout_box)
return;
DCHECK(layout_box->Parent()) << "Should be called on children only.";
// The containing block of |layout_box| on the legacy layout side is normally
// |box_|, but this is not an invariant. Among other things, it does not apply
// to list item markers and multicol container children. Multicol containiner
// children typically have their flow thread (not the multicol container
// itself) as their containing block, and we need to use the right containing
// block for inserting floats, flipping for writing modes, etc.
LayoutBlock* containing_block = layout_box->ContainingBlock();
// LegacyLayout flips vertical-rl horizontal coordinates before paint.
// NGLayout flips X location for LegacyLayout compatibility. horizontal_offset
// will be the offset from the left edge of the container to the left edge of
// the layout object, except when in vertical-rl: Then it will be the offset
// from the right edge of the container to the right edge of the layout
// object.
LayoutUnit horizontal_offset = fragment_offset.left + additional_offset.left;
bool has_flipped_x_axis =
containing_block->StyleRef().IsFlippedBlocksWritingMode();
if (has_flipped_x_axis) {
horizontal_offset = containing_block->Size().Width() - horizontal_offset -
fragment.Size().width;
}
layout_box->SetLocation(LayoutPoint(
horizontal_offset, fragment_offset.top + additional_offset.top));
if (IsFloatFragment(fragment)) {
CopyFloatChildFragmentPosition(
layout_box, fragment_offset + additional_offset, has_flipped_x_axis);
}
}
// For inline children, NG painters handles fragments directly, but there are
// some cases where we need to copy data to the LayoutObject tree. This function
// handles such cases.
void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren(
const NGPhysicalContainerFragment& container,
LayoutUnit initial_container_width,
bool initial_container_is_flipped,
NGPhysicalOffset offset) {
for (const auto& child : container.Children()) {
if (child->IsContainer()) {
NGPhysicalOffset child_offset = offset + child.Offset();
// Replaced elements and inline blocks need Location() set relative to
// their block container.
LayoutObject* layout_object = child->GetLayoutObject();
if (layout_object && layout_object->IsBox()) {
LayoutBox& layout_box = ToLayoutBox(*layout_object);
NGPhysicalOffset maybe_flipped_offset = child_offset;
if (initial_container_is_flipped) {
maybe_flipped_offset.left = initial_container_width -
child->Size().width -
maybe_flipped_offset.left;
}
layout_box.SetLocation(maybe_flipped_offset.ToLayoutPoint());
if (IsFloatFragment(*child)) {
CopyFloatChildFragmentPosition(&layout_box, maybe_flipped_offset,
initial_container_is_flipped);
}
}
// Legacy compatibility. This flag is used in paint layer for
// invalidation.
if (layout_object && layout_object->IsLayoutInline() &&
layout_object->StyleRef().HasOutline() &&
!layout_object->IsElementContinuation() &&
ToLayoutInline(layout_object)->Continuation()) {
box_->SetContainsInlineWithOutlineAndContinuation(true);
}
// The Location() of inline LayoutObject is relative to the
// LayoutBlockFlow. If |child| establishes a new block formatting context,
// it also creates another inline formatting context. Do not copy to its
// descendants in this case.
if (!child->IsBlockFormattingContextRoot()) {
CopyFragmentDataToLayoutBoxForInlineChildren(
ToNGPhysicalContainerFragment(*child), initial_container_width,
initial_container_is_flipped, child_offset);
}
}
}
}
bool NGBlockNode::IsInlineLevel() const {
return GetLayoutBox()->IsInline();
}
bool NGBlockNode::IsAtomicInlineLevel() const {
// LayoutObject::IsAtomicInlineLevel() returns true for e.g., <img
// style="display: block">. Check IsInline() as well.
return GetLayoutBox()->IsAtomicInlineLevel() && GetLayoutBox()->IsInline();
}
bool NGBlockNode::UseLogicalBottomMarginEdgeForInlineBlockBaseline() const {
LayoutBox* layout_box = GetLayoutBox();
return layout_box->IsLayoutBlock() &&
ToLayoutBlock(layout_box)
->UseLogicalBottomMarginEdgeForInlineBlockBaseline();
}
scoped_refptr<NGLayoutResult> NGBlockNode::LayoutAtomicInline(
const NGConstraintSpace& parent_constraint_space,
const ComputedStyle& parent_style,
FontBaseline baseline_type,
bool use_first_line_style) {
NGConstraintSpaceBuilder builder(
parent_constraint_space, Style().GetWritingMode(), /* is_new_fc */ true);
SetOrthogonalFallbackInlineSizeIfNeeded(parent_style, *this, &builder);
builder.SetUseFirstLineStyle(use_first_line_style);
// Request to compute baseline during the layout, except when we know the box
// would synthesize box-baseline.
LayoutBox* layout_box = GetLayoutBox();
if (NGBaseline::ShouldPropagateBaselines(layout_box)) {
builder.AddBaselineRequest(
{NGBaselineAlgorithmType::kAtomicInline, baseline_type});
}
NGConstraintSpace constraint_space =
builder.SetIsShrinkToFit(Style().LogicalWidth().IsAuto())
.SetAvailableSize(parent_constraint_space.AvailableSize())
.SetPercentageResolutionSize(
parent_constraint_space.PercentageResolutionSize())
.SetReplacedPercentageResolutionSize(
parent_constraint_space.ReplacedPercentageResolutionSize())
.SetTextDirection(Style().Direction())
.ToConstraintSpace();
scoped_refptr<NGLayoutResult> result = Layout(constraint_space);
// TODO(kojii): Investigate why ClearNeedsLayout() isn't called automatically
// when it's being laid out.
if (!constraint_space.IsIntermediateLayout())
layout_box->ClearNeedsLayout();
return result;
}
scoped_refptr<NGLayoutResult> NGBlockNode::RunOldLayout(
const NGConstraintSpace& constraint_space) {
// This is an exit-point from LayoutNG to the legacy engine. This means that
// we need to be at a formatting context boundary, since NG and legacy don't
// cooperate on e.g. margin collapsing.
DCHECK(!box_->IsLayoutBlock() ||
ToLayoutBlock(box_)->CreatesNewFormattingContext());
WritingMode writing_mode = Style().GetWritingMode();
LayoutBlock* block = box_->IsLayoutBlock() ? ToLayoutBlock(box_) : nullptr;
const NGConstraintSpace* old_space =
block ? block->CachedConstraintSpace() : nullptr;
if (!old_space || box_->NeedsLayout() || *old_space != constraint_space) {
LayoutUnit inline_size =
CalculateAvailableInlineSizeForLegacy(*box_, constraint_space);
LayoutUnit block_size =
CalculateAvailableBlockSizeForLegacy(*box_, constraint_space);
LayoutObject* containing_block = box_->ContainingBlock();
bool parallel_writing_mode;
if (!containing_block) {
parallel_writing_mode = true;
} else {
parallel_writing_mode = IsParallelWritingMode(
containing_block->StyleRef().GetWritingMode(), writing_mode);
}
if (parallel_writing_mode) {
box_->SetOverrideContainingBlockContentLogicalWidth(inline_size);
box_->SetOverrideContainingBlockContentLogicalHeight(block_size);
} else {
// OverrideContainingBlock should be in containing block writing mode.
box_->SetOverrideContainingBlockContentLogicalWidth(block_size);
box_->SetOverrideContainingBlockContentLogicalHeight(inline_size);
}
if (constraint_space.IsFixedSizeInline()) {
box_->SetOverrideLogicalWidth(
constraint_space.AvailableSize().inline_size);
} else {
box_->ClearOverrideLogicalWidth();
}
if (constraint_space.IsFixedSizeBlock()) {
box_->SetOverrideLogicalHeight(
constraint_space.AvailableSize().block_size);
} else {
box_->ClearOverrideLogicalHeight();
}
box_->ComputeAndSetBlockDirectionMargins(box_->ContainingBlock());
if (box_->NeedsLayout() && box_->IsLayoutNGMixin()) {
ToLayoutBlockFlow(box_)->LayoutBlockFlow::UpdateBlockLayout(true);
} else {
box_->ForceLayout();
}
// Reset the containing block size override size, now that we're done with
// subtree layout. Min/max calculation that depends on the block size of the
// container (e.g. objects with intrinsic ratio and percentage block size)
// in a subsequent layout pass might otherwise become wrong.
box_->ClearOverrideContainingBlockContentSize();
if (block)
block->SetCachedConstraintSpace(constraint_space);
}
NGLogicalSize box_size(box_->LogicalWidth(), box_->LogicalHeight());
// TODO(kojii): Implement use_first_line_style.
NGBoxFragmentBuilder builder(*this, box_->Style(), writing_mode,
box_->StyleRef().Direction());
builder.SetIsNewFormattingContext(constraint_space.IsNewFormattingContext());
builder.SetIsOldLayoutRoot();
builder.SetInlineSize(box_size.inline_size);
builder.SetBlockSize(box_size.block_size);
NGBoxStrut borders(box_->BorderStart(), box_->BorderEnd(),
box_->BorderBefore(), box_->BorderAfter());
builder.SetBorders(borders);
NGBoxStrut padding(box_->PaddingStart(), box_->PaddingEnd(),
box_->PaddingBefore(), box_->PaddingAfter());
builder.SetPadding(padding);
CopyBaselinesFromOldLayout(constraint_space, &builder);
scoped_refptr<NGLayoutResult> layout_result = builder.ToBoxFragment();
UpdateShapeOutsideInfoIfNeeded(
*layout_result, constraint_space.PercentageResolutionInlineSize());
return layout_result;
}
void NGBlockNode::CopyBaselinesFromOldLayout(
const NGConstraintSpace& constraint_space,
NGBoxFragmentBuilder* builder) {
const NGBaselineRequestList requests = constraint_space.BaselineRequests();
if (requests.IsEmpty())
return;
if (UNLIKELY(constraint_space.GetWritingMode() != Style().GetWritingMode()))
return;
for (const auto& request : requests) {
switch (request.AlgorithmType()) {
case NGBaselineAlgorithmType::kAtomicInline: {
LayoutUnit position =
AtomicInlineBaselineFromOldLayout(request, constraint_space);
if (position != -1)
builder->AddBaseline(request, position);
break;
}
case NGBaselineAlgorithmType::kFirstLine: {
LayoutUnit position = box_->FirstLineBoxBaseline();
if (position != -1)
builder->AddBaseline(request, position);
break;
}
}
}
}
LayoutUnit NGBlockNode::AtomicInlineBaselineFromOldLayout(
const NGBaselineRequest& request,
const NGConstraintSpace& constraint_space) {
LineDirectionMode line_direction = box_->IsHorizontalWritingMode()
? LineDirectionMode::kHorizontalLine
: LineDirectionMode::kVerticalLine;
// If this is an inline box, use |BaselinePosition()|. Some LayoutObject
// classes override it assuming inline layout calls |BaselinePosition()|.
if (box_->IsInline()) {
LayoutUnit position = LayoutUnit(box_->BaselinePosition(
request.BaselineType(), constraint_space.UseFirstLineStyle(),
line_direction, kPositionOnContainingLine));
// BaselinePosition() uses margin edge for atomic inlines. Subtract
// margin-over so that the position is relative to the border box.
if (box_->IsAtomicInlineLevel())
position -= box_->MarginOver();
return position;
}
// If this is a block box, use |InlineBlockBaseline()|. When an inline block
// has block children, their inline block baselines need to be propagated.
return box_->InlineBlockBaseline(line_direction);
}
// Floats can optionally have a shape area, specifed by "shape-outside". The
// current shape machinery requires setting the size of the float after layout
// in the parents writing mode.
void NGBlockNode::UpdateShapeOutsideInfoIfNeeded(
const NGLayoutResult& layout_result,
LayoutUnit percentage_resolution_inline_size) {
if (!box_->IsFloating() || !box_->GetShapeOutsideInfo())
return;
// The box_ may not have a valid size yet (due to an intermediate layout),
// use the fragment's size instead.
DCHECK(layout_result.PhysicalFragment());
LayoutSize box_size = layout_result.PhysicalFragment()->Size().ToLayoutSize();
// TODO(ikilpatrick): Ideally this should be moved to a NGLayoutResult
// computing the shape area. There may be an issue with the new fragmentation
// model and computing the correct sizes of shapes.
ShapeOutsideInfo* shape_outside = box_->GetShapeOutsideInfo();
LayoutBlock* containing_block = box_->ContainingBlock();
shape_outside->SetReferenceBoxLogicalSize(
containing_block->IsHorizontalWritingMode() ? box_size
: box_size.TransposedSize());
shape_outside->SetPercentageResolutionInlineSize(
percentage_resolution_inline_size);
}
void NGBlockNode::UseOldOutOfFlowPositioning() const {
DCHECK(box_->IsOutOfFlowPositioned());
box_->ContainingBlock()->InsertPositionedObject(box_);
}
// Save static position for legacy AbsPos layout.
void NGBlockNode::SaveStaticOffsetForLegacy(
const NGLogicalOffset& offset,
const LayoutObject* offset_container) {
DCHECK(box_->IsOutOfFlowPositioned());
// Only set static position if the current offset container
// is one that Legacy layout expects static offset from.
const LayoutObject* parent = box_->Parent();
if (parent == offset_container ||
(parent && parent->IsLayoutInline() &&
parent->ContainingBlock() == offset_container)) {
DCHECK(box_->Layer());
box_->Layer()->SetStaticBlockPosition(offset.block_offset);
box_->Layer()->SetStaticInlinePosition(offset.inline_offset);
}
}
void NGBlockNode::StoreMargins(const NGConstraintSpace& constraint_space,
const NGBoxStrut& margins) {
if (constraint_space.IsIntermediateLayout())
return;
NGPhysicalBoxStrut physical_margins = margins.ConvertToPhysical(
constraint_space.GetWritingMode(), constraint_space.Direction());
box_->SetMargin(physical_margins);
}
} // namespace blink