layout: Ignore indefinite stretch on min and max sizing properties (#35630)

We were always treating an indefinite `stretch` as the automatic size.
This instead treats it as `0px` on min sizing properties, and as `none`
on max sizing properties, aligning with Blink and this recent CSSWG
resolution: https://github.com/w3c/csswg-drafts/issues/11006

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-02-24 14:51:08 +01:00 committed by GitHub
parent e74bb8de15
commit 41c2422a66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 266 additions and 145 deletions

View file

@ -2753,14 +2753,11 @@ impl FlexItemBox {
}
});
let flex_base_size = if let Some(container_size) = container_definite_inner_size.main {
let stretch_size = Au::zero().max(container_size - pbm_auto_is_zero.main);
used_flex_basis.resolve(Size::MaxContent, stretch_size, &content_size)
} else if matches!(used_flex_basis, Size::Stretch | Size::FitContent) {
content_size.max_content
} else {
used_flex_basis.resolve(Size::MaxContent, Au::zero(), &content_size)
};
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)
}
@ -2849,7 +2846,7 @@ impl FlexItemBox {
});
content_box_size
.inline
.resolve(initial_behavior, stretch_size, &content_size)
.resolve_for_preferred(initial_behavior, Some(stretch_size), &content_size)
.clamp_between_extremums(min_size.inline, max_size.inline)
};
let item_as_containing_block = ContainingBlock {
@ -2891,31 +2888,31 @@ impl FlexItemBox {
});
content_block_size
};
let content_block_size = LazyCell::new(|| ContentSizes::from(content_block_size()));
match intrinsic_sizing_mode {
IntrinsicSizingMode::Contribution => {
let stretch_size = flex_context
.containing_block
.size
.block
.to_definite()
.map(|block_size| {
block_size -
padding_border_margin.padding_border_sums.block -
padding_border_margin.margin.block_start.auto_is(Au::zero) -
padding_border_margin.margin.block_end.auto_is(Au::zero)
})
.unwrap_or_else(|| content_block_size.max_content);
let stretch_size =
flex_context.containing_block.size.block.to_definite().map(
|block_size| {
block_size -
padding_border_margin.padding_border_sums.block -
padding_border_margin.margin.block_start.auto_is(Au::zero) -
padding_border_margin.margin.block_end.auto_is(Au::zero)
},
);
let inner_block_size = content_box_size
.block
.resolve(Size::FitContent, stretch_size, &content_block_size)
.resolve_for_preferred(
Size::FitContent,
stretch_size,
&LazyCell::new(|| ContentSizes::from(content_block_size())),
)
.clamp_between_extremums(min_size.block, max_size.block);
inner_block_size +
padding_border_margin.padding_border_sums.block +
padding_border_margin.margin.block_start.auto_is(Au::zero) +
padding_border_margin.margin.block_end.auto_is(Au::zero)
},
IntrinsicSizingMode::Size => content_block_size.max_content,
IntrinsicSizingMode::Size => content_block_size(),
}
},
}

View file

@ -171,7 +171,7 @@ impl BlockLevelBox {
Direction::Inline,
Size::Stretch,
Au::zero(),
available_inline_size,
Some(available_inline_size),
get_inline_content_sizes,
false, /* is_table */
);
@ -996,7 +996,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context(
Direction::Block,
Size::FitContent,
Au::zero(),
available_block_size.unwrap_or(content_block_size),
available_block_size,
|| content_block_size.into(),
false, /* is_table */
);
@ -1117,7 +1117,7 @@ impl IndependentNonReplacedContents {
Direction::Block,
Size::FitContent,
Au::zero(),
available_block_size.unwrap_or(layout.content_block_size),
available_block_size,
|| layout.content_block_size.into(),
layout_style.is_table(),
);
@ -1251,7 +1251,7 @@ impl IndependentNonReplacedContents {
Direction::Inline,
automatic_inline_size,
Au::zero(),
stretch_size,
Some(stretch_size),
get_inline_content_sizes,
is_table,
)
@ -1262,7 +1262,7 @@ impl IndependentNonReplacedContents {
Direction::Block,
Size::FitContent,
Au::zero(),
available_block_size.unwrap_or(layout.content_block_size),
available_block_size,
|| layout.content_block_size.into(),
is_table,
)
@ -1699,7 +1699,7 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
Direction::Inline,
automatic_inline_size,
Au::zero(),
available_inline_size,
Some(available_inline_size),
get_inline_content_sizes,
is_table,
);
@ -2175,12 +2175,12 @@ impl IndependentFormattingContext {
IndependentFormattingContextContents::NonReplaced(non_replaced) => {
let writing_mode = self.style().writing_mode;
let available_inline_size =
(containing_block.size.inline - pbm_sums.inline_sum()).max(Au::zero());
Au::zero().max(containing_block.size.inline - pbm_sums.inline_sum());
let available_block_size = containing_block
.size
.block
.to_definite()
.map(|block_size| (block_size - pbm_sums.block_sum()).max(Au::zero()));
.map(|block_size| Au::zero().max(block_size - pbm_sums.block_sum()));
let tentative_block_size = content_box_sizes_and_pbm
.content_box_sizes
.block
@ -2201,7 +2201,7 @@ impl IndependentFormattingContext {
Direction::Inline,
Size::FitContent,
Au::zero(),
available_inline_size,
Some(available_inline_size),
get_content_size,
is_table,
);
@ -2232,7 +2232,7 @@ impl IndependentFormattingContext {
Direction::Block,
Size::FitContent,
Au::zero(),
available_block_size.unwrap_or(independent_layout.content_block_size),
available_block_size,
|| independent_layout.content_block_size.into(),
is_table,
);

View file

@ -7,7 +7,7 @@ use std::convert::From;
use std::fmt;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use app_units::Au;
use app_units::{Au, MAX_AU};
use style::logical_geometry::{BlockFlowDirection, Direction, InlineBaseDirection, WritingMode};
use style::values::computed::{
CSSPixelLength, LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize,
@ -779,39 +779,65 @@ impl LogicalVec2<Size<LengthPercentage>> {
}
impl Size<Au> {
/// Resolves any size into a numerical value.
/// Resolves a preferred size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#preferred-size-properties>
#[inline]
pub(crate) fn resolve<F: FnOnce() -> ContentSizes>(
pub(crate) fn resolve_for_preferred<F: FnOnce() -> ContentSizes>(
&self,
initial_behavior: Self,
stretch_size: Au,
automatic_size: Size<Au>,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
) -> Au {
if self.is_initial() {
assert!(!initial_behavior.is_initial());
initial_behavior.resolve_non_initial(stretch_size, content_size)
} else {
self.resolve_non_initial(stretch_size, content_size)
match self {
Self::Initial => {
assert!(!automatic_size.is_initial());
automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size)
},
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContent => {
content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content))
},
Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content),
Self::Numeric(numeric) => *numeric,
}
.unwrap()
}
/// Resolves a non-initial size into a numerical value.
/// Returns `None` if the size is the initial one.
/// Resolves a minimum size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#min-size-properties>
#[inline]
pub(crate) fn resolve_non_initial<F: FnOnce() -> ContentSizes>(
pub(crate) fn resolve_for_min<F: FnOnce() -> ContentSizes>(
&self,
stretch_size: Au,
automatic_minimum_size: Au,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
) -> Au {
match self {
Self::Initial => automatic_minimum_size,
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()),
Self::Stretch => stretch_size.unwrap_or_default(),
Self::Numeric(numeric) => *numeric,
}
}
/// Resolves a maximum size into a numerical value.
/// <https://www.w3.org/TR/css-sizing-3/#max-size-properties>
#[inline]
pub(crate) fn resolve_for_max<F: FnOnce() -> ContentSizes>(
&self,
stretch_size: Option<Au>,
content_size: &LazyCell<ContentSizes, F>,
) -> Option<Au> {
match self {
Self::Initial => None,
Self::MinContent => Some(content_size.min_content),
Self::MaxContent => Some(content_size.max_content),
Self::FitContent => Some(content_size.shrink_to_fit(stretch_size)),
Self::Stretch => Some(stretch_size),
Self::Numeric(numeric) => Some(*numeric),
}
Some(match self {
Self::Initial => return None,
Self::MinContent => content_size.min_content,
Self::MaxContent => content_size.max_content,
Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)),
Self::Stretch => return stretch_size,
Self::Numeric(numeric) => *numeric,
})
}
/// Tries to resolve an extrinsic size into a numerical value.
@ -915,7 +941,7 @@ impl Sizes {
axis: Direction,
automatic_size: Size<Au>,
automatic_minimum_size: Au,
stretch_size: Au,
stretch_size: Option<Au>,
get_content_size: impl FnOnce() -> ContentSizes,
is_table: bool,
) -> Au {
@ -937,7 +963,7 @@ impl Sizes {
axis: Direction,
automatic_size: Size<Au>,
automatic_minimum_size: Au,
stretch_size: Au,
stretch_size: Option<Au>,
get_content_size: impl FnOnce() -> ContentSizes,
is_table: bool,
) -> (Au, Au, Option<Au>) {
@ -953,13 +979,12 @@ impl Sizes {
return (content_size.max_content, content_size.min_content, None);
}
let preferred = self
.preferred
.resolve(automatic_size, stretch_size, &content_size);
let preferred =
self.preferred
.resolve_for_preferred(automatic_size, stretch_size, &content_size);
let mut min = self
.min
.resolve_non_initial(stretch_size, &content_size)
.unwrap_or(automatic_minimum_size);
.resolve_for_min(automatic_minimum_size, stretch_size, &content_size);
if is_table {
// In addition to the specified minimum, the inline size of a table is forced to be
// at least as big as its min-content size.
@ -968,7 +993,7 @@ impl Sizes {
// This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408
min.max_assign(content_size.min_content);
}
let max = self.max.resolve_non_initial(stretch_size, &content_size);
let max = self.max.resolve_for_max(stretch_size, &content_size);
(preferred, min, max)
}

View file

@ -822,7 +822,7 @@ impl AbsoluteAxisSolver<'_> {
self.axis,
initial_behavior,
Au::zero(),
stretch_size,
Some(stretch_size),
get_content_size,
self.is_table,
))

View file

@ -536,7 +536,7 @@ impl ReplacedContents {
Direction::Inline,
automatic_size.inline,
Au::zero(),
inline_stretch_size,
Some(inline_stretch_size),
get_inline_content_size,
false, /* is_table */
);
@ -565,7 +565,7 @@ impl ReplacedContents {
Direction::Block,
automatic_size.block,
Au::zero(),
block_stretch_size.unwrap_or_else(|| block_content_size.max_content),
block_stretch_size,
|| *block_content_size,
false, /* is_table */
);