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