tor-browser

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

commit 977361c9c1b0999800da969f405243665f7e6d79
parent c7d0d0103e485a66fd8e3dd4f6cb5105baa67ebb
Author: Blink WPT Bot <blink-w3c-test-autoroller@chromium.org>
Date:   Wed, 12 Nov 2025 08:51:54 +0000

Bug 1999545 [wpt PR 55981] - Respect overscroll-behavior when determining scroll bubbling, a=testonly

Automatic update from web-platform-tests
Respect overscroll-behavior when determining scroll bubbling (#55981)

According to the spec[1]:
- `auto`: allows normal scroll chaining and overscroll behavior.
- `contain`: prevents non-local boundary actions such as scroll chaining
  or navigation, but keeps local overscroll affordances (e.g. rubberband).
- `none`: same as `contain`, but also suppresses local overscroll effects.

Therefore, when overscroll-behavior is not `auto`, scroll should not
bubble to ancestor elements along the scroll chain.

[1]: https://drafts.csswg.org/css-overscroll/

Bug: 41378182
Change-Id: I884802d26d34825014ae6c881c7203bea031c8c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7079630
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Peng Zhou <zhoupeng.1996@bytedance.com>
Cr-Commit-Position: refs/heads/main@{#1543176}

Co-authored-by: Peng Zhou <zhoupeng.1996@bytedance.com>
--

wpt-commits: 9f8490256485e4e9a2729e2896c8ce0336932916
wpt-pr: 55981

Diffstat:
Atesting/web-platform/tests/css/css-overscroll-behavior/overscroll-behavior-keyboard-scroll-child-frame.html | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-overscroll-behavior/overscroll-behavior-keyboard-scroll.html | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-overscroll-behavior/resources/keyboard-scroll-child-frame-iframe.html | 19+++++++++++++++++++
Mtesting/web-platform/tests/dom/events/scrolling/scroll_support.js | 41+++++++++++++++++++++++++++++++++++++++++
4 files changed, 290 insertions(+), 0 deletions(-)

diff --git a/testing/web-platform/tests/css/css-overscroll-behavior/overscroll-behavior-keyboard-scroll-child-frame.html b/testing/web-platform/tests/css/css-overscroll-behavior/overscroll-behavior-keyboard-scroll-child-frame.html @@ -0,0 +1,62 @@ +<!doctype html> +<meta charset="utf-8"> +<title>overscroll-behavior for keyboard scroll in child frame</title> +<meta name="timeout" content="long"> +<link rel="help" href="https://drafts.csswg.org/css-overscroll-behavior"> +<link rel="author" title="Peng Zhou" href="mailto:zhoupeng.1996@bytedance.com"> +<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> +<script src="/dom/events/scrolling/scroll_support.js"></script> +<script src="/css/css-scroll-snap/support/common.js"></script> +<style> +body { + margin: 0; +} + +#container { + overflow: auto; + height: 600px; + position: relative; +} + +iframe { + width: 600px; + height: 400px; + position: absolute; + top: 500px; +} +</style> +<div id="container"> + <iframe id="iframe" src="resources/keyboard-scroll-child-frame-iframe.html"></iframe> +</div> +<script> +window.addEventListener('load', () => { + promise_test(async () => { + const target = iframe.contentWindow; + let scrollEndPromise = waitForScrollEndFallbackToDelayWithoutScrollEvent(container); + container.scrollTop = 300; + await scrollEndPromise; + + scrollEndPromise = waitForScrollEndFallbackToDelayWithoutScrollEvent(target); + target.scrollTo(0, 50); + await scrollEndPromise; + assert_equals(target.scrollY, 50); + + await new test_driver.Actions() + .pointerMove(200, 300) + .pointerDown() + .pointerUp() + .send(); + assert_equals(document.activeElement, iframe); + + scrollEndPromise = waitForScrollEndOrTimeout(target, 1000); + await scrollElementByKeyboard('ArrowUp'); + await scrollEndPromise; + assert_equals(container.scrollTop, 300); + assert_equals(target.scrollY, 0); + }, 'scrolling is not propagated from iframe to the main frame'); +}); +</script> diff --git a/testing/web-platform/tests/css/css-overscroll-behavior/overscroll-behavior-keyboard-scroll.html b/testing/web-platform/tests/css/css-overscroll-behavior/overscroll-behavior-keyboard-scroll.html @@ -0,0 +1,168 @@ +<!doctype html> +<meta charset="utf-8"> +<title>overscroll-behavior for keyboard scroll</title> +<meta name="timeout" content="long"> +<link rel="help" href="https://drafts.csswg.org/css-overscroll-behavior"> +<link rel="author" title="Peng Zhou" href="mailto:zhoupeng.1996@bytedance.com"> +<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> +<script src="/dom/events/scrolling/scroll_support.js"></script> +<script src="/css/css-scroll-snap/support/common.js"></script> + +<style> +body { + margin: 0px; +} +.outer { + height: 400px; + width: 1000px; + background: white +} +.content { + height: 600px; + width: 1200px; +} +#root { + overflow: scroll; + height: 600px; + width: 800px; + background: white; +} +#container { + overflow: scroll; +} +#non_scrollable { + overflow: clip; +} +#green { + background: repeating-linear-gradient(to bottom right, green 15%, white 30%); +} +#blue { + background: repeating-linear-gradient(to bottom right, blue 15%, white 30%); +} +</style> + +<div id='root'> + <div id='non_scrollable' class='outer'> + <div id='green' class='content'></div> + </div> + <div id='container' class='outer'> + <div id='blue' class='content'></div> + </div> +</div> +<!-- +Tests that overscroll-behavior prevents scroll-propagation in the area and +direction as specified. + Manual Steps: + 1. Make two scrolls on blue, in this order: scroll UP (or drag down), then + scroll LEFT (or drag right). Scroll (or drag) until nothing is + scrolling. + 2. Call verify_y_prevented_and_set_boundary_prevents_x() from console + 3. Repeat the same scrolls as in step 1 + 4. Call verify_x_prevented_and_set_boundary_allows_inner() from console + 5. Repeat the same scrolls as in step 1 + 6. Call verify_inner_allowed_and_set_nonscrollable_allows_propagation() + from console + 7. Make two separate scrolls on green, in this order: scroll UP + (or drag down), then scroll LEFT (or drag right). Scroll (or drag) until + nothing is scrolling. + 8. Call verify_non_scrollable_allows_propagation() from console +</ol> --> +<script> +function setScrollPosition(scroller, offset) { + scroller.scrollTop = offset; + scroller.scrollLeft = offset; +} + +async function scrollWithKeyboardWait(scrollElement, overflowScroller) { + const scrollerRect = scrollElement.getBoundingClientRect(); + // Move pointer to scroll_element. + await new test_driver.Actions() + .pointerMove(scrollerRect.left + 200, scrollerRect.top + 100) + .pointerDown() + .pointerUp() + .send(); + // Perform vertical scroll. + let scrollEndPromise = waitForScrollEndOrTimeout(overflowScroller, 1000); + await scrollElementByKeyboard('ArrowUp'); + await scrollEndPromise; + // Perform horizontal scroll. + scrollEndPromise = waitForScrollEndOrTimeout(overflowScroller, 1000); + await scrollElementByKeyboard('ArrowLeft'); + await scrollEndPromise; +} + +promise_test(async t => { + await waitForCompositorReady(); + + container.style.overscrollBehaviorX = 'auto'; + container.style.overscrollBehaviorY = 'none'; + setScrollPosition(root, 100); + setScrollPosition(container, 0); + window.scrollTo(0, 0); + + await scrollWithKeyboardWait(container, root); + + assert_approx_equals(root.scrollTop, 100, 0.5, + "root got unexpected scroll on Y axis"); + assert_approx_equals(root.scrollLeft, 0, 0.5, + "root expected to scroll on X axis"); +}, "overscroll-behavior-y: none prevents scroll propagation on y axis"); + +promise_test(async t => { + await waitForCompositorReady(); + + container.style.overscrollBehaviorX = 'none'; + container.style.overscrollBehaviorY = 'auto'; + setScrollPosition(root, 100); + setScrollPosition(container, 0); + window.scrollTo(0, 0); + + await scrollWithKeyboardWait(container, root); + + assert_approx_equals(root.scrollTop, 0, 0.5, + "root expected to scroll on Y axis"); + assert_approx_equals(root.scrollLeft, 100, 0.5, + "root got unexpected scroll on X axis"); +}, "overscroll-behavior-x: none prevents scroll propagation on x axis"); + +promise_test(async t => { + await waitForCompositorReady(); + + container.style.overscrollBehaviorX = 'none'; + container.style.overscrollBehaviorY = 'none'; + setScrollPosition(root, 100); + setScrollPosition(container, 100); + window.scrollTo(0, 0); + + await scrollWithKeyboardWait(container, container); + + assert_approx_equals(container.scrollTop, 0, 0.5, + "inner container expected to scroll on Y axis"); + assert_approx_equals(container.scrollLeft, 0, 0.5, + "inner container expected to scroll on X axis"); + assert_approx_equals(root.scrollTop, 100, 0.5, + "root got unexpected scroll on Y axis"); + assert_approx_equals(root.scrollLeft, 100, 0.5, + "root got unexpected scroll on X axis"); +}, "overscroll-behavior allows inner scrolling when both axes are none"); + +promise_test(async t => { + await waitForCompositorReady(); + + non_scrollable.style.overscrollBehaviorX = 'none'; + non_scrollable.style.overscrollBehaviorY = 'none'; + setScrollPosition(root, 100); + window.scrollTo(0, 0); + + await scrollWithKeyboardWait(non_scrollable, root); + + assert_approx_equals(root.scrollLeft, 0, 0.5, + "root expected to scroll on X axis"); + assert_approx_equals(root.scrollTop, 0, 0.5, + "root expected to scroll on Y axis"); +}, "overscroll-behavior on non-scrollable area allows scroll propagation"); +</script> diff --git a/testing/web-platform/tests/css/css-overscroll-behavior/resources/keyboard-scroll-child-frame-iframe.html b/testing/web-platform/tests/css/css-overscroll-behavior/resources/keyboard-scroll-child-frame-iframe.html @@ -0,0 +1,19 @@ +<!doctype html> +<meta charset="utf-8"> +<title>:root with overscroll-behavior:none prevents scroll propagation from child frame</title> +<style> +:root { + overscroll-behavior-y: none; +} + +body { + margin: 0; + font-family: sans-serif; +} + +.content { + height: 400vh; + background: red; +} +</style> +<div class="content"></div> diff --git a/testing/web-platform/tests/dom/events/scrolling/scroll_support.js b/testing/web-platform/tests/dom/events/scrolling/scroll_support.js @@ -84,6 +84,27 @@ function waitForScrollEndFallbackToDelayWithoutScrollEvent(eventTargets) { }); } +// Waits for the end of scrolling, but resolves after the given timeout if no +// scroll event occurs. +function waitForScrollEndOrTimeout(eventTarget, timeout) { + const rafTimeout = new Promise(resolve => { + const startTime = performance.now(); + const tick = () => { + if (performance.now() - startTime >= timeout) { + resolve(); + } else { + requestAnimationFrame(tick); + } + }; + requestAnimationFrame(tick); + }); + + return Promise.race([ + waitForScrollEndFallbackToDelayWithoutScrollEvent(eventTarget), + rafTimeout + ]); +} + async function waitForPointercancelEvent(test, target, timeoutMs = 500) { return waitForEvent("pointercancel", test, target, timeoutMs); } @@ -345,3 +366,23 @@ function scrollElementLeft(element, scroll_amount) { .scroll(x, y, delta_x, delta_y, {origin: element}); return actions.send(); } + +async function scrollElementByKeyboard(key) { + const KEY_CODE_MAP = { + 'ArrowLeft': '\uE012', + 'ArrowUp': '\uE013', + 'ArrowRight': '\uE014', + 'ArrowDown': '\uE015', + }; + + if (!KEY_CODE_MAP.hasOwnProperty(key)) { + return Promise.reject(`Invalid key for scrollElementByKeyboard: ${key}`); + } + const code = KEY_CODE_MAP[key]; + for (let i = 0; i < 10; i++) { + await new test_driver.Actions() + .keyDown(code) + .keyUp(code) + .send(); + } +}