// 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 #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, Layout, void*, ()) WITH_ALGORITHM(base::Optional, 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 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 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 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 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 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 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., . 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 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 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 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()); // Using |LayoutObject::LayoutIfNeeded| save us a little bit of overhead, // compared to |LayoutObject::ForceChildLayout|. DCHECK(!box_->IsLayoutNGMixin()); if (box_->NeedsLayout()) box_->LayoutIfNeeded(); else box_->ForceChildLayout(); // 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 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