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 /// A flex item with some intermediate results
struct FlexItem<'a> { struct FlexItem<'a> {
box_: &'a FlexItemBox, box_: &'a FlexItemBox,
content_cross_size: AuOrAuto, content_cross_size: Size<Au>,
content_min_size: FlexRelativeVec2<Au>, content_min_size: FlexRelativeVec2<Au>,
content_max_size: FlexRelativeVec2<Option<Au>>, content_max_size: FlexRelativeVec2<Option<Au>>,
padding: FlexRelativeSides<Au>, padding: FlexRelativeSides<Au>,
@ -1222,8 +1222,7 @@ impl<'a> FlexItem<'a> {
Self { Self {
box_, box_,
// TODO(#32853): handle size keywords. content_cross_size: flex_relative_content_box_size.cross,
content_cross_size: flex_relative_content_box_size.cross.to_auto_or(),
content_min_size: flex_relative_content_min_size, content_min_size: flex_relative_content_min_size,
content_max_size: flex_relative_content_max_size, content_max_size: flex_relative_content_max_size,
padding, padding,
@ -1239,8 +1238,11 @@ impl<'a> FlexItem<'a> {
} }
} }
fn stretches(&self) -> bool { fn stretches_to_line(&self) -> bool {
self.content_cross_size.is_auto() && // 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) 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_used_cross_sizes = Vec::with_capacity(item_count);
let mut item_margins = Vec::with_capacity(item_count); let mut item_margins = Vec::with_capacity(item_count);
for item in self.items.iter_mut() { 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 { let used_cross_size = if stretches {
(final_line_cross_size - item.item.pbm_auto_is_zero.cross).clamp_between_extremums( (final_line_cross_size - item.item.pbm_auto_is_zero.cross).clamp_between_extremums(
item.item.content_min_size.cross, 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 independent_formatting_context = &self.box_.independent_formatting_context;
let item_writing_mode = independent_formatting_context.style().writing_mode; let item_writing_mode = independent_formatting_context.style().writing_mode;
let item_is_horizontal = item_writing_mode.is_horizontal(); 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 (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) (used_main_size, cross_size)
} else { } else {
( let cross_size = used_cross_size_override.unwrap_or_else(|| {
cross_size.to_definite().unwrap_or_else(|| { let style = self.box_.style();
let style = self.box_.style(); let automatic_size = if flex_context
let stretch_size = .config
Au::zero().max(containing_block.size.inline - self.pbm_auto_is_zero.cross); .item_with_auto_cross_size_stretches_to_container_size(style, &self.margin)
if flex_context {
.config Size::Stretch
.item_with_auto_cross_size_stretches_to_container_size(style, &self.margin) } else {
{ Size::FitContent
return stretch_size; };
} 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( let constraint_space = ConstraintSpace::new(
SizeConstraint::Definite(used_main_size), SizeConstraint::Definite(used_main_size),
item_writing_mode, item_writing_mode,
@ -1926,26 +1940,28 @@ impl FlexItem<'_> {
independent_formatting_context independent_formatting_context
.inline_content_sizes(flex_context.layout_context, &constraint_space) .inline_content_sizes(flex_context.layout_context, &constraint_space)
.sizes .sizes
.shrink_to_fit(stretch_size) });
.clamp_between_extremums( self.content_cross_size
self.content_min_size.cross, .resolve_for_preferred(automatic_size, Some(stretch_size), &content_size)
self.content_max_size.cross, .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> // The main size of a flex item is considered to be definite if its flex basis is definite
if self.flex_base_size_is_definite || // or the flex container has a definite main size.
flex_context // <https://drafts.csswg.org/css-flexbox-1/#definite-sizes>
.container_inner_size_constraint let main_size = if self.flex_base_size_is_definite ||
.main flex_context
.is_definite() .container_inner_size_constraint
{ .main
SizeConstraint::Definite(used_main_size) .is_definite()
} else { {
SizeConstraint::default() SizeConstraint::Definite(used_main_size)
}, } else {
) SizeConstraint::default()
};
(cross_size, main_size)
}; };
let container_writing_mode = containing_block.style.writing_mode; let container_writing_mode = containing_block.style.writing_mode;
@ -2013,13 +2029,13 @@ impl FlexItem<'_> {
}, },
IndependentFormattingContextContents::NonReplaced(non_replaced) => { IndependentFormattingContextContents::NonReplaced(non_replaced) => {
let calculate_hypothetical_cross_size = |content_block_size| { let calculate_hypothetical_cross_size = |content_block_size| {
// TODO(#32853): handle size keywords.
self.content_cross_size self.content_cross_size
.auto_is(|| { .to_numeric()
if cross_axis_is_item_block_axis { .unwrap_or(if cross_axis_is_item_block_axis {
content_block_size content_block_size
} else { } else {
inline_size inline_size
}
}) })
.clamp_between_extremums( .clamp_between_extremums(
self.content_min_size.cross, self.content_min_size.cross,
@ -2069,7 +2085,7 @@ impl FlexItem<'_> {
.. ..
} = layout; } = layout;
let depends_on_block_constraints = depends_on_block_constraints || 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| { let has_child_which_depends_on_block_constraints = fragments.iter().any(|fragment| {
fragment.base().is_some_and(|base| 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] [.test 27]
expected: FAIL expected: FAIL
[.test 28]
expected: FAIL
[.test 29]
expected: FAIL
[.test 30]
expected: FAIL
[.test 31] [.test 31]
expected: FAIL expected: FAIL
@ -98,6 +89,15 @@
[.test 45] [.test 45]
expected: FAIL expected: FAIL
[.test 46]
expected: FAIL
[.test 47]
expected: FAIL
[.test 48]
expected: FAIL
[.test 49] [.test 49]
expected: FAIL expected: FAIL
@ -116,24 +116,6 @@
[.test 54] [.test 54]
expected: FAIL 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] [.test 69]
expected: FAIL expected: FAIL

View file

@ -1,28 +1,4 @@
[keyword-sizes-on-flex-item-002.html] [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] [.test 10]
expected: FAIL expected: FAIL

View file

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

View file

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