| // Copyright 2017 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_physical_container_fragment.h" |
| |
| #include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h" |
| #include "third_party/blink/renderer/core/layout/layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/layout_inline.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_layout_overflow_calculator.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h" |
| #include "third_party/blink/renderer/platform/geometry/layout_rect.h" |
| #include "third_party/blink/renderer/platform/wtf/size_assertions.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| struct SameSizeAsNGPhysicalContainerFragment : NGPhysicalFragment { |
| wtf_size_t size; |
| void* break_token; |
| std::unique_ptr<Vector<NGPhysicalOutOfFlowPositionedNode>> |
| oof_positioned_descendants_; |
| void* pointer; |
| }; |
| |
| ASSERT_SIZE(NGPhysicalContainerFragment, SameSizeAsNGPhysicalContainerFragment); |
| |
| } // namespace |
| |
| NGPhysicalContainerFragment::NGPhysicalContainerFragment( |
| NGContainerFragmentBuilder* builder, |
| WritingMode block_or_line_writing_mode, |
| NGLink* buffer, |
| NGFragmentType type, |
| unsigned sub_type) |
| : NGPhysicalFragment(builder, type, sub_type), |
| num_children_(builder->children_.size()), |
| break_token_(std::move(builder->break_token_)), |
| oof_positioned_descendants_( |
| builder->oof_positioned_descendants_.IsEmpty() |
| ? nullptr |
| : new Vector<NGPhysicalOutOfFlowPositionedNode>()), |
| buffer_(buffer) { |
| has_floating_descendants_for_paint_ = |
| builder->has_floating_descendants_for_paint_; |
| has_adjoining_object_descendants_ = |
| builder->has_adjoining_object_descendants_; |
| depends_on_percentage_block_size_ = DependsOnPercentageBlockSize(*builder); |
| |
| PhysicalSize size = Size(); |
| if (oof_positioned_descendants_) { |
| oof_positioned_descendants_->ReserveCapacity( |
| builder->oof_positioned_descendants_.size()); |
| const WritingModeConverter converter( |
| {builder->Style().GetWritingMode(), builder->Direction()}, size); |
| for (const auto& descendant : builder->oof_positioned_descendants_) { |
| oof_positioned_descendants_->emplace_back( |
| descendant.node, |
| descendant.static_position.ConvertToPhysical(converter), |
| descendant.inline_container); |
| } |
| } |
| |
| // Because flexible arrays need to be the last member in a class, we need to |
| // have the buffer passed as a constructor argument and have the actual |
| // storage be part of the subclass. |
| const WritingModeConverter converter( |
| {block_or_line_writing_mode, builder->Direction()}, size); |
| wtf_size_t i = 0; |
| for (auto& child : builder->children_) { |
| buffer[i].offset = |
| converter.ToPhysical(child.offset, child.fragment->Size()); |
| // Call the move constructor to move without |AddRef|. Fragments in |
| // |builder| are not used after |this| was constructed. |
| static_assert( |
| sizeof(buffer[0].fragment) == |
| sizeof(scoped_refptr<const NGPhysicalFragment>), |
| "scoped_refptr must be the size of a pointer for this to work"); |
| new (&buffer[i].fragment) |
| scoped_refptr<const NGPhysicalFragment>(std::move(child.fragment)); |
| DCHECK(!child.fragment); // Ensure it was moved. |
| ++i; |
| } |
| } |
| |
| NGPhysicalContainerFragment::NGPhysicalContainerFragment( |
| const NGPhysicalContainerFragment& other, |
| bool recalculate_layout_overflow, |
| NGLink* buffer) |
| : NGPhysicalFragment(other), |
| num_children_(other.num_children_), |
| break_token_(other.break_token_), |
| oof_positioned_descendants_( |
| other.oof_positioned_descendants_ |
| ? new Vector<NGPhysicalOutOfFlowPositionedNode>( |
| *other.oof_positioned_descendants_) |
| : nullptr), |
| buffer_(buffer) { |
| // To ensure the fragment tree is consistent, use the post-layout fragment. |
| for (wtf_size_t i = 0; i < num_children_; ++i) { |
| buffer[i].offset = other.buffer_[i].offset; |
| scoped_refptr<const NGPhysicalFragment> post_layout = |
| other.buffer_[i]->PostLayout(); |
| // While making the fragment tree consistent, we need to also clone any |
| // fragmentainer fragments, as they don't nessecerily have their result |
| // stored on the layout-object tree. |
| if (post_layout->IsFragmentainerBox()) { |
| const auto& box_fragment = To<NGPhysicalBoxFragment>(*post_layout); |
| |
| base::Optional<PhysicalRect> layout_overflow; |
| if (recalculate_layout_overflow) { |
| layout_overflow = |
| NGLayoutOverflowCalculator::RecalculateLayoutOverflowForFragment( |
| box_fragment); |
| } |
| |
| post_layout = NGPhysicalBoxFragment::CloneWithPostLayoutFragments( |
| box_fragment, layout_overflow); |
| } |
| new (&buffer[i].fragment) |
| scoped_refptr<const NGPhysicalFragment>(std::move(post_layout)); |
| } |
| } |
| |
| NGPhysicalContainerFragment::~NGPhysicalContainerFragment() = default; |
| |
| // additional_offset must be offset from the containing_block. |
| void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren( |
| Vector<PhysicalRect>* outline_rects, |
| const PhysicalOffset& additional_offset, |
| NGOutlineType outline_type, |
| const LayoutBoxModelObject* containing_block) const { |
| if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(this)) { |
| DCHECK_EQ(box->PostLayout(), box); |
| if (const NGFragmentItems* items = box->Items()) { |
| NGInlineCursor cursor(*box, *items); |
| AddOutlineRectsForCursor(outline_rects, additional_offset, outline_type, |
| containing_block, &cursor); |
| // Don't add |Children()|. If |this| has |NGFragmentItems|, children are |
| // either line box, which we already handled in items, or OOF, which we |
| // should ignore. |
| DCHECK(std::all_of(PostLayoutChildren().begin(), |
| PostLayoutChildren().end(), [](const NGLink& child) { |
| return child->IsLineBox() || |
| child->IsOutOfFlowPositioned(); |
| })); |
| return; |
| } |
| } |
| |
| for (const auto& child : PostLayoutChildren()) { |
| // Outlines of out-of-flow positioned descendants are handled in |
| // NGPhysicalBoxFragment::AddSelfOutlineRects(). |
| if (child->IsOutOfFlowPositioned()) |
| continue; |
| |
| // Outline of an element continuation or anonymous block continuation is |
| // added when we iterate the continuation chain. |
| // See NGPhysicalBoxFragment::AddSelfOutlineRects(). |
| if (!child->IsLineBox()) { |
| const LayoutObject* child_layout_object = child->GetLayoutObject(); |
| if (auto* child_layout_block_flow = |
| DynamicTo<LayoutBlockFlow>(child_layout_object)) { |
| if (child_layout_object->IsElementContinuation() || |
| child_layout_block_flow->IsAnonymousBlockContinuation()) |
| continue; |
| } |
| } |
| AddOutlineRectsForDescendant(child, outline_rects, additional_offset, |
| outline_type, containing_block); |
| } |
| } |
| |
| void NGPhysicalContainerFragment::AddOutlineRectsForCursor( |
| Vector<PhysicalRect>* outline_rects, |
| const PhysicalOffset& additional_offset, |
| NGOutlineType outline_type, |
| const LayoutBoxModelObject* containing_block, |
| NGInlineCursor* cursor) const { |
| for (; *cursor; cursor->MoveToNext()) { |
| DCHECK(cursor->Current().Item()); |
| const NGFragmentItem& item = *cursor->Current().Item(); |
| if (UNLIKELY(item.IsLayoutObjectDestroyedOrMoved())) |
| continue; |
| if (item.Type() == NGFragmentItem::kLine) { |
| AddOutlineRectsForDescendant( |
| {item.LineBoxFragment(), item.OffsetInContainerFragment()}, |
| outline_rects, additional_offset, outline_type, containing_block); |
| continue; |
| } |
| if (item.IsText()) { |
| if (outline_type == NGOutlineType::kDontIncludeBlockVisualOverflow) |
| continue; |
| outline_rects->push_back( |
| PhysicalRect(additional_offset + item.OffsetInContainerFragment(), |
| item.Size().ToLayoutSize())); |
| continue; |
| } |
| if (item.Type() == NGFragmentItem::kBox) { |
| if (const NGPhysicalBoxFragment* child_box = |
| item.PostLayoutBoxFragment()) { |
| DCHECK(!child_box->IsOutOfFlowPositioned()); |
| AddOutlineRectsForDescendant( |
| {child_box, item.OffsetInContainerFragment()}, outline_rects, |
| additional_offset, outline_type, containing_block); |
| } |
| continue; |
| } |
| } |
| } |
| |
| void NGPhysicalContainerFragment::AddScrollableOverflowForInlineChild( |
| const NGPhysicalBoxFragment& container, |
| const ComputedStyle& container_style, |
| const NGFragmentItem& line, |
| bool has_hanging, |
| const NGInlineCursor& cursor, |
| TextHeightType height_type, |
| PhysicalRect* overflow) const { |
| DCHECK(IsLineBox() || IsInlineBox()); |
| DCHECK(cursor.Current().Item() && |
| (cursor.Current().Item()->BoxFragment() == this || |
| cursor.Current().Item()->LineBoxFragment() == this)); |
| const WritingMode container_writing_mode = container_style.GetWritingMode(); |
| for (NGInlineCursor descendants = cursor.CursorForDescendants(); |
| descendants;) { |
| const NGFragmentItem* item = descendants.CurrentItem(); |
| DCHECK(item); |
| if (UNLIKELY(item->IsLayoutObjectDestroyedOrMoved())) { |
| NOTREACHED(); |
| descendants.MoveToNextSkippingChildren(); |
| continue; |
| } |
| if (item->IsText()) { |
| PhysicalRect child_scroll_overflow = item->RectInContainerFragment(); |
| if (height_type == TextHeightType::kEmHeight) { |
| child_scroll_overflow = AdjustTextRectForEmHeight( |
| child_scroll_overflow, item->Style(), item->TextShapeResult(), |
| container_writing_mode); |
| } |
| if (UNLIKELY(has_hanging)) { |
| AdjustScrollableOverflowForHanging(line.RectInContainerFragment(), |
| container_writing_mode, |
| &child_scroll_overflow); |
| } |
| overflow->Unite(child_scroll_overflow); |
| descendants.MoveToNextSkippingChildren(); |
| continue; |
| } |
| |
| if (const NGPhysicalBoxFragment* child_box = |
| item->PostLayoutBoxFragment()) { |
| PhysicalRect child_scroll_overflow; |
| if (height_type == TextHeightType::kNormalHeight || |
| (child_box->BoxType() != kInlineBox && !IsRubyBox())) |
| child_scroll_overflow = item->RectInContainerFragment(); |
| if (child_box->IsInlineBox()) { |
| child_box->AddScrollableOverflowForInlineChild( |
| container, container_style, line, has_hanging, descendants, |
| height_type, &child_scroll_overflow); |
| child_box->AdjustScrollableOverflowForPropagation( |
| container, height_type, &child_scroll_overflow); |
| if (UNLIKELY(has_hanging)) { |
| AdjustScrollableOverflowForHanging(line.RectInContainerFragment(), |
| container_writing_mode, |
| &child_scroll_overflow); |
| } |
| } else { |
| child_scroll_overflow = |
| child_box->ScrollableOverflowForPropagation(container, height_type); |
| child_scroll_overflow.offset += item->OffsetInContainerFragment(); |
| } |
| overflow->Unite(child_scroll_overflow); |
| descendants.MoveToNextSkippingChildren(); |
| continue; |
| } |
| |
| // Add all children of a culled inline box; i.e., an inline box without |
| // margin/border/padding etc. |
| DCHECK_EQ(item->Type(), NGFragmentItem::kBox); |
| descendants.MoveToNext(); |
| } |
| } |
| |
| // Chop the hanging part from scrollable overflow. Children overflow in inline |
| // direction should hang, which should not cause scroll. |
| // TODO(kojii): Should move to text fragment to make this more accurate. |
| void NGPhysicalContainerFragment::AdjustScrollableOverflowForHanging( |
| const PhysicalRect& rect, |
| const WritingMode container_writing_mode, |
| PhysicalRect* overflow) { |
| if (IsHorizontalWritingMode(container_writing_mode)) { |
| if (overflow->offset.left < rect.offset.left) |
| overflow->offset.left = rect.offset.left; |
| if (overflow->Right() > rect.Right()) |
| overflow->ShiftRightEdgeTo(rect.Right()); |
| } else { |
| if (overflow->offset.top < rect.offset.top) |
| overflow->offset.top = rect.offset.top; |
| if (overflow->Bottom() > rect.Bottom()) |
| overflow->ShiftBottomEdgeTo(rect.Bottom()); |
| } |
| } |
| |
| // additional_offset must be offset from the containing_block because |
| // LocalToAncestorRect returns rects wrt containing_block. |
| void NGPhysicalContainerFragment::AddOutlineRectsForDescendant( |
| const NGLink& descendant, |
| Vector<PhysicalRect>* outline_rects, |
| const PhysicalOffset& additional_offset, |
| NGOutlineType outline_type, |
| const LayoutBoxModelObject* containing_block) const { |
| DCHECK(!descendant->IsLayoutObjectDestroyedOrMoved()); |
| if (descendant->IsListMarker()) |
| return; |
| |
| if (const auto* descendant_box = |
| DynamicTo<NGPhysicalBoxFragment>(descendant.get())) { |
| DCHECK_EQ(descendant_box->PostLayout(), descendant_box); |
| const LayoutObject* descendant_layout_object = |
| descendant_box->GetLayoutObject(); |
| |
| // TODO(layoutng): Explain this check. I assume we need it because layers |
| // may have transforms and so we have to go through LocalToAncestorRects? |
| if (descendant_box->HasLayer()) { |
| DCHECK(descendant_layout_object); |
| Vector<PhysicalRect> layer_outline_rects; |
| descendant_box->AddOutlineRects(PhysicalOffset(), outline_type, |
| &layer_outline_rects); |
| |
| // Don't pass additional_offset because LocalToAncestorRects will itself |
| // apply it. |
| descendant_layout_object->LocalToAncestorRects( |
| layer_outline_rects, containing_block, PhysicalOffset(), |
| PhysicalOffset()); |
| outline_rects->AppendVector(layer_outline_rects); |
| return; |
| } |
| |
| if (!descendant_box->IsInlineBox()) { |
| descendant_box->AddSelfOutlineRects( |
| additional_offset + descendant.Offset(), outline_type, outline_rects); |
| return; |
| } |
| |
| DCHECK(descendant_layout_object); |
| const auto* descendant_layout_inline = |
| To<LayoutInline>(descendant_layout_object); |
| // As an optimization, an ancestor has added rects for its line boxes |
| // covering descendants' line boxes, so descendants don't need to add line |
| // boxes again. For example, if the parent is a LayoutBlock, it adds rects |
| // for its line box which cover the line boxes of this LayoutInline. So |
| // the LayoutInline needs to add rects for children and continuations |
| // only. |
| if (descendant_box->IsOutlineOwner()) { |
| // We don't pass additional_offset here because the function requires |
| // additional_offset to be the offset from the containing block. |
| descendant_layout_inline->AddOutlineRectsForChildrenAndContinuations( |
| *outline_rects, PhysicalOffset(), outline_type); |
| } |
| return; |
| } |
| |
| if (const auto* descendant_line_box = |
| DynamicTo<NGPhysicalLineBoxFragment>(descendant.get())) { |
| descendant_line_box->AddOutlineRectsForNormalChildren( |
| outline_rects, additional_offset + descendant.Offset(), outline_type, |
| containing_block); |
| |
| if (!descendant_line_box->Size().IsEmpty()) { |
| outline_rects->emplace_back(additional_offset + descendant.Offset(), |
| descendant_line_box->Size().ToLayoutSize()); |
| } |
| } |
| } |
| |
| bool NGPhysicalContainerFragment::DependsOnPercentageBlockSize( |
| const NGContainerFragmentBuilder& builder) { |
| NGLayoutInputNode node = builder.node_; |
| |
| if (!node || node.IsInline()) |
| return builder.has_descendant_that_depends_on_percentage_block_size_; |
| |
| // For the below if-stmt we only want to consider legacy *containers* as |
| // potentially having %-dependent children - i.e. an image doesn't have any |
| // children. |
| bool is_legacy_container_with_percent_height_descendants = |
| builder.is_legacy_layout_root_ && !node.IsReplaced() && |
| node.GetLayoutBox()->MaybeHasPercentHeightDescendant(); |
| |
| // NOTE: If an element is OOF positioned, and has top/bottom constraints |
| // which are percentage based, this function will return false. |
| // |
| // This is fine as the top/bottom constraints are computed *before* layout, |
| // and the result is set as a fixed-block-size constraint. (And the caching |
| // logic will never check the result of this function). |
| // |
| // The result of this function still may be used for an OOF positioned |
| // element if it has a percentage block-size however, but this will return |
| // the correct result from below. |
| |
| // There are two conditions where we need to know about an (arbitrary) |
| // descendant which depends on a %-block-size. |
| // - In quirks mode, the arbitrary descendant may depend the percentage |
| // resolution block-size given (to this node), and need to relayout if |
| // this size changes. |
| // - A flex-item may have its "definiteness" change, (e.g. if itself is a |
| // flex item which is being stretched). This definiteness change will |
| // affect any %-block-size children. |
| // |
| // NOTE(ikilpatrick): For the flex-item case this is potentially too general. |
| // We only need to know about if this flex-item has a %-block-size child if |
| // the "definiteness" changes, not if the percentage resolution size changes. |
| if ((builder.has_descendant_that_depends_on_percentage_block_size_ || |
| is_legacy_container_with_percent_height_descendants) && |
| (node.UseParentPercentageResolutionBlockSizeForChildren() || |
| node.IsFlexItem())) |
| return true; |
| |
| const ComputedStyle& style = builder.Style(); |
| if (style.LogicalHeight().IsPercentOrCalc() || |
| style.LogicalMinHeight().IsPercentOrCalc() || |
| style.LogicalMaxHeight().IsPercentOrCalc()) |
| return true; |
| |
| return false; |
| } |
| |
| } // namespace blink |