diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs index 1077b02..d28abd2 100644 --- a/crates/obscura-render/src/dom.rs +++ b/crates/obscura-render/src/dom.rs @@ -2962,13 +2962,6 @@ fn layout_dom_once( } } - if !static_position_candidates.is_empty() { - let _ = taffy_tree.compute_layout_with_measure(taffy_root, available, &mut measure); - resolve_static_positions_and_reparent( - &mut taffy_tree, - &static_position_candidates, - ); - } let _ = taffy_tree.compute_layout_with_measure(taffy_root, available, &mut measure); if repair_intrinsic_column_flex_negative_margins( &mut taffy_tree, @@ -3065,16 +3058,28 @@ fn layout_dom_once( &mut measure, ); } - } - #[cfg(not(feature = "paint"))] - { + // A fully-auto positioned axis uses the box's static position + // in its original formatting context. Harvest that coordinate + // only after every intrinsic/table/fragmentation repair has + // produced final in-flow geometry; otherwise reparenting pins + // the box to a stale preliminary document offset and can + // manufacture scrolling overflow. The resolver snapshots all + // candidates before changing any parent, preserving nested + // static-position candidates. if !static_position_candidates.is_empty() { - let _ = taffy_tree.compute_layout(taffy_root, available); resolve_static_positions_and_reparent( &mut taffy_tree, &static_position_candidates, ); + let _ = taffy_tree.compute_layout_with_measure( + taffy_root, + available, + &mut measure, + ); } + } + #[cfg(not(feature = "paint"))] + { let _ = taffy_tree.compute_layout(taffy_root, available); if repair_intrinsic_column_flex_negative_margins( &mut taffy_tree, @@ -3142,6 +3147,16 @@ fn layout_dom_once( ) { let _ = taffy_tree.compute_layout(taffy_root, available); } + // Keep the no-paint geometry path in the same final-position + // contract as screenshots: static coordinates are resolved + // after every pass that can move in-flow placeholders. + if !static_position_candidates.is_empty() { + resolve_static_positions_and_reparent( + &mut taffy_tree, + &static_position_candidates, + ); + let _ = taffy_tree.compute_layout(taffy_root, available); + } } sync_resolved_percentage_padding( &taffy_tree, @@ -9435,6 +9450,53 @@ mod tests { ); } + #[test] + fn static_positions_use_final_post_repair_geometry_without_phantom_overflow() { + let tree = parse_html( + r#" +