tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c9a84948bfd20daf2c9b3e3b7dc3ab9948506421
parent 1088cf8db2287670a01b1581de66257a523d013f
Author: Daniil Sakhapov <sakhapov@chromium.org>
Date:   Thu, 27 Nov 2025 15:27:41 +0000

Bug 2002580 [wpt PR 56299] - Implement border-shape hit testing, a=testonly

Automatic update from web-platform-tests
Implement border-shape hit testing

So far it's specified not to affect layout, but we want it to be
hit-testable. Existing hit-testing code normally does a simple border
box check first before going into any clipping details inside, but,
since the border-shape can be painted outside the border box, we need
to check its border-shape bounding box for hit-testing.

To do so, and also not to lose the border when the shape is painted
outside the viewport, this CL computes scrollable (layout) overflow
and uses that box for hit-testing purposes. That box is propagated up,
meaning all the ancestors will now account for scrollable overflow
of a descendant element with border-shape.

The hit test of PaintLayer will be done in the follow-up and tracked
in crbug.com/456675133

Bug: 370041145
Change-Id: I9f548620db37732c198243940325216c85e8f34e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6862996
Reviewed-by: Philip Rogers <pdr@chromium.org>
Commit-Queue: Daniil Sakhapov <sakhapov@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1550496}

--

wpt-commits: 00a31a92138affe472b297761f07e9329bb90b38
wpt-pr: 56299

Diffstat:
Atesting/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-overflow-clip-margin.html | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-overflow-clip.html | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-siblings.html | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test.html | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-borders/tentative/border-shape/border-shape-hit-test-overflow.html | 50++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 317 insertions(+), 0 deletions(-)

diff --git a/testing/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-overflow-clip-margin.html b/testing/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-overflow-clip-margin.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>CSS Borders Test: hit testing border-shape circle with overflow clip parent</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> + #outer { + width: 100px; + height: 100px; + overflow: clip; + overflow-clip-margin: 50px; + } + #target { + width: 100px; + height: 100px; + border-shape: circle(45px at 50% 50%); + border: 10px solid purple; + background: green; + } +</style> +<div id="outer"> + <div id="target"></div> +</div> +<script> + function getCenter(el) { + const rect = el.getBoundingClientRect(); + return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; + } + + function polarToXY(center, radius, angleRad) { + return { + x: Math.round(center.x + radius * Math.cos(angleRad)), + y: Math.round(center.y + radius * Math.sin(angleRad)) + }; + } + + promise_test(async t => { + const target = document.getElementById('target'); + const center = getCenter(target); + const strokeWidthHalf = 5; // 10px border-width + const circleRadius = 45 + strokeWidthHalf; + + // Check a point on the edge of the circle (should hit due to overflow-clip-margin). + 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. + let hit = false; + let hitBody = false; + target.addEventListener('pointerdown', () => { hit = true; }, { once: true }); + document.body.addEventListener('pointerdown', () => { hitBody = true; }, { once: true }); + await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send(); + assert_true(hit, 'Point outside the clipped part should hit the border-shape due to overflow-clip-margin'); + }); +</script> diff --git a/testing/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-overflow-clip.html b/testing/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-overflow-clip.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<title>CSS Borders Test: hit testing border-shape circle with overflow clip parent</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> + #outer { + width: 100px; + height: 100px; + overflow: clip; + } + #target { + width: 100px; + height: 100px; + border-shape: circle(50% at 50% 50%); + stroke: purple; + stroke-width: 10px; + background: green; + } +</style> +<div id="outer"> + <div id="target"></div> +</div> +<script> + function getCenter(el) { + const rect = el.getBoundingClientRect(); + return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; + } + + function polarToXY(center, radius, angleRad) { + return { + x: Math.round(center.x + radius * Math.cos(angleRad)), + y: Math.round(center.y + radius * Math.sin(angleRad)) + }; + } + + promise_test(async t => { + const target = document.getElementById('target'); + const center = getCenter(target); + const strokeWidthHalf = 5; // 10px stroke-width + const circleRadius = 50 + strokeWidthHalf; // 100px box, circle(50%) => 50px radius + + // Check a point on the edge of the circle (shouldn't hit as clipped). + 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. + let hit = false; + let hitBody = false; + target.addEventListener('pointerdown', () => { hit = true; }, { once: true }); + document.body.addEventListener('pointerdown', () => { hitBody = true; }, { once: true }); + await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send(); + assert_false(hit, 'Point outside the clipped part should not hit the border-shape'); + assert_true(hitBody, 'Point outside the clipped part should hit body'); + }); +</script> diff --git a/testing/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-siblings.html b/testing/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test-siblings.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<title>CSS Borders Test: hit testing border-shape circle depth order with siblings</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> + .container { + position: relative; + width: 200px; + height: 100px; + } + + .sibling { + position: absolute; + width: 100px; + height: 100px; + top: 0; + left: 0; + background: blue; + opacity: 0.5; + } + + #target { + position: absolute; + left: 50px; + width: 80px; + height: 80px; + border-shape: circle(45px at 50% 50%); + border: 10px solid purple; + background: green; + } + + .sibling2 { + position: absolute; + left: 100px; + width: 100px; + height: 100px; + background: red; + opacity: 0.5; + } +</style> +<div class="container"> + <div class="sibling" id="before"></div> + <div id="target"></div> + <div class="sibling2" id="after"></div> +</div> +<script> + function getRect(el) { + return el.getBoundingClientRect(); + } + function getCenter(el) { + const r = getRect(el); + return { x: r.left + r.width / 2, y: r.top + r.height / 2 }; + } + function polarToXY(center, radius, angleRad) { + return { + x: Math.round(center.x + radius * Math.cos(angleRad)), + y: Math.round(center.y + radius * Math.sin(angleRad)) + }; + } + + let hit = null; + function handler(e) { hit = e.currentTarget.id; } + + const before = document.getElementById('before'); + const target = document.getElementById('target'); + const after = document.getElementById('after'); + const center = getCenter(target); + const strokeWidthHalf = 5; // 10px stroke-width + const radius = 45 + strokeWidthHalf; + + promise_test(async t => { + // Checking hit on upper left part of the circle border (should be before). + hit = null; + before.addEventListener('pointerdown', handler, { once: true }); + const { x, y } = polarToXY(center, radius + 1, (3 * Math.PI) / 4); // angle 135deg, top-left edge of the circle + await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send(); + assert_equals(hit, 'before', 'Before sibling hit while not overlapping'); + }); + + promise_test(async t => { + // Checking hit on left part of the circle border (should be target). + hit = null; + target.addEventListener('pointerdown', handler, { once: true }); + const { x, y } = polarToXY(center, radius, Math.PI); // angle 180deg, left edge of the circle + await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send(); + assert_equals(hit, 'target', 'Target has higher z-index than before sibling while overlapping'); + }); + + promise_test(async t => { + // Checking hit on right part of the circle (should be after). + hit = null; + after.addEventListener('pointerdown', handler, { once: true }); + const { x, y } = polarToXY(center, radius, 0); // angle 0deg, right edge of the circle + await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send(); + assert_equals(hit, 'after', 'Target has lower z-index than after sibling while overlapping'); + }); +</script> diff --git a/testing/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test.html b/testing/web-platform/tests/css/css-borders/tentative/border-shape-circle-hit-test.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>CSS Borders Test: hit testing border-shape circle</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> + #target { + width: 100px; + height: 100px; + border-shape: circle(45px at 50% 50%); + border: 10px solid purple; + background: green; + } +</style> +<div id="target"></div> +<script> + function getCenter(el) { + const rect = el.getBoundingClientRect(); + return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; + } + + function polarToXY(center, radius, angleRad) { + return { + x: Math.round(center.x + radius * Math.cos(angleRad)), + y: Math.round(center.y + radius * Math.sin(angleRad)) + }; + } + + promise_test(async t => { + const target = document.getElementById('target'); + const center = getCenter(target); + const strokeWidthHalf = 5; // 10px border-width + const circleRadius = 45 + strokeWidthHalf; + + // Check points from center to the edge of the circle (should hit). + for (let r = 0; r < circleRadius; r += 5) { + let angle = Math.random() * 2 * Math.PI; + const { x, y } = polarToXY(center, r, angle); + let hit = false; + target.addEventListener('pointerdown', () => { hit = true; }, { once: true }); + await new test_driver.Actions().pointerMove(x, y).pointerDown().pointerUp().send(); + assert_true(hit, `Point at radius ${r} should hit the element`); + } + + // Check a point just outside the circle (should not hit). + const { x: outX, y: outY } = polarToXY(center, circleRadius + 1, Math.PI / 4); + let hit = false; + target.addEventListener('pointerdown', () => { hit = true; }, { once: true }); + await new test_driver.Actions().pointerMove(outX, outY).pointerDown().pointerUp().send(); + assert_false(hit, 'Point outside the border-shape should not hit the element'); + }); +</script> diff --git a/testing/web-platform/tests/css/css-borders/tentative/border-shape/border-shape-hit-test-overflow.html b/testing/web-platform/tests/css/css-borders/tentative/border-shape/border-shape-hit-test-overflow.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<title>CSS Borders Test: hit testing border-shape with overflow</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> + #bs-target { + width: 200px; + height: 200px; + border-shape: circle(50% at 50% 50%); + border: 20px solid purple; + background: green; + } + + #bs-target:hover { + border-color: orange; + } + + #overflower { + width: 400px; + height: 25px; + background: lightblue; + text-align: end; + } +</style> + +border-shape:<br> +<div id="bs-target"> + <div id="overflower">hover here</div> +</div> +<script> + promise_test(async t => { + let eventTarget; + const target = document.getElementById('bs-target'); + const overflower = document.getElementById('overflower'); + const x = 350, y = 60; + const rect = target.getBoundingClientRect(); + assert_false( + x >= rect.left && x <= rect.right && + y >= rect.top && y <= rect.bottom, + 'Point should be outside the border-shape element'); + + target.addEventListener('mouseover', (e) => { eventTarget = e.target; }, { once: true }); + await new test_driver.Actions().pointerMove(x, y).send(); + assert_equals(eventTarget, overflower, 'Event target should be the overflowing element'); + }); +</script>