test: update DOM events web platform tests

PR-URL: https://github.com/nodejs/node/pull/54642
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Mattias Buelens <mattias@buelens.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Khafra 2024-08-29 16:21:36 -04:00 committed by Antoine du Hamel
parent 0de1cf004c
commit b470e2fcb2
No known key found for this signature in database
GPG Key ID: 21D900FFDB233756
39 changed files with 2675 additions and 547 deletions

View File

@ -14,7 +14,7 @@ Last update:
- compression: https://github.com/web-platform-tests/wpt/tree/5aa50dd415/compression
- console: https://github.com/web-platform-tests/wpt/tree/767ae35464/console
- dom/abort: https://github.com/web-platform-tests/wpt/tree/d1f1ecbd52/dom/abort
- dom/events: https://github.com/web-platform-tests/wpt/tree/ab8999891c/dom/events
- dom/events: https://github.com/web-platform-tests/wpt/tree/0a811c5161/dom/events
- encoding: https://github.com/web-platform-tests/wpt/tree/5aa50dd415/encoding
- fetch/data-urls/resources: https://github.com/web-platform-tests/wpt/tree/7c79d998ff/fetch/data-urls/resources
- FileAPI: https://github.com/web-platform-tests/wpt/tree/cceaf3628d/FileAPI

View File

@ -87,6 +87,22 @@ async_test(function(t) { // as above with <a>
child.dispatchEvent(new MouseEvent("click", {bubbles:true}))
}, "pick the first with activation behavior <a href>")
async_test(function(t) {
var input = document.createElement("input")
input.type = "radio"
dump.appendChild(input)
input.onclick = t.step_func(function() {
assert_false(input.checked, "input pre-click must not be triggered")
})
var child = input.appendChild(document.createElement("input"))
child.type = "radio"
child.onclick = t.step_func(function() {
assert_true(child.checked, "child pre-click must be triggered")
})
child.dispatchEvent(new MouseEvent("click", {bubbles:true}))
t.done()
}, "pick the first with activation behavior <input type=radio>")
async_test(function(t) {
var input = document.createElement("input")
input.type = "checkbox"
@ -173,6 +189,46 @@ async_test(function(t) {
t.done()
}, "disabled checkbox still has activation behavior, part 2")
async_test(function(t) {
var state = "start"
var form = document.createElement("form")
form.onsubmit = t.step_func(() => {
if(state == "start" || state == "radio") {
state = "failure"
} else if(state == "form") {
state = "done"
}
return false
})
dump.appendChild(form)
var button = form.appendChild(document.createElement("button"))
button.type = "submit"
var radio = button.appendChild(document.createElement("input"))
radio.type = "radio"
radio.onclick = t.step_func(() => {
if(state == "start") {
assert_unreached()
} else if(state == "radio") {
assert_true(radio.checked)
}
})
radio.disabled = true
radio.click()
assert_equals(state, "start")
state = "radio"
radio.disabled = false
radio.click()
assert_equals(state, "radio")
state = "form"
button.click()
assert_equals(state, "done")
t.done()
}, "disabled radio still has activation behavior")
async_test(function(t) {
var input = document.createElement("input")
input.type = "checkbox"

View File

@ -19,7 +19,7 @@
<body>
<script>
// HTML elements that can be disabled
const formElements = ["button", "fieldset", "input", "select", "textarea"];
const formElements = ["button", "input", "select", "textarea"];
test(() => {
for (const localName of formElements) {

View File

@ -0,0 +1,164 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title> Only one activation behavior is executed during dispatch</title>
<link rel="author" title="Vincent Hilla" href="mailto:vhilla@mozilla.com">
<link rel="help" href="https://dom.spec.whatwg.org/#eventtarget-activation-behavior">
<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id=log></div>
<div id=test_container></div>
<!--
Three classes:
click
Element to be clicked to cause activation behavior
activates
Element that registers the activation behavior
container
Element in which other elements with activation behavior are placed.
We test that those won't be activated too.
-->
<template>
<!--input, change event bubble, so have to check if checked is true-->
<input class="click activates container" type="checkbox" oninput="this.checked ? activated(this) : null">
<input class="click activates container" type="radio" oninput="this.checked ? activated(this) : null">
<form onsubmit="activated(this); return false" class="activates">
<input class="click container" type="submit">
</form>
<form onsubmit="activated(this); return false" class="activates">
<input class="click container" type="image">
</form>
<form onreset="activated(this)" class="activates">
<input class="click container" type="reset">
</form>
<form onsubmit="activated(this); return false" class="activates">
<button class="click container" type="submit"></button>
</form>
<form onreset="activated(this)" class="activates">
<button class="click container" type="reset"></button>
</form>
<a href="#link" class="click container activates"></a>
<area href="#link" class="click container activates">
<details ontoggle="activated(this)" class="activates">
<summary class="click container"></summary>
</details>
<label>
<input type=checkbox onclick="this.checked ? activated(this) : null" class="activates">
<span class="click container">label</span>
</label>
<!--activation behavior of label for event targeted at interactive content descendant is to do nothing-->
<label class="container">
<button class="click" type="button"></button>
</label>
</template>
<script>
let activations = [];
function activated(e) {
activations.push(e);
}
function getActivations(testidx) {
return activations.filter(a =>
(a.endsWith && a.endsWith("test"+testidx+"_link"))
|| (a.classList && a.classList.contains("test"+testidx))
);
}
// for a and area elements
window.onhashchange = function(e) {
if (e.newURL.endsWith("link")) {
activated(e.newURL);
}
window.location.hash = "";
};
function getElementsByClassNameInclusive(e, clsname) {
let ls = Array.from(e.getElementsByClassName(clsname));
if (e.classList.contains(clsname)) ls.push(e);
return ls;
}
function getClickTarget(e) {
return getElementsByClassNameInclusive(e, "click")[0];
}
function getContainer(e) {
return getElementsByClassNameInclusive(e, "container")[0];
}
function getExpectedActivations(e) {
let ls = getElementsByClassNameInclusive(e, "activates");
// special case, for a and area the window registers the activation
// have to use string, as testrunner cannot stringify the window object
ls = ls.map(e => e.tagName === "A" || e.tagName === "AREA" ? e.href : e);
return ls;
}
function toString(e) {
const children = Array.from(e.children);
const childstr = (children.map(toString)).join("");
const tag = e.tagName;
const typestr = e.type ? " type="+e.type : "";
return `<${tag}${typestr}>${childstr}</${tag}>`;
}
// generate O(n^2) test combinations
const template = document.querySelector("template");
const elements = Array.from(template.content.children);
const tests = []
for (const target of elements) {
for (const parent of elements) {
if (target === parent) continue;
tests.push([target.cloneNode(true), parent.cloneNode(true)])
}
}
const test_container = document.getElementById("test_container");
/**
* Test that if two elements in an event target chain have activation behavior,
* only one of them will be activated.
*
* Each child of <template> represents one case of activation behavior.
* The behavior should be triggered by clicking the element of class click
* and will manifest as a call to activated().
*
* For each [target, parent] in tests, we make target a descendant of parent
* and test that only target gets activated when dispatching a click.
*/
for (let i = 0; i < tests.length; i++) {
let [target, parent] = tests[i];
async_test(function(t) {
let test = document.createElement("div");
test_container.appendChild(test);
test.appendChild(parent);
getContainer(parent).appendChild(target);
// for later filtering out the activations belonging to this test
for (let e of test.getElementsByClassName("activates")) {
e.classList.add("test"+i);
}
for (let e of test.querySelectorAll("a, area")) {
e.href = "#test"+i+"_link";
}
getClickTarget(target).click();
// Need to spin event loop twice, as some clicks might dispatch another task
t.step_timeout(() => {
t.step_timeout(t.step_func_done(() => {
assert_array_equals(getActivations(i), getExpectedActivations(target));
}), 0);
}, 0);
t.add_cleanup(function() {
test_container.removeChild(test);
});
}, `When clicking child ${toString(target)} of parent ${toString(parent)}, only child should be activated.`);
}
</script>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
function createIframe(t, srcdoc = '') {
let iframe = document.createElement('iframe');
iframe.srcdoc = srcdoc;
t.add_cleanup(() => iframe.remove());
return new Promise((resolve, reject) => {
iframe.addEventListener('load', () => resolve(iframe.contentWindow));
document.body.appendChild(iframe);
});
}
// Returns a promise which will resolve with the next error event fired at any
// of `windows`, after the invocation of this function. Once one does, this
// function removes its listeners and produces that error event so that it can
// be examined (most notably for which global proxy it was targeted at).
async function nextErrorEvent(windows) {
let listener;
let p = new Promise((resolve, reject) => {
listener = (event) => { resolve(event); event.preventDefault(); };
});
for (let w of windows) {
w.addEventListener('error', listener);
}
try {
return await p;
} finally {
for (let w of windows) {
w.removeEventListener('error', listener);
}
}
}
promise_test(async t => {
let w = await createIframe(t, `<script>function listener() { throw new Error(); }<`+`/script>`);
let w2 = await createIframe(t);
let target = new w2.EventTarget();
target.addEventListener('party', w.listener);
let nextErrorPromise = nextErrorEvent([self, w, w2]);
target.dispatchEvent(new Event('party'));
let errorEvent = await nextErrorPromise;
if (errorEvent.error) {
assert_true(errorEvent.error instanceof w.Error, 'error should be an instance created inside the listener function');
}
assert_equals(errorEvent.target, w, `error event should target listener's global but instead targets ${event.currentTarget === w2 ? 'target\'s global' : 'test harness global'}`);
}, 'exception thrown in event listener function should result in error event on listener\'s global');
promise_test(async t => {
let w = await createIframe(t, `<script>listener = {};<`+`/script>`);
let w2 = await createIframe(t, `<script>handleEvent = () => { throw new Error; };<`+`/script>`);
let w3 = await createIframe(t);
w.listener.handleEvent = w2.handleEvent;
let target = new w3.EventTarget();
target.addEventListener('party', w.listener);
let nextErrorPromise = nextErrorEvent([self, w, w2, w3]);
target.dispatchEvent(new Event('party'));
let errorEvent = await nextErrorPromise;
if (errorEvent.error) {
assert_true(errorEvent.error instanceof w2.Error, 'error should be an instance created inside the listener function');
}
assert_equals(errorEvent.target, w, `error event should target listener's global but instead targets ${event.currentTarget === w2 ? 'target\'s global' : event.currentTarget === w3 ? 'function\'s global' : 'test harness global'}`);
}, 'exception thrown in event listener interface object should result in error event on listener\'s global');
</script>
</body>

View File

@ -23,6 +23,23 @@ test(() => {
assert_equals(callCount, 2);
}, "A constructed EventTarget can be used as expected");
test(() => {
const target = new EventTarget();
const event = new Event("foo");
function listener(e) {
assert_equals(e, event);
assert_equals(e.target, target);
assert_equals(e.currentTarget, target);
assert_array_equals(e.composedPath(), [target]);
}
target.addEventListener("foo", listener, { once: true });
target.dispatchEvent(event);
assert_equals(event.target, target);
assert_equals(event.currentTarget, null);
assert_array_equals(event.composedPath(), []);
}, "A constructed EventTarget implements dispatch correctly");
test(() => {
class NicerEventTarget extends EventTarget {
on(...args) {

View File

@ -114,4 +114,14 @@ async_test(t => {
target.dispatchEvent(new Event("click"));
}, "window.event is set to the current event, which is the event passed to dispatch");
async_test(t => {
let target = new XMLHttpRequest();
target.onload = t.step_func_done(e => {
assert_equals(e, window.event);
});
target.dispatchEvent(new Event("load"));
}, "window.event is set to the current event, which is the event passed to dispatch (2)");
</script>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<link rel="help" href="https://crbug.com/341104769">
<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>
<template>
<p>TEST</p>
</template>
<body>
<script>
const clone = document.querySelector("template").content.cloneNode(true);
const p = clone.querySelector("p");
let gotEvent = false;
p.addEventListener("pointerup", () => {
gotEvent = true;
});
document.body.append(clone);
promise_test(async () => {
await test_driver.click(document.querySelector("p"));
assert_true(gotEvent);
}, "Moving a node to new document should move the registered event listeners together");
</script>

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<title>preventDefault during activation behavior</title>
<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1197032">
<link rel="help" href="https://html.spec.whatwg.org/multipage/C#the-button-element">
<link rel="help" href="https://dom.spec.whatwg.org/#dispatching-events">
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
<link rel="author" title="Google" href="http://www.google.com/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<form id="f">
<input type="submit" id="b">
</form>
<script>
promise_test(async () => {
let form = document.getElementById("f");
let button = document.getElementById("b");
let cached_event;
let submit_fired = false;
let click_promise = new Promise((resolve, reject) => {
button.addEventListener("click", event => {
assert_false(submit_fired);
cached_event = event;
resolve();
});
});
form.addEventListener("submit", event => {
assert_false(submit_fired);
assert_true(!!cached_event, "click should have fired");
// Call preventDefault on the click event during its activation
// behavior, to test the bug that we're trying to test.
cached_event.preventDefault();
// prevent the form submission from navigating the page
event.preventDefault();
submit_fired = true;
});
assert_false(submit_fired);
button.click();
await click_promise;
assert_true(submit_fired);
}, "behavior of preventDefault during activation behavior");
</script>

View File

@ -0,0 +1,95 @@
<!doctype html>
<title>Various edge cases where listeners are removed during iteration</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
test(function() {
var type = "foo";
var target = document.createElement("div");
var listener1CallCount = 0;
var listener2CallCount = 0;
var listener3CallCount = 0;
function listener1() {
listener1CallCount++;
target.removeEventListener(type, listener1);
target.removeEventListener(type, listener2);
target.addEventListener(type, listener3);
}
function listener2() {
listener2CallCount++;
}
function listener3() {
listener3CallCount++;
}
target.addEventListener(type, listener1);
target.addEventListener(type, listener2);
// Dispatch the event. Only listener1 should be called because
// it removes listener2. And listener3 is added when we've already
// started iterating, so it shouldn't be called either.
target.dispatchEvent(new Event(type));
assert_equals(listener1CallCount, 1);
assert_equals(listener2CallCount, 0);
assert_equals(listener3CallCount, 0);
// Now that only listener3 is set, dispatch another event. Only
// listener3 should be called.
target.dispatchEvent(new Event(type));
assert_equals(listener1CallCount, 1);
assert_equals(listener2CallCount, 0);
assert_equals(listener3CallCount, 1);
}, "Removing all listeners and then adding a new one should work.");
test(function() {
var type = "foo";
var target = document.createElement("div");
var listener1CallCount = 0;
var listener2CallCount = 0;
var listener3CallCount = 0;
function listener1() {
listener1CallCount++;
// Recursively dispatch another event from this listener.
// This will only call listener2 because listener1 is a "once" listener.
target.dispatchEvent(new Event(type));
assert_equals(listener1CallCount, 1);
assert_equals(listener2CallCount, 1);
assert_equals(listener3CallCount, 0);
// Now all listeners are removed - the two "once" listeners have already both
// been called once. Add another listener.
target.addEventListener(type, listener3);
}
function listener2() {
listener2CallCount++;
}
function listener3() {
listener3CallCount++;
}
// Add two "once" listeners.
target.addEventListener(type, listener1, { once: true });
target.addEventListener(type, listener2, { once: true });
// Dispatch the event.
target.dispatchEvent(new Event(type));
// The listener call counts should still match what they were
// at the end of listener1.
assert_equals(listener1CallCount, 1);
assert_equals(listener2CallCount, 1);
assert_equals(listener3CallCount, 0);
// Now that only listener3 is set, dispatch another event. Only
// listener3 should be called.
target.dispatchEvent(new Event(type));
assert_equals(listener1CallCount, 1);
assert_equals(listener2CallCount, 1);
assert_equals(listener3CallCount, 1);
}, "Nested usage of once listeners should work.");
</script>

View File

@ -0,0 +1,4 @@
features:
- name: scrollend
files:
- scrollend-*

View File

@ -6,80 +6,152 @@
<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;
}
#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>
<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;
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 onOverscroll(event) {
overscrolled_x_deltas.push(event.deltaX);
overscrolled_y_deltas.push(event.deltaY);
}
function onScrollend(event) {
scrollend_received = true;
}
async function resetScrollers(test) {
await waitForScrollReset(test, target_div);
await waitForScrollReset(test, document.scrollingElement);
}
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();
function resetOverScrollDeltas() {
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.');
function waitForOverscrollEventWithMinDelta(target, min_x = 0, min_y = 0) {
return new Promise((resolve) => {
target.addEventListener("overscroll", (evt) => {
if (evt.deltaX >= min_x && evt.deltaY >= min_y) {
resolve();
}
});
});
}
// 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.");
function unreachedScrollendListener() {
assert_unreached('Unexpected scrollend event');
}
}, 'Tests that the document gets overscroll event with right deltaX/Y attributes.');
}
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");
// 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 250 pixels which is a fairly conservative
// value.
let overscroll_promise = waitForOverscrollEventWithMinDelta(document,
/*min_x*/0, /*min_y*/250);
scrollend_promise = waitForScrollendEvent(t, document, 2000);
target_div.addEventListener("scrollend", unreachedScrollendListener);
// 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", unreachedScrollendListener);
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), 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 = waitForOverscrollEventWithMinDelta(document,
/*min_x*/250, /*min_y*/ 0);
scrollend_promise = waitForScrollendEvent(t, document, 2000);
target_div.addEventListener("scrollend", unreachedScrollendListener);
// 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", unreachedScrollendListener);
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)");
}, "testing, horizontal");
}
</script>

View File

@ -10,11 +10,14 @@
width: 200px;
height: 200px;
overflow: scroll;
position: absolute;
left: 150px;
top: 150px;
}
#innerDiv {
width: 400px;
height: 400px;
width: 250px;
height: 250px;
}
</style>
@ -45,7 +48,7 @@ function runTest() {
await waitForCompositorCommit();
// Do a horizontal scroll and wait for overscroll event.
await touchScrollInTarget(300, scrolling_div , 'right');
await touchScrollInTarget(100, 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,
@ -55,7 +58,7 @@ function runTest() {
overscrolled_y_delta = 0;
// Do a vertical scroll and wait for overscroll event.
await touchScrollInTarget(300, scrolling_div, 'down');
await touchScrollInTarget(100, 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,

View File

@ -1,15 +1,83 @@
async function waitForScrollendEvent(test, target, timeoutMs = 500) {
async function waitForEvent(eventName, test, target, timeoutMs = 500) {
return new Promise((resolve, reject) => {
const timeoutCallback = test.step_timeout(() => {
reject(`No Scrollend event received for target ${target}`);
reject(`No ${eventName} event received for target ${target}`);
}, timeoutMs);
target.addEventListener('scrollend', (evt) => {
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 waitForScrollendEventNoTimeout(target) {
return new Promise((resolve) => {
target.addEventListener("scrollend", resolve);
});
}
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, x = 0, y = 0) {
return new Promise(resolve => {
if (scroller.scrollTop == x && scroller.scrollLeft == y) {
resolve();
} else {
const eventTarget =
scroller == document.scrollingElement ? document : scroller;
scroller.scrollTo(x, y);
waitForScrollendEventNoTimeout(eventTarget).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;
@ -45,6 +113,29 @@ function waitForCompositorCommit() {
});
}
// Please don't remove this. This is necessary for chromium-based browsers. It
// can be a no-op on user-agents that do not have a separate compositor thread.
// TODO(crbug.com/1509054): This shouldn't be necessary if the test harness
// deferred running the tests until after paint holding.
async function waitForCompositorReady() {
const animation =
document.body.animate({ opacity: [ 0, 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) {
@ -70,6 +161,10 @@ function waitForAnimationEnd(getValue) {
}
// 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")
@ -88,7 +183,7 @@ function touchScrollInTargetSequentiallyWithPause(target, move_path, pause_time_
y += step_y;
test_driver_actions.pointerMove(x, y, {origin: target});
}
test_driver_actions.pause(pause_time_in_ms);
test_driver_actions.pause(pause_time_in_ms); // To prevent inertial scroll
}
return test_driver_actions.pointerUp().send();
@ -125,7 +220,7 @@ function touchScrollInTarget(pixels_to_scroll, target, direction, pause_time_in_
// Trigger fling by doing pointerUp right after pointerMoves.
function touchFlingInTarget(pixels_to_scroll, target, direction) {
touchScrollInTarget(pixels_to_scroll, target, direction, 0 /* pause_time */);
return touchScrollInTarget(pixels_to_scroll, target, direction, 0 /* pause_time */);
}
function mouseActionsInTarget(target, origin, delta, pause_time_in_ms = 100) {
@ -161,3 +256,23 @@ function conditionHolds(condition, error_message = 'Condition is not true anymor
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();
}

View File

@ -14,7 +14,7 @@
}
#innerDiv {
width: 500px;
width: 4000px;
height: 4000px;
}
</style>
@ -28,36 +28,64 @@
<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;
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) => {
// 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
{ x: 0, y: -80 }, // Scroll down
{ x: 0, y: -40 }, // Scroll up
{ x: 0, y: -80 }, // Scroll down
];
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);
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>

View File

@ -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>

View File

@ -1,11 +1,19 @@
<!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="/common/subset-tests-by-key.js"></script>
<meta name="variant" content="?include=subframe-scrollTo-auto"/>
<meta name="variant" content="?include=subframe-scrollTo-smooth"/>
<meta name="variant" content="?include=subframe-scrollBy-auto"/>
<meta name="variant" content="?include=subframe-scrollBy-smooth"/>
<meta name="variant" content="?include=root-scrollTo-auto"/>
<meta name="variant" content="?include=root-scrollTo-smooth"/>
<meta name="variant" content="?include=root-scrollBy-auto"/>
<meta name="variant" content="?include=root-scrollBy-smooth"/>
<script src="scroll_support.js"></script>
<style>
html {
@ -31,8 +39,8 @@ html {
</div>
</body>
<script>
var element_scrollend_arrived = false;
var document_scrollend_arrived = false;
let element_scrollend_arrived = false;
let document_scrollend_arrived = false;
function onElementScrollEnd(event) {
assert_false(event.cancelable);
@ -47,89 +55,95 @@ function onDocumentScrollEnd(event) {
document_scrollend_arrived = true;
}
function callScrollFunction([scrollTarget, scrollFunction, args]) {
scrollTarget[scrollFunction](args);
}
let scroll_fn_variants = [
{
key: "subframe-scrollTo-auto",
target: targetDiv,
fn: "scrollTo",
behavior: "auto",
title: "Tests scrollend event for calling scrollTo with behavior 'auto' on subframe."
},
{
key: "subframe-scrollTo-smooth",
target: targetDiv,
fn: "scrollTo",
behavior: "smooth",
title: "Tests scrollend event for calling scrollTo with behavior 'smooth' on subframe."
},
{
key: "subframe-scrollBy-auto",
target: targetDiv,
fn: "scrollBy",
behavior: "auto",
title: "Tests scrollend event for calling scrollBy with behavior 'auto' on subframe."
},
{
key: "subframe-scrollBy-smooth",
target: targetDiv,
fn: "scrollBy",
behavior: "smooth",
title: "Tests scrollend event for calling scrollBy with behavior 'smooth' on subframe."
},
{
key: "root-scrollTo-auto",
target: document.scrollingElement,
fn: "scrollTo",
behavior: "auto",
title: "Tests scrollend event for calling scrollTo with behavior 'auto' on root."
},
{
key: "root-scrollTo-smooth",
target: document.scrollingElement,
fn: "scrollTo",
behavior: "smooth",
title: "Tests scrollend event for calling scrollTo with behavior 'smooth' on root."
},
{
key: "root-scrollBy-auto",
target: document.scrollingElement,
fn: "scrollBy",
behavior: "auto",
title: "Tests scrollend event for calling scrollBy with behavior 'auto' on root."
},
{
key: "root-scrollBy-smooth",
target: document.scrollingElement,
fn: "scrollBy",
behavior: "smooth",
title: "Tests scrollend event for calling scrollBy with behavior 'smooth' on root."
},
];
function runTest() {
let root_element = document.scrollingElement;
let target_div = document.getElementById("targetDiv");
promise_test (async (t) => {
async function testScrollFn(testInfo, t) {
await waitForCompositorCommit();
target_div.addEventListener("scrollend", onElementScrollEnd);
targetDiv.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" }]]
];
testInfo.target[testInfo.fn]({ top: 200, left: 200,
behavior: testInfo.behavior });
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;
await waitFor(() => {
return element_scrollend_arrived || document_scrollend_arrived;
}, testInfo.target.tagName + "." + testInfo.fn + " did not receive scrollend event.");
if (testInfo.target == document.scrollingElement) {
assert_false(element_scrollend_arrived);
} else {
assert_false(document_scrollend_arrived);
}
}, "Tests scrollend event for calling scroll functions.");
promise_test(async (t) => {
await waitForCompositorCommit();
assert_equals(testInfo.target.scrollLeft, 200,
testInfo.target.tagName + "." + testInfo.fn + " scrollLeft");
assert_equals(testInfo.target.scrollTop, 200,
testInfo.target.tagName + "." + testInfo.fn + " scrollTop");
}
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;
scroll_fn_variants.forEach((testInfo) => {
subsetTestByKey(testInfo.key, promise_test,
async (t) => testScrollFn(testInfo, t), testInfo.title);
});
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>

View File

@ -0,0 +1,147 @@
<!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="/common/subset-tests-by-key.js"></script>
<meta name="variant" content="?include=subframe-scrollTop-smooth"/>
<meta name="variant" content="?include=subframe-scrollLeft-smooth"/>
<meta name="variant" content="?include=root-scrollTop-smooth"/>
<meta name="variant" content="?include=root-scrollLeft-smooth"/>
<meta name="variant" content="?include=subframe-scrollTop-auto"/>
<meta name="variant" content="?include=subframe-scrollLeft-auto"/>
<meta name="variant" content="?include=root-scrollTop-auto"/>
<meta name="variant" content="?include=root-scrollLeft-auto"/>
<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;
}
let scroll_attr_change_variants = [
{
key: "subframe-scrollTop-smooth",
target: targetDiv,
behavior: "smooth",
attribute: "scrollTop",
title: "Tests scrollend event for [scrollTop] behavior:'smooth' on subframe."
},
{
key: "subframe-scrollLeft-smooth",
target: targetDiv,
behavior: "smooth",
attribute: "scrollLeft",
title: "Tests scrollend event for [scrollLeft] behavior:'smooth' on subframe."
},
{
key: "root-scrollTop-smooth",
target: document.scrollingElement,
behavior: "smooth",
attribute: "scrollTop",
title: "Tests scrollend event for [scrollTop] behavior:'smooth' on root."
},
{
key: "root-scrollLeft-smooth",
target: document.scrollingElement,
behavior: "smooth",
attribute: "scrollLeft",
title: "Tests scrollend event for [scrollLeft] behavior:'smooth' on root."
},
{
key: "subframe-scrollTop-auto",
target: targetDiv,
behavior: "auto",
attribute: "scrollTop",
title: "Tests scrollend event for [scrollTop] behavior:'auto' on subframe."
},
{
key: "subframe-scrollLeft-auto",
target: targetDiv,
behavior: "auto",
attribute: "scrollLeft",
title: "Tests scrollend event for [scrollLeft] behavior:'auto' on subframe."
},
{
key: "root-scrollTop-auto",
target: document.scrollingElement,
behavior: "auto",
attribute: "scrollTop",
title: "Tests scrollend event for [scrollTop] behavior:'auto' on root."
},
{
key: "root-scrollLeft-auto",
target: document.scrollingElement,
behavior: "auto",
attribute: "scrollLeft",
title: "Tests scrollend event for [scrollLeft] behavior:'auto' on root."
},
];
function runTest() {
async function testScrollAttrChange(testInfo, t) {
targetDiv.addEventListener("scrollend", onElementScrollEnd);
document.addEventListener("scrollend", onDocumentScrollEnd);
testInfo.target.style.scrollBehavior = testInfo.behavior;
await waitForCompositorCommit();
testInfo.target[testInfo.attribute] = 200;
await waitFor(() => {
return element_scrollend_arrived || document_scrollend_arrived;
}, testInfo.target.tagName + "." + testInfo.attribute + " did not receive scrollend event.");
if (testInfo.target == document.scrollingElement) {
assert_false(element_scrollend_arrived);
} else {
assert_false(document_scrollend_arrived);
}
assert_equals(testInfo.target[testInfo.attribute], 200,
testInfo.target.tagName + "." + testInfo.attribute + " " + testInfo.behavior);
}
scroll_attr_change_variants.forEach((testInfo) => {
subsetTestByKey(testInfo.key, promise_test,
async (t) => testScrollAttrChange(testInfo, t), testInfo.title);
})
}
</script>

View File

@ -108,10 +108,12 @@ function runTest() {
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.");
const scrollend_events = [
waitForScrollendEventNoTimeout(out_div),
waitForScrollendEventNoTimeout(target_div)
];
await Promise.all(scrollend_events);
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");

View File

@ -7,64 +7,100 @@
<script src="/resources/testdriver-vendor.js"></script>
<script src="scroll_support.js"></script>
<style>
#targetDiv {
width: 200px;
height: 200px;
overflow: scroll;
}
#hspacer {
height: 100px;
width: 100vw;
top: 0;
left: 200px;
/* on the right edge od targetDiv */
position: absolute;
}
#innerDiv {
width: 400px;
height: 400px;
}
#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 id="targetDiv">
<div id="innerDiv"></div>
</div>
</div>
<div id="hspacer"></div>
<div id="vspacer"></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;
}
var target_div = document.getElementById('targetDiv');
async function resetScrollers(test) {
await waitForScrollReset(test, target_div);
await waitForScrollReset(test, document.scrollingElement);
}
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();
function fail() {
assert_true(false);
}
// 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);
function runTest() {
promise_test(async (t) => {
await resetScrollers(t);
await waitForCompositorCommit();
// 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.');
}
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>

View File

@ -7,96 +7,167 @@
<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;
}
#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 id="overscrollYDiv">
<div class=content>
<div id="targetDiv">
<div class="content">
</div>
</div>
<div id="overscrollXDiv">
<div class="content"></div>
<div id="targetXDiv">
<div class="content">
</div>
</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 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').
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) => {
// 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();
function runTest() {
promise_test(async (t) => {
await resetScrollers(t);
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);
// Make sure that no scrollend event is sent to document.
document.addEventListener("scrollend",
t.unreached_func("Document got unexpected scrollend event."));
let scrollend_promise;
let touchEndPromise = new Promise((resolve) => {
target_div.addEventListener("touchend", resolve);
});
await touchScrollInTarget(300, target_div, 'up');
scrollend_promise = waitForScrollendEvent(t, targetXDiv, 2000);
targetXDiv.scrollLeft = targetXDiv.scrollWidth;
await scrollend_promise;
// The scrollend event should never be fired before the gesture has completed.
await touchEndPromise;
scrollend_promise = waitForScrollendEvent(t, overscrollXDiv, 2000);
overscrollXDiv.scrollLeft = overscrollXDiv.scrollWidth;
await scrollend_promise;
horizontal_scrollend_arrived = false;
// Ensure we wait at least a tick after the touch end.
await waitForCompositorCommit();
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;
// 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.');
}
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>

View File

@ -1,68 +0,0 @@
<!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>

View File

@ -7,49 +7,63 @@
<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;
}
#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 id="targetDiv">
<div id="innerDiv"></div>
</div>
</div>
<div id="spacer"></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);
var target_div = document.getElementById('targetDiv');
async function resetScrollers(test) {
await waitForScrollReset(test, target_div);
await waitForScrollReset(test, document.scrollingElement);
}
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();
function fail() {
assert_true(false);
}
// 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.');
}
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 = waitForScrollendEventNoTimeout(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 = waitForScrollendEventNoTimeout(window);
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>

View File

@ -0,0 +1,68 @@
<!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="/visual-viewport/viewport_support.js"></script>
<script src="/dom/events/scrolling/scroll_support.js"></script>
</head>
<body>
<style>
.large {
height: 200vh;
width: 200vw;
border: solid 1px black;
}
</style>
<div class="large"></div>
<script>
window.onload = async () => {
async function pan_viewport_test(add_event_listener_func) {
const preScrollVisualViewportOffsetTop = visualViewport.offsetTop;
const preScrollWindowScrollOffset = window.scrollY;
const scrollend_promise = add_event_listener_func();
const scrollAmount = 50;
await touchScrollInTarget(scrollAmount, document.documentElement, "up");
await scrollend_promise;
assert_less_than(visualViewport.offsetTop,
preScrollVisualViewportOffsetTop,
`visualViewport should be scrolled.`);
assert_equals(window.scrollY, preScrollWindowScrollOffset,
"the window should not scroll.");
// No need to undo scroll; subsequent test has room to scroll further.
}
await waitForCompositorCommit();
await pinchZoomIn();
assert_greater_than(visualViewport.scale, 1, "page should be zoomed in.");
promise_test(async (t) => {
await pan_viewport_test(() => {
return new Promise((resolve) => {
visualViewport.addEventListener("scrollend", resolve, { once: true});
});
});
}, "scrollend listener added via addEventlistener fires when the visual " +
"viewport is panned.");
promise_test(async (t) => {
await pan_viewport_test((t) => {
return new Promise((resolve) => {
visualViewport.onscrollend = () => {
visualViewport.onscrollend = undefined;
resolve();
}
});
});
}, "visualviewport.onscrollend fires when the visual viewport is panned.");
}
</script>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<style>
body {
margin: 0px;
}
#spacer {
height: 120vh;
width: 120vw;
border: solid 1px black;
}
#scroller {
height: 200px;
width: 200px;
overflow: scroll;
border: solid 1px red;
}
#inner-spacer {
height: 400px;
width: 400px;
border: solid 1px black;
}
</style>
<body>
<div id="scroller" tabindex=0>
<div id="inner-spacer"></div>
</div>
<div id="spacer"></div>
</body>
</html>

View File

@ -0,0 +1,77 @@
<!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>
<script src="scrollend-user-scroll-common.js"></script>
<style>
iframe {
height: 300px;
width: 300px;
}
</style>
</head>
<body style="margin:0" onload=runTest()>
<iframe id="frame" src="scrollend-event-fires-to-iframe-inner-frame.html"></iframe>
</body>
<script>
function runTest() {
let target_div = frame.contentDocument.getElementById("scroller");
//Tests for scrollend events on an element within an iframe.
promise_test(async (t) => {
await test_scrollend_on_touch_drag(t, target_div);
}, 'Tests that the target_div within iframe gets scrollend event when touch ' +
'dragging.');
promise_test(async (t) => {
await test_scrollend_on_scrollbar_gutter_click(t, target_div);
}, 'Tests that the target_div within iframe gets scrollend event when ' +
'clicking scrollbar.');
// Same issue as previous test.
promise_test(async (t) => {
await test_scrollend_on_scrollbar_thumb_drag(t, target_div);
}, 'Tests that the target_div within iframe gets scrollend event when ' +
'dragging the scrollbar thumb.');
promise_test(async (t) => {
await test_scrollend_on_mousewheel_scroll(t, target_div, frame);
}, 'Tests that the target_div within iframe gets scrollend event when mouse ' +
'wheel scrolling.');
promise_test(async (t) => {
await test_scrollend_on_keyboard_scroll(t, target_div);
}, 'Tests that the target_div within iframe gets scrollend event when ' +
'sending DOWN key to the target.');
// Test for scrollend events on the iframe's window.
// TODO: add similar tests with different input modes.
promise_test(async (t) => {
let scroller = frame.contentDocument.scrollingElement;
await waitForScrollReset(t, scroller);
await waitForCompositorReady();
const targetScrollendPromise = waitForScrollendEventNoTimeout(frame.contentDocument);
verifyNoScrollendOnDocument(t);
let x = target_div.getBoundingClientRect().width + 20;
let y = 20;
let dy = 30;
await new test_driver.Actions().scroll(x, y, 0, dy).send();
await targetScrollendPromise;
assert_equals(scroller.scrollTop, dy, 'window scrolled by mousewheel');
}, 'scrollend fires to iframe window on mousewheelscroll');
}
</script>
</html>

View File

@ -8,6 +8,7 @@
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="scroll_support.js"></script>
<script src="scrollend-user-scroll-common.js"></script>
<style>
#targetDiv {
width: 200px;
@ -31,169 +32,33 @@
<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);
await test_scrollend_on_touch_drag(t, target_div);
}, '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);
await test_scrollend_on_scrollbar_gutter_click(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;
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);
await test_scrollend_on_scrollbar_thumb_drag(t, target_div);
}, '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);
await test_scrollend_on_mousewheel_scroll(t, target_div);
}, '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);
await test_scrollend_on_keyboard_scroll(t, target_div);
}, 'Tests that the target_div gets scrollend event when sending DOWN key ' +
'to the target.');
}
</script>
</html>

View File

@ -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).then(
(/*resolve*/) => {
assert_true(false, "no scroll, so no scrollend expected");
},
(/*reject*/) => { /* Did not see scrollend, which is okay. */ }
);
await upwardScroll(scrolling_element, button_element, scroll_type);
await scrollend_promise;
}
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>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<style>
#inputscroller {
width: 100px;
height: 50px;
}
</style>
<input type="text" id="inputscroller"
value="qwertyuiopasddfghjklzxcvbnmqwertyuiopasddfghjklzxcvbnmqwer">
<script>
promise_test(async() => {
const inputscroller = document.getElementById("inputscroller");
assert_equals(inputscroller.scrollLeft, 0,
"text input field is not initially scrolled.");
const scrollend_promise = new Promise((resolve) => {
inputscroller.addEventListener("scrollend", resolve);
});
inputscroller.scrollLeft = 10;
await scrollend_promise;
assert_equals(inputscroller.scrollLeft, 10,
"text input field is scrolled by the correct amount");
}, "scrolled input field should receive scrollend.");
</script>
</body>
</html>

View File

@ -0,0 +1,150 @@
async function test_scrollend_on_touch_drag(t, target_div) {
// 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 targetScrollendPromise = waitForScrollendEventNoTimeout(target_div);
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);
}
async function test_scrollend_on_scrollbar_gutter_click(t, target_div) {
// 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 = waitForScrollendEventNoTimeout(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 = Math.round(bounds.right - scrollbar_width / 2);
const y = Math.round(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);
}
// Same issue as previous test.
async function test_scrollend_on_scrollbar_thumb_drag(t, target_div) {
// 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 = waitForScrollendEventNoTimeout(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 = Math.round(bounds.right - scrollbar_width / 2);
const y = Math.round(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);
}
async function test_scrollend_on_mousewheel_scroll(t, target_div, frame) {
await resetTargetScrollState(t, target_div);
await waitForCompositorReady();
const targetScrollendPromise = waitForScrollendEventNoTimeout(target_div);
verifyNoScrollendOnDocument(t);
let scroll_origin = target_div;
if (frame) {
// chromedriver doesn't support passing { origin: element }
// for an element within a subframe. Use the frame element itself.
scroll_origin = frame;
}
const x = 0;
const y = 0;
const dx = 0;
const dy = 40;
await new test_driver.Actions()
.scroll(x, y, dx, dy, { origin: scroll_origin })
.send();
await targetScrollendPromise;
assert_true(target_div.scrollTop > 0);
await verifyScrollStopped(t, target_div);
}
async function test_scrollend_on_keyboard_scroll(t, target_div) {
await resetTargetScrollState(t, target_div);
await waitForCompositorReady();
verifyNoScrollendOnDocument(t);
const targetScrollendPromise = waitForScrollendEventNoTimeout(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);
}

View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="scroll_support.js"></script>
<body>
<style>
.scroller {
scroll-snap-type: x mandatory;
overflow-x: auto;
overflow-y: hidden;
position: relative;
height: 500px;
width: 500px;
}
.box {
scroll-snap-align: start;
width: 400px;
position: absolute;
top: 200px;
}
#box1 {
background-color: red;
height: 500px;
}
#box2 {
background-color: yellow;
height: 300px;
left: 700.5px;
}
#box3 {
background-color: blue;
height: 100px;
left: 1400px;
}
</style>
<div id="scroller" class="scroller">
<div class="box" id="box1">1</div>
<div class="box" id="box2">2</div>
<div class="box" id="box3">3</div>
</div>
<script>
let scrollendCount = 0;
scroller.addEventListener('scrollend', () => {
scroller.style.maxHeight = null;
scroller.style.maxHeight = `${box2.clientHeight}px`;
scrollendCount += 1;
});
promise_test(async (test) => {
// This test aims to verify that scrollend fires correctly (i.e. once)
// when the target snap position is not a whole number. In this case, we
// expect to snap to the left edge of box2 which is at a fractional
// offset from the scroller's origin (left: 700.5px).
// The scroll offset resulting from the snap may not be fractional
// (e.g. if the browser does not support fractional scroll offsets) so
// we verify the scroll offset with assert_approx_equals.
assert_equals(scroller.scrollLeft, 0,
"test precondition: scroller is not scrolled.");
const expected_scroll_left = box2.offsetLeft;
const target_offset = box2.offsetLeft + box2.clientWidth / 2;
let scrollend_promise = waitForScrollendEvent(test, scroller);
scroller.scrollTo( { left: target_offset });
await scrollend_promise;
// Instead of a time-based wait for errant scrollends, we wait a frame
// and then scroll back to 0.
await waitForCompositorCommit();
assert_approx_equals(scroller.scrollLeft, expected_scroll_left, 1,
"scroller snaps to the left edge of box 2");
scrollend_promise = waitForScrollendEvent(test, scroller);
scroller.scrollTo({ left: 0 });
await scrollend_promise;
assert_equals(scroller.scrollLeft, 0,
"scroller should be scrolled back to 0.");
assert_equals(scrollendCount, 2, "exactly 2 scrollends should be seen");
}, "snap to fractional offset fires scrollend exactly once.");
</script>
</body>
</html>

View File

@ -0,0 +1,140 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
<meta name="variant" content="?include=target-basic"/>
<meta name="variant" content="?include=scroll-over-scrollable-child"/>
<meta name="variant" content="?include=transaction-not-bound-to-scroll-frame"/>
<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="/common/subset-tests-by-key.js"></script>
<script src="scroll_support.js"></script>
<style>
body {
margin: 0;
padding: 0;
height: 200vh;
}
.spacer {
width: 100%;
height: 25px;
padding: 0;
margin: 0;
}
#scrollableDiv {
width: 100%;
height: 500px;
overflow: scroll;
background: yellow;
}
#innerDiv {
width: 100%;
height: 4000px;
background: red;
}
</style>
<head>
<body onload=runTest()>
<div id="firstRootSpacer" class="spacer" style="background: cyan"></div>
<div id="secondRootSpacer" class="spacer" style="background: magenta"></div>
<div id="scrollableDiv">
<div id="innerDiv">
<div id="firstInnerSpacer" class="spacer" style="background: green">
</div>
<div id="secondInnerSpacer" class="spacer" style="background: blue">
</div>
</div>
</div>
</body>
<script>
let variants = [
// Ensure that the wheel transaction fires all wheel events to the initial
// target.
{
key: 'target-basic',
origin: "viewport",
scrollX: 40,
scrollY: 2,
scrollingElement: document.scrollingElement,
targetElement: firstRootSpacer,
title: 'Wheel events should be captured to one target #1',
},
// Ensure that the wheel transaction fires all wheel events to the initial
// target, even when another scroll frame is under the mouse cursor.
{
key: 'scroll-over-scrollable-child',
origin: "viewport",
scrollX: 40,
scrollY: 27,
scrollingElement: document.scrollingElement,
targetElement: secondRootSpacer,
title: 'Wheel events should be captured to one target #2',
},
// Ensure that the wheel transaction targets the topmost-element, which is
// not the scrollable element.
{
key: 'transaction-not-bound-to-scroll-frame',
origin: innerDiv,
scrollX: 40,
scrollY: 2,
scrollingElement: scrollableDiv,
targetElement: innerDiv,
title: 'The wheel event transactions target may not be a scroll frame',
},
];
function runTest() {
async function testBasic(testInfo, t) {
await waitForCompositorReady();
await waitForCompositorCommit();
let wheelEventTargets = [];
function pushTargetToWheelEventTargets(e) {
wheelEventTargets.push(e.target)
}
window.addEventListener("wheel", pushTargetToWheelEventTargets, {passive: true});
// Scroll past the boundary of the original element to ensure all events in
// transaction have the same target.
await new test_driver.Actions()
.addWheel("wheel1")
.scroll(testInfo.scrollX, testInfo.scrollY, 0, 30, {origin: testInfo.origin})
.pause(1)
.scroll(testInfo.scrollX, testInfo.scrollY, 0, 30, {origin: testInfo.origin})
.pause(1)
.scroll(testInfo.scrollX, testInfo.scrollY, 0, 30, {origin: testInfo.origin})
.send();
// TODO(dlrobertson): Use the scrollend event here to wait for the
// wheel scroll to finish instead of waitForAnimationEnd().
await waitForAnimationEnd(() => { return testInfo.scrollingElement.scrollTop; });
await waitForCompositorCommit();
// Ensure that all the captured wheel events are the expected target.
wheelEventTargets.forEach((wheelEventTarget, i) => {
assert_equals(wheelEventTarget, testInfo.targetElement,
"Wheel event at index `" + i + "` does not have the expected target");
});
assert_greater_than(testInfo.scrollingElement.scrollTop, 0,
"The scrolling element has scrolled");
}
variants.forEach((testInfo) => {
subsetTestByKey(testInfo.key, promise_test, t => testBasic(testInfo, t), testInfo.title);
});
}
</script>
</html>

View File

@ -0,0 +1,101 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<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>
body {
margin: 0;
padding: 0;
height: 200vh;
}
#initial {
background: red;
width: 100%;
height: 25px;
padding: 0;
margin: 0;
}
#second {
background: green;
width: 100%;
height: 200vh;
padding: 0;
margin: 0;
}
</style>
<head>
<body>
<div id="initial"></div>
<div id="second"></div>
</body>
<script>
promise_test(async (t) => {
await new Promise(resolve => addEventListener("load", resolve, {once: true}));
await waitForCompositorReady();
await waitForCompositorCommit();
let firstEventTargets = [];
let secondEventTargets = [];
let isFirstGroup = true;
window.addEventListener("wheel", (e) => {
if (isFirstGroup) {
firstEventTargets.push(e.target);
} else {
secondEventTargets.push(e.target);
}
}, {passive: true});
await waitForCompositorCommit();
// The first action chain should target the initial element
await new test_driver.Actions()
.addWheel("wheel1")
.scroll(40, 2, 0, 30, {origin: "viewport"})
.send();
// Start logging event targets in the second transaction
isFirstGroup = false;
// The second chain should target the second element and the prior wheel event
// group should have no impact on this action chain.
await new test_driver.Actions()
.addWheel("wheel1")
.scroll(40, 30, 0, 30, {origin: "viewport"})
.send();
// TODO(dlrobertson): Use the scrollend event here to wait for the
// wheel scroll to finish instead of waitForAnimationEnd().
await waitForAnimationEnd(() => { return document.scrollingElement.scrollTop; });
await waitForCompositorCommit();
assert_greater_than(firstEventTargets.length, 0,
"There should be at least one event in the first transaction");
assert_greater_than(secondEventTargets.length, 0,
"There should be at least one event in the second transaction");
firstEventTargets.forEach((wheelEventTarget, i) => {
assert_equals(wheelEventTarget, initial,
"Wheel event at index `" + i + "` did not target the initial element");
});
secondEventTargets.forEach((wheelEventTarget, i) => {
assert_equals(wheelEventTarget, second,
"Wheel event at index `" + i + "` did not target the second element");
});
}, "Two separate webdriver action chains should have different wheel event transactions");
</script>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
<meta name="variant" content="?include=none"/>
<meta name="variant" content="?include=contents"/>
<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="/common/subset-tests-by-key.js"></script>
<script src="scroll_support.js"></script>
<style>
body {
margin: 0;
padding: 0;
height: 200vh;
}
.spacer {
width: 100%;
height: 25px;
padding: 0;
margin: 0;
}
</style>
<head>
<body onload=runTest()>
<div id="initialTarget" class="spacer" style="background: red"></div>
<div id="firstRootSpacer" class="spacer" style="background: green"></div>
<div id="secondRootSpacer" class="spacer" style="background: blue"></div>
</body>
<script>
let variants = [
"none",
"contents",
]
function runTest() {
async function testDisplayChange(display, t) {
await waitForCompositorReady();
await waitForCompositorCommit();
let wheelEventTargets = [];
let makeInitialTargetNone = false;
// Modify the initial element the wheel event is targetted at to
// display: <variant> once we fire the first wheel event, then log
// the subsequent wheel event targets.
function makeInitialElementNone(e) {
wheelEventTargets.push(e.target);
if (!makeInitialTargetNone) {
makeInitialTargetNone = true;
e.target.style.display = display;
}
}
window.addEventListener("wheel", makeInitialElementNone, {passive: true});
await waitForCompositorCommit();
await new test_driver.Actions()
.addWheel("wheel1")
.scroll(40, 2, 0, 30, {origin: "viewport"})
.pause(1)
.scroll(40, 2, 0, 30, {origin: "viewport"})
.send();
// TODO(dlrobertson): Use the scrollend event here to wait for the
// wheel scroll to finish instead of waitForAnimationEnd().
await waitForAnimationEnd(() => { return document.scrollingElement.scrollTop; });
await waitForCompositorCommit();
// The first wheel event should be targetted at the modified element.
assert_equals(wheelEventTargets.shift(), initialTarget,
"Initial wheel event is has the modified element as the target");
wheelEventTargets.forEach((wheelEventTarget, i) => {
// TODO(dlrobertson): This assertion is pretty weak, but browsers seem to disagree
// on what element the event should target. Find out what the target should be here
// and make this assertion more restrictive.
assert_not_equals(wheelEventTarget, initialTarget,
"Wheel event at index `" + i + "` targetted the initial element");
});
assert_greater_than(document.scrollingElement.scrollTop, 0, "The document has scrolled");
}
variants.forEach((variant) => {
subsetTestByKey(variant, promise_test, t => testDisplayChange(variant, t),
"Modify the initial wheel event target to display:" + variant);
});
}
</script>
</html>

View File

@ -0,0 +1,74 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css"/>
<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>
body {
margin: 0;
padding: 0;
height: 200vh;
}
.spacer {
width: 100%;
height: 25px;
padding: 0;
margin: 0;
font: 25px/1 Ahem;
}
</style>
<head>
<body>
<div id="firstRootSpacer" class="spacer" style="background: green">X</div>
<div id="secondRootSpacer" class="spacer" style="background: blue"></div>
</body>
<script>
promise_test(async (t) => {
// Wheel event transactions target elements, not nodes. A wheel event
// transaction that begins over a text node should have an event target for
// the containing element.
await waitForCompositorReady();
await waitForCompositorCommit();
let wheelEventTargets = [];
window.addEventListener("wheel", (e) => {
wheelEventTargets.push(e.target);
}, {passive: true});
await waitForCompositorCommit();
await new test_driver.Actions()
.addWheel("wheel1")
.scroll(12, 12, 0, 30, {origin: "viewport"})
.pause(1)
.scroll(12, 12, 0, 30, {origin: "viewport"})
.send();
// TODO(dlrobertson): Use the scrollend event here to wait for the
// wheel scroll to finish instead of waitForAnimationEnd().
await waitForAnimationEnd(() => { return document.scrollingElement.scrollTop; });
await waitForCompositorCommit();
// All of the wheel events should have the first div as their target.
wheelEventTargets.forEach((wheelEventTarget, i) => {
assert_equals(wheelEventTarget, firstRootSpacer,
"Wheel event at index `" + i + "` does not have the expected target");
});
assert_greater_than(document.scrollingElement.scrollTop, 0, "The document has scrolled");
}, "Wheel event transactions target elements");
</script>
</html>

View File

@ -0,0 +1,85 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<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>
body {
margin: 0;
padding: 0;
height: 200vh;
}
.spacer {
width: 100%;
height: 25px;
padding: 0;
margin: 0;
}
</style>
<head>
<body>
<div id="initialTarget" class="spacer" style="background: red"></div>
<div id="firstRootSpacer" class="spacer" style="background: green"></div>
<div id="secondRootSpacer" class="spacer" style="background: blue"></div>
</body>
<script>
promise_test(async (t) => {
await new Promise(resolve => addEventListener("load", resolve, {once: true}));
await waitForCompositorReady();
await waitForCompositorCommit();
let wheelEventTargets = [];
let movedInitialTarget = false;
// Move the initial element the wheel event is targetted at once we fire the
// first wheel event, then log the subsequent wheel event targets.
function moveInitialElement(e) {
wheelEventTargets.push(e.target);
if (!movedInitialTarget) {
movedInitialTarget = true;
secondRootSpacer.insertAdjacentElement('afterend', e.target);
}
}
window.addEventListener("wheel", moveInitialElement, {passive: true});
await waitForCompositorCommit();
await new test_driver.Actions()
.addWheel("wheel1")
.scroll(40, 2, 0, 30, {origin: "viewport"})
.pause(1)
.scroll(40, 2, 0, 30, {origin: "viewport"})
.send();
// TODO(dlrobertson): Use the scrollend event here to wait for the
// wheel scroll to finish instead of waitForAnimationEnd().
await waitForAnimationEnd(() => { return document.scrollingElement.scrollTop; });
await waitForCompositorCommit();
// The first wheel event should be targetted at the moved element.
assert_equals(wheelEventTargets.shift(), initialTarget,
"Initial wheel event has the moved element as the target");
wheelEventTargets.forEach((wheelEventTarget, i) => {
// TODO(dlrobertson): This assertion is pretty weak, but browsers seem to disagree
// on what element the event should target. Find out what the target should be here
// and make this assertion more restrictive.
assert_not_equals(wheelEventTarget, initialTarget,
"Wheel event at index `" + i + "` targetted the initial element");
});
assert_greater_than(document.scrollingElement.scrollTop, 0, "The document has scrolled");
}, "Move the initial wheel event target.");
</script>
</html>

View File

@ -0,0 +1,92 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<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>
body {
margin: 0;
padding: 0;
height: 200vh;
}
.spacer {
width: 100%;
height: 25px;
padding: 0;
margin: 0;
}
</style>
<head>
<body>
<div id="initialTarget" class="spacer" style="background: red"></div>
<div id="firstRootSpacer" class="spacer" style="background: green"></div>
<div id="secondRootSpacer" class="spacer" style="background: blue"></div>
</body>
<script>
promise_test(async (t) => {
await new Promise(resolve => addEventListener("load", resolve, {once: true}));
await waitForCompositorReady();
await waitForCompositorCommit();
let initialTarget = null;
let wheelEventTargets = [];
let removedInitialTarget = false;
// Remove the initial element the wheel event is targetted at once we fire the
// first wheel event, then log the subsequent wheel event targets.
function removeInitialElement(e) {
wheelEventTargets.push(e.target);
if (!removedInitialTarget) {
initialTarget = e.target;
e.target.remove();
removedInitialTarget = true;
initialTarget.addEventListener("wheel", () => {
assert_true(false, "wheel event should never be fired after the target is removed")
}, {passive: true});
}
}
window.addEventListener("wheel", removeInitialElement, {passive: true});
await waitForCompositorCommit();
await new test_driver.Actions()
.addWheel("wheel1")
.scroll(40, 2, 0, 30, {origin: "viewport"})
.pause(1)
.scroll(40, 2, 0, 30, {origin: "viewport"})
.send();
// TODO(dlrobertson): Use the scrollend event here to wait for the
// wheel scroll to finish instead of waitForAnimationEnd().
await waitForAnimationEnd(() => { return document.scrollingElement.scrollTop; });
await waitForCompositorCommit();
assert_true(removedInitialTarget, "Removed the initial target");
// The first wheel event should be targetted at the removed element.
assert_equals(wheelEventTargets.shift(), initialTarget,
"Initial wheel event has the removed element as the target");
wheelEventTargets.forEach((wheelEventTarget, i) => {
// TODO(dlrobertson): This assertion is pretty weak, but browsers seem to disagree
// on what element the event should target. Find out what the target should be here
// and make this assertion more restrictive.
assert_not_equals(wheelEventTarget, initialTarget,
"Wheel event at index `" + i + "` targetted the initial element");
});
assert_greater_than(document.scrollingElement.scrollTop, 0, "The document has scrolled");
}, "Remove the initial wheel event target.");
</script>
</html>

View File

@ -0,0 +1,97 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
<meta name="variant" content="?include=passive-false"/>
<meta name="variant" content="?include=passive-true"/>
<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="/common/subset-tests-by-key.js"></script>
<script src="scroll_support.js"></script>
<style>
body {
margin: 0;
padding: 0;
height: 200vh;
}
.spacer {
width: 100%;
height: 25px;
padding: 0;
margin: 0;
}
</style>
<head>
<body onload=runTest()>
<div id="initialTarget" class="spacer" style="background: red"></div>
<div id="firstRootSpacer" class="spacer" style="background: green"></div>
<div id="secondRootSpacer" class="spacer" style="background: blue"></div>
</body>
<script>
let variants = [
{
key: "passive-false",
passive: false,
},
{
key: "passive-true",
passive: true,
},
];
function runTest() {
async function testResizeTarget(passive, t) {
await waitForCompositorReady();
await waitForCompositorCommit();
let wheelEventTargets = [];
let resizedInitialTarget = false;
// Resize the initial element the wheel event is targetted at once we fire the
// first wheel event, then log the subsequent wheel event targets.
function resizeInitialElement(e) {
wheelEventTargets.push(e.target);
if (!resizedInitialTarget) {
resizedInitialTarget = true;
e.target.style.height = '10px';
}
}
window.addEventListener("wheel", resizeInitialElement, {passive: passive});
await waitForCompositorCommit();
await new test_driver.Actions()
.addWheel("wheel1")
.scroll(2, 2, 0, 30, {origin: "viewport"})
.pause(1)
.scroll(2, 2, 0, 30, {origin: "viewport"})
.send();
// TODO(dlrobertson): Use the scrollend event here to wait for the
// wheel scroll to finish instead of waitForAnimationEnd().
await waitForAnimationEnd(() => { return document.scrollingElement.scrollTop; });
await waitForCompositorCommit();
wheelEventTargets.forEach((wheelEventTarget, i) => {
assert_equals(wheelEventTarget, initialTarget,
"Wheel event at `" + i + "` does not match the expected target")
});
assert_greater_than(document.scrollingElement.scrollTop, 0, "The document has scrolled");
}
variants.forEach((variant) => {
subsetTestByKey(variant.key, promise_test, t => testResizeTarget(variant.passive, t),
"Resize the initial target and use a passive:" + variant.passive + " listener");
});
}
</script>
</html>

View File

@ -16,7 +16,7 @@
"path": "dom/abort"
},
"dom/events": {
"commit": "ab8999891c6225bef1741c2960033aad620481a8",
"commit": "0a811c51619b14f78fec60ba7dd1603795ca6a21",
"path": "dom/events"
},
"encoding": {