1 // Copyright 2016 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_box_fragment_builder.h"
6
7 #include "third_party/blink/renderer/core/layout/layout_object.h"
8 #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h"
9 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
10 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h"
11 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
12 #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
13 #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
14 #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
15 #include "third_party/blink/renderer/core/layout/ng/ng_break_token.h"
16 #include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
17 #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
18 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
19 #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
20 #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
21
22 namespace blink {
23
24 namespace {
25
26 // std::pair.first points to the start linebox fragment.
27 // std::pair.second points to the end linebox fragment.
28 using LineBoxPair = std::pair<const NGPhysicalLineBoxFragment*,
29 const NGPhysicalLineBoxFragment*>;
30
GatherInlineContainerFragmentsFromLinebox(NGBoxFragmentBuilder::InlineContainingBlockMap * inline_containing_block_map,HashMap<const LayoutObject *,LineBoxPair> * containing_linebox_map,const NGPhysicalLineBoxFragment & linebox,const PhysicalOffset linebox_offset)31 void GatherInlineContainerFragmentsFromLinebox(
32 NGBoxFragmentBuilder::InlineContainingBlockMap* inline_containing_block_map,
33 HashMap<const LayoutObject*, LineBoxPair>* containing_linebox_map,
34 const NGPhysicalLineBoxFragment& linebox,
35 const PhysicalOffset linebox_offset) {
36 for (const auto& descendant :
37 NGInlineFragmentTraversal::DescendantsOf(linebox)) {
38 if (!descendant.fragment->IsBox())
39 continue;
40 const LayoutObject* key = descendant.fragment->GetLayoutObject();
41 // Key for inline is the continuation root if it exists.
42 if (key->IsLayoutInline() && key->GetNode())
43 key = key->ContinuationRoot();
44 auto it = inline_containing_block_map->find(key);
45 if (it == inline_containing_block_map->end()) {
46 // Default case, not one of the blocks we are looking for.
47 continue;
48 }
49 base::Optional<NGBoxFragmentBuilder::InlineContainingBlockGeometry>&
50 containing_block_geometry = it->value;
51 LineBoxPair& containing_lineboxes =
52 containing_linebox_map->insert(key, LineBoxPair{nullptr, nullptr})
53 .stored_value->value;
54 DCHECK(containing_block_geometry.has_value() ||
55 !containing_lineboxes.first);
56
57 // |DescendantsOf| returns the offset from the given fragment. Since
58 // we give it the line box, need to add the |linebox_offset|.
59 PhysicalRect fragment_rect(
60 linebox_offset + descendant.offset_to_container_box,
61 descendant.fragment->Size());
62 if (containing_lineboxes.first == &linebox) {
63 containing_block_geometry->start_fragment_union_rect.Unite(fragment_rect);
64 } else if (!containing_lineboxes.first) {
65 containing_lineboxes.first = &linebox;
66 containing_block_geometry =
67 NGBoxFragmentBuilder::InlineContainingBlockGeometry{fragment_rect,
68 PhysicalRect()};
69 }
70 // Skip fragments within an empty line boxes for the end fragment.
71 if (containing_lineboxes.second == &linebox) {
72 containing_block_geometry->end_fragment_union_rect.Unite(fragment_rect);
73 } else if (!containing_lineboxes.second || !linebox.IsEmptyLineBox()) {
74 containing_lineboxes.second = &linebox;
75 containing_block_geometry->end_fragment_union_rect = fragment_rect;
76 }
77 }
78 }
79
GatherInlineContainerFragmentsFromItems(const Vector<std::unique_ptr<NGFragmentItem>> & items,const PhysicalOffset & box_offset,NGBoxFragmentBuilder::InlineContainingBlockMap * inline_containing_block_map,HashMap<const LayoutObject *,LineBoxPair> * containing_linebox_map)80 void GatherInlineContainerFragmentsFromItems(
81 const Vector<std::unique_ptr<NGFragmentItem>>& items,
82 const PhysicalOffset& box_offset,
83 NGBoxFragmentBuilder::InlineContainingBlockMap* inline_containing_block_map,
84 HashMap<const LayoutObject*, LineBoxPair>* containing_linebox_map) {
85 const NGPhysicalLineBoxFragment* linebox = nullptr;
86 for (const auto& item : items) {
87 // Track the current linebox.
88 if (const NGPhysicalLineBoxFragment* current_linebox =
89 item->LineBoxFragment()) {
90 linebox = current_linebox;
91 continue;
92 }
93
94 // We only care about inlines which have generated a box fragment.
95 const NGPhysicalBoxFragment* box = item->BoxFragment();
96 if (!box)
97 continue;
98
99 // The key for the inline is the continuation root if it exists.
100 const LayoutObject* key = box->GetLayoutObject();
101 if (key->IsLayoutInline() && key->GetNode())
102 key = key->ContinuationRoot();
103
104 // See if we need the containing block information for this inline.
105 auto it = inline_containing_block_map->find(key);
106 if (it == inline_containing_block_map->end())
107 continue;
108
109 base::Optional<NGBoxFragmentBuilder::InlineContainingBlockGeometry>&
110 containing_block_geometry = it->value;
111 LineBoxPair& containing_lineboxes =
112 containing_linebox_map->insert(key, LineBoxPair{nullptr, nullptr})
113 .stored_value->value;
114 DCHECK(containing_block_geometry.has_value() ||
115 !containing_lineboxes.first);
116
117 PhysicalRect fragment_rect = item->RectInContainerBlock();
118 fragment_rect.offset += box_offset;
119 if (containing_lineboxes.first == linebox) {
120 // Unite the start rect with the fragment's rect.
121 containing_block_geometry->start_fragment_union_rect.Unite(fragment_rect);
122 } else if (!containing_lineboxes.first) {
123 DCHECK(!containing_lineboxes.second);
124 // This is the first linebox we've encountered, initialize the containing
125 // block geometry.
126 containing_lineboxes.first = linebox;
127 containing_lineboxes.second = linebox;
128 containing_block_geometry =
129 NGBoxFragmentBuilder::InlineContainingBlockGeometry{fragment_rect,
130 fragment_rect};
131 }
132
133 if (containing_lineboxes.second == linebox) {
134 // Unite the end rect with the fragment's rect.
135 containing_block_geometry->end_fragment_union_rect.Unite(fragment_rect);
136 } else if (!linebox->IsEmptyLineBox()) {
137 // We've found a new "end" linebox, update the containing block geometry.
138 containing_lineboxes.second = linebox;
139 containing_block_geometry->end_fragment_union_rect = fragment_rect;
140 }
141 }
142 }
143
144 } // namespace
145
AddBreakBeforeChild(NGLayoutInputNode child,base::Optional<NGBreakAppeal> appeal,bool is_forced_break)146 void NGBoxFragmentBuilder::AddBreakBeforeChild(
147 NGLayoutInputNode child,
148 base::Optional<NGBreakAppeal> appeal,
149 bool is_forced_break) {
150 if (appeal)
151 break_appeal_ = *appeal;
152 if (is_forced_break) {
153 SetHasForcedBreak();
154 // A forced break is considered to always have perfect appeal; they should
155 // never be weighed against other potential breakpoints.
156 DCHECK(!appeal || *appeal == kBreakAppealPerfect);
157 }
158
159 DCHECK(has_block_fragmentation_);
160 SetDidBreak();
161 if (auto* child_inline_node = DynamicTo<NGInlineNode>(child)) {
162 if (inline_break_tokens_.IsEmpty()) {
163 // In some cases we may want to break before the first line, as a last
164 // resort. We need a break token for that as well, so that the machinery
165 // will understand that we should resume at the beginning of the inline
166 // formatting context, rather than concluding that we're done with the
167 // whole thing.
168 inline_break_tokens_.push_back(NGInlineBreakToken::Create(
169 *child_inline_node, /* style */ nullptr, /* item_index */ 0,
170 /* text_offset */ 0, NGInlineBreakToken::kDefault));
171 }
172 return;
173 }
174 auto token = NGBlockBreakToken::CreateBreakBefore(child, is_forced_break);
175 child_break_tokens_.push_back(token);
176 }
177
AddResult(const NGLayoutResult & child_layout_result,const LogicalOffset offset,const LayoutInline * inline_container)178 void NGBoxFragmentBuilder::AddResult(const NGLayoutResult& child_layout_result,
179 const LogicalOffset offset,
180 const LayoutInline* inline_container) {
181 const auto& fragment = child_layout_result.PhysicalFragment();
182 if (items_builder_) {
183 if (const NGPhysicalLineBoxFragment* line =
184 DynamicTo<NGPhysicalLineBoxFragment>(&fragment)) {
185 items_builder_->AddLine(*line, offset);
186 // TODO(kojii): We probably don't need to AddChild this line, but there
187 // maybe OOF objects. Investigate how to handle them.
188 }
189 }
190 AddChild(fragment, offset, inline_container);
191 if (fragment.IsBox())
192 PropagateBreak(child_layout_result);
193 }
194
AddBreakToken(scoped_refptr<const NGBreakToken> token)195 void NGBoxFragmentBuilder::AddBreakToken(
196 scoped_refptr<const NGBreakToken> token) {
197 DCHECK(token.get());
198 child_break_tokens_.push_back(std::move(token));
199 }
200
AddOutOfFlowLegacyCandidate(NGBlockNode node,const NGLogicalStaticPosition & static_position,const LayoutInline * inline_container)201 void NGBoxFragmentBuilder::AddOutOfFlowLegacyCandidate(
202 NGBlockNode node,
203 const NGLogicalStaticPosition& static_position,
204 const LayoutInline* inline_container) {
205 oof_positioned_candidates_.emplace_back(
206 node, static_position,
207 inline_container ? ToLayoutInline(inline_container->ContinuationRoot())
208 : nullptr);
209 }
210
BoxType() const211 NGPhysicalFragment::NGBoxType NGBoxFragmentBuilder::BoxType() const {
212 if (box_type_ != NGPhysicalFragment::NGBoxType::kNormalBox)
213 return box_type_;
214
215 // When implicit, compute from LayoutObject.
216 DCHECK(layout_object_);
217 if (layout_object_->IsFloating())
218 return NGPhysicalFragment::NGBoxType::kFloating;
219 if (layout_object_->IsOutOfFlowPositioned())
220 return NGPhysicalFragment::NGBoxType::kOutOfFlowPositioned;
221 if (layout_object_->IsRenderedLegend())
222 return NGPhysicalFragment::NGBoxType::kRenderedLegend;
223 if (layout_object_->IsInline()) {
224 // Check |IsAtomicInlineLevel()| after |IsInline()| because |LayoutReplaced|
225 // sets |IsAtomicInlineLevel()| even when it's block-level. crbug.com/567964
226 if (layout_object_->IsAtomicInlineLevel())
227 return NGPhysicalFragment::NGBoxType::kAtomicInline;
228 return NGPhysicalFragment::NGBoxType::kInlineBox;
229 }
230 DCHECK(node_) << "Must call SetBoxType if there is no node";
231 DCHECK_EQ(is_new_fc_, node_.CreatesNewFormattingContext())
232 << "Forgot to call builder.SetIsNewFormattingContext";
233 if (is_new_fc_)
234 return NGPhysicalFragment::NGBoxType::kBlockFlowRoot;
235 return NGPhysicalFragment::NGBoxType::kNormalBox;
236 }
237
JoinedBreakBetweenValue(EBreakBetween break_before) const238 EBreakBetween NGBoxFragmentBuilder::JoinedBreakBetweenValue(
239 EBreakBetween break_before) const {
240 return JoinFragmentainerBreakValues(previous_break_after_, break_before);
241 }
242
PropagateBreak(const NGLayoutResult & child_layout_result)243 void NGBoxFragmentBuilder::PropagateBreak(
244 const NGLayoutResult& child_layout_result) {
245 if (LIKELY(!has_block_fragmentation_))
246 return;
247 if (!did_break_) {
248 const auto* token = child_layout_result.PhysicalFragment().BreakToken();
249 did_break_ = token && !token->IsFinished();
250 }
251 if (child_layout_result.HasForcedBreak()) {
252 SetHasForcedBreak();
253 } else if (IsInitialColumnBalancingPass()) {
254 PropagateTallestUnbreakableBlockSize(
255 child_layout_result.TallestUnbreakableBlockSize());
256 } else {
257 PropagateSpaceShortage(child_layout_result.MinimalSpaceShortage());
258 }
259 }
260
ToBoxFragment(WritingMode block_or_line_writing_mode)261 scoped_refptr<const NGLayoutResult> NGBoxFragmentBuilder::ToBoxFragment(
262 WritingMode block_or_line_writing_mode) {
263 #if DCHECK_IS_ON()
264 if (ItemsBuilder()) {
265 for (const ChildWithOffset& child : Children()) {
266 DCHECK(child.fragment);
267 const NGPhysicalFragment& fragment = *child.fragment;
268 DCHECK(fragment.IsLineBox() ||
269 // TODO(kojii): How to place floats and OOF is TBD.
270 fragment.IsFloatingOrOutOfFlowPositioned());
271 }
272 }
273 #endif
274
275 if (UNLIKELY(node_ && has_block_fragmentation_)) {
276 if (!inline_break_tokens_.IsEmpty()) {
277 if (auto token = inline_break_tokens_.back()) {
278 if (!token->IsFinished())
279 child_break_tokens_.push_back(std::move(token));
280 }
281 }
282 if (did_break_) {
283 break_token_ = NGBlockBreakToken::Create(
284 node_, consumed_block_size_, sequence_number_, child_break_tokens_,
285 break_appeal_, has_seen_all_children_);
286 }
287 }
288
289 if (!has_floating_descendants_for_paint_ && items_builder_) {
290 has_floating_descendants_for_paint_ =
291 items_builder_->HasFloatingDescendantsForPaint();
292 }
293
294 scoped_refptr<const NGPhysicalBoxFragment> fragment =
295 NGPhysicalBoxFragment::Create(this, block_or_line_writing_mode);
296 fragment->CheckType();
297
298 return base::AdoptRef(
299 new NGLayoutResult(NGLayoutResult::NGBoxFragmentBuilderPassKey(),
300 std::move(fragment), this));
301 }
302
Abort(NGLayoutResult::EStatus status)303 scoped_refptr<const NGLayoutResult> NGBoxFragmentBuilder::Abort(
304 NGLayoutResult::EStatus status) {
305 return base::AdoptRef(new NGLayoutResult(
306 NGLayoutResult::NGBoxFragmentBuilderPassKey(), status, this));
307 }
308
GetChildOffset(const LayoutObject * object) const309 LogicalOffset NGBoxFragmentBuilder::GetChildOffset(
310 const LayoutObject* object) const {
311 DCHECK(object);
312
313 if (const NGFragmentItemsBuilder* items_builder = items_builder_) {
314 if (auto offset = items_builder->LogicalOffsetFor(*object))
315 return *offset;
316 NOTREACHED();
317 return LogicalOffset();
318 }
319
320 for (const auto& child : children_) {
321 if (child.fragment->GetLayoutObject() == object)
322 return child.offset;
323
324 // TODO(layout-dev): ikilpatrick thinks we may need to traverse
325 // further than the initial line-box children for a nested inline
326 // container. We could not come up with a testcase, it would be
327 // something with split inlines, and nested oof/fixed descendants maybe.
328 if (child.fragment->IsLineBox()) {
329 const auto& line_box_fragment =
330 To<NGPhysicalLineBoxFragment>(*child.fragment);
331 for (const auto& line_box_child : line_box_fragment.Children()) {
332 if (line_box_child->GetLayoutObject() == object) {
333 return child.offset + line_box_child.Offset().ConvertToLogical(
334 GetWritingMode(), Direction(),
335 line_box_fragment.Size(),
336 line_box_child->Size());
337 }
338 }
339 }
340 }
341 NOTREACHED();
342 return LogicalOffset();
343 }
344
ComputeInlineContainerGeometryFromFragmentTree(InlineContainingBlockMap * inline_containing_block_map)345 void NGBoxFragmentBuilder::ComputeInlineContainerGeometryFromFragmentTree(
346 InlineContainingBlockMap* inline_containing_block_map) {
347 if (inline_containing_block_map->IsEmpty())
348 return;
349
350 // This function has detailed knowledge of inline fragment tree structure,
351 // and will break if this changes.
352 DCHECK_GE(InlineSize(), LayoutUnit());
353 DCHECK_GE(BlockSize(), LayoutUnit());
354 #if DCHECK_IS_ON()
355 // Make sure all entries are continuation root.
356 for (const auto& entry : *inline_containing_block_map)
357 DCHECK_EQ(entry.key, entry.key->ContinuationRoot());
358 #endif
359
360 HashMap<const LayoutObject*, LineBoxPair> containing_linebox_map;
361 for (const auto& child : children_) {
362 if (child.fragment->IsLineBox()) {
363 const auto& linebox = To<NGPhysicalLineBoxFragment>(*child.fragment);
364 const PhysicalOffset linebox_offset = child.offset.ConvertToPhysical(
365 GetWritingMode(), Direction(),
366 ToPhysicalSize(Size(), GetWritingMode()), linebox.Size());
367 GatherInlineContainerFragmentsFromLinebox(inline_containing_block_map,
368 &containing_linebox_map,
369 linebox, linebox_offset);
370 } else if (child.fragment->IsBox()) {
371 const auto& box_fragment = To<NGPhysicalBoxFragment>(*child.fragment);
372 bool is_anonymous_container =
373 box_fragment.GetLayoutObject() &&
374 box_fragment.GetLayoutObject()->IsAnonymousBlock();
375 if (!is_anonymous_container)
376 continue;
377 // If child is an anonymous container, this might be a special case of
378 // split inlines. The inline container fragments might be inside
379 // anonymous boxes. To find inline container fragments, traverse
380 // lineboxes inside anonymous box.
381 // For more on this special case, see "css container is an inline, with
382 // inline splitting" comment in NGOutOfFlowLayoutPart::LayoutDescendant.
383 const PhysicalOffset box_offset = child.offset.ConvertToPhysical(
384 GetWritingMode(), Direction(),
385 ToPhysicalSize(Size(), GetWritingMode()), box_fragment.Size());
386
387 // Traverse lineboxes of anonymous box.
388 for (const auto& box_child : box_fragment.Children()) {
389 if (box_child->IsLineBox()) {
390 const auto& linebox = To<NGPhysicalLineBoxFragment>(*box_child);
391 const PhysicalOffset linebox_offset = box_child.Offset() + box_offset;
392 GatherInlineContainerFragmentsFromLinebox(inline_containing_block_map,
393 &containing_linebox_map,
394 linebox, linebox_offset);
395 }
396 }
397 }
398 }
399 }
400
ComputeInlineContainerGeometry(InlineContainingBlockMap * inline_containing_block_map)401 void NGBoxFragmentBuilder::ComputeInlineContainerGeometry(
402 InlineContainingBlockMap* inline_containing_block_map) {
403 if (inline_containing_block_map->IsEmpty())
404 return;
405
406 // This function requires that we have the final size of the fragment set
407 // upon the builder.
408 DCHECK_GE(InlineSize(), LayoutUnit());
409 DCHECK_GE(BlockSize(), LayoutUnit());
410
411 #if DCHECK_IS_ON()
412 // Make sure all entries are a continuation root.
413 for (const auto& entry : *inline_containing_block_map)
414 DCHECK_EQ(entry.key, entry.key->ContinuationRoot());
415 #endif
416
417 HashMap<const LayoutObject*, LineBoxPair> containing_linebox_map;
418
419 if (items_builder_) {
420 // To access the items correctly we need to convert them to the physical
421 // coordinate space.
422 GatherInlineContainerFragmentsFromItems(
423 items_builder_->Items(GetWritingMode(), Direction(),
424 ToPhysicalSize(Size(), GetWritingMode())),
425 PhysicalOffset(), inline_containing_block_map, &containing_linebox_map);
426 return;
427 }
428
429 // If we have children which are anonymous block, we might contain split
430 // inlines, this can occur in the following example:
431 // <div>
432 // Some text <span style="position: relative;">text
433 // <div>block</div>
434 // text </span> text.
435 // </div>
436 for (const auto& child : children_) {
437 if (!child.fragment->IsAnonymousBlock())
438 continue;
439
440 const auto& child_fragment = To<NGPhysicalBoxFragment>(*child.fragment);
441 const auto* items = child_fragment.Items();
442 if (!items)
443 continue;
444
445 const PhysicalOffset child_offset = child.offset.ConvertToPhysical(
446 GetWritingMode(), Direction(), ToPhysicalSize(Size(), GetWritingMode()),
447 child_fragment.Size());
448 GatherInlineContainerFragmentsFromItems(items->Items(), child_offset,
449 inline_containing_block_map,
450 &containing_linebox_map);
451 }
452 }
453
454 #if DCHECK_IS_ON()
455
CheckNoBlockFragmentation() const456 void NGBoxFragmentBuilder::CheckNoBlockFragmentation() const {
457 DCHECK(!did_break_);
458 DCHECK(!has_forced_break_);
459 DCHECK_EQ(consumed_block_size_, LayoutUnit());
460 DCHECK_EQ(minimal_space_shortage_, LayoutUnit::Max());
461 DCHECK_EQ(initial_break_before_, EBreakBetween::kAuto);
462 DCHECK_EQ(previous_break_after_, EBreakBetween::kAuto);
463 }
464
465 #endif
466
467 } // namespace blink
468