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));
|
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")]
|
#[cfg(feature = "render")]
|
||||||
#[tokio::test(flavor = "current_thread")]
|
#[tokio::test(flavor = "current_thread")]
|
||||||
async fn rendered_window_scroll_events_require_actual_movement() {
|
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 right = viewport.0.max(0.0);
|
||||||
let mut bottom = viewport.1.max(0.0);
|
let mut bottom = viewport.1.max(0.0);
|
||||||
|
|
||||||
for (&id, rect) in &self.rects {
|
if let Some(root) = tree
|
||||||
if fixed.contains(&id) {
|
.descendants(tree.document())
|
||||||
continue;
|
.into_iter()
|
||||||
}
|
.find(|id| tree.get_node(*id).is_some_and(|node| node.is_element()))
|
||||||
let (tx, ty) = self.translates.get(&id).copied().unwrap_or((0.0, 0.0));
|
{
|
||||||
let mut painted = Rect {
|
accumulate_scrolling_overflow(
|
||||||
x: rect.x + tx,
|
tree,
|
||||||
y: rect.y + ty,
|
root,
|
||||||
width: rect.width,
|
None,
|
||||||
height: rect.height,
|
&fixed,
|
||||||
};
|
&self.rects,
|
||||||
if let Some(clip) = self.clip_rects.get(&id).copied().flatten() {
|
&self.styles,
|
||||||
let Some(clipped) = painted.intersect(&clip) else {
|
&self.translates,
|
||||||
continue;
|
&mut right,
|
||||||
};
|
&mut bottom,
|
||||||
painted = clipped;
|
);
|
||||||
}
|
|
||||||
right = right.max(painted.x + painted.width);
|
|
||||||
bottom = bottom.max(painted.y + painted.height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(right.ceil(), bottom.ceil())
|
(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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) enum GeneratedBoxKind {
|
pub(crate) enum GeneratedBoxKind {
|
||||||
Before,
|
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]
|
#[test]
|
||||||
fn size_container_auto_block_size_ignores_descendants() {
|
fn size_container_auto_block_size_ignores_descendants() {
|
||||||
let tree = parse_html(
|
let tree = parse_html(
|
||||||
|
|||||||
Reference in new issue
Block a user