layout: Unify layout logic for replaced and non-replaced flex items (#37962)

Laying out a flex item will now use the same logic regardless of whether
it's replaced or not.
This reduces the amount of code, and should have no observable effect.

Testing: Unneeded (no behavior change)
This part of #37942

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-07-09 17:57:58 +02:00 committed by GitHub
parent 378c4648e4
commit d5d131c172
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -27,7 +27,7 @@ use super::geom::{FlexAxis, FlexRelativeRect, FlexRelativeSides, FlexRelativeVec
use super::{FlexContainer, FlexContainerConfig, FlexItemBox, FlexLevelBox}; use super::{FlexContainer, FlexContainerConfig, FlexItemBox, FlexLevelBox};
use crate::cell::ArcRefCell; use crate::cell::ArcRefCell;
use crate::context::LayoutContext; use crate::context::LayoutContext;
use crate::formatting_contexts::{Baselines, IndependentFormattingContextContents}; use crate::formatting_contexts::Baselines;
use crate::fragment_tree::{ use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo, BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
}; };
@ -171,14 +171,6 @@ impl FlexItemLayoutResult {
false false
} }
fn compatible_with_containing_block_size_and_content_size(
&self,
containing_block: &ContainingBlock,
size: LogicalVec2<Au>,
) -> bool {
size == self.content_size && self.compatible_with_containing_block_size(containing_block)
}
} }
/// A data structure to hold all of the information about a flex item that has been placed /// A data structure to hold all of the information about a flex item that has been placed
@ -1581,7 +1573,8 @@ impl InitialFlexLineLayout<'_> {
// but it would prevent stretching. So we only recognize tables in the inline axis. // but it would prevent stretching. So we only recognize tables in the inline axis.
// The interaction of collapsed table tracks and the flexbox algorithms is unclear, // The interaction of collapsed table tracks and the flexbox algorithms is unclear,
// see https://github.com/w3c/csswg-drafts/issues/11408. // see https://github.com/w3c/csswg-drafts/issues/11408.
item.item.is_table() && axis == Direction::Inline, item.item.box_.independent_formatting_context.is_table() &&
axis == Direction::Inline,
) )
} else { } else {
item.layout_result.hypothetical_cross_size item.layout_result.hypothetical_cross_size
@ -1756,6 +1749,7 @@ impl FlexItem<'_> {
) -> Option<FlexItemLayoutResult> { ) -> Option<FlexItemLayoutResult> {
let containing_block = flex_context.containing_block; let containing_block = flex_context.containing_block;
let independent_formatting_context = &self.box_.independent_formatting_context; let independent_formatting_context = &self.box_.independent_formatting_context;
let is_table = independent_formatting_context.is_table();
let mut positioning_context = PositioningContext::default(); let mut positioning_context = PositioningContext::default();
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();
@ -1778,11 +1772,24 @@ impl FlexItem<'_> {
.block .block
.to_definite() .to_definite()
.map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross)); .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
self.content_cross_sizes.resolve_extrinsic( let tentative_block_content_size = independent_formatting_context
Size::FitContent, .tentative_block_content_size(self.preferred_aspect_ratio);
Au::zero(), if let Some(block_content_size) = tentative_block_content_size {
stretch_size, SizeConstraint::Definite(self.content_cross_sizes.resolve(
) Direction::Block,
Size::FitContent,
Au::zero,
stretch_size,
|| block_content_size,
is_table,
))
} else {
self.content_cross_sizes.resolve_extrinsic(
Size::FitContent,
Au::zero(),
stretch_size,
)
}
}, },
}; };
(used_main_size, cross_size) (used_main_size, cross_size)
@ -1806,7 +1813,7 @@ impl FlexItem<'_> {
Au::zero, Au::zero,
Some(stretch_size), Some(stretch_size),
get_content_size, get_content_size,
self.is_table(), is_table,
) )
}); });
// The main size of a flex item is considered to be definite if its flex basis is definite // The main size of a flex item is considered to be definite if its flex basis is definite
@ -1825,191 +1832,118 @@ impl FlexItem<'_> {
(cross_size, main_size) (cross_size, main_size)
}; };
let container_writing_mode = containing_block.style.writing_mode;
let item_style = independent_formatting_context.style(); let item_style = independent_formatting_context.style();
match &independent_formatting_context.contents { let item_as_containing_block = ContainingBlock {
IndependentFormattingContextContents::Replaced(replaced) => { size: ContainingBlockSize {
let min_size = flex_axis.vec2_to_flow_relative(FlexRelativeVec2 { inline: inline_size,
main: Size::Numeric(self.content_min_main_size), block: block_size,
cross: self.content_cross_sizes.min,
});
let max_size = flex_axis.vec2_to_flow_relative(FlexRelativeVec2 {
main: self
.content_max_main_size
.map_or(Size::Initial, Size::Numeric),
cross: self.content_cross_sizes.max,
});
let size = replaced.used_size_as_if_inline_element_from_content_box_sizes(
containing_block,
item_style,
self.preferred_aspect_ratio,
LogicalVec2 {
block: &Sizes::new(
block_size
.to_definite()
.map_or(Size::Initial, Size::Numeric),
min_size.block,
max_size.block,
),
inline: &Sizes::new(
Size::Numeric(inline_size),
min_size.inline,
max_size.inline,
),
},
Size::FitContent.into(),
flex_axis.vec2_to_flow_relative(self.pbm_auto_is_zero),
);
if let Some(non_stretch_layout_result) = non_stretch_layout_result {
if non_stretch_layout_result
.compatible_with_containing_block_size_and_content_size(
containing_block,
size,
)
{
return None;
}
}
let hypothetical_cross_size = flex_axis.vec2_to_flex_relative(size).cross;
let fragments = replaced.make_fragments(
flex_context.layout_context,
item_style,
size.to_physical_size(container_writing_mode),
);
Some(FlexItemLayoutResult {
hypothetical_cross_size,
fragments,
positioning_context,
content_size: size,
containing_block_inline_size: containing_block.size.inline,
containing_block_block_size: containing_block.size.block,
depends_on_block_constraints: false,
has_child_which_depends_on_block_constraints: false,
// We will need to synthesize the baseline, but since the used cross
// size can differ from the hypothetical cross size, we should defer
// synthesizing until needed.
baseline_relative_to_margin_box: None,
specific_layout_info: None,
})
}, },
IndependentFormattingContextContents::NonReplaced(non_replaced) => { style: item_style,
let item_as_containing_block = ContainingBlock { };
size: ContainingBlockSize {
inline: inline_size,
block: block_size,
},
style: item_style,
};
if let Some(non_stretch_layout_result) = non_stretch_layout_result { if non_stretch_layout_result.is_some_and(|old_result| {
if non_stretch_layout_result old_result.compatible_with_containing_block_size(&item_as_containing_block)
.compatible_with_containing_block_size(&item_as_containing_block) }) {
{ return None;
return None;
}
}
let lazy_block_size = if !cross_axis_is_item_block_axis {
used_main_size.into()
} else if let Some(cross_size) = used_cross_size_override {
cross_size.into()
} else {
// 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));
LazySize::new(
&self.content_cross_sizes,
Direction::Block,
Size::FitContent,
Au::zero,
stretch_size,
self.is_table(),
)
};
let layout = non_replaced.layout(
flex_context.layout_context,
&mut positioning_context,
&item_as_containing_block,
containing_block,
&independent_formatting_context.base,
flex_axis == FlexAxis::Column ||
self.cross_size_stretches_to_line ||
self.depends_on_block_constraints,
&lazy_block_size,
);
let CacheableLayoutResult {
fragments,
content_block_size,
baselines: content_box_baselines,
depends_on_block_constraints,
specific_layout_info,
..
} = layout;
let has_child_which_depends_on_block_constraints = fragments.iter().any(|fragment| {
fragment.base().is_some_and(|base|
base.flags.contains(
FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM))
});
let hypothetical_cross_size = if cross_axis_is_item_block_axis {
lazy_block_size.resolve(|| content_block_size)
} else {
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 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 baseline_relative_to_margin_box = match self.align_self.0.value() {
// baseline computes to first baseline.
AlignFlags::BASELINE => baselines_relative_to_margin_box.first,
AlignFlags::LAST_BASELINE => baselines_relative_to_margin_box.last,
_ => None,
};
Some(FlexItemLayoutResult {
hypothetical_cross_size,
fragments,
positioning_context,
baseline_relative_to_margin_box,
content_size: LogicalVec2 {
inline: item_as_containing_block.size.inline,
block: content_block_size,
},
containing_block_inline_size: item_as_containing_block.size.inline,
containing_block_block_size: item_as_containing_block.size.block,
depends_on_block_constraints,
has_child_which_depends_on_block_constraints,
specific_layout_info,
})
},
} }
let lazy_block_size = if !cross_axis_is_item_block_axis {
used_main_size.into()
} else if let Some(cross_size) = used_cross_size_override {
cross_size.into()
} else {
// 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));
LazySize::new(
&self.content_cross_sizes,
Direction::Block,
Size::FitContent,
Au::zero,
stretch_size,
is_table,
)
};
let layout = independent_formatting_context.layout(
flex_context.layout_context,
&mut positioning_context,
&item_as_containing_block,
containing_block,
self.preferred_aspect_ratio,
flex_axis == FlexAxis::Column ||
self.cross_size_stretches_to_line ||
self.depends_on_block_constraints,
&lazy_block_size,
);
let CacheableLayoutResult {
fragments,
content_block_size,
baselines: content_box_baselines,
depends_on_block_constraints,
specific_layout_info,
..
} = layout;
let has_child_which_depends_on_block_constraints = fragments.iter().any(|fragment| {
fragment.base().is_some_and(|base| {
base.flags.contains(
FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
)
})
});
let hypothetical_cross_size = if cross_axis_is_item_block_axis {
lazy_block_size.resolve(|| content_block_size)
} else {
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 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 baseline_relative_to_margin_box = match self.align_self.0.value() {
// baseline computes to first baseline.
AlignFlags::BASELINE => baselines_relative_to_margin_box.first,
AlignFlags::LAST_BASELINE => baselines_relative_to_margin_box.last,
_ => None,
};
Some(FlexItemLayoutResult {
hypothetical_cross_size,
fragments,
positioning_context,
baseline_relative_to_margin_box,
content_size: LogicalVec2 {
inline: item_as_containing_block.size.inline,
block: content_block_size,
},
containing_block_inline_size: item_as_containing_block.size.inline,
containing_block_block_size: item_as_containing_block.size.block,
depends_on_block_constraints,
has_child_which_depends_on_block_constraints,
specific_layout_info,
})
} }
fn synthesized_baseline_relative_to_margin_box(&self, content_size: Au) -> Au { fn synthesized_baseline_relative_to_margin_box(&self, content_size: Au) -> Au {
@ -2143,11 +2077,6 @@ impl FlexItem<'_> {
}; };
outer_cross_start + margin.cross_start + self.border.cross_start + self.padding.cross_start outer_cross_start + margin.cross_start + self.border.cross_start + self.padding.cross_start
} }
#[inline]
fn is_table(&self) -> bool {
self.box_.independent_formatting_context.is_table()
}
} }
impl FlexItemBox { impl FlexItemBox {
@ -2612,109 +2541,88 @@ impl FlexItemBox {
cross_size_stretches_to_container_size: bool, cross_size_stretches_to_container_size: bool,
intrinsic_sizing_mode: IntrinsicSizingMode, intrinsic_sizing_mode: IntrinsicSizingMode,
) -> Au { ) -> Au {
let mut positioning_context = PositioningContext::default(); let content_block_size = || {
let style = self.independent_formatting_context.style(); let mut positioning_context = PositioningContext::default();
match &self.independent_formatting_context.contents { let style = self.independent_formatting_context.style();
IndependentFormattingContextContents::Replaced(replaced) => {
let get_used_size = |block_sizes| { // We are computing the intrinsic block size, so the tentative block size that we use
replaced.used_size_as_if_inline_element_from_content_box_sizes( // as an input to the intrinsic inline sizes needs to ignore the values of the sizing
flex_context.containing_block, // properties in the block axis.
style, let tentative_block_size = SizeConstraint::default();
preferred_aspect_ratio,
LogicalVec2 { // TODO: This is wrong if the item writing mode is different from the flex
block: block_sizes, // container's writing mode.
inline: &content_box_sizes.inline, let inline_size = {
}, let initial_behavior = if cross_size_stretches_to_container_size {
Size::FitContent.into(), Size::Stretch
LogicalVec2 {
inline: pbm_auto_is_zero.cross,
block: pbm_auto_is_zero.main,
},
)
};
if intrinsic_sizing_mode == IntrinsicSizingMode::Size {
get_used_size(&Sizes::default()).block
} else { } else {
get_used_size(&content_box_sizes.block).block Size::FitContent
} };
}, let stretch_size =
IndependentFormattingContextContents::NonReplaced(non_replaced) => { flex_context.containing_block.size.inline - pbm_auto_is_zero.cross;
// TODO: This is wrong if the item writing mode is different from the flex let get_content_size = || {
// container's writing mode. let constraint_space = ConstraintSpace::new(
let inline_size = { tentative_block_size,
let initial_behavior = if cross_size_stretches_to_container_size { style.writing_mode,
Size::Stretch preferred_aspect_ratio,
} else { );
Size::FitContent self.independent_formatting_context
}; .inline_content_sizes(flex_context.layout_context, &constraint_space)
let stretch_size = .sizes
flex_context.containing_block.size.inline - pbm_auto_is_zero.cross; };
let get_content_size = || { content_box_sizes.inline.resolve(
let constraint_space = ConstraintSpace::new( Direction::Inline,
SizeConstraint::default(), initial_behavior,
style.writing_mode, Au::zero,
non_replaced.preferred_aspect_ratio(), Some(stretch_size),
); get_content_size,
self.independent_formatting_context false,
.inline_content_sizes(flex_context.layout_context, &constraint_space) )
.sizes };
}; let item_as_containing_block = ContainingBlock {
content_box_sizes.inline.resolve( size: ContainingBlockSize {
Direction::Inline, inline: inline_size,
initial_behavior, block: tentative_block_size,
Au::zero, },
Some(stretch_size), style,
get_content_size, };
false, self.independent_formatting_context
) .layout(
}; flex_context.layout_context,
let item_as_containing_block = ContainingBlock { &mut positioning_context,
size: ContainingBlockSize { &item_as_containing_block,
inline: inline_size, flex_context.containing_block,
block: SizeConstraint::default(), preferred_aspect_ratio,
}, false, /* depends_on_block_constraints */
style, &LazySize::intrinsic(),
}; )
let mut content_block_size = || { .content_block_size
non_replaced };
.layout( match intrinsic_sizing_mode {
flex_context.layout_context, IntrinsicSizingMode::Contribution => {
&mut positioning_context, let stretch_size = flex_context
&item_as_containing_block, .containing_block
flex_context.containing_block, .size
&self.independent_formatting_context.base, .block
false, /* depends_on_block_constraints */ .to_definite()
&LazySize::intrinsic(), .map(|block_size| block_size - pbm_auto_is_zero.main);
) let inner_block_size = content_box_sizes.block.resolve(
.content_block_size Direction::Block,
}; Size::FitContent,
match intrinsic_sizing_mode { Au::zero,
IntrinsicSizingMode::Contribution => { stretch_size,
let stretch_size = flex_context || ContentSizes::from(content_block_size()),
.containing_block // Tables have a special sizing in the block axis that handles collapsed rows
.size // by ignoring the sizing properties and instead relying on the content block size,
.block // which should indirectly take sizing properties into account.
.to_definite() // However, above we laid out the table with a SizeConstraint::default() block size,
.map(|block_size| block_size - pbm_auto_is_zero.main); // so the content block size doesn't take sizing properties into account.
let inner_block_size = content_box_sizes.block.resolve( // Therefore, pretending that it's never a table tends to provide a better result.
Direction::Block, false, /* is_table */
Size::FitContent, );
Au::zero, inner_block_size + pbm_auto_is_zero.main
stretch_size,
|| ContentSizes::from(content_block_size()),
// Tables have a special sizing in the block axis that handles collapsed rows
// by ignoring the sizing properties and instead relying on the content block size,
// which should indirectly take sizing properties into account.
// However, above we laid out the table with a SizeConstraint::default() block size,
// so the content block size doesn't take sizing properties into account.
// Therefore, pretending that it's never a table tends to provide a better result.
false, /* is_table */
);
inner_block_size + pbm_auto_is_zero.main
},
IntrinsicSizingMode::Size => content_block_size(),
}
}, },
IntrinsicSizingMode::Size => content_block_size(),
} }
} }
} }