1 // Copyright 2019 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/inline/ng_fragment_items.h"
6 
7 #include "third_party/blink/renderer/core/layout/layout_block.h"
8 #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.h"
9 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
10 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
11 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
12 
13 namespace blink {
14 
15 namespace {
16 
17 #if DCHECK_IS_ON()
CheckNoItemsAreAssociated(const NGPhysicalBoxFragment & fragment)18 void CheckNoItemsAreAssociated(const NGPhysicalBoxFragment& fragment) {
19   if (const NGFragmentItems* fragment_items = fragment.Items()) {
20     for (const NGFragmentItem& item : fragment_items->Items()) {
21       if (item.Type() == NGFragmentItem::kLine)
22         continue;
23       if (const LayoutObject* layout_object = item.GetLayoutObject())
24         DCHECK(!layout_object->FirstInlineFragmentItemIndex());
25     }
26   }
27 }
28 
CheckIsLast(const NGFragmentItem & item)29 void CheckIsLast(const NGFragmentItem& item) {
30   if (const NGPhysicalBoxFragment* fragment = item.BoxFragment()) {
31     if (!fragment->IsInline()) {
32       DCHECK(fragment->IsFloating());
33       DCHECK_EQ(item.IsLastForNode(), !fragment->BreakToken());
34     }
35   }
36 }
37 #endif
38 
39 }  // namespace
40 
NGFragmentItems(NGFragmentItemsBuilder * builder)41 NGFragmentItems::NGFragmentItems(NGFragmentItemsBuilder* builder)
42     : text_content_(std::move(builder->text_content_)),
43       first_line_text_content_(std::move(builder->first_line_text_content_)),
44       size_(builder->items_.size()),
45       size_of_earlier_fragments_(0) {
46   NGFragmentItemsBuilder::ItemWithOffsetList& source_items = builder->items_;
47   for (wtf_size_t i = 0; i < size_; ++i) {
48     // Call the move constructor to move without |AddRef|. Items in
49     // |NGFragmentItemsBuilder| are not used after |this| was constructed.
50     new (&items_[i]) NGFragmentItem(std::move(source_items[i].item));
51   }
52 }
53 
NGFragmentItems(const NGFragmentItems & other)54 NGFragmentItems::NGFragmentItems(const NGFragmentItems& other)
55     : text_content_(other.text_content_),
56       first_line_text_content_(other.first_line_text_content_),
57       size_(other.size_),
58       size_of_earlier_fragments_(other.size_of_earlier_fragments_) {
59   for (wtf_size_t i = 0; i < size_; ++i) {
60     const auto& other_item = other.items_[i];
61     new (&items_[i]) NGFragmentItem(other_item);
62 
63     // The |other| object is likely going to be freed after this copy. Detach
64     // any |AbstractInlineTextBox|, as they store a pointer to an individual
65     // |NGFragmentItem|.
66     if (auto* layout_text =
67             DynamicTo<LayoutText>(other_item.GetMutableLayoutObject()))
68       layout_text->DetachAbstractInlineTextBoxesIfNeeded();
69   }
70 }
71 
~NGFragmentItems()72 NGFragmentItems::~NGFragmentItems() {
73   for (unsigned i = 0; i < size_; ++i)
74     items_[i].~NGFragmentItem();
75 }
76 
IsSubSpan(const Span & span) const77 bool NGFragmentItems::IsSubSpan(const Span& span) const {
78   return span.empty() ||
79          (span.data() >= ItemsData() && &span.back() < ItemsData() + Size());
80 }
81 
FinalizeAfterLayout(const Vector<scoped_refptr<const NGLayoutResult>,1> & results)82 void NGFragmentItems::FinalizeAfterLayout(
83     const Vector<scoped_refptr<const NGLayoutResult>, 1>& results) {
84 #if DCHECK_IS_ON()
85   if (!RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) {
86     for (const auto& result : results) {
87       CheckNoItemsAreAssociated(
88           To<NGPhysicalBoxFragment>(result->PhysicalFragment()));
89     }
90   }
91 #endif
92   struct LastItem {
93     const NGFragmentItem* item;
94     wtf_size_t fragment_id;
95     wtf_size_t item_index;
96   };
97   HashMap<const LayoutObject*, LastItem> last_items;
98   wtf_size_t item_index = 0;
99   for (const auto& result : results) {
100     const auto& fragment =
101         To<NGPhysicalBoxFragment>(result->PhysicalFragment());
102     const NGFragmentItems* fragment_items = fragment.Items();
103     if (UNLIKELY(!fragment_items))
104       continue;
105 
106     fragment_items->size_of_earlier_fragments_ = item_index;
107     const Span items = fragment_items->Items();
108     for (const NGFragmentItem& item : items) {
109       ++item_index;
110       if (item.Type() == NGFragmentItem::kLine) {
111         DCHECK_EQ(item.DeltaToNextForSameLayoutObject(), 0u);
112         continue;
113       }
114       LayoutObject* const layout_object = item.GetMutableLayoutObject();
115       DCHECK(!layout_object->IsOutOfFlowPositioned());
116       DCHECK(layout_object->IsInLayoutNGInlineFormattingContext());
117 
118       item.SetDeltaToNextForSameLayoutObject(0);
119       if (UNLIKELY(layout_object->IsFloating())) {
120         // Fragments that aren't really on a line, such as floats, will have
121         // block break tokens if they continue in a subsequent fragmentainer, so
122         // just check that. Floats in particular will continue as regular box
123         // fragment children in subsequent fragmentainers, i.e. they will not be
124         // fragment items (even if we're in an inline formatting context). So
125         // we're not going to find the last fragment by just looking for items.
126         DCHECK(item.BoxFragment() && item.BoxFragment()->IsFloating());
127         item.SetIsLastForNode(!item.BoxFragment()->BreakToken());
128       } else {
129         DCHECK(layout_object->IsInline());
130         // This will be updated later if following fragments are found.
131         item.SetIsLastForNode(true);
132       }
133 
134       // If this is the first fragment, associate with |layout_object|.
135       const auto last_item_result =
136           last_items.insert(layout_object, LastItem{&item, 0, item_index});
137       const bool is_first = last_item_result.is_new_entry;
138       if (is_first) {
139         item.SetFragmentId(0);
140 #if DCHECK_IS_ON()
141         if (!RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled())
142           DCHECK_EQ(layout_object->FirstInlineFragmentItemIndex(), 0u);
143 #endif
144         layout_object->SetFirstInlineFragmentItemIndex(item_index);
145         continue;
146       }
147 
148       // Update the last item for |layout_object|.
149       LastItem* last = &last_item_result.stored_value->value;
150       const NGFragmentItem* last_item = last->item;
151       DCHECK_EQ(last_item->DeltaToNextForSameLayoutObject(), 0u);
152       const wtf_size_t last_index = last->item_index;
153       DCHECK_GT(last_index, 0u);
154       DCHECK_LT(last_index, fragment_items->EndItemIndex());
155       DCHECK_LT(last_index, item_index);
156       last_item->SetDeltaToNextForSameLayoutObject(item_index - last_index);
157       if (!layout_object->IsFloating())
158         last_item->SetIsLastForNode(false);
159 #if DCHECK_IS_ON()
160       CheckIsLast(*last_item);
161 #endif
162 
163       // Update this item.
164       item.SetFragmentId(++last->fragment_id);
165       last->item = &item;
166       last->item_index = item_index;
167     }
168   }
169 #if DCHECK_IS_ON()
170   for (const auto& iter : last_items)
171     CheckIsLast(*iter.value.item);
172 #endif
173 }
174 
ClearAssociatedFragments(LayoutObject * container)175 void NGFragmentItems::ClearAssociatedFragments(LayoutObject* container) {
176   // Clear by traversing |LayoutObject| tree rather than |NGFragmentItem|
177   // because a) we don't need to modify |NGFragmentItem|, and in general the
178   // number of |LayoutObject| is less than the number of |NGFragmentItem|.
179   for (LayoutObject* child = container->SlowFirstChild(); child;
180        child = child->NextSibling()) {
181     if (UNLIKELY(!child->IsInLayoutNGInlineFormattingContext() ||
182                  child->IsOutOfFlowPositioned()))
183       continue;
184     child->ClearFirstInlineFragmentItemIndex();
185 
186     // Children of |LayoutInline| are part of this inline formatting context,
187     // but children of other |LayoutObject| (e.g., floats, oof, inline-blocks)
188     // are not.
189     if (child->IsLayoutInline())
190       ClearAssociatedFragments(child);
191   }
192 #if DCHECK_IS_ON()
193   if (const auto* box = DynamicTo<LayoutBox>(container)) {
194     for (const NGPhysicalBoxFragment& fragment : box->PhysicalFragments())
195       CheckNoItemsAreAssociated(fragment);
196   }
197 #endif
198 }
199 
200 // static
CanReuseAll(NGInlineCursor * cursor)201 bool NGFragmentItems::CanReuseAll(NGInlineCursor* cursor) {
202   for (; *cursor; cursor->MoveToNext()) {
203     const NGFragmentItem& item = *cursor->Current().Item();
204     if (!item.CanReuse())
205       return false;
206   }
207   return true;
208 }
209 
EndOfReusableItems(const NGPhysicalBoxFragment & container) const210 const NGFragmentItem* NGFragmentItems::EndOfReusableItems(
211     const NGPhysicalBoxFragment& container) const {
212   const NGFragmentItem* last_line_start = &front();
213   for (NGInlineCursor cursor(container, *this); cursor;) {
214     const NGFragmentItem& item = *cursor.Current();
215     if (item.IsDirty())
216       return &item;
217 
218     // Top-level fragments that are not line box cannot be reused; e.g., oof
219     // or list markers.
220     if (item.Type() != NGFragmentItem::kLine)
221       return &item;
222 
223     const NGPhysicalLineBoxFragment* line_box_fragment = item.LineBoxFragment();
224     DCHECK(line_box_fragment);
225 
226     // If there is a dirty item in the middle of a line, its previous line is
227     // not reusable, because the dirty item may affect the previous line to wrap
228     // differently.
229     NGInlineCursor line = cursor.CursorForDescendants();
230     if (!CanReuseAll(&line))
231       return last_line_start;
232 
233     // Abort if the line propagated its descendants to outside of the line.
234     // They are propagated through NGLayoutResult, which we don't cache.
235     if (line_box_fragment->HasPropagatedDescendants())
236       return &item;
237 
238     // TODO(kojii): Running the normal layout code at least once for this
239     // child helps reducing the code to setup internal states after the
240     // partial. Remove the last fragment if it is the end of the
241     // fragmentation to do so, but we should figure out how to setup the
242     // states without doing this.
243     const NGBreakToken* break_token = line_box_fragment->BreakToken();
244     DCHECK(break_token);
245     if (break_token->IsFinished())
246       return &item;
247 
248     last_line_start = &item;
249     cursor.MoveToNextSkippingChildren();
250   }
251   return nullptr;  // all items are reusable.
252 }
253 
254 // static
TryDirtyFirstLineFor(const LayoutObject & layout_object,const LayoutBlockFlow & container)255 bool NGFragmentItems::TryDirtyFirstLineFor(const LayoutObject& layout_object,
256                                            const LayoutBlockFlow& container) {
257   DCHECK(layout_object.IsDescendantOf(&container));
258   NGInlineCursor cursor(container);
259   cursor.MoveTo(layout_object);
260   if (!cursor)
261     return false;
262   DCHECK(cursor.Current().Item());
263   DCHECK_EQ(&layout_object, cursor.Current().GetLayoutObject());
264   cursor.Current()->SetDirty();
265   return true;
266 }
267 
268 // static
TryDirtyLastLineFor(const LayoutObject & layout_object,const LayoutBlockFlow & container)269 bool NGFragmentItems::TryDirtyLastLineFor(const LayoutObject& layout_object,
270                                           const LayoutBlockFlow& container) {
271   DCHECK(layout_object.IsDescendantOf(&container));
272   NGInlineCursor cursor(container);
273   cursor.MoveTo(layout_object);
274   if (!cursor)
275     return false;
276   cursor.MoveToLastForSameLayoutObject();
277   DCHECK(cursor.Current().Item());
278   DCHECK_EQ(&layout_object, cursor.Current().GetLayoutObject());
279   cursor.Current()->SetDirty();
280   return true;
281 }
282 
283 // static
DirtyLinesFromChangedChild(const LayoutObject & child,const LayoutBlockFlow & container)284 void NGFragmentItems::DirtyLinesFromChangedChild(
285     const LayoutObject& child,
286     const LayoutBlockFlow& container) {
287   if (child.IsInLayoutNGInlineFormattingContext() &&
288       !child.IsFloatingOrOutOfFlowPositioned()) {
289     if (TryDirtyFirstLineFor(child, container))
290       return;
291   }
292 
293   // If |child| is new, or did not generate fragments, mark the fragments for
294   // previous |LayoutObject| instead.
295   for (const LayoutObject* current = &child;;) {
296     if (const LayoutObject* previous = current->PreviousSibling()) {
297       while (const auto* layout_inline = DynamicTo<LayoutInline>(previous)) {
298         if (const LayoutObject* last_child = layout_inline->LastChild())
299           previous = last_child;
300         else
301           break;
302       }
303       current = previous;
304       if (UNLIKELY(current->IsFloatingOrOutOfFlowPositioned()))
305         continue;
306       if (current->IsInLayoutNGInlineFormattingContext()) {
307         if (TryDirtyLastLineFor(*current, container))
308           return;
309       }
310       continue;
311     }
312 
313     current = current->Parent();
314     if (!current || current->IsLayoutBlockFlow()) {
315       DirtyFirstItem(container);
316       return;
317     }
318     DCHECK(current->IsLayoutInline());
319     if (current->IsInLayoutNGInlineFormattingContext()) {
320       if (TryDirtyFirstLineFor(*current, container))
321         return;
322     }
323   }
324 }
325 
326 // static
DirtyFirstItem(const LayoutBlockFlow & container)327 void NGFragmentItems::DirtyFirstItem(const LayoutBlockFlow& container) {
328   for (const NGPhysicalBoxFragment& fragment : container.PhysicalFragments()) {
329     if (const NGFragmentItems* items = fragment.Items()) {
330       items->front().SetDirty();
331       return;
332     }
333   }
334 }
335 
336 // static
DirtyLinesFromNeedsLayout(const LayoutBlockFlow & container)337 void NGFragmentItems::DirtyLinesFromNeedsLayout(
338     const LayoutBlockFlow& container) {
339   DCHECK(std::any_of(container.PhysicalFragments().begin(),
340                      container.PhysicalFragments().end(),
341                      [](const NGPhysicalBoxFragment& fragment) {
342                        return fragment.HasItems();
343                      }));
344 
345   // Mark dirty for the first top-level child that has |NeedsLayout|.
346   //
347   // TODO(kojii): We could mark first descendant to increase reuse
348   // opportunities. Doing this complicates the logic, especially when culled
349   // inline is involved, and common case is to append to large IFC. Choose
350   // simpler logic and faster to check over more reuse opportunities.
351   const auto writing_mode = container.StyleRef().GetWritingMode();
352   for (LayoutObject* child = container.FirstChild(); child;
353        child = child->NextSibling()) {
354     // NeedsLayout is not helpful for an orthogonal writing-mode root because
355     // its NeedsLayout flag is cleared during the ComputeMinMaxSizes() step of
356     // the container.
357     if (child->NeedsLayout() ||
358         !IsParallelWritingMode(writing_mode,
359                                child->StyleRef().GetWritingMode())) {
360       DirtyLinesFromChangedChild(*child, container);
361       return;
362     }
363   }
364 }
365 
366 // static
LayoutObjectWillBeMoved(const LayoutObject & layout_object)367 void NGFragmentItems::LayoutObjectWillBeMoved(
368     const LayoutObject& layout_object) {
369   NGInlineCursor cursor;
370   cursor.MoveTo(layout_object);
371   for (; cursor; cursor.MoveToNextForSameLayoutObject()) {
372     const NGFragmentItem* item = cursor.Current().Item();
373     item->LayoutObjectWillBeMoved();
374   }
375 }
376 
377 // static
LayoutObjectWillBeDestroyed(const LayoutObject & layout_object)378 void NGFragmentItems::LayoutObjectWillBeDestroyed(
379     const LayoutObject& layout_object) {
380   NGInlineCursor cursor;
381   cursor.MoveTo(layout_object);
382   for (; cursor; cursor.MoveToNextForSameLayoutObject()) {
383     const NGFragmentItem* item = cursor.Current().Item();
384     item->LayoutObjectWillBeDestroyed();
385   }
386 }
387 
388 #if DCHECK_IS_ON()
CheckAllItemsAreValid() const389 void NGFragmentItems::CheckAllItemsAreValid() const {
390   for (const NGFragmentItem& item : Items())
391     DCHECK(!item.IsLayoutObjectDestroyedOrMoved());
392 }
393 #endif
394 
395 }  // namespace blink
396