diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /testing/web-platform/tests/event-timing | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/event-timing')
55 files changed, 2160 insertions, 0 deletions
diff --git a/testing/web-platform/tests/event-timing/META.yml b/testing/web-platform/tests/event-timing/META.yml new file mode 100644 index 0000000000..e38d492da0 --- /dev/null +++ b/testing/web-platform/tests/event-timing/META.yml @@ -0,0 +1,3 @@ +spec: https://wicg.github.io/event-timing/ +suggested_reviewers: + - npm1 diff --git a/testing/web-platform/tests/event-timing/auxclick.html b/testing/web-platform/tests/event-timing/auxclick.html new file mode 100644 index 0000000000..b88328e2aa --- /dev/null +++ b/testing/web-platform/tests/event-timing/auxclick.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing auxclick.</title> +<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=resources/event-timing-test-utils.js></script> +<div id='target'>Click me</div> +<script> + promise_test(async t => { + return testEventType(t, 'auxclick'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/buffered-and-duration-threshold.html b/testing/web-platform/tests/event-timing/buffered-and-duration-threshold.html new file mode 100644 index 0000000000..4a7a63f59a --- /dev/null +++ b/testing/web-platform/tests/event-timing/buffered-and-duration-threshold.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: PerformanceObserver with buffered flag and durationThreshold</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<div id='myDiv'>Click me</div> +<script> +promise_test(async t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + // Add a PerformanceObserver and observe with a durationThreshold of 30 and buffered flag. + return new Promise(async resolve1 => { + // Add a slow click event and make sure it's dispatched to observers. + await new Promise(async resolve2 => { + // Observer to await until event entry is dispatched. + new PerformanceObserver(() => { + resolve2(); + }).observe({type: "event", durationThreshold: 16}); + await clickOnElementAndDelay('myDiv', 30); + }); + const afterFirstClick = performance.now(); + new PerformanceObserver(t.step_func(list => { + const pointerDowns = list.getEntriesByName('pointerdown'); + pointerDowns.forEach(entry => { + if (entry.processingStart < afterFirstClick) { + // It is possible that the first event gets a slow duration and hence gets buffered. + // In this case the minDuration must be at least 104, otherwise it shouldn't have been + // buffered. + verifyClickEvent(entry, 'myDiv', true /* isFirst */); + } else { + verifyClickEvent(pointerDowns[0], 'myDiv', false /* isFirst */, 16 /* minDuration*/); + resolve1(); + } + }); + })).observe({type: 'event', durationThreshold: 16, buffered: true}); + // This should be the only click observed since the other one would not be buffered. + await clickOnElementAndDelay('myDiv', 30); + }); +}, "PerformanceObserver buffering independent of durationThreshold"); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/buffered-flag.html b/testing/web-platform/tests/event-timing/buffered-flag.html new file mode 100644 index 0000000000..7ee152be93 --- /dev/null +++ b/testing/web-platform/tests/event-timing/buffered-flag.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: PerformanceObserver sees entries with buffered flag</title> +<button id='button'>Generate a 'click' event</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<script> + let firstInputSeen = false; + let eventSeen = false; + async_test(t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + const validateEntry = t.step_func(entry => { + if (entry.entryType === 'first-input') + firstInputSeen = true; + else if (entry.entryType === 'event') + eventSeen = true; + else + assert_unreached('Should only observe Event Timing entries!'); + }); + // First observer creates second in callback to ensure the entry has been dispatched by the time + // the second observer begins observing. + new PerformanceObserver((list, obs) => { + list.getEntries().forEach(validateEntry); + if (!firstInputSeen || !eventSeen) + return; + + obs.disconnect(); + firstInputSeen = false; + eventSeen = false; + // Second observer requires 'buffered: true' to see entries. + const bufferedObserver = new PerformanceObserver(entryList => { + entryList.getEntries().forEach(validateEntry); + if (firstInputSeen && eventSeen) + t.done(); + }); + bufferedObserver.observe({type: "event", buffered: true}); + bufferedObserver.observe({type: 'first-input', buffered: true}); + }).observe({entryTypes: ['event', 'first-input']}); + clickAndBlockMain('button'); + }, "PerformanceObserver with buffered flag sees previous Event Timing entries"); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/click-timing.html b/testing/web-platform/tests/event-timing/click-timing.html new file mode 100644 index 0000000000..24bad0aedc --- /dev/null +++ b/testing/web-platform/tests/event-timing/click-timing.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: compare timing of two long clicks +</title> +<button id='button'>Generate a 'click' event</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<script> + /* Timeline: + Observer starts + Long click 1 + Long click 2 + Once two clicks have been received by observer, compare the timestamps. + */ + let timeBeforeFirstClick; + let timeAfterFirstClick; + let timeAfterSecondClick; + let observedEntries = []; + async_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + new PerformanceObserver(t.step_func(entryList => { + observedEntries = observedEntries.concat(entryList.getEntries().filter( + entry => entry.name === 'pointerdown')); + if (observedEntries.length < 2) + return; + + assert_not_equals(timeBeforeFirstClick, undefined); + assert_not_equals(timeAfterFirstClick, undefined); + assert_not_equals(timeAfterSecondClick, undefined); + // First click. + verifyClickEvent(observedEntries[0], 'button'); + assert_between_exclusive(observedEntries[0].processingStart, + timeBeforeFirstClick, + timeAfterFirstClick, + "First click's processingStart"); + assert_greater_than(timeAfterFirstClick, observedEntries[0].startTime, + "timeAfterFirstClick should be later than first click's start time."); + + // Second click. + verifyClickEvent(observedEntries[1], 'button'); + assert_between_exclusive(observedEntries[1].processingStart, + timeAfterFirstClick, + timeAfterSecondClick, + "Second click's processingStart"); + assert_greater_than(timeAfterSecondClick, observedEntries[1].startTime, + "timeAfterSecondClick should be later than second click's start time."); + t.done(); + })).observe({type: 'event'}); + timeBeforeFirstClick = performance.now(); + clickAndBlockMain('button').then( () => { + timeAfterFirstClick = performance.now(); + clickAndBlockMain('button').then(() => { + timeAfterSecondClick = performance.now(); + }) + }); + }, "Event Timing: compare click timings."); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/click.html b/testing/web-platform/tests/event-timing/click.html new file mode 100644 index 0000000000..e3eecabdf1 --- /dev/null +++ b/testing/web-platform/tests/event-timing/click.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing click.</title> +<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=resources/event-timing-test-utils.js></script> +<div id='target'>Click me</div> +<script> + promise_test(async t => { + return testEventType(t, 'click'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/contextmenu.html b/testing/web-platform/tests/event-timing/contextmenu.html new file mode 100644 index 0000000000..9aa05a3b16 --- /dev/null +++ b/testing/web-platform/tests/event-timing/contextmenu.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing contextmenu.</title> +<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=resources/event-timing-test-utils.js></script> +<div id='target' contextmenu="mymenu">Menu +<menu type="context" id="mymenu"> + <menuitem label="label"></menuitem> +</menu> +</div> +<script> + promise_test(async t => { + return testEventType(t, 'contextmenu'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/crossiframe.html b/testing/web-platform/tests/event-timing/crossiframe.html new file mode 100644 index 0000000000..df4d94f09a --- /dev/null +++ b/testing/web-platform/tests/event-timing/crossiframe.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset=utf-8 /> + <title>Event Timing: entries should be observable by its own frame.</title> + <meta name="timeout" content="long"> +</head> + +<body> +<button id='button'>Generate a 'click' event</button> +<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=resources/event-timing-test-utils.js></script> +<iframe id='iframe' src=resources/crossiframe-childframe.html></iframe> +<script> + let clickTimeMin; + let clickDone; + + function validateEntries(entries) { + assert_equals(entries.length, 1, "Only 1 entry should be received"); + const entry = entries[0]; + verifyClickEvent(entry, 'button', true); + + assert_less_than(entry.processingStart, clickDone, + "The entry's processing start should be before clickDone."); + assert_greater_than(entry.startTime, clickTimeMin, + "The entry's start time should be later than clickTimeMin."); + } + + function validateChildFrameEntries(childFrameData) { + if (childFrameData === "failed") { + assert_unreached("Did not receive exactly one entry from child as expected"); + } + // |childFrameData| has properties with the same names as its + // PerformanceEventTiming counterpart. + assert_equals(childFrameData.target, 'iframe_div'); + verifyClickEvent(childFrameData, null, false, 104, 'mousedown'); + assert_less_than(childFrameData.processingStart, childFrameData.clickDone, + "The entry's processing start should be before than the child frame's clickDone."); + } + + promise_test(async t => { + assert_implements(window.PerformanceEventTiming, "Event Timing is not supported"); + // Wait for load event so we can interact with the iframe. + await new Promise(resolve => { + window.addEventListener('load', resolve); + }); + clickTimeMin = performance.now(); + let observedPointerDown = false; + const observerPromise = new Promise(resolve => { + new PerformanceObserver(t.step_func(entries => { + const pointerDowns = entries.getEntriesByName('pointerdown'); + // Ignore the callback if there were no pointerdown entries. + if (pointerDowns.length === 0) + return; + + assert_false(observedPointerDown, + "Observer of main frames should only capture main-frame event-timing entries"); + validateEntries(pointerDowns); + observedPointerDown = true; + resolve(); + })).observe({type: 'event'}); + }); + clickAndBlockMain('button').then(() => { + clickDone = performance.now(); + }); + const childFrameEntriesPromise = new Promise(resolve => { + window.addEventListener("message", (event) => { + // testdriver-complete is a webdriver internal event + if (event.data.type != "testdriver-complete") { + t.step(() => { + validateChildFrameEntries(event.data); + }); + resolve(); + } + }, false); + }); + // Tap on the iframe, with an offset of 10 to target the div inside it. + const actions = new test_driver.Actions() + .pointerMove(10, 10, { origin: document.getElementById("iframe") }) + .pointerDown() + .pointerUp(); + actions.send(); + return Promise.all([observerPromise, childFrameEntriesPromise]); + }, "Event Timing: entries should only be observable by its own frame."); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/event-timing/dblclick.html b/testing/web-platform/tests/event-timing/dblclick.html new file mode 100644 index 0000000000..d33fcfd30d --- /dev/null +++ b/testing/web-platform/tests/event-timing/dblclick.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing dblclick.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Click me</div> +<script> + promise_test(async t => { + return testEventType(t, 'dblclick'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/disconnect-target.html b/testing/web-platform/tests/event-timing/disconnect-target.html new file mode 100644 index 0000000000..04c7c290b6 --- /dev/null +++ b/testing/web-platform/tests/event-timing/disconnect-target.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> + +<script src=resources/event-timing-test-utils.js></script> + +<button id='the_button'>ClickMe</button> + +<script> +async_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + const observer = new PerformanceObserver(t.step_func_done((entryList) => { + const entries = entryList.getEntries().filter(e => e.name === 'pointerdown'); + if (entries.length === 0) + return; + // There must only be one click entry. + assert_equals(entries.length, 1); + const entry = entries[0]; + // This checks that entry.target returns the correct button Node. + verifyClickEvent(entry, 'the_button', true); + const button = document.getElementById('the_button'); + button.parentNode.removeChild(button); + // After removing the button, entry.target should now return null. + assert_equals(entry.target, null); + })); + observer.observe({entryTypes: ['event']}); + clickAndBlockMain('the_button'); +}, "Event Timing: when target is disconnected, entry.target returns null."); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/duration-with-target-low.html b/testing/web-platform/tests/event-timing/duration-with-target-low.html new file mode 100644 index 0000000000..11a8c3ae47 --- /dev/null +++ b/testing/web-platform/tests/event-timing/duration-with-target-low.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: PerformanceObserver with a durationThreshold way smaller than processingDelay</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<div id='myDiv'>Click me</div> +<script> +promise_test(async t => { + return testDurationWithDurationThreshold(t, 'myDiv', numEntries=5, durThreshold=300, processingDelay=0); +}, "PerformanceObserver with durationThreshold of 300 and processingDelay of 0 doesn't see any entries in the observer"); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/event-click-counts.html b/testing/web-platform/tests/event-timing/event-click-counts.html new file mode 100644 index 0000000000..6c6563b958 --- /dev/null +++ b/testing/web-platform/tests/event-timing/event-click-counts.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: eventCounts.</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<div id='div'>Click me</div> +<button id='button'>Click me</button> +<script> + promise_test( t => { + assert_implements(window.EventCounts, "Event Counts isn't supported"); + function testClicks(expectedCount, resolve) { + const clickCount = performance.eventCounts.get('click'); + if (clickCount < expectedCount) { + t.step_timeout(function() { + testClicks(expectedCount, resolve); + }, 5); + return; + } + assert_equals(clickCount, expectedCount,'Incorrect click count.'); + assert_equals(performance.eventCounts.get('mousedown'), expectedCount, 'Incorrect mousedown count'); + assert_equals(performance.eventCounts.get('mouseup'), expectedCount, 'Incorrect mouseup count.'); + resolve(); + } + function promiseClicks(expectedCount) { + return new Promise(resolve => { + testClicks(expectedCount, resolve) + }); + } + + return test_driver.click(document.getElementById('div')).then(() => { + return promiseClicks(1); + }).then(() => { + return test_driver.click(document.getElementById('button')); + }).then(() => { + return promiseClicks(2); + }).then(() => { + return test_driver.click(document.getElementById('div')); + }).then(() => { + return promiseClicks(3); + }); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/event-click-visibilitychange.html b/testing/web-platform/tests/event-timing/event-click-visibilitychange.html new file mode 100644 index 0000000000..beb3ba3512 --- /dev/null +++ b/testing/web-platform/tests/event-timing/event-click-visibilitychange.html @@ -0,0 +1,98 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<meta name="timeout" content="long"> +<title>Event Timing: eventCounts.</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=/page-visibility/resources/window_state_context.js></script> +<script src=resources/event-timing-test-utils.js></script> + +<body> + <button id='target'>Click me</button> + + <script> + let observedEntries = []; + const map = new Map(); + const events = ['pointerdown']; + + promise_test(async t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + const { minimize, restore } = window_state_context(t); + const button = document.getElementById('target'); + + const callback = (entryList) => { observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map))); }; + const readyToResolve = () => { return observedEntries.length >= 1; }; + const observerPromise = createPerformanceObserverPromise(['event'], callback, readyToResolve); + + const tapEventPromise = new Promise(resolve => { + button.addEventListener('pointerdown', async (event) => { + document.body.innerText += "Adding content to force rendering"; + + // await here will yield to event loop, and end event processing time, + // which will allow rendering to continue. + // The visibility change may happen before rendering has a chance + // but it is not guarenteed which will happen first. + await minimize(); + const timeAfterVisibilityFalse = performance.now(); + + await restore(); + const timeAfterVisibilityTrue = performance.now(); + + resolve({ timeAfterVisibilityFalse, timeAfterVisibilityTrue }); + }); + }); + + // A buffered visibility-state PerformanceEntry would have made this test + // cleaner, due to the variability of ordering of events, but it is not + // yet available. + const visibilityEventPromise = new Promise(resolve => { + document.addEventListener('visibilitychange', (event) => { + if (document.visibilityState !== 'visible') { + resolve(performance.now()); + } + }); + }); + + const timeBeforeTap = performance.now(); + await interactAndObserve('tap', button, observerPromise); + + // The order that these events fire is non-deterministic, but we can await + // the result of the promise in any order. + const { timeAfterVisibilityFalse, timeAfterVisibilityTrue } = await tapEventPromise; + const timeOfVisibilityFalse = await visibilityEventPromise; + + assert_equals(observedEntries.length, 1, "Pointerdown was measured"); + const entry = observedEntries[0]; + + assert_not_equals(timeBeforeTap, undefined); + assert_not_equals(timeAfterVisibilityFalse, undefined); + assert_not_equals(timeAfterVisibilityTrue, undefined); + assert_not_equals(timeOfVisibilityFalse, undefined); + + assert_less_than( + entry.processingEnd, + timeOfVisibilityFalse, + "event handler ends before visibility event fires" + ); + assert_less_than( + timeOfVisibilityFalse, + timeAfterVisibilityFalse, + "visibility event fires before event handler continues" + ); + assert_less_than_equal( + entry.startTime + entry.duration, + timeAfterVisibilityFalse, + "event duration ends before visibility is changed" + ); + + }, "Event handlers which change visibility should not measure next paint."); + + </script> +</body> + +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/event-timing/event-counts-zero.html b/testing/web-platform/tests/event-timing/event-counts-zero.html new file mode 100644 index 0000000000..e00eb40255 --- /dev/null +++ b/testing/web-platform/tests/event-timing/event-counts-zero.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: eventCounts.</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script> + test(() => { + assert_implements(window.EventCounts, "Event Counts isn't supported"); + const eventTypes = [ + 'auxclick', + 'click', + 'contextmenu', + 'dblclick', + 'mousedown', + 'mouseenter', + 'mouseleave', + 'mouseout', + 'mouseover', + 'mouseup', + 'pointerover', + 'pointerenter', + 'pointerdown', + 'pointerup', + 'pointercancel', + 'pointerout', + 'pointerleave', + 'gotpointercapture', + 'lostpointercapture', + 'touchstart', + 'touchend', + 'touchcancel', + 'keydown', + 'keypress', + 'keyup', + 'beforeinput', + 'input', + 'compositionstart', + 'compositionupdate', + 'compositionend', + 'dragstart', + 'dragend', + 'dragenter', + 'dragleave', + 'dragover', + 'drop' + ]; + eventTypes.forEach(type => { + assert_equals(performance.eventCounts.get(type), 0, 'There is a nonzero value for ' + type); + }) + assert_equals(performance.eventCounts.size, eventTypes.length, + 'The size of performance.eventCounts is incorrect.'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/event-retarget.html b/testing/web-platform/tests/event-timing/event-retarget.html new file mode 100644 index 0000000000..2ddc85d566 --- /dev/null +++ b/testing/web-platform/tests/event-timing/event-retarget.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> + +<script src=resources/event-timing-test-utils.js></script> + +<custom-button id='custom_button'></custom-button> + +<script> +async_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + let innerButtonClicked = false; + customElements.define('custom-button', class extends HTMLElement { + connectedCallback() { + this.attachShadow({mode: 'open'}); + this.shadowRoot.innerHTML = `<button id='inner_button_id'>Click me</button>`; + this.shadowRoot.getElementById('inner_button_id').onclick = () => { + innerButtonClicked = true; + }; + } + }); + const observer = new PerformanceObserver(t.step_func((entryList) => { + const entries = entryList.getEntriesByName('pointerdown'); + if (entries.length === 0) + return; + + // There must only be one pointerdown entry. + assert_equals(entries.length, 1); + verifyClickEvent(entries[0], 'custom_button', true); + assert_true(innerButtonClicked); + t.done() + })); + observer.observe({entryTypes: ['event']}); + clickAndBlockMain('custom_button'); +}, "Event Timing: target reports the last Event Target, i.e. nothing from shadow DOM."); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/first-input-interactionid-click.html b/testing/web-platform/tests/event-timing/first-input-interactionid-click.html new file mode 100644 index 0000000000..bcd4079256 --- /dev/null +++ b/testing/web-platform/tests/event-timing/first-input-interactionid-click.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>First Input: interactionId-click.</title> +<button id='testButtonId'>Click me</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> + +<script> + let firstInputInteractionId = 0; + let eventTimingPointerDownInteractionId = 0; + let hasFirstInputEntry = false; + let hasEventTimingPointerDownEntry = false; + + promise_test(async t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + const callback = (entryList) => { + entryList.getEntries().forEach(entry => { + switch (entry.entryType) { + case 'first-input': { + firstInputInteractionId = entry.interactionId; + hasFirstInputEntry = true; + break; + } + case 'event': { + if ('pointerdown' == entry.name) { + eventTimingPointerDownInteractionId = entry.interactionId; + hasEventTimingPointerDownEntry = true; + } + break; + } + } + }); + }; + const readyToResolve = () => { + return hasFirstInputEntry && hasEventTimingPointerDownEntry; + } + const observerPromise = createPerformanceObserverPromise(['event', 'first-input'], callback, readyToResolve); + await interactAndObserve('click', document.getElementById('testButtonId'), observerPromise); + + assert_greater_than(firstInputInteractionId, 0, 'The first input entry should have a non-trivial interactionId'); + assert_equals(firstInputInteractionId, eventTimingPointerDownInteractionId, 'The first input entry should have the same interactionId as the event timing pointerdown entry'); + + }, "The interactionId of the first input entry should match the same pointerdown entry of event timing when click."); +</script> + +</html> diff --git a/testing/web-platform/tests/event-timing/first-input-interactionid-tap.html b/testing/web-platform/tests/event-timing/first-input-interactionid-tap.html new file mode 100644 index 0000000000..f5e080eb16 --- /dev/null +++ b/testing/web-platform/tests/event-timing/first-input-interactionid-tap.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>First Input: interactionId-tap.</title> +<button id='testButtonId'>Tap me</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=resources/event-timing-test-utils.js></script> + +<script> + let firstInputInteractionId = 0; + let eventTimingPointerDownInteractionId = 0; + let hasFirstInputEntry = false; + let hasEventTimingPointerDownEntry = false; + + promise_test(async t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + const callback = (entryList) => { + entryList.getEntries().forEach(entry => { + switch (entry.entryType) { + case 'first-input': { + firstInputInteractionId = entry.interactionId; + hasFirstInputEntry = true; + break; + } + case 'event': { + if ('pointerdown' == entry.name) { + eventTimingPointerDownInteractionId = entry.interactionId; + hasEventTimingPointerDownEntry = true; + } + break; + } + } + }); + }; + const readyToResolve = () => { + return hasFirstInputEntry && hasEventTimingPointerDownEntry; + } + const observerPromise = createPerformanceObserverPromise(['event', 'first-input'], callback, readyToResolve); + await interactAndObserve('tap', document.getElementById('testButtonId'), observerPromise); + + assert_greater_than(firstInputInteractionId, 0, 'The first input entry should have a non-trivial interactionId'); + assert_equals(firstInputInteractionId, eventTimingPointerDownInteractionId, 'The first input entry should have the same interactionId as the event timing pointerdown entry'); + + }, "The interactionId of the first input entry should match the same pointerdown entry of event timing when tap."); +</script> + +</html> diff --git a/testing/web-platform/tests/event-timing/first-input-shadow-dom.html b/testing/web-platform/tests/event-timing/first-input-shadow-dom.html new file mode 100644 index 0000000000..5911dabd54 --- /dev/null +++ b/testing/web-platform/tests/event-timing/first-input-shadow-dom.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> + +<body> +<div id='container'> + <custom-button id='custom_button'></custom-button> +</div> +<script> +promise_test(t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + let innerButtonClicked = false; + customElements.define('custom-button', class extends HTMLElement { + connectedCallback() { + this.attachShadow({mode: 'open'}); + this.shadowRoot.innerHTML = `<button id='inner_button_id'>Click me</button>`; + this.shadowRoot.getElementById('inner_button_id').onpointerdown = () => { + innerButtonClicked = true; + }; + } + }); + const observerPromise = new Promise(resolve => { + new PerformanceObserver(t.step_func(entryList => { + // There must only be one first-input entry. + assert_equals(entryList.getEntries().length, 1); + // entry.target must be the shadow host due to retargetting. + assert_equals(entryList.getEntries()[0].target, + document.getElementById('custom_button')); + assert_true(innerButtonClicked, 'Did not reach the shadow DOM event listener!'); + resolve(); + })).observe({entryTypes: ['first-input']}); + }); + const clickPromise = test_driver.click(document.getElementById('custom_button')); + return Promise.all([observerPromise, clickPromise]); +}, "Event Timing: test first input on shadow DOM."); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/event-timing/idlharness.any.js b/testing/web-platform/tests/event-timing/idlharness.any.js new file mode 100644 index 0000000000..b1b57a4bb1 --- /dev/null +++ b/testing/web-platform/tests/event-timing/idlharness.any.js @@ -0,0 +1,20 @@ +// META: global=window,worker +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js + +// https://wicg.github.io/event-timing/ + +'use strict'; + +idl_test( + ['event-timing'], + ['performance-timeline', 'hr-time', 'dom'], + idl_array => { + idl_array.add_objects({ + Performance: ['performance'], + EventCounts: ['performance.eventCounts'], + InteractionCounts: ['performance.interactionCounts'], + // PerformanceEventTiming: [ TODO ] + }); + } +); diff --git a/testing/web-platform/tests/event-timing/interaction-count-click.html b/testing/web-platform/tests/event-timing/interaction-count-click.html new file mode 100644 index 0000000000..73d748c7bd --- /dev/null +++ b/testing/web-platform/tests/event-timing/interaction-count-click.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: interactionCount-click</title> +<div id='div'>Click me</div> +<button id='button'>Click me</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<script> + interactionCount_test('click', [div, button, div]); +</script> + +</html> diff --git a/testing/web-platform/tests/event-timing/interaction-count-press-key.html b/testing/web-platform/tests/event-timing/interaction-count-press-key.html new file mode 100644 index 0000000000..d0d55321a1 --- /dev/null +++ b/testing/web-platform/tests/event-timing/interaction-count-press-key.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: interactionCount-press-key</title> +<input id='input'></input> +<textarea id='textarea'></textarea> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=resources/event-timing-test-utils.js></script> +<script> + interactionCount_test('key', [input, textarea, input], 'h'); +</script> + +</html> diff --git a/testing/web-platform/tests/event-timing/interaction-count-tap.html b/testing/web-platform/tests/event-timing/interaction-count-tap.html new file mode 100644 index 0000000000..561bf5f917 --- /dev/null +++ b/testing/web-platform/tests/event-timing/interaction-count-tap.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: interactionCount-tap</title> +<div id='div'>Tap me</div> +<button id='button'>Tap me</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=resources/event-timing-test-utils.js></script> +<script> + interactionCount_test('tap', [div, button, div]); +</script> + +</html> diff --git a/testing/web-platform/tests/event-timing/interactionid-click.html b/testing/web-platform/tests/event-timing/interactionid-click.html new file mode 100644 index 0000000000..1506e31e1d --- /dev/null +++ b/testing/web-platform/tests/event-timing/interactionid-click.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: interactionId-click.</title> +<button id='button'>Click me.</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<script> + let observedEntries = []; + let map = new Map(); + + function eventsForCheck(entry) { + if (entry.name === 'pointerdown' || entry.name === 'pointerup' || entry.name === 'click' + || entry.name === 'mousedown' || entry.name === 'mouseup') { + map.set(entry.name, entry.interactionId); + return true; + } + return false; + } + async_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + addListenersAndClick(document.getElementById('button')).then(() => { + new PerformanceObserver(t.step_func(entryList => { + observedEntries = observedEntries.concat(entryList.getEntries().filter(eventsForCheck)); + if (observedEntries.length < 5) + return; + assert_equals(map.get('mousedown'), 0, 'Should not have a interactionId'); + assert_equals(map.get('mouseup'), 0, 'Should not have a interactionId'); + assert_greater_than(map.get('pointerdown'), 0, 'Should have a non-trivial interactionId'); + assert_equals(map.get('pointerdown'), map.get('pointerup'), 'Pointerdown and pointerup should have the same interactionId'); + assert_equals(map.get('pointerdown'), map.get('click'), 'Pointerdown and click should have the same interactionId'); + t.done(); + })).observe({ type: 'event', buffered: true }); + }); + + }, "Event Timing: compare event timing interactionId."); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/interactionid-press-key-as-input.html b/testing/web-platform/tests/event-timing/interactionid-press-key-as-input.html new file mode 100644 index 0000000000..b1e725b5c6 --- /dev/null +++ b/testing/web-platform/tests/event-timing/interactionid-press-key-as-input.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<meta name="timeout" content="long"> +<title>Event Timing: interactionId-press-key-as-input.</title> +<textarea id='test'></textarea> +<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=resources/event-timing-test-utils.js></script> + +<script> + let observedEntries = []; + let map = new Map(); + const events = ['keydown', 'keyup']; + + async_test(function (t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + new PerformanceObserver(t.step_func(entryList => { + observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map))); + + if (observedEntries.length < 2) + return; + + events.forEach(e => assert_greater_than(map.get(e), 0, 'Should have a non-trivial interactionId for ' + e + ' event')); + assert_equals(map.get('keydown'), map.get('keyup'), 'The keydown and the keyup should have the same interactionId'); + assert_equals('t', document.getElementById('test').value); + t.done(); + })).observe({ type: "event" }); + + addListenersAndPress(document.getElementById('test'), 't', events); + }, "Event Timing: compare event timing interactionId for key press as input."); +</script> + +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/event-timing/interactionid-press-key-no-effect.html b/testing/web-platform/tests/event-timing/interactionid-press-key-no-effect.html new file mode 100644 index 0000000000..ad4dc2595c --- /dev/null +++ b/testing/web-platform/tests/event-timing/interactionid-press-key-no-effect.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<meta name="timeout" content="long"> +<title>Event Timing: interactionId-press-key-no-effect.</title> +<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=resources/event-timing-test-utils.js></script> + +<body> + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tempus + lacinia nisi, eget tempor orci. Nullam congue pharetra arcu, et consectetur + massa mollis tincidunt. Quisque odio sapien, viverra finibus lectus ac, + consectetur ornare quam. In hac habitasse platea dictumst. Morbi cursus est + odio, non fermentum ligula posuere vitae. Sed ullamcorper convallis rhoncus. + In condimentum neque nec metus hendrerit, et cursus ipsum aliquet. + </p> +</body> +<script> + let observedEntries = []; + let map = new Map(); + const events = ['keydown', 'keyup']; + + async_test(function (t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + new PerformanceObserver(t.step_func(entryList => { + observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map))); + + if (observedEntries.length < 2) + return; + + events.forEach(e => assert_greater_than(map.get(e), 0, 'Should have a non-trivial interactionId for ' + e + ' event')); + assert_equals(map.get('keydown'), map.get('keyup'), 'The keydown and the keyup should have the same interactionId'); + t.done(); + })).observe({ type: "event" }); + + addListenersAndPress(document.body, 't', events); + }, "Event Timing: compare event timing interactionId for key press with no effect."); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/event-timing/interactionid-tap.html b/testing/web-platform/tests/event-timing/interactionid-tap.html new file mode 100644 index 0000000000..6545369088 --- /dev/null +++ b/testing/web-platform/tests/event-timing/interactionid-tap.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: interactionId-tap.</title> +<button id='testButtonId'>Tap</button> +<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=resources/event-timing-test-utils.js></script> + +<script> + let observedEntries = []; + const map = new Map(); + const events = ['pointerdown', 'pointerup']; + + promise_test(async t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + const callback = (entryList) => { observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map))); }; + const readyToResolve = () => { return observedEntries.length >= 2; }; + const observerPromise = createPerformanceObserverPromise(['event'], callback, readyToResolve); + + await interactAndObserve('tap', document.getElementById('testButtonId'), observerPromise); + events.forEach(e => assert_greater_than(map.get(e), 0, 'Should have a non-trivial interactionId for ' + e + ' event')); + assert_equals(map.get('pointerdown'), map.get('pointerup'), 'The pointerdown and the pointerup should have the same interactionId'); + }, "Event Timing: compare event timing interactionId for tap."); + +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/keydown.html b/testing/web-platform/tests/event-timing/keydown.html new file mode 100644 index 0000000000..fd814e12b4 --- /dev/null +++ b/testing/web-platform/tests/event-timing/keydown.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing Keydown.</title> +<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=resources/event-timing-test-utils.js></script> +<input type='text' id='target'></input> +<script> + promise_test(async t => { + return testEventType(t, 'keydown'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/keyup.html b/testing/web-platform/tests/event-timing/keyup.html new file mode 100644 index 0000000000..0ec3670de0 --- /dev/null +++ b/testing/web-platform/tests/event-timing/keyup.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing Keyup.</title> +<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=resources/event-timing-test-utils.js></script> +<input type='text' id='target'></input> +<script> + promise_test(async t => { + return testEventType(t, 'keyup'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/large-duration-threshold.html b/testing/web-platform/tests/event-timing/large-duration-threshold.html new file mode 100644 index 0000000000..4eed8d9756 --- /dev/null +++ b/testing/web-platform/tests/event-timing/large-duration-threshold.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: PerformanceObserver with a durationThreshold of 125</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<div id='myDiv'>Click me</div> +<script> +promise_test(async t => { + return testDuration(t, 'myDiv', numEntries=2, dur=125, slowDur=140); +}, "PerformanceObserver observes events according to its durationThreshold"); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/medium-duration-threshold.html b/testing/web-platform/tests/event-timing/medium-duration-threshold.html new file mode 100644 index 0000000000..a2f79cf6fe --- /dev/null +++ b/testing/web-platform/tests/event-timing/medium-duration-threshold.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: PerformanceObserver with a durationThreshold of 50</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<div id='myDiv'>Click me</div> +<script> +promise_test(async t => { + return testDuration(t, 'myDiv', numEntries=3, dur=50, slowDur=70); +}, "PerformanceObserver observes events according to its durationThreshold"); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/min-duration-threshold.html b/testing/web-platform/tests/event-timing/min-duration-threshold.html new file mode 100644 index 0000000000..b7382f9457 --- /dev/null +++ b/testing/web-platform/tests/event-timing/min-duration-threshold.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: PerformanceObserver with a durationThreshold less than 16</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<div id='myDiv'>Click me</div> +<script> +promise_test(async t => { + return testDuration(t, 'myDiv', numEntries=5, dur=0, slowDur=20); +}, "PerformanceObserver with durationThreshold of 0 sees events of duration >= 16"); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/mousedown.html b/testing/web-platform/tests/event-timing/mousedown.html new file mode 100644 index 0000000000..35a07faeb1 --- /dev/null +++ b/testing/web-platform/tests/event-timing/mousedown.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<meta name="timeout" content="long"> +<title>Event Timing mousedown.</title> +<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=resources/event-timing-test-utils.js></script> +<div id='target'>Click me</div> +<script> + promise_test(async t => { + return testEventType(t, 'mousedown'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/mouseenter.html b/testing/web-platform/tests/event-timing/mouseenter.html new file mode 100644 index 0000000000..1d0171ec46 --- /dev/null +++ b/testing/web-platform/tests/event-timing/mouseenter.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing mouseenter.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Target</div> +<script> + promise_test(async t => { + // PointerMove also creates mouseenter events on the body + return testEventType(t, 'mouseenter', true); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/mouseleave.html b/testing/web-platform/tests/event-timing/mouseleave.html new file mode 100644 index 0000000000..b3cf1447e3 --- /dev/null +++ b/testing/web-platform/tests/event-timing/mouseleave.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing mouseleave.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Target</div> +<script> + promise_test(async t => { + // Loose event because a mouseleave from html -> body also occurs + return testEventType(t, 'mouseleave', true); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/mouseout.html b/testing/web-platform/tests/event-timing/mouseout.html new file mode 100644 index 0000000000..dd321934b0 --- /dev/null +++ b/testing/web-platform/tests/event-timing/mouseout.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing mouseout.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Target</div> +<script> + promise_test(async t => { + return testEventType(t, 'mouseout', true /* looseCount */); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/mouseover.html b/testing/web-platform/tests/event-timing/mouseover.html new file mode 100644 index 0000000000..741ee2d7ec --- /dev/null +++ b/testing/web-platform/tests/event-timing/mouseover.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing mouseover.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Target</div> +<script> + promise_test(async t => { + return testEventType(t, 'mouseover', true /* looseCount */); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/mouseup.html b/testing/web-platform/tests/event-timing/mouseup.html new file mode 100644 index 0000000000..653bef8fdb --- /dev/null +++ b/testing/web-platform/tests/event-timing/mouseup.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing mouseup.</title> +<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=resources/event-timing-test-utils.js></script> +<div id='target'>Target</div> +<script> + promise_test(async t => { + return testEventType(t, 'mouseup'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/only-observe-firstInput.html b/testing/web-platform/tests/event-timing/only-observe-firstInput.html new file mode 100644 index 0000000000..53f9641672 --- /dev/null +++ b/testing/web-platform/tests/event-timing/only-observe-firstInput.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: only observe the first input</title> +<button id='button'>Generate a 'click' event</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> + +<script src=resources/event-timing-test-utils.js></script> + +<script> + /* Test: + PerformanceObserver for first-input is registered + Click 1 + Click 2 + Wait + Expected result: + PerformanceObserver should observe one and only one entry. + */ + async_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + let hasObservedFirstInput = false; + new PerformanceObserver(t.step_func((entryList) => { + assert_false(hasObservedFirstInput); + hasObservedFirstInput = true; + const observedEntries = entryList.getEntries().filter( + entry => entry.name === 'pointerdown'); + assert_equals(observedEntries.length, 1); + assert_equals(observedEntries[0].entryType, 'first-input'); + assert_equals(observedEntries[0].name, 'pointerdown'); + })).observe({ entryTypes: ['first-input'] }); + on_event(window, 'load', () => { + clickAndBlockMain('button').then(() => { + clickAndBlockMain('button').then(wait).then( () => { + // After some wait, the PerformanceObserver should have processed both clicks. + // One and only one first-input entry should have been dispatched, so + // |hasObservedFirstInput| should be true. + t.step_timeout( () => { + assert_true(hasObservedFirstInput); + t.done(); + }, 10); + }); + }); + }); + }, + "Event Timing: check first-input for a PerformanceObserver observing only first-input." + ); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/pointerdown.html b/testing/web-platform/tests/event-timing/pointerdown.html new file mode 100644 index 0000000000..2eb6ae898c --- /dev/null +++ b/testing/web-platform/tests/event-timing/pointerdown.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<meta name="timeout" content="long"> +<title>Event Timing pointerdown.</title> +<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=resources/event-timing-test-utils.js></script> +<div id='target'>Target</div> +<script> + promise_test(async t => { + return testEventType(t, 'pointerdown'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/pointerenter.html b/testing/web-platform/tests/event-timing/pointerenter.html new file mode 100644 index 0000000000..bb8ee08abc --- /dev/null +++ b/testing/web-platform/tests/event-timing/pointerenter.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing pointerenter.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Target</div> +<script> + promise_test(async t => { + // A looseCount because the first move of pointerenter causes a + // `pointerenter` on body + return testEventType(t, 'pointerenter', true); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/pointerleave.html b/testing/web-platform/tests/event-timing/pointerleave.html new file mode 100644 index 0000000000..b17464e0e7 --- /dev/null +++ b/testing/web-platform/tests/event-timing/pointerleave.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing pointerleave.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Target</div> +<script> + promise_test(async t => { + // Loose event because a pointerleave from html -> body also occurs + return testEventType(t, 'pointerleave', true); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/pointerout.html b/testing/web-platform/tests/event-timing/pointerout.html new file mode 100644 index 0000000000..b08f57699c --- /dev/null +++ b/testing/web-platform/tests/event-timing/pointerout.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing pointerout.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Target</div> +<script> + promise_test(async t => { + return testEventType(t, 'pointerout', true /* looseCount */); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/pointerover.html b/testing/web-platform/tests/event-timing/pointerover.html new file mode 100644 index 0000000000..0e1712239c --- /dev/null +++ b/testing/web-platform/tests/event-timing/pointerover.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing pointerover.</title> +<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=resources/event-timing-test-utils.js></script> +<div>Outside target!</div> +<div id='target'>Target</div> +<script> + promise_test(async t => { + return testEventType(t, 'pointerover', true /* looseCount */); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/pointerup.html b/testing/web-platform/tests/event-timing/pointerup.html new file mode 100644 index 0000000000..4324a5beb7 --- /dev/null +++ b/testing/web-platform/tests/event-timing/pointerup.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing pointerup.</title> +<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=resources/event-timing-test-utils.js></script> +<div id='target'>Target</div> +<script> + promise_test(async t => { + return testEventType(t, 'pointerup'); + }) +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/programmatic-click-not-observed.html b/testing/web-platform/tests/event-timing/programmatic-click-not-observed.html new file mode 100644 index 0000000000..049607e04e --- /dev/null +++ b/testing/web-platform/tests/event-timing/programmatic-click-not-observed.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<div id='div' onclick='delay()'>Click me</div> +<div id='div2'>No, click me!</div> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> + +<script src=resources/event-timing-test-utils.js></script> +<script> + let delayCalled = false; + let beforeClick; + function delay() { + const end = performance.now() + 150; + while(performance.now() < end) {} + delayCalled = true; + } + promise_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + let observedPointerDown = false; + const observerPromise = new Promise(resolve => { + const observer = new PerformanceObserver(t.step_func(entryList => { + const pointerDowns = entryList.getEntriesByName('pointerdown'); + // Ignore cases in which there is no pointerdown. + if (pointerDowns.length === 0) + return; + + assert_false(observedPointerDown, 'There must only be one pointerdown entry.'); + assert_equals(pointerDowns.length, 1); + const entry = pointerDowns[0]; + // This ensures that the entry is exposing timing from the second click, i.e. + // the one from the clickAndBlockMain() call. + assert_greater_than_equal(entry.processingStart, beforeClick); + verifyClickEvent(entry, 'div2', true); + observedPointerDown = true; + resolve(); + })); + observer.observe({entryTypes: ['event']}); + }); + document.getElementById('div').click(); + // Take the timestamp after the programmatic click but before the next click. + beforeClick = performance.now(); + // After the programmatic click, use another input to know when entries have been + // dispatched to the PerformanceObserver callback. + const clickPromise = clickAndBlockMain('div2'); + return Promise.all([observerPromise, clickPromise]); + }, "Event Timing: events from programmatic click are not observed"); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/resources/crossiframe-childframe.html b/testing/web-platform/tests/event-timing/resources/crossiframe-childframe.html new file mode 100644 index 0000000000..6a8bc6b642 --- /dev/null +++ b/testing/web-platform/tests/event-timing/resources/crossiframe-childframe.html @@ -0,0 +1,39 @@ +<!DOCType html> +<html> +<body> +<script src=event-timing-test-utils.js></script> +<div style="width: 300px; height: 300px" id='iframe_div' onmousedown="mainThreadBusy(120)"> +<script> +(async () => { + const observerPromise = new Promise(resolve => { + new PerformanceObserver(entryList => { + const mouseDowns = entryList.getEntriesByName('mousedown'); + if (mouseDowns.length === 0) + return; + resolve(mouseDowns); + }).observe({ type:'event' }); + }); + const entries = await observerPromise; + const clickDone = performance.now(); + if (entries.length !== 1) { + top.postMessage("failed", "*"); + return; + } + const entry = entries[0]; + top.postMessage({ + // Entry values (|entry| itself is not clonable) + "name": entry.name, + "cancelable": entry.cancelable, + "entryType": entry.entryType, + "startTime": entry.startTime, + "processingStart": entry.processingStart, + "processingEnd": entry.processingEnd, + "duration": entry.duration, + // Other values + "clickDone" : clickDone, + "target": entry.target.id, + }, '*'); +}) (); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/event-timing/resources/event-timing-test-utils.js b/testing/web-platform/tests/event-timing/resources/event-timing-test-utils.js new file mode 100644 index 0000000000..66aa05a165 --- /dev/null +++ b/testing/web-platform/tests/event-timing/resources/event-timing-test-utils.js @@ -0,0 +1,437 @@ +// Clicks on the element with the given ID. It adds an event handler to the element which +// ensures that the events have a duration of at least |delay|. Calls |callback| during +// event handler if |callback| is provided. +async function clickOnElementAndDelay(id, delay, callback) { + const element = document.getElementById(id); + const clickHandler = () => { + mainThreadBusy(delay); + if (callback) { + callback(); + } + element.removeEventListener("pointerdown", clickHandler); + }; + + element.addEventListener("pointerdown", clickHandler); + await test_driver.click(element); +} + +function mainThreadBusy(duration) { + const now = performance.now(); + while (performance.now() < now + duration); +} + +// This method should receive an entry of type 'event'. |isFirst| is true only when we want +// to check that the event also happens to correspond to the first event. In this case, the +// timings of the 'first-input' entry should be equal to those of this entry. |minDuration| +// is used to compared against entry.duration. +function verifyEvent(entry, eventType, targetId, isFirst=false, minDuration=104, notCancelable=false) { + assert_equals(entry.cancelable, !notCancelable, 'cancelable property'); + assert_equals(entry.name, eventType); + assert_equals(entry.entryType, 'event'); + assert_greater_than_equal(entry.duration, minDuration, + "The entry's duration should be greater than or equal to " + minDuration + " ms."); + assert_greater_than_equal(entry.processingStart, entry.startTime, + "The entry's processingStart should be greater than or equal to startTime."); + assert_greater_than_equal(entry.processingEnd, entry.processingStart, + "The entry's processingEnd must be at least as large as processingStart."); + // |duration| is a number rounded to the nearest 8 ms, so add 4 to get a lower bound + // on the actual duration. + assert_greater_than_equal(entry.duration + 4, entry.processingEnd - entry.startTime, + "The entry's duration must be at least as large as processingEnd - startTime."); + if (isFirst) { + let firstInputs = performance.getEntriesByType('first-input'); + assert_equals(firstInputs.length, 1, 'There should be a single first-input entry'); + let firstInput = firstInputs[0]; + assert_equals(firstInput.name, entry.name); + assert_equals(firstInput.entryType, 'first-input'); + assert_equals(firstInput.startTime, entry.startTime); + assert_equals(firstInput.duration, entry.duration); + assert_equals(firstInput.processingStart, entry.processingStart); + assert_equals(firstInput.processingEnd, entry.processingEnd); + assert_equals(firstInput.cancelable, entry.cancelable); + } + if (targetId) + assert_equals(entry.target, document.getElementById(targetId)); +} + +function verifyClickEvent(entry, targetId, isFirst=false, minDuration=104, event='pointerdown') { + verifyEvent(entry, event, targetId, isFirst, minDuration); +} + +function wait() { + return new Promise((resolve, reject) => { + step_timeout(() => { + resolve(); + }, 0); + }); +} + +function clickAndBlockMain(id) { + return new Promise((resolve, reject) => { + clickOnElementAndDelay(id, 120, resolve); + }); +} + +function waitForTick() { + return new Promise(resolve => { + window.requestAnimationFrame(() => { + window.requestAnimationFrame(resolve); + }); + }); +} + // Add a PerformanceObserver and observe with a durationThreshold of |dur|. This test will + // attempt to check that the duration is appropriately checked by: + // * Asserting that entries received have a duration which is the smallest multiple of 8 + // that is greater than or equal to |dur|. + // * Issuing |numEntries| entries that has duration greater than |slowDur|. + // * Asserting that exactly |numEntries| entries are received. + // Parameters: + // |t| - the test harness. + // |dur| - the durationThreshold for the PerformanceObserver. + // |id| - the ID of the element to be clicked. + // |numEntries| - the number of entries. + // |slowDur| - the min duration of a slow entry. +async function testDuration(t, id, numEntries, dur, slowDur) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + const observerPromise = new Promise(async resolve => { + let minDuration = Math.ceil(dur / 8) * 8; + // Exposed events must always have a minimum duration of 16. + minDuration = Math.max(minDuration, 16); + let numEntriesReceived = 0; + new PerformanceObserver(list => { + const pointerDowns = list.getEntriesByName('pointerdown'); + pointerDowns.forEach(e => { + t.step(() => { + verifyClickEvent(e, id, false /* isFirst */, minDuration); + }); + }); + numEntriesReceived += pointerDowns.length; + // All the entries should be received since the slowDur is higher + // than the duration threshold. + if (numEntriesReceived === numEntries) + resolve(); + }).observe({type: "event", durationThreshold: dur}); + }); + const clicksPromise = new Promise(async resolve => { + for (let index = 0; index < numEntries; index++) { + // Add some click events that has at least slowDur for duration. + await clickOnElementAndDelay(id, slowDur); + } + resolve(); + }); + return Promise.all([observerPromise, clicksPromise]); +} + + // Add a PerformanceObserver and observe with a durationThreshold of |durThreshold|. This test will + // attempt to check that the duration is appropriately checked by: + // * Asserting that entries received have a duration which is the smallest multiple of 8 + // that is greater than or equal to |durThreshold|. + // * Issuing |numEntries| entries that have at least |processingDelay| as duration. + // * Asserting that the entries we receive has duration greater than or equals to the + // duration threshold we setup + // Parameters: + // |t| - the test harness. + // |id| - the ID of the element to be clicked. + // |durThreshold| - the durationThreshold for the PerformanceObserver. + // |numEntries| - the number of slow and number of fast entries. + // |processingDelay| - the event duration we add on each event. + async function testDurationWithDurationThreshold(t, id, numEntries, durThreshold, processingDelay) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + const observerPromise = new Promise(async resolve => { + let minDuration = Math.ceil(durThreshold / 8) * 8; + // Exposed events must always have a minimum duration of 16. + minDuration = Math.max(minDuration, 16); + new PerformanceObserver(t.step_func(list => { + const pointerDowns = list.getEntriesByName('pointerdown'); + pointerDowns.forEach(p => { + assert_greater_than_equal(p.duration, minDuration, + "The entry's duration should be greater than or equal to " + minDuration + " ms."); + }); + resolve(); + })).observe({type: "event", durationThreshold: durThreshold}); + }); + for (let index = 0; index < numEntries; index++) { + // These clicks are expected to be ignored, unless the test has some extra delays. + // In that case, the test will verify the event duration to ensure the event duration is + // greater than the duration threshold + await clickOnElementAndDelay(id, processingDelay); + } + // Send click with event duration equals to or greater than |durThreshold|, so the + // observer promise can be resolved + await clickOnElementAndDelay(id, durThreshold); + return observerPromise; + } + +// Apply events that trigger an event of the given |eventType| to be dispatched to the +// |target|. Some of these assume that the target is not on the top left corner of the +// screen, which means that (0, 0) of the viewport is outside of the |target|. +function applyAction(eventType, target) { + const actions = new test_driver.Actions(); + if (eventType === 'auxclick') { + actions.pointerMove(0, 0, {origin: target}) + .pointerDown({button: actions.ButtonType.MIDDLE}) + .pointerUp({button: actions.ButtonType.MIDDLE}); + } else if (eventType === 'click' || eventType === 'mousedown' || eventType === 'mouseup' + || eventType === 'pointerdown' || eventType === 'pointerup' + || eventType === 'touchstart' || eventType === 'touchend') { + actions.pointerMove(0, 0, {origin: target}) + .pointerDown() + .pointerUp(); + } else if (eventType === 'contextmenu') { + actions.pointerMove(0, 0, {origin: target}) + .pointerDown({button: actions.ButtonType.RIGHT}) + .pointerUp({button: actions.ButtonType.RIGHT}); + } else if (eventType === 'dblclick') { + actions.pointerMove(0, 0, {origin: target}) + .pointerDown() + .pointerUp() + .pointerDown() + .pointerUp() + // Reset by clicking outside of the target. + .pointerMove(0, 0) + .pointerDown() + } else if (eventType === 'mouseenter' || eventType === 'mouseover' + || eventType === 'pointerenter' || eventType === 'pointerover') { + // Move outside of the target and then back inside. + // Moving it to 0, 1 because 0, 0 doesn't cause the pointer to + // move in Firefox. See https://github.com/w3c/webdriver/issues/1545 + actions.pointerMove(0, 1) + .pointerMove(0, 0, {origin: target}); + } else if (eventType === 'mouseleave' || eventType === 'mouseout' + || eventType === 'pointerleave' || eventType === 'pointerout') { + actions.pointerMove(0, 0, {origin: target}) + .pointerMove(0, 0); + } else if (eventType === 'keyup' || eventType === 'keydown') { + // Any key here as an input should work. + // TODO: Switch this to use test_driver.Actions.key{up,down} + // when test driver supports it. + // Please check crbug.com/893480. + const key = 'k'; + return test_driver.send_keys(target, key); + } else { + assert_unreached('The event type ' + eventType + ' is not supported.'); + } + return actions.send(); +} + +function requiresListener(eventType) { + return ['mouseenter', + 'mouseleave', + 'pointerdown', + 'pointerenter', + 'pointerleave', + 'pointerout', + 'pointerover', + 'pointerup', + 'keyup', + 'keydown' + ].includes(eventType); +} + +function notCancelable(eventType) { + return ['mouseenter', 'mouseleave', 'pointerenter', 'pointerleave'].includes(eventType); +} + +// Tests the given |eventType|'s performance.eventCounts value. Since this is populated only when +// the event is processed, we check every 10 ms until we've found the |expectedCount|. +function testCounts(t, resolve, looseCount, eventType, expectedCount) { + const counts = performance.eventCounts.get(eventType); + if (counts < expectedCount) { + t.step_timeout(() => { + testCounts(t, resolve, looseCount, eventType, expectedCount); + }, 10); + return; + } + if (looseCount) { + assert_greater_than_equal(performance.eventCounts.get(eventType), expectedCount, + `Should have at least ${expectedCount} ${eventType} events`) + } else { + assert_equals(performance.eventCounts.get(eventType), expectedCount, + `Should have ${expectedCount} ${eventType} events`); + } + resolve(); +} + +// Tests the given |eventType| by creating events whose target are the element with id +// 'target'. The test assumes that such element already exists. |looseCount| is set for +// eventTypes for which events would occur for other interactions other than the ones being +// specified for the target, so the counts could be larger. +async function testEventType(t, eventType, looseCount=false) { + assert_implements(window.EventCounts, "Event Counts isn't supported"); + const target = document.getElementById('target'); + if (requiresListener(eventType)) { + target.addEventListener(eventType, () =>{}); + } + const initialCount = performance.eventCounts.get(eventType); + if (!looseCount) { + assert_equals(initialCount, 0, 'No events yet.'); + } + // Trigger two 'fast' events of the type. + await applyAction(eventType, target); + await applyAction(eventType, target); + await waitForTick(); + await new Promise(t.step_func(resolve => { + testCounts(t, resolve, looseCount, eventType, initialCount + 2); + })); + // The durationThreshold used by the observer. A slow events needs to be slower than that. + const durationThreshold = 16; + // Now add an event handler to cause a slow event. + target.addEventListener(eventType, () => { + mainThreadBusy(durationThreshold + 4); + }); + const observerPromise = new Promise(async resolve => { + new PerformanceObserver(t.step_func(entryList => { + let eventTypeEntries = entryList.getEntriesByName(eventType); + if (eventTypeEntries.length === 0) + return; + + let entry = null; + if (!looseCount) { + entry = eventTypeEntries[0]; + assert_equals(eventTypeEntries.length, 1); + } else { + // The other events could also be considered slow. Find the one with the correct + // target. + eventTypeEntries.forEach(e => { + if (e.target === document.getElementById('target')) + entry = e; + }); + if (!entry) + return; + } + verifyEvent(entry, + eventType, + 'target', + false /* isFirst */, + durationThreshold, + notCancelable(eventType)); + // Shouldn't need async testing here since we already got the observer entry, but might as + // well reuse the method. + testCounts(t, resolve, looseCount, eventType, initialCount + 3); + })).observe({type: 'event', durationThreshold: durationThreshold}); + }); + // Cause a slow event. + await applyAction(eventType, target); + + await waitForTick(); + + await observerPromise; +} + +function addListeners(element, events) { + const clickHandler = (e) => { + mainThreadBusy(200); + }; + events.forEach(e => { element.addEventListener(e, clickHandler); }); +} + +// The testdriver.js, testdriver-vendor.js and testdriver-actions.js need to be +// included to use this function. +async function tap(element) { + return new test_driver.Actions() + .addPointer("touchPointer", "touch") + .pointerMove(0, 0, { origin: element }) + .pointerDown() + .pointerUp() + .send(); +} + +// The testdriver.js, testdriver-vendor.js need to be included to use this +// function. +async function pressKey(element, key) { + await test_driver.send_keys(element, key); +} + +// The testdriver.js, testdriver-vendor.js need to be included to use this +// function. +async function addListenersAndPress(element, key, events) { + addListeners(element, events); + return pressKey(element, key); +} + +// The testdriver.js, testdriver-vendor.js need to be included to use this +// function. +async function addListenersAndClick(element) { + addListeners(element, + ['mousedown', 'mouseup', 'pointerdown', 'pointerup', 'click']); + return test_driver.click(element); +} + +function filterAndAddToMap(events, map) { + return function (entry) { + if (events.includes(entry.name)) { + map.set(entry.name, entry.interactionId); + return true; + } + return false; + } +} + +async function createPerformanceObserverPromise(observeTypes, callback, readyToResolve +) { + return new Promise(resolve => { + new PerformanceObserver(entryList => { + callback(entryList); + + if (readyToResolve()) { + resolve(); + } + }).observe({ entryTypes: observeTypes }); + }); +} + +// The testdriver.js, testdriver-vendor.js need to be included to use this +// function. +async function interactAndObserve(interactionType, element, observerPromise) { + let interactionPromise; + switch (interactionType) { + case 'tap': { + addListeners(element, ['pointerdown', 'pointerup']); + interactionPromise = tap(element); + break; + } + case 'click': { + addListeners(element, + ['mousedown', 'mouseup', 'pointerdown', 'pointerup', 'click']); + interactionPromise = test_driver.click(element); + break; + } + } + return Promise.all([interactionPromise, observerPromise]); +} + +async function interact(interactionType, element, key = '') { + switch (interactionType) { + case 'click': { + return test_driver.click(element); + } + case 'tap': { + return tap(element); + } + case 'key': { + return test_driver.send_keys(element, key); + } + } +} + +async function verifyInteractionCount(t, expectedCount) { + await t.step_wait(() => { + return performance.interactionCount >= expectedCount; + }, 'interactionCount did not increase enough', 10000, 5); + assert_equals(performance.interactionCount, expectedCount, + 'interactionCount increased more than expected'); +} + +function interactionCount_test(interactionType, elements, key = '') { + return promise_test(async t => { + assert_implements(window.PerformanceEventTiming, + 'Event Timing is not supported'); + assert_equals(performance.interactionCount, 0, 'Initial count is not 0'); + + let expectedCount = 1; + for (let element of elements) { + await interact(interactionType, element, key); + await verifyInteractionCount(t, expectedCount++); + } + }, `EventTiming: verify interactionCount for ${interactionType} interaction`); +} diff --git a/testing/web-platform/tests/event-timing/resources/slow-image.py b/testing/web-platform/tests/event-timing/resources/slow-image.py new file mode 100644 index 0000000000..c2f91655cf --- /dev/null +++ b/testing/web-platform/tests/event-timing/resources/slow-image.py @@ -0,0 +1,7 @@ +import time + +def main(request, response): + # Sleep for 500ms to delay onload. + time.sleep(0.5) + response.headers.set(b"Cache-Control", b"no-cache, must-revalidate"); + response.headers.set(b"Location", b"data:image/gif;base64,R0lGODlhAQABAJAAAMjIyAAAACwAAAAAAQABAAACAgQBADs%3D"); diff --git a/testing/web-platform/tests/event-timing/retrievability.html b/testing/web-platform/tests/event-timing/retrievability.html new file mode 100644 index 0000000000..ab475fc529 --- /dev/null +++ b/testing/web-platform/tests/event-timing/retrievability.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8> +<title>Event Timing: make sure 'event' entries are not retrievable by performance.getEntries* APIs.</title> +<meta name="timeout" content="long"> +<button id='button'>Generate a 'click' event</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> + +<script src=resources/event-timing-test-utils.js></script> + +<script> + function validateEntries() { + const entriesByName = performance.getEntriesByName('mousedown', 'event'); + const entriesByType = performance.getEntriesByType('event'); + const allEntries = performance.getEntries(); + assert_equals(entriesByName.length, 0, 'Event Timing entry should not be retrievable by getEntriesByName'); + assert_equals(entriesByType.length, 0, 'Event Timing entry should not be retrievable by getEntriesByType'); + assert_equals(allEntries.filter(e => e.entryType === 'event').length, 0, 'Event Timing entry should not be retrievable by getEntries'); + } + + /* Timeline: + Begin Busy Loop + Click 1 arrives + End Busy Loop + (Dispatch and Process Click 1 - buffered) + Onload Event Fires + Validate entries + */ + async_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + new PerformanceObserver(t.step_func(entryList => { + if (entryList.getEntriesByName('mousedown').length > 0) { + validateEntries(); + t.done(); + } + })).observe({entryTypes: ['event']}); + clickAndBlockMain('button'); + }, "Event Timing: make sure event-timing entries are not retrievable by performance.getEntries*."); + +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/retrieve-firstInput.html b/testing/web-platform/tests/event-timing/retrieve-firstInput.html new file mode 100644 index 0000000000..c4536cb446 --- /dev/null +++ b/testing/web-platform/tests/event-timing/retrieve-firstInput.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: first-input entry should be buffered even without observer</title> +<button id='button'>Generate a 'click' event</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> + +<script src=resources/event-timing-test-utils.js></script> + +<script> + async_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + function testEntries() { + // First callback is not ensured to have the entry. + if (performance.getEntriesByType('first-input').length === 0) { + t.step_timeout(testEntries, 10); + return; + } + assert_equals(performance.getEntriesByType('first-input').length, 1, + "There should be a first-input entry in the performance timeline"); + const entry = performance.getEntriesByType('first-input')[0]; + assert_equals(entry.name, 'pointerdown'); + assert_equals(entry.entryType, 'first-input'); + assert_greater_than_equal(entry.duration, 104, + "The first input was a long one."); + t.done(); + } + clickAndBlockMain('button').then(wait).then(t.step_func(testEntries)); + }, + "Event Timing: check first-input after onload, observer, click, click." + ); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/shadow-dom-null-target.html b/testing/web-platform/tests/event-timing/shadow-dom-null-target.html new file mode 100644 index 0000000000..89587312c8 --- /dev/null +++ b/testing/web-platform/tests/event-timing/shadow-dom-null-target.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=resources/event-timing-test-utils.js></script> +<body> +<div id='host'> +</div> +<script> +promise_test(async t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + const host = document.getElementById('host'); + const shadowRoot = host.attachShadow({mode: "closed"}); + + const durationThreshold = 16; + + const innerSpan1 = document.createElement("span"); + innerSpan1.textContent = "Span1"; + const innerSpan2 = document.createElement("span"); + innerSpan2.textContent = "Span2"; + + shadowRoot.append(innerSpan1); + shadowRoot.append(innerSpan2); + + innerSpan2.addEventListener("mouseover", function(e) { + // To make this is a slow event + mainThreadBusy(durationThreshold + 4); + }); + + const span1Rect = innerSpan1.getBoundingClientRect(); + const span2Rect = innerSpan2.getBoundingClientRect(); + + const actions = new test_driver.Actions(); + // Move the pointer to innerSpan1 + await actions.pointerMove( + Math.ceil(span1Rect.x + span1Rect.width / 2), + Math.ceil(span1Rect.y + span1Rect.height / 2) + ).send(); + + await waitForTick(); + + const observerPromise = new Promise(resolve => { + new PerformanceObserver(t.step_func(entryList => { + let mouseOverEntries = entryList.getEntriesByName("mouseover"); + assert_equals(mouseOverEntries.length, 1); + assert_equals(mouseOverEntries[0].target, null); + resolve(); + })).observe({type: 'event', durationThreshold: durationThreshold}); + }); + + // Move the pointer to innerSpan2 + // Here we move the pointer within the shadow DOM + await actions.pointerMove( + Math.ceil(span2Rect.x + span2Rect.width / 2), + Math.ceil(span2Rect.y + span2Rect.height / 2) + ).send(); + + await waitForTick(); + + await observerPromise; +}, "Event Timing: Move pointer within shadow DOM should create event-timing entry with null target."); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/event-timing/supported-types-consistent-with-self.html b/testing/web-platform/tests/event-timing/supported-types-consistent-with-self.html new file mode 100644 index 0000000000..b8a6876bf8 --- /dev/null +++ b/testing/web-platform/tests/event-timing/supported-types-consistent-with-self.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing: supportedEntryTypes should be consistent with `'PerformanceEventTiming' in Self` +</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +test(() => { + const isFirstInputSupportedInEntryTypes = PerformanceObserver.supportedEntryTypes.includes('first-input'); + const isEventSupportedInEntryTypes = PerformanceObserver.supportedEntryTypes.includes('event'); + + const isEventTimingSupportedInSelf = 'PerformanceEventTiming' in self; + + assert_equals(isFirstInputSupportedInEntryTypes, isEventTimingSupportedInSelf) + assert_equals(isEventSupportedInEntryTypes, isEventTimingSupportedInSelf) +}, "supportedEntryTypes should be consistent with `'PerformanceEventTiming' in Self`"); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/supported-types.window.js b/testing/web-platform/tests/event-timing/supported-types.window.js new file mode 100644 index 0000000000..1cc43495c0 --- /dev/null +++ b/testing/web-platform/tests/event-timing/supported-types.window.js @@ -0,0 +1,12 @@ +test(() => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + assert_implements(typeof PerformanceObserver.supportedEntryTypes !== "undefined", + 'supportedEntryTypes is not supported'); + const types = PerformanceObserver.supportedEntryTypes; + assert_true(types.includes("first-input"), + "There should be 'first-input' in PerformanceObserver.supportedEntryTypes"); + assert_true(types.includes("event"), + "There should be 'event' in PerformanceObserver.supportedEntryTypes"); + assert_greater_than(types.indexOf("first-input"), types.indexOf('event'), + "The 'first-input' entry should appear after the 'event' entry"); +}, "supportedEntryTypes contains 'event' and 'first-input'."); diff --git a/testing/web-platform/tests/event-timing/timingconditions.html b/testing/web-platform/tests/event-timing/timingconditions.html new file mode 100644 index 0000000000..5f4448c2a1 --- /dev/null +++ b/testing/web-platform/tests/event-timing/timingconditions.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<title>Event Timing only times certain types of trusted event. +</title> +<meta name="timeout" content="long"> +<button id='button' onmousedown='mainThreadBusy(60)' + onfocus='mainThreadBusy(60)'>Generate a 'click' event</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> + +<script src=resources/event-timing-test-utils.js></script> +<script> + let trustedClickStart; + function trustedClickAndBlockMain(id) { + trustedClickStart = performance.now(); + return clickAndBlockMain(id); + } + + function untrustedClickAndBlockMain(id) { + const target = document.getElementById(id); + // Block mainthread in the callback, as dispatchEvent() is a sync call. + target.dispatchEvent(new MouseEvent('mousedown')); + } + + function trustedFocusAndBlockMain(id) { + const target = document.getElementById(id); + trustedFocusStart = performance.now(); + // Block mainthread in the callback, as focus() is a sync call. + target.focus(); + } + + promise_test(function(t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + let observedMouseDown = false; + const observerPromise = new Promise(resolve => { + new PerformanceObserver(t.step_func(entryList => { + const observerCallbackTime = performance.now(); + const mouseDowns = entryList.getEntriesByName('mousedown'); + // Ignore cases in which there is no mousedown. + if (mouseDowns.length === 0) + return; + + assert_false(observedMouseDown, 'Got more than one callback with mousedown.'); + assert_equals(mouseDowns.length, 1, + "Should only observe one mousedown entry. Instead, got these: " + + JSON.stringify(mouseDowns) + "."); + assert_equals(mouseDowns[0].name, 'mousedown', + "The observed entry should be a mousedown"); + assert_less_than(mouseDowns[0].startTime, observerCallbackTime, + "The startTime should be before observerCallbackTime"); + assert_greater_than(mouseDowns[0].startTime, trustedClickStart, + "The startTime should be after trustedClickStart"); + observedMouseDown = true; + resolve(); + })).observe({ entryTypes: ['event'] }); + }); + // Untrusted event of a type event timing cares about. + untrustedClickAndBlockMain('button'); + // Trusted event of a type event timing doesn't cares about. + trustedFocusAndBlockMain('button'); + // Trusted event of a type event timing cares about. + const clickPromise = trustedClickAndBlockMain('button').then(wait); + return Promise.all([observerPromise, clickPromise]); + }, "Event Timing only times certain types of trusted event."); +</script> +</html> diff --git a/testing/web-platform/tests/event-timing/toJSON.html b/testing/web-platform/tests/event-timing/toJSON.html new file mode 100644 index 0000000000..65e2782da3 --- /dev/null +++ b/testing/web-platform/tests/event-timing/toJSON.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Event Timing: toJSON</title> +<body> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/event-timing-test-utils.js></script> +<button id='button'>Generate a 'click' event</button> +<script> + async_test(function (t) { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + const observer = new PerformanceObserver( + t.step_func_done(function(entryList) { + const entry = entryList.getEntries()[0]; + assert_equals(typeof(entry.toJSON), 'function'); + const json = entry.toJSON(); + assert_equals(typeof(json), 'object'); + const keys = [ + // PerformanceEntry + 'name', + 'entryType', + 'startTime', + 'duration', + // PerformanceEventTiming + 'processingStart', + 'processingEnd', + 'cancelable', + ]; + for (const key of keys) { + assert_equals(json[key], entry[key], + 'PerformanceEventTiming ${key} entry does not match its toJSON value'); + } + assert_equals(json['target'], undefined, 'toJSON should not include target'); + }) + ); + observer.observe({type: 'event'}); + clickAndBlockMain('button'); + }, 'Test toJSON() in PerformanceEventTiming.'); +</script> +</body> |