mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 09:45:06 +02:00
LibWeb: Prevent hit testing from transforming position more than once
The transform of each paintable was being applied multiple times due to the recursive nature of the hit testing methods. Previously it used combined_css_transform to transform the position, and then it would pass that position to children, which would then apply combined_css_transform again, and so on. PaintableBoxes are also not hit tested anymore when having a stacking context. A similar check is done in PaintableWithLines, but it was missing from PaintableBox. Without this check some elements can get returned multiple times from a hit test. StackingContexts with zero opacity will now also get hit tested, as it should have been before.
This commit is contained in:
Notes:
github-actions[bot]
2025-08-27 07:15:40 +00:00
Author: https://github.com/zacoons Commit: https://github.com/LadybirdBrowser/ladybird/commit/4070f5a7e03 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5681 Reviewed-by: https://github.com/gmta ✅
@@ -0,0 +1,153 @@
|
||||
<!DOCTYPE HTML>
|
||||
<title>CSS Test (Transforms): Hit Testing</title>
|
||||
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
|
||||
<link rel="author" title="Google" href="http://www.google.com/">
|
||||
<link rel="help" href="https://www.w3.org/TR/css-transforms-1/#transform-property">
|
||||
<link rel="help" href="https://www.w3.org/TR/css-transforms-2/#individual-transforms">
|
||||
<meta name="flags" content="dom">
|
||||
<style>
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
body { margin: 50px; }
|
||||
|
||||
</style>
|
||||
<script>
|
||||
|
||||
let body_x_margin = 50;
|
||||
let body_y_margin = 50;
|
||||
|
||||
</script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
|
||||
class Point {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
let simple_tests = [
|
||||
// In this list of tests, test_points_inside and test_points_outside
|
||||
// are relative to the element's untransformed origin (top, left).
|
||||
{
|
||||
description: "rectangle with 'translate' and 'rotate'",
|
||||
styles: "width: 100px; height: 50px; translate: 30px; rotate: 20deg;",
|
||||
test_points_inside: [
|
||||
new Point(28, 32),
|
||||
new Point(44, -10),
|
||||
new Point(133, 22),
|
||||
new Point(117, 64),
|
||||
new Point(65, 27),
|
||||
],
|
||||
test_points_outside: [
|
||||
new Point(30, 5),
|
||||
new Point(28, 37),
|
||||
new Point(100, 2),
|
||||
new Point(124, 58),
|
||||
],
|
||||
},
|
||||
{
|
||||
description: "rectangle with 'transform'",
|
||||
styles: "width: 100px; height: 50px; transform: translate(30px) rotate(20deg);",
|
||||
test_points_inside: [
|
||||
new Point(28, 32),
|
||||
new Point(44, -10),
|
||||
new Point(133, 22),
|
||||
new Point(117, 64),
|
||||
new Point(65, 27),
|
||||
],
|
||||
test_points_outside: [
|
||||
new Point(30, 5),
|
||||
new Point(28, 37),
|
||||
new Point(100, 2),
|
||||
new Point(124, 58),
|
||||
],
|
||||
},
|
||||
{
|
||||
description: "rectangle with 'translate' and 'rotate' and 'scale' and 'transform'",
|
||||
styles: "width: 100px; height: 50px; translate: 30px; rotate: 40deg; scale: 2; transform: rotate(-20deg) scale(0.5)",
|
||||
test_points_inside: [
|
||||
new Point(28, 32),
|
||||
new Point(44, -10),
|
||||
new Point(133, 22),
|
||||
new Point(117, 64),
|
||||
new Point(65, 27),
|
||||
],
|
||||
test_points_outside: [
|
||||
new Point(30, 5),
|
||||
new Point(28, 37),
|
||||
new Point(100, 2),
|
||||
new Point(124, 58),
|
||||
],
|
||||
},
|
||||
{
|
||||
description: "square with 'rotate'",
|
||||
styles: "width: 10px; height: 10px; rotate: 90deg; transform-origin: 0 10px",
|
||||
test_points_inside: [
|
||||
new Point(1, 11),
|
||||
new Point(9, 11),
|
||||
new Point(1, 19),
|
||||
new Point(9, 19),
|
||||
],
|
||||
test_points_outside: [
|
||||
new Point(1, 9),
|
||||
new Point(9, 9),
|
||||
],
|
||||
},
|
||||
{
|
||||
description: "square with 'scale'",
|
||||
styles: "width: 10px; height: 10px; scale: 0.2;",
|
||||
test_points_inside: [
|
||||
new Point(4, 4),
|
||||
new Point(5, 4),
|
||||
new Point(4, 5),
|
||||
new Point(5, 5),
|
||||
],
|
||||
test_points_outside: [
|
||||
new Point(3, 3),
|
||||
new Point(3, 5),
|
||||
new Point(3, 6),
|
||||
new Point(5, 3),
|
||||
new Point(5, 6),
|
||||
new Point(6, 3),
|
||||
new Point(6, 5),
|
||||
new Point(6, 6),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
for (let t of simple_tests) {
|
||||
test(function() {
|
||||
let e = document.createElement("div");
|
||||
e.setAttribute("style", t.styles);
|
||||
document.body.appendChild(e);
|
||||
|
||||
for (let p of t.test_points_inside) {
|
||||
let res = document.elementFromPoint(p.x + body_x_margin,
|
||||
p.y + body_y_margin);
|
||||
assert_equals(res, e,
|
||||
`point (${p.x}, ${p.y}) is inside element`);
|
||||
}
|
||||
|
||||
for (let p of t.test_points_outside) {
|
||||
let res = document.elementFromPoint(p.x + body_x_margin,
|
||||
p.y + body_y_margin);
|
||||
assert_equals(res, document.body,
|
||||
`point (${p.x}, ${p.y}) is outside element`);
|
||||
}
|
||||
|
||||
e.remove();
|
||||
}, `hit testing of ${t.description}`);
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user