diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/dom/events/scrolling | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/dom/events/scrolling')
19 files changed, 1733 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..6f0b77f22e --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/overscroll-deltas.html @@ -0,0 +1,85 @@ +<!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: 500px; + height: 500px; + background: red; +} +html, body { + /* Prevent any built-in browser overscroll features from consuming the scroll + * deltas */ + overscroll-behavior: none; +} + +</style> + +<body style="margin:0;" onload=runTest()> +<div id="targetDiv"> +</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); +} + +function onScrollend(event) { + scrollend_received = true; +} + +document.addEventListener("overscroll", onOverscroll); +document.addEventListener("scrollend", onScrollend); + +function runTest() { + promise_test (async (t) => { + 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 scrollend_received; }, + 'Document did not receive scrollend event.'); + + // Even though we request 300 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 100 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_less_than_equal(Math.max(...overscrolled_y_deltas), 0, "The deltaY attribute must be <= 0 when there is overscrolling in up direction."); + assert_less_than_equal(Math.min(...overscrolled_y_deltas),-100, "The deltaY attribute must be the number of pixels overscrolled."); + + await waitForCompositorCommit(); + overscrolled_x_deltas = []; + overscrolled_y_deltas = []; + scrollend_received = false; + + // Scroll left on target div and wait for the doc to get overscroll event. + await touchScrollInTarget(300, target_div, 'left'); + await waitFor(() => { return scrollend_received; }, + 'Document did not receive scrollend event.'); + + // 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_y_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), 0, "The deltaX attribute must be <= 0 when there is overscrolling in left direction."); + assert_less_than_equal(Math.min(...overscrolled_x_deltas),-50, "The deltaX attribute must be the number of pixels overscrolled."); + + }, 'Tests that the document gets overscroll event with right deltaX/Y attributes.'); +} +</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..169393e4c3 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scroll_support.js @@ -0,0 +1,163 @@ +async function waitForScrollendEvent(test, target, timeoutMs = 500) { + return new Promise((resolve, reject) => { + const timeoutCallback = test.step_timeout(() => { + reject(`No Scrollend event received for target ${target}`); + }, timeoutMs); + target.addEventListener('scrollend', (evt) => { + clearTimeout(timeoutCallback); + resolve(evt); + }, { once: true }); + }); +} + +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); + }); + }); +} + +// 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 +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); + } + + 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); + }); +} 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..77bf029ced --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html @@ -0,0 +1,63 @@ +<!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: 500px; + 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'); +let scrollend_arrived = false; +let scrollend_event_count = 0; + +function onScrollEnd(event) { + assert_false(event.cancelable); + assert_false(event.bubbles); + scrollend_arrived = true; + scrollend_event_count += 1; +} + +function runTest() { + promise_test (async (t) => { + // Make sure that no scrollend event is sent to document. + document.addEventListener("scrollend", + t.unreached_func("document got unexpected scrollend event.")); + await waitForCompositorCommit(); + + // Scroll down & up & down on target div and wait for the target_div to get scrollend event. + target_div.addEventListener("scrollend", onScrollEnd); + const move_path = [ + { x: 0, y: -300}, // down + { x: 0, y: -100}, // up + { x: 0, y: -400}, // down + { x: 0, y: -200}, // up + ]; + await touchScrollInTargetSequentiallyWithPause(target_div, move_path, 150); + + await waitFor(() => {return scrollend_arrived;}, + 'target_div did not receive scrollend event after sequence of scrolls on target.'); + assert_equals(scrollend_event_count, 1); + }, "Move down, up and down 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-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..3090455388 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-document.html @@ -0,0 +1,70 @@ +<!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: 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 horizontal_scrollend_arrived = false; +var vertical_scrollend_arrived = false; +function onHorizontalScrollEnd(event) { + assert_false(event.cancelable); + // scrollend events are bubbled when the target node is document. + assert_true(event.bubbles); + horizontal_scrollend_arrived = true; +} +function onVerticalScrollEnd(event) { + assert_false(event.cancelable); + // scrollend events are bubbled when the target node is document. + assert_true(event.bubbles); + vertical_scrollend_arrived = true; +} + +function runTest() { + promise_test (async (t) => { + // Make sure that no scrollend event is sent to target_div. + target_div.addEventListener("scrollend", + t.unreached_func("target_div got unexpected scrollend event.")); + await waitForCompositorCommit(); + + // Scroll left on target div and wait for the doc to get scrollend event. + document.addEventListener("scrollend", onHorizontalScrollEnd); + await touchScrollInTarget(300, target_div, 'left'); + await waitFor(() => { return horizontal_scrollend_arrived; }, + 'Document did not receive scrollend event after scroll left on target.'); + assert_equals(target_div.scrollLeft, 0); + document.removeEventListener("scrollend", onHorizontalScrollEnd); + + // Scroll up on target div and wait for the doc to get scrollend event. + document.addEventListener("scrollend", onVerticalScrollEnd); + await touchScrollInTarget(300, target_div, 'up'); + await waitFor(() => { return vertical_scrollend_arrived; }, + 'Document did not receive scrollend event after scroll up on target.'); + assert_equals(target_div.scrollTop, 0); + }, 'Tests that the document gets scrollend event when no element scrolls by ' + + 'touch.'); +} +</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..acad168e56 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html @@ -0,0 +1,102 @@ +<!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: 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 horizontal_scrollend_arrived = false; +var vertical_scrollend_arrived = false; +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) => { + // Make sure that no scrollend event is sent to document or target_div. + document.addEventListener("scrollend", + t.unreached_func("Document got unexpected scrollend event.")); + target_div.addEventListener("scrollend", + t.unreached_func("target_div got unexpected scrollend event.")); + await waitForCompositorCommit(); + + // Scroll left on target div and wait for the element with overscroll-x to + // get scrollend event. + await touchScrollInTarget(300, target_div, 'left'); + await waitFor(() => { return horizontal_scrollend_arrived; }, + 'Expected element did not receive scrollend event after scroll left ' + + 'on target.'); + assert_equals(target_div.scrollLeft, 0); + + let touchEndPromise = new Promise((resolve) => { + target_div.addEventListener("touchend", resolve); + }); + await touchScrollInTarget(300, target_div, 'up'); + + // 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(vertical_scrollend_arrived, false); + assert_equals(target_div.scrollTop, 0); + }, 'Tests that the last element in the cut scroll chain gets scrollend ' + + 'event when no element scrolls by touch.'); +} +</script> diff --git a/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-scrolled-element.html b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-scrolled-element.html new file mode 100644 index 0000000000..7343396942 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-scrolled-element.html @@ -0,0 +1,68 @@ +<!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> +#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 horizontal_scrollend_arrived = false; +var vertical_scrollend_arrived = false; +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; +} +scrolling_div.addEventListener("scrollend", onHorizontalScrollEnd); +scrolling_div.addEventListener("scrollend", onVerticalScrollEnd); + +function runTest() { + promise_test (async (t) => { + // Make sure that no scrollend event is sent to document. + document.addEventListener("scrollend", + t.unreached_func("Document got unexpected scrollend event.")); + await waitForCompositorCommit(); + + // Do a horizontal scroll and wait for scrollend event. + await touchScrollInTarget(300, scrolling_div, 'right'); + await waitFor(() => { return horizontal_scrollend_arrived; }, + 'Scroller did not receive scrollend event after horizontal scroll.'); + assert_equals(scrolling_div.scrollWidth - scrolling_div.scrollLeft, + scrolling_div.clientWidth); + + // Do a vertical scroll and wait for scrollend event. + await touchScrollInTarget(300, scrolling_div, 'down'); + await waitFor(() => { return vertical_scrollend_arrived; }, + 'Scroller did not receive scrollend event after vertical scroll.'); + assert_equals(scrolling_div.scrollHeight - scrolling_div.scrollTop, + scrolling_div.clientHeight); + }, 'Tests that the scrolled element gets scrollend event at the end of touch scrolling.'); +} +</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..ef72f56d2b --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-fired-to-window.html @@ -0,0 +1,55 @@ +<!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: 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 scrollend_arrived = false; +function onScrollEnd(event) { + assert_false(event.cancelable); + // scrollend events targetting document are bubbled to the window. + assert_true(event.bubbles); + scrollend_arrived = true; +} +window.addEventListener("scrollend", onScrollEnd); + +function runTest() { + promise_test (async (t) => { + // Make sure that no scrollend event is sent to target_div. + target_div.addEventListener("scrollend", + t.unreached_func("target_div got unexpected scrollend event.")); + await waitForCompositorCommit(); + + // Scroll up on target div and wait for the doc to get scrollend event. + await touchScrollInTarget(300, target_div, 'up'); + await waitFor(() => { return scrollend_arrived; }, + 'Window did not receive scrollend event after scroll up on target.'); + assert_equals(target_div.scrollTop, 0); + }, 'Tests that the window gets scrollend event when no element scrolls ' + + 'after touch scrolling.'); +} +</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..5146c5f719 --- /dev/null +++ b/testing/web-platform/tests/dom/events/scrolling/scrollend-event-for-user-scroll.html @@ -0,0 +1,199 @@ +<!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'); + +async function resetTargetScrollState(test) { + if (target_div.scrollTop != 0 || target_div.scrollLeft != 0) { + target_div.scrollTop = 0; + target_div.scrollLeft = 0; + return waitForScrollendEvent(test, target_div); + } +} + +async function verifyScrollStopped(test) { + 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(x, target_div.scrollLeft); + assert_equals(y, target_div.scrollTop); + resolve(); + }, unscaled_pause_time_in_ms); + }); +} + +async function verifyNoScrollendOnDocument(test) { + const callback = + test.unreached_func("window got unexpected scrollend event."); + window.addEventListener('scrollend', callback); +} + +async function createScrollendPromise(test) { + return waitForScrollendEvent(test, target_div).then(evt => { + assert_false(evt.cancelable, 'Event is not cancelable'); + assert_false(evt.bubbles, 'Event targeting element does not bubble'); + }); +} + +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); + await waitForCompositorCommit(); + + const targetScrollendPromise = createScrollendPromise(t); + verifyNoScrollendOnDocument(t); + + // 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. + .pointerUp() + .send(); + + await targetScrollendPromise; + + assert_true(target_div.scrollTop > 0); + await verifyScrollStopped(t); + }, '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); + await waitForCompositorCommit(); + + const targetScrollendPromise = createScrollendPromise(t); + verifyNoScrollendOnDocument(t); + + const bounds = target_div.getBoundingClientRect(); + const x = bounds.right - 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); + }, '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; + + resetTargetScrollState(t); + const targetScrollendPromise = createScrollendPromise(t); + verifyNoScrollendOnDocument(t); + + const bounds = target_div.getBoundingClientRect(); + const x = bounds.right - 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); + }, 'Tests that the target_div gets scrollend event when dragging the ' + + 'scrollbar thumb.'); + + promise_test(async (t) => { + resetTargetScrollState(t); + const targetScrollendPromise = createScrollendPromise(t); + 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); + }, 'Tests that the target_div gets scrollend event when mouse wheel ' + + 'scrolling.'); + + promise_test(async (t) => { + await resetTargetScrollState(t); + await waitForCompositorCommit(); + + verifyNoScrollendOnDocument(t); + const targetScrollendPromise = createScrollendPromise(t); + + target_div.focus(); + window.test_driver.send_keys(target_div, '\ue015'); + + await targetScrollendPromise; + assert_true(target_div.scrollTop > 0); + await verifyScrollStopped(t); + }, '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> |