fix(layout): preserve physical insets in reversed flex scrollers

This commit is contained in:
ldm0
2026-09-09 03:15:34 +08:00
parent 0b8e860d05
commit 428c5bd2f4
4 changed files with 104 additions and 12 deletions
Generated
+1 -1
View File
@@ -4842,7 +4842,7 @@ dependencies = [
[[package]]
name = "taffy"
version = "0.13.0"
source = "git+https://github.com/ldm0/taffy?rev=2dce934bd3ead3d4db934f6847c6e6b26565d9c7#2dce934bd3ead3d4db934f6847c6e6b26565d9c7"
source = "git+https://github.com/ldm0/taffy?rev=082327a5cb0d2ee1fed4c0ac5f339e1c1a51b576#082327a5cb0d2ee1fed4c0ac5f339e1c1a51b576"
dependencies = [
"arrayvec",
"serde",
+1 -1
View File
@@ -102,7 +102,7 @@ stylo_atoms = { git = "https://github.com/ldm0/stylo", rev = "e6cdff0a2eb7e51a31
stylo_dom = { git = "https://github.com/ldm0/stylo", rev = "e6cdff0a2eb7e51a313dabe8dc48a13a08314493" }
stylo_static_prefs = { git = "https://github.com/ldm0/stylo", rev = "e6cdff0a2eb7e51a313dabe8dc48a13a08314493" }
stylo_traits = { git = "https://github.com/ldm0/stylo", rev = "e6cdff0a2eb7e51a313dabe8dc48a13a08314493" }
taffy = { git = "https://github.com/ldm0/taffy", rev = "2dce934bd3ead3d4db934f6847c6e6b26565d9c7" }
taffy = { git = "https://github.com/ldm0/taffy", rev = "082327a5cb0d2ee1fed4c0ac5f339e1c1a51b576" }
[workspace.lints.clippy]
disallowed_methods = "deny"
@@ -2,27 +2,52 @@ use super::*;
#[tokio::test(flavor = "current_thread")]
async fn screenshot_resolves_flex_static_margin_boxes_across_containing_blocks() {
assert_flex_geometry_fixture(
include_str!("../../../../tests/fixtures/flex-static-position-margins.html"),
"collectFlexStaticMarginChecks()",
117,
6000,
)
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn screenshot_preserves_physical_absolute_insets_in_reversed_flex_scrollers() {
assert_flex_geometry_fixture(
include_str!("../../../../tests/fixtures/flex-physical-insets.html"),
"collectFlexPhysicalInsetChecks()",
96,
1260,
)
.await;
}
async fn assert_flex_geometry_fixture(
fixture: &'static str,
collect_checks: &'static str,
expected_count: usize,
viewport_height: u32,
) {
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/flex-static-position-margins.html")?,
Url::parse("https://example.com/flex-geometry.html")?,
);
page.set_viewport_surface(Some(crate::protocol_types::ViewportSurface {
inner_width: 1280,
inner_height: 6000,
inner_height: viewport_height,
outer_width: 1280,
outer_height: 6000,
outer_height: viewport_height,
device_pixel_ratio: 1.0,
screen_width: 1280,
screen_height: 6000,
screen_height: viewport_height,
screen_avail_width: 1280,
screen_avail_height: 6000,
screen_avail_height: viewport_height,
}))?;
page.vm_mut()
.set_layout_policy(moli_page_types::LayoutPolicy::OnDemand);
let fixture = include_str!("../../../../tests/fixtures/flex-static-position-margins.html");
page.vm_mut().eval(&format!(
"document.open();document.write({});document.close()",
serde_json::to_string(fixture)?
@@ -40,10 +65,10 @@ async fn screenshot_resolves_flex_static_margin_boxes_across_containing_blocks()
let checks: serde_json::Value = serde_json::from_str(
&page
.vm_mut()
.eval("JSON.stringify(collectFlexStaticMarginChecks())")?,
.eval(&format!("JSON.stringify({collect_checks})"))?,
)?;
let checks = checks.as_array().expect("flex static margin checks");
assert_eq!(checks.len(), 117);
let checks = checks.as_array().expect("flex geometry checks");
assert_eq!(checks.len(), expected_count);
for check in checks {
assert_eq!(check["actual"], check["expected"], "{check}");
let x = check["pixel"][0].as_f64().expect("pixel x") as usize;
@@ -59,5 +84,5 @@ async fn screenshot_resolves_flex_static_margin_boxes_across_containing_blocks()
Ok::<_, anyhow::Error>(())
})
.await
.expect("flex static-position margin fixture should run");
.expect("flex geometry fixture should run");
}
@@ -0,0 +1,67 @@
<!doctype html>
<meta charset="utf-8">
<title>Physical absolute insets in reversed flex scroll containers</title>
<link rel="help" href="https://drafts.csswg.org/css-position-3/#insets">
<style>
html, body { margin: 0; padding: 0; overflow: hidden; }
.container {
position: absolute;
display: flex;
box-sizing: border-box;
width: 200px;
height: 160px;
border-style: solid;
border-width: 2px 5px 4px 3px;
padding: 3px 5px 7px 9px;
overflow: scroll;
font: 0/0 sans-serif;
}
.item { position: absolute; background: rgb(31, 127, 63); }
</style>
<body>
<script>
const cases = [
{id:'near', style:'left:11px;top:7px;width:40px;height:20px;margin:2px 8px 6px 4px', expected:[18,11,40,20]},
{id:'far', style:'right:13px;bottom:9px;width:40px;height:20px;margin:2px 8px 6px 4px', expected:[119,106,40,20]},
{id:'percentage-insets', style:'left:25%;top:25%;width:40px;height:20px;margin:2px 8px 6px 4px', expected:[51.25,38.75,40,20]},
{id:'percentage-size', style:'left:0;top:0;width:100%;height:100%', expected:[3,2,177,139]},
];
let index = 0;
for (const direction of ['ltr', 'rtl']) {
for (const flexDirection of ['row', 'row-reverse', 'column', 'column-reverse']) {
for (const flexWrap of ['nowrap', 'wrap', 'wrap-reverse']) {
const container = document.createElement('div');
container.className = 'container';
container.id = `${direction}-${flexDirection}-${flexWrap}`;
container.style.cssText = `left:${(index % 5) * 240}px;top:${Math.floor(index / 5) * 210}px;direction:${direction};flex-direction:${flexDirection};flex-wrap:${flexWrap}`;
for (const test of cases) {
const item = document.createElement('div');
item.className = 'item';
item.id = `${container.id}-${test.id}`;
item.style.cssText = test.style;
// Classic scrollbars reserve 15px. RTL moves the vertical gutter
// to the left; reversing flex axes never moves either gutter.
const expected = test.expected.slice();
if (direction === 'rtl') expected[0] += 15;
item.dataset.expected = JSON.stringify(expected);
container.append(item);
}
document.body.append(container);
index++;
}
}
}
function collectFlexPhysicalInsetChecks() {
return Array.from(document.querySelectorAll('.item'), item => {
const origin = item.parentElement.getBoundingClientRect();
const rect = item.getBoundingClientRect();
const expected = JSON.parse(item.dataset.expected);
return {
id: item.id,
actual: [rect.x - origin.x, rect.y - origin.y, rect.width, rect.height],
expected,
pixel: [origin.x + expected[0] + 2, origin.y + expected[1] + 2],
};
});
}
</script>