Fix table cell baselines and float vertical alignment

This commit is contained in:
ldm0
2026-09-18 15:09:07 +08:00
parent ceeae06387
commit d66bc2167b
6 changed files with 126 additions and 39 deletions
Generated
+1 -1
View File
@@ -5155,7 +5155,7 @@ dependencies = [
[[package]]
name = "taffy"
version = "0.13.0"
source = "git+https://github.com/ldm0/taffy?rev=acc16f5b0d570656c504a44b52f1736f05991a40#acc16f5b0d570656c504a44b52f1736f05991a40"
source = "git+https://github.com/ldm0/taffy?rev=645bf5a49df607a8bff34ec90c5343901d04f10e#645bf5a49df607a8bff34ec90c5343901d04f10e"
dependencies = [
"arrayvec",
"serde",
+1 -1
View File
@@ -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 = "acc16f5b0d570656c504a44b52f1736f05991a40" }
taffy = { git = "https://github.com/ldm0/taffy", rev = "645bf5a49df607a8bff34ec90c5343901d04f10e" }
[workspace.lints.clippy]
disallowed_methods = "deny"
+6 -37
View File
@@ -241,7 +241,7 @@ impl TableContext {
let natural = mode.to_logical(output.size).block_size;
let cell = &mut self.cells[index];
let baseline = if mode.is_horizontal() {
output.first_baselines.y
output.first_baselines.y.or(output.block_content_end)
} else {
output.first_baselines.x
};
@@ -532,44 +532,13 @@ where
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() {
0.0
} else {
crate::taffy_tree::single_subject_block_alignment_offset(alignment, free)
};
if inputs.run_mode == RunMode::PerformLayout && offset != 0.0 {
shift_cell_contents(world, cell, offset);
}
if let Some(baseline) = &mut output.first_baselines.y {
*baseline += offset;
}
if let Some(baseline) = &mut output.last_baselines.y {
*baseline += offset;
}
if layout.baseline.is_some() && output.first_baselines.y.is_none() {
// Cells with no line baseline use the content's block-end edge. This
// must be sampled again after percentage descendants have laid out.
let padding = world.boxes[cell.index()]
.style
.taffy
.padding
.resolve_or_zero(inputs.parent_size.width, resolve_stylo_calc_value);
output.first_baselines.y = Some(if !layout.definite {
let border = world.boxes[cell.index()]
.style
.taffy
.border
.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).
(layout.natural_size - padding.bottom - border.bottom).max(0.0)
} else {
(output.content_size.height - padding.bottom).max(0.0)
});
// This is measured again with final percentage constraints, but before
// relative positioning or absolute descendants contribute overflow.
output.first_baselines.y = output.block_content_end;
}
output.content_size.height += offset;
// Block/inline layout has already aligned the content group inside this
// final geometry. Only baseline alignment needs a later table-wide shift.
set_block(mode, &mut output.size, layout.size);
output
}
+15
View File
@@ -1766,6 +1766,14 @@ where
}
}
fn block_alignment_includes_floats(&self, node_id: NodeId) -> bool {
!self.is_viewport_taffy_node(node_id)
&& matches!(
self.boxes[LayoutBoxId::from_taffy(node_id).index()].kind,
LayoutBoxKind::TableCell | LayoutBoxKind::AnonymousTableCell
)
}
fn compute_block_child_layout(
&mut self,
node_id: NodeId,
@@ -2202,6 +2210,13 @@ where
content_box_height - measurement.alignment_block_size,
);
measurement.translate_block_axis(block_offset);
output.block_content_end = Some(
measurement.alignment_block_size
+ padding.top
+ border.top
+ scrollbar_insets.top
+ block_offset,
);
output.content_size.height = output.content_size.height.max(
measurement.alignment_block_size
+ padding.top
@@ -110,6 +110,10 @@ impl<N: Copy + Debug + Eq + Hash> LayoutBlockContainer for FullMeasurementTree<'
.get_block_percentage_resolution_height(node_id, height)
}
fn block_alignment_includes_floats(&self, node_id: NodeId) -> bool {
self.0.block_alignment_includes_floats(node_id)
}
fn compute_block_child_layout(
&mut self,
node_id: NodeId,
@@ -2950,6 +2950,105 @@ return [outer.height,rect('inner').height,rect('percent').height,
}).await.expect("nested percentage baseline should match Chromium");
}
#[tokio::test(flavor = "current_thread")]
async fn table_cell_synthetic_baselines_ignore_positioned_overflow() {
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-flow-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><tr>
<td id=cell style="height:100px;position:relative">
<div id=content style="height:10px;width:10px"></div>
<div id=overlay style="position:absolute;inset:0"></div>
</td><td><span id=peer style="display:inline-block;height:20px;width:10px"></span></td>
</tr></table>`;
"#)?;
for relative in [false, true, false] {
for height in [100, 160, 100] {
page.vm_mut().eval(&format!(r#"
document.getElementById('cell').style.height='{height}px';
document.getElementById('overlay').style.display='{}';
document.getElementById('content').style.cssText='height:10px;width:10px;{}';
"#, if relative { "none" } else { "block" }, if relative { "position:relative;top:50%" } else { "" }))?;
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','peer','content','overlay'].map(id => {
const r=document.getElementById(id).getBoundingClientRect(); return [r.y,r.height].join(',');
}).join('|')
"#)?;
let content_y = 10 + if relative { height / 2 } else { 0 };
let overlay_height = if relative { 0 } else { height };
assert_eq!(actual, format!("0,{height}|0,20|{content_y},10|0,{overlay_height}"),
"relative={relative}: synthetic baselines use normal-flow geometry, while positioned descendants retain final cell geometry");
}
}
}
Ok::<_, anyhow::Error>(())
}).await.expect("positioned overflow must not move adjacent cell baselines");
}
#[tokio::test(flavor = "current_thread")]
async fn table_cell_vertical_alignment_includes_floats() {
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-float-alignment.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 }
</style>`;
document.body.innerHTML = `<table><tr>
<td id=cell style="position:relative">
<div id=float style="float:left;width:10px;height:30px"></div>
<div id=block style="margin-left:15px;width:10px;height:20px"></div>
<div id=overlay style="position:absolute;inset:0"></div>
</td><td><div id=tall></div></td></tr></table>`;
"#)?;
for align in ["middle", "bottom"] {
for mixed in [false, true] {
for padding in [0, 5] {
for height in [100, 160, 100] {
page.vm_mut().eval(&format!(r#"
document.getElementById('cell').style.cssText='position:relative;vertical-align:{align};padding:{padding}px';
document.getElementById('block').style.display='{}';
document.getElementById('tall').style.height='{height}px';
"#, if mixed { "block" } else { "none" }))?;
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','float','block','overlay'].map(id => {
const r=document.getElementById(id).getBoundingClientRect(); return [r.y,r.height].join(',');
}).join('|')
"#)?;
let free = height - 2 * padding - 30;
let y = padding + if align == "middle" { free / 2 } else { free };
let block = if mixed { format!("{y},20") } else { "0,0".to_owned() };
assert_eq!(actual, format!("0,{height}|{y},30|{block}|0,{height}"),
"{align}, mixed={mixed}, padding={padding}: align floats and normal flow together without shifting the absolute overlay");
}
}
}
}
}
Ok::<_, anyhow::Error>(())
}).await.expect("table-cell vertical alignment must include floats");
}
#[tokio::test(flavor = "current_thread")]
async fn screenshot_table_row_heights_match_chromium() {
run_page_vm_async_test(async move {