aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020
diff options
context:
space:
mode:
authorOriol Brufau <obrufau@igalia.com>2025-03-19 10:03:49 +0100
committerGitHub <noreply@github.com>2025-03-19 09:03:49 +0000
commitba6c3916fc916daeffd1e8d2c7ccba21d0eb1041 (patch)
tree9f3b4db94b5fcecc73d78fc2189ffd3354e2fb04 /components/layout_2020
parent2362e4c134b3534287465092d6305043e5a580a6 (diff)
downloadservo-ba6c3916fc916daeffd1e8d2c7ccba21d0eb1041.tar.gz
servo-ba6c3916fc916daeffd1e8d2c7ccba21d0eb1041.zip
layout: Support min/max main keyword sizes in flexbox (#35961)
Adds support for min-content, max-content, fit-content and stretch on the min and max main size properties of a flex item. I'm removing `automatic_min_size()` and `flex_base_size()` because they would need to share so much code among themselves and their one caller that it's simpler to just inline the code. Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Diffstat (limited to 'components/layout_2020')
-rw-r--r--components/layout_2020/flexbox/layout.rs442
-rw-r--r--components/layout_2020/geom.rs6
2 files changed, 159 insertions, 289 deletions
diff --git a/components/layout_2020/flexbox/layout.rs b/components/layout_2020/flexbox/layout.rs
index c81524314ac..a95f2fd1143 100644
--- a/components/layout_2020/flexbox/layout.rs
+++ b/components/layout_2020/flexbox/layout.rs
@@ -21,7 +21,7 @@ use style::properties::longhands::flex_direction::computed_value::T as FlexDirec
use style::properties::longhands::flex_wrap::computed_value::T as FlexWrap;
use style::values::computed::LengthPercentage;
use style::values::generics::flex::GenericFlexBasis as FlexBasis;
-use style::values::generics::length::{GenericLengthPercentageOrAuto, LengthPercentageOrNormal};
+use style::values::generics::length::LengthPercentageOrNormal;
use style::values::specified::align::AlignFlags;
use super::geom::{FlexAxis, FlexRelativeRect, FlexRelativeSides, FlexRelativeVec2};
@@ -2203,6 +2203,11 @@ impl FlexItemBox {
style.writing_mode.is_horizontal(),
flex_axis,
);
+ let main_axis = if cross_axis_is_item_block_axis {
+ Direction::Inline
+ } else {
+ Direction::Block
+ };
let ContentBoxSizesAndPBM {
content_box_sizes,
@@ -2210,11 +2215,6 @@ impl FlexItemBox {
depends_on_block_constraints,
} = content_box_sizes_and_pbm;
- let content_box_size = content_box_sizes.map(|size| size.preferred);
- // TODO(#32853): handle size keywords.
- let content_min_box_size = content_box_sizes.map(|size| size.min.to_auto_or());
- let content_max_box_size = content_box_sizes.map(|size| size.max.to_numeric());
-
let preferred_aspect_ratio = self
.independent_formatting_context
.preferred_aspect_ratio(&pbm.padding_border_sums);
@@ -2229,90 +2229,177 @@ impl FlexItemBox {
} + margin_auto_is_zero.sum_by_axis();
let auto_cross_size_stretches_to_container_size =
config.item_with_auto_cross_size_stretches_to_container_size(style, &margin);
-
- let flex_relative_content_box_size = flex_axis.vec2_to_flex_relative(content_box_size);
- let flex_relative_content_max_size = flex_axis.vec2_to_flex_relative(content_max_box_size);
- let flex_relative_content_min_size = flex_axis.vec2_to_flex_relative(content_min_box_size);
+ let (content_main_sizes, content_cross_sizes) = match flex_axis {
+ FlexAxis::Row => (&content_box_sizes.inline, &content_box_sizes.block),
+ FlexAxis::Column => (&content_box_sizes.block, &content_box_sizes.inline),
+ };
let containing_block_size = flex_axis.vec2_to_flex_relative(containing_block.size);
- let flex_relative_content_min_size = FlexRelativeVec2 {
- main: flex_relative_content_min_size.main.auto_is(|| {
- self.automatic_min_size(
- layout_context,
- containing_block_size,
- cross_axis_is_item_block_axis,
- flex_relative_content_box_size,
- flex_relative_content_min_size,
- flex_relative_content_max_size,
- preferred_aspect_ratio,
- &pbm_auto_is_zero,
- auto_cross_size_stretches_to_container_size,
- |item| {
- let min_size_auto_is_zero = content_min_box_size.auto_is(Au::zero);
-
- item.layout_for_block_content_size(
- flex_context_getter(),
- pbm,
- content_box_size,
- min_size_auto_is_zero,
- content_max_box_size,
- preferred_aspect_ratio,
- auto_cross_size_stretches_to_container_size,
- IntrinsicSizingMode::Size,
- )
- },
- )
- }),
- cross: flex_relative_content_min_size.cross.auto_is(Au::zero),
+ let stretch_size = FlexRelativeVec2 {
+ main: containing_block_size
+ .main
+ .map(|v| Au::zero().max(v - pbm_auto_is_zero.main)),
+ cross: containing_block_size
+ .cross
+ .map(|v| Au::zero().max(v - pbm_auto_is_zero.cross)),
};
- let align_self = AlignItems(config.resolve_align_self_for_child(style));
- let (flex_base_size, flex_base_size_is_definite) = self.flex_base_size(
- layout_context,
- containing_block_size,
- cross_axis_is_item_block_axis,
- flex_relative_content_box_size,
- flex_relative_content_min_size,
- flex_relative_content_max_size,
- preferred_aspect_ratio,
- padding_border,
- &pbm_auto_is_zero,
- auto_cross_size_stretches_to_container_size,
- |item| {
- let min_size = flex_axis.vec2_to_flow_relative(flex_relative_content_min_size);
- item.layout_for_block_content_size(
+
+ // <https://drafts.csswg.org/css-flexbox/#definite-sizes>
+ // > If a single-line flex container has a definite cross size, the automatic preferred
+ // > 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.
+ let (preferred_cross_size, min_cross_size, max_cross_size) = content_cross_sizes
+ .resolve_each_extrinsic(
+ if auto_cross_size_stretches_to_container_size {
+ Size::Stretch
+ } else {
+ Size::FitContent
+ },
+ Au::zero(),
+ stretch_size.cross,
+ );
+ let cross_size = SizeConstraint::new(preferred_cross_size, min_cross_size, max_cross_size);
+
+ // <https://drafts.csswg.org/css-flexbox/#transferred-size-suggestion>
+ // > If the item has a preferred aspect ratio and its preferred cross size is definite, then the
+ // > transferred size suggestion is that size (clamped by its minimum and maximum cross sizes if they
+ // > are definite), converted through the aspect ratio. It is otherwise undefined.
+ let transferred_size_suggestion =
+ LazyCell::new(|| match (preferred_aspect_ratio, cross_size) {
+ (Some(ratio), SizeConstraint::Definite(cross_size)) => {
+ Some(ratio.compute_dependent_size(main_axis, cross_size))
+ },
+ _ => None,
+ });
+
+ // <https://drafts.csswg.org/css-flexbox/#algo-main-item>
+ let flex_base_size_is_definite = Cell::new(true);
+ let main_content_sizes = LazyCell::new(|| {
+ let flex_item = &self.independent_formatting_context;
+ // > B: If the flex item has ...
+ // > - a preferred aspect ratio,
+ // > - a used flex basis of content, and
+ // > - a definite cross size,
+ // > then the flex base size is calculated from its used cross size and the flex item’s aspect ratio.
+ if let Some(transferred_size_suggestion) = *transferred_size_suggestion {
+ return transferred_size_suggestion.into();
+ }
+
+ flex_base_size_is_definite.set(false);
+
+ // FIXME: implement cases C, D.
+
+ // > E. Otherwise, size the item into the available space using its used flex basis in place of
+ // > its main size, treating a value of content as max-content. 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, or when
+ // > it has a preferred aspect ratio) 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.
+ if cross_axis_is_item_block_axis {
+ // The main axis is the inline axis, so we can get the content size from the normal
+ // preferred widths calculation.
+ let constraint_space =
+ ConstraintSpace::new(cross_size, style.writing_mode, preferred_aspect_ratio);
+ let content_sizes = flex_item
+ .inline_content_sizes(layout_context, &constraint_space)
+ .sizes;
+ if let Some(ratio) = preferred_aspect_ratio {
+ let transferred_min = ratio.compute_dependent_size(main_axis, min_cross_size);
+ let transferred_max =
+ max_cross_size.map(|v| ratio.compute_dependent_size(main_axis, v));
+ content_sizes
+ .map(|size| size.clamp_between_extremums(transferred_min, transferred_max))
+ } else {
+ content_sizes
+ }
+ } else {
+ self.layout_for_block_content_size(
flex_context_getter(),
pbm,
- content_box_size,
- min_size,
- content_max_box_size,
+ content_box_sizes.map(|size| size.preferred),
+ // TODO(#32853): handle size keywords.
+ content_box_sizes.map(|size| size.min.to_numeric().unwrap_or_default()),
+ content_box_sizes.map(|size| size.max.to_numeric()),
preferred_aspect_ratio,
auto_cross_size_stretches_to_container_size,
IntrinsicSizingMode::Size,
)
- },
- );
+ .into()
+ }
+ });
+
+ let flex_base_size = self
+ .flex_basis(
+ containing_block_size.main,
+ content_main_sizes.preferred,
+ padding_border.main,
+ )
+ .resolve_for_preferred(Size::MaxContent, stretch_size.main, &main_content_sizes);
+ let flex_base_size_is_definite = flex_base_size_is_definite.take();
+
+ let content_max_main_size = content_main_sizes
+ .max
+ .resolve_for_max(stretch_size.main, &main_content_sizes);
- let hypothetical_main_size = flex_base_size.clamp_between_extremums(
- flex_relative_content_min_size.main,
- flex_relative_content_max_size.main,
+ let get_automatic_minimum_size = || {
+ // This is an implementation of <https://drafts.csswg.org/css-flexbox/#min-size-auto>.
+ if style.establishes_scroll_container(self.base_fragment_info().flags) {
+ return Au::zero();
+ }
+
+ // > **specified size suggestion**
+ // > If the item’s preferred main size is definite and not automatic, then the specified
+ // > size suggestion is that size. It is otherwise undefined.
+ let specified_size_suggestion = content_main_sizes
+ .preferred
+ .maybe_resolve_extrinsic(stretch_size.main);
+
+ let is_replaced = self.independent_formatting_context.is_replaced();
+
+ // > **content size suggestion**
+ // > The content size suggestion is the min-content size in the main axis, clamped, if it has a
+ // > preferred aspect ratio, by any definite minimum and maximum cross sizes converted through the
+ // > aspect ratio.
+ let content_size_suggestion = match preferred_aspect_ratio {
+ Some(ratio) => main_content_sizes.min_content.clamp_between_extremums(
+ ratio.compute_dependent_size(main_axis, min_cross_size),
+ max_cross_size.map(|l| ratio.compute_dependent_size(main_axis, l)),
+ ),
+ None => main_content_sizes.min_content,
+ };
+
+ // > The content-based minimum size of a flex item is the smaller of its specified size
+ // > suggestion and its content size suggestion if its specified size suggestion exists;
+ // > otherwise, the smaller of its transferred size suggestion and its content size
+ // > suggestion if the element is replaced and its transferred size suggestion exists;
+ // > otherwise its content size suggestion. In all cases, the size is clamped by the maximum
+ // > main size if it’s definite.
+ match (specified_size_suggestion, *transferred_size_suggestion) {
+ (Some(specified), _) => specified.min(content_size_suggestion),
+ (_, Some(transferred)) if is_replaced => transferred.min(content_size_suggestion),
+ _ => content_size_suggestion,
+ }
+ .clamp_below_max(content_max_main_size)
+ };
+ let content_min_main_size = content_main_sizes.min.resolve_for_min(
+ get_automatic_minimum_size,
+ stretch_size.main,
+ &main_content_sizes,
);
- let margin: FlexRelativeSides<AuOrAuto> = config.sides_to_flex_relative(pbm.margin);
FlexItem {
box_: self,
- content_cross_sizes: match flex_axis {
- FlexAxis::Row => content_box_sizes.block.clone(),
- FlexAxis::Column => content_box_sizes.inline.clone(),
- },
+ content_cross_sizes: content_cross_sizes.clone(),
padding,
border,
- margin,
+ margin: config.sides_to_flex_relative(pbm.margin),
pbm_auto_is_zero,
flex_base_size,
flex_base_size_is_definite,
- hypothetical_main_size,
- content_min_main_size: flex_relative_content_min_size.main,
- content_max_main_size: flex_relative_content_max_size.main,
- align_self,
+ hypothetical_main_size: flex_base_size
+ .clamp_between_extremums(content_min_main_size, content_max_main_size),
+ content_min_main_size,
+ content_max_main_size,
+ align_self: AlignItems(config.resolve_align_self_for_child(style)),
depends_on_block_constraints: *depends_on_block_constraints,
preferred_aspect_ratio,
auto_cross_size_stretches_to_container_size,
@@ -2494,109 +2581,6 @@ impl FlexItemBox {
}
}
- /// This is an implementation of <https://drafts.csswg.org/css-flexbox/#min-size-auto>.
- #[allow(clippy::too_many_arguments)]
- fn automatic_min_size(
- &self,
- layout_context: &LayoutContext,
- containing_block_size: FlexRelativeVec2<Option<Au>>,
- cross_axis_is_item_block_axis: bool,
- content_box_size: FlexRelativeVec2<Size<Au>>,
- min_size: FlexRelativeVec2<GenericLengthPercentageOrAuto<Au>>,
- max_size: FlexRelativeVec2<Option<Au>>,
- preferred_aspect_ratio: Option<AspectRatio>,
- pbm_auto_is_zero: &FlexRelativeVec2<Au>,
- auto_cross_size_stretches_to_container_size: bool,
- block_content_size_callback: impl FnOnce(&FlexItemBox) -> Au,
- ) -> Au {
- // FIXME(stshine): Consider more situations when auto min size is not needed.
- let style = &self.independent_formatting_context.style();
- if style.establishes_scroll_container(self.base_fragment_info().flags) {
- return Au::zero();
- }
-
- // > **specified size suggestion**
- // > If the item’s preferred main size is definite and not automatic, then the specified
- // > size suggestion is that size. It is otherwise undefined.
- let specified_size_suggestion = content_box_size.main.maybe_resolve_extrinsic(
- containing_block_size
- .main
- .map(|v| v - pbm_auto_is_zero.main),
- );
-
- let is_replaced = self.independent_formatting_context.is_replaced();
- let main_axis = if cross_axis_is_item_block_axis {
- Direction::Inline
- } else {
- Direction::Block
- };
-
- let cross_stretch_size = containing_block_size
- .cross
- .map(|v| v - pbm_auto_is_zero.cross);
- let cross_size = SizeConstraint::new(
- if content_box_size.cross.is_initial() && auto_cross_size_stretches_to_container_size {
- cross_stretch_size
- } else {
- content_box_size
- .cross
- .maybe_resolve_extrinsic(cross_stretch_size)
- },
- min_size.cross.auto_is(Au::zero),
- max_size.cross,
- );
-
- // > **transferred size suggestion**
- // > If the item has a preferred aspect ratio and its preferred cross size is definite, then the
- // > transferred size suggestion is that size (clamped by its minimum and maximum cross sizes if they
- // > are definite), converted through the aspect ratio. It is otherwise undefined.
- let transferred_size_suggestion = match (preferred_aspect_ratio, cross_size) {
- (Some(ratio), SizeConstraint::Definite(cross_size)) => {
- Some(ratio.compute_dependent_size(main_axis, cross_size))
- },
- _ => None,
- };
-
- // > **content size suggestion**
- // > The content size suggestion is the min-content size in the main axis, clamped, if it has a
- // > preferred aspect ratio, by any definite minimum and maximum cross sizes converted through the
- // > aspect ratio.
- let main_content_size = if cross_axis_is_item_block_axis {
- let writing_mode = style.writing_mode;
- let constraint_space =
- ConstraintSpace::new(cross_size, writing_mode, preferred_aspect_ratio);
- self.independent_formatting_context
- .inline_content_sizes(layout_context, &constraint_space)
- .sizes
- .min_content
- } else {
- block_content_size_callback(self)
- };
- let content_size_suggestion = preferred_aspect_ratio
- .map(|ratio| {
- main_content_size.clamp_between_extremums(
- ratio.compute_dependent_size(main_axis, min_size.cross.auto_is(Au::zero)),
- max_size
- .cross
- .map(|l| ratio.compute_dependent_size(main_axis, l)),
- )
- })
- .unwrap_or(main_content_size);
-
- // > The content-based minimum size of a flex item is the smaller of its specified size
- // > suggestion and its content size suggestion if its specified size suggestion exists;
- // > otherwise, the smaller of its transferred size suggestion and its content size
- // > suggestion if the element is replaced and its transferred size suggestion exists;
- // > otherwise its content size suggestion. In all cases, the size is clamped by the maximum
- // > main size if it’s definite.
- match (specified_size_suggestion, transferred_size_suggestion) {
- (Some(specified), _) => specified.min(content_size_suggestion),
- (_, Some(transferred)) if is_replaced => transferred.min(content_size_suggestion),
- _ => content_size_suggestion,
- }
- .clamp_below_max(max_size.main)
- }
-
/// <https://drafts.csswg.org/css-flexbox-1/#flex-basis-property>
/// Returns the used value of the `flex-basis` property, after resolving percentages,
/// resolving `auto`, and taking `box-sizing` into account.
@@ -2648,114 +2632,6 @@ impl FlexItemBox {
}
}
- /// <https://drafts.csswg.org/css-flexbox/#algo-main-item>
- #[allow(clippy::too_many_arguments)]
- fn flex_base_size(
- &self,
- layout_context: &LayoutContext,
- container_definite_inner_size: FlexRelativeVec2<Option<Au>>,
- cross_axis_is_item_block_axis: bool,
- content_box_size: FlexRelativeVec2<Size<Au>>,
- content_min_box_size: FlexRelativeVec2<Au>,
- content_max_box_size: FlexRelativeVec2<Option<Au>>,
- preferred_aspect_ratio: Option<AspectRatio>,
- padding_border_sums: FlexRelativeVec2<Au>,
- pbm_auto_is_zero: &FlexRelativeVec2<Au>,
- auto_cross_size_stretches_to_container_size: bool,
- block_content_size_callback: impl FnOnce(&FlexItemBox) -> Au,
- ) -> (Au, bool) {
- let used_flex_basis = self.flex_basis(
- container_definite_inner_size.main,
- content_box_size.main,
- padding_border_sums.main,
- );
-
- let mut flex_base_size_is_definite = true;
-
- let content_size = LazyCell::new(|| {
- let flex_item = &self.independent_formatting_context;
- let main_axis = if cross_axis_is_item_block_axis {
- Direction::Inline
- } else {
- Direction::Block
- };
-
- // > If a single-line flex container has a definite cross size, the automatic preferred
- // > 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.
- let cross_stretch_size = container_definite_inner_size
- .cross
- .map(|v| v - pbm_auto_is_zero.cross);
- let cross_size = SizeConstraint::new(
- if content_box_size.cross.is_initial() &&
- auto_cross_size_stretches_to_container_size
- {
- cross_stretch_size
- } else {
- content_box_size
- .cross
- .maybe_resolve_extrinsic(cross_stretch_size)
- },
- content_min_box_size.cross,
- content_max_box_size.cross,
- );
-
- // > B: If the flex item has ...
- // > - a preferred aspect ratio,
- // > - a used flex basis of content, and
- // > - a definite cross size,
- // > then the flex base size is calculated from its used cross size and the flex item’s aspect ratio.
- if let (Some(ratio), SizeConstraint::Definite(cross_size)) =
- (preferred_aspect_ratio, cross_size)
- {
- return ratio.compute_dependent_size(main_axis, cross_size).into();
- }
-
- flex_base_size_is_definite = false;
-
- // FIXME: implement cases C, D.
-
- // > E. Otherwise, size the item into the available space using its used flex basis in place of
- // > its main size, treating a value of content as max-content. 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, or when
- // > it has a preferred aspect ratio) 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.
- if cross_axis_is_item_block_axis {
- // The main axis is the inline axis, so we can get the content size from the normal
- // preferred widths calculation.
- let constraint_space = ConstraintSpace::new(
- cross_size,
- flex_item.style().writing_mode,
- preferred_aspect_ratio,
- );
- let content_sizes = flex_item
- .inline_content_sizes(layout_context, &constraint_space)
- .sizes;
- if let Some(ratio) = preferred_aspect_ratio {
- let transferred_min =
- ratio.compute_dependent_size(main_axis, content_min_box_size.cross);
- let transferred_max = content_max_box_size
- .cross
- .map(|v| ratio.compute_dependent_size(main_axis, v));
- content_sizes
- .map(|size| size.clamp_between_extremums(transferred_min, transferred_max))
- } else {
- content_sizes
- }
- } else {
- block_content_size_callback(self).into()
- }
- });
-
- let stretch_size = container_definite_inner_size
- .main
- .map(|container_size| Au::zero().max(container_size - pbm_auto_is_zero.main));
- let flex_base_size =
- used_flex_basis.resolve_for_preferred(Size::MaxContent, stretch_size, &content_size);
- (flex_base_size, flex_base_size_is_definite)
- }
-
#[allow(clippy::too_many_arguments)]
#[cfg_attr(
feature = "tracing",
diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs
index 248598fe2d3..8d5ff95f743 100644
--- a/components/layout_2020/geom.rs
+++ b/components/layout_2020/geom.rs
@@ -730,12 +730,6 @@ impl<T: Clone> Size<T> {
}
#[inline]
- pub(crate) fn to_auto_or(&self) -> AutoOr<T> {
- self.to_numeric()
- .map_or(AutoOr::Auto, AutoOr::LengthPercentage)
- }
-
- #[inline]
pub fn map<U>(&self, f: impl FnOnce(T) -> U) -> Size<U> {
match self {
Size::Initial => Size::Initial,