| // Copyright 2016 The Chromium Authors.All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" |
| |
| #include <algorithm> |
| #include "base/optional.h" |
| #include "third_party/blink/renderer/core/layout/geometry/logical_size.h" |
| #include "third_party/blink/renderer/core/layout/layout_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_table_cell.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" |
| #include "third_party/blink/renderer/core/layout/ng/table/ng_table_node.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/platform/geometry/layout_unit.h" |
| #include "third_party/blink/renderer/platform/geometry/length.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| enum class EBlockAlignment { kStart, kCenter, kEnd }; |
| |
| inline EBlockAlignment BlockAlignment(const ComputedStyle& style, |
| const ComputedStyle& container_style) { |
| if (style.MayHaveMargin()) { |
| bool start_auto = style.MarginStartUsing(container_style).IsAuto(); |
| bool end_auto = style.MarginEndUsing(container_style).IsAuto(); |
| if (start_auto || end_auto) { |
| if (start_auto) |
| return end_auto ? EBlockAlignment::kCenter : EBlockAlignment::kEnd; |
| return EBlockAlignment::kStart; |
| } |
| } |
| |
| // If none of the inline margins are auto, look for -webkit- text-align |
| // values (which are really about block alignment). These are typically |
| // mapped from the legacy "align" HTML attribute. |
| switch (container_style.GetTextAlign()) { |
| case ETextAlign::kWebkitLeft: |
| if (container_style.IsLeftToRightDirection()) |
| return EBlockAlignment::kStart; |
| return EBlockAlignment::kEnd; |
| case ETextAlign::kWebkitRight: |
| if (container_style.IsLeftToRightDirection()) |
| return EBlockAlignment::kEnd; |
| return EBlockAlignment::kStart; |
| case ETextAlign::kWebkitCenter: |
| return EBlockAlignment::kCenter; |
| default: |
| return EBlockAlignment::kStart; |
| } |
| } |
| |
| } // anonymous namespace |
| |
| // Check if we shouldn't resolve a percentage/calc()/-webkit-fill-available |
| // if we are in the intrinsic sizes phase. |
| bool InlineLengthUnresolvable(const NGConstraintSpace& constraint_space, |
| const Length& length) { |
| if (length.IsPercentOrCalc()) |
| return constraint_space.PercentageResolutionInlineSize() == kIndefiniteSize; |
| |
| if (length.IsFillAvailable()) |
| return constraint_space.AvailableSize().inline_size == kIndefiniteSize; |
| |
| return false; |
| } |
| |
| // When the containing block size to resolve against is indefinite, we |
| // cannot resolve percentages / calc() / -webkit-fill-available. |
| bool BlockLengthUnresolvable( |
| const NGConstraintSpace& constraint_space, |
| const Length& length, |
| const LayoutUnit* opt_percentage_resolution_block_size_for_min_max) { |
| if (length.IsAuto() || length.IsMinContent() || length.IsMaxContent() || |
| length.IsMinIntrinsic() || length.IsFitContent() || length.IsNone()) |
| return true; |
| if (length.IsPercentOrCalc()) { |
| LayoutUnit percentage_resolution_block_size = |
| opt_percentage_resolution_block_size_for_min_max |
| ? *opt_percentage_resolution_block_size_for_min_max |
| : constraint_space.PercentageResolutionBlockSize(); |
| return percentage_resolution_block_size == kIndefiniteSize; |
| } |
| |
| if (length.IsFillAvailable()) |
| return constraint_space.AvailableSize().block_size == kIndefiniteSize; |
| |
| return false; |
| } |
| |
| LayoutUnit ResolveInlineLengthInternal( |
| const NGConstraintSpace& constraint_space, |
| const ComputedStyle& style, |
| const NGBoxStrut& border_padding, |
| const base::Optional<MinMaxSizes>& min_max_sizes, |
| const Length& length) { |
| DCHECK_EQ(constraint_space.GetWritingMode(), style.GetWritingMode()); |
| |
| switch (length.GetType()) { |
| case Length::kFillAvailable: { |
| DCHECK_GE(constraint_space.AvailableSize().inline_size, LayoutUnit()); |
| LayoutUnit content_size = constraint_space.AvailableSize().inline_size; |
| NGBoxStrut margins = ComputeMarginsForSelf(constraint_space, style); |
| return std::max(border_padding.InlineSum(), |
| content_size - margins.InlineSum()); |
| } |
| case Length::kPercent: |
| case Length::kFixed: |
| case Length::kCalculated: { |
| LayoutUnit percentage_resolution_size = |
| constraint_space.PercentageResolutionInlineSize(); |
| DCHECK(length.IsFixed() || percentage_resolution_size != kIndefiniteSize); |
| LayoutUnit value = |
| MinimumValueForLength(length, percentage_resolution_size); |
| |
| if (style.BoxSizing() == EBoxSizing::kBorderBox) |
| value = std::max(border_padding.InlineSum(), value); |
| else |
| value += border_padding.InlineSum(); |
| return value; |
| } |
| case Length::kMinContent: |
| case Length::kMaxContent: |
| case Length::kMinIntrinsic: |
| case Length::kFitContent: { |
| DCHECK(min_max_sizes.has_value()); |
| LayoutUnit available_size = constraint_space.AvailableSize().inline_size; |
| LayoutUnit value; |
| // TODO(ikilpatrick): The |IsFitContent()| might not be correct for a |
| // max-size, e.g. "max-width: fit-content". |
| if (length.IsMinContent() || length.IsMinIntrinsic() || |
| (length.IsFitContent() && available_size == kIndefiniteSize)) { |
| value = min_max_sizes->min_size; |
| } else if (length.IsMaxContent()) { |
| value = min_max_sizes->max_size; |
| } else { |
| DCHECK_GE(available_size, LayoutUnit()); |
| NGBoxStrut margins = ComputeMarginsForSelf(constraint_space, style); |
| LayoutUnit fill_available = |
| std::max(LayoutUnit(), available_size - margins.InlineSum()); |
| value = min_max_sizes->ShrinkToFit(fill_available); |
| } |
| return value; |
| } |
| case Length::kDeviceWidth: |
| case Length::kDeviceHeight: |
| case Length::kExtendToZoom: |
| NOTREACHED() << "These should only be used for viewport definitions"; |
| FALLTHROUGH; |
| case Length::kAuto: |
| case Length::kNone: |
| default: |
| NOTREACHED(); |
| return border_padding.InlineSum(); |
| } |
| } |
| |
| LayoutUnit ResolveBlockLengthInternal( |
| const NGConstraintSpace& constraint_space, |
| const ComputedStyle& style, |
| const NGBoxStrut& border_padding, |
| const Length& length, |
| LayoutUnit intrinsic_size, |
| LayoutUnit available_block_size_adjustment, |
| const LayoutUnit* opt_percentage_resolution_block_size_for_min_max) { |
| DCHECK_EQ(constraint_space.GetWritingMode(), style.GetWritingMode()); |
| |
| switch (length.GetType()) { |
| case Length::kFillAvailable: { |
| DCHECK_GE(constraint_space.AvailableSize().block_size, LayoutUnit()); |
| LayoutUnit available_size = (constraint_space.AvailableSize().block_size - |
| available_block_size_adjustment) |
| .ClampNegativeToZero(); |
| NGBoxStrut margins = ComputeMarginsForSelf(constraint_space, style); |
| return std::max(border_padding.BlockSum(), |
| available_size - margins.BlockSum()); |
| } |
| case Length::kPercent: |
| case Length::kFixed: |
| case Length::kCalculated: { |
| LayoutUnit percentage_resolution_size = |
| opt_percentage_resolution_block_size_for_min_max |
| ? *opt_percentage_resolution_block_size_for_min_max |
| : constraint_space.PercentageResolutionBlockSize(); |
| DCHECK(length.IsFixed() || percentage_resolution_size != kIndefiniteSize); |
| LayoutUnit value = |
| MinimumValueForLength(length, percentage_resolution_size); |
| |
| if (style.BoxSizing() == EBoxSizing::kBorderBox) |
| value = std::max(border_padding.BlockSum(), value); |
| else |
| value += border_padding.BlockSum(); |
| return value; |
| } |
| case Length::kMinContent: |
| case Length::kMaxContent: |
| case Length::kMinIntrinsic: |
| case Length::kFitContent: |
| #if DCHECK_IS_ON() |
| // Due to how intrinsic_size is calculated, it should always include |
| // border and padding. We cannot check for this if we are |
| // block-fragmented, though, because then the block-start border/padding |
| // may be in a different fragmentainer than the block-end border/padding. |
| if (intrinsic_size != kIndefiniteSize && |
| !constraint_space.HasBlockFragmentation()) |
| DCHECK_GE(intrinsic_size, border_padding.BlockSum()); |
| #endif // DCHECK_IS_ON() |
| return intrinsic_size; |
| case Length::kDeviceWidth: |
| case Length::kDeviceHeight: |
| case Length::kExtendToZoom: |
| NOTREACHED() << "These should only be used for viewport definitions"; |
| FALLTHROUGH; |
| case Length::kAuto: |
| case Length::kNone: |
| default: |
| NOTREACHED(); |
| return border_padding.BlockSum(); |
| } |
| } |
| |
| // logical_aspect_ratio is inline_size / block_size. |
| LayoutUnit InlineSizeFromAspectRatio(const NGBoxStrut& border_padding, |
| double logical_aspect_ratio, |
| EBoxSizing box_sizing, |
| LayoutUnit block_size) { |
| // TODO(dgrogan/ikilpatrick): These calculations might need to be done in |
| // integer space, in a potential BoundedMultiplyAndDivide(LayoutUnit, |
| // LayoutUnit, LayoutUnit) function. |
| if (box_sizing == EBoxSizing::kBorderBox) { |
| return LayoutUnit::FromDoubleRound(block_size * logical_aspect_ratio); |
| } |
| |
| return LayoutUnit::FromDoubleRound((block_size - border_padding.BlockSum()) * |
| logical_aspect_ratio) + |
| border_padding.InlineSum(); |
| } |
| |
| LayoutUnit InlineSizeFromAspectRatio(const NGBoxStrut& border_padding, |
| const LogicalSize& aspect_ratio, |
| EBoxSizing box_sizing, |
| LayoutUnit block_size) { |
| return InlineSizeFromAspectRatio( |
| border_padding, |
| aspect_ratio.inline_size.ToDouble() / aspect_ratio.block_size.ToDouble(), |
| box_sizing, block_size); |
| } |
| |
| // logical_aspect_ratio is block_size / inline_size. |
| LayoutUnit BlockSizeFromAspectRatio(const NGBoxStrut& border_padding, |
| double logical_aspect_ratio, |
| EBoxSizing box_sizing, |
| LayoutUnit inline_size) { |
| if (box_sizing == EBoxSizing::kBorderBox) { |
| return LayoutUnit::FromDoubleRound(inline_size * logical_aspect_ratio); |
| } |
| |
| return LayoutUnit::FromDoubleRound( |
| (inline_size - border_padding.InlineSum()) * |
| logical_aspect_ratio) + |
| border_padding.BlockSum(); |
| } |
| |
| LayoutUnit BlockSizeFromAspectRatio(const NGBoxStrut& border_padding, |
| const LogicalSize& aspect_ratio, |
| EBoxSizing box_sizing, |
| LayoutUnit inline_size) { |
| return BlockSizeFromAspectRatio( |
| border_padding, |
| aspect_ratio.block_size.ToDouble() / aspect_ratio.inline_size.ToDouble(), |
| box_sizing, inline_size); |
| } |
| |
| namespace { |
| |
| template <typename MinMaxSizesFunc> |
| MinMaxSizesResult ComputeMinAndMaxContentContributionInternal( |
| WritingMode parent_writing_mode, |
| const NGBlockNode& child, |
| const MinMaxSizesFunc& min_max_sizes_func) { |
| const ComputedStyle& style = child.Style(); |
| const WritingMode child_writing_mode = style.GetWritingMode(); |
| |
| // Synthesize an indefinite space for resolving sizes against. |
| const NGConstraintSpace space = |
| NGConstraintSpaceBuilder(child_writing_mode, style.GetWritingDirection(), |
| /* is_new_fc */ false) |
| .ToConstraintSpace(); |
| const NGBoxStrut border_padding = |
| ComputeBorders(space, child) + ComputePadding(space, style); |
| |
| MinMaxSizesResult result; |
| |
| const Length& inline_size = parent_writing_mode == WritingMode::kHorizontalTb |
| ? style.Width() |
| : style.Height(); |
| if (inline_size.IsAuto() || inline_size.IsPercentOrCalc() || |
| inline_size.IsFillAvailable() || inline_size.IsFitContent()) { |
| result = min_max_sizes_func(MinMaxSizesType::kContent); |
| } else { |
| if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { |
| MinMaxSizes sizes; |
| sizes = ResolveMainInlineLength(space, style, border_padding, |
| min_max_sizes_func, inline_size); |
| result = MinMaxSizesResult(sizes, |
| /* depends_on_percentage_block_size */ false); |
| } else { |
| auto IntrinsicBlockSizeFunc = [&]() -> LayoutUnit { |
| return min_max_sizes_func(inline_size.IsMinIntrinsic() |
| ? MinMaxSizesType::kIntrinsic |
| : MinMaxSizesType::kContent) |
| .sizes.max_size; |
| }; |
| MinMaxSizes sizes; |
| sizes = ResolveMainBlockLength(space, style, border_padding, inline_size, |
| IntrinsicBlockSizeFunc); |
| result = MinMaxSizesResult(sizes, |
| /* depends_on_percentage_block_size */ false); |
| } |
| } |
| |
| const Length& max_length = parent_writing_mode == WritingMode::kHorizontalTb |
| ? style.MaxWidth() |
| : style.MaxHeight(); |
| LayoutUnit max; |
| if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { |
| max = ResolveMaxInlineLength(space, style, border_padding, |
| min_max_sizes_func, max_length); |
| } else { |
| max = ResolveMaxBlockLength(space, style, border_padding, max_length); |
| } |
| result.sizes.Constrain(max); |
| |
| const Length& min_length = parent_writing_mode == WritingMode::kHorizontalTb |
| ? style.MinWidth() |
| : style.MinHeight(); |
| LayoutUnit min; |
| if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { |
| min = ResolveMinInlineLength(space, style, border_padding, |
| min_max_sizes_func, min_length); |
| } else { |
| min = ResolveMinBlockLength(space, style, border_padding, min_length); |
| } |
| result.sizes.Encompass(min); |
| |
| // Tables need to apply one final constraint. They are never allowed to go |
| // below their min-intrinsic size (even if they have an inline-size, etc). |
| if (child.IsNGTable()) { |
| result.sizes.Encompass( |
| min_max_sizes_func(MinMaxSizesType::kIntrinsic).sizes.min_size); |
| } |
| |
| return result; |
| } |
| |
| // Currently this simply sets the correct override sizes for the replaced |
| // element, and lets legacy layout do the result. |
| MinMaxSizesResult ComputeMinAndMaxContentContributionForReplaced( |
| const NGBlockNode& child, |
| const MinMaxSizesInput& input) { |
| const ComputedStyle& child_style = child.Style(); |
| LayoutBox* box = child.GetLayoutBox(); |
| bool needs_size_reset = false; |
| if (!box->HasOverrideContainingBlockContentLogicalHeight()) { |
| box->SetOverrideContainingBlockContentLogicalHeight( |
| input.percentage_resolution_block_size); |
| needs_size_reset = true; |
| } |
| |
| MinMaxSizes result = box->PreferredLogicalWidths(); |
| |
| if (needs_size_reset) |
| box->ClearOverrideContainingBlockContentSize(); |
| |
| // Replaced elements which have a percentage block-size use the |
| // |MinMaxSizesInput::percentage_resolution_block_size| field. |
| bool depends_on_percentage_block_size = |
| child_style.LogicalMinHeight().IsPercentOrCalc() || |
| child_style.LogicalHeight().IsPercentOrCalc() || |
| child_style.LogicalMaxHeight().IsPercentOrCalc(); |
| return MinMaxSizesResult(result, depends_on_percentage_block_size); |
| } |
| |
| } // namespace |
| |
| MinMaxSizesResult ComputeMinAndMaxContentContribution( |
| const ComputedStyle& parent_style, |
| const NGBlockNode& child, |
| const MinMaxSizesInput& input) { |
| const ComputedStyle& child_style = child.Style(); |
| WritingMode parent_writing_mode = parent_style.GetWritingMode(); |
| WritingMode child_writing_mode = child_style.GetWritingMode(); |
| |
| if (IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { |
| // Legacy tables are special - always let the legacy table code handle this. |
| if (child.IsTable() && !child.IsNGTable()) |
| return child.ComputeMinMaxSizes(parent_writing_mode, input, nullptr); |
| |
| if (child.IsReplaced()) |
| return ComputeMinAndMaxContentContributionForReplaced(child, input); |
| } |
| |
| auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult { |
| MinMaxSizesInput input_copy(input); |
| input_copy.type = type; |
| // We need to set up a constraint space with correct fallback available |
| // inline-size in case of orthogonal children. |
| NGConstraintSpace indefinite_constraint_space; |
| const NGConstraintSpace* child_constraint_space = nullptr; |
| if (!IsParallelWritingMode(parent_writing_mode, child_writing_mode)) { |
| indefinite_constraint_space = CreateIndefiniteConstraintSpaceForChild( |
| parent_style, input_copy, child); |
| child_constraint_space = &indefinite_constraint_space; |
| } |
| return child.ComputeMinMaxSizes(parent_writing_mode, input_copy, |
| child_constraint_space); |
| }; |
| |
| return ComputeMinAndMaxContentContributionInternal(parent_writing_mode, child, |
| MinMaxSizesFunc); |
| } |
| |
| MinMaxSizesResult ComputeMinAndMaxContentContributionForSelf( |
| const NGBlockNode& child, |
| const MinMaxSizesInput& input) { |
| const ComputedStyle& child_style = child.Style(); |
| WritingMode writing_mode = child_style.GetWritingMode(); |
| |
| // Legacy tables are special - always let the legacy table code handle this. |
| if (child.IsTable() && !child.IsNGTable()) |
| return child.ComputeMinMaxSizes(writing_mode, input, nullptr); |
| |
| if (child.IsReplaced()) |
| return ComputeMinAndMaxContentContributionForReplaced(child, input); |
| |
| auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult { |
| MinMaxSizesInput input_copy(input); |
| input_copy.type = type; |
| return child.ComputeMinMaxSizes(writing_mode, input_copy); |
| }; |
| |
| return ComputeMinAndMaxContentContributionInternal(writing_mode, child, |
| MinMaxSizesFunc); |
| } |
| |
| MinMaxSizes ComputeMinAndMaxContentContributionForTest( |
| WritingMode parent_writing_mode, |
| const NGBlockNode& child, |
| const MinMaxSizes& min_max_sizes) { |
| auto MinMaxSizesFunc = [&](MinMaxSizesType) -> MinMaxSizesResult { |
| return MinMaxSizesResult(min_max_sizes, |
| /* depends_on_percentage_block_size */ false); |
| }; |
| return ComputeMinAndMaxContentContributionInternal(parent_writing_mode, child, |
| MinMaxSizesFunc) |
| .sizes; |
| } |
| |
| LayoutUnit ComputeInlineSizeFromAspectRatio( |
| const NGConstraintSpace& space, |
| const ComputedStyle& style, |
| const NGBoxStrut& border_padding, |
| bool should_be_considered_as_replaced, |
| LayoutUnit block_size) { |
| if (LIKELY(style.AspectRatio().IsAuto())) |
| return kIndefiniteSize; |
| |
| if (block_size == kIndefiniteSize) { |
| if (space.IsFixedBlockSize()) { |
| block_size = space.AvailableSize().block_size; |
| } else if (!style.LogicalHeight().IsAuto()) { |
| DCHECK(!style.HasOutOfFlowPosition()) |
| << "OOF should pass in a block size"; |
| block_size = ComputeBlockSizeForFragment( |
| space, style, border_padding, kIndefiniteSize, base::nullopt, |
| should_be_considered_as_replaced); |
| } |
| if (block_size == kIndefiniteSize) |
| return kIndefiniteSize; |
| } |
| // Check if we can get an inline size using the aspect ratio. |
| return InlineSizeFromAspectRatio(border_padding, style.LogicalAspectRatio(), |
| style.BoxSizingForAspectRatio(), block_size); |
| } |
| |
| namespace { |
| |
| LayoutUnit ComputeInlineSizeForFragmentInternal( |
| const NGConstraintSpace& space, |
| NGLayoutInputNode node, |
| const NGBoxStrut& border_padding, |
| const MinMaxSizes* override_min_max_sizes) { |
| auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult { |
| if (override_min_max_sizes) { |
| return MinMaxSizesResult(*override_min_max_sizes, |
| /* depends_on_percentage_block_size */ false); |
| } |
| |
| MinMaxSizesInput input(space.PercentageResolutionBlockSize(), type); |
| return node.ComputeMinMaxSizes(space.GetWritingMode(), input, &space); |
| }; |
| |
| const ComputedStyle& style = node.Style(); |
| Length logical_width = style.LogicalWidth(); |
| Length min_length = style.LogicalMinWidth(); |
| |
| // TODO(ikilpatrick): If we are stretching in both the inline-axis, and |
| // block-axis, we shouldn't apply the aspect-ratio. |
| // TODO(cbiesinger): Should the if also check !node.IsReplaced()? |
| LayoutUnit extent = kIndefiniteSize; |
| if (!style.AspectRatio().IsAuto()) { |
| if (logical_width.IsAuto() || logical_width.IsMinContent() || |
| logical_width.IsMaxContent()) { |
| extent = ComputeInlineSizeFromAspectRatio( |
| space, style, border_padding, node.ShouldBeConsideredAsReplaced()); |
| } |
| if (UNLIKELY(extent != kIndefiniteSize)) { |
| // This means we successfully applied aspect-ratio and now need to check |
| // if we need to apply the implied minimum size: |
| // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum |
| if (style.OverflowInlineDirection() == EOverflow::kVisible && |
| min_length.IsAuto()) |
| min_length = Length::MinIntrinsic(); |
| } |
| } |
| |
| if (LIKELY(extent == kIndefiniteSize)) { |
| if (logical_width.IsAuto()) { |
| logical_width = space.StretchInlineSizeIfAuto() ? Length::FillAvailable() |
| : Length::FitContent(); |
| } |
| extent = ResolveMainInlineLength(space, style, border_padding, |
| MinMaxSizesFunc, logical_width); |
| } |
| |
| const MinMaxSizes min_max_sizes = ComputeMinMaxInlineSizes( |
| space, style, border_padding, MinMaxSizesFunc, &min_length); |
| return min_max_sizes.ClampSizeToMinAndMax(extent); |
| } |
| |
| } // namespace |
| |
| LayoutUnit ComputeInlineSizeForFragment( |
| const NGConstraintSpace& space, |
| NGLayoutInputNode node, |
| const NGBoxStrut& border_padding, |
| const MinMaxSizes* override_min_max_sizes_for_test) { |
| if (space.IsFixedInlineSize() || space.IsAnonymous()) |
| return space.AvailableSize().inline_size; |
| |
| if (node.IsNGTable()) |
| return To<NGTableNode>(node).ComputeTableInlineSize(space, border_padding); |
| |
| return ComputeInlineSizeForFragmentInternal(space, node, border_padding, |
| override_min_max_sizes_for_test); |
| } |
| |
| LayoutUnit ComputeUsedInlineSizeForTableFragment( |
| const NGConstraintSpace& space, |
| NGLayoutInputNode node, |
| const NGBoxStrut& border_padding, |
| const MinMaxSizes& table_grid_min_max_sizes) { |
| DCHECK(!space.IsFixedInlineSize()); |
| return ComputeInlineSizeForFragmentInternal(space, node, border_padding, |
| &table_grid_min_max_sizes); |
| } |
| |
| MinMaxSizes ComputeMinMaxBlockSizes( |
| const NGConstraintSpace& constraint_space, |
| const ComputedStyle& style, |
| const NGBoxStrut& border_padding, |
| LayoutUnit intrinsic_size, |
| LayoutUnit available_block_size_adjustment, |
| const LayoutUnit* opt_percentage_resolution_block_size_for_min_max) { |
| MinMaxSizes sizes = { |
| ResolveMinBlockLength(constraint_space, style, border_padding, |
| style.LogicalMinHeight(), |
| available_block_size_adjustment, |
| opt_percentage_resolution_block_size_for_min_max), |
| ResolveMaxBlockLength(constraint_space, style, border_padding, |
| style.LogicalMaxHeight(), |
| available_block_size_adjustment, |
| opt_percentage_resolution_block_size_for_min_max)}; |
| sizes.max_size = std::max(sizes.max_size, sizes.min_size); |
| return sizes; |
| } |
| |
| MinMaxSizes ComputeTransferredMinMaxInlineSizes( |
| const LogicalSize& ratio, |
| const MinMaxSizes& block_min_max, |
| const NGBoxStrut& border_padding, |
| const EBoxSizing sizing) { |
| MinMaxSizes transferred_min_max = {LayoutUnit(), LayoutUnit::Max()}; |
| if (block_min_max.min_size > LayoutUnit()) { |
| transferred_min_max.min_size = InlineSizeFromAspectRatio( |
| border_padding, ratio, sizing, block_min_max.min_size); |
| } |
| if (block_min_max.max_size != LayoutUnit::Max()) { |
| transferred_min_max.max_size = InlineSizeFromAspectRatio( |
| border_padding, ratio, sizing, block_min_max.max_size); |
| } |
| // Minimum size wins over maximum size. |
| transferred_min_max.max_size = |
| std::max(transferred_min_max.max_size, transferred_min_max.min_size); |
| return transferred_min_max; |
| } |
| |
| MinMaxSizes ComputeMinMaxInlineSizesFromAspectRatio( |
| const NGConstraintSpace& constraint_space, |
| const ComputedStyle& style, |
| const NGBoxStrut& border_padding) { |
| DCHECK(!style.AspectRatio().IsAuto()); |
| |
| // The spec requires us to clamp these by the specified size (it calls it the |
| // preferred size). However, we actually don't need to worry about that, |
| // because we only use this if the width is indefinite. |
| |
| // We do not need to compute the min/max inline sizes; as long as we always |
| // apply the transferred min/max size before the explicit min/max size, the |
| // result will be identical. |
| |
| LogicalSize ratio = style.LogicalAspectRatio(); |
| MinMaxSizes block_min_max = |
| ComputeMinMaxBlockSizes(constraint_space, style, border_padding, |
| /* intrinsic_size */ kIndefiniteSize); |
| return ComputeTransferredMinMaxInlineSizes( |
| ratio, block_min_max, border_padding, style.BoxSizingForAspectRatio()); |
| } |
| |
| namespace { |
| |
| // Computes the block-size for a fragment, ignoring the fixed block-size if set. |
| LayoutUnit ComputeBlockSizeForFragmentInternal( |
| const NGConstraintSpace& space, |
| const ComputedStyle& style, |
| const NGBoxStrut& border_padding, |
| LayoutUnit intrinsic_size, |
| base::Optional<LayoutUnit> inline_size, |
| bool should_be_considered_as_replaced, |
| LayoutUnit available_block_size_adjustment = LayoutUnit(), |
| const LayoutUnit* opt_percentage_resolution_block_size_for_min_max = |
| nullptr) { |
| MinMaxSizes min_max = |
| ComputeMinMaxBlockSizes(space, style, border_padding, intrinsic_size, |
| available_block_size_adjustment, |
| opt_percentage_resolution_block_size_for_min_max); |
| Length logical_height = style.LogicalHeight(); |
| // Scrollable percentage-sized children of table cells, in the table |
| // "measure" phase contribute nothing to the row height measurement. |
| // See: https://drafts.csswg.org/css-tables-3/#row-layout |
| // We only apply this rule if the block size of the containing table cell is |
| // considered to be restricted, though. Otherwise, especially if this is the |
| // only child of the cell, and that is the only cell in the row, we'd end up |
| // with zero block size. To match the legacy layout engine behavior in |
| // LayoutBox::ContainingBlockLogicalHeightForPercentageResolution(), we only |
| // check the block-size of the containing cell and its containing table. Other |
| // things to consider, would be checking the row and row-group, and also other |
| // properties, such as {min,max}-block-size. |
| if (logical_height.IsPercentOrCalc() && |
| space.TableCellChildLayoutMode() == |
| NGTableCellChildLayoutMode::kMeasureRestricted && |
| !should_be_considered_as_replaced && |
| (style.OverflowBlockDirection() == EOverflow::kAuto || |
| style.OverflowBlockDirection() == EOverflow::kScroll)) |
| return min_max.min_size; |
| |
| if (logical_height.IsAuto()) { |
| logical_height = space.StretchBlockSizeIfAuto() ? Length::FillAvailable() |
| : Length::FitContent(); |
| } |
| // TODO(cbiesinger): Audit callers of ResolveMainBlockLength to see whether |
| // they need to respect aspect ratio. |
| LayoutUnit extent = |
| ResolveMainBlockLength(space, style, border_padding, logical_height, |
| intrinsic_size, available_block_size_adjustment, |
| opt_percentage_resolution_block_size_for_min_max); |
| if (UNLIKELY(!style.AspectRatio().IsAuto() && inline_size && |
| BlockLengthUnresolvable( |
| space, logical_height, |
| opt_percentage_resolution_block_size_for_min_max))) { |
| extent = |
| BlockSizeFromAspectRatio(border_padding, style.LogicalAspectRatio(), |
| style.BoxSizingForAspectRatio(), *inline_size); |
| // Apply the automatic minimum size for aspect ratio: |
| // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum |
| // We also check for LayoutUnit::Max() because flexbox uses that as a |
| // "placeholder" to compute the flex line length while still respecting |
| // max-block-size. |
| if (style.LogicalMinHeight().IsAuto() && |
| style.OverflowBlockDirection() == EOverflow::kVisible && |
| intrinsic_size != kIndefiniteSize && |
| intrinsic_size != LayoutUnit::Max()) { |
| min_max.min_size = intrinsic_size; |
| } |
| } else if (extent == kIndefiniteSize) { |
| DCHECK_EQ(intrinsic_size, kIndefiniteSize); |
| return extent; |
| } |
| |
| return min_max.ClampSizeToMinAndMax(extent); |
| } |
| |
| } // namespace |
| |
| LayoutUnit ComputeBlockSizeForFragment( |
| const NGConstraintSpace& constraint_space, |
| const ComputedStyle& style, |
| const NGBoxStrut& border_padding, |
| LayoutUnit intrinsic_size, |
| base::Optional<LayoutUnit> inline_size, |
| bool should_be_considered_as_replaced, |
| LayoutUnit available_block_size_adjustment) { |
| // The |available_block_size_adjustment| should only be used for <table>s. |
| DCHECK(available_block_size_adjustment == LayoutUnit() || |
| style.IsDisplayTableBox()); |
| |
| if (constraint_space.IsLegacyTableCell() && intrinsic_size != kIndefiniteSize) |
| return intrinsic_size; |
| |
| if (constraint_space.IsFixedBlockSize()) { |
| return (constraint_space.AvailableSize().block_size - |
| available_block_size_adjustment) |
| .ClampNegativeToZero(); |
| } |
| |
| if (constraint_space.IsTableCell() && !constraint_space.IsLegacyTableCell() && |
| intrinsic_size != kIndefiniteSize) |
| return intrinsic_size; |
| |
| if (constraint_space.IsAnonymous()) |
| return intrinsic_size; |
| |
| return ComputeBlockSizeForFragmentInternal( |
| constraint_space, style, border_padding, intrinsic_size, inline_size, |
| should_be_considered_as_replaced, available_block_size_adjustment); |
| } |
| |
| // Computes size for a replaced element. |
| void ComputeReplacedSize(const NGBlockNode& node, |
| const NGConstraintSpace& space, |
| const base::Optional<MinMaxSizes>& child_min_max_sizes, |
| base::Optional<LogicalSize>* out_replaced_size, |
| base::Optional<LogicalSize>* out_aspect_ratio) { |
| DCHECK(node.IsReplaced()); |
| DCHECK(!out_replaced_size->has_value()); |
| DCHECK(!out_aspect_ratio->has_value()); |
| |
| const ComputedStyle& style = node.Style(); |
| |
| NGBoxStrut border_padding = |
| ComputeBorders(space, node) + ComputePadding(space, style); |
| LayoutUnit inline_min = |
| ResolveMinInlineLength(space, style, border_padding, child_min_max_sizes, |
| style.LogicalMinWidth()); |
| LayoutUnit inline_max = |
| ResolveMaxInlineLength(space, style, border_padding, child_min_max_sizes, |
| style.LogicalMaxWidth()); |
| LayoutUnit block_min = ResolveMinBlockLength(space, style, border_padding, |
| style.LogicalMinHeight()); |
| LayoutUnit block_max = ResolveMaxBlockLength(space, style, border_padding, |
| style.LogicalMaxHeight()); |
| |
| const Length& inline_length = style.LogicalWidth(); |
| const Length& block_length = style.LogicalHeight(); |
| base::Optional<LayoutUnit> replaced_inline; |
| if (space.IsFixedInlineSize()) { |
| replaced_inline = space.AvailableSize().inline_size; |
| DCHECK_GE(*replaced_inline, 0); |
| } else if (!inline_length.IsAuto() || space.StretchInlineSizeIfAuto()) { |
| Length inline_length_to_resolve = inline_length; |
| if (inline_length_to_resolve.IsAuto()) { |
| // TODO(dgrogan): This code block (and its corresponding block version |
| // below) didn't make any tests pass when written so it may be unnecessary |
| // or untested. Check again when launching ReplacedNG. |
| DCHECK(space.StretchInlineSizeIfAuto()); |
| DCHECK(space.AvailableSize().inline_size != kIndefiniteSize); |
| inline_length_to_resolve = Length::FillAvailable(); |
| } |
| replaced_inline = |
| ResolveMainInlineLength(space, style, border_padding, |
| child_min_max_sizes, inline_length_to_resolve); |
| replaced_inline = |
| ConstrainByMinMax(*replaced_inline, inline_min, inline_max); |
| } |
| base::Optional<LayoutUnit> replaced_block; |
| if (space.IsFixedBlockSize()) { |
| replaced_block = space.AvailableSize().block_size; |
| DCHECK_GE(*replaced_block, 0); |
| } else if (!block_length.IsAuto() || space.StretchBlockSizeIfAuto()) { |
| Length block_length_to_resolve = block_length; |
| if (block_length_to_resolve.IsAuto()) { |
| DCHECK(space.StretchBlockSizeIfAuto()); |
| DCHECK(space.AvailableSize().block_size != kIndefiniteSize); |
| block_length_to_resolve = Length::FillAvailable(); |
| } |
| replaced_block = ResolveMainBlockLength(space, style, border_padding, |
| block_length_to_resolve, |
| space.AvailableSize().block_size); |
| replaced_block = ConstrainByMinMax(*replaced_block, block_min, block_max); |
| } |
| if (replaced_inline && replaced_block) { |
| out_replaced_size->emplace(*replaced_inline, *replaced_block); |
| return; |
| } |
| |
| base::Optional<LayoutUnit> intrinsic_inline; |
| base::Optional<LayoutUnit> intrinsic_block; |
| node.IntrinsicSize(&intrinsic_inline, &intrinsic_block); |
| |
| LogicalSize aspect_ratio = node.GetAspectRatio(); |
| |
| // Computing intrinsic size is complicated by the fact that |
| // intrinsic_inline, intrinsic_block, and aspect_ratio can all |
| // be empty independent of each other. |
| // https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width |
| if (intrinsic_inline) |
| intrinsic_inline = *intrinsic_inline + border_padding.InlineSum(); |
| else if (aspect_ratio.IsEmpty()) |
| intrinsic_inline = LayoutUnit(300) + border_padding.InlineSum(); |
| if (intrinsic_block) |
| intrinsic_block = *intrinsic_block + border_padding.BlockSum(); |
| else if (aspect_ratio.IsEmpty()) |
| intrinsic_block = LayoutUnit(150) + border_padding.BlockSum(); |
| if (!intrinsic_inline) { |
| if (intrinsic_block) { |
| intrinsic_inline = |
| InlineSizeFromAspectRatio(border_padding, aspect_ratio, |
| EBoxSizing::kContentBox, *intrinsic_block); |
| } else if (!replaced_inline && !replaced_block) { |
| // No sizes available, return only the aspect ratio. |
| *out_aspect_ratio = aspect_ratio; |
| return; |
| } |
| } |
| if (intrinsic_inline && !intrinsic_block) { |
| DCHECK(!aspect_ratio.IsEmpty()); |
| intrinsic_block = |
| BlockSizeFromAspectRatio(border_padding, aspect_ratio, |
| EBoxSizing::kContentBox, *intrinsic_inline); |
| } |
| |
| DCHECK(intrinsic_inline || intrinsic_block || replaced_inline || |
| replaced_block); |
| |
| // If we only know one length, the other length gets computed wrt one we know. |
| auto ComputeBlockFromInline = [&replaced_inline, &aspect_ratio, |
| &border_padding](LayoutUnit default_block) { |
| if (aspect_ratio.IsEmpty()) { |
| DCHECK_GE(default_block, border_padding.BlockSum()); |
| return default_block; |
| } |
| return BlockSizeFromAspectRatio(border_padding, aspect_ratio, |
| EBoxSizing::kContentBox, *replaced_inline); |
| }; |
| |
| auto ComputeInlineFromBlock = [&replaced_block, &aspect_ratio, |
| &border_padding](LayoutUnit default_inline) { |
| if (aspect_ratio.IsEmpty()) { |
| DCHECK_GE(default_inline, border_padding.InlineSum()); |
| return default_inline; |
| } |
| return InlineSizeFromAspectRatio(border_padding, aspect_ratio, |
| EBoxSizing::kContentBox, *replaced_block); |
| }; |
| if (replaced_inline) { |
| DCHECK(!replaced_block); |
| DCHECK(intrinsic_block || !aspect_ratio.IsEmpty()); |
| replaced_block = |
| ComputeBlockFromInline(intrinsic_block.value_or(kIndefiniteSize)); |
| replaced_block = ConstrainByMinMax(*replaced_block, block_min, block_max); |
| } else if (replaced_block) { |
| DCHECK(!replaced_inline); |
| DCHECK(intrinsic_inline || !aspect_ratio.IsEmpty()); |
| replaced_inline = |
| ComputeInlineFromBlock(intrinsic_inline.value_or(kIndefiniteSize)); |
| replaced_inline = |
| ConstrainByMinMax(*replaced_inline, inline_min, inline_max); |
| } else { |
| // If both lengths are unknown, they get defined by intrinsic values. |
| DCHECK(!replaced_inline); |
| DCHECK(!replaced_block); |
| replaced_inline = *intrinsic_inline; |
| replaced_block = *intrinsic_block; |
| // If lengths are constrained, keep aspect ratio. |
| // The side that shrank the most defines the other side. |
| LayoutUnit constrained_inline = |
| ConstrainByMinMax(*replaced_inline, inline_min, inline_max); |
| LayoutUnit constrained_block = |
| ConstrainByMinMax(*replaced_block, block_min, block_max); |
| if (constrained_inline != replaced_inline || |
| constrained_block != replaced_block) { |
| LayoutUnit inline_ratio = |
| (*replaced_inline - border_padding.InlineSum()) == LayoutUnit() |
| ? LayoutUnit::Max() |
| : (constrained_inline - border_padding.InlineSum()) / |
| (*replaced_inline - border_padding.InlineSum()); |
| LayoutUnit block_ratio = |
| (*replaced_block - border_padding.BlockSum()) == LayoutUnit() |
| ? LayoutUnit::Max() |
| : (constrained_block - border_padding.BlockSum()) / |
| (*replaced_block - border_padding.BlockSum()); |
| |
| // The following implements spec table from section 10.4 at |
| // https://www.w3.org/TR/CSS22/visudet.html#min-max-widths |
| // Translating specs to code: |
| // inline_ratio < 1 => w > max_width |
| // inline_ratio > 1 => w < min_width |
| // block_ratio < 1 => h > max_height |
| // block_ratio > 1 => h < min_height |
| LayoutUnit one_unit(1); |
| if (inline_ratio != one_unit || block_ratio != one_unit) { |
| if ((inline_ratio < one_unit && block_ratio > one_unit) || |
| (inline_ratio > one_unit && block_ratio < one_unit)) { |
| // Constraints caused us to grow in one dimension and shrink in the |
| // other. Use both constrained sizes. |
| replaced_inline = constrained_inline; |
| replaced_block = constrained_block; |
| } else if (block_ratio == one_unit || |
| (inline_ratio < one_unit && inline_ratio <= block_ratio) || |
| (inline_ratio > one_unit && inline_ratio >= block_ratio)) { |
| // The inline size got constrained more extremely than the block size. |
| // Use constrained inline size, re-calculate block size from aspect |
| // ratio. |
| replaced_inline = constrained_inline; |
| replaced_block = ComputeBlockFromInline(constrained_block); |
| } else { |
| // The block size got constrained more extremely than the inline size. |
| // Use constrained block size, re-calculate inline size from aspect |
| // ratio. |
| replaced_block = constrained_block; |
| replaced_inline = ComputeInlineFromBlock(constrained_inline); |
| } |
| } |
| } |
| } |
| out_replaced_size->emplace(*replaced_inline, *replaced_block); |
| } |
| |
| int ResolveUsedColumnCount(int computed_count, |
| LayoutUnit computed_size, |
| LayoutUnit used_gap, |
| LayoutUnit available_size) { |
| if (computed_size == kIndefiniteSize) { |
| DCHECK(computed_count); |
| return computed_count; |
| } |
| DCHECK_GT(computed_size, LayoutUnit()); |
| int count_from_width = |
| ((available_size + used_gap) / (computed_size + used_gap)).ToInt(); |
| count_from_width = std::max(1, count_from_width); |
| if (!computed_count) |
| return count_from_width; |
| return std::max(1, std::min(computed_count, count_from_width)); |
| } |
| |
| int ResolveUsedColumnCount(LayoutUnit available_size, |
| const ComputedStyle& style) { |
| LayoutUnit computed_column_inline_size = |
| style.HasAutoColumnWidth() |
| ? kIndefiniteSize |
| : std::max(LayoutUnit(1), LayoutUnit(style.ColumnWidth())); |
| LayoutUnit gap = ResolveUsedColumnGap(available_size, style); |
| int computed_count = style.HasAutoColumnCount() ? 0 : style.ColumnCount(); |
| return ResolveUsedColumnCount(computed_count, computed_column_inline_size, |
| gap, available_size); |
| } |
| |
| LayoutUnit ResolveUsedColumnInlineSize(int computed_count, |
| LayoutUnit computed_size, |
| LayoutUnit used_gap, |
| LayoutUnit available_size) { |
| int used_count = ResolveUsedColumnCount(computed_count, computed_size, |
| used_gap, available_size); |
| return std::max(((available_size + used_gap) / used_count) - used_gap, |
| LayoutUnit()); |
| } |
| |
| LayoutUnit ResolveUsedColumnInlineSize(LayoutUnit available_size, |
| const ComputedStyle& style) { |
| // Should only attempt to resolve this if columns != auto. |
| DCHECK(!style.HasAutoColumnCount() || !style.HasAutoColumnWidth()); |
| |
| LayoutUnit computed_size = |
| style.HasAutoColumnWidth() |
| ? kIndefiniteSize |
| : std::max(LayoutUnit(1), LayoutUnit(style.ColumnWidth())); |
| int computed_count = style.HasAutoColumnCount() ? 0 : style.ColumnCount(); |
| LayoutUnit used_gap = ResolveUsedColumnGap(available_size, style); |
| return ResolveUsedColumnInlineSize(computed_count, computed_size, used_gap, |
| available_size); |
| } |
| |
| LayoutUnit ResolveUsedColumnGap(LayoutUnit available_size, |
| const ComputedStyle& style) { |
| if (const base::Optional<Length>& column_gap = style.ColumnGap()) |
| return ValueForLength(*column_gap, available_size); |
| return LayoutUnit(style.GetFontDescription().ComputedPixelSize()); |
| } |
| |
| LayoutUnit ColumnInlineProgression(LayoutUnit available_size, |
| const ComputedStyle& style) { |
| LayoutUnit column_inline_size = |
| ResolveUsedColumnInlineSize(available_size, style); |
| return column_inline_size + ResolveUsedColumnGap(available_size, style); |
| } |
| |
| NGPhysicalBoxStrut ComputePhysicalMargins( |
| const ComputedStyle& style, |
| LayoutUnit percentage_resolution_size) { |
| if (!style.MayHaveMargin()) |
| return NGPhysicalBoxStrut(); |
| |
| // This function may be called for determining intrinsic margins, clamp |
| // indefinite %-sizes to zero. See: |
| // https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution |
| percentage_resolution_size = |
| percentage_resolution_size.ClampIndefiniteToZero(); |
| |
| return { |
| MinimumValueForLength(style.MarginTop(), percentage_resolution_size), |
| MinimumValueForLength(style.MarginRight(), percentage_resolution_size), |
| MinimumValueForLength(style.MarginBottom(), percentage_resolution_size), |
| MinimumValueForLength(style.MarginLeft(), percentage_resolution_size)}; |
| } |
| |
| NGBoxStrut ComputeMarginsFor(const NGConstraintSpace& constraint_space, |
| const ComputedStyle& style, |
| const NGConstraintSpace& compute_for) { |
| if (!style.MayHaveMargin() || constraint_space.IsAnonymous()) |
| return NGBoxStrut(); |
| LayoutUnit percentage_resolution_size = |
| constraint_space.PercentageResolutionInlineSizeForParentWritingMode(); |
| return ComputePhysicalMargins(style, percentage_resolution_size) |
| .ConvertToLogical(compute_for.GetWritingDirection()); |
| } |
| |
| NGBoxStrut ComputeMinMaxMargins(const ComputedStyle& parent_style, |
| NGLayoutInputNode child) { |
| // An inline child just produces line-boxes which don't have any margins. |
| if (child.IsInline() || !child.Style().MayHaveMargin()) |
| return NGBoxStrut(); |
| |
| const Length& inline_start_margin_length = |
| child.Style().MarginStartUsing(parent_style); |
| const Length& inline_end_margin_length = |
| child.Style().MarginEndUsing(parent_style); |
| |
| // TODO(ikilpatrick): We may want to re-visit calculated margins at some |
| // point. Currently "margin-left: calc(10px + 50%)" will resolve to 0px, but |
| // 10px would be more correct, (as percentages resolve to zero). |
| NGBoxStrut margins; |
| if (inline_start_margin_length.IsFixed()) |
| margins.inline_start = LayoutUnit(inline_start_margin_length.Value()); |
| if (inline_end_margin_length.IsFixed()) |
| margins.inline_end = LayoutUnit(inline_end_margin_length.Value()); |
| |
| return margins; |
| } |
| |
| namespace { |
| |
| NGBoxStrut ComputeBordersInternal(const ComputedStyle& style) { |
| NGBoxStrut borders; |
| borders.inline_start = LayoutUnit(style.BorderStartWidth()); |
| borders.inline_end = LayoutUnit(style.BorderEndWidth()); |
| borders.block_start = LayoutUnit(style.BorderBeforeWidth()); |
| borders.block_end = LayoutUnit(style.BorderAfterWidth()); |
| return borders; |
| } |
| |
| } // namespace |
| |
| NGBoxStrut ComputeBorders(const NGConstraintSpace& constraint_space, |
| const NGBlockNode& node) { |
| // If we are producing an anonymous fragment (e.g. a column), it has no |
| // borders, padding or scrollbars. Using the ones from the container can only |
| // cause trouble. |
| if (constraint_space.IsAnonymous()) |
| return NGBoxStrut(); |
| |
| // If we are a table cell we just access the values set by the parent table |
| // layout as border may be collapsed etc. |
| if (constraint_space.IsTableCell()) |
| return constraint_space.TableCellBorders(); |
| |
| if (node.IsNGTable()) |
| return To<NGTableNode>(node).GetTableBorders()->TableBorder(); |
| |
| return ComputeBordersInternal(node.Style()); |
| } |
| |
| NGBoxStrut ComputeBordersForInline(const ComputedStyle& style) { |
| return ComputeBordersInternal(style); |
| } |
| |
| NGBoxStrut ComputeBordersForTest(const ComputedStyle& style) { |
| return ComputeBordersInternal(style); |
| } |
| |
| NGBoxStrut ComputeIntrinsicPadding(const NGConstraintSpace& constraint_space, |
| const ComputedStyle& style, |
| const NGBoxStrut& scrollbar) { |
| DCHECK(constraint_space.IsTableCell()); |
| |
| // During the "layout" table phase, adjust the given intrinsic-padding to |
| // accommodate the scrollbar. |
| NGBoxStrut intrinsic_padding = constraint_space.TableCellIntrinsicPadding(); |
| if (constraint_space.IsFixedBlockSize()) { |
| if (style.VerticalAlign() == EVerticalAlign::kMiddle) { |
| intrinsic_padding.block_start -= scrollbar.block_end / 2; |
| intrinsic_padding.block_end -= scrollbar.block_end / 2; |
| } else { |
| intrinsic_padding.block_end -= scrollbar.block_end; |
| } |
| } |
| |
| return intrinsic_padding; |
| } |
| |
| NGBoxStrut ComputePadding(const NGConstraintSpace& constraint_space, |
| const ComputedStyle& style) { |
| // If we are producing an anonymous fragment (e.g. a column) we shouldn't |
| // have any padding. |
| if (!style.MayHavePadding() || constraint_space.IsAnonymous()) |
| return NGBoxStrut(); |
| |
| // Tables with collapsed borders don't have any padding. |
| if (style.IsDisplayTableBox() && |
| style.BorderCollapse() == EBorderCollapse::kCollapse) { |
| return NGBoxStrut(); |
| } |
| |
| // This function may be called for determining intrinsic padding, clamp |
| // indefinite %-sizes to zero. See: |
| // https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution |
| LayoutUnit percentage_resolution_size = |
| constraint_space.PercentageResolutionInlineSizeForParentWritingMode() |
| .ClampIndefiniteToZero(); |
| NGBoxStrut padding = { |
| MinimumValueForLength(style.PaddingStart(), percentage_resolution_size), |
| MinimumValueForLength(style.PaddingEnd(), percentage_resolution_size), |
| MinimumValueForLength(style.PaddingBefore(), percentage_resolution_size), |
| MinimumValueForLength(style.PaddingAfter(), percentage_resolution_size)}; |
| |
| if (!RuntimeEnabledFeatures::LayoutNGTableEnabled() && |
| style.Display() == EDisplay::kTableCell) { |
| // Compatibility hack to mach legacy layout. Legacy layout floors padding on |
| // the block sides, but not on the inline sides. o.O |
| padding.block_start = LayoutUnit(padding.block_start.Floor()); |
| padding.block_end = LayoutUnit(padding.block_end.Floor()); |
| } |
| |
| return padding; |
| } |
| |
| NGBoxStrut ComputeScrollbarsForNonAnonymous(const NGBlockNode& node) { |
| const ComputedStyle& style = node.Style(); |
| if (!style.IsScrollContainer() && style.IsScrollbarGutterAuto()) |
| return NGBoxStrut(); |
| const LayoutBox* layout_box = node.GetLayoutBox(); |
| return layout_box->ComputeLogicalScrollbars(); |
| } |
| |
| bool NeedsInlineSizeToResolveLineLeft(const ComputedStyle& style, |
| const ComputedStyle& container_style) { |
| // In RTL, there's no block alignment where we can guarantee that line-left |
| // doesn't depend on the inline size of a fragment. |
| if (IsRtl(container_style.Direction())) |
| return true; |
| |
| return BlockAlignment(style, container_style) != EBlockAlignment::kStart; |
| } |
| |
| void ResolveInlineMargins(const ComputedStyle& style, |
| const ComputedStyle& container_style, |
| LayoutUnit available_inline_size, |
| LayoutUnit inline_size, |
| NGBoxStrut* margins) { |
| DCHECK(margins) << "Margins cannot be NULL here"; |
| const LayoutUnit used_space = inline_size + margins->InlineSum(); |
| const LayoutUnit available_space = available_inline_size - used_space; |
| if (available_space > LayoutUnit()) { |
| EBlockAlignment alignment = BlockAlignment(style, container_style); |
| if (alignment == EBlockAlignment::kCenter) |
| margins->inline_start += available_space / 2; |
| else if (alignment == EBlockAlignment::kEnd) |
| margins->inline_start += available_space; |
| } |
| margins->inline_end = |
| available_inline_size - inline_size - margins->inline_start; |
| } |
| |
| LayoutUnit LineOffsetForTextAlign(ETextAlign text_align, |
| TextDirection direction, |
| LayoutUnit space_left) { |
| bool is_ltr = IsLtr(direction); |
| if (text_align == ETextAlign::kStart || text_align == ETextAlign::kJustify) |
| text_align = is_ltr ? ETextAlign::kLeft : ETextAlign::kRight; |
| else if (text_align == ETextAlign::kEnd) |
| text_align = is_ltr ? ETextAlign::kRight : ETextAlign::kLeft; |
| |
| switch (text_align) { |
| case ETextAlign::kLeft: |
| case ETextAlign::kWebkitLeft: { |
| // The direction of the block should determine what happens with wide |
| // lines. In particular with RTL blocks, wide lines should still spill |
| // out to the left. |
| if (is_ltr) |
| return LayoutUnit(); |
| return space_left.ClampPositiveToZero(); |
| } |
| case ETextAlign::kRight: |
| case ETextAlign::kWebkitRight: { |
| // In RTL, trailing spaces appear on the left of the line. |
| if (UNLIKELY(!is_ltr)) |
| return space_left; |
| // Wide lines spill out of the block based off direction. |
| // So even if text-align is right, if direction is LTR, wide lines |
| // should overflow out of the right side of the block. |
| if (space_left > LayoutUnit()) |
| return space_left; |
| return LayoutUnit(); |
| } |
| case ETextAlign::kCenter: |
| case ETextAlign::kWebkitCenter: { |
| if (is_ltr) |
| return (space_left / 2).ClampNegativeToZero(); |
| // In RTL, trailing spaces appear on the left of the line. |
| if (space_left > LayoutUnit()) |
| return (space_left / 2).ClampNegativeToZero(); |
| // In RTL, wide lines should spill out to the left, same as kRight. |
| return space_left; |
| } |
| default: |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| } |
| |
| // Calculates default content size for html and body elements in quirks mode. |
| // Returns |kIndefiniteSize| in all other cases. |
| LayoutUnit CalculateDefaultBlockSize( |
| const NGConstraintSpace& space, |
| const NGBlockNode& node, |
| const NGBoxStrut& border_scrollbar_padding) { |
| // In quirks mode, html and body elements will completely fill the ICB, block |
| // percentages should resolve against this size. |
| if (node.IsQuirkyAndFillsViewport()) { |
| LayoutUnit block_size = space.AvailableSize().block_size; |
| block_size -= ComputeMarginsForSelf(space, node.Style()).BlockSum(); |
| return std::max(block_size.ClampNegativeToZero(), |
| border_scrollbar_padding.BlockSum()); |
| } |
| return kIndefiniteSize; |
| } |
| |
| namespace { |
| |
| // Calculates default content size for html and body elements in quirks mode. |
| // Returns |kIndefiniteSize| in all other cases. |
| LayoutUnit ComputeInitialBlockSizeForFragment( |
| const NGConstraintSpace& space, |
| const ComputedStyle& style, |
| const NGBoxStrut& border_padding, |
| LayoutUnit intrinsic_size, |
| base::Optional<LayoutUnit> inline_size) { |
| if (space.IsFixedBlockSizeIndefinite()) |
| return intrinsic_size; |
| // <html> and <body> should not be considered as replaced. |
| const bool should_be_considered_as_replaced = false; |
| return ComputeBlockSizeForFragment(space, style, border_padding, |
| intrinsic_size, inline_size, |
| should_be_considered_as_replaced); |
| } |
| |
| // Clamp the inline size of the scrollbar, unless it's larger than the inline |
| // size of the content box, in which case we'll return that instead. Scrollbar |
| // handling is quite bad in such situations, and this method here is just to |
| // make sure that left-hand scrollbars don't mess up scrollWidth. For the full |
| // story, visit http://crbug.com/724255. |
| bool ClampScrollbarToContentBox(NGBoxStrut* scrollbars, |
| LayoutUnit content_box_inline_size) { |
| DCHECK(scrollbars->InlineSum()); |
| if (scrollbars->InlineSum() <= content_box_inline_size) |
| return false; |
| if (scrollbars->inline_end) { |
| DCHECK(!scrollbars->inline_start); |
| scrollbars->inline_end = content_box_inline_size; |
| } else { |
| DCHECK(scrollbars->inline_start); |
| scrollbars->inline_start = content_box_inline_size; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| NGFragmentGeometry CalculateInitialFragmentGeometry( |
| const NGConstraintSpace& constraint_space, |
| const NGBlockNode& node) { |
| DCHECK(node.CanUseNewLayout()); |
| const ComputedStyle& style = node.Style(); |
| NGBoxStrut border = ComputeBorders(constraint_space, node); |
| NGBoxStrut padding = ComputePadding(constraint_space, style); |
| NGBoxStrut scrollbar = ComputeScrollbars(constraint_space, node); |
| NGBoxStrut border_padding = border + padding; |
| NGBoxStrut border_scrollbar_padding = border_padding + scrollbar; |
| |
| // If we have a percentage size, we need to set the |
| // HasPercentHeightDescendants flag correctly so that flexbox knows it may |
| // need to redo layout and can also do some performance optimizations. |
| if (style.LogicalHeight().IsPercentOrCalc() || |
| style.LogicalMinHeight().IsPercentOrCalc() || |
| style.LogicalMaxHeight().IsPercentOrCalc() || |
| style.LogicalTop().IsPercentOrCalc() || |
| style.LogicalBottom().IsPercentOrCalc() || |
| (node.IsFlexItem() && style.FlexBasis().IsPercentOrCalc())) { |
| // This call has the side-effect of setting HasPercentHeightDescendants |
| // correctly. |
| node.GetLayoutBox()->ComputePercentageLogicalHeight(Length::Percent(0)); |
| } |
| |
| if (node.IsReplaced()) { |
| LogicalSize border_box_size; |
| MinMaxSizesInput min_max_input( |
| constraint_space.ReplacedPercentageResolutionBlockSize(), |
| MinMaxSizesType::kIntrinsic); |
| MinMaxSizes intrinsic_min_max_sizes = |
| node.ComputeMinMaxSizes(constraint_space.GetWritingMode(), |
| min_max_input) |
| .sizes; |
| base::Optional<LogicalSize> replaced_size; |
| base::Optional<LogicalSize> aspect_ratio; |
| ComputeReplacedSize(node, constraint_space, intrinsic_min_max_sizes, |
| &replaced_size, &aspect_ratio); |
| bool has_aspect_ratio_without_intrinsic_size = |
| !replaced_size && aspect_ratio && !aspect_ratio->IsEmpty(); |
| if (has_aspect_ratio_without_intrinsic_size) { |
| // TODO(dgrogan): This doesn't obey min/max-width or transferred |
| // min/max-height or margins. Maybe use ResolveInlineLength with |
| // fill-available, then clamp with ComputeMinMaxInlineSizes after adapting |
| // it for Replaced elements. Or use ComputeInlineSizeForFragment with a |
| // ConstraintSpace with StretchInlineSizeIfAuto. |
| DCHECK_GE(constraint_space.AvailableSize().inline_size, LayoutUnit()); |
| border_box_size.inline_size = |
| std::max(constraint_space.AvailableSize().inline_size, |
| border_padding.InlineSum()); |
| border_box_size.block_size = BlockSizeFromAspectRatio( |
| border_padding, *aspect_ratio, EBoxSizing::kContentBox, |
| border_box_size.inline_size); |
| } else { |
| DCHECK(replaced_size.has_value()) |
| << "Images should have at least one of aspect_ratio or replaced_size"; |
| border_box_size = *replaced_size; |
| } |
| return {border_box_size, border, scrollbar, padding}; |
| } |
| |
| LayoutUnit default_block_size = CalculateDefaultBlockSize( |
| constraint_space, node, border_scrollbar_padding); |
| LayoutUnit inline_size = |
| ComputeInlineSizeForFragment(constraint_space, node, border_padding); |
| LogicalSize border_box_size(inline_size, |
| ComputeInitialBlockSizeForFragment( |
| constraint_space, style, border_padding, |
| default_block_size, inline_size)); |
| |
| if (UNLIKELY(border_box_size.inline_size < |
| border_scrollbar_padding.InlineSum() && |
| scrollbar.InlineSum() && !constraint_space.IsAnonymous())) { |
| ClampScrollbarToContentBox( |
| &scrollbar, border_box_size.inline_size - border_padding.InlineSum()); |
| } |
| |
| return {border_box_size, border, scrollbar, padding}; |
| } |
| |
| NGFragmentGeometry CalculateInitialMinMaxFragmentGeometry( |
| const NGConstraintSpace& constraint_space, |
| const NGBlockNode& node) { |
| const ComputedStyle& style = node.Style(); |
| // TODO(ikilpatrick): Investigate if we should bring in the block-size logic |
| // from CalculateChildPercentageBlockSizeForMinMax here. |
| LogicalSize border_box_size = {kIndefiniteSize, kIndefiniteSize}; |
| NGBoxStrut border = ComputeBorders(constraint_space, node); |
| NGBoxStrut padding = ComputePadding(constraint_space, style); |
| NGBoxStrut scrollbar = ComputeScrollbars(constraint_space, node); |
| |
| return {border_box_size, border, scrollbar, padding}; |
| } |
| |
| LogicalSize ShrinkLogicalSize(LogicalSize size, const NGBoxStrut& insets) { |
| if (size.inline_size != kIndefiniteSize) { |
| size.inline_size = |
| (size.inline_size - insets.InlineSum()).ClampNegativeToZero(); |
| } |
| if (size.block_size != kIndefiniteSize) { |
| size.block_size = |
| (size.block_size - insets.BlockSum()).ClampNegativeToZero(); |
| } |
| |
| return size; |
| } |
| |
| LogicalSize CalculateChildAvailableSize( |
| const NGConstraintSpace& space, |
| const NGBlockNode& node, |
| const LogicalSize border_box_size, |
| const NGBoxStrut& border_scrollbar_padding) { |
| LogicalSize child_available_size = |
| ShrinkLogicalSize(border_box_size, border_scrollbar_padding); |
| |
| if (space.IsAnonymous() || node.IsAnonymousBlock()) |
| child_available_size.block_size = space.AvailableSize().block_size; |
| |
| return child_available_size; |
| } |
| |
| namespace { |
| |
| // Implements the common part of the child percentage size calculation. Deals |
| // with how percentages are propagated from parent to child in quirks mode. |
| LogicalSize AdjustChildPercentageSize(const NGConstraintSpace& space, |
| const NGBlockNode node, |
| LogicalSize child_percentage_size, |
| LayoutUnit parent_percentage_block_size) { |
| // Flex items may have a fixed block-size, but children shouldn't resolve |
| // their percentages against this. |
| if (space.IsFixedBlockSizeIndefinite()) { |
| DCHECK(node.IsFlexItem() || space.IsTableCell()); |
| child_percentage_size.block_size = kIndefiniteSize; |
| return child_percentage_size; |
| } |
| |
| bool is_table_cell_in_measure_phase = |
| space.IsTableCell() && !space.IsFixedBlockSize(); |
| // A table-cell during the "measure" phase forces its descendants to have an |
| // indefinite percentage resolution size. |
| // NOTE: If the Layout and ComputeMinMaxSizes ever get merged, this can be |
| // removed (as we'll need to allow for indefinite %-inline-sizes). |
| if (is_table_cell_in_measure_phase) { |
| // Orthogonal cells need to call layout on the cell to determine |
| // size of the table. Because table's inline size is unknown, percentages |
| // are resolved against 0. |
| if (space.IsOrthogonalWritingModeRoot()) |
| child_percentage_size.block_size = LayoutUnit(); |
| else |
| child_percentage_size.block_size = kIndefiniteSize; |
| return child_percentage_size; |
| } |
| |
| // In quirks mode the percentage resolution height is passed from parent to |
| // child. |
| // https://quirks.spec.whatwg.org/#the-percentage-height-calculation-quirk |
| if (child_percentage_size.block_size == kIndefiniteSize && |
| node.UseParentPercentageResolutionBlockSizeForChildren()) |
| child_percentage_size.block_size = parent_percentage_block_size; |
| |
| return child_percentage_size; |
| } |
| |
| } // namespace |
| |
| LogicalSize CalculateChildPercentageSize( |
| const NGConstraintSpace& space, |
| const NGBlockNode node, |
| const LogicalSize child_available_size) { |
| // Anonymous block or spaces should pass the percent size straight through. |
| if (space.IsAnonymous() || node.IsAnonymousBlock()) |
| return space.PercentageResolutionSize(); |
| |
| // Table cell children don't apply the "percentage-quirk". I.e. if their |
| // percentage resolution block-size is indefinite, they don't pass through |
| // their parent's percentage resolution block-size. |
| if (space.TableCellChildLayoutMode() != |
| NGTableCellChildLayoutMode::kNotTableCellChild) |
| return child_available_size; |
| |
| return AdjustChildPercentageSize(space, node, child_available_size, |
| space.PercentageResolutionBlockSize()); |
| } |
| |
| LogicalSize CalculateReplacedChildPercentageSize( |
| const NGConstraintSpace& space, |
| const NGBlockNode node, |
| const LogicalSize child_available_size, |
| const NGBoxStrut& border_scrollbar_padding, |
| const NGBoxStrut& border_padding) { |
| // Anonymous block or spaces should pass the percent size straight through. |
| if (space.IsAnonymous() || node.IsAnonymousBlock()) |
| return space.ReplacedPercentageResolutionSize(); |
| |
| // Replaced descendants of a table-cell which has a definite block-size, |
| // always resolve their percentages against this size (even during the |
| // "layout" pass where the fixed block-size may be different). |
| // |
| // This ensures that between the table-cell "measure" and "layout" passes |
| // the replaced descendants remain the same size. |
| const ComputedStyle& style = node.Style(); |
| if (space.IsTableCell() && style.LogicalHeight().IsFixed()) { |
| LayoutUnit block_size = ComputeBlockSizeForFragmentInternal( |
| space, style, border_padding, kIndefiniteSize /* intrinsic_size */, |
| base::nullopt /* inline_size */, node.ShouldBeConsideredAsReplaced()); |
| DCHECK_NE(block_size, kIndefiniteSize); |
| return {child_available_size.inline_size, |
| (block_size - border_scrollbar_padding.BlockSum()) |
| .ClampNegativeToZero()}; |
| } |
| |
| return AdjustChildPercentageSize( |
| space, node, child_available_size, |
| space.ReplacedPercentageResolutionBlockSize()); |
| } |
| |
| LayoutUnit CalculateChildPercentageBlockSizeForMinMax( |
| const NGConstraintSpace& space, |
| const NGBlockNode node, |
| const NGBoxStrut& border_padding, |
| const NGBoxStrut& scrollbar, |
| LayoutUnit input_percentage_block_size, |
| bool* uses_input_percentage_block_size) { |
| *uses_input_percentage_block_size = false; |
| |
| // Anonymous block or spaces should pass the percent size straight through. |
| if (space.IsAnonymous() || node.IsAnonymousBlock()) { |
| *uses_input_percentage_block_size = true; |
| return input_percentage_block_size; |
| } |
| |
| const ComputedStyle& style = node.Style(); |
| const NGBoxStrut& border_scrollbar_padding = border_padding + scrollbar; |
| LayoutUnit block_size; |
| |
| if (node.IsOutOfFlowPositioned()) { |
| // If this node is OOF-positioned, our size was pre-calculated. |
| block_size = input_percentage_block_size; |
| *uses_input_percentage_block_size = true; |
| } else { |
| block_size = ComputeBlockSizeForFragmentInternal( |
| space, style, border_padding, |
| CalculateDefaultBlockSize(space, node, border_scrollbar_padding), |
| /* inline_size */ base::nullopt, node.ShouldBeConsideredAsReplaced(), |
| /* available_block_size_adjustment */ LayoutUnit(), |
| &input_percentage_block_size); |
| |
| if (style.LogicalMinHeight().IsPercentOrCalc() || |
| style.LogicalHeight().IsPercentOrCalc() || |
| style.LogicalMaxHeight().IsPercentOrCalc()) |
| *uses_input_percentage_block_size = true; |
| } |
| |
| LayoutUnit child_percentage_block_size = |
| block_size == kIndefiniteSize |
| ? kIndefiniteSize |
| : (block_size - border_scrollbar_padding.BlockSum()) |
| .ClampNegativeToZero(); |
| |
| // In quirks mode some 'auto' block-size nodes pass the %-block-size through. |
| if (child_percentage_block_size == kIndefiniteSize && |
| node.UseParentPercentageResolutionBlockSizeForChildren()) { |
| *uses_input_percentage_block_size = true; |
| child_percentage_block_size = input_percentage_block_size; |
| } |
| |
| return child_percentage_block_size; |
| } |
| |
| LayoutUnit ClampIntrinsicBlockSize( |
| const NGConstraintSpace& space, |
| const NGBlockNode& node, |
| const NGBoxStrut& border_scrollbar_padding, |
| LayoutUnit current_intrinsic_block_size, |
| base::Optional<LayoutUnit> body_margin_block_sum) { |
| // Tables don't respect size containment, or apply the "fill viewport" quirk. |
| DCHECK(!node.IsTable()); |
| const ComputedStyle& style = node.Style(); |
| |
| // Apply the "fills viewport" quirk if needed. |
| LayoutUnit available_block_size = space.AvailableSize().block_size; |
| if (node.IsQuirkyAndFillsViewport() && style.LogicalHeight().IsAuto() && |
| available_block_size != kIndefiniteSize) { |
| DCHECK_EQ(node.IsBody() && !node.CreatesNewFormattingContext(), |
| body_margin_block_sum.has_value()); |
| LayoutUnit margin_sum = body_margin_block_sum.value_or( |
| ComputeMarginsForSelf(space, style).BlockSum()); |
| current_intrinsic_block_size = std::max( |
| current_intrinsic_block_size, |
| (space.AvailableSize().block_size - margin_sum).ClampNegativeToZero()); |
| } |
| |
| // If the intrinsic size was overridden, then use that. |
| LayoutUnit intrinsic_size_override = node.OverrideIntrinsicContentBlockSize(); |
| if (intrinsic_size_override != kIndefiniteSize) { |
| return intrinsic_size_override + border_scrollbar_padding.BlockSum(); |
| } else { |
| LayoutUnit default_intrinsic_size = node.DefaultIntrinsicContentBlockSize(); |
| if (default_intrinsic_size != kIndefiniteSize) { |
| // <textarea>'s intrinsic size should ignore scrollbar existence. |
| if (node.IsTextArea()) { |
| return default_intrinsic_size + border_scrollbar_padding.BlockSum() - |
| ComputeScrollbars(space, node).BlockSum(); |
| } |
| return default_intrinsic_size + border_scrollbar_padding.BlockSum(); |
| } |
| } |
| |
| // If we have size containment, we ignore child contributions to intrinsic |
| // sizing. |
| if (node.ShouldApplyBlockSizeContainment()) |
| return border_scrollbar_padding.BlockSum(); |
| return current_intrinsic_block_size; |
| } |
| |
| base::Optional<MinMaxSizesResult> CalculateMinMaxSizesIgnoringChildren( |
| const NGBlockNode& node, |
| const NGBoxStrut& border_scrollbar_padding) { |
| MinMaxSizes sizes; |
| sizes += border_scrollbar_padding.InlineSum(); |
| |
| // If intrinsic size was overridden, then use that. |
| const LayoutUnit intrinsic_size_override = |
| node.OverrideIntrinsicContentInlineSize(); |
| if (intrinsic_size_override != kIndefiniteSize) { |
| sizes += intrinsic_size_override; |
| return MinMaxSizesResult{sizes, |
| /* depends_on_percentage_block_size */ false}; |
| } else { |
| LayoutUnit default_inline_size = node.DefaultIntrinsicContentInlineSize(); |
| if (default_inline_size != kIndefiniteSize) { |
| sizes += default_inline_size; |
| // <textarea>'s intrinsic size should ignore scrollbar existence. |
| if (node.IsTextArea()) |
| sizes -= ComputeScrollbarsForNonAnonymous(node).InlineSum(); |
| return MinMaxSizesResult{sizes, |
| /* depends_on_percentage_block_size */ false}; |
| } |
| } |
| |
| // Size contained elements don't consider children for intrinsic sizing. |
| // Also, if we don't have children, we can determine the size immediately. |
| if (node.ShouldApplyInlineSizeContainment() || !node.FirstChild()) { |
| return MinMaxSizesResult{sizes, |
| /* depends_on_percentage_block_size */ false}; |
| } |
| |
| return base::nullopt; |
| } |
| |
| } // namespace blink |