Preserve root scrolling overflow geometry
This commit is contained in:
1 parent
3822542e50
commit
139d4e77e1
2 files changed
+148
-19
No files matched your search
@@ -3194,6 +3194,43 @@ mod tests {
|
||||
assert_eq!(after[2].as_f64(), Some(80.0));
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
#[test]
|
||||
fn root_overflow_clip_preserves_cssom_scroll_range() {
|
||||
let dom = parse_html(
|
||||
r#"<html style="margin:0;height:100%;overflow:hidden">
|
||||
<body style="margin:0;height:100%;overflow:hidden">
|
||||
<main id="main" style="padding-top:48px">
|
||||
<div style="height:5000px"></div>
|
||||
</main>
|
||||
</body>
|
||||
</html>"#,
|
||||
);
|
||||
let mut rt = ObscuraJsRuntime::new();
|
||||
rt.set_dom(dom);
|
||||
rt.set_viewport(900.0, 1000.0);
|
||||
rt.run_page_init();
|
||||
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
const main = document.getElementById("main");
|
||||
const before = main.getBoundingClientRect();
|
||||
scrollTo(0, 99999);
|
||||
const after = main.getBoundingClientRect();
|
||||
return [
|
||||
before.height,
|
||||
document.documentElement.scrollHeight,
|
||||
document.body.scrollHeight,
|
||||
scrollY,
|
||||
after.top,
|
||||
];
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result, serde_json::json!([5048, 5048, 5048, 4048, -4048]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn rendered_window_scroll_events_require_actual_movement() {
|
||||
|
||||
@@ -285,25 +285,22 @@ impl DomLayout {
|
||||
let mut right = viewport.0.max(0.0);
|
||||
let mut bottom = viewport.1.max(0.0);
|
||||
|
||||
for (&id, rect) in &self.rects {
|
||||
if fixed.contains(&id) {
|
||||
continue;
|
||||
}
|
||||
let (tx, ty) = self.translates.get(&id).copied().unwrap_or((0.0, 0.0));
|
||||
let mut painted = Rect {
|
||||
x: rect.x + tx,
|
||||
y: rect.y + ty,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
};
|
||||
if let Some(clip) = self.clip_rects.get(&id).copied().flatten() {
|
||||
let Some(clipped) = painted.intersect(&clip) else {
|
||||
continue;
|
||||
};
|
||||
painted = clipped;
|
||||
}
|
||||
right = right.max(painted.x + painted.width);
|
||||
bottom = bottom.max(painted.y + painted.height);
|
||||
if let Some(root) = tree
|
||||
.descendants(tree.document())
|
||||
.into_iter()
|
||||
.find(|id| tree.get_node(*id).is_some_and(|node| node.is_element()))
|
||||
{
|
||||
accumulate_scrolling_overflow(
|
||||
tree,
|
||||
root,
|
||||
None,
|
||||
&fixed,
|
||||
&self.rects,
|
||||
&self.styles,
|
||||
&self.translates,
|
||||
&mut right,
|
||||
&mut bottom,
|
||||
);
|
||||
}
|
||||
|
||||
(right.ceil(), bottom.ceil())
|
||||
@@ -458,6 +455,69 @@ impl DomLayout {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn accumulate_scrolling_overflow(
|
||||
tree: &DomTree,
|
||||
id: NodeId,
|
||||
inherited_clip: Option<Rect>,
|
||||
fixed: &HashSet<NodeId>,
|
||||
rects: &HashMap<NodeId, Rect>,
|
||||
styles: &HashMap<NodeId, crate::LayoutStyle>,
|
||||
translates: &HashMap<NodeId, (f32, f32)>,
|
||||
right: &mut f32,
|
||||
bottom: &mut f32,
|
||||
) {
|
||||
if fixed.contains(&id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let translated = rects.get(&id).map(|rect| {
|
||||
let (tx, ty) = translates.get(&id).copied().unwrap_or((0.0, 0.0));
|
||||
Rect {
|
||||
x: rect.x + tx,
|
||||
y: rect.y + ty,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
});
|
||||
if let Some(overflow) = translated {
|
||||
let visible = if let Some(clip) = inherited_clip {
|
||||
overflow.intersect(&clip)
|
||||
} else {
|
||||
Some(overflow)
|
||||
};
|
||||
if let Some(overflow) = visible {
|
||||
*right = right.max(overflow.x + overflow.width);
|
||||
*bottom = bottom.max(overflow.y + overflow.height);
|
||||
}
|
||||
}
|
||||
|
||||
// Overflow propagated from html/body establishes the viewport clip for
|
||||
// painting, but it does not truncate the root scrolling area's CSSOM
|
||||
// overflow dimensions. Ordinary descendant overflow clips still bound
|
||||
// their subtree's contribution.
|
||||
let is_viewport_overflow_source = tree.get_node(id).is_some_and(|node| {
|
||||
node.as_element()
|
||||
.is_some_and(|element| matches!(element.local.as_ref(), "html" | "body"))
|
||||
});
|
||||
let child_clip = match (styles.get(&id), rects.get(&id)) {
|
||||
(Some(style), Some(rect)) if style.overflow_hidden && !is_viewport_overflow_source => {
|
||||
let (tx, ty) = translates.get(&id).copied().unwrap_or((0.0, 0.0));
|
||||
let own = overflow_clip_rect(rect, style, tx, ty);
|
||||
Some(match inherited_clip {
|
||||
Some(clip) => clip.intersect(&own).unwrap_or(Rect::default()),
|
||||
None => own,
|
||||
})
|
||||
}
|
||||
_ => inherited_clip,
|
||||
};
|
||||
for child in tree.children(id) {
|
||||
accumulate_scrolling_overflow(
|
||||
tree, child, child_clip, fixed, rects, styles, translates, right, bottom,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum GeneratedBoxKind {
|
||||
Before,
|
||||
@@ -8164,6 +8224,38 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn viewport_overflow_clip_does_not_truncate_root_scrolling_overflow() {
|
||||
let tree = parse_html(
|
||||
r#"<style>
|
||||
html,body{margin:0;height:100%;overflow:hidden}
|
||||
main{padding-top:48px}
|
||||
#long{height:5000px}
|
||||
</style><main id="main"><div id="long"></div></main>"#,
|
||||
);
|
||||
let laid = layout_dom(&tree, (900.0, 1000.0));
|
||||
let main = laid.rects[&tree.get_element_by_id("main").unwrap()];
|
||||
let content = laid.scrolling_content_size(&tree, (900.0, 1000.0));
|
||||
assert!((main.height - 5048.0).abs() < 0.01, "{main:?}");
|
||||
assert_eq!(content, (900.0, 5048.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn descendant_overflow_clip_still_bounds_root_scrolling_overflow() {
|
||||
let tree = parse_html(
|
||||
r#"<style>
|
||||
html,body{margin:0}
|
||||
#clip{height:100px;overflow:hidden}
|
||||
#long{height:5000px}
|
||||
</style><div id="clip"><div id="long"></div></div>"#,
|
||||
);
|
||||
let laid = layout_dom(&tree, (900.0, 1000.0));
|
||||
assert_eq!(
|
||||
laid.scrolling_content_size(&tree, (900.0, 1000.0)),
|
||||
(900.0, 1000.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_container_auto_block_size_ignores_descendants() {
|
||||
let tree = parse_html(
|
||||
|
||||
Reference in new issue
Block a user