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_block_node.h"
6 
7 #include <memory>
8 
9 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
10 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
11 #include "third_party/blink/renderer/core/html/html_marquee_element.h"
12 #include "third_party/blink/renderer/core/input_type_names.h"
13 #include "third_party/blink/renderer/core/layout/box_layout_extra_input.h"
14 #include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h"
15 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
16 #include "third_party/blink/renderer/core/layout/layout_fieldset.h"
17 #include "third_party/blink/renderer/core/layout/layout_inline.h"
18 #include "third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.h"
19 #include "third_party/blink/renderer/core/layout/layout_multi_column_set.h"
20 #include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h"
21 #include "third_party/blink/renderer/core/layout/layout_table.h"
22 #include "third_party/blink/renderer/core/layout/layout_table_cell.h"
23 #include "third_party/blink/renderer/core/layout/layout_video.h"
24 #include "third_party/blink/renderer/core/layout/min_max_sizes.h"
25 #include "third_party/blink/renderer/core/layout/ng/custom/layout_ng_custom.h"
26 #include "third_party/blink/renderer/core/layout/ng/custom/ng_custom_layout_algorithm.h"
27 #include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h"
28 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
29 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
30 #include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h"
31 #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h"
32 #include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.h"
33 #include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_layout_utils.h"
34 #include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_row_layout_algorithm.h"
35 #include "third_party/blink/renderer/core/layout/ng/mathml/ng_math_space_layout_algorithm.h"
36 #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
37 #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h"
38 #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
39 #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
40 #include "third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h"
41 #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
42 #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
43 #include "third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h"
44 #include "third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.h"
45 #include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
46 #include "third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h"
47 #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
48 #include "third_party/blink/renderer/core/layout/ng/ng_layout_utils.h"
49 #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
50 #include "third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.h"
51 #include "third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.h"
52 #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
53 #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h"
54 #include "third_party/blink/renderer/core/layout/text_autosizer.h"
55 #include "third_party/blink/renderer/core/mathml/mathml_element.h"
56 #include "third_party/blink/renderer/core/mathml/mathml_fraction_element.h"
57 #include "third_party/blink/renderer/core/mathml/mathml_space_element.h"
58 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
59 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
60 #include "third_party/blink/renderer/platform/text/writing_mode.h"
61 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
62 
63 namespace blink {
64 
65 namespace {
66 
GetFlowThread(const LayoutBlockFlow * block_flow)67 inline LayoutMultiColumnFlowThread* GetFlowThread(
68     const LayoutBlockFlow* block_flow) {
69   if (!block_flow)
70     return nullptr;
71   return block_flow->MultiColumnFlowThread();
72 }
73 
GetFlowThread(const LayoutBox & box)74 inline LayoutMultiColumnFlowThread* GetFlowThread(const LayoutBox& box) {
75   return GetFlowThread(DynamicTo<LayoutBlockFlow>(box));
76 }
77 
78 // The entire purpose of this function is to avoid allocating space on the stack
79 // for all layout algorithms for each node we lay out. Therefore it must not be
80 // inline.
81 template <typename Algorithm, typename Callback>
CreateAlgorithmAndRun(const NGLayoutAlgorithmParams & params,const Callback & callback)82 NOINLINE void CreateAlgorithmAndRun(const NGLayoutAlgorithmParams& params,
83                                     const Callback& callback) {
84   Algorithm algorithm(params);
85   callback(&algorithm);
86 }
87 
88 template <typename Callback>
DetermineMathMLAlgorithmAndRun(const LayoutBox & box,const NGLayoutAlgorithmParams & params,const Callback & callback)89 NOINLINE void DetermineMathMLAlgorithmAndRun(
90     const LayoutBox& box,
91     const NGLayoutAlgorithmParams& params,
92     const Callback& callback) {
93   DCHECK(box.IsMathML());
94   // Currently math layout algorithms can only apply to MathML elements.
95   auto* element = box.GetNode();
96   DCHECK(element);
97   if (IsA<MathMLSpaceElement>(element))
98     CreateAlgorithmAndRun<NGMathSpaceLayoutAlgorithm>(params, callback);
99   else if (IsA<MathMLFractionElement>(element) &&
100            IsValidMathMLFraction(params.node))
101     CreateAlgorithmAndRun<NGMathFractionLayoutAlgorithm>(params, callback);
102   else
103     CreateAlgorithmAndRun<NGMathRowLayoutAlgorithm>(params, callback);
104 }
105 
106 template <typename Callback>
DetermineAlgorithmAndRun(const NGLayoutAlgorithmParams & params,const Callback & callback)107 NOINLINE void DetermineAlgorithmAndRun(const NGLayoutAlgorithmParams& params,
108                                        const Callback& callback) {
109   const ComputedStyle& style = params.node.Style();
110   const LayoutBox& box = *params.node.GetLayoutBox();
111   if (box.IsLayoutNGFlexibleBox()) {
112     CreateAlgorithmAndRun<NGFlexLayoutAlgorithm>(params, callback);
113   } else if (box.IsLayoutNGCustom()) {
114     CreateAlgorithmAndRun<NGCustomLayoutAlgorithm>(params, callback);
115   } else if (box.IsMathML()) {
116     DetermineMathMLAlgorithmAndRun(box, params, callback);
117   } else if (box.IsLayoutNGFieldset()) {
118     CreateAlgorithmAndRun<NGFieldsetLayoutAlgorithm>(params, callback);
119     // If there's a legacy layout box, we can only do block fragmentation if
120     // we would have done block fragmentation with the legacy engine.
121     // Otherwise writing data back into the legacy tree will fail. Look for
122     // the flow thread.
123   } else if (GetFlowThread(box)) {
124     if (style.SpecifiesColumns())
125       CreateAlgorithmAndRun<NGColumnLayoutAlgorithm>(params, callback);
126     else
127       CreateAlgorithmAndRun<NGPageLayoutAlgorithm>(params, callback);
128   } else {
129     CreateAlgorithmAndRun<NGBlockLayoutAlgorithm>(params, callback);
130   }
131 }
132 
LayoutWithAlgorithm(const NGLayoutAlgorithmParams & params)133 inline scoped_refptr<const NGLayoutResult> LayoutWithAlgorithm(
134     const NGLayoutAlgorithmParams& params) {
135   scoped_refptr<const NGLayoutResult> result;
136   DetermineAlgorithmAndRun(params,
137                            [&result](NGLayoutAlgorithmOperations* algorithm) {
138                              result = algorithm->Layout();
139                            });
140   return result;
141 }
142 
ComputeMinMaxSizesWithAlgorithm(const NGLayoutAlgorithmParams & params,const MinMaxSizesInput & input)143 inline base::Optional<MinMaxSizes> ComputeMinMaxSizesWithAlgorithm(
144     const NGLayoutAlgorithmParams& params,
145     const MinMaxSizesInput& input) {
146   base::Optional<MinMaxSizes> min_max_sizes;
147   DetermineAlgorithmAndRun(
148       params, [&min_max_sizes, &input](NGLayoutAlgorithmOperations* algorithm) {
149         min_max_sizes = algorithm->ComputeMinMaxSizes(input);
150       });
151   return min_max_sizes;
152 }
153 
UpdateLegacyMultiColumnFlowThread(NGBlockNode node,LayoutMultiColumnFlowThread * flow_thread,const NGConstraintSpace & constraint_space,const NGPhysicalBoxFragment & fragment)154 void UpdateLegacyMultiColumnFlowThread(
155     NGBlockNode node,
156     LayoutMultiColumnFlowThread* flow_thread,
157     const NGConstraintSpace& constraint_space,
158     const NGPhysicalBoxFragment& fragment) {
159   WritingMode writing_mode = constraint_space.GetWritingMode();
160   LayoutUnit flow_end;
161   bool has_processed_first_column_in_flow_thread = false;
162   bool has_processed_first_column_in_row = false;
163 
164   // Stitch the columns together.
165   NGBoxStrut border_scrollbar_padding =
166       ComputeBorders(constraint_space, node.Style()) +
167       ComputeScrollbars(constraint_space, node) +
168       ComputePadding(constraint_space, node.Style());
169   NGFragment logical_multicol_fragment(writing_mode, fragment);
170   LayoutUnit column_row_inline_size = logical_multicol_fragment.InlineSize() -
171                                       border_scrollbar_padding.InlineSum();
172   LayoutMultiColumnSet* column_set =
173       ToLayoutMultiColumnSetOrNull(flow_thread->FirstMultiColumnBox());
174   for (const auto& child : fragment.Children()) {
175     if (child->GetLayoutObject() &&
176         child->GetLayoutObject()->IsColumnSpanAll()) {
177       // Column spanners are not part of the fragmentation context. We'll use
178       // them as stepping stones to get to the next column set. Note that there
179       // are known discrepancies between when the legacy engine creates column
180       // sets, and when LayoutNG creates column fragments, so our code here
181       // needs to deal with:
182       // 1: NG column fragments with no associated legacy column set
183       // 2: A legacy column set with no associated NG column fragments
184       NGFragment logical_spanner_fragment(writing_mode, *child);
185       if (column_set)
186         column_set->EndFlow(flow_end);
187       // Prepare the next column set, if there's one directly following this
188       // spanner.
189       LayoutMultiColumnSpannerPlaceholder* spanner_placeholder =
190           child->GetLayoutObject()->SpannerPlaceholder();
191       column_set = ToLayoutMultiColumnSetOrNull(
192           spanner_placeholder->NextSiblingMultiColumnBox());
193       if (column_set)
194         column_set->BeginFlow(flow_end);
195       has_processed_first_column_in_row = false;
196       continue;
197     }
198     NGFragment logical_column_fragment(writing_mode, *child);
199     flow_end += logical_column_fragment.BlockSize();
200     // Non-uniform fragmentainer widths not supported by legacy layout.
201     DCHECK(!has_processed_first_column_in_flow_thread ||
202            flow_thread->LogicalWidth() == logical_column_fragment.InlineSize());
203     if (!has_processed_first_column_in_flow_thread) {
204       // The offset of the flow thread should be the same as that of the first
205       // first column.
206       flow_thread->SetLocationAndUpdateOverflowControlsIfNeeded(
207           child.Offset().ToLayoutPoint());
208       flow_thread->SetLogicalWidth(logical_column_fragment.InlineSize());
209       has_processed_first_column_in_flow_thread = true;
210     }
211     if (!has_processed_first_column_in_row && column_set) {
212       column_set->SetLogicalLeft(border_scrollbar_padding.inline_start);
213       if (IsHorizontalWritingMode(writing_mode)) {
214         column_set->SetLogicalTop(child.offset.top);
215       } else if (IsFlippedBlocksWritingMode(writing_mode)) {
216         column_set->SetLogicalTop(fragment.Size().width - child.offset.left -
217                                   child->Size().width);
218       } else {
219         column_set->SetLogicalTop(child.offset.left);
220       }
221       column_set->SetLogicalWidth(column_row_inline_size);
222       column_set->SetLogicalHeight(logical_column_fragment.BlockSize());
223       has_processed_first_column_in_row = true;
224     }
225   }
226 
227   if (column_set)
228     column_set->EndFlow(flow_end);
229 
230   flow_thread->UpdateFromNG();
231   flow_thread->ValidateColumnSets();
232   flow_thread->SetLogicalHeight(flow_end);
233   flow_thread->UpdateAfterLayout();
234   flow_thread->ClearNeedsLayout();
235 }
236 
CreateConstraintSpaceBuilderForMinMax(NGBlockNode node)237 NGConstraintSpaceBuilder CreateConstraintSpaceBuilderForMinMax(
238     NGBlockNode node) {
239   NGConstraintSpaceBuilder builder(node.Style().GetWritingMode(),
240                                    node.Style().GetWritingMode(),
241                                    node.CreatesNewFormattingContext());
242   builder.SetTextDirection(node.Style().Direction());
243   return builder;
244 }
245 
CalculateAvailableInlineSizeForLegacy(const LayoutBox & box,const NGConstraintSpace & space)246 LayoutUnit CalculateAvailableInlineSizeForLegacy(
247     const LayoutBox& box,
248     const NGConstraintSpace& space) {
249   if (box.ShouldComputeSizeAsReplaced())
250     return space.ReplacedPercentageResolutionInlineSize();
251 
252   return space.PercentageResolutionInlineSize();
253 }
254 
CalculateAvailableBlockSizeForLegacy(const LayoutBox & box,const NGConstraintSpace & space)255 LayoutUnit CalculateAvailableBlockSizeForLegacy(
256     const LayoutBox& box,
257     const NGConstraintSpace& space) {
258   if (box.ShouldComputeSizeAsReplaced())
259     return space.ReplacedPercentageResolutionBlockSize();
260 
261   return space.PercentageResolutionBlockSize();
262 }
263 
SetupBoxLayoutExtraInput(const NGConstraintSpace & space,const LayoutBox & box,BoxLayoutExtraInput * input)264 void SetupBoxLayoutExtraInput(const NGConstraintSpace& space,
265                               const LayoutBox& box,
266                               BoxLayoutExtraInput* input) {
267   input->containing_block_content_inline_size =
268       CalculateAvailableInlineSizeForLegacy(box, space);
269   input->containing_block_content_block_size =
270       CalculateAvailableBlockSizeForLegacy(box, space);
271 
272   WritingMode writing_mode = box.StyleRef().GetWritingMode();
273   if (LayoutObject* containing_block = box.ContainingBlock()) {
274     if (!IsParallelWritingMode(containing_block->StyleRef().GetWritingMode(),
275                                writing_mode)) {
276       // The sizes should be in the containing block writing mode.
277       std::swap(input->containing_block_content_block_size,
278                 input->containing_block_content_inline_size);
279 
280       // We cannot lay out without a definite containing block inline-size. We
281       // end up here if we're performing a measure pass (as part of resolving
282       // the intrinsic min/max inline-size of some ancestor, for instance).
283       // Legacy layout has a tendency of clamping negative sizes to 0 anyway,
284       // but this is missing when it comes to resolving percentage-based
285       // padding, for instance.
286       if (input->containing_block_content_inline_size == kIndefiniteSize)
287         input->containing_block_content_inline_size = LayoutUnit();
288     }
289   }
290 
291   // We need a definite containing block inline-size, or we'd be unable to
292   // resolve percentages.
293   DCHECK_GE(input->containing_block_content_inline_size, LayoutUnit());
294 
295   input->available_inline_size = space.AvailableSize().inline_size;
296 
297   if (space.IsFixedInlineSize())
298     input->override_inline_size = space.AvailableSize().inline_size;
299   if (space.IsFixedBlockSize())
300     input->override_block_size = space.AvailableSize().block_size;
301 }
302 
CanUseCachedIntrinsicInlineSizes(const MinMaxSizesInput & input,const LayoutBox & box)303 bool CanUseCachedIntrinsicInlineSizes(const MinMaxSizesInput& input,
304                                       const LayoutBox& box) {
305   // Obviously can't use the cache if our intrinsic logical widths are dirty.
306   if (box.IntrinsicLogicalWidthsDirty())
307     return false;
308 
309   // We don't store the float inline sizes for comparison, always skip the
310   // cache in this case.
311   if (input.float_left_inline_size || input.float_right_inline_size)
312     return false;
313 
314   // Check if we have any percentage inline padding.
315   const auto& style = box.StyleRef();
316   if (style.MayHavePadding() && (style.PaddingStart().IsPercentOrCalc() ||
317                                  style.PaddingEnd().IsPercentOrCalc()))
318     return false;
319 
320   // Check if the %-block-size matches.
321   if (input.percentage_resolution_block_size !=
322       box.IntrinsicLogicalWidthsPercentageResolutionBlockSize())
323     return false;
324 
325   return true;
326 }
327 
328 }  // namespace
329 
Layout(const NGConstraintSpace & constraint_space,const NGBlockBreakToken * break_token,const NGEarlyBreak * early_break)330 scoped_refptr<const NGLayoutResult> NGBlockNode::Layout(
331     const NGConstraintSpace& constraint_space,
332     const NGBlockBreakToken* break_token,
333     const NGEarlyBreak* early_break) {
334   // Use the old layout code and synthesize a fragment.
335   if (!CanUseNewLayout())
336     return RunLegacyLayout(constraint_space);
337 
338   auto* block_flow = DynamicTo<LayoutBlockFlow>(box_);
339   if (RuntimeEnabledFeatures::TrackLayoutPassesPerBlockEnabled() && block_flow)
340     block_flow->IncrementLayoutPassCount();
341 
342   // The exclusion space internally is a pointer to a shared vector, and
343   // equality of exclusion spaces is performed using pointer comparison on this
344   // internal shared vector.
345   // In order for the caching logic to work correctly we need to set the
346   // pointer to the value previous shared vector.
347   if (const NGLayoutResult* previous_result = box_->GetCachedLayoutResult()) {
348     constraint_space.ExclusionSpace().PreInitialize(
349         previous_result->GetConstraintSpaceForCaching().ExclusionSpace());
350   }
351 
352   NGLayoutCacheStatus cache_status;
353   base::Optional<NGFragmentGeometry> fragment_geometry;
354   scoped_refptr<const NGLayoutResult> layout_result =
355       box_->CachedLayoutResult(constraint_space, break_token, early_break,
356                                &fragment_geometry, &cache_status);
357   if (cache_status == NGLayoutCacheStatus::kHit) {
358     DCHECK(layout_result);
359 
360     // We may have to update the margins on box_; we reuse the layout result
361     // even if a percentage margin may have changed.
362     if (UNLIKELY(Style().MayHaveMargin() && !constraint_space.IsTableCell()))
363       box_->SetMargin(ComputePhysicalMargins(constraint_space, Style()));
364 
365     UpdateShapeOutsideInfoIfNeeded(
366         *layout_result, constraint_space.PercentageResolutionInlineSize());
367 
368     // Even if we can reuse the result, we may still need to recalculate our
369     // overflow. TODO(crbug.com/919415): Explain why.
370     if (box_->NeedsLayoutOverflowRecalc())
371       box_->RecalcLayoutOverflow();
372 
373     // Return the cached result unless we're marked for layout. We may have
374     // added or removed scrollbars during overflow recalculation, which may have
375     // marked us for layout. In that case the cached result is unusable, and we
376     // need to re-lay out now.
377     if (!box_->NeedsLayout())
378       return layout_result;
379   }
380 
381   if (!fragment_geometry) {
382     fragment_geometry =
383         CalculateInitialFragmentGeometry(constraint_space, *this);
384   }
385 
386   TextAutosizer::NGLayoutScope text_autosizer_layout_scope(
387       box_, fragment_geometry->border_box_size.inline_size);
388 
389   PrepareForLayout();
390 
391   NGLayoutAlgorithmParams params(*this, *fragment_geometry, constraint_space,
392                                  break_token, early_break);
393 
394   // Try to perform "simplified" layout.
395   // TODO(crbug.com/992953): Add a simplified layout pass for custom layout.
396   if (cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout &&
397       block_flow && !GetFlowThread(block_flow) &&
398       // TODO(kojii): Enable simplified layout for fragment items.
399       !(block_flow->ChildrenInline() &&
400         RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) &&
401       !block_flow->IsLayoutNGCustom()) {
402     DCHECK(layout_result);
403 #if DCHECK_IS_ON()
404     scoped_refptr<const NGLayoutResult> previous_result = layout_result;
405 #endif
406 
407     // A child may have changed size while performing "simplified" layout (it
408     // may have gained or removed scrollbars, changing its size). In these
409     // cases "simplified" layout will return a null layout-result, indicating
410     // we need to perform a full layout.
411     layout_result = RunSimplifiedLayout(params, *layout_result);
412 
413 #if DCHECK_IS_ON()
414     if (layout_result) {
415       layout_result->CheckSameForSimplifiedLayout(
416           *previous_result, /* check_same_block_size */ false);
417     }
418 #endif
419   } else {
420     layout_result = nullptr;
421   }
422 
423   // Fragment geometry scrollbars are potentially size constrained, and cannot
424   // be used for comparison with their after layout size.
425   NGBoxStrut before_layout_scrollbars =
426       ComputeScrollbars(constraint_space, *this);
427   bool before_layout_intrinsic_logical_widths_dirty =
428       box_->IntrinsicLogicalWidthsDirty();
429 
430   if (!layout_result)
431     layout_result = LayoutWithAlgorithm(params);
432 
433   FinishLayout(block_flow, constraint_space, break_token, layout_result);
434 
435   // We may need to relayout if:
436   // - Our scrollbars have changed causing our size to change (shrink-to-fit)
437   //   or the available space to our children changing.
438   // - A child changed scrollbars causing our size to change (shrink-to-fit).
439   //
440   // This mirrors legacy code in PaintLayerScrollableArea::UpdateAfterLayout.
441   if ((before_layout_scrollbars !=
442        ComputeScrollbars(constraint_space, *this)) ||
443       (!before_layout_intrinsic_logical_widths_dirty &&
444        box_->IntrinsicLogicalWidthsDirty())) {
445     PaintLayerScrollableArea::FreezeScrollbarsScope freeze_scrollbars;
446 
447     // We need to clear any previous results when scrollbars change. For
448     // example - we may have stored a "measure" layout result which will be
449     // incorrect if we try and reuse it.
450     box_->ClearLayoutResults();
451 
452 #if DCHECK_IS_ON()
453     // Ensure turning on/off scrollbars only once at most, when we call
454     // |LayoutWithAlgorithm| recursively.
455     DEFINE_STATIC_LOCAL(HashSet<LayoutBox*>, scrollbar_changed, ());
456     DCHECK(scrollbar_changed.insert(box_).is_new_entry);
457 #endif
458 
459     // Scrollbar changes are hard to detect. Make sure everyone gets the
460     // message.
461     box_->SetNeedsLayout(layout_invalidation_reason::kScrollbarChanged,
462                          kMarkOnlyThis);
463 
464     fragment_geometry =
465         CalculateInitialFragmentGeometry(constraint_space, *this);
466     layout_result = LayoutWithAlgorithm(params);
467     FinishLayout(block_flow, constraint_space, break_token, layout_result);
468 
469 #if DCHECK_IS_ON()
470     scrollbar_changed.erase(box_);
471 #endif
472   }
473 
474   // We always need to update the ShapeOutsideInfo even if the layout is
475   // intermediate (e.g. called during a min/max pass).
476   //
477   // If a shape-outside float is present in an orthogonal flow, when
478   // calculating the min/max-size (by performing an intermediate layout), we
479   // might calculate this incorrectly, as the layout won't take into account the
480   // shape-outside area.
481   //
482   // TODO(ikilpatrick): This should be fixed by moving the shape-outside data
483   // to the NGLayoutResult, removing this "side" data-structure.
484   UpdateShapeOutsideInfoIfNeeded(
485       *layout_result, constraint_space.PercentageResolutionInlineSize());
486 
487   return layout_result;
488 }
489 
SimplifiedLayout()490 scoped_refptr<const NGLayoutResult> NGBlockNode::SimplifiedLayout() {
491   scoped_refptr<const NGLayoutResult> previous_result =
492       box_->GetCachedLayoutResult();
493   DCHECK(previous_result);
494 
495   if (!box_->NeedsLayout())
496     return previous_result;
497 
498   DCHECK(
499       box_->NeedsSimplifiedLayoutOnly() ||
500       box_->LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren));
501 
502   // Perform layout on ourselves using the previous constraint space.
503   const NGConstraintSpace space(
504       previous_result->GetConstraintSpaceForCaching());
505   scoped_refptr<const NGLayoutResult> result =
506       Layout(space, /* break_token */ nullptr);
507 
508   // If we changed size from performing "simplified" layout, we have
509   // added/removed scrollbars. Return null indicating to our parent that it
510   // needs to perform a full layout.
511   if (previous_result->PhysicalFragment().Size() !=
512       result->PhysicalFragment().Size())
513     return nullptr;
514 
515 #if DCHECK_IS_ON()
516   result->CheckSameForSimplifiedLayout(*previous_result);
517 #endif
518 
519   return result;
520 }
521 
522 scoped_refptr<const NGLayoutResult>
CachedLayoutResultForOutOfFlowPositioned(LogicalSize container_content_size) const523 NGBlockNode::CachedLayoutResultForOutOfFlowPositioned(
524     LogicalSize container_content_size) const {
525   DCHECK(IsOutOfFlowPositioned());
526 
527   if (box_->NeedsLayout())
528     return nullptr;
529 
530   const NGLayoutResult* cached_layout_result = box_->GetCachedLayoutResult();
531   if (!cached_layout_result)
532     return nullptr;
533 
534   // The containing-block may have borders/scrollbars which might change
535   // between passes affecting the final position.
536   if (!cached_layout_result->CanUseOutOfFlowPositionedFirstTierCache())
537     return nullptr;
538 
539   // TODO(layout-dev): There are potentially more cases where we can reuse this
540   // layout result.
541   // E.g. when we have a fixed-length top position constraint (top: 5px), we
542   // are in the correct writing mode (htb-ltr), and we have a fixed width.
543   const NGConstraintSpace& space =
544       cached_layout_result->GetConstraintSpaceForCaching();
545   if (space.PercentageResolutionSize() != container_content_size)
546     return nullptr;
547 
548   // We currently don't keep the static-position around to determine if it is
549   // the same as the previous layout pass. As such, only reuse the result when
550   // we know it doesn't depend on the static-position.
551   //
552   // TODO(layout-dev): We might be able to determine what the previous
553   // static-position was based on |NGLayoutResult::OutOfFlowPositionedOffset|.
554   bool depends_on_static_position =
555       (Style().Left().IsAuto() && Style().Right().IsAuto()) ||
556       (Style().Top().IsAuto() && Style().Bottom().IsAuto());
557 
558   if (depends_on_static_position)
559     return nullptr;
560 
561   return cached_layout_result;
562 }
563 
PrepareForLayout()564 void NGBlockNode::PrepareForLayout() {
565   auto* block = DynamicTo<LayoutBlock>(box_);
566   if (block && block->HasOverflowClip()) {
567     DCHECK(block->GetScrollableArea());
568     if (block->GetScrollableArea()->ShouldPerformScrollAnchoring())
569       block->GetScrollableArea()->GetScrollAnchor()->NotifyBeforeLayout();
570   }
571 
572   // TODO(layoutng) Can UpdateMarkerTextIfNeeded call be moved
573   // somewhere else? List items need up-to-date markers before layout.
574   if (IsListItem())
575     ToLayoutNGListItem(box_)->UpdateMarkerTextIfNeeded();
576 }
577 
FinishLayout(LayoutBlockFlow * block_flow,const NGConstraintSpace & constraint_space,const NGBlockBreakToken * break_token,scoped_refptr<const NGLayoutResult> layout_result)578 void NGBlockNode::FinishLayout(
579     LayoutBlockFlow* block_flow,
580     const NGConstraintSpace& constraint_space,
581     const NGBlockBreakToken* break_token,
582     scoped_refptr<const NGLayoutResult> layout_result) {
583   // If we abort layout and don't clear the cached layout-result, we can end
584   // up in a state where the layout-object tree doesn't match fragment tree
585   // referenced by this layout-result.
586   if (layout_result->Status() != NGLayoutResult::kSuccess) {
587     box_->ClearLayoutResults();
588     return;
589   }
590 
591   // Add all layout results (and fragments) generated from a node to a list in
592   // the layout object. Some extra care is required to correctly overwrite
593   // intermediate layout results: The sequence number of an incoming break token
594   // corresponds with the fragment index in the layout object (off by 1,
595   // though). When writing back a layout result, we remove any fragments in the
596   // layout box at higher indices than that of the one we're writing back.
597   const auto& physical_fragment =
598       To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment());
599   wtf_size_t fragment_index = 0;
600   if (break_token && !break_token->IsBreakBefore())
601     fragment_index = break_token->SequenceNumber() + 1;
602 
603   if (layout_result->IsSingleUse())
604     box_->AddLayoutResult(layout_result, fragment_index);
605   else
606     box_->SetCachedLayoutResult(layout_result);
607 
608   if (block_flow) {
609     auto* child = GetLayoutObjectForFirstChildNode(block_flow);
610     bool has_inline_children =
611         child && AreNGBlockFlowChildrenInline(block_flow);
612 
613     // Don't consider display-locked objects as having any children.
614     if (has_inline_children && box_->LayoutBlockedByDisplayLock(
615                                    DisplayLockLifecycleTarget::kChildren)) {
616       has_inline_children = false;
617       // It could be the case that our children are already clean at the time
618       // the lock was acquired. This means that |box_| self dirty bits might be
619       // set, and child dirty bits might not be. We clear the self bits since we
620       // want to treat the |box_| as layout clean, even when locked. However,
621       // here we also skip appending paint fragments for inline children. This
622       // means that we potentially can end up in a situation where |box_| is
623       // completely layout clean, but its inline children didn't append the
624       // paint fragments to it, which causes problems. In order to solve this,
625       // we set a child dirty bit on |box_| ensuring that when the lock
626       // is removed, or update is forced, we will visit this box again and
627       // properly create the paint fragments. See https://crbug.com/962614.
628       box_->SetChildNeedsLayout(kMarkOnlyThis);
629     }
630 
631     if (has_inline_children) {
632       if (!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled()) {
633         CopyFragmentDataToLayoutBoxForInlineChildren(
634             physical_fragment, physical_fragment.Size().width,
635             Style().IsFlippedBlocksWritingMode());
636         block_flow->SetPaintFragment(break_token, &physical_fragment);
637       } else {
638         CopyFragmentDataToLayoutBoxForInlineChildren(physical_fragment);
639       }
640     } else {
641       // We still need to clear paint fragments in case it had inline children,
642       // and thus had NGPaintFragment.
643       block_flow->ClearNGInlineNodeData();
644       block_flow->SetPaintFragment(break_token, nullptr);
645     }
646   }
647 
648   CopyFragmentDataToLayoutBox(constraint_space, *layout_result, break_token);
649 }
650 
ComputeMinMaxSizes(WritingMode container_writing_mode,const MinMaxSizesInput & input,const NGConstraintSpace * constraint_space)651 MinMaxSizes NGBlockNode::ComputeMinMaxSizes(
652     WritingMode container_writing_mode,
653     const MinMaxSizesInput& input,
654     const NGConstraintSpace* constraint_space) {
655   // TODO(layoutng) Can UpdateMarkerTextIfNeeded call be moved
656   // somewhere else? List items need up-to-date markers before layout.
657   if (IsListItem())
658     ToLayoutNGListItem(box_)->UpdateMarkerTextIfNeeded();
659 
660   bool is_orthogonal_flow_root =
661       !IsParallelWritingMode(container_writing_mode, Style().GetWritingMode());
662 
663   if (CanUseCachedIntrinsicInlineSizes(input, *box_))
664     return box_->IntrinsicLogicalWidths();
665 
666   box_->SetIntrinsicLogicalWidthsDirty();
667 
668   MinMaxSizes sizes;
669   // If we're orthogonal, we have to run layout to compute the sizes. However,
670   // if we're outside of layout, we can't do that. This can happen on Mac.
671   if ((!CanUseNewLayout() && !is_orthogonal_flow_root) ||
672       (is_orthogonal_flow_root && !box_->GetFrameView()->IsInPerformLayout()))
673     return ComputeMinMaxSizesFromLegacy(input);
674 
675   NGConstraintSpace zero_constraint_space =
676       CreateConstraintSpaceBuilderForMinMax(*this).ToConstraintSpace();
677 
678   if (!constraint_space) {
679     // Using the zero-sized constraint space when measuring for an orthogonal
680     // flow root isn't going to give the right result.
681     DCHECK(!is_orthogonal_flow_root);
682 
683     constraint_space = &zero_constraint_space;
684   }
685 
686   if (is_orthogonal_flow_root || !CanUseNewLayout()) {
687     scoped_refptr<const NGLayoutResult> layout_result =
688         Layout(*constraint_space);
689     DCHECK_EQ(layout_result->Status(), NGLayoutResult::kSuccess);
690     NGBoxFragment fragment(
691         container_writing_mode,
692         TextDirection::kLtr,  // irrelevant here
693         To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment()));
694     sizes.min_size = sizes.max_size = fragment.Size().inline_size;
695     return sizes;
696   }
697 
698   NGFragmentGeometry fragment_geometry =
699       CalculateInitialMinMaxFragmentGeometry(*constraint_space, *this);
700   base::Optional<MinMaxSizes> maybe_sizes = ComputeMinMaxSizesWithAlgorithm(
701       NGLayoutAlgorithmParams(*this, fragment_geometry, *constraint_space),
702       input);
703 
704   if (maybe_sizes.has_value()) {
705     auto* html_marquee_element = DynamicTo<HTMLMarqueeElement>(box_->GetNode());
706     if (UNLIKELY(html_marquee_element && html_marquee_element->IsHorizontal()))
707       maybe_sizes->min_size = LayoutUnit();
708     else if (UNLIKELY(IsA<HTMLSelectElement>(box_->GetNode()) ||
709                       (IsA<HTMLInputElement>(box_->GetNode()) &&
710                        To<HTMLInputElement>(box_->GetNode())->type() ==
711                            input_type_names::kFile)) &&
712              Style().LogicalWidth().IsPercentOrCalc())
713       maybe_sizes->min_size = LayoutUnit();
714     box_->SetIntrinsicLogicalWidthsFromNG(
715         *maybe_sizes, input.percentage_resolution_block_size);
716     return *maybe_sizes;
717   }
718 
719   if (!box_->GetFrameView()->IsInPerformLayout()) {
720     // We can't synthesize these using Layout() if we're not in PerformLayout.
721     // This situation can happen on mac. Fall back to legacy instead.
722     return ComputeMinMaxSizesFromLegacy(input);
723   }
724 
725   // Have to synthesize this value.
726   scoped_refptr<const NGLayoutResult> layout_result =
727       Layout(zero_constraint_space);
728   sizes.min_size =
729       NGFragment(container_writing_mode, layout_result->PhysicalFragment())
730           .InlineSize();
731 
732   // Now, redo with infinite space for max_content
733   NGConstraintSpaceBuilder builder =
734       CreateConstraintSpaceBuilderForMinMax(*this);
735   builder.SetAvailableSize({LayoutUnit::Max(), LayoutUnit()});
736   builder.SetPercentageResolutionSize({LayoutUnit(), LayoutUnit()});
737   NGConstraintSpace infinite_constraint_space = builder.ToConstraintSpace();
738 
739   layout_result = Layout(infinite_constraint_space);
740   NGBoxFragment max_fragment(
741       container_writing_mode,
742       TextDirection::kLtr,  // irrelevant here
743       To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment()));
744   sizes.max_size = max_fragment.Size().inline_size;
745   return sizes;
746 }
747 
ComputeMinMaxSizesFromLegacy(const MinMaxSizesInput & input) const748 MinMaxSizes NGBlockNode::ComputeMinMaxSizesFromLegacy(
749     const MinMaxSizesInput& input) const {
750   bool needs_size_reset = false;
751   if (!box_->HasOverrideContainingBlockContentLogicalHeight()) {
752     box_->SetOverrideContainingBlockContentLogicalHeight(
753         input.percentage_resolution_block_size);
754     needs_size_reset = true;
755   }
756 
757   // Tables don't calculate their min/max content contribution the same way as
758   // other layout nodes. This is because width/min-width/etc have a different
759   // meaning for tables.
760   //
761   // Due to this the min/max content contribution is their min/max content size.
762   MinMaxSizes sizes = box_->IsTable() ? box_->PreferredLogicalWidths()
763                                       : box_->IntrinsicLogicalWidths();
764 
765   if (needs_size_reset)
766     box_->ClearOverrideContainingBlockContentSize();
767 
768   return sizes;
769 }
770 
NextSibling() const771 NGLayoutInputNode NGBlockNode::NextSibling() const {
772   LayoutObject* next_sibling = GetLayoutObjectForNextSiblingNode(box_);
773 
774   // We may have some LayoutInline(s) still within the tree (due to treating
775   // inline-level floats and/or OOF-positioned nodes as block-level), we need
776   // to skip them and clear layout.
777   while (next_sibling && next_sibling->IsInline()) {
778     // TODO(layout-dev): Clearing needs-layout within this accessor is an
779     // unexpected side-effect. There may be additional invalidations that need
780     // to be performed.
781     DCHECK(next_sibling->IsText());
782     next_sibling->ClearNeedsLayout();
783     next_sibling = next_sibling->NextSibling();
784   }
785 
786   if (!next_sibling)
787     return nullptr;
788 
789   return NGBlockNode(ToLayoutBox(next_sibling));
790 }
791 
FirstChild() const792 NGLayoutInputNode NGBlockNode::FirstChild() const {
793   auto* block = To<LayoutBlock>(box_);
794   auto* child = GetLayoutObjectForFirstChildNode(block);
795   if (!child)
796     return nullptr;
797   if (!AreNGBlockFlowChildrenInline(block))
798     return NGBlockNode(ToLayoutBox(child));
799 
800   NGInlineNode inline_node(To<LayoutBlockFlow>(block));
801   if (!inline_node.IsBlockLevel())
802     return inline_node;
803 
804   // At this point we have a node which is empty or only has floats and
805   // OOF-positioned nodes. We treat all children as block-level, even though
806   // they are within a inline-level LayoutBlockFlow.
807 
808   // We may have some LayoutInline(s) still within the tree (due to treating
809   // inline-level floats and/or OOF-positioned nodes as block-level), we need
810   // to skip them and clear layout.
811   while (child && child->IsInline()) {
812     // TODO(layout-dev): Clearing needs-layout within this accessor is an
813     // unexpected side-effect. There may be additional invalidations that need
814     // to be performed.
815     DCHECK(child->IsText());
816     child->ClearNeedsLayout();
817     child = child->NextSibling();
818   }
819 
820   if (!child)
821     return nullptr;
822 
823   DCHECK(child->IsFloatingOrOutOfFlowPositioned());
824   return NGBlockNode(ToLayoutBox(child));
825 }
826 
GetRenderedLegend() const827 NGBlockNode NGBlockNode::GetRenderedLegend() const {
828   if (!IsFieldsetContainer())
829     return nullptr;
830   return NGBlockNode(LayoutFieldset::FindInFlowLegend(*To<LayoutBlock>(box_)));
831 }
832 
GetFieldsetContent() const833 NGBlockNode NGBlockNode::GetFieldsetContent() const {
834   if (!IsFieldsetContainer())
835     return nullptr;
836   auto* child = GetLayoutObjectForFirstChildNode(To<LayoutBlock>(box_));
837   if (!child)
838     return nullptr;
839   return NGBlockNode(ToLayoutBox(child));
840 }
841 
CanUseNewLayout(const LayoutBox & box)842 bool NGBlockNode::CanUseNewLayout(const LayoutBox& box) {
843   DCHECK(RuntimeEnabledFeatures::LayoutNGEnabled());
844   if (box.ForceLegacyLayout())
845     return false;
846   return box.IsLayoutNGMixin();
847 }
848 
CanUseNewLayout() const849 bool NGBlockNode::CanUseNewLayout() const {
850   return CanUseNewLayout(*box_);
851 }
852 
ToString() const853 String NGBlockNode::ToString() const {
854   return String::Format("NGBlockNode: '%s'",
855                         GetLayoutBox()->DebugName().Ascii().c_str());
856 }
857 
CopyFragmentDataToLayoutBox(const NGConstraintSpace & constraint_space,const NGLayoutResult & layout_result,const NGBlockBreakToken * previous_break_token)858 void NGBlockNode::CopyFragmentDataToLayoutBox(
859     const NGConstraintSpace& constraint_space,
860     const NGLayoutResult& layout_result,
861     const NGBlockBreakToken* previous_break_token) {
862   const auto& physical_fragment =
863       To<NGPhysicalBoxFragment>(layout_result.PhysicalFragment());
864 
865   NGBoxFragment fragment(constraint_space.GetWritingMode(),
866                          constraint_space.Direction(), physical_fragment);
867   LogicalSize fragment_logical_size = fragment.Size();
868   // For each fragment we process, we'll accumulate the logical height and
869   // logical intrinsic content box height. We reset it at the first fragment,
870   // and accumulate at each method call for fragments belonging to the same
871   // layout object. Logical width will only be set at the first fragment and is
872   // expected to remain the same throughout all subsequent fragments, since
873   // legacy layout doesn't support non-uniform fragmentainer widths.
874   LayoutUnit intrinsic_content_logical_height;
875   if (LIKELY(physical_fragment.IsFirstForNode())) {
876     box_->SetSize(LayoutSize(physical_fragment.Size().width,
877                              physical_fragment.Size().height));
878   } else {
879     DCHECK_EQ(box_->LogicalWidth(), fragment_logical_size.inline_size)
880         << "Variable fragment inline size not supported";
881     LayoutUnit logical_height = fragment_logical_size.block_size;
882     if (previous_break_token)
883       logical_height += previous_break_token->ConsumedBlockSize();
884     box_->SetLogicalHeight(logical_height);
885     intrinsic_content_logical_height = box_->IntrinsicContentLogicalHeight();
886   }
887 
888   intrinsic_content_logical_height += layout_result.IntrinsicBlockSize();
889 
890   NGBoxStrut borders = fragment.Borders();
891   NGBoxStrut scrollbars = ComputeScrollbars(constraint_space, *this);
892   NGBoxStrut padding = fragment.Padding();
893   NGBoxStrut border_scrollbar_padding = borders + scrollbars + padding;
894   bool is_last_fragment = !physical_fragment.BreakToken();
895 
896   if (LIKELY(is_last_fragment))
897     intrinsic_content_logical_height -= border_scrollbar_padding.BlockSum();
898   if (!constraint_space.IsFixedBlockSize()) {
899     // If we had a fixed block size, our children will have sized themselves
900     // relative to the fixed size, which would make our intrinsic size
901     // incorrect (too big).
902     box_->SetIntrinsicContentLogicalHeight(intrinsic_content_logical_height);
903   }
904 
905   // TODO(mstensho): This should always be done by the parent algorithm, since
906   // we may have auto margins, which only the parent is able to resolve. Remove
907   // the following line when all layout modes do this properly.
908   if (UNLIKELY(box_->IsTableCell())) {
909     // Table-cell margins compute to zero.
910     box_->SetMargin(NGPhysicalBoxStrut());
911   } else {
912     box_->SetMargin(ComputePhysicalMargins(constraint_space, Style()));
913   }
914 
915   auto* block_flow = DynamicTo<LayoutBlockFlow>(box_);
916   LayoutMultiColumnFlowThread* flow_thread = GetFlowThread(block_flow);
917 
918   // Position the children inside the box. We skip this if display-lock prevents
919   // child layout.
920   if (!LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren)) {
921     if (UNLIKELY(flow_thread))
922       PlaceChildrenInFlowThread(physical_fragment);
923     else
924       PlaceChildrenInLayoutBox(physical_fragment, previous_break_token);
925   }
926 
927   LayoutBlock* block = DynamicTo<LayoutBlock>(box_);
928   bool needs_full_invalidation = false;
929   if (LIKELY(block && is_last_fragment)) {
930     LayoutUnit overflow_block_size = layout_result.OverflowBlockSize();
931     if (UNLIKELY(previous_break_token))
932       overflow_block_size += previous_break_token->ConsumedBlockSize();
933 
934 #if DCHECK_IS_ON()
935     block->CheckPositionedObjectsNeedLayout();
936 #endif
937 
938     if (UNLIKELY(flow_thread)) {
939       UpdateLegacyMultiColumnFlowThread(*this, flow_thread, constraint_space,
940                                         physical_fragment);
941 
942       // Issue full invalidation, in case the number of column rules have
943       // changed.
944       if (Style().HasColumnRule())
945         needs_full_invalidation = true;
946     }
947 
948     BoxLayoutExtraInput input(*block);
949     SetupBoxLayoutExtraInput(constraint_space, *block, &input);
950 
951     // |ComputeOverflow()| below calls |AddVisualOverflowFromChildren()|, which
952     // computes visual overflow from |RootInlineBox| if |ChildrenInline()|
953     block->SetNeedsOverflowRecalc(
954         LayoutObject::OverflowRecalcType::kOnlyVisualOverflowRecalc);
955     block->ComputeLayoutOverflow(overflow_block_size - borders.block_end -
956                                  scrollbars.block_end);
957   }
958 
959   box_->UpdateAfterLayout();
960 
961   if (needs_full_invalidation)
962     box_->ClearNeedsLayoutWithFullPaintInvalidation();
963   else
964     box_->ClearNeedsLayout();
965 
966   // Overflow computation depends on this being set.
967   if (LIKELY(block_flow))
968     block_flow->SetIsSelfCollapsingFromNG(layout_result.IsSelfCollapsing());
969 
970   // We should notify the display lock that we've done layout on self, and if
971   // it's not blocked, on children.
972   if (auto* context = box_->GetDisplayLockContext()) {
973     context->DidLayout(DisplayLockLifecycleTarget::kSelf);
974     if (!LayoutBlockedByDisplayLock(DisplayLockLifecycleTarget::kChildren))
975       context->DidLayout(DisplayLockLifecycleTarget::kChildren);
976   }
977 }
978 
PlaceChildrenInLayoutBox(const NGPhysicalBoxFragment & physical_fragment,const NGBlockBreakToken * previous_break_token)979 void NGBlockNode::PlaceChildrenInLayoutBox(
980     const NGPhysicalBoxFragment& physical_fragment,
981     const NGBlockBreakToken* previous_break_token) {
982   LayoutBox* rendered_legend = nullptr;
983   for (const auto& child_fragment : physical_fragment.Children()) {
984     // Skip any line-boxes we have as children, this is handled within
985     // NGInlineNode at the moment.
986     if (!child_fragment->IsBox())
987       continue;
988 
989     const auto& box_fragment = *To<NGPhysicalBoxFragment>(child_fragment.get());
990     if (box_fragment.IsFirstForNode()) {
991       if (box_fragment.IsRenderedLegend())
992         rendered_legend = ToLayoutBox(box_fragment.GetMutableLayoutObject());
993       CopyChildFragmentPosition(box_fragment, child_fragment.offset,
994                                 physical_fragment, previous_break_token);
995     }
996   }
997 
998   if (rendered_legend) {
999     // The rendered legend is a child of the the anonymous fieldset content
1000     // child wrapper object on the legacy side. LayoutNG, on the other hand,
1001     // generates a fragment for the rendered legend as a direct child of the
1002     // fieldset container fragment (as a *sibling* preceding the anonymous
1003     // fieldset content wrapper). Now that we have positioned the anonymous
1004     // wrapper, we're ready to compensate for this discrepancy. See
1005     // LayoutNGFieldset for more details.
1006     LayoutBlock* content_wrapper = rendered_legend->ContainingBlock();
1007     DCHECK(content_wrapper->IsAnonymous());
1008     DCHECK(IsA<HTMLFieldSetElement>(content_wrapper->Parent()->GetNode()));
1009     LayoutPoint location = rendered_legend->Location();
1010     location -= content_wrapper->Location();
1011     rendered_legend->SetLocationAndUpdateOverflowControlsIfNeeded(location);
1012   }
1013 }
1014 
PlaceChildrenInFlowThread(const NGPhysicalBoxFragment & physical_fragment)1015 void NGBlockNode::PlaceChildrenInFlowThread(
1016     const NGPhysicalBoxFragment& physical_fragment) {
1017   const NGBlockBreakToken* previous_break_token = nullptr;
1018   for (const auto& child : physical_fragment.Children()) {
1019     const LayoutObject* child_object = child->GetLayoutObject();
1020     if (child_object && child_object != box_) {
1021       DCHECK(child_object->IsColumnSpanAll());
1022       CopyChildFragmentPosition(To<NGPhysicalBoxFragment>(*child), child.offset,
1023                                 physical_fragment);
1024       continue;
1025     }
1026     // Each anonymous child of a multicol container constitutes one column.
1027     // Position each child fragment in the first column that they occur,
1028     // relatively to the block-start of the flow thread.
1029     const auto* column = To<NGPhysicalBoxFragment>(child.get());
1030     PlaceChildrenInLayoutBox(*column, previous_break_token);
1031     previous_break_token = To<NGBlockBreakToken>(column->BreakToken());
1032   }
1033 }
1034 
1035 // Copies data back to the legacy layout tree for a given child fragment.
CopyChildFragmentPosition(const NGPhysicalBoxFragment & child_fragment,PhysicalOffset offset,const NGPhysicalBoxFragment & container_fragment,const NGBlockBreakToken * previous_container_break_token)1036 void NGBlockNode::CopyChildFragmentPosition(
1037     const NGPhysicalBoxFragment& child_fragment,
1038     PhysicalOffset offset,
1039     const NGPhysicalBoxFragment& container_fragment,
1040     const NGBlockBreakToken* previous_container_break_token) {
1041   LayoutBox* layout_box = ToLayoutBox(child_fragment.GetMutableLayoutObject());
1042   if (!layout_box)
1043     return;
1044 
1045   DCHECK(layout_box->Parent()) << "Should be called on children only.";
1046 
1047   if (UNLIKELY(container_fragment.Style().IsFlippedBlocksWritingMode())) {
1048     // Move the physical offset to the right side of the child fragment,
1049     // relative to the right edge of the container fragment. This is the
1050     // block-start offset in vertical-rl, and the legacy engine expects always
1051     // expects the block offset to be relative to block-start.
1052     offset.left = container_fragment.Size().width - offset.left -
1053                   child_fragment.Size().width;
1054   }
1055 
1056   if (UNLIKELY(previous_container_break_token)) {
1057     // Add the amount of block-size previously (in previous fragmentainers)
1058     // consumed by the container fragment. This will map the child's offset
1059     // nicely into the flow thread coordinate system used by the legacy engine.
1060     LayoutUnit consumed = previous_container_break_token->ConsumedBlockSize();
1061     if (container_fragment.Style().IsHorizontalWritingMode())
1062       offset.top += consumed;
1063     else
1064       offset.left += consumed;
1065   }
1066 
1067   layout_box->SetLocationAndUpdateOverflowControlsIfNeeded(
1068       offset.ToLayoutPoint());
1069 }
1070 
1071 // For inline children, NG painters handles fragments directly, but there are
1072 // some cases where we need to copy data to the LayoutObject tree. This function
1073 // handles such cases.
CopyFragmentDataToLayoutBoxForInlineChildren(const NGPhysicalContainerFragment & container,LayoutUnit initial_container_width,bool initial_container_is_flipped,PhysicalOffset offset)1074 void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren(
1075     const NGPhysicalContainerFragment& container,
1076     LayoutUnit initial_container_width,
1077     bool initial_container_is_flipped,
1078     PhysicalOffset offset) {
1079   DCHECK(!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled());
1080   for (const auto& child : container.Children()) {
1081     if (child->IsContainer()) {
1082       PhysicalOffset child_offset = offset + child.Offset();
1083 
1084       // Replaced elements and inline blocks need Location() set relative to
1085       // their block container.
1086       LayoutObject* layout_object = child->GetMutableLayoutObject();
1087       if (layout_object && layout_object->IsBox()) {
1088         LayoutBox& layout_box = ToLayoutBox(*layout_object);
1089         PhysicalOffset maybe_flipped_offset = child_offset;
1090         if (initial_container_is_flipped) {
1091           maybe_flipped_offset.left = initial_container_width -
1092                                       child->Size().width -
1093                                       maybe_flipped_offset.left;
1094         }
1095         layout_box.SetLocationAndUpdateOverflowControlsIfNeeded(
1096             maybe_flipped_offset.ToLayoutPoint());
1097       }
1098 
1099       // Legacy compatibility. This flag is used in paint layer for
1100       // invalidation.
1101       if (layout_object && layout_object->IsLayoutInline() &&
1102           layout_object->StyleRef().HasOutline() &&
1103           !layout_object->IsElementContinuation() &&
1104           ToLayoutInline(layout_object)->Continuation()) {
1105         box_->SetContainsInlineWithOutlineAndContinuation(true);
1106       }
1107 
1108       // The Location() of inline LayoutObject is relative to the
1109       // LayoutBlockFlow. If |child| establishes a new block formatting context,
1110       // it also creates another inline formatting context. Do not copy to its
1111       // descendants in this case.
1112       if (!child->IsFormattingContextRoot()) {
1113         CopyFragmentDataToLayoutBoxForInlineChildren(
1114             To<NGPhysicalContainerFragment>(*child), initial_container_width,
1115             initial_container_is_flipped, child_offset);
1116       }
1117     }
1118   }
1119 }
1120 
CopyFragmentDataToLayoutBoxForInlineChildren(const NGPhysicalBoxFragment & container)1121 void NGBlockNode::CopyFragmentDataToLayoutBoxForInlineChildren(
1122     const NGPhysicalBoxFragment& container) {
1123   DCHECK(RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled());
1124   const NGFragmentItems* items = container.Items();
1125   if (!items)
1126     return;
1127   bool initial_container_is_flipped = Style().IsFlippedBlocksWritingMode();
1128   for (NGInlineCursor cursor(*items); cursor; cursor.MoveToNext()) {
1129     if (const NGPhysicalBoxFragment* child = cursor.Current().BoxFragment()) {
1130       // Replaced elements and inline blocks need Location() set relative to
1131       // their block container.
1132       LayoutObject* layout_object = child->GetMutableLayoutObject();
1133       if (!layout_object)
1134         continue;
1135       if (LayoutBox* layout_box = ToLayoutBoxOrNull(layout_object)) {
1136         PhysicalOffset maybe_flipped_offset =
1137             cursor.Current().OffsetInContainerBlock();
1138         if (initial_container_is_flipped) {
1139           maybe_flipped_offset.left = container.Size().width -
1140                                       child->Size().width -
1141                                       maybe_flipped_offset.left;
1142         }
1143         layout_box->SetLocationAndUpdateOverflowControlsIfNeeded(
1144             maybe_flipped_offset.ToLayoutPoint());
1145         continue;
1146       }
1147 
1148       // Legacy compatibility. This flag is used in paint layer for
1149       // invalidation.
1150       if (LayoutInline* layout_inline = ToLayoutInlineOrNull(layout_object)) {
1151         if (layout_inline->StyleRef().HasOutline() &&
1152             !layout_inline->IsElementContinuation() &&
1153             layout_inline->Continuation()) {
1154           box_->SetContainsInlineWithOutlineAndContinuation(true);
1155         }
1156       }
1157     }
1158   }
1159 }
1160 
IsInlineFormattingContextRoot() const1161 bool NGBlockNode::IsInlineFormattingContextRoot() const {
1162   if (const auto* block = DynamicTo<LayoutBlockFlow>(box_))
1163     return AreNGBlockFlowChildrenInline(block) && FirstChild().IsInline();
1164   return false;
1165 }
1166 
IsInlineLevel() const1167 bool NGBlockNode::IsInlineLevel() const {
1168   return GetLayoutBox()->IsInline();
1169 }
1170 
IsAtomicInlineLevel() const1171 bool NGBlockNode::IsAtomicInlineLevel() const {
1172   // LayoutObject::IsAtomicInlineLevel() returns true for e.g., <img
1173   // style="display: block">. Check IsInline() as well.
1174   return GetLayoutBox()->IsAtomicInlineLevel() && GetLayoutBox()->IsInline();
1175 }
1176 
HasAspectRatio() const1177 bool NGBlockNode::HasAspectRatio() const {
1178   LayoutBox* layout_object = GetLayoutBox();
1179   if (!layout_object->IsImage() && !IsA<LayoutVideo>(layout_object) &&
1180       !layout_object->IsCanvas())
1181     return false;
1182 
1183   // Retrieving this and throwing it away is wasteful. We could make this method
1184   // return Optional<LogicalSize> that returns the aspect_ratio if there is one.
1185   return !GetAspectRatio().IsEmpty();
1186 }
1187 
GetAspectRatio() const1188 LogicalSize NGBlockNode::GetAspectRatio() const {
1189   base::Optional<LayoutUnit> computed_inline_size;
1190   base::Optional<LayoutUnit> computed_block_size;
1191   GetOverrideIntrinsicSize(&computed_inline_size, &computed_block_size);
1192   if (computed_inline_size && computed_block_size)
1193     return LogicalSize(*computed_inline_size, *computed_block_size);
1194 
1195   IntrinsicSizingInfo legacy_sizing_info;
1196   ToLayoutReplaced(box_)->ComputeIntrinsicSizingInfo(legacy_sizing_info);
1197   return LogicalSize(LayoutUnit(legacy_sizing_info.aspect_ratio.Width()),
1198                      LayoutUnit(legacy_sizing_info.aspect_ratio.Height()));
1199 }
1200 
UseLogicalBottomMarginEdgeForInlineBlockBaseline() const1201 bool NGBlockNode::UseLogicalBottomMarginEdgeForInlineBlockBaseline() const {
1202   auto* layout_box = DynamicTo<LayoutBlock>(GetLayoutBox());
1203   return layout_box &&
1204          layout_box->UseLogicalBottomMarginEdgeForInlineBlockBaseline();
1205 }
1206 
IsCustomLayoutLoaded() const1207 bool NGBlockNode::IsCustomLayoutLoaded() const {
1208   DCHECK(box_->IsLayoutNGCustom());
1209   return To<LayoutNGCustom>(box_)->IsLoaded();
1210 }
1211 
LayoutAtomicInline(const NGConstraintSpace & parent_constraint_space,const ComputedStyle & parent_style,bool use_first_line_style,NGBaselineAlgorithmType baseline_algorithm_type)1212 scoped_refptr<const NGLayoutResult> NGBlockNode::LayoutAtomicInline(
1213     const NGConstraintSpace& parent_constraint_space,
1214     const ComputedStyle& parent_style,
1215     bool use_first_line_style,
1216     NGBaselineAlgorithmType baseline_algorithm_type) {
1217   NGConstraintSpaceBuilder builder(
1218       parent_constraint_space, Style().GetWritingMode(), /* is_new_fc */ true);
1219   SetOrthogonalFallbackInlineSizeIfNeeded(parent_style, *this, &builder);
1220 
1221   builder.SetIsPaintedAtomically(true);
1222   builder.SetUseFirstLineStyle(use_first_line_style);
1223 
1224   builder.SetNeedsBaseline(true);
1225   builder.SetBaselineAlgorithmType(baseline_algorithm_type);
1226 
1227   builder.SetIsShrinkToFit(Style().LogicalWidth().IsAuto());
1228   builder.SetAvailableSize(parent_constraint_space.AvailableSize());
1229   builder.SetPercentageResolutionSize(
1230       parent_constraint_space.PercentageResolutionSize());
1231   builder.SetReplacedPercentageResolutionSize(
1232       parent_constraint_space.ReplacedPercentageResolutionSize());
1233   builder.SetTextDirection(Style().Direction());
1234   NGConstraintSpace constraint_space = builder.ToConstraintSpace();
1235   scoped_refptr<const NGLayoutResult> result = Layout(constraint_space);
1236   // TODO(kojii): Investigate why ClearNeedsLayout() isn't called automatically
1237   // when it's being laid out.
1238   GetLayoutBox()->ClearNeedsLayout();
1239   return result;
1240 }
1241 
RunLegacyLayout(const NGConstraintSpace & constraint_space)1242 scoped_refptr<const NGLayoutResult> NGBlockNode::RunLegacyLayout(
1243     const NGConstraintSpace& constraint_space) {
1244   // This is an exit-point from LayoutNG to the legacy engine. This means that
1245   // we need to be at a formatting context boundary, since NG and legacy don't
1246   // cooperate on e.g. margin collapsing.
1247   DCHECK(!box_->IsLayoutBlock() ||
1248          To<LayoutBlock>(box_)->CreatesNewFormattingContext());
1249 
1250   // We cannot enter legacy layout for something fragmentable if we're inside an
1251   // NG block fragmentation context. LayoutNG and legacy block fragmentation
1252   // cannot cooperate within the same fragmentation context.
1253   DCHECK(!constraint_space.HasBlockFragmentation() ||
1254          box_->GetPaginationBreakability() == LayoutBox::kForbidBreaks);
1255 
1256   scoped_refptr<const NGLayoutResult> layout_result =
1257       box_->GetCachedLayoutResult();
1258 
1259   // We need to force a layout on the child if the constraint space given will
1260   // change the layout.
1261   bool needs_force_relayout =
1262       layout_result &&
1263       !MaySkipLegacyLayout(*this, *layout_result, constraint_space);
1264 
1265   if (box_->NeedsLayout() || !layout_result || needs_force_relayout) {
1266     BoxLayoutExtraInput input(*box_);
1267     WritingMode writing_mode = Style().GetWritingMode();
1268 
1269     SetupBoxLayoutExtraInput(constraint_space, *box_, &input);
1270     box_->ComputeAndSetBlockDirectionMargins(box_->ContainingBlock());
1271 
1272     // Using |LayoutObject::LayoutIfNeeded| save us a little bit of overhead,
1273     // compared to |LayoutObject::ForceLayout|.
1274     DCHECK(!box_->IsLayoutNGMixin());
1275     bool needed_layout = box_->NeedsLayout();
1276     if (box_->NeedsLayout() && !needs_force_relayout)
1277       box_->LayoutIfNeeded();
1278     else
1279       box_->ForceLayout();
1280 
1281     // Synthesize a new layout result.
1282     NGFragmentGeometry fragment_geometry;
1283     fragment_geometry.border_box_size = {box_->LogicalWidth(),
1284                                          box_->LogicalHeight()};
1285     fragment_geometry.border = {box_->BorderStart(), box_->BorderEnd(),
1286                                 box_->BorderBefore(), box_->BorderAfter()};
1287     fragment_geometry.scrollbar = ComputeScrollbars(constraint_space, *this);
1288     fragment_geometry.padding = {box_->PaddingStart(), box_->PaddingEnd(),
1289                                  box_->PaddingBefore(), box_->PaddingAfter()};
1290 
1291     // TODO(kojii): Implement use_first_line_style.
1292     NGBoxFragmentBuilder builder(*this, box_->Style(), &constraint_space,
1293                                  writing_mode, box_->StyleRef().Direction());
1294     builder.SetIsNewFormattingContext(
1295         constraint_space.IsNewFormattingContext());
1296     builder.SetInitialFragmentGeometry(fragment_geometry);
1297     builder.SetIsLegacyLayoutRoot();
1298     if (box_->ShouldComputeSizeAsReplaced()) {
1299       builder.SetIntrinsicBlockSize(box_->LogicalHeight());
1300     } else {
1301       builder.SetIntrinsicBlockSize(box_->IntrinsicContentLogicalHeight() +
1302                                     box_->BorderAndPaddingLogicalHeight() +
1303                                     box_->ScrollbarLogicalHeight());
1304     }
1305 
1306     // If we're block-fragmented, we can only handle monolithic content, since
1307     // the two block fragmentation machineries (NG and legacy) cannot cooperate.
1308     DCHECK(!constraint_space.HasBlockFragmentation() || IsMonolithic());
1309 
1310     if (constraint_space.IsInitialColumnBalancingPass()) {
1311       // In the initial column balancing pass we need to provide the tallest
1312       // unbreakable block-size. However, since the content is monolithic,
1313       // that's already handled by the parent algorithm (so we don't need to
1314       // propagate anything here). We still have to tell the builder that we're
1315       // in this layout pass, though, so that the layout result is set up
1316       // correctly.
1317       builder.SetIsInitialColumnBalancingPass();
1318     }
1319 
1320     CopyBaselinesFromLegacyLayout(constraint_space, &builder);
1321     layout_result = builder.ToBoxFragment();
1322 
1323     box_->SetCachedLayoutResult(layout_result);
1324 
1325     // If |SetCachedLayoutResult| did not update cached |LayoutResult|,
1326     // |NeedsLayout()| flag should not be cleared.
1327     if (needed_layout) {
1328       if (layout_result != box_->GetCachedLayoutResult()) {
1329         // TODO(kojii): If we failed to update CachedLayoutResult for other
1330         // reasons, we'd like to review it.
1331         NOTREACHED();
1332         box_->SetNeedsLayout(layout_invalidation_reason::kUnknown);
1333       }
1334     }
1335   } else if (layout_result) {
1336     // OOF-positioned nodes have a two-tier cache, and their layout results
1337     // must always contain the correct percentage resolution size.
1338     // See |NGBlockNode::CachedLayoutResultForOutOfFlowPositioned|.
1339     const NGConstraintSpace& old_space =
1340         layout_result->GetConstraintSpaceForCaching();
1341     bool needs_cached_result_update =
1342         IsOutOfFlowPositioned() &&
1343         constraint_space.PercentageResolutionSize() !=
1344             old_space.PercentageResolutionSize();
1345     if (needs_cached_result_update) {
1346       layout_result = base::AdoptRef(new NGLayoutResult(
1347           *layout_result, constraint_space, layout_result->EndMarginStrut(),
1348           layout_result->BfcLineOffset(), base::pass_optional(layout_result->BfcBlockOffset()),
1349           LayoutUnit() /* block_offset_delta */));
1350       box_->SetCachedLayoutResult(layout_result);
1351     }
1352   }
1353 
1354   UpdateShapeOutsideInfoIfNeeded(
1355       *layout_result, constraint_space.PercentageResolutionInlineSize());
1356 
1357   return layout_result;
1358 }
1359 
RunSimplifiedLayout(const NGLayoutAlgorithmParams & params,const NGLayoutResult & result) const1360 scoped_refptr<const NGLayoutResult> NGBlockNode::RunSimplifiedLayout(
1361     const NGLayoutAlgorithmParams& params,
1362     const NGLayoutResult& result) const {
1363   return NGSimplifiedLayoutAlgorithm(params, result).Layout();
1364 }
1365 
CopyBaselinesFromLegacyLayout(const NGConstraintSpace & constraint_space,NGBoxFragmentBuilder * builder)1366 void NGBlockNode::CopyBaselinesFromLegacyLayout(
1367     const NGConstraintSpace& constraint_space,
1368     NGBoxFragmentBuilder* builder) {
1369   // As the calls to query baselines from legacy layout are potentially
1370   // expensive we only ask for them if needed.
1371   // TODO(layout-dev): Once we have flexbox, and editing switched over to
1372   // LayoutNG we should be able to safely remove this flag without a
1373   // performance penalty.
1374   if (!constraint_space.NeedsBaseline())
1375     return;
1376 
1377   switch (constraint_space.BaselineAlgorithmType()) {
1378     case NGBaselineAlgorithmType::kFirstLine: {
1379       LayoutUnit position = box_->FirstLineBoxBaseline();
1380       if (position != -1)
1381         builder->SetBaseline(position);
1382       break;
1383     }
1384     case NGBaselineAlgorithmType::kInlineBlock: {
1385       LayoutUnit position =
1386           AtomicInlineBaselineFromLegacyLayout(constraint_space);
1387       if (position != -1)
1388         builder->SetBaseline(position);
1389       break;
1390     }
1391   }
1392 }
1393 
AtomicInlineBaselineFromLegacyLayout(const NGConstraintSpace & constraint_space)1394 LayoutUnit NGBlockNode::AtomicInlineBaselineFromLegacyLayout(
1395     const NGConstraintSpace& constraint_space) {
1396   LineDirectionMode line_direction = box_->IsHorizontalWritingMode()
1397                                          ? LineDirectionMode::kHorizontalLine
1398                                          : LineDirectionMode::kVerticalLine;
1399 
1400   // If this is an inline box, use |BaselinePosition()|. Some LayoutObject
1401   // classes override it assuming inline layout calls |BaselinePosition()|.
1402   if (box_->IsInline()) {
1403     LayoutUnit position = LayoutUnit(box_->BaselinePosition(
1404         box_->Style()->GetFontBaseline(), constraint_space.UseFirstLineStyle(),
1405         line_direction, kPositionOnContainingLine));
1406 
1407     // BaselinePosition() uses margin edge for atomic inlines. Subtract
1408     // margin-over so that the position is relative to the border box.
1409     if (box_->IsAtomicInlineLevel())
1410       position -= box_->MarginOver();
1411 
1412     if (IsFlippedLinesWritingMode(constraint_space.GetWritingMode()))
1413       return box_->Size().Width() - position;
1414 
1415     return position;
1416   }
1417 
1418   // If this is a block box, use |InlineBlockBaseline()|. When an inline block
1419   // has block children, their inline block baselines need to be propagated.
1420   return box_->InlineBlockBaseline(line_direction);
1421 }
1422 
1423 // Floats can optionally have a shape area, specifed by "shape-outside". The
1424 // current shape machinery requires setting the size of the float after layout
1425 // in the parents writing mode.
UpdateShapeOutsideInfoIfNeeded(const NGLayoutResult & layout_result,LayoutUnit percentage_resolution_inline_size)1426 void NGBlockNode::UpdateShapeOutsideInfoIfNeeded(
1427     const NGLayoutResult& layout_result,
1428     LayoutUnit percentage_resolution_inline_size) {
1429   if (!box_->IsFloating() || !box_->GetShapeOutsideInfo())
1430     return;
1431 
1432   // The box_ may not have a valid size yet (due to an intermediate layout),
1433   // use the fragment's size instead.
1434   LayoutSize box_size = layout_result.PhysicalFragment().Size().ToLayoutSize();
1435 
1436   // TODO(ikilpatrick): Ideally this should be moved to a NGLayoutResult
1437   // computing the shape area. There may be an issue with the new fragmentation
1438   // model and computing the correct sizes of shapes.
1439   ShapeOutsideInfo* shape_outside = box_->GetShapeOutsideInfo();
1440   LayoutBlock* containing_block = box_->ContainingBlock();
1441   shape_outside->SetReferenceBoxLogicalSize(
1442       containing_block->IsHorizontalWritingMode() ? box_size
1443                                                   : box_size.TransposedSize());
1444   shape_outside->SetPercentageResolutionInlineSize(
1445       percentage_resolution_inline_size);
1446 }
1447 
UseLegacyOutOfFlowPositioning() const1448 void NGBlockNode::UseLegacyOutOfFlowPositioning() const {
1449   DCHECK(box_->IsOutOfFlowPositioned());
1450   box_->ContainingBlock()->InsertPositionedObject(box_);
1451 }
1452 
StoreMargins(const NGConstraintSpace & constraint_space,const NGBoxStrut & margins)1453 void NGBlockNode::StoreMargins(const NGConstraintSpace& constraint_space,
1454                                const NGBoxStrut& margins) {
1455   NGPhysicalBoxStrut physical_margins = margins.ConvertToPhysical(
1456       constraint_space.GetWritingMode(), constraint_space.Direction());
1457   box_->SetMargin(physical_margins);
1458 }
1459 
StoreMargins(const NGPhysicalBoxStrut & physical_margins)1460 void NGBlockNode::StoreMargins(const NGPhysicalBoxStrut& physical_margins) {
1461   box_->SetMargin(physical_margins);
1462 }
1463 
1464 }  // namespace blink
1465