layout: Gracefully handle script queries on nodes with uninvertible transforms (#39075)

Instead of panicking when doing a geometry script query on a node with
an uninvertible transform, return a zero-sized rectangle at the
untransformed position. This is similar to what Gecko and Blink do
(though it seems there are some differences in positioning this
zero-sized rectangle). Mostly importantly, do not panic.

Testing: This change adds a new WPT crash test.
Fixes: #38848.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-09-02 06:27:43 -07:00 committed by GitHub
parent 4de84b0ca5
commit efe9ea2306
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 53 additions and 6 deletions

View file

@ -1009,6 +1009,7 @@ impl BoxFragment {
// > If a transform function causes the current transformation matrix of an object // > If a transform function causes the current transformation matrix of an object
// > to be non-invertible, the object and its content do not get displayed. // > to be non-invertible, the object and its content do not get displayed.
if !reference_frame_data.transform.is_invertible() { if !reference_frame_data.transform.is_invertible() {
self.clear_spatial_tree_node_including_descendants();
return; return;
} }
@ -1705,6 +1706,29 @@ impl BoxFragment {
Perspective::None => None, Perspective::None => None,
} }
} }
fn clear_spatial_tree_node_including_descendants(&self) {
fn assign_spatial_tree_node_on_fragments(fragments: &[Fragment]) {
for fragment in fragments.iter() {
match fragment {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
box_fragment
.borrow()
.clear_spatial_tree_node_including_descendants();
},
Fragment::Positioning(positioning_fragment) => {
assign_spatial_tree_node_on_fragments(
&positioning_fragment.borrow().children,
);
},
_ => {},
}
}
}
*self.spatial_tree_node.borrow_mut() = None;
assign_spatial_tree_node_on_fragments(&self.children);
}
} }
impl PositioningFragment { impl PositioningFragment {

View file

@ -60,10 +60,8 @@ fn root_transform_for_layout_node(
.first() .first()
.and_then(Fragment::retrieve_box_fragment)? .and_then(Fragment::retrieve_box_fragment)?
.borrow(); .borrow();
let scroll_tree_node_id = box_fragment let scroll_tree_node_id = box_fragment.spatial_tree_node.borrow();
.spatial_tree_node let scroll_tree_node_id = (*scroll_tree_node_id)?;
.borrow()
.expect("Should always have a scroll tree node when querying bounding box.");
Some(scroll_tree.cumulative_node_to_root_transform(&scroll_tree_node_id)) Some(scroll_tree.cumulative_node_to_root_transform(&scroll_tree_node_id))
} }
@ -87,7 +85,7 @@ pub(crate) fn process_box_area_request(
let Some(transform) = let Some(transform) =
root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node) root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
else { else {
return Some(rect_union); return Some(Rect::new(rect_union.origin, Size2D::zero()));
}; };
transform_au_rectangle(rect_union, transform) transform_au_rectangle(rect_union, transform)
@ -107,7 +105,9 @@ pub(crate) fn process_box_areas_request(
let Some(transform) = let Some(transform) =
root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node) root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
else { else {
return box_areas.collect(); return box_areas
.map(|rect| Rect::new(rect.origin, Size2D::zero()))
.collect();
}; };
box_areas box_areas

View file

@ -5819,6 +5819,13 @@
{} {}
] ]
], ],
"uninvertible-transform-and-script-queries.html": [
"8a3c88fcb8ade4038ba47a347cf3a5375d521bd4",
[
null,
{}
]
],
"w-crossing-zero-001.html": [ "w-crossing-zero-001.html": [
"64ba0e85f24d501e4115d7e697052acb3a84f27a", "64ba0e85f24d501e4115d7e697052acb3a84f27a",
[ [

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<link rel="help" href="https://github.com/servo/servo/issues/38848">
<div style="transform: scale(0)">
<img style="height: 200px; width: 200px; border: solid;" id="img">
<div style="height: 200px; width: 200px; border: solid;" id="div"></div>
</div>
<script>
div.getBoundingClientRect();
div.getClientRects();
img.getBoundingClientRect();
img.getClientRects();
img.height;
img.width;
</script>