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