diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/dom/events/scrolling | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/dom/events/scrolling')
20 files changed, 2180 insertions, 0 deletions
diff --git a/testing/web-platform/tests/dom/events/scrolling/iframe-chains.html b/testing/web-platform/tests/dom/events/scrolling/iframe-chains.html new file mode 100644 index 0000000000..fb7d674aae --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/iframe-chains.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> +<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> + +body { margin: 0; padding: 10px; } +.space { height: 2000px; } + +#scroller { + border: 3px solid green; + position: absolute; + z-index: 0; + overflow: auto; + padding: 10px; + width: 250px; + height: 150px; +} + +.ifr { + border: 3px solid blue; + width: 200px; + height: 100px; +} + +</style> +</head> +<body> +<div id=scroller> + <iframe srcdoc="SCROLL ME" class=ifr></iframe> + <div class=space></div> +</div> +<div class=space></div> +<script> + +promise_test(async t => { + await new test_driver.Actions().scroll(50, 50, 0, 50).send(); + // Allow the possibility the scroll is not fully synchronous + await t.step_wait(() => scroller.scrollTop === 50); +}, "Wheel scroll in iframe chains to containing element."); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html b/testing/web-platform/tests/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html new file mode 100644 index 0000000000..f84e446527 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html @@ -0,0 +1,71 @@ +<!doctype html> +<html> +<head> +<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> + +</head> +<body onload=runTest()> + <p>Moving the cursor using the arrow keys into an + input element fires scroll events when text has to scroll into view. + Uses arrow keys to move forward and backwards in the input + element.</p> + <input type="text" style='width: 50px' + value="Fooooooooooooooooooooooooooooooooooooooooooooooooo"/> + <textarea rows="4" cols="4"> + Fooooooooooooooooooooooooooooooooooooooooooooooooo + </textarea> + + <script> + async function moveCursorRightInsideElement(element, value){ + var arrowRight = '\uE014'; + for(var i=0;i<value;i++){ + await test_driver.send_keys(element, arrowRight); + } + } + + function runTest(){ + promise_test(async(t) => { return new Promise(async (resolve, reject) => { + var input = document.getElementsByTagName('input')[0]; + function handleScroll(){ + resolve("Scroll Event successfully fired!"); + } + input.addEventListener('scroll', handleScroll, false); + // move cursor to the right until the text scrolls + while(input.scrollLeft === 0){ + await moveCursorRightInsideElement(input, 1); + } + // if there is no scroll event fired then test will fail by timeout + })}, + /* + Moving the cursor using the arrow keys into an input element + fires scroll events when text has to scroll into view. + Uses arrow keys to move right in the input element. + */ + "Scroll event fired for <input> element."); + + promise_test(async(t) => { return new Promise(async (resolve, reject) => { + var textarea = document.getElementsByTagName('textarea')[0]; + function handleScroll(){ + resolve("Scroll Event successfully fired!"); + } + textarea.addEventListener('scroll', handleScroll, false); + // move cursor to the right until the text scrolls + while(textarea.scrollLeft === 0){ + await moveCursorRightInsideElement(textarea, 1); + } + // if there is no scroll event fired then test will fail by timeout + })}, + /* + Moving the cursor using the arrow keys into a textarea element + fires scroll events when text has to scroll into view. + Uses arrow keys to move right in the textarea element. + */ + "Scroll event fired for <textarea> element."); + } + </script> +</body> +</html> diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-deltas.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-deltas.html new file mode 100644 index 0000000000..2158632250 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-deltas.html @@ -0,0 +1,147 @@ +<!DOCTYPE HTML> +<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="scroll_support.js"></script> +<style> + #hspacer { + height: 100px; + width: 100vw; + top: 0; + /* on the right edge of targetDiv */ + left: 200px; + position: absolute; + } + + #vspacer { + height: 100vh; + width: 100px; + position: absolute; + } + + #targetDiv { + width: 200px; + height: 200px; + overflow: scroll; + } + + #innerDiv { + width: 400px; + height: 400px; + } +</style> + +<body style="margin:0" onload=runTest()> + <div id="targetDiv"> + <div id="innerDiv"></div> + </div> + <div id="hspacer"></div> + <div id="vspacer"></div> +</body> + +<script> + var target_div = document.getElementById('targetDiv'); + var overscrolled_x_deltas = []; + var overscrolled_y_deltas = []; + var scrollend_received = false; + + function onOverscroll(event) { + overscrolled_x_deltas.push(event.deltaX); + overscrolled_y_deltas.push(event.deltaY); + } + + async function resetScrollers(test) { + await waitForScrollReset(test, target_div); + await waitForScrollReset(test, document.scrollingElement); + } + + function resetOverScrollDeltas() { + overscrolled_x_deltas = []; + overscrolled_y_deltas = []; + } + + function fail() { + assert_true(false); + } + + document.addEventListener("overscroll", onOverscroll); + + function runTest() { + promise_test(async (t) => { + await resetScrollers(t); + await waitForCompositorCommit(); + resetOverScrollDeltas(); + + assert_equals(document.scrollingElement.scrollTop, 0, + "document should not be scrolled"); + + let scrollend_promise = waitForScrollendEvent(t, target_div); + let max_target_div_scroll_top = target_div.scrollHeight - target_div.clientHeight; + target_div.scrollTop = target_div.scrollHeight; + await scrollend_promise; + assert_equals(target_div.scrollTop, max_target_div_scroll_top, + "target_div should be fully scrolled down"); + + let overscroll_promise = waitForOverscrollEvent(t, document, 2000); + scrollend_promise = waitForScrollendEvent(t, document, 2000); + target_div.addEventListener("scrollend", fail); + // Scroll target div vertically and wait for the doc to get scrollend event. + await scrollElementDown(target_div, target_div.clientHeight + 300); + await waitForCompositorCommit(); + await overscroll_promise; + await scrollend_promise; + + target_div.removeEventListener("scrollend", fail); + // Even though we request 300 extra pixels of scroll, the API above doesn't + // guarantee how much scroll delta will be generated - different browsers + // can consume different amounts for "touch slop" (for example). Ensure the + // overscroll reaches at least 50 pixels which is a fairly conservative + // value. + assert_greater_than(overscrolled_y_deltas.length, 0, "There should be at least one overscroll events when overscrolling."); + assert_equals(overscrolled_x_deltas.filter(function (x) { return x != 0; }).length, 0, "The deltaX attribute must be 0 when there is no scrolling in x direction."); + assert_greater_than_equal(Math.min(...overscrolled_y_deltas), 50, "The deltaY attribute must be the number of pixels overscrolled."); + assert_less_than_equal(Math.max(...overscrolled_y_deltas), 300, "The deltaY attribute must be <= the number of pixels overscrolled (300)"); + assert_greater_than(document.scrollingElement.scrollTop, target_div.clientHeight - 1, + "document is scrolled by the height of target_div"); + }, "testing, vertical"); + + promise_test(async (t) => { + await resetScrollers(t); + await waitForCompositorCommit(); + resetOverScrollDeltas(); + + assert_equals(document.scrollingElement.scrollLeft, 0, + "document should not be scrolled"); + + let scrollend_promise = waitForScrollendEvent(t, target_div); + let max_target_div_scroll_left = target_div.scrollWidth - target_div.clientWidth; + target_div.scrollLeft = target_div.scrollWidth; + await scrollend_promise; + assert_equals(target_div.scrollLeft, max_target_div_scroll_left, + "target_div should be fully scrolled right"); + + let overscroll_promise = waitForOverscrollEvent(t, document, 2000); + scrollend_promise = waitForScrollendEvent(t, document, 2000); + target_div.addEventListener("scrollend", fail); + // Scroll target div horizontally and wait for the doc to get scrollend event. + await scrollElementLeft(target_div, target_div.clientWidth + 300); + await waitForCompositorCommit(); + await overscroll_promise; + await scrollend_promise; + + target_div.removeEventListener("scrollend", fail); + assert_greater_than(document.scrollingElement.scrollLeft, target_div.clientWidth - 1, + "document is scrolled by the height of target_div"); + // TODO(bokan): It looks like Chrome inappropriately filters some scroll + // events despite |overscroll-behavior| being set to none. The overscroll + // amount here has been loosened but this should be fixed in Chrome. + // https://crbug.com/1112183. + assert_greater_than(overscrolled_x_deltas.length, 0, "There should be at least one overscroll events when overscrolling."); + assert_equals(overscrolled_y_deltas.filter(function(x){ return x!=0; }).length, 0, "The deltaY attribute must be 0 when there is no scrolling in y direction."); + assert_less_than_equal(Math.max(...overscrolled_x_deltas), 300, "The deltaX attribute must be <= number of pixels overscrolled (300)"); + assert_greater_than_equal(Math.min(...overscrolled_x_deltas),50, "The deltaX attribute must be the number of pixels overscrolled."); + }, "testing, horizontal"); + } +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-document.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-document.html new file mode 100644 index 0000000000..c054ffca9c --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-document.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<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="scroll_support.js"></script> +<style> +#targetDiv { + width: 200px; + height: 200px; + overflow: scroll; +} + +#innerDiv { + width: 400px; + height: 400px; +} +</style> + +<body style="margin:0" onload=runTest()> +<div id="targetDiv"> + <div id="innerDiv"> + </div> +</div> +</body> + +<script> +var target_div = document.getElementById('targetDiv'); +var overscrolled_x_delta = 0; +var overscrolled_y_delta = 0; +function onOverscroll(event) { + assert_false(event.cancelable); + // overscroll events are bubbled when the target node is document. + assert_true(event.bubbles); + overscrolled_x_delta = event.deltaX; + overscrolled_y_delta = event.deltaY; +} +document.addEventListener("overscroll", onOverscroll); + +function runTest() { + promise_test (async (t) => { + // Make sure that no overscroll event is sent to target_div. + target_div.addEventListener("overscroll", + t.unreached_func("target_div got unexpected overscroll event.")); + await waitForCompositorCommit(); + + // Scroll up on target div and wait for the doc to get overscroll event. + await touchScrollInTarget(300, target_div, 'up'); + await waitFor(() => { return overscrolled_y_delta < 0; }, + 'Document did not receive overscroll event after scroll up on target.'); + assert_equals(target_div.scrollTop, 0); + + // Scroll left on target div and wait for the doc to get overscroll event. + await touchScrollInTarget(300, target_div, 'left'); + await waitFor(() => { return overscrolled_x_delta < 0; }, + 'Document did not receive overscroll event after scroll left on target.'); + assert_equals(target_div.scrollLeft, 0); + }, 'Tests that the document gets overscroll event when no element scrolls ' + + 'after touch scrolling.'); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html new file mode 100644 index 0000000000..750080e656 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html @@ -0,0 +1,92 @@ +<!DOCTYPE HTML> +<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="scroll_support.js"></script> +<style> +#overscrollXDiv { + width: 600px; + height: 600px; + overflow: scroll; + overscroll-behavior-x: contain; +} +#overscrollYDiv { + width: 500px; + height: 500px; + overflow: scroll; + overscroll-behavior-y: none; +} +#targetDiv { + width: 400px; + height: 400px; + overflow: scroll; +} +.content { + width:800px; + height:800px; +} +</style> + +<body style="margin:0" onload=runTest()> +<div id="overscrollXDiv"> + <div class=content> + <div id="overscrollYDiv"> + <div class=content> + <div id="targetDiv"> + <div class="content"> + </div> + </div> + </div> + </div> + </div> +</div> +</body> + +<script> +var target_div = document.getElementById('targetDiv'); +var overscrolled_x_delta = 0; +var overscrolled_y_delta = 0; +function onOverscrollX(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + overscrolled_x_delta = event.deltaX; +} +function onOverscrollY(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + overscrolled_y_delta = event.deltaY; +} +// Test that both "onoverscroll" and addEventListener("overscroll"... work. +document.getElementById('overscrollXDiv').onoverscroll = onOverscrollX; +document.getElementById('overscrollYDiv'). + addEventListener("overscroll", onOverscrollY); + +function runTest() { + promise_test (async (t) => { + // Make sure that no overscroll event is sent to document or target_div. + document.addEventListener("overscroll", + t.unreached_func("Document got unexpected overscroll event.")); + target_div.addEventListener("overscroll", + t.unreached_func("target_div got unexpected overscroll event.")); + await waitForCompositorCommit(); + // Scroll up on target div and wait for the element with overscroll-y to get + // overscroll event. + await touchScrollInTarget(300, target_div, 'up'); + await waitFor(() => { return overscrolled_y_delta < 0; }, + 'Expected element did not receive overscroll event after scroll up on ' + + 'target.'); + assert_equals(target_div.scrollTop, 0); + + // Scroll left on target div and wait for the element with overscroll-x to + // get overscroll event. + await touchScrollInTarget(300, target_div, 'left'); + await waitFor(() => { return overscrolled_x_delta < 0; }, + 'Expected element did not receive overscroll event after scroll left ' + + 'on target.'); + assert_equals(target_div.scrollLeft, 0); + }, 'Tests that the last element in the cut scroll chain gets overscroll ' + + 'event when no element scrolls by touch.'); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html new file mode 100644 index 0000000000..cfc782a809 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<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="scroll_support.js"></script> +<style> +#scrollableDiv { + width: 200px; + height: 200px; + overflow: scroll; +} + +#innerDiv { + width: 400px; + height: 400px; +} +</style> + +<body style="margin:0" onload=runTest()> +<div id="scrollableDiv"> + <div id="innerDiv"> + </div> +</div> +</body> + +<script> +var scrolling_div = document.getElementById('scrollableDiv'); +var overscrolled_x_delta = 0; +var overscrolled_y_delta = 0; +function onOverscroll(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + overscrolled_x_delta = event.deltaX; + overscrolled_y_delta = event.deltaY; +} +scrolling_div.addEventListener("overscroll", onOverscroll); + +function runTest() { + promise_test (async (t) => { + // Make sure that no overscroll event is sent to document. + document.addEventListener("overscroll", + t.unreached_func("Document got unexpected overscroll event.")); + await waitForCompositorCommit(); + + // Do a horizontal scroll and wait for overscroll event. + await touchScrollInTarget(300, scrolling_div , 'right'); + await waitFor(() => { return overscrolled_x_delta > 0; }, + 'Scroller did not receive overscroll event after horizontal scroll.'); + assert_equals(scrolling_div.scrollWidth - scrolling_div.scrollLeft, + scrolling_div.clientWidth); + + overscrolled_x_delta = 0; + overscrolled_y_delta = 0; + + // Do a vertical scroll and wait for overscroll event. + await touchScrollInTarget(300, scrolling_div, 'down'); + await waitFor(() => { return overscrolled_y_delta > 0; }, + 'Scroller did not receive overscroll event after vertical scroll.'); + assert_equals(scrolling_div.scrollHeight - scrolling_div.scrollTop, + scrolling_div.clientHeight); + }, 'Tests that the scrolled element gets overscroll event after fully scrolling by touch.'); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-window.html b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-window.html new file mode 100644 index 0000000000..ef5ae3daef --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-event-fired-to-window.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<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="scroll_support.js"></script> +<style> +#targetDiv { + width: 200px; + height: 200px; + overflow: scroll; +} + +#innerDiv { + width: 400px; + height: 400px; +} +</style> + +<body style="margin:0" onload=runTest()> +<div id="targetDiv"> + <div id="innerDiv"> + </div> +</div> +</body> + +<script> +var target_div = document.getElementById('targetDiv'); +var window_received_overscroll = false; + +function onOverscroll(event) { + assert_false(event.cancelable); + // overscroll events targetting document are bubbled to the window. + assert_true(event.bubbles); + window_received_overscroll = true; +} +window.addEventListener("overscroll", onOverscroll); + +function runTest() { + promise_test (async (t) => { + // Make sure that no overscroll event is sent to target_div. + target_div.addEventListener("overscroll", + t.unreached_func("target_div got unexpected overscroll event.")); + // Scroll up on target div and wait for the window to get overscroll event. + await touchScrollInTarget(300, target_div, 'up'); + await waitFor(() => { return window_received_overscroll; }, + 'Window did not receive overscroll event after scroll up on target.'); + }, 'Tests that the window gets overscroll event when no element scrolls' + + 'after touch scrolling.'); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scroll_support.js b/testing/web-platform/tests/dom/events/scrolling/scroll_support.js new file mode 100644 index 0000000000..52c2f58723 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scroll_support.js @@ -0,0 +1,278 @@ +async function waitForEvent(eventName, test, target, timeoutMs = 500) { + return new Promise((resolve, reject) => { + const timeoutCallback = test.step_timeout(() => { + reject(`No ${eventName} event received for target ${target}`); + }, timeoutMs); + target.addEventListener(eventName, (evt) => { + clearTimeout(timeoutCallback); + resolve(evt); + }, { once: true }); + }); +} + +async function waitForScrollendEvent(test, target, timeoutMs = 500) { + return waitForEvent("scrollend", test, target, timeoutMs); +} + +async function waitForOverscrollEvent(test, target, timeoutMs = 500) { + return waitForEvent("overscroll", test, target, timeoutMs); +} + +async function waitForPointercancelEvent(test, target, timeoutMs = 500) { + return waitForEvent("pointercancel", test, target, timeoutMs); +} + +// Resets the scroll position to (0,0). If a scroll is required, then the +// promise is not resolved until the scrollend event is received. +async function waitForScrollReset(test, scroller, timeoutMs = 500) { + return new Promise(resolve => { + if (scroller.scrollTop == 0 && + scroller.scrollLeft == 0) { + resolve(); + } else { + const eventTarget = + scroller == document.scrollingElement ? document : scroller; + scroller.scrollTop = 0; + scroller.scrollLeft = 0; + waitForScrollendEvent(test, eventTarget, timeoutMs).then(resolve); + } + }); +} + +async function createScrollendPromiseForTarget(test, + target_div, + timeoutMs = 500) { + return waitForScrollendEvent(test, target_div, timeoutMs).then(evt => { + assert_false(evt.cancelable, 'Event is not cancelable'); + assert_false(evt.bubbles, 'Event targeting element does not bubble'); + }); +} + +function verifyNoScrollendOnDocument(test) { + const callback = + test.unreached_func("window got unexpected scrollend event."); + window.addEventListener('scrollend', callback); + test.add_cleanup(() => { + window.removeEventListener('scrollend', callback); + }); +} + +async function verifyScrollStopped(test, target_div) { + const unscaled_pause_time_in_ms = 100; + const x = target_div.scrollLeft; + const y = target_div.scrollTop; + return new Promise(resolve => { + test.step_timeout(() => { + assert_equals(target_div.scrollLeft, x); + assert_equals(target_div.scrollTop, y); + resolve(); + }, unscaled_pause_time_in_ms); + }); +} + +async function resetTargetScrollState(test, target_div) { + if (target_div.scrollTop != 0 || target_div.scrollLeft != 0) { + target_div.scrollTop = 0; + target_div.scrollLeft = 0; + return waitForScrollendEvent(test, target_div); + } +} + +const MAX_FRAME = 700; +const MAX_UNCHANGED_FRAMES = 20; + +// Returns a promise that resolves when the given condition is met or rejects +// after MAX_FRAME animation frames. +// TODO(crbug.com/1400399): deprecate. We should not use frame based waits in +// WPT as frame rates may vary greatly in different testing environments. +function waitFor(condition, error_message = 'Reaches the maximum frames.') { + return new Promise((resolve, reject) => { + function tick(frames) { + // We requestAnimationFrame either for MAX_FRAM frames or until condition + // is met. + if (frames >= MAX_FRAME) + reject(error_message); + else if (condition()) + resolve(); + else + requestAnimationFrame(tick.bind(this, frames + 1)); + } + tick(0); + }); +} + +// TODO(crbug.com/1400446): Test driver should defer sending events until the +// browser is ready. Also the term compositor-commit is misleading as not all +// user-agents use a compositor process. +function waitForCompositorCommit() { + return new Promise((resolve) => { + // rAF twice. + window.requestAnimationFrame(() => { + window.requestAnimationFrame(resolve); + }); + }); +} + +// Please don't remove this. This is necessary for chromium-based browsers. +// This shouldn't be necessary if the test harness deferred running the tests +// until after paint holding. This can be a no-op on user-agents that do not +// have a separate compositor thread. +async function waitForCompositorReady() { + const animation = + document.body.animate({ opacity: [ 1, 1 ] }, {duration: 1 }); + return animation.finished; +} + +function waitForNextFrame() { + const startTime = performance.now(); + return new Promise(resolve => { + window.requestAnimationFrame((frameTime) => { + if (frameTime < startTime) { + window.requestAnimationFrame(resolve); + } else { + resolve(); + } + }); + }); +} + +// TODO(crbug.com/1400399): Deprecate as frame rates may vary greatly in +// different test environments. +function waitForAnimationEnd(getValue) { + var last_changed_frame = 0; + var last_position = getValue(); + return new Promise((resolve, reject) => { + function tick(frames) { + // We requestAnimationFrame either for MAX_FRAME or until + // MAX_UNCHANGED_FRAMES with no change have been observed. + if (frames >= MAX_FRAME || frames - last_changed_frame > MAX_UNCHANGED_FRAMES) { + resolve(); + } else { + current_value = getValue(); + if (last_position != current_value) { + last_changed_frame = frames; + last_position = current_value; + } + requestAnimationFrame(tick.bind(this, frames + 1)); + } + } + tick(0); + }) +} + +// Scrolls in target according to move_path with pauses in between +// The move_path should contains coordinates that are within target boundaries. +// Keep in mind that 0,0 is the center of the target element and is also +// the pointerDown position. +// pointerUp() is fired after sequence of moves. +function touchScrollInTargetSequentiallyWithPause(target, move_path, pause_time_in_ms = 100) { + const test_driver_actions = new test_driver.Actions() + .addPointer("pointer1", "touch") + .pointerMove(0, 0, {origin: target}) + .pointerDown(); + + const substeps = 5; + let x = 0; + let y = 0; + // Do each move in 5 steps + for(let move of move_path) { + let step_x = (move.x - x) / substeps; + let step_y = (move.y - y) / substeps; + for(let step = 0; step < substeps; step++) { + x += step_x; + y += step_y; + test_driver_actions.pointerMove(x, y, {origin: target}); + } + test_driver_actions.pause(pause_time_in_ms); // To prevent inertial scroll + } + + return test_driver_actions.pointerUp().send(); +} + +function touchScrollInTarget(pixels_to_scroll, target, direction, pause_time_in_ms = 100) { + var x_delta = 0; + var y_delta = 0; + const num_movs = 5; + if (direction == "down") { + y_delta = -1 * pixels_to_scroll / num_movs; + } else if (direction == "up") { + y_delta = pixels_to_scroll / num_movs; + } else if (direction == "right") { + x_delta = -1 * pixels_to_scroll / num_movs; + } else if (direction == "left") { + x_delta = pixels_to_scroll / num_movs; + } else { + throw("scroll direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); + } + return new test_driver.Actions() + .addPointer("pointer1", "touch") + .pointerMove(0, 0, {origin: target}) + .pointerDown() + .pointerMove(x_delta, y_delta, {origin: target}) + .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) + .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) + .pointerMove(4 * x_delta, 4 * y_delta, {origin: target}) + .pointerMove(5 * x_delta, 5 * y_delta, {origin: target}) + .pause(pause_time_in_ms) + .pointerUp() + .send(); +} + +// Trigger fling by doing pointerUp right after pointerMoves. +function touchFlingInTarget(pixels_to_scroll, target, direction) { + touchScrollInTarget(pixels_to_scroll, target, direction, 0 /* pause_time */); +} + +function mouseActionsInTarget(target, origin, delta, pause_time_in_ms = 100) { + return new test_driver.Actions() + .addPointer("pointer1", "mouse") + .pointerMove(origin.x, origin.y, { origin: target }) + .pointerDown() + .pointerMove(origin.x + delta.x, origin.y + delta.y, { origin: target }) + .pointerMove(origin.x + delta.x * 2, origin.y + delta.y * 2, { origin: target }) + .pause(pause_time_in_ms) + .pointerUp() + .send(); +} + +// Returns a promise that resolves when the given condition holds for 10 +// animation frames or rejects if the condition changes to false within 10 +// animation frames. +// TODO(crbug.com/1400399): Deprecate as frame rates may very greatly in +// different test environments. +function conditionHolds(condition, error_message = 'Condition is not true anymore.') { + const MAX_FRAME = 10; + return new Promise((resolve, reject) => { + function tick(frames) { + // We requestAnimationFrame either for 10 frames or until condition is + // violated. + if (frames >= MAX_FRAME) + resolve(); + else if (!condition()) + reject(error_message); + else + requestAnimationFrame(tick.bind(this, frames + 1)); + } + tick(0); + }); +} + +function scrollElementDown(element, scroll_amount) { + let x = 0; + let y = 0; + let delta_x = 0; + let delta_y = scroll_amount; + let actions = new test_driver.Actions() + .scroll(x, y, delta_x, delta_y, {origin: element}); + return actions.send(); +} + +function scrollElementLeft(element, scroll_amount) { + let x = 0; + let y = 0; + let delta_x = scroll_amount; + let delta_y = 0; + let actions = new test_driver.Actions() + .scroll(x, y, delta_x, delta_y, {origin: element}); + return actions.send(); +} diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html new file mode 100644 index 0000000000..dab6dcc9bd --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> +#targetDiv { + width: 200px; + height: 200px; + overflow: scroll; +} + +#innerDiv { + width: 4000px; + height: 4000px; +} +</style> + +<body style="margin:0" onload=runTest()> +<div id="targetDiv"> + <div id="innerDiv"> + </div> +</div> +</body> + +<script> +const target_div = document.getElementById('targetDiv'); + +async function testWithMovePath(t, move_path) { + // Skip the test on a Mac as they do not support touch screens. + const isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; + if (isMac) + return; + + await resetTargetScrollState(t, target_div); + await waitForCompositorReady(); + + verifyNoScrollendOnDocument(t); + + let scrollend_count = 0; + const scrollend_listener = () => { scrollend_count += 1; }; + target_div.addEventListener("scrollend", scrollend_listener); + t.add_cleanup(() => { target_div.removeEventListener('scrollend', scrollend_listener); }); + + const pointercancel_listener = () => { + assert_equals(scrollend_count, 0, 'scrollend should happen after pointercancel.'); + }; + target_div.addEventListener("pointercancel", pointercancel_listener); + t.add_cleanup(() => { target_div.removeEventListener('pointercancel', pointercancel_listener); }); + + // Because we have several pointer moves, we choose bigger timeout. + const timeoutMs = 3000; + const targetPointercancelPromise = waitForPointercancelEvent(t, target_div, timeoutMs); + const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div, timeoutMs); + + await touchScrollInTargetSequentiallyWithPause(target_div, move_path); + + // Because we start scrolling after pointerdown, there is no pointerup, instead the target + // will receive a pointercancel, so we wait for pointercancel, and then continue. + await targetPointercancelPromise; + await targetScrollendPromise; + await verifyScrollStopped(t, target_div); + assert_equals(scrollend_count, 1, 'Only one scrollend event should be fired'); +} + +function runTest() { + promise_test (async (t) => { + // Scroll down & up & down on target div and wait for the target_div to get scrollend event. + const move_path = [ + { x: 0, y: -80 }, // Scroll down + { x: 0, y: -40 }, // Scroll up + { x: 0, y: -80 }, // Scroll down + ]; + await testWithMovePath(t, move_path); + }, "Move down, up and down again, receive scrollend event only once"); + + promise_test (async (t) => { + // Scroll right & left & right on target div and wait for the target_div to get scrollend event. + const move_path = [ + { x: -80, y: 0 }, // Scroll right + { x: -40, y: 0 }, // Scroll left + { x: -80, y: 0 }, // Scroll right + ]; + await testWithMovePath(t, move_path); + }, "Move right, left and right again, receive scrollend event only once"); + +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-snap.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-snap.html new file mode 100644 index 0000000000..03079ddc6c --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-snap.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> +div { + position: absolute; +} +#scroller { + width: 500px; + height: 500px; + overflow: scroll; + scroll-snap-type: both mandatory; + border: solid black 5px; +} +#space { + width: 2000px; + height: 2000px; +} +.target { + width: 200px; + height: 200px; + scroll-snap-align: start; + background-color: blue; +} +</style> + +<body style="margin:0" onload=runTests()> + <div id="scroller"> + <div id="space"></div> + <div class="target" style="left: 0px; top: 0px;"></div> + <div class="target" style="left: 80px; top: 80px;"></div> + <div class="target" style="left: 200px; top: 200px;"></div> + </div> +</body> + +<script> +var scroller = document.getElementById("scroller"); +var space = document.getElementById("space"); +const MAX_FRAME_COUNT = 700; +const MAX_UNCHANGED_FRAME = 20; + +function scrollTop() { + return scroller.scrollTop; +} + +var scroll_arrived_after_scroll_end = false; +var scroll_end_arrived = false; +scroller.addEventListener("scroll", () => { + if (scroll_end_arrived) + scroll_arrived_after_scroll_end = true; +}); +scroller.addEventListener("scrollend", () => { + scroll_end_arrived = true; +}); + +function runTests() { + promise_test (async () => { + await waitForCompositorCommit(); + await touchScrollInTarget(100, scroller, 'down'); + // Wait for the scroll snap animation to finish. + await waitForAnimationEnd(scrollTop); + await waitFor(() => { return scroll_end_arrived; }); + // Verify that scroll snap animation has finished before firing scrollend event. + assert_false(scroll_arrived_after_scroll_end); + }, "Tests that scrollend is fired after scroll snap animation completion."); + + promise_test (async () => { + // Reset scroll state. + scroller.scrollTo(0, 0); + await waitForCompositorCommit(); + scroll_end_arrived = false; + scroll_arrived_after_scroll_end = false; + + await touchFlingInTarget(50, scroller, 'down'); + // Wait for the scroll snap animation to finish. + await waitForAnimationEnd(scrollTop); + await waitFor(() => { return scroll_end_arrived; }); + // Verify that scroll snap animation has finished before firing scrollend event. + assert_false(scroll_arrived_after_scroll_end); + }, "Tests that scrollend is fired after fling snap animation completion."); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-mandatory-snap-point-after-load.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-mandatory-snap-point-after-load.html new file mode 100644 index 0000000000..f379113420 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-mandatory-snap-point-after-load.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width"> + <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="scroll_support.js"></script> + <title>scrollend + mandatory scroll snap test</title> + + <style> + #root { + width: 400px; + height: 400px; + overflow: auto; + scroll-snap-type: y mandatory; + border: 1px solid black; + --page-height: 400px; + } + + #scroller { + height: 200px; + width: 200px; + overflow: auto; + border: 1px solid black; + --page-height: 200px; + } + + .page { + height: var(--page-height); + scroll-snap-align: start; + } + + .hidden { + display: none; + } + </style> +</head> + +<body onload="runTests()"> +<div id="root" class="hidden"> + <h1>scrollend + mandatory scroll snap test</h1> + <div id="scroller"> + <div class="page"> + <p>Page 1</p> + </div> + <div class="page"> + <p>Page 2</p> + </div> + <div class="page"> + <p>Page 3</p> + </div> + </div> + + <div class="page"> + <p>Page A</p> + </div> + <div class="page"> + <p>Page B</p> + </div> + <div class="page"> + <p>Page C</p> + </div> +</div> + +<script> + function runTests() { + const root_div = document.getElementById("root"); + + promise_test(async (t) => { + const targetScrollendPromise = createScrollendPromiseForTarget(t, root_div); + + await waitForNextFrame(); + root_div.classList.remove("hidden"); + await waitForNextFrame(); + + await targetScrollendPromise; + await verifyScrollStopped(t, root_div); + }, "scrollend event fired after load for mandatory snap point"); + } +</script> +</body> + +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html new file mode 100644 index 0000000000..c6569e0beb --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html @@ -0,0 +1,135 @@ +<!DOCTYPE HTML> +<meta name="timeout" content="long"> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> +html { + height: 3000px; + width: 3000px; +} +#targetDiv { + width: 200px; + height: 200px; + overflow: scroll; +} + +#innerDiv { + width: 400px; + height: 400px; +} +</style> + +<body style="margin:0" onload=runTest()> +<div id="targetDiv"> + <div id="innerDiv"> + </div> +</div> +</body> +<script> +var element_scrollend_arrived = false; +var document_scrollend_arrived = false; + +function onElementScrollEnd(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + element_scrollend_arrived = true; +} + +function onDocumentScrollEnd(event) { + assert_false(event.cancelable); + // scrollend events are bubbled when the target node is document. + assert_true(event.bubbles); + document_scrollend_arrived = true; +} + +function callScrollFunction([scrollTarget, scrollFunction, args]) { + scrollTarget[scrollFunction](args); +} + +function runTest() { + let root_element = document.scrollingElement; + let target_div = document.getElementById("targetDiv"); + + promise_test (async (t) => { + await waitForCompositorCommit(); + target_div.addEventListener("scrollend", onElementScrollEnd); + document.addEventListener("scrollend", onDocumentScrollEnd); + + let test_cases = [ + [target_div, 200, 200, [target_div, "scrollTo", { top: 200, left: 200, behavior: "auto" }]], + [target_div, 0, 0, [target_div, "scrollTo", { top: 0, left: 0, behavior: "smooth" }]], + [root_element, 200, 200, [root_element, "scrollTo", { top: 200, left: 200, behavior: "auto" }]], + [root_element, 0, 0, [root_element, "scrollTo", { top: 0, left: 0, behavior: "smooth" }]], + [target_div, 200, 200, [target_div, "scrollBy", { top: 200, left: 200, behavior: "auto" }]], + [target_div, 0, 0, [target_div, "scrollBy", { top: -200, left: -200, behavior: "smooth" }]], + [root_element, 200, 200, [root_element, "scrollBy", { top: 200, left: 200, behavior: "auto" }]], + [root_element, 0, 0, [root_element, "scrollBy", { top: -200, left: -200, behavior: "smooth" }]] + ]; + + for(i = 0; i < test_cases.length; i++) { + let t = test_cases[i]; + let target = t[0]; + let expected_x = t[1]; + let expected_y = t[2]; + let scroll_datas = t[3]; + + callScrollFunction(scroll_datas); + await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, target.tagName + "." + scroll_datas[1] + " did not receive scrollend event."); + if (target == root_element) + assert_false(element_scrollend_arrived); + else + assert_false(document_scrollend_arrived); + assert_equals(target.scrollLeft, expected_x, target.tagName + "." + scroll_datas[1] + " scrollLeft"); + assert_equals(target.scrollTop, expected_y, target.tagName + "." + scroll_datas[1] + " scrollTop"); + + element_scrollend_arrived = false; + document_scrollend_arrived = false; + } + }, "Tests scrollend event for calling scroll functions."); + + promise_test(async (t) => { + await waitForCompositorCommit(); + + let test_cases = [ + [target_div, "scrollTop"], + [target_div, "scrollLeft"], + [root_element, "scrollTop"], + [root_element, "scrollLeft"] + ]; + for (i = 0; i < test_cases.length; i++) { + let t = test_cases[i]; + let target = t[0]; + let attribute = t[1]; + let position = 200; + + target.style.scrollBehavior = "smooth"; + target[attribute] = position; + await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, target.tagName + "." + attribute + " did not receive scrollend event."); + if (target == root_element) + assert_false(element_scrollend_arrived); + else + assert_false(document_scrollend_arrived); + assert_equals(target[attribute], position, target.tagName + "." + attribute + " "); + element_scrollend_arrived = false; + document_scrollend_arrived = false; + + await waitForCompositorCommit(); + target.style.scrollBehavior = "auto"; + target[attribute] = 0; + await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, target.tagName + "." + attribute + " did not receive scrollend event."); + if (target == root_element) + assert_false(element_scrollend_arrived); + else + assert_false(document_scrollend_arrived); + assert_equals(target[attribute], 0, target.tagName + "." + attribute + " "); + element_scrollend_arrived = false; + document_scrollend_arrived = false; + } + }, "Tests scrollend event for changing scroll attributes."); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html new file mode 100644 index 0000000000..8782b1dfee --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html @@ -0,0 +1,124 @@ +<!DOCTYPE HTML> +<meta name="timeout" content="long"> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> +html { + height: 3000px; + width: 3000px; +} +#targetDiv { + width: 200px; + height: 200px; + overflow: scroll; +} + +#innerDiv { + width: 400px; + height: 400px; +} +</style> + +<body style="margin:0" onload=runTest()> +<div id="targetDiv"> + <div id="innerDiv"> + </div> +</div> +</body> +<script> +var element_scrollend_arrived = false; +var document_scrollend_arrived = false; + +function onElementScrollEnd(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + element_scrollend_arrived = true; +} + +function onDocumentScrollEnd(event) { + assert_false(event.cancelable); + // scrollend events are bubbled when the target node is document. + assert_true(event.bubbles); + document_scrollend_arrived = true; +} + +function callScrollFunction([scrollTarget, scrollFunction, args]) { + scrollTarget[scrollFunction](args); +} + +function runTest() { + let root_element = document.scrollingElement; + let target_div = document.getElementById("targetDiv"); + let inner_div = document.getElementById("innerDiv"); + + // Get expected position for root_element scrollIntoView. + root_element.scrollTo(10000, 10000); + let max_root_x = root_element.scrollLeft; + let max_root_y = root_element.scrollTop; + root_element.scrollTo(0, 0); + + target_div.scrollTo(10000, 10000); + let max_element_x = target_div.scrollLeft; + let max_element_y = target_div.scrollTop; + target_div.scrollTo(0, 0); + + promise_test (async (t) => { + await waitForCompositorCommit(); + target_div.addEventListener("scrollend", onElementScrollEnd); + document.addEventListener("scrollend", onDocumentScrollEnd); + + let test_cases = [ + [target_div, max_element_x, max_element_y, [inner_div, "scrollIntoView", { inline: "end", block: "end", behavior: "auto" }]], + [target_div, 0, 0, [inner_div, "scrollIntoView", { inline: "start", block: "start", behavior: "smooth" }]], + [root_element, max_root_x, max_root_y, [root_element, "scrollIntoView", { inline: "end", block: "end", behavior: "smooth" }]], + [root_element, 0, 0, [root_element, "scrollIntoView", { inline: "start", block: "start", behavior: "smooth" }]] + ]; + + for(i = 0; i < test_cases.length; i++) { + let t = test_cases[i]; + let target = t[0]; + let expected_x = t[1]; + let expected_y = t[2]; + let scroll_datas = t[3]; + + callScrollFunction(scroll_datas); + await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, target.tagName + ".scrollIntoView did not receive scrollend event."); + if (target == root_element) + assert_false(element_scrollend_arrived); + else + assert_false(document_scrollend_arrived); + assert_equals(target.scrollLeft, expected_x, target.tagName + ".scrollIntoView scrollLeft"); + assert_equals(target.scrollTop, expected_y, target.tagName + ".scrollIntoView scrollTop"); + + element_scrollend_arrived = false; + document_scrollend_arrived = false; + } + }, "Tests scrollend event for scrollIntoView."); + + promise_test(async (t) => { + document.body.removeChild(target_div); + let out_div = document.createElement("div"); + out_div.style = "width: 100px; height:100px; overflow:scroll; scroll-behavior:smooth;"; + out_div.appendChild(target_div); + document.body.appendChild(out_div); + await waitForCompositorCommit(); + + element_scrollend_arrived = false; + document_scrollend_arrived = false; + inner_div.scrollIntoView({ inline: "end", block: "end", behavior: "auto" }); + await waitFor(() => { return element_scrollend_arrived || document_scrollend_arrived; }, "Nested scrollIntoView did not receive scrollend event."); + assert_equals(root_element.scrollLeft, 0, "Nested scrollIntoView root_element scrollLeft"); + assert_equals(root_element.scrollTop, 0, "Nested scrollIntoView root_element scrollTop"); + assert_equals(out_div.scrollLeft, 100, "Nested scrollIntoView out_div scrollLeft"); + assert_equals(out_div.scrollTop, 100, "Nested scrollIntoView out_div scrollTop"); + assert_equals(target_div.scrollLeft, max_element_x, "Nested scrollIntoView target_div scrollLeft"); + assert_equals(target_div.scrollTop, max_element_y, "Nested scrollIntoView target_div scrollTop"); + assert_false(document_scrollend_arrived); + }, "Tests scrollend event for nested scrollIntoView."); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-document.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-document.html new file mode 100644 index 0000000000..797c2eb53d --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-document.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> + #hspacer { + height: 100px; + width: 100vw; + top: 0; + left: 200px; + /* on the right edge od targetDiv */ + position: absolute; + } + + #vspacer { + height: 100vh; + width: 100px; + position: absolute; + } + + #targetDiv { + width: 200px; + height: 200px; + overflow: scroll; + } + + #innerDiv { + width: 400px; + height: 400px; + } +</style> + +<body style="margin:0" onload=runTest()> + <div id="targetDiv"> + <div id="innerDiv"></div> + </div> + <div id="hspacer"></div> + <div id="vspacer"></div> +</body> + +<script> + var target_div = document.getElementById('targetDiv'); + async function resetScrollers(test) { + await waitForScrollReset(test, target_div); + await waitForScrollReset(test, document.scrollingElement); + } + + function fail() { + assert_true(false); + } + + function runTest() { + promise_test(async (t) => { + await resetScrollers(t); + await waitForCompositorCommit(); + + assert_equals(document.scrollingElement.scrollTop, 0, + "document should not be scrolled"); + + let scrollend_promise = waitForScrollendEvent(t, target_div); + let max_target_div_scroll_top = target_div.scrollHeight - target_div.clientHeight; + target_div.scrollTo({ top: target_div.scrollHeight, left: 0 }); + await scrollend_promise; + assert_approx_equals(target_div.scrollTop, max_target_div_scroll_top, 1, + "target_div should be fully scrolled down"); + + scrollend_promise = waitForScrollendEvent(t, document, 2000); + target_div.addEventListener("scrollend", fail); + // Scroll up on target div and wait for the doc to get scrollend event. + await scrollElementDown(target_div, target_div.clientHeight + 25); + await scrollend_promise; + + target_div.removeEventListener("scrollend", fail); + assert_greater_than(document.scrollingElement.scrollTop, target_div.clientHeight - 1, + "document is scrolled by the height of target_div"); + }, "testing, vertical"); + + promise_test(async (t) => { + await resetScrollers(t); + await waitForCompositorCommit(); + + assert_equals(document.scrollingElement.scrollLeft, 0, + "document should not be scrolled"); + + let scrollend_promise = waitForScrollendEvent(t, target_div); + let max_target_div_scroll_left = target_div.scrollWidth - target_div.clientWidth; + target_div.scrollTo({ left: target_div.scrollWidth, top: 0 }); + await scrollend_promise; + assert_approx_equals(target_div.scrollLeft, max_target_div_scroll_left, 1, + "target_div should be fully scrolled right"); + + scrollend_promise = waitForScrollendEvent(t, document, 2000); + target_div.addEventListener("scrollend", fail); + await scrollElementLeft(target_div, target_div.clientWidth + 25); + await scrollend_promise; + + target_div.removeEventListener("scrollend", fail); + assert_greater_than(document.scrollingElement.scrollLeft, target_div.clientWidth - 1, + "document is scrolled by the height of target_div"); + }, "testing, horizontal"); + } +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html new file mode 100644 index 0000000000..edda88e7cb --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html @@ -0,0 +1,173 @@ +<!DOCTYPE HTML> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> + #overscrollXDiv { + width: 200px; + height: 200px; + overflow: scroll; + overscroll-behavior-x: contain; + border: solid 1px black; + display: grid; + /* Places content and targetXDiv beside each other. */ + grid-template-columns: 500px 100px; + } + + #overscrollYDiv { + width: 200px; + height: 200px; + overflow: scroll; + overscroll-behavior-y: none; + border: solid 1px black; + } + + #targetXDiv { + width: 100px; + height: 100px; + overflow: scroll; + border: solid 1px black; + } + + #targetYDiv { + width: 100px; + height: 100px; + overflow: scroll; + border: solid 1px black; + } + + .content { + width: 500px; + height: 500px; + } + + #spacer { + height: 200vh; + width: 200vw; + border: solid 1px black; + } +</style> + +<body style="margin:0" onload=runTest()> + <div id="overscrollXDiv"> + <div class="content"></div> + <div id="targetXDiv"> + <div class="content"> + </div> + </div> + </div> + <div id="overscrollYDiv"> + <div class="content"></div> + <!-- Place targetYDiv below content so that is in view when + overscrollYDiv is scrolled all the way down --> + <div id="targetYDiv"> + <div class="content"> + </div> + </div> + </div> + <div id="spacer"></div> +</body> + +<script> + var horizontal_scrollend_arrived = false; + var vertical_scrollend_arrived = false; + let scrollers = [document.scrollingElement, targetXDiv, targetYDiv, + overscrollXDiv, overscrollYDiv]; + + async function resetScrollers(test) { + for (const scroller of scrollers) { + await resetTargetScrollState(test, scroller); + } + } + function onHorizontalScrollEnd(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + horizontal_scrollend_arrived = true; + } + function onVerticalScrollEnd(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + vertical_scrollend_arrived = true; + } + // Test that both "onscrollend" and addEventListener("scrollend"... work. + document.getElementById('overscrollXDiv').onscrollend = onHorizontalScrollEnd; + document.getElementById('overscrollYDiv'). + addEventListener("scrollend", onVerticalScrollEnd); + + function runTest() { + promise_test(async (t) => { + await resetScrollers(t); + await waitForCompositorCommit(); + + // Make sure that no scrollend event is sent to document. + document.addEventListener("scrollend", + t.unreached_func("Document got unexpected scrollend event.")); + let scrollend_promise; + + scrollend_promise = waitForScrollendEvent(t, targetXDiv, 2000); + targetXDiv.scrollLeft = targetXDiv.scrollWidth; + await scrollend_promise; + + scrollend_promise = waitForScrollendEvent(t, overscrollXDiv, 2000); + overscrollXDiv.scrollLeft = overscrollXDiv.scrollWidth; + await scrollend_promise; + horizontal_scrollend_arrived = false; + + assert_equals(targetXDiv.scrollLeft, + targetXDiv.scrollWidth - targetXDiv.clientWidth); + assert_equals(overscrollXDiv.scrollLeft, + overscrollXDiv.scrollWidth - overscrollXDiv.clientWidth); + // Attempt to scroll targetXDiv further to the right. + // targetXDiv and overscrollXDiv are already fully scrolled right but the + // scroll should not propagate to the document because of + // overscroll-behavior-x: contain on overscrollXDiv. + let touchEndPromise = new Promise((resolve) => { + targetXDiv.addEventListener("touchend", resolve); + }); + await touchScrollInTarget(100, targetXDiv, 'right'); + // The scrollend event should never be fired before the gesture has + // completed. + await touchEndPromise; + + scrollend_promise = waitForScrollendEvent(t, targetYDiv, 2000); + targetYDiv.scrollTop = targetXDiv.scrollHeight; + await scrollend_promise; + + scrollend_promise = waitForScrollendEvent(t, overscrollYDiv, 2000); + overscrollYDiv.scrollTop = overscrollYDiv.scrollHeight; + await scrollend_promise; + vertical_scrollend_arrived = false; + + assert_equals(targetYDiv.scrollTop, + targetYDiv.scrollHeight - targetYDiv.clientHeight); + assert_equals(overscrollYDiv.scrollTop, + overscrollYDiv.scrollHeight - overscrollYDiv.clientHeight); + // Attempt to scroll targetYDiv further down. + // targetYDiv and overscrollYDiv are already fully scrolled down but the + // scroll should not propagate to the document because of + // overscroll-behavior-y: none on overscrollYDiv. + touchEndPromise = new Promise((resolve) => { + targetYDiv.addEventListener("touchend", resolve); + }); + await touchScrollInTarget(50, targetYDiv, 'down'); + // The scrollend event should never be fired before the gesture has + // completed. + await touchEndPromise; + + // Ensure we wait at least a tick after the touch end. + await waitForCompositorCommit(); + + // We should not trigger a scrollend event for a scroll that did not + // change the scroll position. + assert_equals(horizontal_scrollend_arrived, false, + "overscrollXDiv should not receive scrollend"); + assert_equals(vertical_scrollend_arrived, false, + "overscrollYDiv should not receive scrollend"); + }, "Tests that the scroll is not propagated beyond div with non-auto " + + "overscroll-behavior."); + } +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-window.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-window.html new file mode 100644 index 0000000000..faacf7e572 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-window.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> + #spacer { + height: 100vh; + width: 100px; + } + #targetDiv { + width: 200px; + height: 200px; + overflow: scroll; + } + #innerDiv { + width: 400px; + height: 400px; + } +</style> + +<body style="margin:0" onload=runTest()> + <div id="targetDiv"> + <div id="innerDiv"></div> + </div> + <div id="spacer"></div> +</body> + +<script> + var target_div = document.getElementById('targetDiv'); + async function resetScrollers(test) { + await waitForScrollReset(test, target_div); + await waitForScrollReset(test, document.scrollingElement); + } + + function fail() { + assert_true(false); + } + + function runTest() { + promise_test(async (t) => { + await resetScrollers(t); + await waitForCompositorCommit(); + + assert_equals(document.scrollingElement.scrollTop, 0, + "document should not be scrolled"); + + let scrollend_promise = waitForScrollendEvent(t, target_div); + let max_target_div_scroll_top = target_div.scrollHeight - target_div.clientHeight; + target_div.scrollTo({ top: target_div.scrollHeight, left: 0 }); + await scrollend_promise; + assert_approx_equals(target_div.scrollTop, max_target_div_scroll_top, 1, + "target_div should be fully scrolled down"); + + scrollend_promise = waitForScrollendEvent(t, window, 2000); + target_div.addEventListener("scrollend", fail); + // Scroll up on target div and wait for the doc to get scrollend event. + await scrollElementDown(target_div, target_div.clientHeight + 25); + await scrollend_promise; + + target_div.removeEventListener("scrollend", fail); + assert_greater_than(document.scrollingElement.scrollTop, target_div.clientHeight - 1, + "document is scrolled by the height of target_div"); + }, "testing, vertical"); + } +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-for-user-scroll.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-for-user-scroll.html new file mode 100644 index 0000000000..561c90ca94 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-for-user-scroll.html @@ -0,0 +1,187 @@ +<!DOCTYPE html> +<html> +<head> + <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> + #targetDiv { + width: 200px; + height: 200px; + overflow: scroll; + } + + #innerDiv { + width: 400px; + height: 400px; + } +</style> +</head> +<body style="margin:0" onload=runTest()> + <div id="targetDiv"> + <div id="innerDiv"> + </div> + </div> +</body> + +<script> +var target_div = document.getElementById('targetDiv'); + +function runTest() { + promise_test(async (t) => { + // Skip the test on a Mac as they do not support touch screens. + const isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; + if (isMac) + return; + + await resetTargetScrollState(t, target_div); + await waitForCompositorReady(); + + const timeout = 3000; // Because we have two pauses we need longer timeout + const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div, timeout); + verifyNoScrollendOnDocument(t); + + let scrollend_count = 0; + const scrollend_listener = () => { + scrollend_count += 1; + }; + target_div.addEventListener("scrollend", scrollend_listener); + t.add_cleanup(() => { + target_div.removeEventListener('scrollend', scrollend_listener); + }); + + // Perform a touch drag on target div and wait for target_div to get + // a scrollend event. + await new test_driver.Actions() + .addPointer('TestPointer', 'touch') + .pointerMove(0, 0, {origin: target_div}) // 0, 0 is center of element. + .pointerDown() + .addTick() + .pointerMove(0, -40, {origin: target_div}) // Drag up to move down. + .addTick() + .pause(200) // Prevent inertial scroll. + .pointerMove(0, -60, {origin: target_div}) + .addTick() + .pause(200) // Prevent inertial scroll. + .pointerUp() + .send(); + + await targetScrollendPromise; + + assert_true(target_div.scrollTop > 0); + await verifyScrollStopped(t, target_div); + assert_equals(scrollend_count, 1); + }, 'Tests that the target_div gets scrollend event when touch dragging.'); + + promise_test(async (t) => { + // Skip test on platforms that do not have a visible scrollbar (e.g. + // overlay scrollbar). + const scrollbar_width = target_div.offsetWidth - target_div.clientWidth; + if (scrollbar_width == 0) + return; + + await resetTargetScrollState(t, target_div); + await waitForCompositorReady(); + + const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div); + verifyNoScrollendOnDocument(t); + + const bounds = target_div.getBoundingClientRect(); + // Some versions of webdriver have been known to frown at non-int arguments + // to pointerMove. + const x = bounds.right - Math.round(scrollbar_width / 2); + const y = bounds.bottom - 20; + await new test_driver.Actions() + .addPointer('TestPointer', 'mouse') + .pointerMove(x, y, {origin: 'viewport'}) + .pointerDown() + .addTick() + .pointerUp() + .send(); + + await targetScrollendPromise; + assert_true(target_div.scrollTop > 0); + await verifyScrollStopped(t, target_div); + }, 'Tests that the target_div gets scrollend event when clicking ' + + 'scrollbar.'); + + // Same issue as previous test. + promise_test(async (t) => { + // Skip test on platforms that do not have a visible scrollbar (e.g. + // overlay scrollbar). + const scrollbar_width = target_div.offsetWidth - target_div.clientWidth; + if (scrollbar_width == 0) + return; + + await resetTargetScrollState(t, target_div); + await waitForCompositorReady(); + + const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div, 1000); + verifyNoScrollendOnDocument(t); + + const bounds = target_div.getBoundingClientRect(); + // Some versions of webdriver have been known to frown at non-int arguments + // to pointerMove. + const x = bounds.right - Math.round(scrollbar_width / 2); + const y = bounds.top + 30; + const dy = 30; + await new test_driver.Actions() + .addPointer('TestPointer', 'mouse') + .pointerMove(x, y, { origin: 'viewport' }) + .pointerDown() + .pointerMove(x, y + dy, { origin: 'viewport' }) + .addTick() + .pointerUp() + .send(); + + await targetScrollendPromise; + assert_true(target_div.scrollTop > 0); + await verifyScrollStopped(t, target_div); + }, 'Tests that the target_div gets scrollend event when dragging the ' + + 'scrollbar thumb.'); + + promise_test(async (t) => { + await resetTargetScrollState(t, target_div); + await waitForCompositorReady(); + + const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div); + verifyNoScrollendOnDocument(t); + + const x = 0; + const y = 0; + const dx = 0; + const dy = 40; + const duration_ms = 10; + await new test_driver.Actions() + .scroll(x, y, dx, dy, { origin: target_div }, duration_ms) + .send(); + + await targetScrollendPromise; + assert_true(target_div.scrollTop > 0); + await verifyScrollStopped(t, target_div); + }, 'Tests that the target_div gets scrollend event when mouse wheel ' + + 'scrolling.'); + + promise_test(async (t) => { + await resetTargetScrollState(t, target_div); + await waitForCompositorReady(); + + verifyNoScrollendOnDocument(t); + const targetScrollendPromise = createScrollendPromiseForTarget(t, target_div); + + target_div.focus(); + window.test_driver.send_keys(target_div, '\ue015'); + + await targetScrollendPromise; + assert_true(target_div.scrollTop > 0); + await verifyScrollStopped(t, target_div); + }, 'Tests that the target_div gets scrollend event when sending DOWN key ' + + 'to the target.'); +} + +</script> +</html> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-handler-content-attributes.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-handler-content-attributes.html new file mode 100644 index 0000000000..47f563c39b --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-handler-content-attributes.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> +html, body { + margin: 0 +} + +body { + height: 3000px; + width: 3000px; +} + +#targetDiv { + width: 200px; + height: 200px; + overflow: scroll; +} + +#innerDiv { + width: 400px; + height: 400px; +} +</style> + +<body onload=runTest() onscrollend="failOnScrollEnd(event)"> +<div id="targetDiv" onscrollend="onElementScrollEnd(event)"> + <div id="innerDiv"> + </div> +</div> +</body> +<script> +let element_scrollend_arrived = false; + +function onElementScrollEnd(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + element_scrollend_arrived = true; +} + +function failOnScrollEnd(event) { + assert_true(false, "Scrollend should not be called on: " + event.target); +} + +function runTest() { + let target_div = document.getElementById("targetDiv"); + + promise_test (async (t) => { + await waitForCompositorCommit(); + + target_div.scrollTo({top: 200, left: 200}); + await waitFor(() => { return element_scrollend_arrived; }, + target_div.tagName + " did not receive scrollend event."); + assert_equals(target_div.scrollLeft, 200, target_div.tagName + " scrollLeft"); + assert_equals(target_div.scrollTop, 200, target_div.tagName + " scrollTop"); + }, "Tests scrollend event is handled by event handler content attribute."); + + promise_test (async (t) => { + await waitForCompositorCommit(); + + document.scrollingElement.scrollTo({top: 200, left: 200}); + // The document body onscrollend event handler content attribute will fail + // here, if it is fired. + await waitForCompositorCommit(); + assert_equals(document.scrollingElement.scrollLeft, 200, + "Document scrolled on horizontal axis"); + assert_equals(document.scrollingElement.scrollTop, 200, + "Document scrolled on vertical axis"); + }, "Tests scrollend event is not fired to document body event handler content attribute."); + + promise_test (async (t) => { + await waitForCompositorCommit(); + + // Reset the scroll position. + document.scrollingElement.scrollTo({top: 0, left: 0}); + + let scrollend_event = new Promise(resolve => document.onscrollend = resolve); + document.scrollingElement.scrollTo({top: 200, left: 200}); + await scrollend_event; + + assert_equals(document.scrollingElement.scrollTop, 200, + "Document scrolled on horizontal axis"); + assert_equals(document.scrollingElement.scrollLeft, 200, + "Document scrolled on vertical axis"); + }, "Tests scrollend event is fired to document event handler property"); + + promise_test (async (t) => { + await waitForCompositorCommit(); + + // Reset the scroll position. + target_div.scrollTo({top: 0, left: 0}); + + let scrollend_event = new Promise(resolve => target_div.onscrollend = resolve); + target_div.scrollTo({top: 200, left: 200}); + await scrollend_event; + + assert_equals(target_div.scrollLeft, 200, + target_div.tagName + " scrolled on horizontal axis"); + assert_equals(target_div.scrollLeft, 200, + target_div.tagName + " scrolled on vertical axis"); + }, "Tests scrollend event is fired to element event handler property"); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-after-removing-scroller.tentative.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-after-removing-scroller.tentative.html new file mode 100644 index 0000000000..95447fbd12 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-after-removing-scroller.tentative.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> +#rootDiv { + width: 500px; + height: 500px; +} + +#targetDiv { + width: 200px; + height: 200px; + overflow: scroll; +} + +#innerDiv { + width: 500px; + height: 4000px; +} +</style> + +<body style="margin:0" onload=runTest()> +</body> + +<script> +let scrollend_arrived = false; + +async function setupHtmlAndScrollAndRemoveElement(element_to_remove_id) { + document.body.innerHTML=` + <div id="rootDiv"> + <div id="targetDiv"> + <div id="innerDiv"> + </div> + </div> + </div> + `; + await waitForCompositorCommit(); + + const target_div = document.getElementById('targetDiv'); + const element_to_remove = document.getElementById(element_to_remove_id); + let reached_half_scroll = false; + scrollend_arrived = false; + + target_div.addEventListener("scrollend", () => { + scrollend_arrived = true; + }); + + target_div.onscroll = () => { + // Remove the element after reached half of the scroll offset, + if(target_div.scrollTop >= 1000) { + reached_half_scroll = true; + element_to_remove.remove(); + } + }; + + target_div.scrollTo({top:2000, left:0, behavior:"smooth"}); + await waitFor(() => {return reached_half_scroll; }, + "target_div never reached scroll offset of 1000"); + await waitForCompositorCommit(); +} + +function runTest() { + promise_test (async (t) => { + await setupHtmlAndScrollAndRemoveElement("rootDiv"); + await conditionHolds(() => { return !scrollend_arrived; }); + }, "No scrollend is received after removing parent div"); + + promise_test (async (t) => { + await setupHtmlAndScrollAndRemoveElement("targetDiv"); + await conditionHolds(() => { return !scrollend_arrived; }); + }, "No scrollend is received after removing scrolling element"); + + promise_test (async (t) => { + await setupHtmlAndScrollAndRemoveElement("innerDiv"); + await waitFor(() => { return scrollend_arrived; }, + 'target_div did not receive scrollend event after vertical scroll.'); + }, "scrollend is received after removing descendant div"); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-on-no-scroll.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-on-no-scroll.html new file mode 100644 index 0000000000..eaa345aee9 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-not-fired-on-no-scroll.html @@ -0,0 +1,114 @@ +<!DOCTYPE HTML> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<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="scroll_support.js"></script> +<style> + #spacer { + height: 100vh; + width: 100px; + position: relative; + } + + #targetDiv { + width: 200px; + height: 200px; + overflow: scroll; + } + + #innerDiv { + width: 400px; + height: 400px; + } +</style> + +<body style="margin:0" onload=runTest()> + <div id="targetDiv"> + <!-- This test uses button elements as a consistent mechanism for + ensuring that focus is on the correct scrolling element when + scrolling via keys --> + <button id="targetButton">target</button> + <div id="innerDiv"></div> + </div> + <button id="docButton">doc</button> + <div id="spacer"></div> +</body> + +<script> + var target_div = document.getElementById('targetDiv'); + + async function resetScrollers(test) { + await waitForScrollReset(test, target_div); + await waitForScrollReset(test, document.scrollingElement); + } + + function getBoundingClientRect(element) { + if (element == document) { + return document.documentElement.getBoundingClientRect(); + } + return element.getBoundingClientRect(); + } + + async function upwardScroll(scrolling_element, button_element, scroll_type) { + if (scroll_type == "wheel") { + let x = 0; + let y = 0; + let delta_x = 0; + let delta_y = -50; + let actions = new test_driver.Actions() + .scroll(x, y, delta_x, delta_y, {origin: scrolling_element}); + await actions.send(); + } else if (scroll_type == "keys") { + const num_keydowns = 5; + const arrowUp = '\uE013'; + for (let i = 0; i < num_keydowns; i++) { + await test_driver.send_keys(button_element, arrowUp); + } + } + } + + async function testScrollendNotFiredOnNoScroll(test, scrolling_element, + listening_element, + button_element, scroll_type) { + await resetScrollers(test); + await waitForCompositorCommit(); + + assert_greater_than(scrolling_element.scrollHeight, + scrolling_element.clientHeight); + assert_equals(scrolling_element.scrollTop, 0); + + let scrollend_promise = waitForScrollendEvent(test, listening_element); + await upwardScroll(scrolling_element, button_element, scroll_type); + await scrollend_promise.then( + (/*resolve*/) => { + assert_true(false, "no scroll, so no scrollend expected"); + }, + (/*reject*/) => { /* Did not see scrollend, which is okay. */ } + ); + } + + function runTest() { + promise_test(async (t) => { + await testScrollendNotFiredOnNoScroll(t, target_div, target_div, + targetButton, "wheel"); + }, "No scroll via wheel on div shouldn't fire scrollend."); + + promise_test(async (t) => { + await testScrollendNotFiredOnNoScroll(t, target_div, target_div, + targetButton, "keys"); + }, "No scroll via keys on div shouldn't fire scrollend."); + + promise_test(async (t) => { + await testScrollendNotFiredOnNoScroll(t, document.scrollingElement, + document, docButton, "wheel"); + }, "No scroll via wheel on document shouldn't fire scrollend."); + + promise_test(async (t) => { + await testScrollendNotFiredOnNoScroll(t, document.scrollingElement, + document, docButton, "keys"); + }, "No scroll via keys on document shouldn't fire scrollend.") + } +</script> |