Bug 1509598 [wpt PR 14215] - Move EventTiming tests to WPT, a=testonly
authorNicolás Peña <npm@chromium.org>
Fri, 30 Nov 2018 18:01:37 +0000
changeset 450005 e6929693231bddf4bbaf82bc7caf3b21ff981a79
parent 450004 d1b59860e1d3bc7e9b0e633c1e447a6963b61942
child 450006 bcb9f53e5d7b5b3ff11a2512c9bef10b468c4f21
push id35189
push userccoroiu@mozilla.com
push dateTue, 11 Dec 2018 21:33:05 +0000
treeherdermozilla-central@ac7f3beb6333 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1509598, 14215, 841224, 1349451, 610669
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1509598 [wpt PR 14215] - Move EventTiming tests to WPT, a=testonly Automatic update from web-platform-tests Move EventTiming tests to WPT This CL moves the tests in http/tests/event-timing to external/wpt/event-timing. The slow image is change from php based to python based. The click is now handled by test driver. and setTimeout is replaced with step_timeout. Bug: 841224 Change-Id: I035c087550a2d9a67cda1aed88553c16967d04b6 Reviewed-on: https://chromium-review.googlesource.com/c/1349451 Commit-Queue: Nicolás Peña Moreno <npm@chromium.org> Reviewed-by: Timothy Dresser <tdresser@chromium.org> Cr-Commit-Position: refs/heads/master@{#610669} -- wpt-commits: 42061028f3951380908b400fb1b23a2d6e16bb22 wpt-pr: 14215
testing/web-platform/tests/event-timing/event-timing-bufferbeforeonload.html
testing/web-platform/tests/event-timing/event-timing-crossiframe.html
testing/web-platform/tests/event-timing/event-timing-observer-manual.html
testing/web-platform/tests/event-timing/event-timing-observethenonload.html
testing/web-platform/tests/event-timing/event-timing-onloadthenobserve-firstInput.html
testing/web-platform/tests/event-timing/event-timing-onloadthenobserve.html
testing/web-platform/tests/event-timing/event-timing-only-observe-firstInput.html
testing/web-platform/tests/event-timing/event-timing-retrievability.html
testing/web-platform/tests/event-timing/event-timing-timingconditions.html
testing/web-platform/tests/event-timing/resources/event-timing-crossiframe-childframe.html
testing/web-platform/tests/event-timing/resources/event-timing-observer-manual-childframe.html
testing/web-platform/tests/event-timing/resources/event-timing-support.js
testing/web-platform/tests/event-timing/resources/slow-image.py
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-bufferbeforeonload.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Event Timing: buffer long-latency events before onload</title>
+<button id='button' onclick='clickDelay()'>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-support.js></script>
+<img src=./resources/slow-image.py>
+
+<script>
+  let clickTimeMin;
+  let processingStartMin;
+  let firstClickStart = 0;
+  let firstClickEnd = 0;
+  function clickDelay() {
+    const onclickStart = performance.now();
+    if (firstClickStart == 0)
+      firstClickStart = onclickStart;
+    while(performance.now() < onclickStart + 10) {}
+    if (firstClickEnd == 0)
+      firstClickEnd = performance.now();
+  }
+
+  function validateEntries() {
+    const entries = performance.getEntriesByName('click', 'event');
+    const onloadTime = performance.timing.loadEventStart - performance.timeOrigin;
+
+    const entriesBeforeOnload = entries.filter(
+        e => e.startTime < onloadTime);
+    assert_equals(entriesBeforeOnload.length, 1,
+        "Long latency events before onload should be buffered.");
+    const entry = entriesBeforeOnload[0];
+    verifyClickEvent(entry, true);
+
+    assert_greater_than(entry.processingStart, processingStartMin,
+        "The entry should be processed later than processingStartMin.");
+    assert_greater_than(entry.startTime, clickTimeMin,
+        "The entry's start time should be later than clickTimeMin.");
+    assert_greater_than_equal(firstClickStart, entry.processingStart,
+        "The processingStart must be before the onclick starts.")
+    assert_greater_than_equal(entry.processingEnd, firstClickEnd,
+        "The processingEnd must be after onclick finishes.");
+
+    const entriesAfterOnload = entries.filter(
+        e => e.startTime > onloadTime);
+    assert_equals(entriesAfterOnload.length, 0,
+        "Events after onload shouldn't be buffered.");
+  }
+
+  /* Timeline:
+     Begin Busy Loop
+     Click 1 arrives
+     End Busy Loop
+     (Dispatch and Process Click 1 - buffered)
+     Onload Event Fires
+     Begin Busy Loop
+     Click 2 arrives
+     End Busy Loop
+     (Dispatch and Process Click 2 - not buffered)
+  */
+  async_test(function(t) {
+    clickTimeMin = performance.now();
+    clickAndBlockMain('button');
+    processingStartMin = performance.now();
+    on_event(window, 'load', e => {
+      clickAndBlockMain('button').then(wait).then(
+          t.step_func_done(validateEntries));
+    });
+  }, "Event Timing: click, onload.");
+
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-crossiframe.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset=utf-8 />
+  <title>Event Timing: entries should be observable by its own frame.</title>
+</head>
+
+<body>
+  <button id='button' onclick='1'>Generate a 'click' event</button>
+</body>
+<div>
+  <iframe src=./resources/event-timing-crossiframe-childframe.html></iframe>
+</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-support.js></script>
+<img src=./resources/slow-image.py>
+<script>
+  let clickTimeMin;
+  let processingStartMin;
+
+  function validateEntries() {
+    const entries = performance.getEntriesByName('click', 'event');
+    const onloadTime = performance.timing.loadEventStart - performance.timeOrigin;
+
+    assert_equals(entries.length, 1,
+      "Observer of main frames should only capture main-frame event-timing entries."
+    );
+    const entry = entries[0];
+    verifyClickEvent(entry, true);
+
+    assert_greater_than(entry.processingStart, processingStartMin,
+        "The entry's processing start should be later than processingStartMin.");
+    assert_greater_than(onloadTime, entry.processingStart,
+        "onload should occur later than the entry's procesisng start.");
+    assert_greater_than(entry.startTime, clickTimeMin,
+        "The entry's start time should be later than clickTimeMin.");
+    assert_greater_than(onloadTime, entry.startTime,
+        "onload should occur later than the entry's start time.");
+  }
+
+  function validateChildFrameEntries(childFrameData) {
+    assert_equals(childFrameData.bufferedEntries.length, 1,
+      "Event Timing of child frames should only capture child-frame event-timing entries."
+    );
+    const entry = entries[0];
+    verifyClickEvent(entry);
+
+    assert_greater_than(entry.processingStart, childFrameData.processingStartMin,
+        "The entry's processing start should be later than the child frame's processingStartMin.");
+    assert_greater_than(childFrameData.onloadTime, entry.processingStart,
+        "Child frame's onload should occur later than the entry's processing \
+        start.");
+    assert_greater_than(entry.startTime, childFrameData.clickTimeMin,
+        "The entry's start time should be later than the child frame's \
+        clickTimeMin.");
+    assert_greater_than(childFrameData.onloadTime, entry.startTime,
+        "Child frame's onload should be later than the entry's start time.");
+
+    assert_array_equals(childFrameData.observedEntries,
+        childFrameData.bufferedEntries,
+        "The child frame's observed entries should be buffered as well.");
+  }
+
+  async_test(function(t) {
+    clickTimeMin = performance.now();
+    clickAndBlockMain('button');
+    processingStartMin = performance.now();
+    const childFrameEntriesPromise = new Promise((resolve, reject) => {
+      window.addEventListener("message", (event) => {
+        resolve(event.data);
+      }, false);
+    });
+    on_event(window, 'load', e => {
+      childFrameEntriesPromise.then((entries) => {
+        t.step(() => {
+          validateChildFrameEntries(entries);
+          validateEntries();
+        });
+        t.done();
+      });
+    });
+  }, "Event Timing: entries should only be observable by its own frame.");
+
+</script>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-observer-manual.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset=utf-8 />
+  <title>Event Timing: entries should be observable by its own frame.
+  </title>
+  <script src=./resources/event-timing-support.js></script>
+</head>
+<body>
+  <h2>Description:</h2>
+  <p>
+    <div>
+      The goal of this manual test is to verify that observers that have
+      registered "event" entry type can observe the long-latency input events,
+      and verify the same behavior within iframe and in cross-frame scenario.
+    </div>
+  </p>
+  <h2>Manual test steps:</h2>
+  <p>
+    <div>
+      Step 1: Click the "make busy" button to make main-thread busy for 2 seconds.</span>
+    </div>
+    <div>
+      Step 2: do several clicks on "click while busy" while busy to generate long-latency inputs.
+    </div>
+    <div>
+      Step 3: observe in the "timeline" section that the long-latency clicks are captured by the observer.
+    </div>
+    <div>
+      Step 4: do step 1 to step 3 for the iframe. Observe that the observers only observe input events within its frame.
+    </div>
+  </p>
+  <div>
+    <h2>Actions:</h2>
+    <button id='busy_button' onclick='onMakeBusy()'>make busy</button>
+    <button id='click_input_button' onclick='1'> click while busy </button>
+  </div>
+  <h2>iframe:</h2>
+  <div>
+  <iframe name='childframe' width="100%" height="30%" src=./resources/event-timing-observer-manual-childframe.html></iframe>
+  </div>
+  <h2>Timeline:</h2>
+  <p id='timeline'></p>
+</body>
+<script>
+  function log(message) {
+    const timestamp = performance.now();
+    const elem = document.createElement('div');
+    elem.innerHTML = `${timestamp.toFixed(1)}: ${message}`;
+    const timeline = document.getElementById('timeline');
+    timeline.appendChild(elem);
+  }
+
+  function onMakeBusy() {
+    log("busy start");
+    step_timeout(()=>{
+      mainThreadBusy(2000);
+      log("busy end");
+    }, 0);
+  }
+
+  document.body.onload = () => {
+    new PerformanceObserver((entryList) => {
+      entryList.getEntries().forEach(e => {
+        log(`entry observed: ${JSON.stringify(e)}`);
+      });
+    }).observe({ entryTypes: ['event'] });
+    log("observer registered");
+  };
+
+  window.onmessage = (msg) => {
+    log("received msg: " + msg.data);
+    if (msg.data === 'CHILD_FRAME_IS_READY') {
+      // TODO(crbug/831729): automate clicks on iframe when testdriver support
+      // clicking in iframe.
+    }
+  };
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-observethenonload.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Event Timing: Performance observers can observe long-latency events
+</title>
+<button id='button' onclick='1'>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-support.js></script>
+<img src=./resources/slow-image.py>
+
+<script>
+  let timeAfterFirstClick;
+  let observedEntries = [];
+
+  function verifyBuffer(bufferedEntries) {
+    const loadStart =
+        performance.timing.loadEventStart - performance.timeOrigin;
+    assert_equals(bufferedEntries.length, 1,
+        "Only events before onload should be buffered.");
+    const entry = bufferedEntries[0];
+    assert_greater_than(loadStart, entry.startTime,
+        "onload should be later than entry's start time.");
+    assert_greater_than(entry.processingStart, timeAfterFirstClick,
+        "The entry's processing start should be later than timeAfterFirstClick.");
+    verifyClickEvent(entry, true);
+  }
+
+  function verifyObserverEntries(observedEntries) {
+    const entriesAfterFirstClick = observedEntries.filter(
+        e => e.startTime > timeAfterFirstClick);
+    assert_equals(entriesAfterFirstClick.length, 1,
+        'There should be one event after timeAfterFirstClick.');
+    const entry1 = entriesAfterFirstClick[0];
+    verifyClickEvent(entry1);
+    assert_greater_than(entry1.processingStart, timeAfterFirstClick,
+        "entry1's processing start should be later than timeAfterFirstClick.");
+    assert_greater_than(entry1.startTime, timeAfterFirstClick,
+        "entry1's start time should be later than timeAfterFirstClick.");
+
+    const entriesBeforeFirstClick =
+        observedEntries.filter(e => e.startTime < timeAfterFirstClick);
+    assert_equals(entriesBeforeFirstClick.length, 1,
+        'There should be one event before timeAfterFirstClick.'
+    );
+    const entry2 = entriesBeforeFirstClick[0];
+    verifyClickEvent(entry2);
+    assert_greater_than(entry2.processingStart, timeAfterFirstClick,
+        "entry2's processing start should be later than timeAfterFirstClick.");
+    assert_greater_than(timeAfterFirstClick, entry2.startTime,
+        "timeAfterFirstClick should be later than entry2's start time.");
+  }
+
+  /* Timeline:
+     Observer starts
+     Begin Busy Loop
+     Click 1 arrives
+     End Busy Loop
+     (Dispatch and Process Click 1 (buffered, observed))
+     Onload Event Fires
+     Begin Busy Loop
+     Click 2 arrives
+     End Busy Loop
+     (Dispatch and Process Click 2 (buffered, observed))
+     observer callback start
+  */
+  async_test(function(t) {
+    const observerPromise = new Promise((resolve, reject) => {
+      new PerformanceObserver(function(entryList) {
+        observedEntries = observedEntries.concat(entryList.getEntries());
+        if (observedEntries.length < 2) return;
+        resolve(observedEntries);
+      }).observe({ entryTypes: ['event'] });
+    });
+    clickAndBlockMain('button');
+    timeAfterFirstClick = performance.now();
+    on_event(window, 'load', function(e) {
+      // After onload start and before registering observer.
+      const bufferPromise = clickAndBlockMain('button').then(wait);
+      Promise.all([observerPromise, bufferPromise]).then((results) => {
+        t.step(verifyObserverEntries.bind(null, results[0]));
+        t.step(verifyBuffer.bind(null, performance.getEntriesByName('click', 'event')));
+        t.done();
+      });
+    });
+  }, "Event Timing: click, observer, onload, click.");
+
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-onloadthenobserve-firstInput.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Event Timing: buffer long-latency events before onload</title>
+<button id='button' onclick='1'>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-support.js></script>
+
+<script>
+  /* Timeline:
+     Onload
+     PerformanceObserver is registered
+     Click 1
+     Click 2
+     PerformanceObserver should observe only one firstInput entry.
+     (Dispatch and Process Click 2 - not buffered)
+  */
+  async_test(function(t) {
+    let numFirstInputObserved = 0;
+    let numEventsObserved = 0;
+    new PerformanceObserver(t.step_func((entryList, obs) => {
+        const observedEntries = entryList.getEntries();
+        numEventsObserved += observedEntries.filter(entry =>
+            entry.entryType == 'event').length;
+        numFirstInputObserved += observedEntries.filter(entry =>
+            entry.entryType == 'firstInput').length;
+        if (numEventsObserved >= 2) {
+          assert_equals(performance.getEntriesByType('event').length, 0,
+            "There should be no buffered event entries.");
+          assert_equals(performance.getEntriesByType('firstInput').length, 0,
+            "There should be no buffered firstInput entries.");
+          // There should be 2 event entries and one firstInput entry.
+          assert_equals(numEventsObserved, 2,
+            "There should be 2 observed event entries.");
+          assert_equals(numFirstInputObserved, 1,
+            "There should be only 1 observed firstInput entry.");
+          t.done();
+        }
+    })).observe({ entryTypes: ['event', 'firstInput'] });
+    on_event(window, 'load', () => {
+      clickAndBlockMain('button').then(wait).then(() => {
+        clickAndBlockMain('button').then(wait);
+      });
+    });
+  },
+  "Event Timing: check firstInput after onload, observer, click, click."
+  );
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-onloadthenobserve.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Event Timing: long-latency events after onload and before observer
+registration are lost
+</title>
+<button id='button' onclick='1'>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-support.js></script>
+<script>
+  let callbackTime;
+  let observerStart;
+  let processingStartMin;
+
+  function verifyBufferAndObserverEntries(observedEntries) {
+    // Verify buffer entries
+    const bufferedEntries = performance.getEntriesByName('click', 'event');
+    const bufferedEntriesBeforeObserver = bufferedEntries.filter(e => e.startTime <
+      observerStart);
+    assert_equals(bufferedEntries.length, 0,
+      "Long latency events after onload should not be buffered."
+    );
+
+    // Verify observer entries
+    assert_equals(observedEntries.length, 1, "Long latency task after observer start should be observed.");
+    const entry = observedEntries[0];
+    verifyClickEvent(entry);
+    assert_greater_than(entry.processingStart, processingStartMin,
+        "The entry's processing start should be later than processingStartMin.");
+    assert_greater_than(callbackTime, entry.processingStart,
+        "The callback time should be later than the entry's processing start.");
+    assert_greater_than(entry.startTime, observerStart,
+        "The entry's start time should be later than observer start.");
+    assert_greater_than(callbackTime, entry.startTime,
+        "The callback time should be later than entry's start time.");
+  }
+
+  function startObserver(t) {
+    new PerformanceObserver(t.step_func_done((entryList, obs) => {
+        callbackTime = performance.now();
+        const observedEntries = entryList.getEntries();
+        verifyBufferAndObserverEntries(observedEntries);
+      })).observe({ entryTypes: ['event'] });
+    observerStart = performance.now();
+  }
+
+  /* Timeline:
+     Onload Event fires
+     Begin Busy Loop
+     Click 1 arrives
+     End Busy Loop
+     (Dispatch and Process Click 1 (not buffered, not observed))
+     Observer start
+     Begin Busy Loop
+     Click 2 arrives
+     End Busy Loop
+     (Dispatch and Process Click 2 (not buffered, observed))
+  */
+  async_test(function(t) {
+      on_event(window, 'load', () => {
+        clickAndBlockMain('button').then(() => {
+          startObserver(t);
+          clickAndBlockMain('button').then(wait);
+          processingStartMin = performance.now();
+        });
+      });
+    },
+    "Event Timing: onload, click, observer, click."
+  );
+
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-only-observe-firstInput.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Event Timing: only observe the first input</title>
+<button id='button' onclick='1'>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-support.js></script>
+
+<script>
+  /* Test:
+     PerformanceObserver for firstInput is registered
+     Click 1
+     Click 2
+     Wait
+     Expected result:
+     PerformanceObserver should observe one and only one entry.
+  */
+  async_test(function(t) {
+    let hasObservedFirstInput = false;
+    new PerformanceObserver(t.step_func((entryList) => {
+        assert_false(hasObservedFirstInput);
+        hasObservedFirstInput = true;
+        const observedEntries = entryList.getEntries();
+        assert_equals(observedEntries.length, 1);
+        assert_equals(observedEntries[0].entryType, 'firstInput');
+        assert_equals(observedEntries[0].name, 'click');
+    })).observe({ entryTypes: ['firstInput'] });
+    on_event(window, 'load', () => {
+      clickAndBlockMain('button').then(wait).then(() => {
+        clickAndBlockMain('button').then(wait);
+        // After some wait, the PerformanceObserver should have processed both clicks.
+        // One and only one firstInput entry should have been dispatched, so
+        // |hasObservedFirstInput| should be true.
+        t.step_timeout( () => {
+          assert_true(hasObservedFirstInput);
+          t.done();
+        }, 100);
+      });
+    });
+  },
+  "Event Timing: check firstInput for a PerformanceObserver observing only firstInput."
+  );
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-retrievability.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8>
+<title>Event Timing: make sure event-timing entries are retrievable by existing perf APIs.</title>
+<button id='button' onclick='1'>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-support.js></script>
+<img src=./resources/slow-image.py>
+
+<script>
+  function validateEntries() {
+    const entriesByName = performance.getEntriesByName('click', 'event');
+    const entriesByType = performance.getEntriesByType('event');
+    const allEntries = performance.getEntries();
+    assert_equals(entriesByName.length, 1, 'event-timing entry should be retrievable by getEntriesByName');
+    const e = entriesByName[0];
+    assert_true(entriesByType.includes(e), 'event-timing entry should be retrievable by getEntries');
+    assert_true(allEntries.includes(e), 'event-timing entry should be retrievable by getEntriesByType');
+  }
+
+  /* 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) {
+    clickAndBlockMain('button').then(wait).then(t.step_func_done(validateEntries));
+  }, "Event Timing: make sure event-timing entries are retrievable by existing perf APIs.");
+
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/event-timing-timingconditions.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Event Timing only times certain types of trusted event.
+</title>
+<button id='button' onfocus='mainThreadBusy(100)'>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-support.js></script>
+<script>
+  let trustedClickStart = 0;
+
+  function untrustedClickAndBlockMain(id) {
+    return new Promise((resolve, reject) => {
+      const target = document.getElementById(id);
+      target.dispatchEvent(new MouseEvent('click'));
+      // Block mainthread in the callback, as dispatchEvent() is an sync call.
+      resolve();
+    });
+  }
+
+  function trustedFocusAndBlockMain(id) {
+    return new Promise((resolve, reject) => {
+      const target = document.getElementById(id);
+      trustedFocusStart = performance.now();
+      target.focus();
+      // Block mainthread in the callback,  as focus() is an sync call.
+      resolve();
+    });
+  }
+
+  async_test(function(t) {
+    new PerformanceObserver(entryList => {
+      const observerCallbackTime = performance.now();
+      const entries = entryList.getEntries();
+      t.step(()=>{
+        assert_equals(entries.length, 1,
+            "Observe more than one entries: " +
+            JSON.stringify(entries) + ".");
+        assert_equals(entries[0].name, 'click',
+            "The observed entry should be a click");
+        assert_greater_than(observerCallbackTime, entries[0].startTime,
+            "assert(untrustedClickStart > entries[0].startTime) failed");
+        assert_greater_than(entries[0].startTime, trustedClickStart,
+            "assert(entries[0].startTime > trustedClickStart) failed");
+      });
+      t.done();
+    }).observe({ entryTypes: ['event'] });
+    // untrusted event of a type event timing cares about
+    untrustedClickAndBlockMain('button').then(wait);
+    // trusted event of a type event timing doesn't cares about
+    trustedFocusAndBlockMain('button').then(wait);
+    // trusted event of a type event timing cares about
+    trustedClickStart = performance.now();
+    clickAndBlockMain('button').then(wait);
+  }, "Event Timing only times certain types of trusted event.");
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/resources/event-timing-crossiframe-childframe.html
@@ -0,0 +1,29 @@
+<!DOCType html>
+<html>
+<script src=event-timing-support.js></script>
+<button id='button_child_frame' onclick='2'>Generate a 'click' event</button>
+<img src=slow-image.py>
+<script>
+  const clickTimeMin = performance.now();
+  clickAndBlockMain('button_child_frame');
+  const processingStartMin = performance.now();
+  const observerPromise = new Promise((resolve, reject) => {
+    new PerformanceObserver((entryList) => {
+      resolve(entryList.getEntries());
+    }).observe({ entryTypes: ['event'] });
+  });
+  window.addEventListener('load', e => {
+    observerPromise.then((observedEntries) => {
+      const bufferedEntries = performance.getEntriesByType('event');
+      const onloadTime = performance.timing.loadEventStart - performance.timeOrigin;
+      top.postMessage({
+        "bufferedEntries" : bufferedEntries,
+        "observedEntries": observedEntries,
+        "clickTimeMin": clickTimeMin,
+        "processingStartMin" : processingStartMin,
+        "onloadTime" : onloadTime,
+      }, '*');
+    });
+  });
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/resources/event-timing-observer-manual-childframe.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset=utf-8 />
+</head>
+<script src=./event-timing-support.js></script>
+<script>
+  function log(message) {
+    const timestamp = performance.now();
+    const elem = document.createElement('div');
+    elem.innerHTML = `${timestamp.toFixed(1)}: ${message}`;
+    const timeline = document.getElementById('timeline');
+    timeline.appendChild(elem);
+  }
+
+  function run() {
+    new PerformanceObserver((entryList) => {
+        entryList.getEntries().forEach(e => {
+          log(`entry observed: ${JSON.stringify(e)}`);
+        });
+      }).observe({ entryTypes: ['event'] });
+    log("observer registered");
+    top.postMessage('CHILD_FRAME_IS_READY', "*");
+  }
+
+  function onMakeBusy() {
+    log("busy start");
+    step_timeout(()=>{
+      mainThreadBusy(2000);
+      log("busy end");
+    }, 0);
+  }
+</script>
+<body onload='run()'>
+  <h3>Actions:</h3>
+  <p>
+    <button id='busy_button' onclick='onMakeBusy()'>Make busy</button>
+    <button id='click_input_button' onclick='1'> click while busy </button>
+  </p>
+  <h3>Timeline:</h3>
+  <p id='timeline'></p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/event-timing/resources/event-timing-support.js
@@ -0,0 +1,58 @@
+function clickOnElement(id, resolve) {
+  const element = document.getElementById(id);
+  const clickHandler = () => {
+    element.removeEventListener("click", clickHandler);
+    resolve();
+  };
+  element.addEventListener("click", clickHandler);
+  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'. |is_first| is true only
+// when the event also happens to correspond to the first event. In this case,
+// the timings of the 'firstInput' entry should be equal to those of this entry.
+function verifyClickEvent(entry, is_first=false) {
+  assert_true(entry.cancelable);
+  assert_equals(entry.name, 'click');
+  assert_equals(entry.entryType, 'event');
+  assert_greater_than(entry.duration, 50,
+      "The entry's duration should be greater than 50ms.");
+  assert_greater_than(entry.processingStart, entry.startTime,
+      "The entry's processingStart should be greater than startTime.");
+  assert_greater_than_equal(entry.processingEnd, entry.processingStart,
+      "The entry's processingEnd must be at least as large as processingStart.");
+  assert_greater_than_equal(entry.duration, entry.processingEnd - entry.startTime,
+      "The entry's duration must be at least as large as processingEnd - startTime.");
+  if (is_first) {
+    let firstInputs = performance.getEntriesByType('firstInput');
+    assert_equals(firstInputs.length, 1, 'There should be a single firstInput entry');
+    let firstInput = firstInputs[0];
+    assert_equals(firstInput.name, entry.name);
+    assert_equals(firstInput.entryType, 'firstInput');
+    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);
+  }
+}
+
+function wait() {
+  return new Promise((resolve, reject) => {
+    step_timeout(() => {
+      resolve();
+    }, 0);
+  });
+}
+
+function clickAndBlockMain(id) {
+  return new Promise((resolve, reject) => {
+    clickOnElement(id, resolve);
+    mainThreadBusy(300);
+  });
+}
new file mode 100644
--- /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("Cache-Control", "no-cache, must-revalidate");
+    response.headers.set("Location", "data:image/gif;base64,R0lGODlhAQABAJAAAMjIyAAAACwAAAAAAQABAAACAgQBADs%3D");