mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 00:00:55 +00:00
Fix table cell containing blocks and measured baselines
This commit is contained in:
Generated
+1
-1
@@ -5155,7 +5155,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "taffy"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/ldm0/taffy?rev=e4e8f77c4301ba917cca96b9d67b8000c5a06995#e4e8f77c4301ba917cca96b9d67b8000c5a06995"
|
||||
source = "git+https://github.com/ldm0/taffy?rev=acc16f5b0d570656c504a44b52f1736f05991a40#acc16f5b0d570656c504a44b52f1736f05991a40"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"serde",
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ stylo_atoms = { git = "https://github.com/ldm0/stylo", rev = "671d13d31b3b00e7b1
|
||||
stylo_dom = { git = "https://github.com/ldm0/stylo", rev = "671d13d31b3b00e7b1e0088ba2b36a4e35c0858c" }
|
||||
stylo_static_prefs = { git = "https://github.com/ldm0/stylo", rev = "671d13d31b3b00e7b1e0088ba2b36a4e35c0858c" }
|
||||
stylo_traits = { git = "https://github.com/ldm0/stylo", rev = "671d13d31b3b00e7b1e0088ba2b36a4e35c0858c" }
|
||||
taffy = { git = "https://github.com/ldm0/taffy", rev = "e4e8f77c4301ba917cca96b9d67b8000c5a06995" }
|
||||
taffy = { git = "https://github.com/ldm0/taffy", rev = "acc16f5b0d570656c504a44b52f1736f05991a40" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
disallowed_methods = "deny"
|
||||
|
||||
@@ -2005,7 +2005,17 @@ mod tests {
|
||||
},
|
||||
..LayoutInput::HIDDEN
|
||||
};
|
||||
let before: Vec<_> = world.boxes.iter().map(|b| b.unrounded_layout).collect();
|
||||
let cold = world.compute_child_layout(root.to_taffy(), inputs);
|
||||
assert_eq!(
|
||||
world
|
||||
.boxes
|
||||
.iter()
|
||||
.map(|b| b.unrounded_layout)
|
||||
.collect::<Vec<_>>(),
|
||||
before,
|
||||
"complete baseline measurement must not publish numeric layouts"
|
||||
);
|
||||
assert_eq!(
|
||||
cold.size,
|
||||
Size {
|
||||
|
||||
@@ -231,7 +231,7 @@ impl TableContext {
|
||||
context: self,
|
||||
};
|
||||
let output = wrapper.with_grid_cell_style(index, |world, cell| {
|
||||
world.compute_child_layout(cell.to_taffy(), measure_inputs)
|
||||
world.measure_complete_layout(cell.to_taffy(), measure_inputs)
|
||||
});
|
||||
self.cells[index].style.padding = final_padding.map(style_helpers::length);
|
||||
for (id, style) in restored {
|
||||
@@ -324,6 +324,62 @@ impl TableContext {
|
||||
+ cell.row_span.saturating_sub(1) as f32 * self.block_border_spacing;
|
||||
layout.definite |= !preferred.is_auto() && layout.size > layout.natural_size;
|
||||
}
|
||||
// Percentage descendants can acquire a different baseline once the
|
||||
// row heights are known. Return that baseline even during ComputeSize:
|
||||
// a parent table uses it to solve its own ascent/descent constraints.
|
||||
// These probes retain content bounds without publishing child layouts.
|
||||
for row in &mut rows {
|
||||
row.ascent = None;
|
||||
}
|
||||
for index in 0..self.cells.len() {
|
||||
let cell = &self.cells[index];
|
||||
let layout = cell.block_layout.unwrap();
|
||||
if layout.baseline.is_none() {
|
||||
continue;
|
||||
}
|
||||
let inline = self.column_sizes[cell.column..cell.column + cell.column_span]
|
||||
.iter()
|
||||
.sum::<f32>()
|
||||
+ self.inline_border_spacing * cell.column_span.saturating_sub(1) as f32;
|
||||
let measure_inputs = LayoutInput {
|
||||
known_dimensions: Size {
|
||||
width: Some(inline),
|
||||
height: None,
|
||||
},
|
||||
definite_dimensions: Size {
|
||||
width: Some(inline),
|
||||
height: None,
|
||||
},
|
||||
parent_size: Size {
|
||||
width: Some(cell_percentage_basis),
|
||||
height: None,
|
||||
},
|
||||
parent_writing_mode: mode,
|
||||
available_space: Size {
|
||||
width: AvailableSpace::Definite(inline),
|
||||
height: AvailableSpace::Definite(layout.size),
|
||||
},
|
||||
run_mode: RunMode::ComputeSize,
|
||||
sizing_mode: SizingMode::InherentSize,
|
||||
sizing_purpose: SizingPurpose::Layout,
|
||||
axis: RequestedAxis::Both,
|
||||
block_auto_behavior: AutoSizeBehavior::FitContent,
|
||||
vertical_margins_are_collapsible: Line::FALSE,
|
||||
};
|
||||
let mut wrapper = TableTreeWrapper {
|
||||
world,
|
||||
context: self,
|
||||
};
|
||||
let output = wrapper.with_grid_cell_style(index, |world, cell| {
|
||||
layout_cell(world, cell, measure_inputs, mode, Some(layout))
|
||||
});
|
||||
let cell = &mut self.cells[index];
|
||||
cell.block_layout.as_mut().unwrap().baseline = output.first_baselines.y;
|
||||
if let Some(baseline) = output.first_baselines.y {
|
||||
let row = &mut rows[cell.row];
|
||||
row.ascent = Some(row.ascent.unwrap_or(0.0).max(baseline));
|
||||
}
|
||||
}
|
||||
let mut tracks = Vec::new();
|
||||
// Empty sections take up height, but do not introduce cell spacing.
|
||||
// With such sections, explicit spacer tracks express the gaps before
|
||||
@@ -459,24 +515,23 @@ where
|
||||
return world.compute_child_layout(cell.to_taffy(), inputs);
|
||||
};
|
||||
set_block(mode, &mut inputs.parent_size, None);
|
||||
set_block(
|
||||
mode,
|
||||
&mut inputs.known_dimensions,
|
||||
layout.definite.then_some(layout.size),
|
||||
);
|
||||
set_block(mode, &mut inputs.known_dimensions, Some(layout.size));
|
||||
set_block(
|
||||
mode,
|
||||
&mut inputs.definite_dimensions,
|
||||
layout.definite.then_some(layout.size),
|
||||
);
|
||||
if !layout.definite {
|
||||
set_block(
|
||||
mode,
|
||||
&mut inputs.available_space,
|
||||
AvailableSpace::MaxContent,
|
||||
);
|
||||
}
|
||||
let mut output = world.compute_child_layout(cell.to_taffy(), inputs);
|
||||
// The used border box is always the absolute containing block, including
|
||||
// auto-height cells. Normal-flow percentages need a separate guarantee.
|
||||
let previous = world
|
||||
.table_cell_percentage_height
|
||||
.replace((cell, layout.definite));
|
||||
let mut output = if inputs.run_mode == RunMode::ComputeSize {
|
||||
world.measure_complete_layout(cell.to_taffy(), inputs)
|
||||
} else {
|
||||
world.compute_child_layout(cell.to_taffy(), inputs)
|
||||
};
|
||||
world.table_cell_percentage_height = previous;
|
||||
let free = (layout.size - mode.to_logical(output.size).block_size).max(0.0);
|
||||
let alignment = world.boxes[cell.index()].style.taffy.align_content;
|
||||
let offset = if layout.baseline.is_some() {
|
||||
@@ -509,11 +564,9 @@ where
|
||||
.resolve_or_zero(inputs.parent_size.width, resolve_stylo_calc_value);
|
||||
// The natural border box includes empty block children whose
|
||||
// overflow content size can be zero (for example an empty group).
|
||||
(output.size.height - padding.bottom - border.bottom).max(0.0)
|
||||
} else if inputs.run_mode == RunMode::PerformLayout {
|
||||
(output.content_size.height - padding.bottom).max(0.0)
|
||||
(layout.natural_size - padding.bottom - border.bottom).max(0.0)
|
||||
} else {
|
||||
layout.baseline.unwrap_or(0.0)
|
||||
(output.content_size.height - padding.bottom).max(0.0)
|
||||
});
|
||||
}
|
||||
output.content_size.height += offset;
|
||||
|
||||
@@ -33,6 +33,9 @@ use crate::{
|
||||
world::InlineStaticPosition,
|
||||
};
|
||||
|
||||
mod measurement;
|
||||
use measurement::FullMeasurementTree;
|
||||
|
||||
pub(crate) struct PreparedWorldLayout {
|
||||
positioned_static_sources: Vec<PositionedStaticSource>,
|
||||
numeric_unrounded_layouts: Vec<Layout>,
|
||||
@@ -1684,6 +1687,9 @@ where
|
||||
.store(inputs, output);
|
||||
return output;
|
||||
}
|
||||
if self.measure_baselines && inputs.run_mode == RunMode::ComputeSize {
|
||||
return self.compute_child_layout_uncached(node_id, inputs, None);
|
||||
}
|
||||
compute_cached_layout(self, node_id, inputs, |world, node_id, inputs| {
|
||||
world.compute_child_layout_uncached(node_id, inputs, None)
|
||||
})
|
||||
@@ -1749,6 +1755,17 @@ where
|
||||
self.get_core_container_style(child_node_id)
|
||||
}
|
||||
|
||||
fn get_block_percentage_resolution_height(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
height: Option<f32>,
|
||||
) -> Option<f32> {
|
||||
match self.table_cell_percentage_height {
|
||||
Some((cell, false)) if cell.to_taffy() == node_id => None,
|
||||
_ => height,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_block_child_layout(
|
||||
&mut self,
|
||||
node_id: NodeId,
|
||||
@@ -1869,6 +1886,19 @@ impl<N> LayoutWorld<N>
|
||||
where
|
||||
N: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Obtain baselines and content bounds without publishing numeric or paint layout.
|
||||
pub(crate) fn measure_complete_layout(
|
||||
&mut self,
|
||||
node_id: NodeId,
|
||||
inputs: LayoutInput,
|
||||
) -> LayoutOutput {
|
||||
debug_assert_eq!(inputs.run_mode, RunMode::ComputeSize);
|
||||
let previous = std::mem::replace(&mut self.measure_baselines, true);
|
||||
let output = self.compute_child_layout(node_id, inputs);
|
||||
self.measure_baselines = previous;
|
||||
output
|
||||
}
|
||||
|
||||
fn should_hide(&self, node_id: NodeId, inputs: LayoutInput) -> bool {
|
||||
inputs.run_mode == RunMode::PerformHiddenLayout
|
||||
|| self.boxes[LayoutBoxId::from_taffy(node_id).index()]
|
||||
@@ -1908,6 +1938,29 @@ where
|
||||
return self.compute_inline_formatting_context(id, inputs, block_context);
|
||||
}
|
||||
|
||||
if self.measure_baselines
|
||||
&& inputs.run_mode == RunMode::ComputeSize
|
||||
&& !matches!(
|
||||
kind,
|
||||
LayoutBoxKind::TableWrapper
|
||||
| LayoutBoxKind::InlineTableWrapper
|
||||
| LayoutBoxKind::AnonymousTableWrapper
|
||||
)
|
||||
{
|
||||
let mut tree = FullMeasurementTree(self);
|
||||
let full_inputs = LayoutInput {
|
||||
run_mode: RunMode::PerformLayout,
|
||||
..inputs
|
||||
};
|
||||
return if display.is_flex_container() {
|
||||
compute_flexbox_layout(&mut tree, node_id, full_inputs)
|
||||
} else if display.is_grid_container() {
|
||||
compute_grid_layout(&mut tree, node_id, full_inputs)
|
||||
} else {
|
||||
compute_block_layout(&mut tree, node_id, full_inputs, block_context)
|
||||
};
|
||||
}
|
||||
|
||||
// Pseudo origins retain a pseudo-specific box kind, so their computed
|
||||
// display cannot be recovered from the kind. Dispatch their formatting
|
||||
// context exactly like a principal box. Table remains the explicit
|
||||
@@ -2059,6 +2112,17 @@ where
|
||||
} else {
|
||||
inputs
|
||||
};
|
||||
// The leaf adapter's size-only shortcut drops the line baselines.
|
||||
// Only the adapter needs full measurement: the original inputs still
|
||||
// keep Parley on its measurement path without final paint fragments.
|
||||
let leaf_inputs = if self.measure_baselines && inputs.run_mode == RunMode::ComputeSize {
|
||||
LayoutInput {
|
||||
run_mode: RunMode::PerformLayout,
|
||||
..leaf_inputs
|
||||
}
|
||||
} else {
|
||||
leaf_inputs
|
||||
};
|
||||
let alignment = self.boxes[id.index()].style.text_align();
|
||||
let mut inline_context = self.boxes[id.index()]
|
||||
.inline_layout
|
||||
@@ -2218,7 +2282,7 @@ where
|
||||
reset_inline_layout_for_probe(layout);
|
||||
|
||||
let parent_writing_mode = self.boxes[owner.index()].style.writing_mode();
|
||||
let child_inputs = LayoutInput {
|
||||
let mut child_inputs = LayoutInput {
|
||||
run_mode: inputs.run_mode,
|
||||
sizing_mode: SizingMode::InherentSize,
|
||||
sizing_purpose: inputs.sizing_purpose,
|
||||
@@ -2231,6 +2295,9 @@ where
|
||||
block_auto_behavior: AutoSizeBehavior::FitContent,
|
||||
vertical_margins_are_collapsible: Line::FALSE,
|
||||
};
|
||||
if self.table_cell_percentage_height == Some((owner, false)) {
|
||||
child_inputs.parent_size.height = None;
|
||||
}
|
||||
// A float's max-content contribution is measured independently from
|
||||
// the finite line slot it will eventually occupy. Final fit-content
|
||||
// layout still uses the IFC owner's content width; it must not use
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
//! Retain the complete numeric result of Taffy's algorithms during measurement.
|
||||
//!
|
||||
//! Their size-only shortcuts may omit baselines and content bounds. Run the
|
||||
//! numeric algorithm through its final phase, but discard layout writes and keep
|
||||
//! descendant callbacks in measurement mode. In particular, Parley and nested
|
||||
//! tables must not publish fragments or structural geometry during this probe.
|
||||
use super::*;
|
||||
|
||||
pub(super) struct FullMeasurementTree<'a, N: Copy + Debug + Eq + Hash>(pub &'a mut LayoutWorld<N>);
|
||||
|
||||
impl<N: Copy + Debug + Eq + Hash> TraversePartialTree for FullMeasurementTree<'_, N> {
|
||||
type ChildIter<'a>
|
||||
= ChildIter<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
|
||||
self.0.child_ids(node_id)
|
||||
}
|
||||
|
||||
fn child_count(&self, node_id: NodeId) -> usize {
|
||||
self.0.child_count(node_id)
|
||||
}
|
||||
|
||||
fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
|
||||
self.0.get_child_id(node_id, index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Copy + Debug + Eq + Hash> LayoutPartialTree for FullMeasurementTree<'_, N> {
|
||||
type CoreContainerStyle<'a>
|
||||
= &'a Style<Atom>
|
||||
where
|
||||
Self: 'a;
|
||||
type CustomIdent = Atom;
|
||||
|
||||
fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_> {
|
||||
self.0.get_core_container_style(node_id)
|
||||
}
|
||||
|
||||
fn get_writing_mode(&self, node_id: NodeId) -> taffy::WritingMode {
|
||||
self.0.get_writing_mode(node_id)
|
||||
}
|
||||
|
||||
fn get_scrollbar_insets(&self, node_id: NodeId) -> taffy::Rect<f32> {
|
||||
self.0.get_scrollbar_insets(node_id)
|
||||
}
|
||||
|
||||
fn get_resolved_aspect_ratio(&self, node_id: NodeId) -> Option<taffy::ResolvedAspectRatio> {
|
||||
self.0.get_resolved_aspect_ratio(node_id)
|
||||
}
|
||||
|
||||
fn resolve_calc_value(&self, value: *const (), basis: f32) -> f32 {
|
||||
self.0.resolve_calc_value(value, basis)
|
||||
}
|
||||
|
||||
fn set_unrounded_layout(&mut self, _: NodeId, _: &Layout) {}
|
||||
|
||||
fn compute_child_layout(&mut self, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
|
||||
if self.0.should_hide(node_id, inputs) {
|
||||
return LayoutOutput::HIDDEN;
|
||||
}
|
||||
self.0.compute_child_layout(
|
||||
node_id,
|
||||
LayoutInput {
|
||||
run_mode: RunMode::ComputeSize,
|
||||
..inputs
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn compute_child_size(
|
||||
&mut self,
|
||||
node_id: NodeId,
|
||||
inputs: LayoutInput,
|
||||
) -> taffy::IntrinsicSizeResult {
|
||||
// Intrinsic probes consume only size and provenance, so they can keep
|
||||
// the compact cache and the algorithms' ordinary size-only shortcuts.
|
||||
let previous = std::mem::replace(&mut self.0.measure_baselines, false);
|
||||
let output = self.0.compute_child_size(node_id, inputs);
|
||||
self.0.measure_baselines = previous;
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Copy + Debug + Eq + Hash> LayoutBlockContainer for FullMeasurementTree<'_, N> {
|
||||
type BlockContainerStyle<'a>
|
||||
= &'a Style<Atom>
|
||||
where
|
||||
Self: 'a;
|
||||
type BlockItemStyle<'a>
|
||||
= &'a Style<Atom>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
|
||||
self.0.get_block_container_style(node_id)
|
||||
}
|
||||
|
||||
fn get_block_child_style(&self, node_id: NodeId) -> Self::BlockItemStyle<'_> {
|
||||
self.0.get_block_child_style(node_id)
|
||||
}
|
||||
|
||||
fn get_block_percentage_resolution_height(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
height: Option<f32>,
|
||||
) -> Option<f32> {
|
||||
self.0
|
||||
.get_block_percentage_resolution_height(node_id, height)
|
||||
}
|
||||
|
||||
fn compute_block_child_layout(
|
||||
&mut self,
|
||||
node_id: NodeId,
|
||||
inputs: LayoutInput,
|
||||
block_context: Option<&mut BlockContext<'_>>,
|
||||
) -> LayoutOutput {
|
||||
if self.0.should_hide(node_id, inputs) {
|
||||
return LayoutOutput::HIDDEN;
|
||||
}
|
||||
self.0.compute_block_child_layout(
|
||||
node_id,
|
||||
LayoutInput {
|
||||
run_mode: RunMode::ComputeSize,
|
||||
..inputs
|
||||
},
|
||||
block_context,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Copy + Debug + Eq + Hash> LayoutFlexboxContainer for FullMeasurementTree<'_, N> {
|
||||
type FlexboxContainerStyle<'a>
|
||||
= &'a Style<Atom>
|
||||
where
|
||||
Self: 'a;
|
||||
type FlexboxItemStyle<'a>
|
||||
= &'a Style<Atom>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
|
||||
self.0.get_flexbox_container_style(node_id)
|
||||
}
|
||||
|
||||
fn get_flexbox_child_style(&self, node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
|
||||
self.0.get_flexbox_child_style(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Copy + Debug + Eq + Hash> LayoutGridContainer for FullMeasurementTree<'_, N> {
|
||||
type GridContainerStyle<'a>
|
||||
= &'a Style<Atom>
|
||||
where
|
||||
Self: 'a;
|
||||
type GridItemStyle<'a>
|
||||
= &'a Style<Atom>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
|
||||
self.0.get_grid_container_style(node_id)
|
||||
}
|
||||
|
||||
fn get_grid_child_style(&self, node_id: NodeId) -> Self::GridItemStyle<'_> {
|
||||
self.0.get_grid_child_style(node_id)
|
||||
}
|
||||
|
||||
fn set_detailed_grid_info(&mut self, _: NodeId, _: DetailedGridInfo) {}
|
||||
}
|
||||
@@ -609,6 +609,10 @@ where
|
||||
pub(crate) viewport_scroll_policy: ViewportScrollPolicy,
|
||||
pub(crate) viewport_layout: ViewportLayoutState,
|
||||
pub(crate) css_image_references: Vec<LayoutCssImageReference<N>>,
|
||||
/// Full numeric measurement retains baselines without publishing layouts.
|
||||
pub(crate) measure_baselines: bool,
|
||||
/// The active table cell's used height need not be a percentage guarantee.
|
||||
pub(crate) table_cell_percentage_height: Option<(LayoutBoxId, bool)>,
|
||||
numeric_layout_tracking: bool,
|
||||
numeric_layout_touched: Vec<LayoutBoxId>,
|
||||
numeric_layout_touched_marks: Vec<bool>,
|
||||
@@ -628,6 +632,8 @@ where
|
||||
viewport_scroll_policy: ViewportScrollPolicy::default(),
|
||||
viewport_layout: ViewportLayoutState::default(),
|
||||
css_image_references: Vec::new(),
|
||||
measure_baselines: false,
|
||||
table_cell_percentage_height: None,
|
||||
numeric_layout_tracking: false,
|
||||
numeric_layout_touched: Vec::new(),
|
||||
numeric_layout_touched_marks: vec![false],
|
||||
|
||||
@@ -2872,6 +2872,84 @@ async fn screenshot_preserves_table_cell_dimension_hints_and_avatar_columns() {
|
||||
.expect("table-cell dimension hints should preserve avatar alignment");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn table_cell_absolute_descendants_use_final_geometry() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default())?;
|
||||
let mut page = test_page_vm_with_loader_and_document_url(
|
||||
&loader,
|
||||
Vec::new(),
|
||||
Url::parse("https://example.com/table-cell-absolute.html")?,
|
||||
);
|
||||
page.vm_mut().set_layout_policy(moli_page_types::LayoutPolicy::OnDemand);
|
||||
page.vm_mut().eval(r#"
|
||||
document.head.innerHTML = `<style>
|
||||
body { margin:0 } table { width:200px;border-spacing:0;table-layout:fixed }
|
||||
td { padding:0;font-size:0;line-height:0;vertical-align:top }
|
||||
</style>`;
|
||||
document.body.innerHTML = `<table><tr>
|
||||
<td id=cell style="position:relative">
|
||||
<div style="height:10px"></div><div id=inflow style="height:50%"></div>
|
||||
<div id=percent style="position:absolute;top:0;left:0;height:50%;width:10px"></div>
|
||||
<div id=fill style="position:absolute;inset:0"></div>
|
||||
</td><td><div id=tall style="height:100px"></div></td>
|
||||
</tr></table>`;
|
||||
"#)?;
|
||||
for height in [100, 160, 100] {
|
||||
page.vm_mut().eval(&format!("document.getElementById('tall').style.height='{height}px'"))?;
|
||||
for _ in 0..2 {
|
||||
page.vm_mut().screenshot_layout_snapshot(moli_layout::PaintViewport::new(320, 240, 1.0))?.expect("table root");
|
||||
let actual = page.vm_mut().eval(r#"
|
||||
['cell','inflow','percent','fill'].map(id => document.getElementById(id).getBoundingClientRect().height).join('|')
|
||||
"#)?;
|
||||
assert_eq!(actual, format!("{height}|0|{}|{height}", height / 2),
|
||||
"absolute descendants use the final cell box; normal-flow percentages remain indefinite");
|
||||
}
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}).await.expect("table cell absolute layout should match Chromium");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn table_cell_nested_percentage_baseline_reaches_parent_measurement() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default())?;
|
||||
let mut page = test_page_vm_with_loader_and_document_url(
|
||||
&loader,
|
||||
Vec::new(),
|
||||
Url::parse("https://example.com/table-cell-nested-baseline.html")?,
|
||||
);
|
||||
page.vm_mut().set_layout_policy(moli_page_types::LayoutPolicy::OnDemand);
|
||||
page.vm_mut().eval(r#"
|
||||
document.head.innerHTML = `<style>
|
||||
body { margin:0 } table { width:200px;border-spacing:0;table-layout:fixed }
|
||||
td { padding:0;font-size:0;line-height:0;vertical-align:baseline }
|
||||
</style>`;
|
||||
document.body.innerHTML = `<table id=outer><tr><td>
|
||||
<table id=inner style="width:100px"><tr>
|
||||
<td id=cell style="height:100px"><div id=percent style="height:50%;width:10px"></div></td>
|
||||
<td><div id=peer style="height:20px;width:10px"></div></td>
|
||||
</tr></table>
|
||||
</td><td><span id=reference style="display:inline-block;height:50px;width:10px"></span></td></tr></table>`;
|
||||
"#)?;
|
||||
for height in [100, 160, 100] {
|
||||
page.vm_mut().eval(&format!("document.getElementById('cell').style.height='{height}px'"))?;
|
||||
for _ in 0..2 {
|
||||
page.vm_mut().screenshot_layout_snapshot(moli_layout::PaintViewport::new(320, 240, 1.0))?.expect("nested table root");
|
||||
let actual = page.vm_mut().eval(r#"(() => {
|
||||
const rect = id => document.getElementById(id).getBoundingClientRect();
|
||||
const outer = rect('outer');
|
||||
return [outer.height,rect('inner').height,rect('percent').height,
|
||||
rect('peer').y-outer.y,rect('reference').y-outer.y].join('|');
|
||||
})()"#)?;
|
||||
assert_eq!(actual, format!("{height}|{height}|{}|{}|{}", height / 2, height / 2 - 20, height / 2 - 50),
|
||||
"outer row measurement must use the nested table's final percentage-dependent baseline");
|
||||
}
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}).await.expect("nested percentage baseline should match Chromium");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn screenshot_table_row_heights_match_chromium() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
Reference in New Issue
Block a user