layout: Fix which axis to consider for baseline propagation of flex items (#44281)

* When a flex item line participates in baseline alignment, the relevant
baseline must be parallel to the **main axis** (affected by
`flex-direction`) of the flex container.
* When a flex item propagates a baseline to a parent layout (such as for
`display: inline-flex`) the relevant baseline must be parallel to the
**inline axis** (not affected by `flex-direction`) of the flex
container.

See:

* https://drafts.csswg.org/css-flexbox-1/#box-model
* https://drafts.csswg.org/css-flexbox-1/#flex-direction-property
* https://drafts.csswg.org/css-flexbox-1/#baseline-participation
* https://drafts.csswg.org/css-align-3/#baseline-export

Testing: covered by existing WPT tests
Fixes: https://github.com/servo/servo/issues/43687

Signed-off-by: Simon Sapin <simon@igalia.com>
This commit is contained in:
Simon Sapin
2026-04-17 12:08:26 +02:00
committed by GitHub
parent 9f81a8f54d
commit 56345cdab0
8 changed files with 40 additions and 47 deletions

View File

@@ -117,7 +117,7 @@ struct FlexItemLayoutResult {
/// Baselines from the items content, relative to the items margin box.
/// Used only for baseline propagation to parent layout contexts.
content_baselines_relative_to_margin_box: Baselines,
content_baselines_for_parent_relative_to_margin_box: Baselines,
/// This is the single baseline this item uses for flex alignment.
/// Either the first or the last baseline or None, depending on align-self.
@@ -223,7 +223,9 @@ impl FlexLineItem<'_> {
item_margin.cross_start
};
let baselines = self.layout_result.content_baselines_relative_to_margin_box;
let baselines = self
.layout_result
.content_baselines_for_parent_relative_to_margin_box;
if flex_context.config.flex_direction_is_reversed {
if let Some(last_baseline) = baselines.last {
all_baselines
@@ -1885,36 +1887,47 @@ impl FlexItem<'_> {
inline_size
};
let item_writing_mode_is_orthogonal_to_container_writing_mode =
flex_context.config.writing_mode.is_horizontal() !=
item_style.writing_mode.is_horizontal();
let has_compatible_baseline = match flex_axis {
FlexAxis::Row => !item_writing_mode_is_orthogonal_to_container_writing_mode,
FlexAxis::Column => item_writing_mode_is_orthogonal_to_container_writing_mode,
let item_inline_axis_is_horizontal = item_style.writing_mode.is_horizontal();
let container_inline_axis_is_horizontal = flex_context.config.writing_mode.is_horizontal();
let container_main_axis_is_horizontal = match flex_axis {
FlexAxis::Row => container_inline_axis_is_horizontal,
FlexAxis::Column => !container_inline_axis_is_horizontal,
};
let item_inline_axis_parallel_to_container_inline_axis =
item_inline_axis_is_horizontal == container_inline_axis_is_horizontal;
let item_inline_axis_parallel_to_container_main_axis =
item_inline_axis_is_horizontal == container_main_axis_is_horizontal;
let content_baselines_relative_to_margin_box = if has_compatible_baseline {
content_box_baselines.offset(
self.margin.cross_start.auto_is(Au::zero) +
self.padding.cross_start +
self.border.cross_start,
)
} else {
Baselines::default()
};
let content_baselines_relative_to_margin_box = content_box_baselines.offset(
self.margin.cross_start.auto_is(Au::zero) +
self.padding.cross_start +
self.border.cross_start,
);
let flex_alignment_baseline_relative_to_margin_box = match self.align_self.0.value() {
// baseline computes to first baseline.
AlignFlags::BASELINE => content_baselines_relative_to_margin_box.first,
AlignFlags::LAST_BASELINE => content_baselines_relative_to_margin_box.last,
_ => None,
};
let content_baselines_for_parent_relative_to_margin_box =
if item_inline_axis_parallel_to_container_inline_axis {
content_baselines_relative_to_margin_box
} else {
Baselines::default()
};
let flex_alignment_baseline_relative_to_margin_box =
if item_inline_axis_parallel_to_container_main_axis {
match self.align_self.0.value() {
// baseline computes to first baseline.
AlignFlags::BASELINE => content_baselines_relative_to_margin_box.first,
AlignFlags::LAST_BASELINE => content_baselines_relative_to_margin_box.last,
_ => None,
}
} else {
None
};
FlexItemLayoutResult {
hypothetical_cross_size,
fragments,
positioning_context,
content_baselines_relative_to_margin_box,
content_baselines_for_parent_relative_to_margin_box,
flex_alignment_baseline_relative_to_margin_box,
content_block_size,
containing_block_size: item_as_containing_block.size,

View File

@@ -1,7 +1,4 @@
[flex-align-baseline-flex-001.html]
[.target > * 9]
expected: FAIL
[.target > * 11]
expected: FAIL
@@ -32,17 +29,8 @@
[.target > * 45]
expected: FAIL
[.target > * 25]
expected: FAIL
[.target > * 43]
expected: FAIL
[.target > * 47]
expected: FAIL
[.target > * 13]
expected: FAIL
[.target > * 29]
expected: FAIL

View File

@@ -1,2 +0,0 @@
[flexbox-baseline-multi-item-vert-001a.html]
expected: FAIL

View File

@@ -1,2 +0,0 @@
[flexbox-baseline-multi-item-vert-001b.html]
expected: FAIL

View File

@@ -1,2 +0,0 @@
[flexbox-baseline-single-item-001b.html]
expected: FAIL

View File

@@ -1,2 +0,0 @@
[percentage-heights-004.html]
expected: FAIL

View File

@@ -73,3 +73,6 @@
[.target > * 25]
expected: FAIL
[.target > * 10]
expected: FAIL

View File

@@ -16,6 +16,3 @@
[.target > * 21]
expected: FAIL
[.target > * 11]
expected: FAIL