diff --git a/crates/obscura-render/tests/layout_test.rs b/crates/obscura-render/tests/layout_test.rs
index 266f96d..0dc70f5 100644
--- a/crates/obscura-render/tests/layout_test.rs
+++ b/crates/obscura-render/tests/layout_test.rs
@@ -777,6 +777,90 @@ fn ratio_only_inline_svg_sizes_inside_an_auto_grid_row() {
);
}
+/// Chromium 150 includes collapsed descendant margins in a grid item's
+/// intrinsic block-size. The nested 64px margin reaches through `section`,
+/// making the first item 200px tall; the second row therefore begins after
+/// that item and the 20px row gap, rather than overlapping the descendant.
+#[test]
+fn grid_item_intrinsic_height_includes_collapsed_descendant_margins() {
+ 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 outer = rect("outer");
+ let item = rect("item");
+ let deep = rect("deep");
+ let after = rect("after");
+
+ assert!(
+ (item.height - 200.0).abs() < 0.01 && (deep.y + deep.height - 200.0).abs() < 0.01,
+ "the grid item must contain the collapsed nested margin: item={item:?} deep={deep:?}"
+ );
+ assert!(
+ (after.y - 220.0).abs() < 0.01 && (outer.height - 230.0).abs() < 0.01,
+ "later grid rows must not overlap overflowing intrinsic content: outer={outer:?} after={after:?}"
+ );
+}
+
+/// Collapsible margin sets retain their largest positive and most-negative
+/// members separately. A negative nested margin therefore shortens the grid
+/// item's intrinsic contribution and overlaps the preceding block in exactly
+/// the same way during measurement and final layout.
+#[test]
+fn grid_item_intrinsic_height_keeps_negative_collapsed_margin() {
+ 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 outer = rect("outer");
+ let item = rect("item");
+ let deep = rect("deep");
+ let after = rect("after");
+
+ assert!(
+ (item.height - 112.0).abs() < 0.01
+ && (deep.y - 12.0).abs() < 0.01
+ && (deep.height - 100.0).abs() < 0.01,
+ "negative collapsed margin geometry must survive measurement: item={item:?} deep={deep:?}"
+ );
+ assert!(
+ (after.y - 132.0).abs() < 0.01 && (outer.height - 142.0).abs() < 0.01,
+ "the following grid row must use the shortened contribution: outer={outer:?} after={after:?}"
+ );
+}
+
#[test]
fn inline_block_flex_items_keep_block_inner_flow() {
let tree = parse_html(include_str!("../../../render-repros/inline-block-flex-items.html"));
diff --git a/vendor/taffy/src/compute/block.rs b/vendor/taffy/src/compute/block.rs
index 17fee8d..fcd9b0f 100644
--- a/vendor/taffy/src/compute/block.rs
+++ b/vendor/taffy/src/compute/block.rs
@@ -534,9 +534,35 @@ fn compute_inner(
}
}
+ // Margin-collapsing metadata is part of a block's intrinsic contribution,
+ // not only its final child placement. Grid and flex sizing can measure a
+ // block here and use its exposed start/end margin sets to size an ancestor
+ // item. Dropping them from ComputeSize makes the ancestor track shorter
+ // than the block's eventual descendants.
+ let all_in_flow_children_can_be_collapsed_through =
+ items.iter().all(|item| item.position == Position::Absolute || item.can_be_collapsed_through);
+ let can_be_collapsed_through =
+ !has_styles_preventing_being_collapsed_through && all_in_flow_children_can_be_collapsed_through;
+ let top_margin = if own_margins_collapse_with_children.start {
+ first_child_top_margin_set
+ } else {
+ let margin_top = raw_margin.top.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+ CollapsibleMarginSet::from_margin(margin_top)
+ };
+ let bottom_margin = if own_margins_collapse_with_children.end {
+ last_child_bottom_margin_set
+ } else {
+ let margin_bottom = raw_margin.bottom.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
+ CollapsibleMarginSet::from_margin(margin_bottom)
+ };
+
// Short-circuit if computing size
if run_mode == RunMode::ComputeSize {
- return LayoutOutput::from_outer_size(final_outer_size);
+ let mut output = LayoutOutput::from_outer_size(final_outer_size);
+ output.top_margin = top_margin;
+ output.bottom_margin = bottom_margin;
+ output.margins_can_collapse_through = can_be_collapsed_through;
+ return output;
}
// Commit deferred in-flow layouts to the tree. Floated items already wrote their own layouts.
@@ -577,12 +603,6 @@ fn compute_inner(
}
}
- // 7. Determine whether this node can be collapsed through
- let all_in_flow_children_can_be_collapsed_through =
- items.iter().all(|item| item.position == Position::Absolute || item.can_be_collapsed_through);
- let can_be_collapsed_through =
- !has_styles_preventing_being_collapsed_through && all_in_flow_children_can_be_collapsed_through;
-
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
let content_size = inflow_content_size.f32_max(absolute_content_size);
@@ -591,19 +611,8 @@ fn compute_inner(
#[cfg(feature = "content_size")]
content_size,
first_baselines: Point::NONE,
- top_margin: if own_margins_collapse_with_children.start {
- first_child_top_margin_set
- } else {
- let margin_top = raw_margin.top.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
- CollapsibleMarginSet::from_margin(margin_top)
- },
- bottom_margin: if own_margins_collapse_with_children.end {
- last_child_bottom_margin_set
- } else {
- let margin_bottom =
- raw_margin.bottom.resolve_or_zero(parent_size.width, |val, basis| tree.calc(val, basis));
- CollapsibleMarginSet::from_margin(margin_bottom)
- },
+ top_margin,
+ bottom_margin,
margins_can_collapse_through: can_be_collapsed_through,
}
}
diff --git a/vendor/taffy/src/tree/cache.rs b/vendor/taffy/src/tree/cache.rs
index 5187419..03f2010 100644
--- a/vendor/taffy/src/tree/cache.rs
+++ b/vendor/taffy/src/tree/cache.rs
@@ -4,7 +4,7 @@
use crate::geometry::Size;
use crate::style::AvailableSpace;
-use crate::tree::{LayoutInput, LayoutOutput, RunMode};
+use crate::tree::{CollapsibleMarginSet, LayoutInput, LayoutOutput, RunMode};
use crate::RequestedAxis;
/// The number of cache entries for each node in the tree
@@ -81,6 +81,12 @@ fn size_mixed_cache_key(kd: Size