Preserve collapsed margins in intrinsic sizing
This commit is contained in:
1 parent
90c64b2c31
commit
839f1c2338
3 files changed
+160
-24
No files matched your search
@@ -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#"<html><head><style>
|
||||||
|
html,body{margin:0}
|
||||||
|
#outer{display:grid;width:300px;grid-template-rows:auto auto;row-gap:20px}
|
||||||
|
#item{position:relative}
|
||||||
|
#lead{height:20px;margin-top:16px}
|
||||||
|
#deep{height:100px;margin-top:64px}
|
||||||
|
#after{height:10px}
|
||||||
|
</style></head><body>
|
||||||
|
<div id="outer">
|
||||||
|
<div id="item">
|
||||||
|
<div id="lead"></div>
|
||||||
|
<section><div id="deep"></div></section>
|
||||||
|
</div>
|
||||||
|
<div id="after"></div>
|
||||||
|
</div>
|
||||||
|
</body></html>"#,
|
||||||
|
);
|
||||||
|
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#"<html><head><style>
|
||||||
|
html,body{margin:0}
|
||||||
|
#outer{display:grid;width:300px;grid-template-rows:auto auto;row-gap:20px}
|
||||||
|
#item{position:relative}
|
||||||
|
#lead{height:20px;margin-top:16px}
|
||||||
|
#deep{height:100px;margin-top:-24px}
|
||||||
|
#after{height:10px}
|
||||||
|
</style></head><body>
|
||||||
|
<div id="outer">
|
||||||
|
<div id="item">
|
||||||
|
<div id="lead"></div>
|
||||||
|
<section><div id="deep"></div></section>
|
||||||
|
</div>
|
||||||
|
<div id="after"></div>
|
||||||
|
</div>
|
||||||
|
</body></html>"#,
|
||||||
|
);
|
||||||
|
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]
|
#[test]
|
||||||
fn inline_block_flex_items_keep_block_inner_flow() {
|
fn inline_block_flex_items_keep_block_inner_flow() {
|
||||||
let tree = parse_html(include_str!("../../../render-repros/inline-block-flex-items.html"));
|
let tree = parse_html(include_str!("../../../render-repros/inline-block-flex-items.html"));
|
||||||
|
|||||||
Vendored
+29
-20
@@ -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
|
// Short-circuit if computing size
|
||||||
if run_mode == RunMode::ComputeSize {
|
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.
|
// 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))]
|
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
|
||||||
let content_size = inflow_content_size.f32_max(absolute_content_size);
|
let content_size = inflow_content_size.f32_max(absolute_content_size);
|
||||||
|
|
||||||
@@ -591,19 +611,8 @@ fn compute_inner(
|
|||||||
#[cfg(feature = "content_size")]
|
#[cfg(feature = "content_size")]
|
||||||
content_size,
|
content_size,
|
||||||
first_baselines: Point::NONE,
|
first_baselines: Point::NONE,
|
||||||
top_margin: if own_margins_collapse_with_children.start {
|
top_margin,
|
||||||
first_child_top_margin_set
|
bottom_margin,
|
||||||
} 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)
|
|
||||||
},
|
|
||||||
margins_can_collapse_through: can_be_collapsed_through,
|
margins_can_collapse_through: can_be_collapsed_through,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+47
-4
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use crate::geometry::Size;
|
use crate::geometry::Size;
|
||||||
use crate::style::AvailableSpace;
|
use crate::style::AvailableSpace;
|
||||||
use crate::tree::{LayoutInput, LayoutOutput, RunMode};
|
use crate::tree::{CollapsibleMarginSet, LayoutInput, LayoutOutput, RunMode};
|
||||||
use crate::RequestedAxis;
|
use crate::RequestedAxis;
|
||||||
|
|
||||||
/// The number of cache entries for each node in the tree
|
/// The number of cache entries for each node in the tree
|
||||||
@@ -81,6 +81,12 @@ fn size_mixed_cache_key(kd: Size<Option<f32>>, avs: Size<AvailableSpace>) -> u64
|
|||||||
(mixed_cache_key(kd.width, avs.width) as u64) << 32 | mixed_cache_key(kd.height, avs.height) as u64
|
(mixed_cache_key(kd.width, avs.width) as u64) << 32 | mixed_cache_key(kd.height, avs.height) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn vertical_margin_context_key(input: &LayoutInput) -> u8 {
|
||||||
|
u8::from(input.vertical_margins_are_collapsible.start)
|
||||||
|
| (u8::from(input.vertical_margins_are_collapsible.end) << 1)
|
||||||
|
}
|
||||||
|
|
||||||
/// Space-optimised cache key that packs bits into as small a size as possible
|
/// Space-optimised cache key that packs bits into as small a size as possible
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize))]
|
||||||
@@ -132,6 +138,40 @@ pub(crate) struct CacheEntry<T> {
|
|||||||
content: T,
|
content: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The block-layout metadata required by a preliminary size contribution.
|
||||||
|
///
|
||||||
|
/// A compact entry avoids retaining full baselines/content-size while keeping
|
||||||
|
/// the collapsible margins that an ancestor grid or flex item must consume.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize))]
|
||||||
|
struct MeasureOutput {
|
||||||
|
size: Size<f32>,
|
||||||
|
top_margin: CollapsibleMarginSet,
|
||||||
|
bottom_margin: CollapsibleMarginSet,
|
||||||
|
margins_can_collapse_through: bool,
|
||||||
|
vertical_margins_are_collapsible: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeasureOutput {
|
||||||
|
fn new(input: &LayoutInput, output: LayoutOutput) -> Self {
|
||||||
|
Self {
|
||||||
|
size: output.size,
|
||||||
|
top_margin: output.top_margin,
|
||||||
|
bottom_margin: output.bottom_margin,
|
||||||
|
margins_can_collapse_through: output.margins_can_collapse_through,
|
||||||
|
vertical_margins_are_collapsible: vertical_margin_context_key(input),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_layout_output(self) -> LayoutOutput {
|
||||||
|
let mut output = LayoutOutput::from_outer_size(self.size);
|
||||||
|
output.top_margin = self.top_margin;
|
||||||
|
output.bottom_margin = self.bottom_margin;
|
||||||
|
output.margins_can_collapse_through = self.margins_can_collapse_through;
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A cache for caching the results of a sizing a Grid Item or Flexbox Item
|
/// A cache for caching the results of a sizing a Grid Item or Flexbox Item
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize))]
|
||||||
@@ -139,7 +179,7 @@ pub struct Cache {
|
|||||||
/// The cache entry for the node's final layout
|
/// The cache entry for the node's final layout
|
||||||
final_layout_entry: Option<CacheEntry<LayoutOutput>>,
|
final_layout_entry: Option<CacheEntry<LayoutOutput>>,
|
||||||
/// The cache entries for the node's preliminary size measurements
|
/// The cache entries for the node's preliminary size measurements
|
||||||
measure_entries: [Option<CacheEntry<Size<f32>>>; CACHE_SIZE],
|
measure_entries: [Option<CacheEntry<MeasureOutput>>; CACHE_SIZE],
|
||||||
/// Tracks if all cache entries are empty
|
/// Tracks if all cache entries are empty
|
||||||
is_empty: bool,
|
is_empty: bool,
|
||||||
}
|
}
|
||||||
@@ -230,8 +270,10 @@ impl Cache {
|
|||||||
for entry in self.measure_entries.iter().flatten() {
|
for entry in self.measure_entries.iter().flatten() {
|
||||||
if entry.key.kd_available_space == key.kd_available_space
|
if entry.key.kd_available_space == key.kd_available_space
|
||||||
&& (entry.key.x_axis_parent_size() == key.x_axis_parent_size())
|
&& (entry.key.x_axis_parent_size() == key.x_axis_parent_size())
|
||||||
|
&& entry.content.vertical_margins_are_collapsible
|
||||||
|
== vertical_margin_context_key(input)
|
||||||
{
|
{
|
||||||
return Some(LayoutOutput::from_outer_size(entry.content));
|
return Some(entry.content.into_layout_output());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +294,8 @@ impl Cache {
|
|||||||
RunMode::ComputeSize => {
|
RunMode::ComputeSize => {
|
||||||
self.is_empty = false;
|
self.is_empty = false;
|
||||||
let cache_slot = Self::compute_cache_slot(input.known_dimensions, input.available_space);
|
let cache_slot = Self::compute_cache_slot(input.known_dimensions, input.available_space);
|
||||||
self.measure_entries[cache_slot] = Some(CacheEntry { key, content: layout_output.size });
|
self.measure_entries[cache_slot] =
|
||||||
|
Some(CacheEntry { key, content: MeasureOutput::new(input, layout_output) });
|
||||||
}
|
}
|
||||||
RunMode::PerformHiddenLayout => {}
|
RunMode::PerformHiddenLayout => {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user