blob: 3c5d9f618603edc1dcc97c896337186f9686d772 [file] [log] [blame]
// Copyright 2018 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_flex_layout_algorithm.h"
#include <memory>
#include "third_party/blink/renderer/core/layout/flexible_box_algorithm.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_flexible_box.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.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_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
NGFlexLayoutAlgorithm::NGFlexLayoutAlgorithm(
const NGLayoutAlgorithmParams& params)
: NGLayoutAlgorithm(params),
border_padding_(params.fragment_geometry.border +
params.fragment_geometry.padding),
border_scrollbar_padding_(border_padding_ +
params.fragment_geometry.scrollbar),
is_column_(Style().IsColumnFlexDirection()) {
container_builder_.SetIsNewFormattingContext(
params.space.IsNewFormattingContext());
container_builder_.SetInitialFragmentGeometry(params.fragment_geometry);
}
bool NGFlexLayoutAlgorithm::MainAxisIsInlineAxis(
const NGBlockNode& child) const {
return child.Style().IsHorizontalWritingMode() ==
FlexLayoutAlgorithm::IsHorizontalFlow(Style());
}
LayoutUnit NGFlexLayoutAlgorithm::MainAxisContentExtent(
LayoutUnit sum_hypothetical_main_size) {
if (Style().IsColumnFlexDirection()) {
return ComputeBlockSizeForFragment(
ConstraintSpace(), Node(), border_padding_,
sum_hypothetical_main_size + (border_padding_).BlockSum()) -
border_scrollbar_padding_.BlockSum();
}
return content_box_size_.inline_size;
}
void NGFlexLayoutAlgorithm::HandleOutOfFlowPositioned(NGBlockNode child) {
// TODO(dgrogan): There's stuff from
// https://www.w3.org/TR/css-flexbox-1/#abspos-items that isn't done here.
// Specifically, neither rtl nor alignment is handled here, at least.
// Look at LayoutFlexibleBox::PrepareChildForPositionedLayout and
// SetStaticPositionForPositionedLayout to see how to statically position
// this.
container_builder_.AddOutOfFlowChildCandidate(
child, {border_scrollbar_padding_.inline_start,
border_scrollbar_padding_.block_start});
}
bool NGFlexLayoutAlgorithm::IsContainerCrossSizeDefinite() const {
// A column flexbox's cross axis is an inline size, so is definite.
if (is_column_)
return true;
// If this flex container is also a flex item, it might have a definite size
// imposed on it by its parent flex container.
if (ConstraintSpace().IsFixedBlockSize() &&
!ConstraintSpace().IsFixedBlockSizeIndefinite())
return true;
Length cross_size = Style().LogicalHeight();
if (cross_size.IsAuto())
return false;
return !BlockLengthUnresolvable(ConstraintSpace(), cross_size,
LengthResolvePhase::kLayout);
}
bool NGFlexLayoutAlgorithm::DoesItemStretch(const NGBlockNode& child) const {
DCHECK(IsItemCrossSizeAuto(child));
const ComputedStyle& child_style = child.Style();
// https://drafts.csswg.org/css-flexbox/#valdef-align-items-stretch
// If the cross size property of the flex item computes to auto, and neither
// of the cross-axis margins are auto, the flex item is stretched.
if (is_horizontal_flow_ &&
(child_style.MarginTop().IsAuto() || child_style.MarginBottom().IsAuto()))
return false;
if (!is_horizontal_flow_ &&
(child_style.MarginLeft().IsAuto() || child_style.MarginRight().IsAuto()))
return false;
return FlexLayoutAlgorithm::AlignmentForChild(Style(), child_style) ==
ItemPosition::kStretch;
}
bool NGFlexLayoutAlgorithm::IsItemCrossSizeAuto(
const NGBlockNode& child) const {
const ComputedStyle& child_style = child.Style();
Length cross_size =
is_horizontal_flow_ ? child_style.Height() : child_style.Width();
// The following DCHECK assures that the container's cross axis is the
// same direction as the inline axis of the item, so the cross axis is never
// the item's block axis. That absolves this function of having to check for
// the cross axis being auto due to being a block-size percent without a
// definite block size to resolve against.
DCHECK(!MainAxisIsInlineAxis(child));
return cross_size.IsAuto();
}
// This function is used to handle two requirements from the spec.
// (1) Calculating flex base size; case 3E at
// https://drafts.csswg.org/css-flexbox/#algo-main-item : If a cross size is
// needed to determine the main size (e.g. when the flex item’s main size is
// in its block axis) and the flex item’s cross size is auto and not
// definite, in this calculation use fit-content as the flex item’s cross size.
// The flex base size is the item’s resulting main size.
// (2) Cross size determination after main size has been calculated.
// https://drafts.csswg.org/css-flexbox/#algo-cross-item : Determine the
// hypothetical cross size of each item by performing layout with the used main
// size and the available space, treating auto as fit-content.
bool NGFlexLayoutAlgorithm::ShouldItemShrinkToFit(
const NGBlockNode& child) const {
if (MainAxisIsInlineAxis(child)) {
// The cross size isn't needed to determine main size, so don't use
// fit-content.
return false;
}
if (!IsItemCrossSizeAuto(child)) {
// The cross size is already specified, so don't use fit-content.
return false;
}
// We also don't use fit-content if the item qualifies for the first case in
// https://drafts.csswg.org/css-flexbox/#definite-sizes :
// 1. If a single-line flex container has a definite cross size, the outer
// cross size of any stretched flex items is the flex container’s inner cross
// size (clamped to the flex item’s min and max cross size) and is considered
// definite.
if (!algorithm_->IsMultiline() && IsContainerCrossSizeDefinite() &&
DoesItemStretch(child))
return false;
return true;
}
void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() {
for (NGLayoutInputNode generic_child = Node().FirstChild(); generic_child;
generic_child = generic_child.NextSibling()) {
auto child = To<NGBlockNode>(generic_child);
if (child.IsOutOfFlowPositioned()) {
HandleOutOfFlowPositioned(child);
continue;
}
const ComputedStyle& child_style = child.Style();
NGConstraintSpaceBuilder space_builder(ConstraintSpace(),
child_style.GetWritingMode(),
/* is_new_fc */ true);
SetOrthogonalFallbackInlineSizeIfNeeded(Style(), child, &space_builder);
if (ShouldItemShrinkToFit(child))
space_builder.SetIsShrinkToFit(true);
// TODO(dgrogan): Change SetPercentageResolutionSize everywhere in this file
// to use CalculateChildPercentageSize.
space_builder.SetAvailableSize(content_box_size_);
space_builder.SetPercentageResolutionSize(content_box_size_);
NGConstraintSpace child_space = space_builder.ToConstraintSpace();
NGBoxStrut border_padding_in_child_writing_mode =
ComputeBorders(child_space, child) +
ComputePadding(child_space, child_style);
NGBoxStrut border_scrollbar_padding_in_child_writing_mode =
border_padding_in_child_writing_mode +
ComputeScrollbars(child_space, child);
NGPhysicalBoxStrut physical_border_padding(
border_padding_in_child_writing_mode.ConvertToPhysical(
child_style.GetWritingMode(), child_style.Direction()));
NGPhysicalBoxStrut physical_border_scrollbar_padding(
border_scrollbar_padding_in_child_writing_mode.ConvertToPhysical(
child_style.GetWritingMode(), child_style.Direction()));
LayoutUnit main_axis_border_padding =
is_horizontal_flow_ ? physical_border_padding.HorizontalSum()
: physical_border_padding.VerticalSum();
LayoutUnit main_axis_border_scrollbar_padding =
is_horizontal_flow_ ? physical_border_scrollbar_padding.HorizontalSum()
: physical_border_scrollbar_padding.VerticalSum();
// We want the child's min/max size in its writing mode, not ours. We'll
// only ever use it if the child's inline axis is our main axis.
MinMaxSizeInput input(
/* percentage_resolution_block_size */ content_box_size_.block_size);
MinMaxSize intrinsic_sizes_border_box = child.ComputeMinMaxSize(
child_style.GetWritingMode(), input, &child_space);
// TODO(dgrogan): Don't layout every time, just when you need to.
// Use ChildHasIntrinsicMainAxisSize as a guide.
scoped_refptr<const NGLayoutResult> layout_result =
child.Layout(child_space, nullptr /*break token*/);
NGFragment fragment_in_child_writing_mode(
child_style.GetWritingMode(), layout_result->PhysicalFragment());
LayoutUnit flex_base_border_box;
const Length& specified_length_in_main_axis =
is_horizontal_flow_ ? child_style.Width() : child_style.Height();
const Length& flex_basis = child_style.FlexBasis();
// TODO(dgrogan): Generalize IsAuto: See the <'width'> section of
// https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis
// and https://drafts.csswg.org/css-flexbox/#flex-basis-property, which says
// that if a flex-basis value would resolve to auto (but not literally auto)
// we should interpret it as flex-basis:content.
if (flex_basis.IsAuto() && specified_length_in_main_axis.IsAuto()) {
if (MainAxisIsInlineAxis(child))
flex_base_border_box = intrinsic_sizes_border_box.max_size;
else
flex_base_border_box = fragment_in_child_writing_mode.BlockSize();
} else {
// TODO(dgrogan): Check for definiteness.
// This block covers case A in
// https://drafts.csswg.org/css-flexbox/#algo-main-item.
const Length& length_to_resolve =
flex_basis.IsAuto() ? specified_length_in_main_axis : flex_basis;
DCHECK(!length_to_resolve.IsAuto());
if (MainAxisIsInlineAxis(child)) {
flex_base_border_box = ResolveMainInlineLength(
child_space, child_style, border_padding_in_child_writing_mode,
intrinsic_sizes_border_box, length_to_resolve);
} else {
// Flex container's main axis is in child's block direction. Child's
// flex basis is in child's block direction.
flex_base_border_box = ResolveMainBlockLength(
child_space, child_style, border_padding_in_child_writing_mode,
length_to_resolve, fragment_in_child_writing_mode.BlockSize(),
LengthResolvePhase::kLayout);
}
}
// Spec calls this "flex base size"
// https://www.w3.org/TR/css-flexbox-1/#algo-main-item
// Blink's FlexibleBoxAlgorithm expects it to be content + scrollbar widths,
// but no padding or border.
LayoutUnit flex_base_content_size =
flex_base_border_box - main_axis_border_padding;
NGPhysicalBoxStrut physical_child_margins =
ComputePhysicalMargins(child_space, child_style);
LayoutUnit main_axis_margin = is_horizontal_flow_
? physical_child_margins.HorizontalSum()
: physical_child_margins.VerticalSum();
MinMaxSize min_max_sizes_in_main_axis_direction{LayoutUnit(),
LayoutUnit::Max()};
const Length& max_property_in_main_axis = is_horizontal_flow_
? child.Style().MaxWidth()
: child.Style().MaxHeight();
if (MainAxisIsInlineAxis(child)) {
min_max_sizes_in_main_axis_direction.max_size = ResolveMaxInlineLength(
child_space, child_style, border_padding_in_child_writing_mode,
intrinsic_sizes_border_box, max_property_in_main_axis,
LengthResolvePhase::kLayout);
} else {
min_max_sizes_in_main_axis_direction.max_size = ResolveMaxBlockLength(
child_space, child_style, border_padding_in_child_writing_mode,
max_property_in_main_axis, fragment_in_child_writing_mode.BlockSize(),
LengthResolvePhase::kLayout);
}
const Length& min = is_horizontal_flow_ ? child.Style().MinWidth()
: child.Style().MinHeight();
if (min.IsAuto()) {
if (algorithm_->ShouldApplyMinSizeAutoForChild(*child.GetLayoutBox())) {
// TODO(dgrogan): Do the aspect ratio parts of
// https://www.w3.org/TR/css-flexbox-1/#min-size-auto
LayoutUnit content_size_suggestion =
MainAxisIsInlineAxis(child) ? intrinsic_sizes_border_box.min_size
: layout_result->IntrinsicBlockSize();
content_size_suggestion =
std::min(content_size_suggestion,
min_max_sizes_in_main_axis_direction.max_size);
content_size_suggestion -= main_axis_border_scrollbar_padding;
LayoutUnit specified_size_suggestion(LayoutUnit::Max());
// If the item’s computed main size property is definite, then the
// specified size suggestion is that size.
if (MainAxisIsInlineAxis(child)) {
if (!specified_length_in_main_axis.IsAuto()) {
// TODO(dgrogan): Optimization opportunity: we may have already
// resolved specified_length_in_main_axis in the flex basis
// calculation. Reuse that if possible.
specified_size_suggestion = ResolveMainInlineLength(
child_space, child_style, border_padding_in_child_writing_mode,
intrinsic_sizes_border_box, specified_length_in_main_axis);
}
} else if (!specified_length_in_main_axis.IsAuto() &&
!BlockLengthUnresolvable(child_space,
specified_length_in_main_axis,
LengthResolvePhase::kLayout)) {
specified_size_suggestion = ResolveMainBlockLength(
child_space, child_style, border_padding_in_child_writing_mode,
specified_length_in_main_axis,
layout_result->IntrinsicBlockSize(), LengthResolvePhase::kLayout);
DCHECK_NE(specified_size_suggestion, kIndefiniteSize);
}
// Spec says to clamp specified_size_suggestion by max size but because
// content_size_suggestion already is, and we take the min of those
// two, we don't need to clamp specified_size_suggestion.
// https://github.com/w3c/csswg-drafts/issues/3669
if (specified_size_suggestion != LayoutUnit::Max())
specified_size_suggestion -= main_axis_border_scrollbar_padding;
min_max_sizes_in_main_axis_direction.min_size =
std::min(specified_size_suggestion, content_size_suggestion);
}
} else if (MainAxisIsInlineAxis(child)) {
min_max_sizes_in_main_axis_direction.min_size =
ResolveMinInlineLength(
child_space, child_style, border_padding_in_child_writing_mode,
intrinsic_sizes_border_box, min, LengthResolvePhase::kLayout) -
main_axis_border_scrollbar_padding;
// TODO(dgrogan): No tests changed status as result of subtracting
// main_axis_border_scrollbar_padding. It may be untested.
} else {
min_max_sizes_in_main_axis_direction.min_size =
ResolveMinBlockLength(child_space, child_style,
border_padding_in_child_writing_mode, min,
fragment_in_child_writing_mode.BlockSize(),
LengthResolvePhase::kLayout) -
main_axis_border_scrollbar_padding;
// TODO(dgrogan): Same as above WRT subtracting
// main_axis_border_scrollbar_padding. It may be untested.
}
algorithm_
->emplace_back(child.GetLayoutBox(), flex_base_content_size,
min_max_sizes_in_main_axis_direction,
main_axis_border_padding, main_axis_margin)
.ng_input_node = child;
}
}
scoped_refptr<const NGLayoutResult> NGFlexLayoutAlgorithm::Layout() {
border_box_size_ = container_builder_.InitialBorderBoxSize();
content_box_size_ =
ShrinkAvailableSize(border_box_size_, border_scrollbar_padding_);
const LayoutUnit line_break_length = MainAxisContentExtent(LayoutUnit::Max());
algorithm_.emplace(&Style(), line_break_length);
is_horizontal_flow_ = algorithm_->IsHorizontalFlow();
ConstructAndAppendFlexItems();
LayoutUnit main_axis_offset = border_scrollbar_padding_.inline_start;
LayoutUnit cross_axis_offset = border_scrollbar_padding_.block_start;
if (is_column_) {
main_axis_offset = border_scrollbar_padding_.block_start;
cross_axis_offset = border_scrollbar_padding_.inline_start;
}
FlexLine* line;
while (
(line = algorithm_->ComputeNextFlexLine(border_box_size_.inline_size))) {
line->SetContainerMainInnerSize(
MainAxisContentExtent(line->sum_hypothetical_main_size));
line->FreezeInflexibleItems();
while (!line->ResolveFlexibleLengths()) {
continue;
}
for (wtf_size_t i = 0; i < line->line_items.size(); ++i) {
FlexItem& flex_item = line->line_items[i];
WritingMode child_writing_mode =
flex_item.ng_input_node.Style().GetWritingMode();
NGConstraintSpaceBuilder space_builder(ConstraintSpace(),
child_writing_mode,
/* is_new_fc */ true);
SetOrthogonalFallbackInlineSizeIfNeeded(Style(), flex_item.ng_input_node,
&space_builder);
LogicalSize available_size;
if (is_column_) {
available_size.inline_size = content_box_size_.inline_size;
available_size.block_size =
flex_item.flexed_content_size + flex_item.main_axis_border_padding;
space_builder.SetIsFixedBlockSize(true);
// TODO(dgrogan): Set IsFixedBlockSizeIndefinite if neither the item's
// nor container's main size is definite. (The latter being exception 2
// from https://drafts.csswg.org/css-flexbox/#definite-sizes )
} else {
available_size.inline_size =
flex_item.flexed_content_size + flex_item.main_axis_border_padding;
available_size.block_size = content_box_size_.block_size;
space_builder.SetIsFixedInlineSize(true);
}
space_builder.SetAvailableSize(available_size);
// https://drafts.csswg.org/css-flexbox/#algo-cross-item
// Determine the hypothetical cross size of each item by performing layout
// with the used main size and the available space, treating auto as
// fit-content.
if (ShouldItemShrinkToFit(flex_item.ng_input_node))
space_builder.SetIsShrinkToFit(true);
space_builder.SetPercentageResolutionSize(content_box_size_);
NGConstraintSpace child_space = space_builder.ToConstraintSpace();
flex_item.layout_result =
flex_item.ng_input_node.Layout(child_space, nullptr /*break token*/);
flex_item.cross_axis_size =
is_horizontal_flow_
? flex_item.layout_result->PhysicalFragment().Size().height
: flex_item.layout_result->PhysicalFragment().Size().width;
}
// cross_axis_offset is updated in each iteration of the loop, for passing
// in to the next iteration.
line->ComputeLineItemsPosition(main_axis_offset, cross_axis_offset);
}
LayoutUnit intrinsic_block_size = algorithm_->IntrinsicContentBlockSize() +
border_scrollbar_padding_.BlockSum();
LayoutUnit block_size = ComputeBlockSizeForFragment(
ConstraintSpace(), Node(), border_padding_, intrinsic_block_size);
container_builder_.SetIntrinsicBlockSize(intrinsic_block_size);
container_builder_.SetBlockSize(block_size);
GiveLinesAndItemsFinalPositionAndSize();
NGOutOfFlowLayoutPart(
Node(), ConstraintSpace(),
container_builder_.Borders() + container_builder_.Scrollbar(),
&container_builder_)
.Run();
return container_builder_.ToBoxFragment();
}
void NGFlexLayoutAlgorithm::GiveLinesAndItemsFinalPositionAndSize() {
// TODO(dgrogan): This needs to eventually encompass all of the behavior in
// LayoutFlexibleBox::RepositionLogicalHeightDependentFlexItems. It currently
// does AlignFlexLines and the stretch part of AlignChildren.
LayoutUnit final_content_cross_size =
is_column_ ? container_builder_.InlineSize() -
border_scrollbar_padding_.InlineSum()
: container_builder_.BlockSize() -
border_scrollbar_padding_.BlockSum();
if (!algorithm_->IsMultiline() && !algorithm_->FlexLines().IsEmpty())
algorithm_->FlexLines()[0].cross_axis_extent = final_content_cross_size;
algorithm_->AlignFlexLines(final_content_cross_size);
for (FlexLine& line_context : algorithm_->FlexLines()) {
for (wtf_size_t child_number = 0;
child_number < line_context.line_items.size(); ++child_number) {
FlexItem& flex_item = line_context.line_items[child_number];
// TODO(dgrogan): Make this obey the rule that the item stretches only if
// neither of the cross-axis margins are auto from
// https://drafts.csswg.org/css-flexbox/#valdef-align-items-stretch
// Just use DoesItemStretch here?
if (flex_item.Alignment() == ItemPosition::kStretch) {
flex_item.ComputeStretchedSize();
WritingMode child_writing_mode =
flex_item.ng_input_node.Style().GetWritingMode();
NGConstraintSpaceBuilder space_builder(ConstraintSpace(),
child_writing_mode,
/* is_new_fc */ true);
SetOrthogonalFallbackInlineSizeIfNeeded(
Style(), flex_item.ng_input_node, &space_builder);
LogicalSize available_size(
flex_item.flexed_content_size + flex_item.main_axis_border_padding,
flex_item.cross_axis_size);
if (is_column_) {
available_size.Transpose();
// TODO(dgrogan): Set IsFixedBlockSizeIndefinite if neither the item's
// nor container's main size is definite. (The latter being exception
// 2 from https://drafts.csswg.org/css-flexbox/#definite-sizes )
}
space_builder.SetAvailableSize(available_size);
space_builder.SetPercentageResolutionSize(content_box_size_);
space_builder.SetIsFixedInlineSize(true);
space_builder.SetIsFixedBlockSize(true);
NGConstraintSpace child_space = space_builder.ToConstraintSpace();
flex_item.layout_result = flex_item.ng_input_node.Layout(
child_space, /* break_token */ nullptr);
}
// TODO(dgrogan): Add an extra pass for kColumnReverse containers like
// legacy does in LayoutColumnReverse.
// flex_item.desired_location stores the main axis offset in X and the
// cross axis offset in Y. But AddChild wants offset from parent
// rectangle, so we have to transpose for columns. AddChild takes care of
// any writing mode differences though.
LayoutPoint location = is_column_
? flex_item.desired_location.TransposedPoint()
: flex_item.desired_location;
container_builder_.AddChild(flex_item.layout_result->PhysicalFragment(),
{location.X(), location.Y()});
}
}
}
base::Optional<MinMaxSize> NGFlexLayoutAlgorithm::ComputeMinMaxSize(
const MinMaxSizeInput& input) const {
base::Optional<MinMaxSize> sizes = CalculateMinMaxSizesIgnoringChildren(
Node(), border_scrollbar_padding_, input.size_type);
if (sizes)
return sizes;
sizes.emplace();
LayoutUnit child_percentage_resolution_block_size =
CalculateChildPercentageBlockSizeForMinMax(
ConstraintSpace(), Node(), border_padding_,
input.percentage_resolution_block_size);
// Use default MinMaxSizeInput:
// - Children of flexbox ignore any specified float properties, so children
// never have to take floated siblings into account, and external floats
// don't make it through the new formatting context that flexbox
// establishes.
// - We want the child's border box MinMaxSize, which is the default.
MinMaxSizeInput child_input(child_percentage_resolution_block_size);
for (NGLayoutInputNode generic_child = Node().FirstChild(); generic_child;
generic_child = generic_child.NextSibling()) {
auto child = To<NGBlockNode>(generic_child);
if (child.IsOutOfFlowPositioned())
continue;
MinMaxSize child_min_max_sizes =
ComputeMinAndMaxContentContribution(Style(), child, child_input);
NGBoxStrut child_margins = ComputeMinMaxMargins(Style(), child);
child_min_max_sizes += child_margins.InlineSum();
if (is_column_) {
sizes->min_size = std::max(sizes->min_size, child_min_max_sizes.min_size);
sizes->max_size = std::max(sizes->max_size, child_min_max_sizes.max_size);
} else {
sizes->max_size += child_min_max_sizes.max_size;
if (IsMultiline()) {
sizes->min_size =
std::max(sizes->min_size, child_min_max_sizes.min_size);
} else {
sizes->min_size += child_min_max_sizes.min_size;
}
}
}
sizes->max_size = std::max(sizes->max_size, sizes->min_size);
// Due to negative margins, it is possible that we calculated a negative
// intrinsic width. Make sure that we never return a negative width.
sizes->Encompass(LayoutUnit());
if (input.size_type == NGMinMaxSizeType::kBorderBoxSize)
*sizes += border_scrollbar_padding_.InlineSum();
return sizes;
}
bool NGFlexLayoutAlgorithm::IsMultiline() const {
return Style().FlexWrap() != EFlexWrap::kNowrap;
}
} // namespace blink