From 107bdb5fe72f380f149faf99a6a06e676ef28716 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:16:44 +0200 Subject: [PATCH 01/12] LibWeb: Drop the layout engine's `_mut` used-values accessors Every one of `used_mut()`, `item_used_mut()` and `container_used_mut()` took `&self` and returned the same `&UsedValues` as its non-`_mut` counterpart, so the name promised a mutable borrow that Rust never handed out. They are leftovers from the C++ layout code, where the distinction was real. Remove the aliases and call the plain accessors instead. --- .../LibWeb/Rust/src/layout/abspos_engine.rs | 26 +++++------ .../src/layout/block_formatting_context.rs | 32 ++++++-------- .../src/layout/flex_formatting_context.rs | 44 ++++++++----------- .../src/layout/grid_formatting_context.rs | 22 +++------- .../src/layout/inline_formatting_context.rs | 4 -- .../LibWeb/Rust/src/layout/line_builder.rs | 2 +- .../LibWeb/Rust/src/layout/sizing_context.rs | 24 +++++----- 7 files changed, 61 insertions(+), 93 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs b/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs index f83558eaa7798..7723384857fdb 100644 --- a/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs +++ b/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs @@ -90,10 +90,6 @@ impl<'pass> AbsposEngine<'pass> { self.state.used_values(&self.callbacks, node) } - fn used_mut(&self, node: Node) -> &'pass UsedValues { - self.used(node) - } - fn static_position_containing_block(&self, node: Node) -> Node { self.callbacks.static_position_containing_block(node) } @@ -860,7 +856,7 @@ impl AbsposEngine<'_> { } let content_inline_size = shrink_to_fit(left, margin_left, margin_right, right); inline_size = Some(content_inline_size); - self.used_mut(node).set_content_inline_size(content_inline_size); + self.used(node).set_content_inline_size(content_inline_size); left = self.static_offset(node, static_position_rect).inline_offset; right = solve_for_right(left, inline_size, margin_left, margin_right); } @@ -980,7 +976,7 @@ impl AbsposEngine<'_> { } } - let used = self.used_mut(node); + let used = self.used(node); used.set_content_inline_size(auto_px_value(used_inline_size)); used.inset_left.set(left); used.inset_right.set(right); @@ -1019,7 +1015,7 @@ impl AbsposEngine<'_> { }, ); - let used = self.used_mut(node); + let used = self.used(node); used.inset_left.set(solution.start); used.inset_right.set(solution.end); used.margin_left.set(solution.margin_start); @@ -1159,7 +1155,7 @@ impl AbsposEngine<'_> { }; block_size = Some(automatic); let constrained = self.apply_min_max_block_size_constraints(node, available_space, constraints, block_size); - self.used_mut(node).set_content_block_size(auto_px_value(constrained)); + self.used(node).set_content_block_size(auto_px_value(constrained)); top = Some(self.static_offset(node, static_position_rect).block_offset); bottom = Some(solve_for( bottom, @@ -1371,7 +1367,7 @@ impl AbsposEngine<'_> { let containing_block_inline_size = available_space.inline_size.to_px_or_zero(); let containing_block_block_size = available_space.block_size.to_px_or_zero(); - let used = self.used_mut(node); + let used = self.used(node); used.set_content_block_size(auto_px_value(used_block_size)); if style.height().is_auto() && pass == BlockSizePass::BeforeInsideLayout { return; @@ -1423,7 +1419,7 @@ impl AbsposEngine<'_> { }, ); - let used = self.used_mut(node); + let used = self.used(node); used.set_content_block_size(block_size); if style.height().is_auto() && pass == BlockSizePass::BeforeInsideLayout { return; @@ -1465,7 +1461,7 @@ impl<'pass> AbsposEngine<'pass> { let containing_block_inline_size = available_space.inline_size.to_px_or_zero(); let style = self.style(node); { - let used = self.used_mut(node); + let used = self.used(node); used.border_left.set(style.border_left_width()); used.border_right.set(style.border_right_width()); used.border_top.set(style.border_top_width()); @@ -1490,7 +1486,7 @@ impl<'pass> AbsposEngine<'pass> { ); { - let used = self.used_mut(node); + let used = self.used(node); if !style.inset_left().is_auto() && !style.inset_right().is_auto() { used.has_definite_inline_size.set(true); } @@ -1505,7 +1501,7 @@ impl<'pass> AbsposEngine<'pass> { let block_size_resolved_from_aspect_ratio = style.height().is_auto() && self.facts(node).has_preferred_aspect_ratio() && self.used(node).has_definite_inline_size(); - let used = self.used_mut(node); + let used = self.used(node); used.has_definite_inline_size.set(true); if (!style.height().is_auto() && !style.height().is_intrinsic_sizing_constraint()) || block_size_resolved_from_aspect_ratio @@ -1543,7 +1539,7 @@ impl<'pass> AbsposEngine<'pass> { } { - let used = self.used_mut(node); + let used = self.used(node); let collapsed = used.uses_collapsing_borders_model.get(); if let Some(inline_alignment) = inputs.containing_block_info.inline_alignment && style.inset_left().is_auto() @@ -1743,7 +1739,7 @@ impl<'pass> AbsposEngine<'pass> { block_axis_inset_value(style.inset_bottom()), containing_block_size.block_size, ); - let used = self.used_mut(node); + let used = self.used(node); used.inset_left.set(left); used.inset_right.set(right); used.inset_top.set(top); diff --git a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs index e1ad059ec5e9a..c717ede9c84c6 100644 --- a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs @@ -236,10 +236,6 @@ impl<'pass> BlockFormattingContext<'pass> { self.state.used_values(&self.callbacks, node) } - fn used_mut(&self, node: Node) -> &'pass UsedValues { - self.used(node) - } - fn create_used_values(&self, node: Node, constraints: ContainingBlockConstraints) -> &'pass UsedValues { self.state.create_used_values(&self.callbacks, node, constraints) } @@ -358,7 +354,7 @@ impl<'pass> BlockFormattingContext<'pass> { fn resolve_vertical_box_model_metrics(&self, node: Node, containing_block_inline_size: CssPixels) { let style = self.style(node); - let used = self.used_mut(node); + let used = self.used(node); used.margin_top .set(style.margin_top().to_px(containing_block_inline_size)); used.margin_bottom @@ -448,7 +444,7 @@ impl<'pass> BlockFormattingContext<'pass> { let content_inline_size = self.resolve_root_inline_metrics_and_content_size(node, available_space, constraints, float_avoidance_inline_size); if let Some(content_inline_size) = content_inline_size { - self.used_mut(node).set_content_inline_size(content_inline_size); + self.used(node).set_content_inline_size(content_inline_size); } } @@ -476,7 +472,7 @@ impl<'pass> BlockFormattingContext<'pass> { // margins. Replaced sizing consults the box's own used metrics, // so they are written first. { - let used = self.used_mut(node); + let used = self.used(node); used.margin_left.set(style.margin_left().to_px(available_inline_size)); used.margin_right.set(style.margin_right().to_px(available_inline_size)); used.border_left.set(style.border_left_width()); @@ -507,7 +503,7 @@ impl<'pass> BlockFormattingContext<'pass> { let border_left_width = style.border_left_width(); let border_right_width = style.border_right_width(); { - let used = self.used_mut(node); + let used = self.used(node); used.margin_left.set(margin_left); used.margin_right.set(margin_right); used.border_left.set(border_left_width); @@ -700,7 +696,7 @@ impl<'pass> BlockFormattingContext<'pass> { } { - let used = self.used_mut(node); + let used = self.used(node); used.margin_left.set(margin_left); used.margin_right.set(margin_right); } @@ -723,7 +719,7 @@ impl<'pass> BlockFormattingContext<'pass> { let padding_left = style.padding_left().to_px(containing_block_inline_size); let padding_right = style.padding_right().to_px(containing_block_inline_size); { - let used = self.used_mut(node); + let used = self.used(node); used.padding_left.set(padding_left); used.padding_right.set(padding_right); used.margin_left.set(margin_left); @@ -1430,7 +1426,7 @@ impl<'pass> BlockFormattingContext<'pass> { let max_content_inline_size = self .sizing() .calculate_max_content_inline_size(marker, marker_constraints); - let marker_used = self.used_mut(marker); + let marker_used = self.used(marker); marker_used.set_content_inline_size(max_content_inline_size); marker_used.has_definite_inline_size.set(true); let inner_available_space = crate::layout::AvailableSpace { @@ -1692,7 +1688,7 @@ impl<'pass> BlockFormattingContext<'pass> { if !has_independent_formatting_context && let Some(content_inline_size) = probe.content_inline_size { - self.used_mut(node).set_content_inline_size(content_inline_size); + self.used(node).set_content_inline_size(content_inline_size); } let content_inline_size_now = probe .content_inline_size @@ -1902,7 +1898,7 @@ impl<'pass> BlockFormattingContext<'pass> { if let Some(position) = pending_position { if let Some(coordinate) = containing_line_box_fragment { - let used = self.used_mut(node); + let used = self.used(node); used.has_containing_line_box_fragment.set(true); used.containing_line_box_fragment.set(coordinate); } @@ -2039,7 +2035,7 @@ impl<'pass> BlockFormattingContext<'pass> { )); } } - let used = self.used_mut(block_container); + let used = self.used(block_container); used.set_content_inline_size(inline_size); used.set_content_block_size(bottom_of_lowest_margin_box); } @@ -2112,7 +2108,7 @@ impl<'pass> BlockFormattingContext<'pass> { )); } } - let used = self.used_mut(fieldset); + let used = self.used(fieldset); used.set_content_inline_size(inline_size); used.set_content_block_size(bottom_of_lowest_margin_box); } @@ -2308,7 +2304,7 @@ impl<'pass> BlockFormattingContext<'pass> { input.containing_block_constraints, input.sizing.float_avoidance_inline_size, ) { - self.used_mut(node).set_content_inline_size(content_inline_size); + self.used(node).set_content_inline_size(content_inline_size); } } @@ -2375,7 +2371,7 @@ impl<'pass> BlockFormattingContext<'pass> { if self.facts(node).is_table_wrapper() && let Some((automatic_content_inline_size, _)) = run_automatic_sizes { - self.used_mut(node).set_content_inline_size(automatic_content_inline_size); + self.used(node).set_content_inline_size(automatic_content_inline_size); } self.resolve_used_block_size_if_treated_as_auto( node, @@ -2593,7 +2589,7 @@ impl<'pass> BlockFormattingContext<'pass> { used_inline_size = used_inline_size.max(minimum); } } - let used = self.used_mut(block_container); + let used = self.used(block_container); used.set_content_inline_size(used_inline_size); used.set_content_block_size(automatic_block_size); } diff --git a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs index 5fa16b968628c..5f756826364c5 100644 --- a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs @@ -214,18 +214,10 @@ impl<'pass> FlexFormattingContext<'pass> { self.flex_items[index].used_values } - fn item_used_mut(&self, index: usize) -> &'pass UsedValues { - self.flex_items[index].used_values - } - fn container_used(&self) -> &'pass UsedValues { self.flex_container_state } - fn container_used_mut(&self) -> &'pass UsedValues { - self.flex_container_state - } - fn style(&self, node: Node) -> StyleValues<'pass> { self.state.style_facts(&self.callbacks, node) } @@ -432,49 +424,49 @@ impl<'pass> FlexFormattingContext<'pass> { fn set_has_definite_main_size(&mut self, index: usize) { if self.main_axis_is_horizontal() { - self.item_used_mut(index).has_definite_inline_size.set(true); + self.item_used(index).has_definite_inline_size.set(true); } else { - self.item_used_mut(index).has_definite_block_size.set(true); + self.item_used(index).has_definite_block_size.set(true); } } fn set_has_definite_cross_size(&mut self, index: usize) { if self.cross_axis_is_horizontal() { - self.item_used_mut(index).has_definite_inline_size.set(true); + self.item_used(index).has_definite_inline_size.set(true); } else { - self.item_used_mut(index).has_definite_block_size.set(true); + self.item_used(index).has_definite_block_size.set(true); } } fn set_main_size(&mut self, index: usize, size: CssPixels) { if self.main_axis_is_horizontal() { - self.item_used_mut(index).set_content_inline_size(size); + self.item_used(index).set_content_inline_size(size); } else { - self.item_used_mut(index).set_content_block_size(size); + self.item_used(index).set_content_block_size(size); } } fn set_cross_size(&mut self, index: usize, size: CssPixels) { if self.cross_axis_is_horizontal() { - self.item_used_mut(index).set_content_inline_size(size); + self.item_used(index).set_content_inline_size(size); } else { - self.item_used_mut(index).set_content_block_size(size); + self.item_used(index).set_content_block_size(size); } } fn set_container_main_size(&mut self, size: CssPixels) { if self.main_axis_is_horizontal() { - self.container_used_mut().set_content_inline_size(size); + self.container_used().set_content_inline_size(size); } else { - self.container_used_mut().set_content_block_size(size); + self.container_used().set_content_block_size(size); } } fn set_container_cross_size(&mut self, size: CssPixels) { if self.cross_axis_is_horizontal() { - self.container_used_mut().set_content_inline_size(size); + self.container_used().set_content_inline_size(size); } else { - self.container_used_mut().set_content_block_size(size); + self.container_used().set_content_block_size(size); } } @@ -734,7 +726,7 @@ impl<'pass> FlexFormattingContext<'pass> { let padding_top = style.padding_top().to_px(basis); let padding_bottom = style.padding_bottom().to_px(basis); { - let used = self.item_used_mut(index); + let used = self.item_used(index); used.padding_left.set(padding_left); used.padding_right.set(padding_right); used.padding_top.set(padding_top); @@ -1872,18 +1864,18 @@ impl<'pass> FlexFormattingContext<'pass> { fn set_main_axis_first_margin(&mut self, index: usize, margin: CssPixels) { self.flex_items[index].margins.main_before = margin; if self.main_axis_is_horizontal() { - self.item_used_mut(index).margin_left.set(margin); + self.item_used(index).margin_left.set(margin); } else { - self.item_used_mut(index).margin_top.set(margin); + self.item_used(index).margin_top.set(margin); } } fn set_main_axis_second_margin(&mut self, index: usize, margin: CssPixels) { self.flex_items[index].margins.main_after = margin; if self.main_axis_is_horizontal() { - self.item_used_mut(index).margin_right.set(margin); + self.item_used(index).margin_right.set(margin); } else { - self.item_used_mut(index).margin_bottom.set(margin); + self.item_used(index).margin_bottom.set(margin); } } @@ -2347,7 +2339,7 @@ impl<'pass> FlexFormattingContext<'pass> { for index in 0..self.flex_items.len() { let style = self.style(self.flex_items[index].box_); { - let used = self.item_used_mut(index); + let used = self.item_used(index); used.margin_left.set(style.margin_left().to_px(reference)); used.margin_right.set(style.margin_right().to_px(reference)); used.margin_top.set(style.margin_top().to_px(reference)); diff --git a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs index cb36c30f8550c..415c93d448937 100644 --- a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs @@ -1179,18 +1179,10 @@ impl<'pass> GridFormattingContext<'pass> { self.container_used_values } - fn container_used_mut(&self) -> &'pass UsedValues { - self.container_used_values - } - fn used(&self, item: GridItem<'pass>) -> &'pass UsedValues { item.used_values } - fn used_mut(&self, item: GridItem<'pass>) -> &'pass UsedValues { - item.used_values - } - fn style(&self, node: Node) -> StyleValues<'pass> { self.state.style_facts(&self.callbacks, node) } @@ -2713,7 +2705,7 @@ impl<'pass> GridFormattingContext<'pass> { let item_start = item.position(axis); let item_end = item_start + item.span(axis) as i32; let track_count = self.axis_tracks(axis).len() as i32; - let used = self.used_mut(item); + let used = self.used(item); if axis.is_column() { used.padding_left.set(style.padding_left().to_px(inline_basis)); used.padding_right.set(style.padding_right().to_px(inline_basis)); @@ -2974,7 +2966,7 @@ impl<'pass> GridFormattingContext<'pass> { // display:table, the anonymous table wrapper is the grid item, while table layout computes the inner // table's border-box inline size, so resolve the wrapper with the same grid-area basis used later. let resolved = self.resolve_table_wrapper_inline_size(item, containing); - let used = self.used_mut(item); + let used = self.used(item); used.margin_left.set(resolved.margin_start); used.margin_right.set(resolved.margin_end); used.set_content_inline_size(resolved.size); @@ -3134,7 +3126,7 @@ impl<'pass> GridFormattingContext<'pass> { } } - let used = self.used_mut(item); + let used = self.used(item); if axis.is_column() { used.margin_left.set(resolved.margin_start); used.margin_right.set(resolved.margin_end); @@ -3393,13 +3385,13 @@ impl<'pass> GridFormattingContext<'pass> { let resolved = self.resolve_table_wrapper_inline_size(item, area.size.inline_size); table_wrapper_inline_basis = Some(self.non_cyclic_table_wrapper_inline_size(item, area.size.inline_size)); - let used = self.used_mut(item); + let used = self.used(item); used.margin_left.set(resolved.margin_start); used.margin_right.set(resolved.margin_end); used.set_content_inline_size(resolved.size); } { - let used = self.used_mut(item); + let used = self.used(item); used.has_definite_inline_size.set(true); used.has_definite_block_size.set(true); } @@ -3696,11 +3688,11 @@ impl<'pass> GridFormattingContext<'pass> { // (including gutters) in the appropriate axis, when the grid is sized under a max-content constraint (min-content constraint). if available.inline_size.is_intrinsic_sizing_constraint() { let size = self.track_sum(Axis::Column); - self.container_used_mut().set_content_inline_size(size); + self.container_used().set_content_inline_size(size); } if available.block_size.is_intrinsic_sizing_constraint() { let size = self.track_sum(Axis::Row); - self.container_used_mut().set_content_block_size(size); + self.container_used().set_content_block_size(size); } return; } diff --git a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs index e66a01b350c8d..0010b333bcd70 100644 --- a/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/inline_formatting_context.rs @@ -756,10 +756,6 @@ impl<'context, 'pass> InlineFormattingContext<'context, 'pass> { self.state.used_values(&self.callbacks, node) } - pub(crate) fn used_mut(&self, node: Node) -> &'pass UsedValues { - self.state.used_values(&self.callbacks, node) - } - pub(crate) fn create_used_values( &self, node: Node, diff --git a/Libraries/LibWeb/Rust/src/layout/line_builder.rs b/Libraries/LibWeb/Rust/src/layout/line_builder.rs index e8a0fb396b825..25c87f0674571 100644 --- a/Libraries/LibWeb/Rust/src/layout/line_builder.rs +++ b/Libraries/LibWeb/Rust/src/layout/line_builder.rs @@ -257,7 +257,7 @@ impl<'builder, 'context, 'pass> LineBuilder<'builder, 'context, 'pass> { ); self.line_mut(line_index).fragments[fragment_index].content_baselines = Some(content_baselines); self.max_block_size_on_current_line = self.max_block_size_on_current_line.max(margin_block_size); - let used = self.context().used_mut(node); + let used = self.context().used(node); used.has_containing_line_box_fragment.set(false); if self.context().facts(node).is_atomic_inline() { used.has_containing_line_box_fragment.set(true); diff --git a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs index 100cae9cfa99d..9b170c912e110 100644 --- a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs @@ -26,10 +26,6 @@ impl<'pass> SizingContext<'pass> { self.state.used_values(&self.callbacks, node) } - fn used_mut(&self, node: Node) -> &'pass UsedValues { - self.state.used_values(&self.callbacks, node) - } - fn parent(&self, node: Node) -> Node { self.callbacks.parent(node) } @@ -947,7 +943,7 @@ impl<'pass> SizingContext<'pass> { constraints, )); } - let used = self.used_mut(node); + let used = self.used(node); used.set_content_block_size(block_size); if !style.height().is_intrinsic_sizing_constraint() { used.has_definite_block_size.set(true); @@ -1007,7 +1003,7 @@ impl<'pass> SizingContext<'pass> { // according to the CSS specification. block_size = block_size.max(size); // NOTE: The block size of the root element when affected by this quirk is considered to be definite. - self.used_mut(node).has_definite_block_size.set(true); + self.used(node).has_definite_block_size.set(true); } if facts.document_in_quirks_mode() && facts.is_html_body_element() && style.height().is_auto() { @@ -1034,12 +1030,12 @@ impl<'pass> SizingContext<'pass> { block_size = block_size.max(size); } } - self.used_mut(node).set_content_block_size(block_size); + self.used(node).set_content_block_size(block_size); } pub(crate) fn resolve_box_model_metrics_against_inline_basis(&self, node: Node, containing_inline_size: CssPixels) { let style = self.style(node); - let used = self.used_mut(node); + let used = self.used(node); used.margin_left.set(style.margin_left().to_px(containing_inline_size)); used.border_left.set(style.border_left_width()); used.padding_left @@ -1073,14 +1069,14 @@ impl<'pass> SizingContext<'pass> { let facts = self.facts(node); if self.box_is_sized_as_replaced_element(node, available_space, constraints) { let inline_size = self.compute_inline_size_for_replaced_element(node, available_space, constraints); - self.used_mut(node).set_content_inline_size(inline_size); + self.used(node).set_content_inline_size(inline_size); let block_size = self.compute_block_size_for_replaced_element(node, available_space, constraints); - self.used_mut(node).set_content_block_size(block_size); + self.used(node).set_content_block_size(block_size); let block_size_is_automatic = style.height().is_auto() || self.should_treat_block_size_as_auto(node, available_space, constraints); if self.used(node).has_definite_inline_size() && facts.has_preferred_aspect_ratio() && block_size_is_automatic { - self.used_mut(node).has_definite_block_size.set(true); + self.used(node).has_definite_block_size.set(true); } return; } @@ -1131,7 +1127,7 @@ impl<'pass> SizingContext<'pass> { constraints, )); } - self.used_mut(node).set_content_inline_size(inline_size); + self.used(node).set_content_inline_size(inline_size); let inline_definite_space = AvailableSpace { inline_size: AvailableSize::definite(inline_size), @@ -1292,7 +1288,7 @@ impl<'pass> SizingContext<'pass> { if measurement.available_block_size != available_block_size { return None; } - let used = self.used_mut(node); + let used = self.used(node); if used.content_inline_size.get() != measurement.content_inline_size { return None; } @@ -1823,7 +1819,7 @@ impl<'pass> SizingContext<'pass> { if used_block_size <= natural { return; } - let used = self.used_mut(node); + let used = self.used(node); used.set_content_block_size(used_block_size); used.has_definite_block_size.set(true); } From 79fc1d0166afc1f7df8525a2ded37b0105145ba9 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:16:49 +0200 Subject: [PATCH 02/12] LibWeb: Select flex main/cross axis values with a helper Nearly every per-axis accessor the flex formatting context owns had to spell out a whole `if self.main_axis_is_horizontal()` block just to pick between an inline-axis and a block-axis value. Add `select_main()` and `select_cross()`, plus their `SizingAxis` variants, and route those accessors through them so each one states which pair of values it chooses between. --- .../src/layout/flex_formatting_context.rs | 209 +++++++----------- 1 file changed, 79 insertions(+), 130 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs index 5f756826364c5..85c3081483149 100644 --- a/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/flex_formatting_context.rs @@ -289,17 +289,30 @@ impl<'pass> FlexFormattingContext<'pass> { } fn main_axis_is_horizontal(&self) -> bool { - if self.is_row_layout() { - self.inline_axis_is_horizontal(self.flex_container) - } else { - !self.inline_axis_is_horizontal(self.flex_container) - } + self.inline_axis_is_horizontal(self.flex_container) == self.is_row_layout() } fn cross_axis_is_horizontal(&self) -> bool { !self.main_axis_is_horizontal() } + // Picks whichever of the two values describes the main (resp. cross) axis of this flex container. + fn select_main(&self, horizontal: T, vertical: T) -> T { + if self.main_axis_is_horizontal() { horizontal } else { vertical } + } + + fn select_cross(&self, horizontal: T, vertical: T) -> T { + self.select_main(vertical, horizontal) + } + + fn main_sizing_axis(&self) -> SizingAxis { + self.select_main(SizingAxis::Inline, SizingAxis::Block) + } + + fn cross_sizing_axis(&self) -> SizingAxis { + self.select_cross(SizingAxis::Inline, SizingAxis::Block) + } + fn main_axis_is_parallel_to_inline_axis(&self, node: Node) -> bool { self.main_axis_is_horizontal() == self.inline_axis_is_horizontal(node) } @@ -375,19 +388,11 @@ impl<'pass> FlexFormattingContext<'pass> { } fn has_definite_main_size_used(&self, used: &UsedValues) -> bool { - if self.main_axis_is_horizontal() { - used.has_definite_inline_size() - } else { - used.has_definite_block_size() - } + self.select_main(used.has_definite_inline_size(), used.has_definite_block_size()) } fn has_definite_cross_size_used(&self, used: &UsedValues) -> bool { - if self.cross_axis_is_horizontal() { - used.has_definite_inline_size() - } else { - used.has_definite_block_size() - } + self.select_cross(used.has_definite_inline_size(), used.has_definite_block_size()) } fn has_definite_main_size(&self, index: usize) -> bool { @@ -399,19 +404,11 @@ impl<'pass> FlexFormattingContext<'pass> { } fn inner_main_size_used(&self, used: &UsedValues) -> CssPixels { - if self.main_axis_is_horizontal() { - used.content_inline_size.get() - } else { - used.content_block_size.get() - } + self.select_main(used.content_inline_size.get(), used.content_block_size.get()) } fn inner_cross_size_used(&self, used: &UsedValues) -> CssPixels { - if self.cross_axis_is_horizontal() { - used.content_inline_size.get() - } else { - used.content_block_size.get() - } + self.select_cross(used.content_inline_size.get(), used.content_block_size.get()) } fn inner_main_size(&self, index: usize) -> CssPixels { @@ -423,105 +420,83 @@ impl<'pass> FlexFormattingContext<'pass> { } fn set_has_definite_main_size(&mut self, index: usize) { - if self.main_axis_is_horizontal() { - self.item_used(index).has_definite_inline_size.set(true); - } else { - self.item_used(index).has_definite_block_size.set(true); - } + let used = self.item_used(index); + self.select_main(&used.has_definite_inline_size, &used.has_definite_block_size) + .set(true); } fn set_has_definite_cross_size(&mut self, index: usize) { - if self.cross_axis_is_horizontal() { - self.item_used(index).has_definite_inline_size.set(true); - } else { - self.item_used(index).has_definite_block_size.set(true); - } + let used = self.item_used(index); + self.select_cross(&used.has_definite_inline_size, &used.has_definite_block_size) + .set(true); } fn set_main_size(&mut self, index: usize, size: CssPixels) { - if self.main_axis_is_horizontal() { - self.item_used(index).set_content_inline_size(size); - } else { - self.item_used(index).set_content_block_size(size); - } + self.set_main_size_used(self.item_used(index), size); } fn set_cross_size(&mut self, index: usize, size: CssPixels) { - if self.cross_axis_is_horizontal() { - self.item_used(index).set_content_inline_size(size); - } else { - self.item_used(index).set_content_block_size(size); - } + self.set_cross_size_used(self.item_used(index), size); } fn set_container_main_size(&mut self, size: CssPixels) { + self.set_main_size_used(self.container_used(), size); + } + + fn set_container_cross_size(&mut self, size: CssPixels) { + self.set_cross_size_used(self.container_used(), size); + } + + fn set_main_size_used(&self, used: &UsedValues, size: CssPixels) { if self.main_axis_is_horizontal() { - self.container_used().set_content_inline_size(size); + used.set_content_inline_size(size); } else { - self.container_used().set_content_block_size(size); + used.set_content_block_size(size); } } - fn set_container_cross_size(&mut self, size: CssPixels) { + fn set_cross_size_used(&self, used: &UsedValues, size: CssPixels) { if self.cross_axis_is_horizontal() { - self.container_used().set_content_inline_size(size); + used.set_content_inline_size(size); } else { - self.container_used().set_content_block_size(size); + used.set_content_block_size(size); } } fn computed_main_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); - if self.main_axis_is_horizontal() { - (style.width(), SizingProperty::Width) - } else { - (style.height(), SizingProperty::Height) - } + self.select_main((style.width(), SizingProperty::Width), (style.height(), SizingProperty::Height)) } fn computed_main_min_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); - if self.main_axis_is_horizontal() { - (style.min_width(), SizingProperty::MinWidth) - } else { - (style.min_height(), SizingProperty::MinHeight) - } + self.select_main((style.min_width(), SizingProperty::MinWidth), (style.min_height(), SizingProperty::MinHeight)) } fn computed_main_max_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); - if self.main_axis_is_horizontal() { - (style.max_width(), SizingProperty::MaxWidth) - } else { - (style.max_height(), SizingProperty::MaxHeight) - } + self.select_main((style.max_width(), SizingProperty::MaxWidth), (style.max_height(), SizingProperty::MaxHeight)) } fn computed_cross_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); - if self.cross_axis_is_horizontal() { - (style.width(), SizingProperty::Width) - } else { - (style.height(), SizingProperty::Height) - } + self.select_cross((style.width(), SizingProperty::Width), (style.height(), SizingProperty::Height)) } fn computed_cross_min_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); - if self.cross_axis_is_horizontal() { - (style.min_width(), SizingProperty::MinWidth) - } else { - (style.min_height(), SizingProperty::MinHeight) - } + self.select_cross( + (style.min_width(), SizingProperty::MinWidth), + (style.min_height(), SizingProperty::MinHeight), + ) } fn computed_cross_max_size(&self, node: Node) -> (&'pass ComputedSize, SizingProperty) { let style = self.style(node); - if self.cross_axis_is_horizontal() { - (style.max_width(), SizingProperty::MaxWidth) - } else { - (style.max_height(), SizingProperty::MaxHeight) - } + self.select_cross( + (style.max_width(), SizingProperty::MaxWidth), + (style.max_height(), SizingProperty::MaxHeight), + ) } fn calculate_inner_size( @@ -621,11 +596,7 @@ impl<'pass> FlexFormattingContext<'pass> { fn should_treat_main_size_as_auto(&self, node: Node) -> bool { self.should_treat_size_as_auto( node, - if self.main_axis_is_horizontal() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, + self.main_sizing_axis(), ) } @@ -968,35 +939,31 @@ impl<'pass> FlexFormattingContext<'pass> { } fn calculate_min_content_main_size(&self, index: usize) -> CssPixels { - if self.main_axis_is_horizontal() { - self.calculate_min_content_inline_size(index) - } else { - self.calculate_min_content_block_size(index, self.intrinsic_block_inline_size(index)) - } + self.select_main( + self.calculate_min_content_inline_size(index), + self.calculate_min_content_block_size(index, self.intrinsic_block_inline_size(index)), + ) } fn calculate_max_content_main_size(&self, index: usize) -> CssPixels { - if self.main_axis_is_horizontal() { - self.calculate_max_content_inline_size(index) - } else { - self.calculate_max_content_block_size(index, self.intrinsic_block_inline_size(index)) - } + self.select_main( + self.calculate_max_content_inline_size(index), + self.calculate_max_content_block_size(index, self.intrinsic_block_inline_size(index)), + ) } fn calculate_min_content_cross_size(&self, index: usize) -> CssPixels { - if self.cross_axis_is_horizontal() { - self.calculate_min_content_inline_size(index) - } else { - self.calculate_min_content_block_size(index, self.intrinsic_block_inline_size(index)) - } + self.select_cross( + self.calculate_min_content_inline_size(index), + self.calculate_min_content_block_size(index, self.intrinsic_block_inline_size(index)), + ) } fn calculate_max_content_cross_size(&self, index: usize) -> CssPixels { - if self.cross_axis_is_horizontal() { - self.calculate_max_content_inline_size(index) - } else { - self.calculate_max_content_block_size(index, self.intrinsic_block_inline_size(index)) - } + self.select_cross( + self.calculate_max_content_inline_size(index), + self.calculate_max_content_block_size(index, self.intrinsic_block_inline_size(index)), + ) } fn calculate_fit_content_main_size(&self, index: usize) -> CssPixels { @@ -1228,21 +1195,13 @@ impl<'pass> FlexFormattingContext<'pass> { fn main_gap(&self) -> CssPixels { let style = self.style(self.flex_container); - let gap = if self.is_row_layout() { - style.column_gap() - } else { - style.row_gap() - }; + let gap = if self.is_row_layout() { style.column_gap() } else { style.row_gap() }; gap.to_px(self.inner_main_size_used(self.container_used())) } fn cross_gap(&self) -> CssPixels { let style = self.style(self.flex_container); - let gap = if self.is_row_layout() { - style.row_gap() - } else { - style.column_gap() - }; + let gap = if self.is_row_layout() { style.row_gap() } else { style.column_gap() }; gap.to_px(self.inner_cross_size_used(self.container_used())) } @@ -1713,11 +1672,7 @@ impl<'pass> FlexFormattingContext<'pass> { } fn calculate_inner_container_cross_size(&self, property: SizingProperty) -> CssPixels { - let axis = if self.cross_axis_is_horizontal() { - SizingAxis::Inline - } else { - SizingAxis::Block - }; + let axis = self.cross_sizing_axis(); self.calculate_inner_size_with_constraints( self.flex_container, axis, @@ -1863,20 +1818,14 @@ impl<'pass> FlexFormattingContext<'pass> { fn set_main_axis_first_margin(&mut self, index: usize, margin: CssPixels) { self.flex_items[index].margins.main_before = margin; - if self.main_axis_is_horizontal() { - self.item_used(index).margin_left.set(margin); - } else { - self.item_used(index).margin_top.set(margin); - } + let used = self.item_used(index); + self.select_main(&used.margin_left, &used.margin_top).set(margin); } fn set_main_axis_second_margin(&mut self, index: usize, margin: CssPixels) { self.flex_items[index].margins.main_after = margin; - if self.main_axis_is_horizontal() { - self.item_used(index).margin_right.set(margin); - } else { - self.item_used(index).margin_bottom.set(margin); - } + let used = self.item_used(index); + self.select_main(&used.margin_right, &used.margin_bottom).set(margin); } // https://www.w3.org/TR/css-flexbox-1/#algo-main-align From f3868d4a54889aac62a02e9826f3ed4976cfff10 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:16:52 +0200 Subject: [PATCH 03/12] LibWeb: Solve the abspos block axis through a solution struct `solve_non_replaced_block_once()` kept the block axis unknowns in five separate locals, so its `solve_for` closure could not capture them and every call site had to re-pass all of them by hand. Collect them into a `BlockAxisSolution` that the closure takes by value, which also lets the caller thread one value around instead of destructuring a five-tuple three times. --- .../LibWeb/Rust/src/layout/abspos_engine.rs | 266 ++++++------------ 1 file changed, 91 insertions(+), 175 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs b/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs index 7723384857fdb..13c6eef6d4d80 100644 --- a/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs +++ b/Libraries/LibWeb/Rust/src/layout/abspos_engine.rs @@ -695,6 +695,24 @@ pub(crate) fn solve_abspos_axis_for( } } +// The block-axis unknowns of the absolute positioning equation. Each field is `None` while it is +// still unresolved, and holds its used value once solved for. +#[derive(Clone, Copy)] +struct BlockAxisSolution { + block_size: AutoPx, + top: AutoPx, + bottom: AutoPx, + margin_top: AutoPx, + margin_bottom: AutoPx, +} + +impl BlockAxisSolution { + fn zero_out_auto_margins(&mut self) { + self.margin_top = Some(auto_px_value(self.margin_top)); + self.margin_bottom = Some(auto_px_value(self.margin_bottom)); + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct ReplacedAxisSolution { pub(crate) start: CssPixels, @@ -1107,191 +1125,92 @@ impl AbsposEngine<'_> { constraints: ContainingBlockConstraints, static_position_rect: StaticPositionRect, pass: BlockSizePass, - mut block_size: AutoPx, - ) -> (AutoPx, AutoPx, AutoPx, AutoPx, AutoPx) { + block_size: AutoPx, + ) -> BlockAxisSolution { let style = self.style(node); let containing_block_inline_size = available_space.inline_size.to_px_or_zero(); let containing_block_block_size = available_space.block_size.to_px_or_zero(); - let mut margin_top = resolve_margin_or_auto(style.margin_top(), containing_block_inline_size); - let mut margin_bottom = resolve_margin_or_auto(style.margin_bottom(), containing_block_inline_size); - let mut top = resolve_or_auto(style.inset_top(), containing_block_block_size); - let mut bottom = resolve_or_auto(style.inset_bottom(), containing_block_block_size); let used = self.used(node); let padding_top = used.padding_top.get(); let padding_bottom = used.padding_bottom.get(); + let mut solution = BlockAxisSolution { + block_size, + top: resolve_or_auto(style.inset_top(), containing_block_block_size), + bottom: resolve_or_auto(style.inset_bottom(), containing_block_block_size), + margin_top: resolve_margin_or_auto(style.margin_top(), containing_block_inline_size), + margin_bottom: resolve_margin_or_auto(style.margin_bottom(), containing_block_inline_size), + }; - let solve_for = |length: AutoPx, - clamp_to_zero: bool, - top: AutoPx, - margin_top: AutoPx, - block_size: AutoPx, - margin_bottom: AutoPx, - bottom: AutoPx| { + // Solves the block axis equation for `target`, which must be one of the solution's own + // fields; every other field contributes its current value. + let solve_for = |target: AutoPx, clamp_to_zero: bool, solution: BlockAxisSolution| { solve_abspos_axis_for( containing_block_block_size, - length, + target, clamp_to_zero, - top, - margin_top, + solution.top, + solution.margin_top, style.border_top_width(), padding_top, - block_size, + solution.block_size, padding_bottom, style.border_bottom_width(), - margin_bottom, - bottom, + solution.margin_bottom, + solution.bottom, ) }; - if top.is_none() && block_size.is_none() && bottom.is_none() { - if margin_top.is_none() { - margin_top = Some(CssPixels::default()); - } - if margin_bottom.is_none() { - margin_bottom = Some(CssPixels::default()); - } + if solution.top.is_none() && solution.block_size.is_none() && solution.bottom.is_none() { + solution.zero_out_auto_margins(); let Some(automatic) = self.automatic_block_size(node, available_space, constraints, pass) else { - return (block_size, top, bottom, margin_top, margin_bottom); + return solution; }; - block_size = Some(automatic); - let constrained = self.apply_min_max_block_size_constraints(node, available_space, constraints, block_size); + solution.block_size = Some(automatic); + let constrained = + self.apply_min_max_block_size_constraints(node, available_space, constraints, solution.block_size); self.used(node).set_content_block_size(auto_px_value(constrained)); - top = Some(self.static_offset(node, static_position_rect).block_offset); - bottom = Some(solve_for( - bottom, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); - } else if top.is_some() && block_size.is_some() && bottom.is_some() { - if margin_top.is_none() && margin_bottom.is_none() { - let remainder = solve_for( - Some(auto_px_value(margin_top) + auto_px_value(margin_bottom)), - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - ); - margin_top = Some(remainder / 2); - margin_bottom = Some(remainder / 2); - } else if margin_top.is_none() || margin_bottom.is_none() { - if margin_top.is_none() { - margin_top = Some(solve_for( - margin_top, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); - } else { - margin_bottom = Some(solve_for( - margin_bottom, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); - } + solution.top = Some(self.static_offset(node, static_position_rect).block_offset); + solution.bottom = Some(solve_for(solution.bottom, false, solution)); + } else if solution.top.is_some() && solution.block_size.is_some() && solution.bottom.is_some() { + if solution.margin_top.is_none() && solution.margin_bottom.is_none() { + let total = Some(auto_px_value(solution.margin_top) + auto_px_value(solution.margin_bottom)); + let remainder = solve_for(total, false, solution); + solution.margin_top = Some(remainder / 2); + solution.margin_bottom = Some(remainder / 2); + } else if solution.margin_top.is_none() { + solution.margin_top = Some(solve_for(solution.margin_top, false, solution)); + } else if solution.margin_bottom.is_none() { + solution.margin_bottom = Some(solve_for(solution.margin_bottom, false, solution)); } else { - bottom = Some(solve_for( - bottom, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); + solution.bottom = Some(solve_for(solution.bottom, false, solution)); } } else { - if margin_top.is_none() { - margin_top = Some(CssPixels::default()); - } - if margin_bottom.is_none() { - margin_bottom = Some(CssPixels::default()); - } + solution.zero_out_auto_margins(); - if top.is_none() && block_size.is_none() && bottom.is_some() { + if solution.top.is_none() && solution.block_size.is_none() && solution.bottom.is_some() { let Some(automatic) = self.automatic_block_size(node, available_space, constraints, pass) else { - return (block_size, top, bottom, margin_top, margin_bottom); + return solution; }; - block_size = Some(automatic); - top = Some(solve_for( - top, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); - } else if top.is_none() && bottom.is_none() && block_size.is_some() { - top = Some(self.static_offset(node, static_position_rect).block_offset); - bottom = Some(solve_for( - bottom, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); - } else if block_size.is_none() && bottom.is_none() && top.is_some() { + solution.block_size = Some(automatic); + solution.top = Some(solve_for(solution.top, false, solution)); + } else if solution.top.is_none() && solution.bottom.is_none() && solution.block_size.is_some() { + solution.top = Some(self.static_offset(node, static_position_rect).block_offset); + solution.bottom = Some(solve_for(solution.bottom, false, solution)); + } else if solution.block_size.is_none() && solution.bottom.is_none() && solution.top.is_some() { let Some(automatic) = self.automatic_block_size(node, available_space, constraints, pass) else { - return (block_size, top, bottom, margin_top, margin_bottom); + return solution; }; - block_size = Some(automatic); - bottom = Some(solve_for( - bottom, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); - } else if top.is_none() && block_size.is_some() && bottom.is_some() { - top = Some(solve_for( - top, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); - } else if block_size.is_none() && top.is_some() && bottom.is_some() { - block_size = Some(solve_for( - block_size, - true, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); - } else if bottom.is_none() && top.is_some() && block_size.is_some() { - bottom = Some(solve_for( - bottom, - false, - top, - margin_top, - block_size, - margin_bottom, - bottom, - )); + solution.block_size = Some(automatic); + solution.bottom = Some(solve_for(solution.bottom, false, solution)); + } else if solution.top.is_none() && solution.block_size.is_some() && solution.bottom.is_some() { + solution.top = Some(solve_for(solution.top, false, solution)); + } else if solution.block_size.is_none() && solution.top.is_some() && solution.bottom.is_some() { + solution.block_size = Some(solve_for(solution.block_size, true, solution)); + } else if solution.bottom.is_none() && solution.top.is_some() && solution.block_size.is_some() { + solution.bottom = Some(solve_for(solution.bottom, false, solution)); } } - (block_size, top, bottom, margin_top, margin_bottom) + solution } fn compute_block_size_for_non_replaced( @@ -1321,18 +1240,18 @@ impl AbsposEngine<'_> { .calculate_inner_block_size(node, intrinsic_available_space, style.height(), constraints), ) }; - let (mut used_block_size, mut top, mut bottom, mut margin_top, mut margin_bottom) = + let mut solution = self.solve_non_replaced_block_once(node, available_space, constraints, static_position_rect, pass, initial); - if used_block_size.is_some() && !style.max_height().is_none() { + if solution.block_size.is_some() && !style.max_height().is_none() { let max_block_size = self.sizing().calculate_inner_block_size( node, intrinsic_available_space, style.max_height(), constraints, ); - if auto_px_value(used_block_size) > max_block_size { - (used_block_size, top, bottom, margin_top, margin_bottom) = self.solve_non_replaced_block_once( + if auto_px_value(solution.block_size) > max_block_size { + solution = self.solve_non_replaced_block_once( node, available_space, constraints, @@ -1342,15 +1261,15 @@ impl AbsposEngine<'_> { ); } } - if used_block_size.is_some() && !style.min_height().is_auto() { + if solution.block_size.is_some() && !style.min_height().is_auto() { let min_block_size = self.sizing().calculate_inner_block_size( node, intrinsic_available_space, style.min_height(), constraints, ); - if auto_px_value(used_block_size) < min_block_size { - (used_block_size, top, bottom, margin_top, margin_bottom) = self.solve_non_replaced_block_once( + if auto_px_value(solution.block_size) < min_block_size { + solution = self.solve_non_replaced_block_once( node, available_space, constraints, @@ -1360,28 +1279,25 @@ impl AbsposEngine<'_> { ); } } - if used_block_size.is_none() { - used_block_size = - self.apply_min_max_block_size_constraints(node, available_space, constraints, used_block_size); + if solution.block_size.is_none() { + solution.block_size = + self.apply_min_max_block_size_constraints(node, available_space, constraints, solution.block_size); } - let containing_block_inline_size = available_space.inline_size.to_px_or_zero(); - let containing_block_block_size = available_space.block_size.to_px_or_zero(); let used = self.used(node); - used.set_content_block_size(auto_px_value(used_block_size)); + used.set_content_block_size(auto_px_value(solution.block_size)); if style.height().is_auto() && pass == BlockSizePass::BeforeInsideLayout { return; } if !style.height().is_intrinsic_sizing_constraint() { used.has_definite_block_size.set(true); } - used.inset_top.set(auto_px_value(top)); - used.inset_bottom.set(auto_px_value(bottom)); - // The local values are already resolved against these bases. Keep the - // variables to document and pin the C++ basis distinction. - let _ = (containing_block_inline_size, containing_block_block_size); - used.margin_top.set(auto_px_value(margin_top)); - used.margin_bottom.set(auto_px_value(margin_bottom)); + used.inset_top.set(auto_px_value(solution.top)); + used.inset_bottom.set(auto_px_value(solution.bottom)); + // NOTE: solve_non_replaced_block_once() already resolved these against the bases the C++ layout code used: + // the insets against the containing block's block size, but the margins against its inline size. + used.margin_top.set(auto_px_value(solution.margin_top)); + used.margin_bottom.set(auto_px_value(solution.margin_bottom)); } fn compute_block_size_for_replaced( From 56bd0b81791b33f07ec9105355ca2fb360b44194 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:16:57 +0200 Subject: [PATCH 04/12] LibWeb: Group the text chunker's per-chunk state into a struct `next_chunk()` commits chunks from eight different places, and each one had to restate the same `try_commit_chunk()` call in full because the chunk start, font, text type and pending tab break were all loose locals. Bundle them into a `PendingChunk` and add a helper that commits everything up to the cursor, leaving the varying `can_break_after` as the only thing a call site has to say. --- .../LibWeb/Rust/src/layout/text_chunker.rs | 161 ++++++------------ 1 file changed, 55 insertions(+), 106 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/text_chunker.rs b/Libraries/LibWeb/Rust/src/layout/text_chunker.rs index c9481a3e195ce..fb23ed5516ec9 100644 --- a/Libraries/LibWeb/Rust/src/layout/text_chunker.rs +++ b/Libraries/LibWeb/Rust/src/layout/text_chunker.rs @@ -176,6 +176,16 @@ fn is_interword_space(code_point: u32) -> bool { code_point == 0x0020 || code_point == 0x00a0 } +// A chunk while it is still being accumulated: where it starts, plus the properties shared by +// every chunk that can be committed from it. +#[derive(Clone, Copy)] +struct PendingChunk<'text> { + start: usize, + font: FontRef<'text>, + text_type: u8, + broken_on_tab: bool, +} + #[derive(Clone, Copy)] struct ChunkBreakFlags { has_breaking_newline: bool, @@ -414,32 +424,22 @@ impl<'text> TextChunker<'text> { let mut code_point = self.current_code_point(); let mut can_break_at_current_position = self.is_at_line_break_opportunity(); - let start_of_chunk = self.current_index; - - let font = self.expected_font_for(code_point); - let text_type = self.current_text_type(); - - let mut broken_on_tab = false; + let mut pending = PendingChunk { + start: self.current_index, + font: self.expected_font_for(code_point), + text_type: self.current_text_type(), + broken_on_tab: false, + }; while self.current_index < self.text.len() { code_point = self.current_code_point(); if code_point == '\t' as u32 { - if let Some(chunk) = self.try_commit_chunk( - start_of_chunk, - self.current_index, - ChunkBreakFlags { - has_breaking_newline: false, - has_breaking_tab: broken_on_tab, - can_break_after: false, - }, - font, - text_type, - ) { + if let Some(chunk) = self.try_commit_chunk_at_cursor(pending, false) { return Some(chunk); } - broken_on_tab = true; + pending.broken_on_tab = true; // consume any consecutive tabs while self.current_index < self.text.len() && self.current_code_point() == '\t' as u32 { self.current_index = self.next_grapheme_boundary(); @@ -449,18 +449,8 @@ impl<'text> TextChunker<'text> { let expected_font = self.expected_font_for(code_point); - if font != expected_font - && let Some(chunk) = self.try_commit_chunk( - start_of_chunk, - self.current_index, - ChunkBreakFlags { - has_breaking_newline: false, - has_breaking_tab: broken_on_tab, - can_break_after: can_break_at_current_position, - }, - font, - text_type, - ) + if pending.font != expected_font + && let Some(chunk) = self.try_commit_chunk_at_cursor(pending, can_break_at_current_position) { return Some(chunk); } @@ -469,32 +459,22 @@ impl<'text> TextChunker<'text> { // Newline encountered, and we're supposed to preserve them. // If we have accumulated some code points in the current chunk, commit them now and continue with // the newline next time. - if let Some(chunk) = self.try_commit_chunk( - start_of_chunk, - self.current_index, - ChunkBreakFlags { - has_breaking_newline: false, - has_breaking_tab: broken_on_tab, - can_break_after: false, - }, - font, - text_type, - ) { + if let Some(chunk) = self.try_commit_chunk_at_cursor(pending, false) { return Some(chunk); } // Otherwise, commit the newline! self.current_index = self.next_grapheme_boundary(); let chunk = self.try_commit_chunk( - start_of_chunk, + pending.start, self.current_index, ChunkBreakFlags { has_breaking_newline: true, - has_breaking_tab: broken_on_tab, + has_breaking_tab: pending.broken_on_tab, can_break_after: false, }, - font, - text_type, + pending.font, + pending.text_type, ); return Some(chunk.expect("newline chunk must be non-empty")); } @@ -505,17 +485,7 @@ impl<'text> TextChunker<'text> { && self.current_index > 0 && self.is_collapsible(code_point_at(self.text, self.current_index - 1)) { - let chunk = self.try_commit_chunk( - start_of_chunk, - self.current_index, - ChunkBreakFlags { - has_breaking_newline: false, - has_breaking_tab: broken_on_tab, - can_break_after: false, - }, - font, - text_type, - ); + let chunk = self.try_commit_chunk_at_cursor(pending, false); while self.current_index < self.text.len() && self.is_collapsible(self.current_code_point()) { self.current_index = self.next_grapheme_boundary(); @@ -533,18 +503,8 @@ impl<'text> TextChunker<'text> { // read into a non-split. if self.should_wrap_lines && self.current_index < self.text.len() - && text_type != self.current_text_type() - && let Some(chunk) = self.try_commit_chunk( - start_of_chunk, - self.current_index, - ChunkBreakFlags { - has_breaking_newline: false, - has_breaking_tab: broken_on_tab, - can_break_after: can_break_at_current_position, - }, - font, - text_type, - ) + && pending.text_type != self.current_text_type() + && let Some(chunk) = self.try_commit_chunk_at_cursor(pending, can_break_at_current_position) { return Some(chunk); } @@ -554,17 +514,7 @@ impl<'text> TextChunker<'text> { // Whitespace encountered, and we're allowed to break on whitespace. // If we have accumulated some code points in the current chunk, commit them now and continue // with the whitespace next time. - if let Some(chunk) = self.try_commit_chunk( - start_of_chunk, - self.current_index, - ChunkBreakFlags { - has_breaking_newline: false, - has_breaking_tab: broken_on_tab, - can_break_after: false, - }, - font, - text_type, - ) { + if let Some(chunk) = self.try_commit_chunk_at_cursor(pending, false) { return Some(chunk); } @@ -572,34 +522,18 @@ impl<'text> TextChunker<'text> { self.current_index = self.next_grapheme_boundary(); can_break_at_current_position = self.is_at_line_break_opportunity(); let space_font = self.font_for_space(self.current_index, code_point); - if let Some(chunk) = self.try_commit_chunk( - start_of_chunk, - self.current_index, - ChunkBreakFlags { - has_breaking_newline: false, - has_breaking_tab: broken_on_tab, - can_break_after: false, - }, - space_font, - text_type, - ) { + let space = PendingChunk { + font: space_font, + ..pending + }; + if let Some(chunk) = self.try_commit_chunk_at_cursor(space, false) { return Some(chunk); } continue; } if can_break_at_current_position - && let Some(chunk) = self.try_commit_chunk( - start_of_chunk, - self.current_index, - ChunkBreakFlags { - has_breaking_newline: false, - has_breaking_tab: broken_on_tab, - can_break_after: true, - }, - font, - text_type, - ) + && let Some(chunk) = self.try_commit_chunk_at_cursor(pending, true) { return Some(chunk); } @@ -609,18 +543,18 @@ impl<'text> TextChunker<'text> { can_break_at_current_position = self.is_at_line_break_opportunity(); } - if start_of_chunk != self.text.len() { + if pending.start != self.text.len() { // Try to output whatever's left at the end of the text node. if let Some(chunk) = self.try_commit_chunk( - start_of_chunk, + pending.start, self.text.len(), ChunkBreakFlags { has_breaking_newline: false, - has_breaking_tab: broken_on_tab, + has_breaking_tab: pending.broken_on_tab, can_break_after: false, }, - font, - text_type, + pending.font, + pending.text_type, ) { return Some(chunk); } @@ -629,4 +563,19 @@ impl<'text> TextChunker<'text> { return None; } } + + // Commits everything accumulated since `pending.start` up to the cursor, if that range is non-empty. + fn try_commit_chunk_at_cursor(&mut self, pending: PendingChunk<'text>, can_break_after: bool) -> Option { + self.try_commit_chunk( + pending.start, + self.current_index, + ChunkBreakFlags { + has_breaking_newline: false, + has_breaking_tab: pending.broken_on_tab, + can_break_after, + }, + pending.font, + pending.text_type, + ) + } } From 7917c4c178ef2751d66c906915ba3a95388ef3d8 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:17:01 +0200 Subject: [PATCH 05/12] LibWeb: Share the SVG nested context's layout input Laying out a nested viewport and laying out a mask or clip resource both built the same `LayoutInput` literal. Move it into a `nested_layout_input()` helper. --- .../Rust/src/layout/svg_formatting_context.rs | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs index bb587042441d3..bc69f3cef2c8f 100644 --- a/Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs @@ -422,6 +422,19 @@ impl<'pass> SvgFormattingContext<'pass> { } } + // The input a nested viewport or resource context is laid out with: it inherits this context's + // available space and quirks-mode percentage basis, and participates as an item. + fn nested_layout_input(&self) -> LayoutInput { + LayoutInput::new( + self.available_space.unwrap(), + ContainingBlockConstraints { + quirks_mode_percentage_basis_block_size: self.quirks_mode_percentage_basis_block_size, + ..Default::default() + }, + ParticipationInParentFormattingContext::Item, + ) + } + fn first_child(&self, node: Node) -> Node { self.callbacks.node_data(node).first_child } @@ -816,19 +829,7 @@ impl<'pass> SvgFormattingContext<'pass> { parent_viewbox_transform, Some(parent_svg_transform), ); - nested_context.run( - run, - LayoutInput { - available_space: self.available_space.unwrap(), - containing_block_constraints: ContainingBlockConstraints { - quirks_mode_percentage_basis_block_size: self.quirks_mode_percentage_basis_block_size, - ..Default::default() - }, - content_box_position_in_bfc_root: None, - sizing: RootSizingDirectives::default(), - participation: ParticipationInParentFormattingContext::Item, - }, - ); + nested_context.run(run, self.nested_layout_input()); self.set_svg_viewport_size( viewport, FfiCssPixelSize { @@ -1071,19 +1072,7 @@ impl<'pass> SvgFormattingContext<'pass> { parent_viewbox_transform, Some(FfiAffineTransform::default()), ); - nested_context.run( - run, - LayoutInput { - available_space: self.available_space.unwrap(), - containing_block_constraints: ContainingBlockConstraints { - quirks_mode_percentage_basis_block_size: self.quirks_mode_percentage_basis_block_size, - ..Default::default() - }, - content_box_position_in_bfc_root: None, - sizing: RootSizingDirectives::default(), - participation: ParticipationInParentFormattingContext::Item, - }, - ); + nested_context.run(run, self.nested_layout_input()); let used = used_pointer; let mapped_rect = parent_viewbox_transform.map_rect(FfiFloatRect { From 88a2ac65df420a617b54f0ef3658dc2b6cc85cb6 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:17:04 +0200 Subject: [PATCH 06/12] LibWeb: Share the auto margin reset in inline size resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both places where an auto margin stops being honoured — an over-wide box, and a box with an automatic inline size — zeroed `margin_left` and `margin_right` and cleared their auto flags with the same code. Give that step a name and call it twice. --- .../src/layout/block_formatting_context.rs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs index c717ede9c84c6..7c1f2ecdb0305 100644 --- a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs @@ -537,6 +537,14 @@ impl<'pass> BlockFormattingContext<'pass> { } } + // An auto margin that is not honoured resolves to zero and stops taking part in the remaining steps. + fn zero_out_auto_margin(margin: &mut CssPixels, is_auto: &mut bool) { + if *is_auto { + *margin = CssPixels::default(); + *is_auto = false; + } + } + let remaining_inline_size = remaining_available_space.inline_size.to_px_or_zero(); let computed_margin_left = style.margin_left(); let computed_margin_right = style.margin_right(); @@ -566,14 +574,8 @@ impl<'pass> BlockFormattingContext<'pass> { // width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the // following rules, treated as zero. if inline_size.is_some() && total > remaining_inline_size { - if *margin_left_is_auto { - *margin_left = CssPixels::default(); - *margin_left_is_auto = false; - } - if *margin_right_is_auto { - *margin_right = CssPixels::default(); - *margin_right_is_auto = false; - } + zero_out_auto_margin(margin_left, margin_left_is_auto); + zero_out_auto_margin(margin_right, margin_right_is_auto); total = border_left_width + border_right_width + *margin_left @@ -589,14 +591,8 @@ impl<'pass> BlockFormattingContext<'pass> { underflow = CssPixels::default(); } if inline_size.is_none() { - if *margin_left_is_auto { - *margin_left = CssPixels::default(); - *margin_left_is_auto = false; - } - if *margin_right_is_auto { - *margin_right = CssPixels::default(); - *margin_right_is_auto = false; - } + zero_out_auto_margin(margin_left, margin_left_is_auto); + zero_out_auto_margin(margin_right, margin_right_is_auto); if matches!(available_space.inline_size, AvailableSize::Definite(_)) { inline_size = Some(underflow.max(CssPixels::default())); } else if available_space.inline_size == AvailableSize::MinContent { From 2f06389fe163d28ad783a58b0b163bb6ce8af669 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:17:08 +0200 Subject: [PATCH 07/12] LibWeb: Build a float's placement in one place `place_float()` had two exits from its band search, and each one built the same `FloatPlacement` with the same nested left/right offset expression. Turn the search into a single `continue` and fall through to one construction site. --- .../src/layout/block_formatting_context.rs | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs index 7c1f2ecdb0305..af05f0db55992 100644 --- a/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/block_formatting_context.rs @@ -938,39 +938,29 @@ impl<'pass> BlockFormattingContext<'pass> { available_space.inline_size.to_px_or_zero() - intrusions.left - intrusions.right; let has_floats_present = band.left_intrusion > CssPixels::default() || band.right_intrusion > CssPixels::default(); - if matches!( + let fits = matches!( available_space.inline_size, AvailableSize::MaxContent | AvailableSize::Indefinite - ) - || margin_box_inline_size <= available_inline_size - || !has_floats_present + ) || margin_box_inline_size <= available_inline_size + || !has_floats_present; + if !fits + && let Some(next) = self.next_float_band_block_start_after(candidate_block_start) { - return FloatPlacement { - block_start: candidate_block_start, - offset_from_edge: if side == FloatSide::Left { - intrusions.left + used.margin_left.get() + used.border_box_left(false) - } else { - intrusions.right - + used.content_inline_size.get() - + used.margin_right.get() - + used.border_box_right(false) - }, - }; + candidate_block_start = next; + continue; } - let Some(next) = self.next_float_band_block_start_after(candidate_block_start) else { - return FloatPlacement { - block_start: candidate_block_start, - offset_from_edge: if side == FloatSide::Left { - intrusions.left + used.margin_left.get() + used.border_box_left(false) - } else { - intrusions.right - + used.content_inline_size.get() - + used.margin_right.get() - + used.border_box_right(false) - }, - }; + let offset_from_edge = if side == FloatSide::Left { + intrusions.left + used.margin_left.get() + used.border_box_left(false) + } else { + intrusions.right + + used.content_inline_size.get() + + used.margin_right.get() + + used.border_box_right(false) + }; + return FloatPlacement { + block_start: candidate_block_start, + offset_from_edge, }; - candidate_block_start = next; } } From efcc5670c8a67bbdb78190795b71f63f5407f4bd Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:17:11 +0200 Subject: [PATCH 08/12] LibWeb: Share the replaced element's computed size lookup Computing a replaced element's inline size and computing its block size opened the same way, substituting `auto` for whichever of `width` and `height` has to be treated as automatic. Name that step and call it from both. --- .../LibWeb/Rust/src/layout/sizing_context.rs | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs index 9b170c912e110..676030c576c37 100644 --- a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs @@ -475,25 +475,39 @@ impl<'pass> SizingContext<'pass> { (input_inline_size, input_block_size) } - pub(crate) fn compute_inline_size_for_replaced_element( + // The computed inline and block sizes a replaced element is sized from, with sizes that must be + // treated as automatic replaced by `auto`. + fn computed_sizes_for_replaced_element( &self, node: Node, available_space: AvailableSpace, constraints: ContainingBlockConstraints, - ) -> CssPixels { - // 10.3.4 Block-level, replaced elements in normal flow... - // 10.3.2 Inline, replaced elements + ) -> (&'pass ComputedSize, &'pass ComputedSize) { let style = self.style(node); - let computed_inline = if self.should_treat_inline_size_as_auto(node, available_space) { + let inline = if self.should_treat_inline_size_as_auto(node, available_space) { auto_computed_size() } else { style.width() }; - let computed_block = if self.should_treat_block_size_as_auto(node, available_space, constraints) { + let block = if self.should_treat_block_size_as_auto(node, available_space, constraints) { auto_computed_size() } else { style.height() }; + (inline, block) + } + + pub(crate) fn compute_inline_size_for_replaced_element( + &self, + node: Node, + available_space: AvailableSpace, + constraints: ContainingBlockConstraints, + ) -> CssPixels { + // 10.3.4 Block-level, replaced elements in normal flow... + // 10.3.2 Inline, replaced elements + let style = self.style(node); + let (computed_inline, computed_block) = + self.computed_sizes_for_replaced_element(node, available_space, constraints); // 1. The tentative used width is calculated (without 'min-width' and 'max-width') let mut used = self.tentative_inline_size_for_replaced_element(node, computed_inline, available_space, constraints); @@ -546,16 +560,8 @@ impl<'pass> SizingContext<'pass> { // 10.6.6 Floating replaced elements // 10.6.10 'inline-block' replaced elements in normal flow let style = self.style(node); - let computed_inline = if self.should_treat_inline_size_as_auto(node, available_space) { - auto_computed_size() - } else { - style.width() - }; - let computed_block = if self.should_treat_block_size_as_auto(node, available_space, constraints) { - auto_computed_size() - } else { - style.height() - }; + let (computed_inline, computed_block) = + self.computed_sizes_for_replaced_element(node, available_space, constraints); // 1. The tentative used height is calculated (without 'min-height' and 'max-height') let mut used = self.tentative_block_size_for_replaced_element(node, computed_block, available_space, constraints); From 60d828e8c2bac2f38e01978e8cd18c4b3aa0756c Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:17:14 +0200 Subject: [PATCH 09/12] LibWeb: Share the block size min/max clamp in SizingContext Resolving a used block size clamped it against `min-height` and `max-height` the same way whether or not the size is treated as automatic. Move the clamp into one helper. --- .../LibWeb/Rust/src/layout/sizing_context.rs | 59 ++++++++----------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs index 676030c576c37..d6dbea8d652e4 100644 --- a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs @@ -920,35 +920,39 @@ impl<'pass> SizingContext<'pass> { resolution_space } - pub(crate) fn resolve_used_block_size_if_not_treated_as_auto( + fn clamp_block_size_to_min_max( &self, node: Node, + mut block_size: CssPixels, available_space: AvailableSpace, constraints: ContainingBlockConstraints, - ) { - if self.should_treat_block_size_as_auto(node, available_space, constraints) { - return; - } + ) -> CssPixels { let style = self.style(node); - let mut block_size = self.calculate_inner_block_size(node, available_space, style.height(), constraints); if !self.should_treat_max_block_size_as_none(node, available_space.block_size, constraints) && !style.max_height().is_auto() { - block_size = block_size.min(self.calculate_inner_block_size( - node, - available_space, - style.max_height(), - constraints, - )); + let max = self.calculate_inner_block_size(node, available_space, style.max_height(), constraints); + block_size = block_size.min(max); } if !style.min_height().is_auto() { - block_size = block_size.max(self.calculate_inner_block_size( - node, - available_space, - style.min_height(), - constraints, - )); + let min = self.calculate_inner_block_size(node, available_space, style.min_height(), constraints); + block_size = block_size.max(min); + } + block_size + } + + pub(crate) fn resolve_used_block_size_if_not_treated_as_auto( + &self, + node: Node, + available_space: AvailableSpace, + constraints: ContainingBlockConstraints, + ) { + if self.should_treat_block_size_as_auto(node, available_space, constraints) { + return; } + let style = self.style(node); + let block_size = self.calculate_inner_block_size(node, available_space, style.height(), constraints); + let block_size = self.clamp_block_size_to_min_max(node, block_size, available_space, constraints); let used = self.used(node); used.set_content_block_size(block_size); if !style.height().is_intrinsic_sizing_constraint() { @@ -974,24 +978,7 @@ impl<'pass> SizingContext<'pass> { } else { child_automatic_block_size.unwrap_or_else(automatic_block_size_fallback) }; - if !self.should_treat_max_block_size_as_none(node, available_space.block_size, constraints) - && !style.max_height().is_auto() - { - block_size = block_size.min(self.calculate_inner_block_size( - node, - available_space, - style.max_height(), - constraints, - )); - } - if !style.min_height().is_auto() { - block_size = block_size.max(self.calculate_inner_block_size( - node, - available_space, - style.min_height(), - constraints, - )); - } + block_size = self.clamp_block_size_to_min_max(node, block_size, available_space, constraints); if facts.document_in_quirks_mode() && facts.is_html_html_element() && style.height().is_auto() { // 3.6. The html element fills the viewport quirk From 218a9bd1ecfe3cd6f575320c36f3d7846ebdcd78 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:17:18 +0200 Subject: [PATCH 10/12] LibWeb: Share the intrinsic size measurement in SizingContext The four min/max-content size calculators all ended the same way: consult the measurement cache, set up a `MeasurementState` under the matching size constraint, lay the box out and store the result. Move those tails into one helper per axis, keyed on the cache kind, and drop the hand-written `LayoutInput` literals in favour of `LayoutInput::new()` now that both agree on every field. --- .../LibWeb/Rust/src/layout/sizing_context.rs | 174 +++++++----------- 1 file changed, 64 insertions(+), 110 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs index d6dbea8d652e4..c6ba5ba3a68f3 100644 --- a/Libraries/LibWeb/Rust/src/layout/sizing_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/sizing_context.rs @@ -1387,46 +1387,7 @@ impl<'pass> SizingContext<'pass> { if !self.has_children(node) { return CssPixels::default(); } - let key = cache_key(None, constraints); - if let Some(cached) = - self.intrinsic_inline_measurement_cache_get(node, IntrinsicSizeCacheKind::MinContentInline, key) - { - return cached.automatic_content_inline_size; - } - - let measurement = MeasurementState::create(self.callbacks, node, constraints); - let root = measurement.root_used(); - root.inline_size_constraint.set(SizeConstraint::MinContent); - root.has_definite_inline_size.set(false); - let block_size = if root.has_definite_block_size() { - AvailableSize::definite(root.content_block_size.get()) - } else { - AvailableSize::Indefinite - }; - let mut result = measurement.run( - node, - LayoutInput { - available_space: AvailableSpace { - inline_size: AvailableSize::MinContent, - block_size, - }, - containing_block_constraints: constraints, - content_box_position_in_bfc_root: None, - sizing: RootSizingDirectives::default(), - participation: ParticipationInParentFormattingContext::Root, - }, - ); - result.automatic_content_inline_size = clamp_to_max_dimension_value(result.automatic_content_inline_size); - let value = result.automatic_content_inline_size; - self.cache_intrinsic_inline_measurement( - node, - IntrinsicSizeCacheKind::MinContentInline, - key, - &measurement, - result, - block_size, - ); - value + self.measure_intrinsic_inline_size(node, constraints, IntrinsicSizeCacheKind::MinContentInline) } pub(crate) fn calculate_max_content_inline_size( @@ -1588,16 +1549,29 @@ impl<'pass> SizingContext<'pass> { if !self.has_children(node) { return CssPixels::default(); } + self.measure_intrinsic_inline_size(node, constraints, IntrinsicSizeCacheKind::MaxContentInline) + } + + // Lays `node` out under an intrinsic inline-size constraint, reusing and populating the measurement cache. + fn measure_intrinsic_inline_size( + &self, + node: Node, + constraints: ContainingBlockConstraints, + kind: IntrinsicSizeCacheKind, + ) -> CssPixels { + let (size_constraint, available_inline_size) = match kind { + IntrinsicSizeCacheKind::MinContentInline => (SizeConstraint::MinContent, AvailableSize::MinContent), + IntrinsicSizeCacheKind::MaxContentInline => (SizeConstraint::MaxContent, AvailableSize::MaxContent), + _ => unreachable!("inline measurement cache kind must use the inline axis"), + }; let key = cache_key(None, constraints); - if let Some(cached) = - self.intrinsic_inline_measurement_cache_get(node, IntrinsicSizeCacheKind::MaxContentInline, key) - { + if let Some(cached) = self.intrinsic_inline_measurement_cache_get(node, kind, key) { return cached.automatic_content_inline_size; } let measurement = MeasurementState::create(self.callbacks, node, constraints); let root = measurement.root_used(); - root.inline_size_constraint.set(SizeConstraint::MaxContent); + root.inline_size_constraint.set(size_constraint); root.has_definite_inline_size.set(false); let block_size = if root.has_definite_block_size() { AvailableSize::definite(root.content_block_size.get()) @@ -1606,27 +1580,57 @@ impl<'pass> SizingContext<'pass> { }; let mut result = measurement.run( node, - LayoutInput { - available_space: AvailableSpace { - inline_size: AvailableSize::MaxContent, + LayoutInput::new( + AvailableSpace { + inline_size: available_inline_size, block_size, }, - containing_block_constraints: constraints, - content_box_position_in_bfc_root: None, - sizing: RootSizingDirectives::default(), - participation: ParticipationInParentFormattingContext::Root, - }, + constraints, + ParticipationInParentFormattingContext::Root, + ), ); result.automatic_content_inline_size = clamp_to_max_dimension_value(result.automatic_content_inline_size); let value = result.automatic_content_inline_size; - self.cache_intrinsic_inline_measurement( + self.cache_intrinsic_inline_measurement(node, kind, key, &measurement, result, block_size); + value + } + + // Lays `node` out at `inline_size` under an intrinsic block-size constraint, reusing and populating the cache. + fn measure_intrinsic_block_size( + &self, + node: Node, + inline_size: CssPixels, + constraints: ContainingBlockConstraints, + kind: IntrinsicSizeCacheKind, + ) -> CssPixels { + let (size_constraint, available_block_size) = match kind { + IntrinsicSizeCacheKind::MinContentBlock => (SizeConstraint::MinContent, AvailableSize::MinContent), + IntrinsicSizeCacheKind::MaxContentBlock => (SizeConstraint::MaxContent, AvailableSize::MaxContent), + _ => unreachable!("block size cache kind must use the block axis"), + }; + let key = cache_key(Some(inline_size), constraints); + if let Some(cached) = self.intrinsic_block_cache_get(node, kind, key) { + return cached; + } + + let measurement = MeasurementState::create(self.callbacks, node, constraints); + let root = measurement.root_used(); + root.block_size_constraint.set(size_constraint); + root.has_definite_block_size.set(false); + root.set_content_inline_size(inline_size); + let result = measurement.run( node, - IntrinsicSizeCacheKind::MaxContentInline, - key, - &measurement, - result, - block_size, + LayoutInput::new( + AvailableSpace { + inline_size: AvailableSize::definite(inline_size), + block_size: available_block_size, + }, + constraints, + ParticipationInParentFormattingContext::Root, + ), ); + let value = clamp_to_max_dimension_value(result.automatic_content_block_size); + self.intrinsic_block_cache_put(node, kind, key, value); value } @@ -1650,32 +1654,7 @@ impl<'pass> SizingContext<'pass> { if !self.has_children(node) { return CssPixels::default(); } - let key = cache_key(Some(inline_size), constraints); - if let Some(cached) = self.intrinsic_block_cache_get(node, IntrinsicSizeCacheKind::MinContentBlock, key) { - return cached; - } - - let measurement = MeasurementState::create(self.callbacks, node, constraints); - let root = measurement.root_used(); - root.block_size_constraint.set(SizeConstraint::MinContent); - root.has_definite_block_size.set(false); - root.set_content_inline_size(inline_size); - let result = measurement.run( - node, - LayoutInput { - available_space: AvailableSpace { - inline_size: AvailableSize::definite(inline_size), - block_size: AvailableSize::MinContent, - }, - containing_block_constraints: constraints, - content_box_position_in_bfc_root: None, - sizing: RootSizingDirectives::default(), - participation: ParticipationInParentFormattingContext::Root, - }, - ); - let value = clamp_to_max_dimension_value(result.automatic_content_block_size); - self.intrinsic_block_cache_put(node, IntrinsicSizeCacheKind::MinContentBlock, key, value); - value + self.measure_intrinsic_block_size(node, inline_size, constraints, IntrinsicSizeCacheKind::MinContentBlock) } pub(crate) fn calculate_max_content_block_size( @@ -1703,32 +1682,7 @@ impl<'pass> SizingContext<'pass> { if !self.has_children(node) { return CssPixels::default(); } - let key = cache_key(Some(inline_size), constraints); - if let Some(cached) = self.intrinsic_block_cache_get(node, IntrinsicSizeCacheKind::MaxContentBlock, key) { - return cached; - } - - let measurement = MeasurementState::create(self.callbacks, node, constraints); - let root = measurement.root_used(); - root.block_size_constraint.set(SizeConstraint::MaxContent); - root.has_definite_block_size.set(false); - root.set_content_inline_size(inline_size); - let result = measurement.run( - node, - LayoutInput { - available_space: AvailableSpace { - inline_size: AvailableSize::definite(inline_size), - block_size: AvailableSize::MaxContent, - }, - containing_block_constraints: constraints, - content_box_position_in_bfc_root: None, - sizing: RootSizingDirectives::default(), - participation: ParticipationInParentFormattingContext::Root, - }, - ); - let value = clamp_to_max_dimension_value(result.automatic_content_block_size); - self.intrinsic_block_cache_put(node, IntrinsicSizeCacheKind::MaxContentBlock, key, value); - value + self.measure_intrinsic_block_size(node, inline_size, constraints, IntrinsicSizeCacheKind::MaxContentBlock) } pub(crate) fn measure_automatic_content_block_size( From a22110a9e47d1b3bc2c2a819a524e48e3202aba2 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:17:21 +0200 Subject: [PATCH 11/12] LibWeb: Name a subgrid's inherited area lines in one place Adopting a parent grid area's names into a subgrid pushed the same `LineName` four times over, once per axis and edge, differing only in which raw name and which end it records. Loop over the two edges in a helper instead. --- .../src/layout/grid_formatting_context.rs | 59 ++++++------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs index 415c93d448937..fccd827a6cc7c 100644 --- a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs @@ -1340,6 +1340,23 @@ impl<'pass> GridFormattingContext<'pass> { column_end: Option, } + // Names the two lines a parent grid area contributes to a subgrid, in subgrid-local coordinates. + fn name_subgrid_area_lines(lines: &mut [Vec], area: Area, start: i32, end: i32) { + for (index, raw, area_is_start) in [(start, area.start_name_raw, true), (end, area.end_name_raw, false)] { + let Some(line) = lines.get_mut(index as usize) else { + continue; + }; + line.push(LineName { + name_index: crate::layout::GRID_NO_INDEX, + raw, + implicit: true, + adopted_from_parent: false, + area_name_raw: area.name_raw, + area_is_start, + }); + } + } + let Some(parent) = self.parent_grid() else { return; }; @@ -1411,50 +1428,12 @@ impl<'pass> GridFormattingContext<'pass> { if column_is_subgrid { let start = column_start.max(subgrid_column_start) - subgrid_column_start; let end = column_end.min(subgrid_column_end) - subgrid_column_start; - if let Some(line) = columns.get_mut(start as usize) { - line.push(LineName { - name_index: crate::layout::GRID_NO_INDEX, - raw: area.start_name_raw, - implicit: true, - adopted_from_parent: false, - area_name_raw: area.name_raw, - area_is_start: true, - }); - } - if let Some(line) = columns.get_mut(end as usize) { - line.push(LineName { - name_index: crate::layout::GRID_NO_INDEX, - raw: area.end_name_raw, - implicit: true, - adopted_from_parent: false, - area_name_raw: area.name_raw, - area_is_start: false, - }); - } + name_subgrid_area_lines(columns, area, start, end); } if row_is_subgrid { let start = row_start.max(subgrid_row_start) - subgrid_row_start; let end = row_end.min(subgrid_row_end) - subgrid_row_start; - if let Some(line) = rows.get_mut(start as usize) { - line.push(LineName { - name_index: crate::layout::GRID_NO_INDEX, - raw: area.start_name_raw, - implicit: true, - adopted_from_parent: false, - area_name_raw: area.name_raw, - area_is_start: true, - }); - } - if let Some(line) = rows.get_mut(end as usize) { - line.push(LineName { - name_index: crate::layout::GRID_NO_INDEX, - raw: area.end_name_raw, - implicit: true, - adopted_from_parent: false, - area_name_raw: area.name_raw, - area_is_start: false, - }); - } + name_subgrid_area_lines(rows, area, start, end); } } } From cd3843a881e786877e3d08611326a864502b60a7 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 9 Aug 2026 17:17:25 +0200 Subject: [PATCH 12/12] LibWeb: Select grid per-axis values with a helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Almost everything the grid formatting context reads is stored once per axis, so it spelled out an `if axis.is_column()` block to pick between the two all over the file. Add `Axis::select()` along with `sizing_axis()` and `opposite()`, and give the repeatedly selected pairs — track lines, explicit line counts, content alignment and container padding — accessors of their own. `outer_edges()` now sums the two margin box edge helpers rather than restating them. --- .../src/layout/grid_formatting_context.rs | 436 +++++------------- 1 file changed, 118 insertions(+), 318 deletions(-) diff --git a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs index fccd827a6cc7c..043457e34b9bc 100644 --- a/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs +++ b/Libraries/LibWeb/Rust/src/layout/grid_formatting_context.rs @@ -989,6 +989,19 @@ impl Axis { fn is_column(self) -> bool { self == Self::Column } + + // Picks whichever of the two per-axis values belongs to this axis. + fn select(self, column: T, row: T) -> T { + if self.is_column() { column } else { row } + } + + fn sizing_axis(self) -> SizingAxis { + self.select(SizingAxis::Inline, SizingAxis::Block) + } + + fn opposite(self) -> Self { + self.select(Self::Row, Self::Column) + } } #[derive(Clone, Copy)] @@ -1077,11 +1090,7 @@ impl GridItemPlacement { } fn span(self, axis: Axis) -> usize { - if axis.is_column() { - self.column_span - } else { - self.row_span - } + axis.select(self.column_span, self.row_span) } } @@ -1218,20 +1227,12 @@ impl<'pass> GridFormattingContext<'pass> { // a subgrid. // FIXME: Also reject subgrid here when the grid container is forced to // establish an independent formatting context. - let list = if axis.is_column() { - grid_style.template_columns - } else { - grid_style.template_rows - }; + let list = axis.select(grid_style.template_columns, grid_style.template_rows); list.is_subgrid && self.parent_grid_placement().is_some() } fn container_is_subgridded(&self, axis: Axis) -> bool { - if axis.is_column() { - self.subgridded_columns - } else { - self.subgridded_rows - } + axis.select(self.subgridded_columns, self.subgridded_rows) } fn cache_subgrid_axes(&mut self, grid_style: &GridValues) { @@ -1241,20 +1242,12 @@ impl<'pass> GridFormattingContext<'pass> { fn axis_available(&self, axis: Axis) -> AvailableSize { let space = self.available_space.unwrap(); - if axis.is_column() { - space.inline_size - } else { - space.block_size - } + axis.select(space.inline_size, space.block_size) } fn axis_gap_value(&self, axis: Axis) -> &'pass ComputedGap { let style = self.style(self.grid_container); - if axis.is_column() { - style.column_gap() - } else { - style.row_gap() - } + axis.select(style.column_gap(), style.row_gap()) } fn parent_gap_size_for_subgrid(&self, axis: Axis) -> CssPixels { @@ -1267,11 +1260,7 @@ impl<'pass> GridFormattingContext<'pass> { if item.span(axis) <= 1 { return CssPixels::default(); } - let gaps = if axis.is_column() { - &parent.column_gaps - } else { - &parent.row_gaps - }; + let gaps = axis.select(&parent.column_gaps, &parent.row_gaps); if gaps.is_empty() { return CssPixels::default(); } @@ -1500,11 +1489,7 @@ impl<'pass> GridFormattingContext<'pass> { } fn expand_axis(&self, axis: Axis, grid_style: &'static GridValues) -> ExpandedTrackList { - let list = if axis.is_column() { - grid_style.template_columns - } else { - grid_style.template_rows - }; + let list = axis.select(grid_style.template_columns, grid_style.template_rows); let source = TrackListSource::from_grid_style(grid_style); if self.is_subgridded(axis, grid_style) { let parent_item = self.parent_grid_placement().unwrap(); @@ -1512,11 +1497,7 @@ impl<'pass> GridFormattingContext<'pass> { let inherited = self .parent_grid() .map(|parent| { - let lines = if axis.is_column() { - &parent.column_lines - } else { - &parent.row_lines - }; + let lines = axis.select(&parent.column_lines, &parent.row_lines); let start = parent_item.position(axis).max(0) as usize; lines .iter() @@ -1565,16 +1546,8 @@ impl<'pass> GridFormattingContext<'pass> { ) -> ResolvedAxisPlacement { let start_is_auto = start.kind != crate::layout::ComputedGridPlacementKind::Line as u8; let end_is_auto = end.kind != crate::layout::ComputedGridPlacementKind::Line as u8; - let lines = if axis.is_column() { - &self.column_lines - } else { - &self.row_lines - }; - let explicit_lines = if axis.is_column() { - self.explicit_column_line_count - } else { - self.explicit_row_line_count - }; + let lines = self.axis_lines(axis); + let explicit_lines = self.explicit_line_count(axis); let explicit_tracks = lines.len().saturating_sub(1); if start_is_auto && end_is_auto { return ResolvedAxisPlacement { @@ -1737,11 +1710,7 @@ impl<'pass> GridFormattingContext<'pass> { } fn expanded_auto_tracks(&self, grid_style: &'static GridValues, axis: Axis) -> Vec { - let list = if axis.is_column() { - grid_style.auto_columns - } else { - grid_style.auto_rows - }; + let list = axis.select(grid_style.auto_columns, grid_style.auto_rows); expand_standalone(TrackListSource::from_grid_style(grid_style), list, |_index, _entry| 1).tracks } @@ -1762,11 +1731,7 @@ impl<'pass> GridFormattingContext<'pass> { return vec![Track::auto()]; }; let parent_item = self.parent_grid_placement().unwrap(); - let parent_tracks = if axis.is_column() { - &parent.columns - } else { - &parent.rows - }; + let parent_tracks = axis.select(&parent.columns, &parent.rows); let mut tracks = Vec::new(); for offset in 0..parent_item.span(axis) { let index = parent_item.position(axis) + offset as i32; @@ -1836,11 +1801,7 @@ impl<'pass> GridFormattingContext<'pass> { }) }; let items = &self.items; - let tracks = if axis.is_column() { - &mut self.columns - } else { - &mut self.rows - }; + let tracks = axis.select(&mut self.columns, &mut self.rows); for (index, track) in tracks.iter_mut().enumerate() { if track.is_auto_fit && !occupied(index, items) { // A collapsed grid track is treated as having a fixed track sizing function of 0px, and the gutters on @@ -1901,15 +1862,37 @@ impl<'pass> GridFormattingContext<'pass> { } fn axis_tracks(&self, axis: Axis) -> &[Track] { - if axis.is_column() { &self.columns } else { &self.rows } + axis.select(&self.columns, &self.rows) } fn axis_gaps(&self, axis: Axis) -> &[Track] { - if axis.is_column() { - &self.column_gaps - } else { - &self.row_gaps - } + axis.select(&self.column_gaps, &self.row_gaps) + } + + fn axis_lines(&self, axis: Axis) -> &[Vec] { + axis.select(&self.column_lines, &self.row_lines) + } + + fn explicit_line_count(&self, axis: Axis) -> usize { + axis.select(self.explicit_column_line_count, self.explicit_row_line_count) + } + + fn content_alignment(&self, axis: Axis) -> Alignment { + let style = self.style(self.grid_container); + axis.select( + inline_content_alignment(style.justify_content()), + block_content_alignment(style.align_content()), + ) + } + + fn container_padding_start(&self, axis: Axis) -> CssPixels { + let used = self.container_used(); + axis.select(used.padding_left.get(), used.padding_top.get()) + } + + fn container_padding_end(&self, axis: Axis) -> CssPixels { + let used = self.container_used(); + axis.select(used.padding_right.get(), used.padding_bottom.get()) } fn interleaved_tracks(&self, axis: Axis) -> Vec { @@ -1943,11 +1926,8 @@ impl<'pass> GridFormattingContext<'pass> { } fn store_interleaved_tracks(&mut self, axis: Axis, interleaved: &[Track]) { - let (tracks, gaps) = if axis.is_column() { - (&mut self.columns, &mut self.column_gaps) - } else { - (&mut self.rows, &mut self.row_gaps) - }; + let (tracks, gaps) = + axis.select((&mut self.columns, &mut self.column_gaps), (&mut self.rows, &mut self.row_gaps)); for (index, track) in tracks.iter_mut().enumerate() { *track = interleaved[Self::interleaved_index_of_track(index)]; } @@ -2032,26 +2012,7 @@ impl<'pass> GridFormattingContext<'pass> { } fn outer_edges(&self, item: GridItem, axis: Axis) -> CssPixels { - let used = self.used(item); - if axis.is_column() { - used.margin_left.get() - + used.border_left.get() - + used.padding_left.get() - + used.padding_right.get() - + used.border_right.get() - + used.margin_right.get() - + item.extra_margin_left - + item.extra_margin_right - } else { - used.margin_top.get() - + used.border_top.get() - + used.padding_top.get() - + used.padding_bottom.get() - + used.border_bottom.get() - + used.margin_bottom.get() - + item.extra_margin_top - + item.extra_margin_bottom - } + self.item_margin_box_start(item, axis) + self.item_margin_box_end(item, axis) } fn add_outer_size(&self, item: GridItem, axis: Axis, size: CssPixels) -> CssPixels { @@ -2060,40 +2021,24 @@ impl<'pass> GridFormattingContext<'pass> { fn preferred_size(&self, item: GridItem, axis: Axis) -> &'pass ComputedSize { let style = self.style(item.box_); - if axis.is_column() { - style.width() - } else { - style.height() - } + axis.select(style.width(), style.height()) } fn minimum_size(&self, item: GridItem, axis: Axis) -> &'pass ComputedSize { let style = self.style(item.box_); - if axis.is_column() { - style.min_width() - } else { - style.min_height() - } + axis.select(style.min_width(), style.min_height()) } fn maximum_size(&self, item: GridItem, axis: Axis) -> &'pass ComputedSize { let style = self.style(item.box_); - if axis.is_column() { - style.max_width() - } else { - style.max_height() - } + axis.select(style.max_width(), style.max_height()) } fn preferred_behaves_as_auto(&self, item: GridItem, axis: Axis) -> bool { let available = self.item_available_space(item); let behaves_as_auto = self.sizing().should_treat_size_as_auto( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, + axis.sizing_axis(), available, self.track_sizing_constraints(), ); @@ -2136,18 +2081,10 @@ impl<'pass> GridFormattingContext<'pass> { self.min_content_size(item, axis) } } else { - let property = if axis.is_column() { - SizingProperty::Width - } else { - SizingProperty::Height - }; + let property = axis.select(SizingProperty::Width, SizingProperty::Height); self.sizing().calculate_inner_size_for_property( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, + axis.sizing_axis(), property, self.available_space.unwrap(), self.track_sizing_constraints(), @@ -2167,11 +2104,7 @@ impl<'pass> GridFormattingContext<'pass> { let content = if self.preferred_behaves_as_auto(item, axis) || preferred.is_fit_content() { self.sizing().calculate_fit_content_size( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, + axis.sizing_axis(), self.item_available_space(item), self.container_constraints(), ) @@ -2186,16 +2119,8 @@ impl<'pass> GridFormattingContext<'pass> { }; self.sizing().calculate_inner_size_for_property( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, - if axis.is_column() { - SizingProperty::Width - } else { - SizingProperty::Height - }, + axis.sizing_axis(), + axis.select(SizingProperty::Width, SizingProperty::Height), area_space, self.track_sizing_constraints(), ) @@ -2219,11 +2144,8 @@ impl<'pass> GridFormattingContext<'pass> { return None; } - let has_definite_preferred_size = if axis.is_column() { - self.used(item).has_definite_inline_size() - } else { - self.used(item).has_definite_block_size() - }; + let used = self.used(item); + let has_definite_preferred_size = axis.select(used.has_definite_inline_size(), used.has_definite_block_size()); if has_definite_preferred_size { // FIXME: consider margins, padding and borders because it is outer size. let containing_block_size = self.containing_block_size(item, axis); @@ -2240,8 +2162,7 @@ impl<'pass> GridFormattingContext<'pass> { // through the aspect ratio. It is otherwise undefined. let preferred_aspect_ratio = self.facts(item.box_).preferred_aspect_ratio()?; - let preferred_size_in_opposite_axis = - self.preferred_size(item, if axis.is_column() { Axis::Row } else { Axis::Column }); + let preferred_size_in_opposite_axis = self.preferred_size(item, axis.opposite()); if preferred_size_in_opposite_axis.is_length() { let opposite_axis_size = preferred_size_in_opposite_axis.to_px(CssPixels::default()); // FIXME: Clamp by opposite-axis minimum and maximum sizes if they are definite @@ -2366,16 +2287,8 @@ impl<'pass> GridFormattingContext<'pass> { } self.sizing().calculate_inner_size_for_property( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, - if axis.is_column() { - SizingProperty::MinWidth - } else { - SizingProperty::MinHeight - }, + axis.sizing_axis(), + axis.select(SizingProperty::MinWidth, SizingProperty::MinHeight), available, self.track_sizing_constraints(), ) @@ -2421,16 +2334,8 @@ impl<'pass> GridFormattingContext<'pass> { let constraints = self.layout_input.unwrap().containing_block_constraints; self.sizing().calculate_inner_size_for_property( self.grid_container, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, - if axis.is_column() { - SizingProperty::MaxWidth - } else { - SizingProperty::MaxHeight - }, + axis.sizing_axis(), + axis.select(SizingProperty::MaxWidth, SizingProperty::MaxHeight), available, constraints, ) @@ -2522,11 +2427,7 @@ impl<'pass> GridFormattingContext<'pass> { return false; } let grid_style = self.grid_style(item.box_); - if axis.is_column() { - grid_style.template_columns.is_subgrid - } else { - grid_style.template_rows.is_subgrid - } + axis.select(grid_style.template_columns.is_subgrid, grid_style.template_rows.is_subgrid) } fn apply_subgrid_edge_extra_margins(&self, item: &mut GridItem, axis: Axis) { @@ -2658,11 +2559,10 @@ impl<'pass> GridFormattingContext<'pass> { let mut tracks = self.interleaved_tracks(axis); let contributions = self.item_contributions_to_track_sizing(axis); let style = self.style(self.grid_container); - let distribution_stretches = if axis.is_column() { - matches!(style.justify_content(), justify_content::NORMAL | justify_content::STRETCH) - } else { - matches!(style.align_content(), align_content::NORMAL | align_content::STRETCH) - }; + let distribution_stretches = axis.select( + matches!(style.justify_content(), justify_content::NORMAL | justify_content::STRETCH), + matches!(style.align_content(), align_content::NORMAL | align_content::STRETCH), + ); run_track_sizing( &mut tracks, CssPixels::default(), @@ -2718,11 +2618,10 @@ impl<'pass> GridFormattingContext<'pass> { fn item_alignment_for_node(&self, node: Node, axis: Axis) -> Alignment { let item_style = self.style(node); let container_style = self.style(self.grid_container); - if axis.is_column() { - inline_item_alignment(item_style.justify_self(), container_style.justify_items()) - } else { - block_item_alignment(item_style.align_self(), container_style.align_items()) - } + axis.select( + inline_item_alignment(item_style.justify_self(), container_style.justify_items()), + block_item_alignment(item_style.align_self(), container_style.align_items()), + ) } fn item_alignment(&self, item: GridItem, axis: Axis) -> Alignment { @@ -2731,20 +2630,18 @@ impl<'pass> GridFormattingContext<'pass> { fn item_margin_box_start(&self, item: GridItem, axis: Axis) -> CssPixels { let used = self.used(item); - if axis.is_column() { - used.margin_left.get() + used.border_left.get() + used.padding_left.get() + item.extra_margin_left - } else { - used.margin_top.get() + used.border_top.get() + used.padding_top.get() + item.extra_margin_top - } + axis.select( + used.margin_left.get() + used.border_left.get() + used.padding_left.get() + item.extra_margin_left, + used.margin_top.get() + used.border_top.get() + used.padding_top.get() + item.extra_margin_top, + ) } fn item_margin_box_end(&self, item: GridItem, axis: Axis) -> CssPixels { let used = self.used(item); - if axis.is_column() { - used.padding_right.get() + used.border_right.get() + used.margin_right.get() + item.extra_margin_right - } else { - used.padding_bottom.get() + used.border_bottom.get() + used.margin_bottom.get() + item.extra_margin_bottom - } + axis.select( + used.padding_right.get() + used.border_right.get() + used.margin_right.get() + item.extra_margin_right, + used.padding_bottom.get() + used.border_bottom.get() + used.margin_bottom.get() + item.extra_margin_bottom, + ) } fn non_cyclic_table_wrapper_inline_size(&self, item: GridItem, containing: CssPixels) -> CssPixels { @@ -2920,18 +2817,13 @@ impl<'pass> GridFormattingContext<'pass> { constraints.percentage_basis_block_size = Some(containing); } let style = self.style(item.box_); - let preferred = if axis.is_column() { - style.width() - } else { - style.height() - }; + let preferred = axis.select(style.width(), style.height()); let alignment = self.item_alignment(item, axis); let facts = self.facts(item.box_); - let has_natural = if axis.is_column() { - facts.has_auto_content_width() || (facts.has_auto_content_height() && facts.has_preferred_aspect_ratio()) - } else { - facts.has_auto_content_height() || (facts.has_auto_content_width() && facts.has_preferred_aspect_ratio()) - }; + let has_natural = axis.select( + facts.has_auto_content_width() || facts.has_auto_content_height() && facts.has_preferred_aspect_ratio(), + facts.has_auto_content_height() || facts.has_auto_content_width() && facts.has_preferred_aspect_ratio(), + ); // https://drafts.csswg.org/css-grid-1/#grid-item-sizing // If the grid item has no preferred aspect ratio, and no natural size in the relevant axis (if it is a replaced // element), the grid item is sized as for 'align-self: stretch'. @@ -2993,27 +2885,15 @@ impl<'pass> GridFormattingContext<'pass> { } else if preferred.is_auto() || preferred.is_fit_content() { self.sizing().calculate_fit_content_size( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, + axis.sizing_axis(), available, constraints, ) } else { self.sizing().calculate_inner_size_for_property( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, - if axis.is_column() { - SizingProperty::Width - } else { - SizingProperty::Height - }, + axis.sizing_axis(), + axis.select(SizingProperty::Width, SizingProperty::Height), available, constraints, ) @@ -3062,16 +2942,8 @@ impl<'pass> GridFormattingContext<'pass> { if !maximum_is_none { let maximum = self.sizing().calculate_inner_size_for_property( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, - if axis.is_column() { - SizingProperty::MaxWidth - } else { - SizingProperty::MaxHeight - }, + axis.sizing_axis(), + axis.select(SizingProperty::MaxWidth, SizingProperty::MaxHeight), available, constraints, ); @@ -3079,24 +2951,12 @@ impl<'pass> GridFormattingContext<'pass> { resolved = resolve_alignment(maximum, false); } } - let minimum = if axis.is_column() { - style.min_width() - } else { - style.min_height() - }; + let minimum = axis.select(style.min_width(), style.min_height()); if !minimum.is_auto() { let minimum = self.sizing().calculate_inner_size_for_property( item.box_, - if axis.is_column() { - SizingAxis::Inline - } else { - SizingAxis::Block - }, - if axis.is_column() { - SizingProperty::MinWidth - } else { - SizingProperty::MinHeight - }, + axis.sizing_axis(), + axis.select(SizingProperty::MinWidth, SizingProperty::MinHeight), available, constraints, ); @@ -3149,18 +3009,10 @@ impl<'pass> GridFormattingContext<'pass> { .axis_tracks(axis) .iter() .fold(CssPixels::default(), |sum, track| sum + track.base_size); - let alignment = if axis.is_column() { - inline_content_alignment(self.style(self.grid_container).justify_content()) - } else { - block_content_alignment(self.style(self.grid_container).align_content()) - }; + let alignment = self.content_alignment(axis); let minimum = self.resolved_gap(axis, AvailableSize::definite(container)); let size = distributed_gap_size(alignment, container, track_sum, self.axis_gaps(axis).len(), minimum); - let gaps = if axis.is_column() { - &mut self.column_gaps - } else { - &mut self.row_gaps - }; + let gaps = axis.select(&mut self.column_gaps, &mut self.row_gaps); for gap in gaps { gap.base_size = size; } @@ -3220,16 +3072,8 @@ impl<'pass> GridFormattingContext<'pass> { } fn axis_grid_area(&self, axis: Axis, placement: Option<(i32, usize)>) -> (CssPixels, CssPixels) { - let padding_start = if axis.is_column() { - self.container_used().padding_left.get() - } else { - self.container_used().padding_top.get() - }; - let padding_end = if axis.is_column() { - self.container_used().padding_right.get() - } else { - self.container_used().padding_bottom.get() - }; + let padding_start = self.container_padding_start(axis); + let padding_end = self.container_padding_end(axis); let Some((position, span)) = placement else { let content_size = match self.axis_available(axis) { AvailableSize::Definite(size) => size, @@ -3244,11 +3088,7 @@ impl<'pass> GridFormattingContext<'pass> { let start = position.saturating_mul(2); let end = start.saturating_add(span.saturating_mul(2) as i32); let container = self.grid_container_alignment_size(axis); - let alignment = if axis.is_column() { - inline_content_alignment(self.style(self.grid_container).justify_content()) - } else { - block_content_alignment(self.style(self.grid_container).align_content()) - }; + let alignment = self.content_alignment(axis); let initial_offset = content_start_offset(alignment, container, self.track_sum(axis)); let mut start_offset = initial_offset; let mut end_offset = initial_offset; @@ -3268,16 +3108,8 @@ impl<'pass> GridFormattingContext<'pass> { end: ComputedGridPlacement, placement_names: &[usize], ) -> (CssPixels, CssPixels) { - let lines = if axis.is_column() { - &self.column_lines - } else { - &self.row_lines - }; - let explicit_lines = if axis.is_column() { - self.explicit_column_line_count - } else { - self.explicit_row_line_count - }; + let lines = self.axis_lines(axis); + let explicit_lines = self.explicit_line_count(axis); let resolved = resolve_placement_position( start, end, @@ -3304,11 +3136,7 @@ impl<'pass> GridFormattingContext<'pass> { // overflows). These lines become the first and last lines (0th and -0th) of the augmented grid used for positioning absolutely-positioned items. let explicit_line_position = |line: i32| { let tracks = self.interleaved_tracks(axis); - let alignment = if axis.is_column() { - inline_content_alignment(self.style(self.grid_container).justify_content()) - } else { - block_content_alignment(self.style(self.grid_container).align_content()) - }; + let alignment = self.content_alignment(axis); let mut offset = content_start_offset( alignment, self.axis_available(axis).to_px_or_zero(), @@ -3321,21 +3149,13 @@ impl<'pass> GridFormattingContext<'pass> { }; let augmented_edge = |is_start: bool| { if is_start { - if axis.is_column() { - -self.container_used().padding_left.get() - } else { - -self.container_used().padding_top.get() - } + -self.container_padding_start(axis) } else { let mut offset = match self.axis_available(axis) { AvailableSize::Definite(size) => size, _ => self.track_sum(axis), }; - offset += if axis.is_column() { - self.container_used().padding_right.get() - } else { - self.container_used().padding_bottom.get() - }; + offset += self.container_padding_end(axis); offset } }; @@ -3434,11 +3254,7 @@ impl<'pass> GridFormattingContext<'pass> { // grid-template-rows and grid-template-columns properties represents the used number of columns, // serialized as the subgrid keyword followed by a list representing each of its lines as a line // name set of all the line's names explicitly defined on the subgrid, without using repeat(). - let lines = if axis.is_column() { - &self.column_lines - } else { - &self.row_lines - }; + let lines = self.axis_lines(axis); let mut names = Vec::with_capacity(lines.len()); for line in lines { names.push( @@ -3479,26 +3295,10 @@ impl<'pass> GridFormattingContext<'pass> { let serialize = |axis: Axis| { let tracks = self.axis_tracks(axis); let gaps = self.axis_gaps(axis); - let lines = if axis.is_column() { - &self.column_lines - } else { - &self.row_lines - }; - let explicit_start = if axis.is_column() { - self.explicit_column_start - } else { - self.explicit_row_start - }; - let explicit_count = if axis.is_column() { - self.explicit_column_line_count - } else { - self.explicit_row_line_count - }; - let alignment = if axis.is_column() { - inline_content_alignment(self.style(self.grid_container).justify_content()) - } else { - block_content_alignment(self.style(self.grid_container).align_content()) - }; + let lines = self.axis_lines(axis); + let explicit_start = axis.select(self.explicit_column_start, self.explicit_row_start); + let explicit_count = self.explicit_line_count(axis); + let alignment = self.content_alignment(axis); let mut start = content_start_offset( alignment, self.grid_container_alignment_size(axis),