1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h"
6 
7 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
8 #include "third_party/blink/renderer/core/layout/layout_inline.h"
9 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
10 #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
11 #include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h"
12 #include "third_party/blink/renderer/core/layout/ng/ng_outline_utils.h"
13 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
14 #include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h"
15 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
16 
17 namespace blink {
18 
19 namespace {
20 
21 struct SameSizeAsNGPhysicalContainerFragment : NGPhysicalFragment {
22   void* break_token;
23   std::unique_ptr<Vector<NGPhysicalOutOfFlowPositionedNode>>
24       oof_positioned_descendants_;
25   void* pointer;
26   wtf_size_t size;
27 };
28 
29 static_assert(sizeof(NGPhysicalContainerFragment) ==
30                   sizeof(SameSizeAsNGPhysicalContainerFragment),
31               "NGPhysicalContainerFragment should stay small");
32 
33 }  // namespace
34 
NGPhysicalContainerFragment(NGContainerFragmentBuilder * builder,WritingMode block_or_line_writing_mode,NGLink * buffer,NGFragmentType type,unsigned sub_type)35 NGPhysicalContainerFragment::NGPhysicalContainerFragment(
36     NGContainerFragmentBuilder* builder,
37     WritingMode block_or_line_writing_mode,
38     NGLink* buffer,
39     NGFragmentType type,
40     unsigned sub_type)
41     : NGPhysicalFragment(builder, type, sub_type),
42       break_token_(std::move(builder->break_token_)),
43       oof_positioned_descendants_(
44           builder->oof_positioned_descendants_.IsEmpty()
45               ? nullptr
46               : new Vector<NGPhysicalOutOfFlowPositionedNode>()),
47       buffer_(buffer),
48       num_children_(builder->children_.size()) {
49   has_floating_descendants_for_paint_ =
50       builder->has_floating_descendants_for_paint_;
51   has_adjoining_object_descendants_ =
52       builder->has_adjoining_object_descendants_;
53   has_orthogonal_flow_roots_ = builder->has_orthogonal_flow_roots_;
54   may_have_descendant_above_block_start_ =
55       builder->may_have_descendant_above_block_start_;
56   depends_on_percentage_block_size_ = DependsOnPercentageBlockSize(*builder);
57 
58   PhysicalSize size = Size();
59   if (oof_positioned_descendants_) {
60     oof_positioned_descendants_->ReserveCapacity(
61         builder->oof_positioned_descendants_.size());
62     for (const auto& descendant : builder->oof_positioned_descendants_) {
63       oof_positioned_descendants_->emplace_back(
64           descendant.node,
65           descendant.static_position.ConvertToPhysical(
66               builder->Style().GetWritingMode(), builder->Direction(), size),
67           descendant.inline_container);
68     }
69   }
70 
71   // Because flexible arrays need to be the last member in a class, we need to
72   // have the buffer passed as a constructor argument and have the actual
73   // storage be part of the subclass.
74   wtf_size_t i = 0;
75   for (auto& child : builder->children_) {
76     buffer[i].fragment = child.fragment.get();
77     buffer[i].fragment->AddRef();
78     buffer[i].offset = child.offset.ConvertToPhysical(
79         block_or_line_writing_mode, builder->Direction(), size,
80         child.fragment->Size());
81     ++i;
82   }
83 }
84 
85 NGPhysicalContainerFragment::~NGPhysicalContainerFragment() = default;
86 
87 // additional_offset must be offset from the containing_block.
AddOutlineRectsForNormalChildren(Vector<PhysicalRect> * outline_rects,const PhysicalOffset & additional_offset,NGOutlineType outline_type,const LayoutBoxModelObject * containing_block) const88 void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren(
89     Vector<PhysicalRect>* outline_rects,
90     const PhysicalOffset& additional_offset,
91     NGOutlineType outline_type,
92     const LayoutBoxModelObject* containing_block) const {
93   if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(this)) {
94     if (const NGFragmentItems* items = box->Items()) {
95       for (NGInlineCursor cursor(*items); cursor; cursor.MoveToNext()) {
96         DCHECK(cursor.Current().Item());
97         const NGFragmentItem& item = *cursor.Current().Item();
98         if (item.Type() == NGFragmentItem::kLine) {
99           AddOutlineRectsForDescendant(
100               {item.LineBoxFragment(), item.OffsetInContainerBlock()},
101               outline_rects, additional_offset, outline_type, containing_block);
102           continue;
103         }
104         if (item.Type() == NGFragmentItem::kBox) {
105           if (const NGPhysicalBoxFragment* child_box = item.BoxFragment()) {
106             DCHECK(!child_box->IsOutOfFlowPositioned());
107             AddOutlineRectsForDescendant(
108                 {child_box, item.OffsetInContainerBlock()}, outline_rects,
109                 additional_offset, outline_type, containing_block);
110           }
111           continue;
112         }
113         DCHECK(item.IsText());
114       }
115       // Don't add |Children()|. If |this| has |NGFragmentItems|, children are
116       // either line box, which we already handled in items, or OOF, which we
117       // should ignore.
118       DCHECK(std::all_of(PostLayoutChildren().begin(),
119                          PostLayoutChildren().end(), [](const NGLink& child) {
120                            return child->IsLineBox() ||
121                                   child->IsOutOfFlowPositioned();
122                          }));
123       return;
124     }
125   }
126 
127   for (const auto& child : PostLayoutChildren()) {
128     // Outlines of out-of-flow positioned descendants are handled in
129     // NGPhysicalBoxFragment::AddSelfOutlineRects().
130     if (child->IsOutOfFlowPositioned())
131       continue;
132 
133     // Outline of an element continuation or anonymous block continuation is
134     // added when we iterate the continuation chain.
135     // See NGPhysicalBoxFragment::AddSelfOutlineRects().
136     if (!child->IsLineBox()) {
137       const LayoutObject* child_layout_object = child->GetLayoutObject();
138       if (auto* child_layout_block_flow =
139               DynamicTo<LayoutBlockFlow>(child_layout_object)) {
140         if (child_layout_object->IsElementContinuation() ||
141             child_layout_block_flow->IsAnonymousBlockContinuation())
142           continue;
143       }
144     }
145     AddOutlineRectsForDescendant(child, outline_rects, additional_offset,
146                                  outline_type, containing_block);
147   }
148 }
149 
AddScrollableOverflowForInlineChild(const NGPhysicalBoxFragment & container,const ComputedStyle & container_style,const NGFragmentItem & line,bool has_hanging,const NGInlineCursor & cursor,PhysicalRect * overflow) const150 void NGPhysicalContainerFragment::AddScrollableOverflowForInlineChild(
151     const NGPhysicalBoxFragment& container,
152     const ComputedStyle& container_style,
153     const NGFragmentItem& line,
154     bool has_hanging,
155     const NGInlineCursor& cursor,
156     PhysicalRect* overflow) const {
157   DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled());
158   DCHECK(IsLineBox() || IsInlineBox());
159   DCHECK(cursor.Current().Item() &&
160          (cursor.Current().Item()->BoxFragment() == this ||
161           cursor.Current().Item()->LineBoxFragment() == this));
162   const WritingMode container_writing_mode = container_style.GetWritingMode();
163   const TextDirection container_direction = container_style.Direction();
164   for (NGInlineCursor descendants = cursor.CursorForDescendants();
165        descendants;) {
166     const NGFragmentItem* item = descendants.CurrentItem();
167     DCHECK(item);
168     if (item->IsText()) {
169       PhysicalRect child_scroll_overflow = item->RectInContainerBlock();
170       if (UNLIKELY(has_hanging)) {
171         AdjustScrollableOverflowForHanging(line.RectInContainerBlock(),
172                                            container_writing_mode,
173                                            &child_scroll_overflow);
174       }
175       overflow->Unite(child_scroll_overflow);
176       descendants.MoveToNextSkippingChildren();
177       continue;
178     }
179 
180     if (const NGPhysicalBoxFragment* child_box = item->BoxFragment()) {
181       PhysicalRect child_scroll_overflow = item->RectInContainerBlock();
182       if (child_box->IsInlineBox()) {
183         child_box->AddScrollableOverflowForInlineChild(
184             container, container_style, line, has_hanging, descendants,
185             &child_scroll_overflow);
186         child_box->AdjustScrollableOverflowForPropagation(
187             container, &child_scroll_overflow);
188       } else {
189         child_scroll_overflow =
190             child_box->ScrollableOverflowForPropagation(container);
191         child_scroll_overflow.offset += item->OffsetInContainerBlock();
192       }
193       child_scroll_overflow.offset +=
194           ComputeRelativeOffset(child_box->Style(), container_writing_mode,
195                                 container_direction, container.Size());
196       overflow->Unite(child_scroll_overflow);
197       descendants.MoveToNextSkippingChildren();
198       continue;
199     }
200 
201     // Add all children of a culled inline box; i.e., an inline box without
202     // margin/border/padding etc.
203     DCHECK_EQ(item->Type(), NGFragmentItem::kBox);
204     descendants.MoveToNext();
205   }
206 }
207 
208 // Chop the hanging part from scrollable overflow. Children overflow in inline
209 // direction should hang, which should not cause scroll.
210 // TODO(kojii): Should move to text fragment to make this more accurate.
AdjustScrollableOverflowForHanging(const PhysicalRect & rect,const WritingMode container_writing_mode,PhysicalRect * overflow)211 void NGPhysicalContainerFragment::AdjustScrollableOverflowForHanging(
212     const PhysicalRect& rect,
213     const WritingMode container_writing_mode,
214     PhysicalRect* overflow) {
215   if (IsHorizontalWritingMode(container_writing_mode)) {
216     if (overflow->offset.left < rect.offset.left)
217       overflow->offset.left = rect.offset.left;
218     if (overflow->Right() > rect.Right())
219       overflow->ShiftRightEdgeTo(rect.Right());
220   } else {
221     if (overflow->offset.top < rect.offset.top)
222       overflow->offset.top = rect.offset.top;
223     if (overflow->Bottom() > rect.Bottom())
224       overflow->ShiftBottomEdgeTo(rect.Bottom());
225   }
226 }
227 
228 // additional_offset must be offset from the containing_block because
229 // LocalToAncestorRect returns rects wrt containing_block.
AddOutlineRectsForDescendant(const NGLink & descendant,Vector<PhysicalRect> * outline_rects,const PhysicalOffset & additional_offset,NGOutlineType outline_type,const LayoutBoxModelObject * containing_block) const230 void NGPhysicalContainerFragment::AddOutlineRectsForDescendant(
231     const NGLink& descendant,
232     Vector<PhysicalRect>* outline_rects,
233     const PhysicalOffset& additional_offset,
234     NGOutlineType outline_type,
235     const LayoutBoxModelObject* containing_block) const {
236   if (descendant->IsText() || descendant->IsListMarker())
237     return;
238 
239   if (const auto* descendant_box =
240           DynamicTo<NGPhysicalBoxFragment>(descendant.get())) {
241     const LayoutObject* descendant_layout_object =
242         descendant_box->GetLayoutObject();
243 
244     // TODO(layoutng): Explain this check. I assume we need it because layers
245     // may have transforms and so we have to go through LocalToAncestorRects?
246     if (descendant_box->HasLayer()) {
247       DCHECK(descendant_layout_object);
248       Vector<PhysicalRect> layer_outline_rects;
249       descendant_box->AddSelfOutlineRects(PhysicalOffset(), outline_type,
250                                           &layer_outline_rects);
251 
252       // Don't pass additional_offset because LocalToAncestorRects will itself
253       // apply it.
254       descendant_layout_object->LocalToAncestorRects(
255           layer_outline_rects, containing_block, PhysicalOffset(),
256           PhysicalOffset());
257       outline_rects->AppendVector(layer_outline_rects);
258       return;
259     }
260 
261     if (!descendant_box->IsInlineBox()) {
262       descendant_box->AddSelfOutlineRects(
263           additional_offset + descendant.Offset(), outline_type, outline_rects);
264       return;
265     }
266 
267     DCHECK(descendant_layout_object);
268     DCHECK(descendant_layout_object->IsLayoutInline());
269     const LayoutInline* descendant_layout_inline =
270         ToLayoutInline(descendant_layout_object);
271     // As an optimization, an ancestor has added rects for its line boxes
272     // covering descendants' line boxes, so descendants don't need to add line
273     // boxes again. For example, if the parent is a LayoutBlock, it adds rects
274     // for its line box which cover the line boxes of this LayoutInline. So
275     // the LayoutInline needs to add rects for children and continuations
276     // only.
277     if (NGOutlineUtils::ShouldPaintOutline(*descendant_box)) {
278       descendant_layout_inline->AddOutlineRectsForChildrenAndContinuations(
279           *outline_rects, additional_offset, outline_type);
280     }
281     return;
282   }
283 
284   if (const auto* descendant_line_box =
285           DynamicTo<NGPhysicalLineBoxFragment>(descendant.get())) {
286     descendant_line_box->AddOutlineRectsForNormalChildren(
287         outline_rects, additional_offset + descendant.Offset(), outline_type,
288         containing_block);
289 
290     if (!descendant_line_box->Size().IsEmpty()) {
291       outline_rects->emplace_back(additional_offset,
292                                   descendant_line_box->Size().ToLayoutSize());
293     } else if (descendant_line_box->Children().empty()) {
294       // Special-case for when the first continuation does not generate
295       // fragments. NGInlineLayoutAlgorithm suppresses box fragments when the
296       // line is "empty". When there is a continuation from the LayoutInline,
297       // the suppression makes such continuation not reachable. Check the
298       // continuation from LayoutInline in such case.
299       DCHECK(GetLayoutObject());
300       if (LayoutInline* first_layout_inline =
301               ToLayoutInlineOrNull(GetLayoutObject()->SlowFirstChild())) {
302         if (!first_layout_inline->IsElementContinuation()) {
303           first_layout_inline->AddOutlineRectsForChildrenAndContinuations(
304               *outline_rects, additional_offset, outline_type);
305         }
306       }
307     }
308   }
309 }
310 
DependsOnPercentageBlockSize(const NGContainerFragmentBuilder & builder)311 bool NGPhysicalContainerFragment::DependsOnPercentageBlockSize(
312     const NGContainerFragmentBuilder& builder) {
313   NGLayoutInputNode node = builder.node_;
314 
315   if (!node || node.IsInline())
316     return builder.has_descendant_that_depends_on_percentage_block_size_;
317 
318   // NOTE: If an element is OOF positioned, and has top/bottom constraints
319   // which are percentage based, this function will return false.
320   //
321   // This is fine as the top/bottom constraints are computed *before* layout,
322   // and the result is set as a fixed-block-size constraint. (And the caching
323   // logic will never check the result of this function).
324   //
325   // The result of this function still may be used for an OOF positioned
326   // element if it has a percentage block-size however, but this will return
327   // the correct result from below.
328 
329   // There are two conditions where we need to know about an (arbitrary)
330   // descendant which depends on a %-block-size.
331   //  - In quirks mode, the arbitrary descendant may depend the percentage
332   //    resolution block-size given (to this node), and need to relayout if
333   //    this size changes.
334   //  - A flex-item may have its "definiteness" change, (e.g. if itself is a
335   //    flex item which is being stretched). This definiteness change will
336   //    affect any %-block-size children.
337   //
338   // NOTE(ikilpatrick): For the flex-item case this is potentially too general.
339   // We only need to know about if this flex-item has a %-block-size child if
340   // the "definiteness" changes, not if the percentage resolution size changes.
341   if ((builder.has_descendant_that_depends_on_percentage_block_size_ ||
342        builder.is_legacy_layout_root_) &&
343       (node.UseParentPercentageResolutionBlockSizeForChildren() ||
344        node.IsFlexItem()))
345     return true;
346 
347   const ComputedStyle& style = builder.Style();
348   if (style.LogicalHeight().IsPercentOrCalc() ||
349       style.LogicalMinHeight().IsPercentOrCalc() ||
350       style.LogicalMaxHeight().IsPercentOrCalc())
351     return true;
352 
353   return false;
354 }
355 
356 }  // namespace blink
357