Skip to content

Commit 9233783

Browse files
Implement border-shape hit testing
So far it's not yet specced if border-shape should be hit-testable outside border-box, so this CL only handles hit-testing inside of it and outside in cases that reach the box with border-shape (e.g. parent node doesn't stop hit-testing children). The spec discussion is at w3c/csswg-drafts#13371 The hit test of PaintLayer will be done in the follow-up and tracked in crbug.com/456675133 Bug: 370041145 Change-Id: I952b3af09d0e3f3c079bdd2938b58e83d44d89a0 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7510826 Reviewed-by: Philip Rogers <[email protected]> Commit-Queue: Daniil Sakhapov <[email protected]> Cr-Commit-Position: refs/heads/main@{#1576735}
1 parent 6e3fbc6 commit 9233783

File tree

5 files changed

+321
-0
lines changed

5 files changed

+321
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!DOCTYPE html>
2+
<title>CSS Borders Test: hit testing border-shape circle with overflow clip parent</title>
3+
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
<script src="/resources/testdriver.js"></script>
7+
<script src="/resources/testdriver-actions.js"></script>
8+
<script src="/resources/testdriver-vendor.js"></script>
9+
<style>
10+
#outer {
11+
width: 100px;
12+
height: 100px;
13+
overflow: clip;
14+
overflow-clip-margin: 50px;
15+
}
16+
#target {
17+
width: 100px;
18+
height: 100px;
19+
border-shape: circle(45px at 50% 50%);
20+
border: 10px solid purple;
21+
background: green;
22+
}
23+
</style>
24+
<div id="outer">
25+
<div id="target"></div>
26+
</div>
27+
<script>
28+
function getCenter(el) {
29+
const rect = el.getBoundingClientRect();
30+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
31+
}
32+
33+
function polarToXY(center, radius, angleRad) {
34+
return {
35+
x: Math.round(center.x + radius * Math.cos(angleRad)),
36+
y: Math.round(center.y + radius * Math.sin(angleRad))
37+
};
38+
}
39+
40+
promise_test(async t => {
41+
const target = document.getElementById('target');
42+
const center = getCenter(target);
43+
const strokeWidthHalf = 5; // 10px border-width
44+
const circleRadius = 45 + strokeWidthHalf;
45+
46+
// Check a point on the edge of the circle (should hit due to overflow-clip-margin).
47+
const { x, y } = polarToXY(center, circleRadius - 2, 0); // angle 0deg, right edge of the circle, -2px just to make it inside the border-shape contour.
48+
let hit = false;
49+
let hitBody = false;
50+
target.addEventListener('pointerdown', () => { hit = true; }, { once: true });
51+
document.body.addEventListener('pointerdown', () => { hitBody = true; }, { once: true });
52+
await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send();
53+
assert_true(hit, 'Point outside the clipped part should hit the border-shape due to overflow-clip-margin');
54+
});
55+
</script>
56+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<!DOCTYPE html>
2+
<title>CSS Borders Test: hit testing border-shape circle with overflow clip parent</title>
3+
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
<script src="/resources/testdriver.js"></script>
7+
<script src="/resources/testdriver-actions.js"></script>
8+
<script src="/resources/testdriver-vendor.js"></script>
9+
<style>
10+
#outer {
11+
width: 100px;
12+
height: 100px;
13+
overflow: clip;
14+
}
15+
#target {
16+
width: 100px;
17+
height: 100px;
18+
border-shape: circle(50% at 50% 50%);
19+
stroke: purple;
20+
stroke-width: 10px;
21+
background: green;
22+
}
23+
</style>
24+
<div id="outer">
25+
<div id="target"></div>
26+
</div>
27+
<script>
28+
function getCenter(el) {
29+
const rect = el.getBoundingClientRect();
30+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
31+
}
32+
33+
function polarToXY(center, radius, angleRad) {
34+
return {
35+
x: Math.round(center.x + radius * Math.cos(angleRad)),
36+
y: Math.round(center.y + radius * Math.sin(angleRad))
37+
};
38+
}
39+
40+
promise_test(async t => {
41+
const target = document.getElementById('target');
42+
const center = getCenter(target);
43+
const strokeWidthHalf = 5; // 10px stroke-width
44+
const circleRadius = 50 + strokeWidthHalf; // 100px box, circle(50%) => 50px radius
45+
46+
// Check a point on the edge of the circle (shouldn't hit as clipped).
47+
const { x, y } = polarToXY(center, circleRadius - 2, 0); // angle 0deg, right edge of the circle, -2px just to make it inside the border-shape contour.
48+
let hit = false;
49+
let hitBody = false;
50+
target.addEventListener('pointerdown', () => { hit = true; }, { once: true });
51+
document.body.addEventListener('pointerdown', () => { hitBody = true; }, { once: true });
52+
await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send();
53+
assert_false(hit, 'Point outside the clipped part should not hit the border-shape');
54+
assert_true(hitBody, 'Point outside the clipped part should hit body');
55+
});
56+
</script>
57+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
2+
<!DOCTYPE html>
3+
<title>CSS Borders Test: hit testing border-shape circle depth order with siblings</title>
4+
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
5+
<script src="/resources/testharness.js"></script>
6+
<script src="/resources/testharnessreport.js"></script>
7+
<script src="/resources/testdriver.js"></script>
8+
<script src="/resources/testdriver-actions.js"></script>
9+
<script src="/resources/testdriver-vendor.js"></script>
10+
<style>
11+
.container {
12+
position: relative;
13+
width: 200px;
14+
height: 100px;
15+
}
16+
.sibling {
17+
position: absolute;
18+
width: 100px;
19+
height: 100px;
20+
top: 0;
21+
left: 0;
22+
background: blue;
23+
opacity: 0.5;
24+
}
25+
#target {
26+
position: absolute;
27+
left: 50px;
28+
width: 80px;
29+
height: 80px;
30+
border-shape: circle(45px at 50% 50%);
31+
border: 10px solid purple;
32+
background: green;
33+
}
34+
.sibling2 {
35+
position: absolute;
36+
left: 100px;
37+
width: 100px;
38+
height: 100px;
39+
background: red;
40+
opacity: 0.5;
41+
}
42+
</style>
43+
<div class="container">
44+
<div class="sibling" id="before"></div>
45+
<div id="target"></div>
46+
<div class="sibling2" id="after"></div>
47+
</div>
48+
<script>
49+
function getRect(el) {
50+
return el.getBoundingClientRect();
51+
}
52+
function getCenter(el) {
53+
const r = getRect(el);
54+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
55+
}
56+
function polarToXY(center, radius, angleRad) {
57+
return {
58+
x: Math.round(center.x + radius * Math.cos(angleRad)),
59+
y: Math.round(center.y + radius * Math.sin(angleRad))
60+
};
61+
}
62+
let hit = null;
63+
function handler(e) { hit = e.currentTarget.id; }
64+
const before = document.getElementById('before');
65+
const target = document.getElementById('target');
66+
const after = document.getElementById('after');
67+
const center = getCenter(target);
68+
const strokeWidthHalf = 5; // 10px stroke-width
69+
const radius = 45 + strokeWidthHalf;
70+
promise_test(async t => {
71+
// Checking hit on upper left part of the circle border (should be before).
72+
hit = null;
73+
before.addEventListener('pointerdown', handler, { once: true });
74+
const { x, y } = polarToXY(center, radius + 1, (3 * Math.PI) / 4); // angle 135deg, top-left edge of the circle
75+
await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send();
76+
assert_equals(hit, 'before', 'Before sibling hit while not overlapping');
77+
});
78+
promise_test(async t => {
79+
// Checking hit on left part of the circle border (should be target).
80+
hit = null;
81+
target.addEventListener('pointerdown', handler, { once: true });
82+
const { x, y } = polarToXY(center, radius, Math.PI); // angle 180deg, left edge of the circle
83+
await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send();
84+
assert_equals(hit, 'target', 'Target has higher z-index than before sibling while overlapping');
85+
});
86+
promise_test(async t => {
87+
// Checking hit on right part of the circle (should be after).
88+
hit = null;
89+
after.addEventListener('pointerdown', handler, { once: true });
90+
const { x, y } = polarToXY(center, radius, 0); // angle 0deg, right edge of the circle
91+
await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send();
92+
assert_equals(hit, 'after', 'Target has lower z-index than after sibling while overlapping');
93+
});
94+
</script>
95+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!DOCTYPE html>
2+
<title>CSS Borders Test: hit testing border-shape circle</title>
3+
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
<script src="/resources/testdriver.js"></script>
7+
<script src="/resources/testdriver-actions.js"></script>
8+
<script src="/resources/testdriver-vendor.js"></script>
9+
<style>
10+
#target {
11+
width: 100px;
12+
height: 100px;
13+
border-shape: circle(45px at 50% 50%);
14+
border: 10px solid purple;
15+
background: green;
16+
}
17+
</style>
18+
<div id="target"></div>
19+
<script>
20+
function getCenter(el) {
21+
const rect = el.getBoundingClientRect();
22+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
23+
}
24+
25+
function polarToXY(center, radius, angleRad) {
26+
return {
27+
x: Math.round(center.x + radius * Math.cos(angleRad)),
28+
y: Math.round(center.y + radius * Math.sin(angleRad))
29+
};
30+
}
31+
32+
promise_test(async t => {
33+
const target = document.getElementById('target');
34+
const center = getCenter(target);
35+
const strokeWidthHalf = 5; // 10px border-width
36+
const circleRadius = 45 + strokeWidthHalf;
37+
38+
// Check points from center to the edge of the circle (should hit).
39+
for (let r = 0; r < circleRadius; r += 5) {
40+
let angle = Math.random() * 2 * Math.PI;
41+
const { x, y } = polarToXY(center, r, angle);
42+
let hit = false;
43+
target.addEventListener('pointerdown', () => { hit = true; }, { once: true });
44+
await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send();
45+
assert_true(hit, `Point at radius ${r} should hit the element`);
46+
}
47+
48+
// Check a point just outside the circle (should not hit).
49+
const { x: outX, y: outY } = polarToXY(center, circleRadius + 1, Math.PI / 4);
50+
let hit = false;
51+
target.addEventListener('pointerdown', () => { hit = true; }, { once: true });
52+
await new test_driver.Actions().pointerMove(outX, outY).pointerDown().pointerUp().send();
53+
assert_false(hit, 'Point outside the border-shape should not hit the element');
54+
});
55+
</script>
56+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<!DOCTYPE html>
2+
<title>CSS Borders Test: hit testing border-shape with overflow</title>
3+
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
<script src="/resources/testdriver.js"></script>
7+
<script src="/resources/testdriver-actions.js"></script>
8+
<script src="/resources/testdriver-vendor.js"></script>
9+
<style>
10+
#wrapper {
11+
width: 100px;
12+
height: 100px;
13+
}
14+
#bs-target {
15+
width: 200px;
16+
height: 200px;
17+
border-shape: circle(50% at 50% 50%);
18+
border: 20px solid purple;
19+
background: green;
20+
}
21+
22+
#bs-target:hover {
23+
border-color: orange;
24+
}
25+
26+
#overflower {
27+
width: 400px;
28+
height: 25px;
29+
background: lightblue;
30+
text-align: end;
31+
}
32+
</style>
33+
34+
border-shape:<br>
35+
<div id="wrapper">
36+
<div id="bs-target">
37+
<div id="overflower">hover here</div>
38+
</div>
39+
</div>
40+
<script>
41+
promise_test(async t => {
42+
let eventTarget;
43+
const target = document.getElementById('bs-target');
44+
const overflower = document.getElementById('overflower');
45+
const x = 350, y = 60;
46+
const rect = target.getBoundingClientRect();
47+
assert_false(
48+
x >= rect.left && x <= rect.right &&
49+
y >= rect.top && y <= rect.bottom,
50+
'Point should be outside the border-shape element');
51+
52+
target.addEventListener('mouseover', (e) => { eventTarget = e.target; }, { once: true });
53+
await new test_driver.Actions().pointerMove(x, y).send();
54+
assert_equals(eventTarget, overflower, 'Event target should be the overflowing element');
55+
});
56+
</script>
57+

0 commit comments

Comments
 (0)