layout: Partial support for keyword sizes on preferred cross size (#35682)

This changes `FlexItem::content_cross_size` into `Size<Au>` to preserve
keyword sizes. The calculation of the hypothetical cross size still
ignores them though, that will be addressed in a follow-up.

Also, browsers don't follow the spec and treat a stretch size different
than a stretch alignment: the former stretches to the containing block,
while the latter stretches to the line. This aligns Servo with that
behavior (following the spec would require bigger refactorings), so
`stretches()` is renamed to `stretches_to_line()` for clarity.

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-03-04 06:58:46 +01:00 committed by GitHub
parent f3e6e4f04e
commit ff5683680f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 84 additions and 113 deletions

View file

@ -62,7 +62,7 @@ struct FlexContext<'a> {
/// A flex item with some intermediate results
struct FlexItem<'a> {
box_: &'a FlexItemBox,
content_cross_size: AuOrAuto,
content_cross_size: Size<Au>,
content_min_size: FlexRelativeVec2<Au>,
content_max_size: FlexRelativeVec2<Option<Au>>,
padding: FlexRelativeSides<Au>,
@ -1222,8 +1222,7 @@ impl<'a> FlexItem<'a> {
Self {
box_,
// TODO(#32853): handle size keywords.
content_cross_size: flex_relative_content_box_size.cross.to_auto_or(),
content_cross_size: flex_relative_content_box_size.cross,
content_min_size: flex_relative_content_min_size,
content_max_size: flex_relative_content_max_size,
padding,
@ -1239,8 +1238,11 @@ impl<'a> FlexItem<'a> {
}
}
fn stretches(&self) -> bool {
self.content_cross_size.is_auto() &&
fn stretches_to_line(&self) -> bool {
// Note this returns false for a `stretch` size, because that stretches to the
// containing block, not to the flex line.
// To be discussed in https://github.com/w3c/csswg-drafts/issues/11784.
self.content_cross_size.is_initial() &&
item_with_auto_cross_size_stretches_to_line_size(self.align_self, &self.margin)
}
}
@ -1693,7 +1695,7 @@ impl InitialFlexLineLayout<'_> {
let mut item_used_cross_sizes = Vec::with_capacity(item_count);
let mut item_margins = Vec::with_capacity(item_count);
for item in self.items.iter_mut() {
let stretches = item.item.stretches();
let stretches = item.item.stretches_to_line();
let used_cross_size = if stretches {
(final_line_cross_size - item.item.pbm_auto_is_zero.cross).clamp_between_extremums(
item.item.content_min_size.cross,
@ -1885,15 +1887,6 @@ impl FlexItem<'_> {
)
});
let cross_size = match used_cross_size_override {
Some(s) => SizeConstraint::Definite(s),
None => SizeConstraint::new(
self.content_cross_size.non_auto(),
self.content_min_size.cross,
self.content_max_size.cross,
),
};
let independent_formatting_context = &self.box_.independent_formatting_context;
let item_writing_mode = independent_formatting_context.style().writing_mode;
let item_is_horizontal = item_writing_mode.is_horizontal();
@ -1905,19 +1898,40 @@ impl FlexItem<'_> {
);
let (inline_size, block_size) = if cross_axis_is_item_block_axis {
let cross_size = match used_cross_size_override {
Some(s) => SizeConstraint::Definite(s),
None => {
// This means that an auto size with stretch alignment will behave different than
// a stretch size. That's not what the spec says, but matches other browsers.
// To be discussed in https://github.com/w3c/csswg-drafts/issues/11784.
let stretch_size = containing_block
.size
.block
.to_definite()
.map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
SizeConstraint::new(
self.content_cross_size
.maybe_resolve_extrinsic(stretch_size),
self.content_min_size.cross,
self.content_max_size.cross,
)
},
};
(used_main_size, cross_size)
} else {
(
cross_size.to_definite().unwrap_or_else(|| {
let style = self.box_.style();
let stretch_size =
Au::zero().max(containing_block.size.inline - self.pbm_auto_is_zero.cross);
if flex_context
.config
.item_with_auto_cross_size_stretches_to_container_size(style, &self.margin)
{
return stretch_size;
}
let cross_size = used_cross_size_override.unwrap_or_else(|| {
let style = self.box_.style();
let automatic_size = if flex_context
.config
.item_with_auto_cross_size_stretches_to_container_size(style, &self.margin)
{
Size::Stretch
} else {
Size::FitContent
};
let stretch_size =
Au::zero().max(containing_block.size.inline - self.pbm_auto_is_zero.cross);
let content_size = LazyCell::new(|| {
let constraint_space = ConstraintSpace::new(
SizeConstraint::Definite(used_main_size),
item_writing_mode,
@ -1926,26 +1940,28 @@ impl FlexItem<'_> {
independent_formatting_context
.inline_content_sizes(flex_context.layout_context, &constraint_space)
.sizes
.shrink_to_fit(stretch_size)
.clamp_between_extremums(
self.content_min_size.cross,
self.content_max_size.cross,
)
}),
// The main size of a flex item is considered to be definite if its flex basis is definite
// or the flex container has a definite main size.
// <https://drafts.csswg.org/css-flexbox-1/#definite-sizes>
if self.flex_base_size_is_definite ||
flex_context
.container_inner_size_constraint
.main
.is_definite()
{
SizeConstraint::Definite(used_main_size)
} else {
SizeConstraint::default()
},
)
});
self.content_cross_size
.resolve_for_preferred(automatic_size, Some(stretch_size), &content_size)
.clamp_between_extremums(
self.content_min_size.cross,
self.content_max_size.cross,
)
});
// The main size of a flex item is considered to be definite if its flex basis is definite
// or the flex container has a definite main size.
// <https://drafts.csswg.org/css-flexbox-1/#definite-sizes>
let main_size = if self.flex_base_size_is_definite ||
flex_context
.container_inner_size_constraint
.main
.is_definite()
{
SizeConstraint::Definite(used_main_size)
} else {
SizeConstraint::default()
};
(cross_size, main_size)
};
let container_writing_mode = containing_block.style.writing_mode;
@ -2013,13 +2029,13 @@ impl FlexItem<'_> {
},
IndependentFormattingContextContents::NonReplaced(non_replaced) => {
let calculate_hypothetical_cross_size = |content_block_size| {
// TODO(#32853): handle size keywords.
self.content_cross_size
.auto_is(|| {
if cross_axis_is_item_block_axis {
content_block_size
} else {
inline_size
}
.to_numeric()
.unwrap_or(if cross_axis_is_item_block_axis {
content_block_size
} else {
inline_size
})
.clamp_between_extremums(
self.content_min_size.cross,
@ -2069,7 +2085,7 @@ impl FlexItem<'_> {
..
} = layout;
let depends_on_block_constraints = depends_on_block_constraints ||
(flex_axis == FlexAxis::Row && self.stretches());
(flex_axis == FlexAxis::Row && self.stretches_to_line());
let has_child_which_depends_on_block_constraints = fragments.iter().any(|fragment| {
fragment.base().is_some_and(|base|

View file

@ -1,3 +0,0 @@
[orthogonal-writing-modes-and-intrinsic-sizing.html]
[.flexbox 2]
expected: FAIL

View file

@ -53,15 +53,6 @@
[.test 27]
expected: FAIL
[.test 28]
expected: FAIL
[.test 29]
expected: FAIL
[.test 30]
expected: FAIL
[.test 31]
expected: FAIL
@ -98,6 +89,15 @@
[.test 45]
expected: FAIL
[.test 46]
expected: FAIL
[.test 47]
expected: FAIL
[.test 48]
expected: FAIL
[.test 49]
expected: FAIL
@ -116,24 +116,6 @@
[.test 54]
expected: FAIL
[.test 57]
expected: FAIL
[.test 58]
expected: FAIL
[.test 59]
expected: FAIL
[.test 66]
expected: FAIL
[.test 67]
expected: FAIL
[.test 68]
expected: FAIL
[.test 69]
expected: FAIL

View file

@ -1,28 +1,4 @@
[keyword-sizes-on-flex-item-002.html]
[.test 1]
expected: FAIL
[.test 2]
expected: FAIL
[.test 3]
expected: FAIL
[.test 4]
expected: FAIL
[.test 6]
expected: FAIL
[.test 7]
expected: FAIL
[.test 8]
expected: FAIL
[.test 9]
expected: FAIL
[.test 10]
expected: FAIL

View file

@ -2,6 +2,9 @@
[[data-expected-height\] 8]
expected: FAIL
[[data-expected-height\] 19]
expected: FAIL
[[data-expected-height\] 21]
expected: FAIL
@ -11,6 +14,9 @@
[[data-expected-height\] 31]
expected: FAIL
[[data-expected-height\] 42]
expected: FAIL
[[data-expected-height\] 44]
expected: FAIL

View file

@ -2,17 +2,11 @@
[[data-expected-width\] 8]
expected: FAIL
[[data-expected-width\] 21]
expected: FAIL
[[data-expected-width\] 22]
expected: FAIL
[[data-expected-width\] 31]
expected: FAIL
[[data-expected-width\] 44]
expected: FAIL
[[data-expected-width\] 45]
expected: FAIL