| // 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_out_of_flow_layout_part.h" |
| |
| #include "third_party/blink/renderer/core/layout/layout_block.h" |
| #include "third_party/blink/renderer/core/layout/layout_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_descendant.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| |
| namespace blink { |
| |
| NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart( |
| NGBoxFragmentBuilder* container_builder, |
| bool contains_absolute, |
| bool contains_fixed, |
| const NGBoxStrut& border_scrollbar, |
| const NGConstraintSpace& container_space, |
| const ComputedStyle& container_style, |
| base::Optional<NGLogicalSize> initial_containing_block_fixed_size) |
| : container_builder_(container_builder), |
| contains_absolute_(contains_absolute), |
| contains_fixed_(contains_fixed) { |
| if (!container_builder->HasOutOfFlowDescendantCandidates()) |
| return; |
| NGPhysicalBoxStrut physical_border_scrollbar = |
| border_scrollbar.ConvertToPhysical(container_style.GetWritingMode(), |
| container_style.Direction()); |
| |
| default_containing_block_.style = &container_style; |
| default_containing_block_.content_size_for_absolute = |
| container_builder_->Size(); |
| default_containing_block_.content_size_for_absolute.inline_size = |
| std::max(default_containing_block_.content_size_for_absolute.inline_size - |
| border_scrollbar.InlineSum(), |
| LayoutUnit()); |
| default_containing_block_.content_size_for_absolute.block_size = |
| std::max(default_containing_block_.content_size_for_absolute.block_size - |
| border_scrollbar.BlockSum(), |
| LayoutUnit()); |
| default_containing_block_.content_size_for_fixed = |
| initial_containing_block_fixed_size |
| ? initial_containing_block_fixed_size.value() |
| : default_containing_block_.content_size_for_absolute; |
| |
| default_containing_block_.container_offset = NGLogicalOffset( |
| border_scrollbar.inline_start, border_scrollbar.block_start); |
| default_containing_block_.physical_container_offset = NGPhysicalOffset( |
| physical_border_scrollbar.left, physical_border_scrollbar.top); |
| } |
| |
| void NGOutOfFlowLayoutPart::Run(LayoutBox* only_layout) { |
| 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) && |
| (!only_layout || candidate.node.GetLayoutBox() == only_layout)) { |
| NGLogicalOffset offset; |
| scoped_refptr<NGLayoutResult> result = |
| LayoutDescendant(candidate, &offset); |
| container_builder_->AddChild(*result, offset); |
| if (candidate.node.GetLayoutBox() != only_layout) |
| 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.Shrink(0); |
| 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) { |
| NGBoxFragmentBuilder::InlineContainingBlockMap inline_container_fragments; |
| |
| for (auto& descendant : descendants) { |
| if (descendant.inline_container && |
| !inline_container_fragments.Contains(descendant.inline_container)) { |
| NGBoxFragmentBuilder::InlineContainingBlockGeometry inline_geometry = {}; |
| inline_container_fragments.insert(descendant.inline_container, |
| inline_geometry); |
| } |
| } |
| // Fetch start/end fragment info. |
| container_builder_->ComputeInlineContainerFragments( |
| &inline_container_fragments); |
| NGLogicalSize container_builder_size = container_builder_->Size(); |
| NGPhysicalSize container_builder_physical_size = |
| ToNGPhysicalSize(container_builder_size, |
| default_containing_block_.style->GetWritingMode()); |
| // Translate start/end fragments into ContainingBlockInfo. |
| for (auto& block_info : inline_container_fragments) { |
| // Variables needed to describe ContainingBlockInfo |
| const ComputedStyle* inline_cb_style = block_info.key->Style(); |
| NGLogicalSize inline_cb_size; |
| NGLogicalOffset container_offset; |
| NGPhysicalOffset physical_container_offset; |
| |
| if (!block_info.value.has_value()) { |
| // 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(block_info.key->IsLayoutInline()); |
| NOTIMPLEMENTED() |
| << "Inline containing block might need geometry information"; |
| // TODO(atotic) ContainingBlockInfo geometry |
| // must be computed from Legacy algorithm |
| } else { |
| DCHECK(inline_cb_style); |
| |
| // TODO Creating dummy constraint space just to get borders feels wrong. |
| NGConstraintSpace dummy_constraint_space = |
| NGConstraintSpaceBuilder(inline_cb_style->GetWritingMode(), |
| inline_cb_style->GetWritingMode(), |
| /* is_new_fc */ false) |
| .ToConstraintSpace(); |
| NGBoxStrut inline_cb_borders = |
| ComputeBorders(dummy_constraint_space, *inline_cb_style); |
| |
| // The calculation below determines the size of the inline containing |
| // block rect. |
| // |
| // To perform this calculation we: |
| // 1. Determine the start_offset "^", this is at the logical-start (wrt. |
| // default containing block), of the start fragment rect. |
| // 2. Determine the end_offset "$", this is at the logical-end (wrt. |
| // default containing block), of the end fragment rect. |
| // 3. Determine the logical rectangle defined by these two offsets. |
| // |
| // Case 1a: Same direction, overlapping fragments. |
| // +--------------- |
| // ---> |^*****--------> |
| // +*----*--------- |
| // * * |
| // ------*----*+ |
| // ----> *****$| ---> |
| // ------------+ |
| // |
| // Case 1b: Different direction, overlapping fragments. |
| // +--------------- |
| // ---> ^******* <-----| |
| // *------*-------- |
| // * * |
| // -----*------* |
| // |<-- *******$ ---> |
| // ------------+ |
| // |
| // Case 2a: Same direction, non-overlapping fragments. |
| // +-------- |
| // ---------> |^ -----> |
| // +*------- |
| // * |
| // --------+ * |
| // ------->| $ ---> |
| // --------+ |
| // |
| // Case 2b: Same direction, non-overlapping fragments. |
| // +-------- |
| // ---------> ^ <-----| |
| // *-------- |
| // * |
| // --------+ * |
| // | <------ $ ---> |
| // --------+ |
| // |
| // Note in cases [1a, 2a] we need to account for the inline borders of |
| // the rectangles, where-as in [1b, 2b] we do not. This is handled by the |
| // is_same_direction check(s). |
| // |
| // Note in cases [2a, 2b] we don't allow a "negative" containing block |
| // size, we clamp negative sizes to zero. |
| WritingMode container_writing_mode = |
| default_containing_block_.style->GetWritingMode(); |
| TextDirection container_direction = |
| default_containing_block_.style->Direction(); |
| |
| bool is_same_direction = |
| container_direction == inline_cb_style->Direction(); |
| |
| // Step 1 - determine the start_offset. |
| const NGPhysicalOffsetRect& start_rect = |
| block_info.value.value().start_fragment_union_rect; |
| NGLogicalOffset start_offset = start_rect.offset.ConvertToLogical( |
| container_writing_mode, container_direction, |
| container_builder_physical_size, start_rect.size); |
| |
| // Make sure we add the inline borders, we don't need to do this in the |
| // inline direction if the blocks are in opposite directions. |
| start_offset.block_offset += inline_cb_borders.block_start; |
| if (is_same_direction) |
| start_offset.inline_offset += inline_cb_borders.inline_start; |
| |
| // Step 2 - determine the end_offset. |
| const NGPhysicalOffsetRect& end_rect = |
| block_info.value.value().end_fragment_union_rect; |
| NGLogicalOffset end_offset = end_rect.offset.ConvertToLogical( |
| container_writing_mode, container_direction, |
| container_builder_physical_size, end_rect.size); |
| |
| // Add in the size of the fragment to get the logical end of the fragment. |
| end_offset += end_rect.size.ConvertToLogical(container_writing_mode); |
| |
| // Make sure we substract the inline borders, we don't need to do this in |
| // the inline direction if the blocks are in opposite directions. |
| end_offset.block_offset -= inline_cb_borders.block_end; |
| if (is_same_direction) |
| end_offset.inline_offset -= inline_cb_borders.inline_end; |
| |
| // Make sure we don't end up with a rectangle with "negative" size. |
| end_offset.inline_offset = |
| std::max(end_offset.inline_offset, start_offset.inline_offset); |
| |
| // Step 3 - determine the logical rectange. |
| |
| // Determine the logical size of the containing block. |
| inline_cb_size = {end_offset.inline_offset - start_offset.inline_offset, |
| end_offset.block_offset - start_offset.block_offset}; |
| DCHECK_GE(inline_cb_size.inline_size, LayoutUnit()); |
| DCHECK_GE(inline_cb_size.block_size, LayoutUnit()); |
| |
| // Determine the container offsets. |
| container_offset = start_offset; |
| physical_container_offset = container_offset.ConvertToPhysical( |
| container_writing_mode, container_direction, |
| container_builder_physical_size, |
| ToNGPhysicalSize(inline_cb_size, container_writing_mode)); |
| } |
| containing_blocks_map_.insert( |
| block_info.key, |
| ContainingBlockInfo{inline_cb_style, inline_cb_size, inline_cb_size, |
| container_offset, physical_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 (which is currently relative to the default |
| // container's border-box). ng_absolute_utils expects the static position to |
| // be relative to the container's padding-box. |
| NGStaticPosition static_position(descendant.static_position); |
| static_position.offset -= container_info.physical_container_offset; |
| |
| NGLogicalSize container_content_size = |
| container_info.ContentSize(descendant.node.Style().GetPosition()); |
| |
| NGConstraintSpace descendant_constraint_space = |
| NGConstraintSpaceBuilder(container_writing_mode, descendant_writing_mode, |
| /* is_new_fc */ true) |
| .SetTextDirection(container_info.style->Direction()) |
| .SetAvailableSize(container_content_size) |
| .SetPercentageResolutionSize(container_content_size) |
| .ToConstraintSpace(); |
| |
| // The block_estimate is in the descendant's writing mode. |
| base::Optional<LayoutUnit> block_estimate; |
| base::Optional<MinMaxSize> min_max_size; |
| |
| scoped_refptr<NGLayoutResult> layout_result = nullptr; |
| |
| NGBlockNode node = descendant.node; |
| if (AbsoluteNeedsChildInlineSize(descendant.node.Style()) || |
| NeedMinMaxSize(descendant.node.Style()) || |
| descendant.node.ShouldBeConsideredAsReplaced()) { |
| // This is a new formatting context, so whatever happened on the outside |
| // doesn't concern us. |
| MinMaxSizeInput zero_input; |
| min_max_size = node.ComputeMinMaxSize(descendant_writing_mode, zero_input, |
| &descendant_constraint_space); |
| } |
| |
| base::Optional<NGLogicalSize> replaced_size; |
| if (descendant.node.IsReplaced()) { |
| replaced_size = ComputeReplacedSize( |
| descendant.node, descendant_constraint_space, min_max_size); |
| } else if (descendant.node.ShouldBeConsideredAsReplaced()) { |
| replaced_size = NGLogicalSize{ |
| min_max_size->ShrinkToFit( |
| descendant_constraint_space.AvailableSize().inline_size), |
| NGSizeIndefinite}; |
| } |
| NGAbsolutePhysicalPosition node_position = |
| ComputePartialAbsoluteWithChildInlineSize( |
| descendant_constraint_space, descendant.node.Style(), static_position, |
| min_max_size, replaced_size, container_writing_mode, |
| container_info.style->Direction()); |
| |
| // ShouldBeConsideredAsReplaced sets inline size. |
| // It does not set block size. This is a compatiblity quirk. |
| if (!descendant.node.IsReplaced() && |
| descendant.node.ShouldBeConsideredAsReplaced()) |
| replaced_size.reset(); |
| |
| if (AbsoluteNeedsChildBlockSize(descendant.node.Style())) { |
| layout_result = GenerateFragment(descendant.node, container_info, |
| block_estimate, node_position); |
| |
| DCHECK(layout_result->PhysicalFragment()); |
| 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); |
| } |
| if (node.GetLayoutBox()->IsLayoutNGObject()) { |
| ToLayoutBlock(node.GetLayoutBox()) |
| ->SetIsLegacyInitiatedOutOfFlowLayout(false); |
| } |
| |
| NGBoxStrut inset = node_position.inset.ConvertToLogical( |
| container_writing_mode, default_containing_block_.style->Direction()); |
| |
| // inset is relative to the container's padding-box. Convert this to being |
| // relative to the default container's border-box. |
| offset->inline_offset = |
| inset.inline_start + container_info.container_offset.inline_offset; |
| offset->block_offset = |
| inset.block_start + container_info.container_offset.block_offset; |
| |
| base::Optional<LayoutUnit> y = ComputeAbsoluteDialogYPosition( |
| *descendant.node.GetLayoutBox(), |
| layout_result->PhysicalFragment()->Size().height); |
| if (y.has_value()) { |
| if (IsHorizontalWritingMode(container_writing_mode)) |
| offset->block_offset = y.value(); |
| else |
| offset->inline_offset = y.value(); |
| } |
| |
| return layout_result; |
| } |
| |
| bool NGOutOfFlowLayoutPart::IsContainingBlockForDescendant( |
| const NGOutOfFlowPositionedDescendant& descendant) { |
| EPosition position = descendant.node.Style().GetPosition(); |
| |
| // Descendants whose containing block is inline are always positioned |
| // inside closest parent block flow. |
| if (descendant.inline_container) { |
| DCHECK( |
| descendant.node.Style().GetPosition() == EPosition::kAbsolute && |
| descendant.inline_container->CanContainAbsolutePositionObjects() || |
| (descendant.node.Style().GetPosition() == EPosition::kFixed && |
| descendant.inline_container->CanContainFixedPositionObjects())); |
| 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 base::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( |
| ToNGPhysicalSize( |
| container_info.ContentSize(descendant.Style().GetPosition()), |
| 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, writing_mode, |
| /* is_new_fc */ true); |
| builder.SetAvailableSize(available_size) |
| .SetTextDirection(descendant.Style().Direction()) |
| .SetPercentageResolutionSize(container_size) |
| .SetIsFixedSizeInline(true); |
| if (block_estimate) |
| builder.SetIsFixedSizeBlock(true); |
| NGConstraintSpace space = builder.ToConstraintSpace(); |
| |
| scoped_refptr<NGLayoutResult> result = descendant.Layout(space); |
| |
| // Legacy Grid and Flexbox seem to handle oof margins correctly |
| // on their own, and break if we set them here. |
| if (!descendant.GetLayoutBox() |
| ->ContainingBlock() |
| ->Style() |
| ->IsDisplayFlexibleOrGridBox()) |
| descendant.GetLayoutBox()->SetMargin(node_position.margins); |
| |
| return result; |
| } |
| |
| } // namespace blink |