diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs index bee72fa..0223777 100644 --- a/crates/obscura-render/src/dom.rs +++ b/crates/obscura-render/src/dom.rs @@ -5367,28 +5367,42 @@ fn effective_container_type(style: &crate::LayoutStyle) -> crate::ContainerType } } -/// Does this auto inline-size use shrink-to-fit/content sizing rather than -/// filling a definite containing block? -fn container_auto_inline_size_is_intrinsic( +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum ContainerAutoInlineSize { + FillAvailable, + Intrinsic, + StretchedGridItem, +} + +/// Classify how an auto inline-size is resolved. Stretched grid items need +/// their intrinsic contribution contained without replacing the final auto +/// size that stretch alignment consumes. +fn container_auto_inline_size( tree: &DomTree, id: NodeId, style: &crate::LayoutStyle, styles: &HashMap, -) -> bool { +) -> ContainerAutoInlineSize { if style.is_inline_block || style.float.is_some() { - return true; + return ContainerAutoInlineSize::Intrinsic; } if matches!(style.position, Some(taffy::Position::Absolute)) { // An absolutely positioned auto width is fill-available only when // both inline insets are definite. - return style.inset[1].is_none() || style.inset[3].is_none(); + return if style.inset[1].is_none() || style.inset[3].is_none() { + ContainerAutoInlineSize::Intrinsic + } else { + ContainerAutoInlineSize::FillAvailable + }; } let mut parent = tree.get_node(id).and_then(|node| node.parent); let parent_style = loop { - let Some(parent_id) = parent else { return false }; + let Some(parent_id) = parent else { + return ContainerAutoInlineSize::FillAvailable; + }; let Some(parent_style) = styles.get(&parent_id) else { - return false; + return ContainerAutoInlineSize::FillAvailable; }; if parent_style.display_contents { parent = tree.get_node(parent_id).and_then(|node| node.parent); @@ -5406,30 +5420,38 @@ fn container_auto_inline_size_is_intrinsic( ) ); if row { - true + ContainerAutoInlineSize::Intrinsic + } else if style + .align_self + .unwrap_or( + parent_style + .align_items + .unwrap_or(taffy::AlignItems::STRETCH), + ) + == taffy::AlignSelf::STRETCH + { + ContainerAutoInlineSize::FillAvailable } else { - style - .align_self - .unwrap_or( - parent_style - .align_items - .unwrap_or(taffy::AlignItems::STRETCH), - ) - != taffy::AlignSelf::STRETCH + ContainerAutoInlineSize::Intrinsic } } crate::Display::Grid => { - style + if style .justify_self .unwrap_or( parent_style .justify_items .unwrap_or(taffy::JustifyItems::STRETCH), ) - != taffy::AlignSelf::STRETCH + == taffy::AlignSelf::STRETCH + { + ContainerAutoInlineSize::StretchedGridItem + } else { + ContainerAutoInlineSize::Intrinsic + } } - crate::Display::Inline => true, - _ => false, + crate::Display::Inline => ContainerAutoInlineSize::Intrinsic, + _ => ContainerAutoInlineSize::FillAvailable, } } @@ -5458,11 +5480,16 @@ fn apply_container_size_containment( } let ratio_transfers_inline_size = style.aspect_ratio.is_some() && !matches!(style.height, crate::Dimension::Auto); - if matches!(style.width, crate::Dimension::Auto) - && !ratio_transfers_inline_size - && container_auto_inline_size_is_intrinsic(tree, id, style, styles) - { - taffy_style.size.width = taffy::Dimension::length(0.0); + if matches!(style.width, crate::Dimension::Auto) && !ratio_transfers_inline_size { + match container_auto_inline_size(tree, id, style, styles) { + ContainerAutoInlineSize::Intrinsic => { + taffy_style.size.width = taffy::Dimension::length(0.0); + } + ContainerAutoInlineSize::StretchedGridItem => { + taffy_style.intrinsic_size_containment.width = true; + } + ContainerAutoInlineSize::FillAvailable => {} + } } if kind == crate::ContainerType::Size { diff --git a/crates/obscura-render/tests/layout_test.rs b/crates/obscura-render/tests/layout_test.rs index 7d5c059..cc0f32a 100644 --- a/crates/obscura-render/tests/layout_test.rs +++ b/crates/obscura-render/tests/layout_test.rs @@ -1090,6 +1090,63 @@ fn item_self_alignment_places_flex_and_grid_items() { ); } +#[test] +fn stretched_inline_size_container_has_zero_intrinsic_grid_contribution() { + // Chromium 140: size containment affects intrinsic track sizing, but not + // the item's later stretch alignment. In the first case the grid therefore + // shrink-wraps to zero. In the definite-width control the contained item + // contributes zero, then stretches to the 50px left after the fixed track. + // Its 240px child remains visible overflow in both cases. + let tree = parse_html( + r#" + +
+
+
+
+
+
+
+
+
+ "#, + ); + let layout = layout_dom(&tree, (800.0, 600.0)); + let rect = |id| layout.rects[&tree.get_element_by_id(id).unwrap()]; + let shrink_grid = rect("shrink-grid"); + let shrink_item = rect("shrink-item"); + let shrink_child = rect("shrink-child"); + let fixed_grid = rect("fixed-grid"); + let fixed_item = rect("fixed-item"); + let fixed_child = rect("fixed-child"); + let fixed = rect("fixed"); + assert!( + shrink_grid.width.abs() < 0.01 + && shrink_item.width.abs() < 0.01 + && (shrink_child.width - 240.0).abs() < 0.01 + && (fixed_grid.width - 150.0).abs() < 0.01 + && (fixed_item.width - 50.0).abs() < 0.01 + && (fixed_child.width - 240.0).abs() < 0.01 + && (fixed.x - 50.0).abs() < 0.01 + && (fixed.width - 100.0).abs() < 0.01, + "contained intrinsic contribution leaked into the grid track: \ + shrink=({shrink_grid:?}, {shrink_item:?}, {shrink_child:?}) \ + fixed=({fixed_grid:?}, {fixed_item:?}, {fixed_child:?}, {fixed:?})" + ); +} + #[test] fn non_stretched_auto_grid_item_shrink_wraps_inside_grid_area() { let tree = parse_html( diff --git a/vendor/taffy/src/compute/grid/types/grid_item.rs b/vendor/taffy/src/compute/grid/types/grid_item.rs index 1ed6e24..d407d0c 100644 --- a/vendor/taffy/src/compute/grid/types/grid_item.rs +++ b/vendor/taffy/src/compute/grid/types/grid_item.rs @@ -31,6 +31,8 @@ pub(in super::super) struct GridItem { /// Is it a compressible replaced element? /// https://drafts.csswg.org/css-sizing-3/#min-content-zero pub is_compressible_replaced: bool, + /// Whether descendants are ignored for intrinsic size contributions in each axis. + pub intrinsic_size_containment: Size, /// The item's overflow style pub overflow: Point, /// The item's box_sizing style @@ -108,6 +110,7 @@ impl GridItem { row: row_span, column: col_span, is_compressible_replaced: style.is_compressible_replaced(), + intrinsic_size_containment: style.intrinsic_size_containment(), overflow: style.overflow(), box_sizing: style.box_sizing(), size: style.size(), @@ -425,6 +428,13 @@ impl GridItem { available_space: Size>, ) -> f32 { let known_dimensions = self.known_dimensions(tree, grid_area_size); + if self.intrinsic_size_containment.get(axis) { + let padding = + self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis)); + let border = + self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis)); + return known_dimensions.get(axis).unwrap_or((padding + border).sum_axes().get(axis)); + } // The child sees the grid area as its containing block during intrinsic measurement, so // percentage box properties resolve against the grid area when that size is definite. // Spec: @@ -469,6 +479,13 @@ impl GridItem { available_space: Size>, ) -> f32 { let known_dimensions = self.known_dimensions(tree, grid_area_size); + if self.intrinsic_size_containment.get(axis) { + let padding = + self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis)); + let border = + self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis)); + return known_dimensions.get(axis).unwrap_or((padding + border).sum_axes().get(axis)); + } // See the min-content path above. Max-content measurement uses the same containing-block // basis so percentage-dependent item geometry is measured from the grid area rather than // from the container. diff --git a/vendor/taffy/src/style/grid.rs b/vendor/taffy/src/style/grid.rs index 1e0a8b0..22c3507 100644 --- a/vendor/taffy/src/style/grid.rs +++ b/vendor/taffy/src/style/grid.rs @@ -237,6 +237,12 @@ pub trait GridContainerStyle: CoreStyle { /// The set of styles required for a CSS Grid item (child of a CSS Grid container) pub trait GridItemStyle: CoreStyle { + /// Whether descendants are ignored for intrinsic size contributions in each axis. + #[inline(always)] + fn intrinsic_size_containment(&self) -> Size { + Size { width: false, height: false } + } + /// Defines which row in the grid the item should start and end at #[inline(always)] fn grid_row(&self) -> Line> { diff --git a/vendor/taffy/src/style/mod.rs b/vendor/taffy/src/style/mod.rs index beba852..00b53d6 100644 --- a/vendor/taffy/src/style/mod.rs +++ b/vendor/taffy/src/style/mod.rs @@ -442,6 +442,11 @@ pub struct Style { /// Is it a replaced element like an image or form field? /// pub item_is_replaced: bool, + /// Whether descendants are ignored for intrinsic size contributions in each axis. + /// + /// Ordinary final layout is unaffected, allowing an auto-sized grid item to + /// retain stretch alignment after its contained intrinsic contribution is removed. + pub intrinsic_size_containment: Size, /// Should size styles apply to the content box or the border box of the node pub box_sizing: BoxSizing, /// Sets the direction of text, table and grid columns, and horizontal overflow. @@ -591,6 +596,7 @@ impl Style { display: Display::DEFAULT, item_is_table: false, item_is_replaced: false, + intrinsic_size_containment: Size { width: false, height: false }, box_sizing: BoxSizing::BorderBox, direction: Direction::Ltr, overflow: Point { x: Overflow::Visible, y: Overflow::Visible }, @@ -1150,6 +1156,10 @@ impl GridContainerStyle for &'_ T { #[cfg(feature = "grid")] impl GridItemStyle for Style { + #[inline(always)] + fn intrinsic_size_containment(&self) -> Size { + self.intrinsic_size_containment + } #[inline(always)] fn grid_row(&self) -> Line> { // TODO: Investigate eliminating clone @@ -1172,6 +1182,10 @@ impl GridItemStyle for Style { #[cfg(feature = "grid")] impl GridItemStyle for &'_ T { + #[inline(always)] + fn intrinsic_size_containment(&self) -> Size { + (*self).intrinsic_size_containment() + } #[inline(always)] fn grid_row(&self) -> Line> { (*self).grid_row() @@ -1208,6 +1222,7 @@ mod tests { display: Default::default(), item_is_table: false, item_is_replaced: false, + intrinsic_size_containment: Size { width: false, height: false }, box_sizing: Default::default(), #[cfg(feature = "float_layout")] float: Default::default(),