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/geometry/writing_mode_converter.h"
8 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
9 #include "third_party/blink/renderer/core/layout/layout_inline.h"
10 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
11 #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
12 #include "third_party/blink/renderer/core/layout/ng/inline/ng_ruby_utils.h"
13 #include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h"
14 #include "third_party/blink/renderer/core/layout/ng/ng_layout_overflow_calculator.h"
15 #include "third_party/blink/renderer/core/layout/ng/ng_outline_utils.h"
16 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
17 #include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h"
18 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
19 #include "third_party/blink/renderer/platform/wtf/size_assertions.h"
20 
21 namespace blink {
22 
23 namespace {
24 
25 struct SameSizeAsNGPhysicalContainerFragment : NGPhysicalFragment {
26   wtf_size_t size;
27   void* break_token;
28   std::unique_ptr<Vector<NGPhysicalOutOfFlowPositionedNode>>
29       oof_positioned_descendants_;
30   void* pointer;
31 };
32 
33 ASSERT_SIZE(NGPhysicalContainerFragment, SameSizeAsNGPhysicalContainerFragment);
34 
35 }  // namespace
36 
NGPhysicalContainerFragment(NGContainerFragmentBuilder * builder,WritingMode block_or_line_writing_mode,NGLink * buffer,NGFragmentType type,unsigned sub_type)37 NGPhysicalContainerFragment::NGPhysicalContainerFragment(
38     NGContainerFragmentBuilder* builder,
39     WritingMode block_or_line_writing_mode,
40     NGLink* buffer,
41     NGFragmentType type,
42     unsigned sub_type)
43     : NGPhysicalFragment(builder, type, sub_type),
44       num_children_(builder->children_.size()),
45       break_token_(std::move(builder->break_token_)),
46       oof_positioned_descendants_(
47           builder->oof_positioned_descendants_.IsEmpty()
48               ? nullptr
49               : new Vector<NGPhysicalOutOfFlowPositionedNode>()),
50       buffer_(buffer) {
51   has_floating_descendants_for_paint_ =
52       builder->has_floating_descendants_for_paint_;
53   has_adjoining_object_descendants_ =
54       builder->has_adjoining_object_descendants_;
55   depends_on_percentage_block_size_ = DependsOnPercentageBlockSize(*builder);
56 
57   PhysicalSize size = Size();
58   if (oof_positioned_descendants_) {
59     oof_positioned_descendants_->ReserveCapacity(
60         builder->oof_positioned_descendants_.size());
61     const WritingModeConverter converter(
62         {builder->Style().GetWritingMode(), builder->Direction()}, size);
63     for (const auto& descendant : builder->oof_positioned_descendants_) {
64       oof_positioned_descendants_->emplace_back(
65           descendant.node,
66           descendant.static_position.ConvertToPhysical(converter),
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   const WritingModeConverter converter(
75       {block_or_line_writing_mode, builder->Direction()}, size);
76   wtf_size_t i = 0;
77   for (auto& child : builder->children_) {
78     buffer[i].offset =
79         converter.ToPhysical(child.offset, child.fragment->Size());
80     // Call the move constructor to move without |AddRef|. Fragments in
81     // |builder| are not used after |this| was constructed.
82     static_assert(
83         sizeof(buffer[0].fragment) ==
84             sizeof(scoped_refptr<const NGPhysicalFragment>),
85         "scoped_refptr must be the size of a pointer for this to work");
86     new (&buffer[i].fragment)
87         scoped_refptr<const NGPhysicalFragment>(std::move(child.fragment));
88     DCHECK(!child.fragment);  // Ensure it was moved.
89     ++i;
90   }
91 }
92 
NGPhysicalContainerFragment(const NGPhysicalContainerFragment & other,bool recalculate_layout_overflow,NGLink * buffer)93 NGPhysicalContainerFragment::NGPhysicalContainerFragment(
94     const NGPhysicalContainerFragment& other,
95     bool recalculate_layout_overflow,
96     NGLink* buffer)
97     : NGPhysicalFragment(other),
98       num_children_(other.num_children_),
99       break_token_(other.break_token_),
100       oof_positioned_descendants_(
101           other.oof_positioned_descendants_
102               ? new Vector<NGPhysicalOutOfFlowPositionedNode>(
103                     *other.oof_positioned_descendants_)
104               : nullptr),
105       buffer_(buffer) {
106   // To ensure the fragment tree is consistent, use the post-layout fragment.
107   for (wtf_size_t i = 0; i < num_children_; ++i) {
108     buffer[i].offset = other.buffer_[i].offset;
109     scoped_refptr<const NGPhysicalFragment> post_layout =
110         other.buffer_[i]->PostLayout();
111     // While making the fragment tree consistent, we need to also clone any
112     // fragmentainer fragments, as they don't nessecerily have their result
113     // stored on the layout-object tree.
114     if (post_layout->IsFragmentainerBox()) {
115       const auto& box_fragment = To<NGPhysicalBoxFragment>(*post_layout);
116 
117       base::Optional<PhysicalRect> layout_overflow;
118       if (recalculate_layout_overflow) {
119         layout_overflow =
120             NGLayoutOverflowCalculator::RecalculateLayoutOverflowForFragment(
121                 box_fragment);
122       }
123 
124       post_layout = NGPhysicalBoxFragment::CloneWithPostLayoutFragments(
125           box_fragment, layout_overflow);
126     }
127     new (&buffer[i].fragment)
128         scoped_refptr<const NGPhysicalFragment>(std::move(post_layout));
129   }
130 }
131 
132 NGPhysicalContainerFragment::~NGPhysicalContainerFragment() = default;
133 
134 // 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) const135 void NGPhysicalContainerFragment::AddOutlineRectsForNormalChildren(
136     Vector<PhysicalRect>* outline_rects,
137     const PhysicalOffset& additional_offset,
138     NGOutlineType outline_type,
139     const LayoutBoxModelObject* containing_block) const {
140   if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(this)) {
141     DCHECK_EQ(box->PostLayout(), box);
142     if (const NGFragmentItems* items = box->Items()) {
143       for (NGInlineCursor cursor(*box, *items); cursor; cursor.MoveToNext()) {
144         DCHECK(cursor.Current().Item());
145         const NGFragmentItem& item = *cursor.Current().Item();
146         if (UNLIKELY(item.IsLayoutObjectDestroyedOrMoved()))
147           continue;
148         if (item.Type() == NGFragmentItem::kLine) {
149           AddOutlineRectsForDescendant(
150               {item.LineBoxFragment(), item.OffsetInContainerBlock()},
151               outline_rects, additional_offset, outline_type, containing_block);
152           continue;
153         }
154         if (item.IsText()) {
155           if (outline_type == NGOutlineType::kDontIncludeBlockVisualOverflow)
156             continue;
157           outline_rects->push_back(
158               PhysicalRect(additional_offset + item.OffsetInContainerBlock(),
159                            item.Size().ToLayoutSize()));
160           continue;
161         }
162         if (item.Type() == NGFragmentItem::kBox) {
163           if (const NGPhysicalBoxFragment* child_box =
164                   item.PostLayoutBoxFragment()) {
165             DCHECK(!child_box->IsOutOfFlowPositioned());
166             AddOutlineRectsForDescendant(
167                 {child_box, item.OffsetInContainerBlock()}, outline_rects,
168                 additional_offset, outline_type, containing_block);
169           }
170           continue;
171         }
172       }
173       // Don't add |Children()|. If |this| has |NGFragmentItems|, children are
174       // either line box, which we already handled in items, or OOF, which we
175       // should ignore.
176       DCHECK(std::all_of(PostLayoutChildren().begin(),
177                          PostLayoutChildren().end(), [](const NGLink& child) {
178                            return child->IsLineBox() ||
179                                   child->IsOutOfFlowPositioned();
180                          }));
181       return;
182     }
183   }
184 
185   for (const auto& child : PostLayoutChildren()) {
186     // Outlines of out-of-flow positioned descendants are handled in
187     // NGPhysicalBoxFragment::AddSelfOutlineRects().
188     if (child->IsOutOfFlowPositioned())
189       continue;
190 
191     // Outline of an element continuation or anonymous block continuation is
192     // added when we iterate the continuation chain.
193     // See NGPhysicalBoxFragment::AddSelfOutlineRects().
194     if (!child->IsLineBox()) {
195       const LayoutObject* child_layout_object = child->GetLayoutObject();
196       if (auto* child_layout_block_flow =
197               DynamicTo<LayoutBlockFlow>(child_layout_object)) {
198         if (child_layout_object->IsElementContinuation() ||
199             child_layout_block_flow->IsAnonymousBlockContinuation())
200           continue;
201       }
202     }
203     AddOutlineRectsForDescendant(child, outline_rects, additional_offset,
204                                  outline_type, containing_block);
205   }
206 }
207 
AddScrollableOverflowForInlineChild(const NGPhysicalBoxFragment & container,const ComputedStyle & container_style,const NGFragmentItem & line,bool has_hanging,const NGInlineCursor & cursor,TextHeightType height_type,PhysicalRect * overflow) const208 void NGPhysicalContainerFragment::AddScrollableOverflowForInlineChild(
209     const NGPhysicalBoxFragment& container,
210     const ComputedStyle& container_style,
211     const NGFragmentItem& line,
212     bool has_hanging,
213     const NGInlineCursor& cursor,
214     TextHeightType height_type,
215     PhysicalRect* overflow) const {
216   DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled());
217   DCHECK(IsLineBox() || IsInlineBox());
218   DCHECK(cursor.Current().Item() &&
219          (cursor.Current().Item()->BoxFragment() == this ||
220           cursor.Current().Item()->LineBoxFragment() == this));
221   const WritingMode container_writing_mode = container_style.GetWritingMode();
222   for (NGInlineCursor descendants = cursor.CursorForDescendants();
223        descendants;) {
224     const NGFragmentItem* item = descendants.CurrentItem();
225     DCHECK(item);
226     if (UNLIKELY(item->IsLayoutObjectDestroyedOrMoved())) {
227       NOTREACHED();
228       descendants.MoveToNextSkippingChildren();
229       continue;
230     }
231     if (item->IsText()) {
232       PhysicalRect child_scroll_overflow = item->RectInContainerBlock();
233       if (height_type == TextHeightType::kEmHeight) {
234         child_scroll_overflow = AdjustTextRectForEmHeight(
235             child_scroll_overflow, item->Style(), item->TextShapeResult(),
236             container_writing_mode);
237       }
238       if (UNLIKELY(has_hanging)) {
239         AdjustScrollableOverflowForHanging(line.RectInContainerBlock(),
240                                            container_writing_mode,
241                                            &child_scroll_overflow);
242       }
243       overflow->Unite(child_scroll_overflow);
244       descendants.MoveToNextSkippingChildren();
245       continue;
246     }
247 
248     if (const NGPhysicalBoxFragment* child_box =
249             item->PostLayoutBoxFragment()) {
250       PhysicalRect child_scroll_overflow;
251       if (height_type == TextHeightType::kNormalHeight ||
252           (child_box->BoxType() != kInlineBox && !IsRubyBox()))
253         child_scroll_overflow = item->RectInContainerBlock();
254       if (child_box->IsInlineBox()) {
255         child_box->AddScrollableOverflowForInlineChild(
256             container, container_style, line, has_hanging, descendants,
257             height_type, &child_scroll_overflow);
258         child_box->AdjustScrollableOverflowForPropagation(
259             container, height_type, &child_scroll_overflow);
260         if (UNLIKELY(has_hanging)) {
261           AdjustScrollableOverflowForHanging(line.RectInContainerBlock(),
262                                              container_writing_mode,
263                                              &child_scroll_overflow);
264         }
265       } else {
266         child_scroll_overflow =
267             child_box->ScrollableOverflowForPropagation(container, height_type);
268         child_scroll_overflow.offset += item->OffsetInContainerBlock();
269       }
270       overflow->Unite(child_scroll_overflow);
271       descendants.MoveToNextSkippingChildren();
272       continue;
273     }
274 
275     // Add all children of a culled inline box; i.e., an inline box without
276     // margin/border/padding etc.
277     DCHECK_EQ(item->Type(), NGFragmentItem::kBox);
278     descendants.MoveToNext();
279   }
280 }
281 
282 // Chop the hanging part from scrollable overflow. Children overflow in inline
283 // direction should hang, which should not cause scroll.
284 // TODO(kojii): Should move to text fragment to make this more accurate.
AdjustScrollableOverflowForHanging(const PhysicalRect & rect,const WritingMode container_writing_mode,PhysicalRect * overflow)285 void NGPhysicalContainerFragment::AdjustScrollableOverflowForHanging(
286     const PhysicalRect& rect,
287     const WritingMode container_writing_mode,
288     PhysicalRect* overflow) {
289   if (IsHorizontalWritingMode(container_writing_mode)) {
290     if (overflow->offset.left < rect.offset.left)
291       overflow->offset.left = rect.offset.left;
292     if (overflow->Right() > rect.Right())
293       overflow->ShiftRightEdgeTo(rect.Right());
294   } else {
295     if (overflow->offset.top < rect.offset.top)
296       overflow->offset.top = rect.offset.top;
297     if (overflow->Bottom() > rect.Bottom())
298       overflow->ShiftBottomEdgeTo(rect.Bottom());
299   }
300 }
301 
302 // additional_offset must be offset from the containing_block because
303 // 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) const304 void NGPhysicalContainerFragment::AddOutlineRectsForDescendant(
305     const NGLink& descendant,
306     Vector<PhysicalRect>* outline_rects,
307     const PhysicalOffset& additional_offset,
308     NGOutlineType outline_type,
309     const LayoutBoxModelObject* containing_block) const {
310   DCHECK(!descendant->IsLayoutObjectDestroyedOrMoved());
311   if (descendant->IsText() || descendant->IsListMarker())
312     return;
313 
314   if (const auto* descendant_box =
315           DynamicTo<NGPhysicalBoxFragment>(descendant.get())) {
316     DCHECK_EQ(descendant_box->PostLayout(), descendant_box);
317     const LayoutObject* descendant_layout_object =
318         descendant_box->GetLayoutObject();
319 
320     // TODO(layoutng): Explain this check. I assume we need it because layers
321     // may have transforms and so we have to go through LocalToAncestorRects?
322     if (descendant_box->HasLayer()) {
323       DCHECK(descendant_layout_object);
324       Vector<PhysicalRect> layer_outline_rects;
325       descendant_box->AddSelfOutlineRects(PhysicalOffset(), outline_type,
326                                           &layer_outline_rects);
327 
328       // Don't pass additional_offset because LocalToAncestorRects will itself
329       // apply it.
330       descendant_layout_object->LocalToAncestorRects(
331           layer_outline_rects, containing_block, PhysicalOffset(),
332           PhysicalOffset());
333       outline_rects->AppendVector(layer_outline_rects);
334       return;
335     }
336 
337     if (!descendant_box->IsInlineBox()) {
338       descendant_box->AddSelfOutlineRects(
339           additional_offset + descendant.Offset(), outline_type, outline_rects);
340       return;
341     }
342 
343     DCHECK(descendant_layout_object);
344     const auto* descendant_layout_inline =
345         To<LayoutInline>(descendant_layout_object);
346     // As an optimization, an ancestor has added rects for its line boxes
347     // covering descendants' line boxes, so descendants don't need to add line
348     // boxes again. For example, if the parent is a LayoutBlock, it adds rects
349     // for its line box which cover the line boxes of this LayoutInline. So
350     // the LayoutInline needs to add rects for children and continuations
351     // only.
352     if (NGOutlineUtils::ShouldPaintOutline(*descendant_box)) {
353       // We don't pass additional_offset here because the function requires
354       // additional_offset to be the offset from the containing block.
355       descendant_layout_inline->AddOutlineRectsForChildrenAndContinuations(
356           *outline_rects, PhysicalOffset(), outline_type);
357     }
358     return;
359   }
360 
361   if (const auto* descendant_line_box =
362           DynamicTo<NGPhysicalLineBoxFragment>(descendant.get())) {
363     descendant_line_box->AddOutlineRectsForNormalChildren(
364         outline_rects, additional_offset + descendant.Offset(), outline_type,
365         containing_block);
366 
367     if (!descendant_line_box->Size().IsEmpty()) {
368       outline_rects->emplace_back(additional_offset + descendant.Offset(),
369                                   descendant_line_box->Size().ToLayoutSize());
370     } else if (descendant_line_box->Children().empty()) {
371       // Special-case for when the first continuation does not generate
372       // fragments. NGInlineLayoutAlgorithm suppresses box fragments when the
373       // line is "empty". When there is a continuation from the LayoutInline,
374       // the suppression makes such continuation not reachable. Check the
375       // continuation from LayoutInline in such case.
376       DCHECK(GetLayoutObject());
377       if (auto* first_layout_inline =
378               DynamicTo<LayoutInline>(GetLayoutObject()->SlowFirstChild())) {
379         if (!first_layout_inline->IsElementContinuation()) {
380           first_layout_inline->AddOutlineRectsForChildrenAndContinuations(
381               *outline_rects, additional_offset, outline_type);
382         }
383       }
384     }
385   }
386 }
387 
DependsOnPercentageBlockSize(const NGContainerFragmentBuilder & builder)388 bool NGPhysicalContainerFragment::DependsOnPercentageBlockSize(
389     const NGContainerFragmentBuilder& builder) {
390   NGLayoutInputNode node = builder.node_;
391 
392   if (!node || node.IsInline())
393     return builder.has_descendant_that_depends_on_percentage_block_size_;
394 
395   // For the below if-stmt we only want to consider legacy *containers* as
396   // potentially having %-dependent children - i.e. an image doesn't have any
397   // children.
398   bool is_legacy_container_with_percent_height_descendants =
399       builder.is_legacy_layout_root_ && !node.IsReplaced() &&
400       node.GetLayoutBox()->MaybeHasPercentHeightDescendant();
401 
402   // NOTE: If an element is OOF positioned, and has top/bottom constraints
403   // which are percentage based, this function will return false.
404   //
405   // This is fine as the top/bottom constraints are computed *before* layout,
406   // and the result is set as a fixed-block-size constraint. (And the caching
407   // logic will never check the result of this function).
408   //
409   // The result of this function still may be used for an OOF positioned
410   // element if it has a percentage block-size however, but this will return
411   // the correct result from below.
412 
413   // There are two conditions where we need to know about an (arbitrary)
414   // descendant which depends on a %-block-size.
415   //  - In quirks mode, the arbitrary descendant may depend the percentage
416   //    resolution block-size given (to this node), and need to relayout if
417   //    this size changes.
418   //  - A flex-item may have its "definiteness" change, (e.g. if itself is a
419   //    flex item which is being stretched). This definiteness change will
420   //    affect any %-block-size children.
421   //
422   // NOTE(ikilpatrick): For the flex-item case this is potentially too general.
423   // We only need to know about if this flex-item has a %-block-size child if
424   // the "definiteness" changes, not if the percentage resolution size changes.
425   if ((builder.has_descendant_that_depends_on_percentage_block_size_ ||
426        is_legacy_container_with_percent_height_descendants) &&
427       (node.UseParentPercentageResolutionBlockSizeForChildren() ||
428        node.IsFlexItem()))
429     return true;
430 
431   const ComputedStyle& style = builder.Style();
432   if (style.LogicalHeight().IsPercentOrCalc() ||
433       style.LogicalMinHeight().IsPercentOrCalc() ||
434       style.LogicalMaxHeight().IsPercentOrCalc())
435     return true;
436 
437   return false;
438 }
439 
440 }  // namespace blink
441