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