diff --git a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs index 9b16770721cbf..626e24cf967f6 100644 --- a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs @@ -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, @@ -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(); @@ -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, diff --git a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs index 6257df4a17f36..1d5d47bf6821b 100644 --- a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs @@ -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)); } @@ -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; @@ -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); + } + + 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, @@ -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); @@ -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(); @@ -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, diff --git a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs index 71bfe49de8e1b..6dd138b93fee1 100644 --- a/Libraries/LibWeb/Rust/src/layout/formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/formatting_context.rs @@ -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. @@ -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 { + 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, @@ -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( @@ -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); @@ -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); @@ -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 } @@ -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) => { diff --git a/Libraries/LibWeb/Rust/src/layout/geometry.rs b/Libraries/LibWeb/Rust/src/layout/geometry.rs index fa84d623852d5..ef533fa5fc519 100644 --- a/Libraries/LibWeb/Rust/src/layout/geometry.rs +++ b/Libraries/LibWeb/Rust/src/layout/geometry.rs @@ -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, pub(crate) adopt_automatic_content_block_size: bool, + pub(crate) flex_self_block_size_resolution_space: Option, pub(crate) float_avoidance_inline_size: Option, pub(crate) outer_float_intrusion_before_list_item_children: SpaceUsedByFloats, pub(crate) treat_block_axis_percentage_insets_as_auto_beyond_root: bool, diff --git a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs index c878f2370b07e..f866b657ab3f5 100644 --- a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs @@ -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); } diff --git a/Tests/LibWeb/Layout/expected/flex/auto-height-row-flex-with-natural-ratio-only-svg-item.txt b/Tests/LibWeb/Layout/expected/flex/auto-height-row-flex-with-natural-ratio-only-svg-item.txt new file mode 100644 index 0000000000000..4a9855b677615 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/flex/auto-height-row-flex-with-natural-ratio-only-svg-item.txt @@ -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 at [0,0] [0+0+0 800 0+0+0] [0+0+0 96 0+0+0] [BFC] children: not-inline + BlockContainer at [8,8] [8+0+0 784 0+0+8] [8+0+0 80 0+0+8] children: not-inline + Box 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 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
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) [0,0 800x96] + PaintableWithLines (BlockContainer) [8,8 784x80] + Paintable (Box
#auto-height-flex) [8,8 784x80] + SVGSVGPaintable (SVGSVGBox) [8,8 60x30] + PaintableWithLines (BlockContainer
) [68,8 50x80] + PaintableWithLines (BlockContainer(anonymous)) [8,88 784x0] + +SC for Viewport<#document> [0,0 800x600] (z-index: auto) + SC for BlockContainer [0,0 800x96] (z-index: auto) diff --git a/Tests/LibWeb/Layout/expected/flex/auto-height-row-flex-with-percentage-min-height-through-aspect-ratio.txt b/Tests/LibWeb/Layout/expected/flex/auto-height-row-flex-with-percentage-min-height-through-aspect-ratio.txt new file mode 100644 index 0000000000000..995b75f8fbc7a --- /dev/null +++ b/Tests/LibWeb/Layout/expected/flex/auto-height-row-flex-with-percentage-min-height-through-aspect-ratio.txt @@ -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 at [0,0] [0+0+0 800 0+0+0] [0+0+0 76 0+0+0] [BFC] children: not-inline + BlockContainer at [8,8] [8+0+0 784 0+0+8] [8+0+0 60 0+0+8] children: not-inline + Box at [8,8] flex-container(row) [0+0+0 784 0+0+0] [0+0+0 60 0+0+0] [FFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> (not painted) + BlockContainer at [8,8] flex-item [0+0+0 0 0+0+0] [0+0+0 60 0+0+0] [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> (not painted) + BlockContainer at [8,8] flex-item [0+0+0 40 0+0+0] [0+0+0 60 0+0+0] [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> (not painted) + BlockContainer <(anonymous)> at [8,68] [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) [0,0 800x76] + PaintableWithLines (BlockContainer) [8,8 784x60] + Paintable (Box
#auto-height-flex) [8,8 784x60] + PaintableWithLines (BlockContainer
#ratio-item) [8,8 0x60] + PaintableWithLines (BlockContainer
#fixed-item) [8,8 40x60] + PaintableWithLines (BlockContainer(anonymous)) [8,68 784x0] + +SC for Viewport<#document> [0,0 800x600] (z-index: auto) + SC for BlockContainer [0,0 800x76] (z-index: auto) diff --git a/Tests/LibWeb/Layout/input/flex/auto-height-row-flex-with-natural-ratio-only-svg-item.html b/Tests/LibWeb/Layout/input/flex/auto-height-row-flex-with-natural-ratio-only-svg-item.html new file mode 100644 index 0000000000000..49724cde1efd8 --- /dev/null +++ b/Tests/LibWeb/Layout/input/flex/auto-height-row-flex-with-natural-ratio-only-svg-item.html @@ -0,0 +1,21 @@ + + +
+ +
+
diff --git a/Tests/LibWeb/Layout/input/flex/auto-height-row-flex-with-percentage-min-height-through-aspect-ratio.html b/Tests/LibWeb/Layout/input/flex/auto-height-row-flex-with-percentage-min-height-through-aspect-ratio.html new file mode 100644 index 0000000000000..647f255d6eee1 --- /dev/null +++ b/Tests/LibWeb/Layout/input/flex/auto-height-row-flex-with-percentage-min-height-through-aspect-ratio.html @@ -0,0 +1,21 @@ + + +
+
+
+