summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/event-timing
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/event-timing')
-rw-r--r--testing/web-platform/tests/event-timing/META.yml3
-rw-r--r--testing/web-platform/tests/event-timing/auxclick.html17
-rw-r--r--testing/web-platform/tests/event-timing/buffered-and-duration-threshold.html44
-rw-r--r--testing/web-platform/tests/event-timing/buffered-flag.html46
-rw-r--r--testing/web-platform/tests/event-timing/click-timing.html62
-rw-r--r--testing/web-platform/tests/event-timing/click.html17
-rw-r--r--testing/web-platform/tests/event-timing/contextmenu.html21
-rw-r--r--testing/web-platform/tests/event-timing/crossiframe.html94
-rw-r--r--testing/web-platform/tests/event-timing/dblclick.html18
-rw-r--r--testing/web-platform/tests/event-timing/disconnect-target.html34
-rw-r--r--testing/web-platform/tests/event-timing/duration-with-target-low.html16
-rw-r--r--testing/web-platform/tests/event-timing/event-click-counts.html46
-rw-r--r--testing/web-platform/tests/event-timing/event-counts-zero.html57
-rw-r--r--testing/web-platform/tests/event-timing/event-retarget.html41
-rw-r--r--testing/web-platform/tests/event-timing/first-input-interactionid-click.html51
-rw-r--r--testing/web-platform/tests/event-timing/first-input-interactionid-tap.html52
-rw-r--r--testing/web-platform/tests/event-timing/first-input-shadow-dom.html42
-rw-r--r--testing/web-platform/tests/event-timing/idlharness.any.js20
-rw-r--r--testing/web-platform/tests/event-timing/interactionid-click.html42
-rw-r--r--testing/web-platform/tests/event-timing/interactionid-press-key-as-input.html38
-rw-r--r--testing/web-platform/tests/event-timing/interactionid-press-key-no-effect.html43
-rw-r--r--testing/web-platform/tests/event-timing/interactionid-tap.html31
-rw-r--r--testing/web-platform/tests/event-timing/keydown.html17
-rw-r--r--testing/web-platform/tests/event-timing/keyup.html17
-rw-r--r--testing/web-platform/tests/event-timing/large-duration-threshold.html16
-rw-r--r--testing/web-platform/tests/event-timing/medium-duration-threshold.html16
-rw-r--r--testing/web-platform/tests/event-timing/min-duration-threshold.html16
-rw-r--r--testing/web-platform/tests/event-timing/mousedown.html18
-rw-r--r--testing/web-platform/tests/event-timing/mouseenter.html19
-rw-r--r--testing/web-platform/tests/event-timing/mouseleave.html19
-rw-r--r--testing/web-platform/tests/event-timing/mouseout.html18
-rw-r--r--testing/web-platform/tests/event-timing/mouseover.html18
-rw-r--r--testing/web-platform/tests/event-timing/mouseup.html17
-rw-r--r--testing/web-platform/tests/event-timing/only-observe-firstInput.html51
-rw-r--r--testing/web-platform/tests/event-timing/pointerdown.html18
-rw-r--r--testing/web-platform/tests/event-timing/pointerenter.html20
-rw-r--r--testing/web-platform/tests/event-timing/pointerleave.html19
-rw-r--r--testing/web-platform/tests/event-timing/pointerout.html18
-rw-r--r--testing/web-platform/tests/event-timing/pointerover.html18
-rw-r--r--testing/web-platform/tests/event-timing/pointerup.html17
-rw-r--r--testing/web-platform/tests/event-timing/programmatic-click-not-observed.html51
-rw-r--r--testing/web-platform/tests/event-timing/resources/crossiframe-childframe.html39
-rw-r--r--testing/web-platform/tests/event-timing/resources/event-timing-test-utils.js397
-rw-r--r--testing/web-platform/tests/event-timing/resources/slow-image.py7
-rw-r--r--testing/web-platform/tests/event-timing/retrievability.html44
-rw-r--r--testing/web-platform/tests/event-timing/retrieve-firstInput.html36
-rw-r--r--testing/web-platform/tests/event-timing/shadow-dom-null-target.html69
-rw-r--r--testing/web-platform/tests/event-timing/supported-types-consistent-with-self.html19
-rw-r--r--testing/web-platform/tests/event-timing/supported-types.window.js12
-rw-r--r--testing/web-platform/tests/event-timing/timingconditions.html69
-rw-r--r--testing/web-platform/tests/event-timing/toJSON.html42
51 files changed, 1972 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-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/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..fc4d1aee72
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/resources/event-timing-test-utils.js
@@ -0,0 +1,397 @@
+// 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.
+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.
+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;
+ }
+}
+
+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.
+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]);
+}
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>