From 5976146db36e55fa140618b1deb66890bb0fe3f5 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Mon, 19 Oct 2026 00:50:56 +0800 Subject: [PATCH] fix(layout): retain grid track areas for positioned descendants --- moli-layout/src/grid.rs | 10 ++-- moli-layout/src/taffy_tree.rs | 41 ++++++++++---- moli-layout/src/world.rs | 5 +- moli-layout/tests/main_layout_contract.rs | 67 +++++++++++++++++++++++ 4 files changed, 105 insertions(+), 18 deletions(-) diff --git a/moli-layout/src/grid.rs b/moli-layout/src/grid.rs index 4a7b46214d..f67626c5a4 100644 --- a/moli-layout/src/grid.rs +++ b/moli-layout/src/grid.rs @@ -8,7 +8,7 @@ use crate::{ pub(crate) fn project_resolved_grid_tracks( style: &taffy::Style, - detailed: taffy::DetailedGridInfo, + detailed: &taffy::DetailedGridInfo, ) -> Option { let row_line_names = expanded_line_names( &style.grid_template_rows, @@ -23,13 +23,13 @@ pub(crate) fn project_resolved_grid_tracks( usize::from(detailed.columns.auto_repetitions), )?; Some(LayoutResolvedGridTracks { - rows: project_tracks(detailed.rows, row_line_names)?, - columns: project_tracks(detailed.columns, column_line_names)?, + rows: project_tracks(&detailed.rows, row_line_names)?, + columns: project_tracks(&detailed.columns, column_line_names)?, }) } fn project_tracks( - tracks: taffy::DetailedGridTracksInfo, + tracks: &taffy::DetailedGridTracksInfo, explicit_line_names: Vec>, ) -> Option { let track_count = usize::from(tracks.negative_implicit_tracks) @@ -42,7 +42,7 @@ fn project_tracks( } let used_track_sizes = tracks .positions - .into_iter() + .iter() .map(|position| to_blink_layout_unit(position.end - position.start)) .collect::>>()?; Some(LayoutResolvedGridTrackList { diff --git a/moli-layout/src/taffy_tree.rs b/moli-layout/src/taffy_tree.rs index ba3cb7407e..c451c72984 100644 --- a/moli-layout/src/taffy_tree.rs +++ b/moli-layout/src/taffy_tree.rs @@ -139,6 +139,7 @@ where N: Copy + Debug + Eq + Hash, { world.resolved_grid_tracks.clear(); + world.native_grid_details.clear(); for index in 0..world.boxes.len() { world.clear_layout_caches(LayoutBoxId::from_index(index)); let layout_box = &mut world.boxes[index]; @@ -1907,25 +1908,33 @@ where node_id: NodeId, detailed_grid_info: DetailedGridInfo, ) { - let layout_box = &self.boxes[LayoutBoxId::from_taffy(node_id).index()]; - if layout_box + let id = LayoutBoxId::from_taffy(node_id); + let layout_box = &self.boxes[id.index()]; + let expose_to_cssom = !layout_box .capability_diagnostics .contains(&LayoutCapabilityDiagnostic::GridTemplateModeDeferred) // The current backend reports detailed Grid tracks in physical // axes. Publishing them for a vertical container would expose its // width as the used `grid-template-columns` inline size. Keep the // computed value until logical Grid constraints land in Taffy. - || !layout_box.style.uses_horizontal_writing_mode() - { - return; + && layout_box.style.uses_horizontal_writing_mode(); + let resolved_grid_tracks = expose_to_cssom + .then(|| { + crate::grid::project_resolved_grid_tracks( + &layout_box.style.taffy, + &detailed_grid_info, + ) + }) + .flatten(); + // The native OOF pass needs these positions even when the browser's + // CSSOM projection deliberately retains the computed track value. + self.native_grid_details.insert( + id, + taffy::DetailedLayoutInfo::Grid(Box::new(detailed_grid_info)), + ); + if let Some(tracks) = resolved_grid_tracks { + self.resolved_grid_tracks.insert(id, tracks); } - let Some(resolved_grid_tracks) = - crate::grid::project_resolved_grid_tracks(&layout_box.style.taffy, detailed_grid_info) - else { - return; - }; - self.resolved_grid_tracks - .insert(LayoutBoxId::from_taffy(node_id), resolved_grid_tracks); } } @@ -3270,6 +3279,14 @@ impl taffy::LayoutContainingBlock for LayoutWorld Self::OofItemStyle<'_> { self.get_core_container_style(node) } + fn get_detailed_layout_info(&self, node: NodeId) -> &taffy::DetailedLayoutInfo { + if self.is_viewport_taffy_node(node) { + return &taffy::DetailedLayoutInfo::None; + } + self.native_grid_details + .get(&LayoutBoxId::from_taffy(node)) + .unwrap_or(&taffy::DetailedLayoutInfo::None) + } fn get_containing_block_claims(&self, _: NodeId) -> taffy::ContainingBlockClaims { // prepare_layout_tree has already attached every positioned box to // its CSS containing block. Internal static-position probes retain diff --git a/moli-layout/src/world.rs b/moli-layout/src/world.rs index 32c6bd8139..9f23e4e4c9 100644 --- a/moli-layout/src/world.rs +++ b/moli-layout/src/world.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, num::NonZeroU32, sync::Arc}; use style::Atom; -use taffy::{Cache, Layout, Point, Style}; +use taffy::{Cache, DetailedLayoutInfo, Layout, Point, Style}; use crate::{ LayoutCssImageReference, LayoutElementSemantics, LayoutError, LayoutPoint, LayoutPseudo, @@ -639,6 +639,8 @@ where /// Used Grid tracks for actual owners. Their canonical result is frozen /// for CSSOM before this pass-local map is released. pub(crate) resolved_grid_tracks: HashMap, + /// Native track positions and names read by Taffy's out-of-flow pass. + pub(crate) native_grid_details: HashMap>, pub(crate) source_mapping: HashMap, pub(crate) display_contents_mapping: HashMap>, pub(crate) root: LayoutBoxId, @@ -669,6 +671,7 @@ where box_caches: Vec::new(), collapsed_table_borders: HashMap::new(), resolved_grid_tracks: HashMap::new(), + native_grid_details: HashMap::new(), source_mapping: HashMap::new(), display_contents_mapping: HashMap::new(), root: LayoutBoxId::from_index(0), diff --git a/moli-layout/tests/main_layout_contract.rs b/moli-layout/tests/main_layout_contract.rs index 0157dc1b06..a381965bbc 100644 --- a/moli-layout/tests/main_layout_contract.rs +++ b/moli-layout/tests/main_layout_contract.rs @@ -709,6 +709,73 @@ fn grid_item_placement_and_self_alignment_use_the_selected_grid_area() { ); } +#[test] +fn absolute_grid_items_use_retained_track_areas_in_both_directions() { + for (direction, x) in [ + (taffy::Direction::Ltr, 135.0), + (taffy::Direction::Rtl, 95.0), + ] { + let source = FixtureSource { + nodes: vec![ + FixtureNode::div("grid", vec![1]), + FixtureNode::div("positioned", Vec::new()), + ], + }; + let mut styles = FixtureStyles::default(); + styles.0.insert( + 0, + resolved( + LayoutDisplay::Grid, + Style { + display: Display::Grid, + direction, + box_sizing: BoxSizing::ContentBox, + size: Size { + width: length(400.0), + height: length(300.0), + }, + padding: Rect::length(10.0), + border: Rect::length(5.0), + gap: Size { + width: length(20.0), + height: length(10.0), + }, + grid_template_columns: vec![length(100.0), length(200.0)], + grid_template_rows: vec![length(50.0), length(100.0)], + ..Style::default() + }, + PaintColor::TRANSPARENT, + ) + .with_position(LayoutPosition::Relative), + ); + styles.0.insert( + 1, + resolved( + LayoutDisplay::Block, + Style { + inset: Rect::length(0.0), + grid_column: Line { + start: line(2), + end: line(3), + }, + grid_row: Line { + start: line(2), + end: line(3), + }, + ..Style::default() + }, + RED, + ) + .with_position(LayoutPosition::Absolute), + ); + let snapshot = render(&source, &mut styles, PaintViewport::new(800, 600, 1.0)); + assert_rect( + solid_rect(&snapshot, RED), + PaintRect::new(x, 75.0, 200.0, 100.0), + ); + } +} + #[test] fn grid_container_justify_items_center_shrink_wraps_an_auto_sized_item() { let source = FixtureSource {