Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2318,14 +2318,21 @@ impl BlockFormattingContext {
}
}

pub(crate) fn resolve_block_level_root_block_size_before_body(&self, node: Node, input: &LayoutInput) {
pub(crate) fn resolve_block_level_root_block_size_before_body(
&self,
node: Node,
input: &LayoutInput,
flex_root_resolves_own_auto_block_size: bool,
) {
let resolution_space = self.sizing().available_space_for_block_size_resolution(
node,
input.available_space,
input.containing_block_constraints,
);
self.resolve_used_block_size_if_not_treated_as_auto(node, resolution_space, input.containing_block_constraints);
if self.facts(node).has_auto_content_box_size() || self.style(node).display().is_flex_inside() {
if self.facts(node).has_auto_content_box_size()
|| (self.style(node).display().is_flex_inside() && !flex_root_resolves_own_auto_block_size)
{
self.resolve_used_block_size_if_treated_as_auto(
node,
resolution_space,
Expand All @@ -2335,7 +2342,12 @@ impl BlockFormattingContext {
}
}

pub(crate) fn dimension_float_root(&self, node: Node, input: &LayoutInput) {
pub(crate) fn dimension_float_root(
&self,
node: Node,
input: &LayoutInput,
flex_root_resolves_own_auto_block_size: bool,
) {
let available_space = input.available_space;
let block_container = self.containing_block(node);
let block_container_inline_size = self.used(block_container).content_inline_size.get();
Expand All @@ -2360,7 +2372,9 @@ impl BlockFormattingContext {
},
);
self.resolve_used_block_size_if_not_treated_as_auto(node, available_space, input.containing_block_constraints);
if self.facts(node).has_auto_content_box_size() || self.style(node).display().is_flex_inside() {
if self.facts(node).has_auto_content_box_size()
|| (self.style(node).display().is_flex_inside() && !flex_root_resolves_own_auto_block_size)
{
self.resolve_used_block_size_if_treated_as_auto(
node,
available_space,
Expand Down
85 changes: 74 additions & 11 deletions Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,9 @@ impl<'pass> FlexFormattingContext<'pass> {
main_size =
main_size.min(self.main_size_from_cross_size_and_aspect_ratio(max_cross_size.to_px(reference), ratio));
}
if !min_cross_size.is_auto() {
if !min_cross_size.is_auto()
&& (!min_cross_size.contains_percentage() || self.has_definite_cross_size_used(&self.container_used()))
{
main_size =
main_size.max(self.main_size_from_cross_size_and_aspect_ratio(min_cross_size.to_px(reference), ratio));
}
Expand Down Expand Up @@ -1629,7 +1631,10 @@ impl<'pass> FlexFormattingContext<'pass> {
// to the flex container instead of transferring the used main size through the aspect ratio.
let replaced_with_only_natural_ratio =
facts.is_replaced_box() && !(facts.has_auto_content_width() && facts.has_auto_content_height());
if replaced_with_only_natural_ratio && !self.flex_items[index].used_flex_basis_is_definite {
if replaced_with_only_natural_ratio
&& !self.flex_items[index].used_flex_basis_is_definite
&& self.has_definite_cross_size_used(&self.container_used())
{
self.flex_items[index].hypothetical_cross_size =
css_clamp(self.inner_cross_size_used(&self.container_used()), clamp_min, clamp_max);
return;
Expand Down Expand Up @@ -2707,6 +2712,64 @@ impl<'pass> FlexFormattingContext<'pass> {
}
}

fn resolve_own_auto_block_size_from_max_content_main_size(&mut self) {
let Some(resolution_space) = self.layout_input.unwrap().sizing.flex_self_block_size_resolution_space else {
return;
};
if self.main_axis_is_horizontal() {
return;
}
// https://drafts.csswg.org/css-flexbox-1/#algo-main-item
// If the used flex basis is content or depends on its available space, and the flex container
// is being sized under a min-content or max-content constraint (e.g. when performing automatic
// table layout [CSS2]), size the item under that constraint.
let real_space_for_items = self.available_space_for_items.unwrap().space;
self.determine_available_space_for_items(AvailableSpace {
inline_size: real_space_for_items.inline_size,
block_size: AvailableSize::MaxContent,
});
// https://drafts.csswg.org/css-align-3/#gap-percent
// In Flex Layout: Cyclic percentage sizes resolve against zero in all cases.
self.container_used().set_content_block_size(CssPixels::default());
for index in 0..self.flex_items.len() {
self.determine_flex_base_size(index);
}
let max_content_main_size = self.calculate_intrinsic_main_size_of_flex_container();
self.flex_lines.clear();
self.determine_available_space_for_items(real_space_for_items);
self.resolve_own_auto_block_size(max_content_main_size, resolution_space);
}

fn resolve_own_auto_block_size_from_line_cross_sizes(&mut self) {
let Some(resolution_space) = self.layout_input.unwrap().sizing.flex_self_block_size_resolution_space else {
return;
};
if self.cross_axis_is_horizontal() {
return;
}
// https://drafts.csswg.org/css-align-3/#gap-percent
// In Flex Layout: Cyclic percentage sizes resolve against zero in all cases.
let style = self.style(self.flex_container);
let gap = if self.is_row_layout() { style.row_gap() } else { style.column_gap() };
let cross_gap_resolved_against_zero = gap.to_px(CssPixels::default());
let mut line_cross_size_sum = self
.flex_lines
.iter()
.fold(CssPixels::default(), |sum, line| sum + line.cross_size);
line_cross_size_sum += cross_gap_resolved_against_zero * self.flex_lines.len().saturating_sub(1);
self.resolve_own_auto_block_size(line_cross_size_sum, resolution_space);
Comment on lines +2750 to +2760

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep cyclic percentage cross gaps resolved against zero.

The helper resolves a cyclic percentage cross gap against zero. After line 3101 resolves the automatic block size, align_all_flex_lines() recalculates cross_gap() against that resolved size.

A wrapping auto-height row flex container with row-gap: 50% will size without the gap, then place lines with a nonzero gap. The lines can overflow the resolved block size.

  • Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs#L2750-L2760: retain the zero-based cross-gap result for the self-resolution flow.
  • Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs#L3098-L3101: ensure later line alignment does not resolve the cyclic gap against the newly resolved block size.
📍 Affects 1 file
  • Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs#L2750-L2760 (this comment)
  • Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs#L3098-L3101
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs` around lines
2750 - 2760, Preserve the zero-resolved cross-gap in the self-resolution flow at
Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs:2750-2760. Update
the later automatic block-size and line-alignment flow at
Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs:3098-3101 so
align_all_flex_lines() does not recompute the cyclic gap against the newly
resolved block size; reuse the zero-based gap result when positioning lines.

}

fn resolve_own_auto_block_size(&self, automatic_block_size: CssPixels, resolution_space: AvailableSpace) {
self.sizing().resolve_used_block_size_if_treated_as_auto(
self.flex_container,
resolution_space,
self.layout_input.unwrap().containing_block_constraints,
Some(automatic_block_size),
|| automatic_block_size,
);
}

// https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes
fn calculate_intrinsic_main_size_of_flex_container(&mut self) -> CssPixels {
// The min-content main size of a single-line flex container is calculated identically to the max-content main size,
Expand Down Expand Up @@ -2938,6 +3001,11 @@ impl<'pass> FlexFormattingContext<'pass> {
}
}

// 4. Determine the main size of the flex container
// Determine the main size of the flex container using the rules of the formatting context in which it participates.
// NOTE: The automatic block size of a block-level flex container is its max-content size.
self.resolve_own_auto_block_size_from_max_content_main_size();

// 3. Determine the flex base size and hypothetical main size of each item
for index in 0..self.flex_items.len() {
self.determine_flex_base_size(index);
Expand Down Expand Up @@ -3000,14 +3068,6 @@ impl<'pass> FlexFormattingContext<'pass> {
css_clamp(self.flex_items[index].flex_base_size, clamp_min, clamp_max).max(CssPixels::default());
}

// 4. Determine the main size of the flex container
// Determine the main size of the flex container using the rules of the formatting context in which it participates.
// NOTE: The automatic block size of a block-level flex container is its max-content size.

// NOTE: We've already handled this in the parent formatting context.
// Specifically, all formatting contexts will have assigned inline and block sizes to the flex container
// before this formatting context runs.

// 5. Collect flex items into flex lines:
// After this step no additional items are to be added to flex_lines or any of its items!
self.collect_flex_items_into_flex_lines();
Expand Down Expand Up @@ -3035,7 +3095,10 @@ impl<'pass> FlexFormattingContext<'pass> {
self.align_all_flex_items_along_the_cross_axis();

// 15. Determine the flex container’s used cross size
// NOTE: This is handled by the parent formatting context.
// Determine the flex container's used cross size using the rules of the formatting context
// in which it participates. If a content-based cross size is needed, use the sum of the
// flex lines' cross sizes.
self.resolve_own_auto_block_size_from_line_cross_sizes();

// https://drafts.csswg.org/css-flexbox-1/#definite-sizes
// 4. Once the cross size of a flex line has been determined,
Expand Down
92 changes: 86 additions & 6 deletions Libraries/LibWeb/Rust/src/layout/formatting_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,12 @@ pub(crate) fn independent_root_automatic_block_size(
return automatic_content_block_size_of_completed_run.unwrap_or_default();
}
let style = StyleValues::for_node(callbacks, node);
if style.display().is_flex_inside()
&& let Some(automatic_content_block_size_reported_by_completed_run) =
automatic_content_block_size_of_completed_run
{
return automatic_content_block_size_reported_by_completed_run;
}
if style.display().is_flex_inside() || style.display().is_grid_inside() || style.display().is_table_inside() {
// The automatic block size of a flex, grid, or table container is its
// max-content size.
Expand All @@ -1306,6 +1312,63 @@ pub(crate) fn independent_root_automatic_block_size(
CssPixels::default()
}

fn flex_self_block_size_resolution_space(
run: &FormattingContextRun,
resolution_space: AvailableSpace,
constraints: ContainingBlockConstraints,
) -> Option<AvailableSpace> {
run.fragments.as_ref()?;
let node = run.box_;
let style = StyleValues::for_node(&run.callbacks, node);
if !style.display().is_flex_inside() || !style.min_height().is_auto() {
return None;
}
if NodeFacts::new(&run.callbacks, node).document_in_quirks_mode() {
return None;
}
let sizing = run.sizing();
if !sizing.should_treat_block_size_as_auto(node, resolution_space, constraints)
|| sizing.box_is_sized_as_replaced_element(node, resolution_space, constraints)
{
return None;
}
Some(resolution_space)
}

// Flex containers with an automatic block size are treated as max-content, so resolve it early.
fn eagerly_resolve_atomic_flex_root_auto_block_size(
run: &FormattingContextRun,
input: &LayoutInput,
inline_definite_space: AvailableSpace,
) {
let node = run.box_;
let sizing = run.sizing();
if !StyleValues::for_node(&run.callbacks, node).display().is_flex_inside()
|| sizing.box_is_sized_as_replaced_element(node, input.available_space, input.containing_block_constraints)
{
return;
}
sizing.resolve_used_block_size_if_treated_as_auto(
node,
inline_definite_space,
input.containing_block_constraints,
None,
|| {
independent_root_automatic_block_size(
run.purpose,
&run.records,
&run.callbacks,
node,
run.records
.used_values(node)
.available_inner_space_or_constraints_from(inline_definite_space),
input.containing_block_constraints,
None,
)
},
);
}

fn apply_root_sizing_directives(
run: &FormattingContextRun,
input: &LayoutInput,
Expand All @@ -1315,8 +1378,12 @@ fn apply_root_sizing_directives(
ParticipationInParentFormattingContext::BlockLevel => dimension_block_level_root(run, input, parent_block),
ParticipationInParentFormattingContext::Float => {
let parent = parent_block.expect("a floating run requires an enclosing block formatting context");
parent.dimension_float_root(run.box_, input);
body_input_with_inner_available_space(run, input)
let flex_self_resolution_space =
flex_self_block_size_resolution_space(run, input.available_space, input.containing_block_constraints);
parent.dimension_float_root(run.box_, input, flex_self_resolution_space.is_some());
let mut body_input = body_input_with_inner_available_space(run, input);
body_input.sizing.flex_self_block_size_resolution_space = flex_self_resolution_space;
body_input
}
ParticipationInParentFormattingContext::AtomicInline => {
run.sizing().dimension_atomic_root(
Expand All @@ -1325,7 +1392,18 @@ fn apply_root_sizing_directives(
input.containing_block_constraints,
run.layout_mode,
);
body_input_with_inner_available_space(run, input)
let mut body_input = body_input_with_inner_available_space(run, input);
let inline_definite_space = AvailableSpace {
inline_size: AvailableSize::definite(run.records.used_values(run.box_).content_inline_size.get()),
block_size: AvailableSize::Indefinite,
};
let flex_self_resolution_space =
flex_self_block_size_resolution_space(run, inline_definite_space, input.containing_block_constraints);
if flex_self_resolution_space.is_none() {
eagerly_resolve_atomic_flex_root_auto_block_size(run, input, inline_definite_space);
}
body_input.sizing.flex_self_block_size_resolution_space = flex_self_resolution_space;
body_input
}
ParticipationInParentFormattingContext::AbsolutelyPositioned(abspos_inputs) => {
AbsposEngine::for_run(run).dimension_out_of_flow_root(run.box_, abspos_inputs);
Expand Down Expand Up @@ -1367,7 +1445,8 @@ fn dimension_block_level_root(
let constraints = input.containing_block_constraints;
let parent = parent_block.expect("a block-level run requires an enclosing block formatting context");
parent.commit_block_level_root_inline_size(node, input);
parent.resolve_block_level_root_block_size_before_body(node, input);
let flex_self_resolution_space = flex_self_block_size_resolution_space(run, available_space, constraints);
parent.resolve_block_level_root_block_size_before_body(node, input, flex_self_resolution_space.is_some());
let sizing = run.sizing();
let style = StyleValues::for_node(&run.callbacks, node);
let mut body_input = body_input_with_inner_available_space(run, input);
Expand All @@ -1388,6 +1467,7 @@ fn dimension_block_level_root(
constraints,
measured_content_block_size,
);
body_input.sizing.flex_self_block_size_resolution_space = flex_self_resolution_space;
body_input
}

Expand Down Expand Up @@ -1431,11 +1511,11 @@ fn size_skipped_independent_root(
ParticipationInParentFormattingContext::BlockLevel => {
let parent = parent_block.expect("a block-level run requires an enclosing block formatting context");
parent.commit_block_level_root_inline_size(child, input);
parent.resolve_block_level_root_block_size_before_body(child, input);
parent.resolve_block_level_root_block_size_before_body(child, input, false);
}
ParticipationInParentFormattingContext::Float => {
let parent = parent_block.expect("a floating run requires an enclosing block formatting context");
parent.dimension_float_root(child, input);
parent.dimension_float_root(child, input, false);
parent.finalize_float_root(child, input, None);
}
ParticipationInParentFormattingContext::AbsolutelyPositioned(abspos_inputs) => {
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibWeb/Rust/src/layout/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub(crate) struct RootSizingDirectives {
// wrapper) can be placed in wrapper coordinates.
pub(crate) table_box_content_block_offset_in_wrapper: Option<CssPixels>,
pub(crate) adopt_automatic_content_block_size: bool,
pub(crate) flex_self_block_size_resolution_space: Option<AvailableSpace>,
pub(crate) float_avoidance_inline_size: Option<CssPixels>,
pub(crate) outer_float_intrusion_before_list_item_children: SpaceUsedByFloats,
pub(crate) treat_block_axis_percentage_insets_as_auto_beyond_root: bool,
Expand Down
15 changes: 0 additions & 15 deletions Libraries/LibWeb/Rust/src/layout/sizing_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1137,21 +1137,6 @@ impl SizingContext {
block_size: AvailableSize::Indefinite,
};
self.resolve_used_block_size_if_not_treated_as_auto(node, inline_definite_space, constraints);
if style.display().is_flex_inside() {
// Flex containers with an automatic block size are treated as max-content, so resolve it early.
self.resolve_used_block_size_if_treated_as_auto(node, inline_definite_space, constraints, None, || {
crate::layout::independent_root_automatic_block_size(
self.purpose,
&self.records,
&self.callbacks,
node,
self.used(node)
.available_inner_space_or_constraints_from(inline_definite_space),
constraints,
None,
)
});
}
self.make_button_content_box_definite(node, layout_mode, available_space, constraints, None);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children: not-inline
BlockContainer <html> at [0,0] [0+0+0 800 0+0+0] [0+0+0 96 0+0+0] [BFC] children: not-inline
BlockContainer <body> at [8,8] [8+0+0 784 0+0+8] [8+0+0 80 0+0+8] children: not-inline
Box <div#auto-height-flex> at [8,8] flex-container(row) [0+0+0 784 0+0+0] [0+0+0 80 0+0+0] [FFC] children: not-inline
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
TextNode <#text> (not painted)
SVGSVGBox <svg> at [8,8] flex-item [0+0+0 60 0+0+0] [0+0+0 30 0+0+0] [SVG] children: not-inline
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
TextNode <#text> (not painted)
BlockContainer <div> at [68,8] flex-item [0+0+0 50 0+0+0] [0+0+0 80 0+0+0] [BFC] children: not-inline
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
TextNode <#text> (not painted)
BlockContainer <(anonymous)> at [8,88] [0+0+0 784 0+0+0] [0+0+0 0 0+0+0] children: inline
TextNode <#text> (not painted)

ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x96]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x80]
Paintable (Box<DIV>#auto-height-flex) [8,8 784x80]
SVGSVGPaintable (SVGSVGBox<svg>) [8,8 60x30]
PaintableWithLines (BlockContainer<DIV>) [68,8 50x80]
PaintableWithLines (BlockContainer(anonymous)) [8,88 784x0]

SC for Viewport<#document> [0,0 800x600] (z-index: auto)
SC for BlockContainer<HTML> [0,0 800x96] (z-index: auto)
Loading
Loading