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