layout: Obey intrinsic min/max block sizes on flex containers (#36973)

Intrinsic sizing keywords weren't working correctly on the min and max
block sizes of a flex container, because we weren't setting the
`CacheableLayoutResult::content_block_size` to the right value. This
also ensures that `align-content` aligns within the final size of the
container.

Note it's not very clear what to do for single-line containers, they are
being discussed in https://github.com/w3c/csswg-drafts/issues/12123

Testing: Adding new WPT tests. There are still some failures, but most
subtests would fail without this change.
Fixes: #36981

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-05-15 00:57:07 -07:00 committed by GitHub
parent 103cbed928
commit cd0dbb9afb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 401 additions and 22 deletions

View file

@ -655,6 +655,7 @@ impl FlexContainer {
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
depends_on_block_constraints: bool,
lazy_block_size: &LazySize,
) -> CacheableLayoutResult {
let depends_on_block_constraints =
depends_on_block_constraints || self.config.flex_direction == FlexDirection::Column;
@ -676,14 +677,13 @@ impl FlexContainer {
// https://drafts.csswg.org/css-flexbox/#algo-main-container
let container_main_size = match self.config.flex_axis {
FlexAxis::Row => containing_block.size.inline,
FlexAxis::Column => match containing_block.size.block {
SizeConstraint::Definite(size) => size,
SizeConstraint::MinMax(min, max) => self
.main_content_sizes(layout_context, &containing_block.into(), || &flex_context)
FlexAxis::Column => lazy_block_size.resolve(|| {
let mut containing_block = IndefiniteContainingBlock::from(containing_block);
containing_block.size.block = None;
self.main_content_sizes(layout_context, &containing_block, || &flex_context)
.sizes
.max_content
.clamp_between_extremums(min, max),
},
}),
};
// Actual length may be less, but we guess that usually not by a lot
@ -766,30 +766,23 @@ impl FlexContainer {
.map(|layout| layout.line_size.cross)
.sum::<Au>() +
cross_gap * (line_count as i32 - 1);
let content_block_size = match self.config.flex_axis {
FlexAxis::Row => content_cross_size,
FlexAxis::Column => container_main_size,
};
// https://drafts.csswg.org/css-flexbox/#algo-cross-container
let container_cross_size = match flex_context.container_inner_size_constraint.cross {
SizeConstraint::Definite(cross_size) => cross_size,
SizeConstraint::MinMax(min, max) => {
content_cross_size.clamp_between_extremums(min, max)
},
let container_cross_size = match self.config.flex_axis {
FlexAxis::Row => lazy_block_size.resolve(|| content_cross_size),
FlexAxis::Column => containing_block.size.inline,
};
let container_size = FlexRelativeVec2 {
main: container_main_size,
cross: container_cross_size,
};
let content_block_size = flex_context
.config
.flex_axis
.vec2_to_flow_relative(container_size)
.block;
let mut remaining_free_cross_space =
match flex_context.container_inner_size_constraint.cross {
SizeConstraint::Definite(cross_size) => cross_size - content_cross_size,
_ => Au::zero(),
};
let mut remaining_free_cross_space = container_cross_size - content_cross_size;
// Implement fallback alignment.
//

View file

@ -243,7 +243,7 @@ impl IndependentNonReplacedContents {
containing_block_for_children: &ContainingBlock,
containing_block: &ContainingBlock,
depends_on_block_constraints: bool,
_lazy_block_size: &LazySize,
lazy_block_size: &LazySize,
) -> CacheableLayoutResult {
match self {
IndependentNonReplacedContents::Flow(bfc) => bfc.layout(
@ -257,6 +257,7 @@ impl IndependentNonReplacedContents {
positioning_context,
containing_block_for_children,
depends_on_block_constraints,
lazy_block_size,
),
IndependentNonReplacedContents::Grid(fc) => fc.layout(
layout_context,

View file

@ -586672,6 +586672,20 @@
{}
]
],
"flex-container-max-content-002.tentative.html": [
"77a074b153a3b3fcb47c5665bab614c1720a73d2",
[
null,
{}
]
],
"flex-container-min-content-002.tentative.html": [
"92d37e3b9d8db7b57b813b49c26a3e9cd03b5179",
[
null,
{}
]
],
"flex-direction-column-overlap-001.html": [
"4d483a44fc630e46ddbe75ca79d8246eed67a94d",
[

View file

@ -0,0 +1,12 @@
[flex-container-max-content-002.tentative.html]
[.flex 2]
expected: FAIL
[.flex 3]
expected: FAIL
[.flex 5]
expected: FAIL
[.flex 6]
expected: FAIL

View file

@ -0,0 +1,21 @@
[flex-container-min-content-002.tentative.html]
[.flex 2]
expected: FAIL
[.flex 3]
expected: FAIL
[.flex 5]
expected: FAIL
[.flex 6]
expected: FAIL
[.flex 13]
expected: FAIL
[.flex 14]
expected: FAIL
[.flex 15]
expected: FAIL

View file

@ -0,0 +1,169 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Flex Container Max-Content Sizes</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes"
title="9.9.1. Flex Container Intrinsic Main Sizes">
<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes"
title="9.9.2. Flex Container Intrinsic Cross Sizes">
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/12123">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
<style>
.flex {
display: inline-flex;
vertical-align: top;
border: 5px solid magenta;
width: max-content;
height: max-content;
}
.flex.min {
width: 0;
height: 0;
min-width: max-content;
min-height: max-content;
}
.flex.max {
width: 200px;
height: 200px;
max-width: max-content;
max-height: max-content;
}
.flex > div {
font: 25px/1 Ahem;
border: 5px solid cyan;
}
</style>
<!-- Single-line row flex container -->
<div class="flex" style="flex-flow: row nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex min" style="flex-flow: row nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex max" style="flex-flow: row nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex" style="flex-flow: row nowrap"
data-expected-width="180" data-expected-height="45">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<div class="flex min" style="flex-flow: row nowrap"
data-expected-width="180" data-expected-height="45">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<div class="flex max" style="flex-flow: row nowrap"
data-expected-width="180" data-expected-height="45">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<hr>
<!-- Single-line column flex container -->
<div class="flex" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex min" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex max" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex" style="flex-flow: column nowrap"
data-expected-width="95" data-expected-height="80">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<div class="flex min" style="flex-flow: column nowrap"
data-expected-width="95" data-expected-height="80">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<div class="flex max" style="flex-flow: column nowrap"
data-expected-width="95" data-expected-height="80">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<hr>
<!-- Multi-line row flex container -->
<div class="flex" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex min" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex max" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex" style="flex-flow: row wrap"
data-expected-width="180" data-expected-height="45">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<div class="flex min" style="flex-flow: row wrap"
data-expected-width="180" data-expected-height="45">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<div class="flex max" style="flex-flow: row wrap"
data-expected-width="180" data-expected-height="45">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<hr>
<!-- Multi-line column flex container -->
<div class="flex" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex min" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex max" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex" style="flex-flow: column wrap"
data-expected-width="95" data-expected-height="80">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<div class="flex min" style="flex-flow: column wrap"
data-expected-width="95" data-expected-height="80">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<div class="flex max" style="flex-flow: column wrap"
data-expected-width="95" data-expected-height="80">
<div data-expected-width="85" data-expected-height="35">X X</div>
<div data-expected-width="85" data-expected-height="35">X X</div>
</div>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<script>
checkLayout(".flex");
</script>

View file

@ -0,0 +1,169 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Flex Container Min-Content Sizes</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes"
title="9.9.1. Flex Container Intrinsic Main Sizes">
<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes"
title="9.9.2. Flex Container Intrinsic Cross Sizes">
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/12123">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
<style>
.flex {
display: inline-flex;
vertical-align: top;
border: 5px solid magenta;
width: min-content;
height: min-content;
}
.flex.min {
width: 0;
height: 0;
min-width: min-content;
min-height: min-content;
}
.flex.max {
width: 200px;
height: 200px;
max-width: min-content;
max-height: min-content;
}
.flex > div {
font: 25px/1 Ahem;
border: 5px solid cyan;
}
</style>
<!-- Single-line row flex container -->
<div class="flex" style="flex-flow: row nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex min" style="flex-flow: row nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex max" style="flex-flow: row nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex" style="flex-flow: row nowrap"
data-expected-width="80" data-expected-height="70">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<div class="flex min" style="flex-flow: row nowrap"
data-expected-width="80" data-expected-height="70">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<div class="flex max" style="flex-flow: row nowrap"
data-expected-width="80" data-expected-height="70">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<hr>
<!-- Single-line column flex container -->
<div class="flex" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex min" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex max" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<div class="flex min" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<div class="flex max" style="flex-flow: column nowrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<hr>
<!-- Multi-line row flex container -->
<div class="flex" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex min" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex max" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<div class="flex min" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<div class="flex max" style="flex-flow: row wrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<hr>
<!-- Multi-line column flex container -->
<div class="flex" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex min" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex max" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="45">
<div style="width: 25px; height: 25px">X</div>
</div>
<div class="flex" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<div class="flex min" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<div class="flex max" style="flex-flow: column wrap"
data-expected-width="45" data-expected-height="130">
<div data-expected-width="35" data-expected-height="60">X X</div>
<div data-expected-width="35" data-expected-height="60">X X</div>
</div>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<script>
checkLayout(".flex");
</script>