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