| // 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_out_of_flow_layout_part.h" |
| |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/ng/inline/ng_physical_line_box_fragment.h" |
| #include "core/layout/ng/ng_absolute_utils.h" |
| #include "core/layout/ng/ng_block_node.h" |
| #include "core/layout/ng/ng_box_fragment.h" |
| #include "core/layout/ng/ng_constraint_space_builder.h" |
| #include "core/layout/ng/ng_fragment_builder.h" |
| #include "core/layout/ng/ng_layout_result.h" |
| #include "core/layout/ng/ng_length_utils.h" |
| #include "core/layout/ng/ng_physical_fragment.h" |
| #include "core/style/ComputedStyle.h" |
| |
| namespace blink { |
| |
| NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart( |
| const NGBlockNode container, |
| const NGConstraintSpace& container_space, |
| const ComputedStyle& container_style, |
| NGFragmentBuilder* container_builder) |
| : container_builder_(container_builder), |
| contains_absolute_(container.IsAbsoluteContainer()), |
| contains_fixed_(container.IsFixedContainer()) { |
| NGBoxStrut borders_and_scrollers = |
| ComputeBorders(container_space, container_style) + |
| container.GetScrollbarSizes(); |
| NGPhysicalBoxStrut physical_borders = borders_and_scrollers.ConvertToPhysical( |
| container_style.GetWritingMode(), container_style.Direction()); |
| |
| icb_size_ = container_space.InitialContainingBlockSize(); |
| |
| default_containing_block_.style = &container_style; |
| default_containing_block_.content_size = container_builder_->Size(); |
| default_containing_block_.content_size.inline_size -= |
| borders_and_scrollers.InlineSum(); |
| default_containing_block_.content_size.block_size -= |
| borders_and_scrollers.BlockSum(); |
| |
| default_containing_block_.content_offset = NGLogicalOffset{ |
| borders_and_scrollers.inline_start, borders_and_scrollers.block_start}; |
| default_containing_block_.content_physical_offset = |
| NGPhysicalOffset(physical_borders.left, physical_borders.top); |
| } |
| |
| void NGOutOfFlowLayoutPart::Run(bool update_legacy) { |
| Vector<NGOutOfFlowPositionedDescendant> descendant_candidates; |
| container_builder_->GetAndClearOutOfFlowDescendantCandidates( |
| &descendant_candidates, container_builder_->GetLayoutObject()); |
| |
| while (descendant_candidates.size() > 0) { |
| ComputeInlineContainingBlocks(descendant_candidates); |
| for (auto& candidate : descendant_candidates) { |
| if (IsContainingBlockForDescendant(candidate)) { |
| NGLogicalOffset offset; |
| scoped_refptr<NGLayoutResult> result = |
| LayoutDescendant(candidate, &offset); |
| container_builder_->AddChild(std::move(result), offset); |
| if (update_legacy) |
| candidate.node.UseOldOutOfFlowPositioning(); |
| } else { |
| container_builder_->AddOutOfFlowDescendant(candidate); |
| } |
| } |
| // Sweep any descendants that might have been added. |
| // This happens when an absolute container has a fixed child. |
| descendant_candidates.clear(); |
| container_builder_->GetAndClearOutOfFlowDescendantCandidates( |
| &descendant_candidates, container_builder_->GetLayoutObject()); |
| } |
| } |
| |
| NGOutOfFlowLayoutPart::ContainingBlockInfo |
| NGOutOfFlowLayoutPart::GetContainingBlockInfo( |
| const NGOutOfFlowPositionedDescendant& descendant) const { |
| // TODO remove containing_blocks_map_.Contains |
| if (descendant.inline_container && |
| containing_blocks_map_.Contains(descendant.inline_container)) { |
| DCHECK(containing_blocks_map_.Contains(descendant.inline_container)); |
| return containing_blocks_map_.at(descendant.inline_container); |
| } |
| return default_containing_block_; |
| } |
| |
| void NGOutOfFlowLayoutPart::ComputeInlineContainingBlocks( |
| Vector<NGOutOfFlowPositionedDescendant> descendants) { |
| HashMap<const LayoutObject*, NGFragmentBuilder::FragmentPair> |
| inline_container_fragments; |
| |
| for (auto& descendant : descendants) { |
| if (descendant.inline_container && |
| !inline_container_fragments.Contains(descendant.inline_container)) { |
| NGFragmentBuilder::FragmentPair fragment_pair = {}; |
| inline_container_fragments.insert(descendant.inline_container, |
| fragment_pair); |
| } |
| } |
| // Fetch start/end fragment info. |
| NGLogicalSize container_builder_size; |
| container_builder_->ComputeInlineContainerFragments( |
| &inline_container_fragments, &container_builder_size); |
| NGPhysicalSize container_builder_physical_size = |
| container_builder_size.ConvertToPhysical( |
| default_containing_block_.style->GetWritingMode()); |
| // Translate start/end fragments into ContainingBlockInfo. |
| for (auto& block_info : inline_container_fragments) { |
| if (!block_info.value.start_fragment) { |
| // This happens when Legacy block is the default container. |
| // In this case, container builder does not have any fragments because |
| // ng layout algorithm did not run. |
| DCHECK(false); |
| // TODO(atotic) ContainingBlockInfo must be computed from Legacy algorithm |
| } |
| NGLogicalOffset inline_content_offset; |
| NGPhysicalOffset inline_content_physical_offset; |
| NGLogicalSize inline_cb_size; |
| const ComputedStyle* inline_cb_style; |
| inline_cb_style = &block_info.value.start_fragment->Style(); |
| scoped_refptr<NGConstraintSpace> dummy_constraint_space = |
| NGConstraintSpaceBuilder(inline_cb_style->GetWritingMode(), icb_size_) |
| .ToConstraintSpace(inline_cb_style->GetWritingMode()); |
| // TODO Creating dummy constraint space just to get borders feels wrong. |
| NGBoxStrut inline_cb_borders = |
| ComputeBorders(*dummy_constraint_space, *inline_cb_style); |
| NGPhysicalBoxStrut physical_borders = inline_cb_borders.ConvertToPhysical( |
| inline_cb_style->GetWritingMode(), inline_cb_style->Direction()); |
| NGBoxStrut inline_cb_padding = |
| ComputePadding(*dummy_constraint_space, *inline_cb_style); |
| |
| // Warning: lots of non-obvious coordinate manipulation ahead. |
| // |
| // High level goal is: |
| // - Find logical topleft of start fragment, and logical bottomright |
| // of end fragment. |
| // - Use these to compute inline-cb geometry: |
| // - inline-cb size (content_size) |
| // - inline-cb offset from containing block (default_container_offset) |
| // |
| // We start with: |
| // start_fragment, which has physical offset from start_linebox_fragment, |
| // end_fragment, also with physical offset. |
| // start_linebox_fragment, which has logical offset from containing box. |
| // end_linebox_fragment, which also has logical offset from containing box. |
| // |
| // Then magic happens.^H^H^H^H^H^H^H |
| // |
| // Then we do the following: |
| // 1. Find start fragment physical topleft wrt containing box. |
| // - convert start_fragment offset to logical. |
| // - convert start fragment inline/block start to physical. |
| // - convert linebox topleft to physical. |
| // - add start fragment to linebox topleft |
| // 2. Find end fragment bottom right wrt containing box |
| // - convert end fragment offset to logical. |
| // - convert end fragment inline/block end to physical |
| // - convert linebox topleft to physical |
| // - add end fragment bottomLeft to linebox topleft |
| // 3. Convert both topleft/bottomright to logical, so that we can |
| // 4. Enforce logical topLeft < bottomRight |
| // 5. Compute size, physical offset |
| const NGPhysicalFragment* start_fragment = block_info.value.start_fragment; |
| const NGPhysicalLineBoxFragment* start_linebox_fragment = |
| block_info.value.start_linebox_fragment; |
| WritingMode container_writing_mode = |
| default_containing_block_.style->GetWritingMode(); |
| TextDirection container_direction = |
| default_containing_block_.style->Direction(); |
| // Step 1 |
| NGLogicalOffset start_fragment_logical_offset = |
| block_info.value.start_fragment_union_rect.offset.ConvertToLogical( |
| container_writing_mode, container_direction, |
| start_linebox_fragment->Size(), |
| block_info.value.start_fragment_union_rect.size); |
| // Text fragments do not include inline-cb borders and padding. |
| if (start_fragment->IsText()) { |
| start_fragment_logical_offset -= inline_cb_borders.StartOffset(); |
| start_fragment_logical_offset -= inline_cb_padding.StartOffset(); |
| } |
| NGPhysicalOffset start_fragment_physical_offset = |
| start_fragment_logical_offset.ConvertToPhysical( |
| container_writing_mode, container_direction, |
| start_linebox_fragment->Size(), NGPhysicalSize()); |
| NGPhysicalOffset start_linebox_physical_offset = |
| block_info.value.start_linebox_offset.ConvertToPhysical( |
| container_writing_mode, container_direction, |
| container_builder_physical_size, start_linebox_fragment->Size()); |
| start_fragment_physical_offset += start_linebox_physical_offset; |
| // Step 2 |
| const NGPhysicalLineBoxFragment* end_linebox_fragment = |
| block_info.value.end_linebox_fragment; |
| const NGPhysicalFragment* end_fragment = block_info.value.end_fragment; |
| NGLogicalOffset end_fragment_logical_offset = |
| block_info.value.end_fragment_union_rect.offset.ConvertToLogical( |
| container_writing_mode, container_direction, |
| end_linebox_fragment->Size(), |
| block_info.value.end_fragment_union_rect.size); |
| // Text fragments do not include inline-cb borders and padding. |
| if (end_fragment->IsText()) { |
| end_fragment_logical_offset += NGLogicalOffset( |
| inline_cb_borders.inline_end, inline_cb_borders.block_end); |
| end_fragment_logical_offset += NGLogicalOffset( |
| inline_cb_padding.inline_end, inline_cb_padding.block_end); |
| } |
| NGLogicalOffset end_fragment_bottom_right = |
| end_fragment_logical_offset + |
| block_info.value.end_fragment_union_rect.size.ConvertToLogical( |
| container_writing_mode); |
| NGPhysicalOffset end_fragment_physical_offset = |
| end_fragment_bottom_right.ConvertToPhysical( |
| container_writing_mode, container_direction, |
| end_linebox_fragment->Size(), NGPhysicalSize()); |
| NGPhysicalOffset end_linebox_physical_offset = |
| block_info.value.end_linebox_offset.ConvertToPhysical( |
| container_writing_mode, container_direction, |
| container_builder_physical_size, end_linebox_fragment->Size()); |
| end_fragment_physical_offset += end_linebox_physical_offset; |
| // Step 3 |
| NGLogicalOffset start_fragment_logical_offset_wrt_box = |
| start_fragment_physical_offset.ConvertToLogical( |
| inline_cb_style->GetWritingMode(), inline_cb_style->Direction(), |
| container_builder_physical_size, NGPhysicalSize()); |
| NGLogicalOffset end_fragment_logical_offset_wrt_box = |
| end_fragment_physical_offset.ConvertToLogical( |
| inline_cb_style->GetWritingMode(), inline_cb_style->Direction(), |
| container_builder_physical_size, NGPhysicalSize()); |
| |
| // Step 4 |
| end_fragment_logical_offset_wrt_box.inline_offset = |
| std::max(end_fragment_logical_offset_wrt_box.inline_offset, |
| start_fragment_logical_offset_wrt_box.inline_offset + |
| inline_cb_borders.InlineSum()); |
| end_fragment_logical_offset_wrt_box.block_offset = |
| std::max(end_fragment_logical_offset_wrt_box.block_offset, |
| start_fragment_logical_offset_wrt_box.block_offset + |
| inline_cb_borders.BlockSum()); |
| |
| // Step 5 |
| inline_cb_size.inline_size = |
| end_fragment_logical_offset_wrt_box.inline_offset - |
| start_fragment_logical_offset_wrt_box.inline_offset - |
| inline_cb_borders.InlineSum(); |
| inline_cb_size.block_size = |
| end_fragment_logical_offset_wrt_box.block_offset - |
| start_fragment_logical_offset_wrt_box.block_offset - |
| inline_cb_borders.BlockSum(); |
| |
| DCHECK_GE(inline_cb_size.inline_size, LayoutUnit()); |
| DCHECK_GE(inline_cb_size.block_size, LayoutUnit()); |
| |
| inline_content_offset = NGLogicalOffset{inline_cb_borders.inline_start, |
| inline_cb_borders.block_start}; |
| inline_content_physical_offset = |
| NGPhysicalOffset(physical_borders.left, physical_borders.top); |
| |
| NGLogicalOffset default_container_offset; |
| if (RuntimeEnabledFeatures::LayoutNGPaintFragmentsEnabled()) { |
| // NGPaint offset is wrt parent fragment. |
| default_container_offset = start_fragment_logical_offset_wrt_box - |
| default_containing_block_.content_offset; |
| default_container_offset += inline_cb_borders.StartOffset(); |
| } else { |
| // Legacy offset is wrt inline container. |
| default_container_offset = |
| NGLogicalOffset(inline_cb_borders.inline_start, |
| inline_cb_borders.block_start) - |
| default_containing_block_.content_offset; |
| } |
| |
| containing_blocks_map_.insert( |
| block_info.key, ContainingBlockInfo{inline_cb_style, inline_cb_size, |
| inline_content_offset, |
| inline_content_physical_offset, |
| default_container_offset}); |
| } |
| } |
| |
| scoped_refptr<NGLayoutResult> NGOutOfFlowLayoutPart::LayoutDescendant( |
| const NGOutOfFlowPositionedDescendant& descendant, |
| NGLogicalOffset* offset) { |
| ContainingBlockInfo container_info = GetContainingBlockInfo(descendant); |
| |
| WritingMode container_writing_mode(container_info.style->GetWritingMode()); |
| WritingMode descendant_writing_mode(descendant.node.Style().GetWritingMode()); |
| |
| // Adjust the static_position origin. The static_position coordinate origin is |
| // relative to the container's border box, ng_absolute_utils expects it to be |
| // relative to the container's padding box. |
| NGStaticPosition static_position(descendant.static_position); |
| static_position.offset -= container_info.content_physical_offset; |
| |
| // The block estimate is in the descendant's writing mode. |
| scoped_refptr<NGConstraintSpace> descendant_constraint_space = |
| NGConstraintSpaceBuilder(container_writing_mode, icb_size_) |
| .SetTextDirection(container_info.style->Direction()) |
| .SetAvailableSize(container_info.content_size) |
| .SetPercentageResolutionSize(container_info.content_size) |
| .ToConstraintSpace(descendant_writing_mode); |
| Optional<MinMaxSize> min_max_size; |
| Optional<LayoutUnit> block_estimate; |
| |
| scoped_refptr<NGLayoutResult> layout_result = nullptr; |
| |
| NGBlockNode node = descendant.node; |
| if (AbsoluteNeedsChildInlineSize(descendant.node.Style()) || |
| NeedMinMaxSize(descendant.node.Style())) { |
| min_max_size = node.ComputeMinMaxSize(); |
| } |
| |
| Optional<NGLogicalSize> replaced_size; |
| if (descendant.node.IsReplaced()) |
| replaced_size = ComputeReplacedSize( |
| descendant.node, *descendant_constraint_space, min_max_size); |
| |
| NGAbsolutePhysicalPosition node_position = |
| ComputePartialAbsoluteWithChildInlineSize( |
| *descendant_constraint_space, descendant.node.Style(), |
| static_position, min_max_size, replaced_size, container_writing_mode, |
| container_info.style->Direction()); |
| |
| if (AbsoluteNeedsChildBlockSize(descendant.node.Style())) { |
| layout_result = GenerateFragment(descendant.node, container_info, |
| block_estimate, node_position); |
| |
| DCHECK(layout_result->PhysicalFragment().get()); |
| NGFragment fragment(descendant_writing_mode, |
| *layout_result->PhysicalFragment()); |
| |
| block_estimate = fragment.BlockSize(); |
| } |
| |
| ComputeFullAbsoluteWithChildBlockSize( |
| *descendant_constraint_space, descendant.node.Style(), static_position, |
| block_estimate, replaced_size, container_writing_mode, |
| container_info.style->Direction(), &node_position); |
| |
| // Skip this step if we produced a fragment when estimating the block size. |
| if (!layout_result) { |
| block_estimate = |
| node_position.size.ConvertToLogical(descendant_writing_mode).block_size; |
| layout_result = GenerateFragment(descendant.node, container_info, |
| block_estimate, node_position); |
| } |
| |
| // Compute logical offset, NGAbsolutePhysicalPosition is calculated relative |
| // to the padding box so add back the container's borders. |
| NGBoxStrut inset = node_position.inset.ConvertToLogical( |
| container_writing_mode, container_info.style->Direction()); |
| offset->inline_offset = |
| inset.inline_start + |
| default_containing_block_.content_offset.inline_offset; |
| offset->block_offset = |
| inset.block_start + default_containing_block_.content_offset.block_offset; |
| |
| offset->inline_offset += |
| container_info.default_container_offset.inline_offset; |
| offset->block_offset += container_info.default_container_offset.block_offset; |
| return layout_result; |
| } |
| |
| bool NGOutOfFlowLayoutPart::IsContainingBlockForDescendant( |
| const NGOutOfFlowPositionedDescendant& descendant) { |
| EPosition position = descendant.node.Style().GetPosition(); |
| if (descendant.inline_container) { |
| DCHECK(position == EPosition::kAbsolute); |
| return true; |
| } |
| return (contains_absolute_ && position == EPosition::kAbsolute) || |
| (contains_fixed_ && position == EPosition::kFixed); |
| } |
| |
| // The fragment is generated in one of these two scenarios: |
| // 1. To estimate descendant's block size, in this case block_size is |
| // container's available size. |
| // 2. To compute final fragment, when block size is known from the absolute |
| // position calculation. |
| scoped_refptr<NGLayoutResult> NGOutOfFlowLayoutPart::GenerateFragment( |
| NGBlockNode descendant, |
| const ContainingBlockInfo& container_info, |
| const Optional<LayoutUnit>& block_estimate, |
| const NGAbsolutePhysicalPosition node_position) { |
| // As the block_estimate is always in the descendant's writing mode, we build |
| // the constraint space in the descendant's writing mode. |
| WritingMode writing_mode(descendant.Style().GetWritingMode()); |
| NGLogicalSize container_size( |
| container_info.content_size |
| .ConvertToPhysical(default_containing_block_.style->GetWritingMode()) |
| .ConvertToLogical(writing_mode)); |
| |
| LayoutUnit inline_size = |
| node_position.size.ConvertToLogical(writing_mode).inline_size; |
| LayoutUnit block_size = |
| block_estimate ? *block_estimate : container_size.block_size; |
| |
| NGLogicalSize available_size{inline_size, block_size}; |
| |
| // TODO(atotic) will need to be adjusted for scrollbars. |
| NGConstraintSpaceBuilder builder(writing_mode, icb_size_); |
| builder.SetAvailableSize(available_size) |
| .SetTextDirection(descendant.Style().Direction()) |
| .SetPercentageResolutionSize(container_size) |
| .SetIsNewFormattingContext(true) |
| .SetIsFixedSizeInline(true); |
| if (block_estimate) |
| builder.SetIsFixedSizeBlock(true); |
| scoped_refptr<NGConstraintSpace> space = |
| builder.ToConstraintSpace(writing_mode); |
| |
| return descendant.Layout(*space); |
| } |
| |
| } // namespace blink |