summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/performance-timeline
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/performance-timeline')
-rw-r--r--testing/web-platform/tests/performance-timeline/META.yml4
-rw-r--r--testing/web-platform/tests/performance-timeline/back-forward-cache-restoration.tentative.html95
-rw-r--r--testing/web-platform/tests/performance-timeline/buffered-flag-after-timeout.any.js11
-rw-r--r--testing/web-platform/tests/performance-timeline/buffered-flag-observer.any.js15
-rw-r--r--testing/web-platform/tests/performance-timeline/case-sensitivity.any.js64
-rw-r--r--testing/web-platform/tests/performance-timeline/droppedentriescount.any.js81
-rw-r--r--testing/web-platform/tests/performance-timeline/get-invalid-entries.html27
-rw-r--r--testing/web-platform/tests/performance-timeline/idlharness-shadowrealm.window.js2
-rw-r--r--testing/web-platform/tests/performance-timeline/idlharness.any.js25
-rw-r--r--testing/web-platform/tests/performance-timeline/multiple-buffered-flag-observers.any.js32
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id-detached-frame.tentative.html30
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id-element-timing.tentative.html14
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id-initial-load.tentative.html49
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id-long-task-task-attribution.tentative.html14
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id-mark-measure.tentative.html14
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id-reset.tentative.html53
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id-resource-timing.tentative.html14
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id-worker-created-entries.html27
-rw-r--r--testing/web-platform/tests/performance-timeline/navigation-id.helper.js144
-rw-r--r--testing/web-platform/tests/performance-timeline/not-clonable.html10
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/abort-block-bfcache.window.js21
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-attributes.tentative.window.js55
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache-reasons-stay.tentative.window.js47
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache.tentative.window.js28
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-cross-origin-bfcache.tentative.window.js61
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-fetch.tentative.window.js40
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-lock.https.tentative.window.js32
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-navigation-failure.tentative.window.js26
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-not-bfcached.tentative.window.js36
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-redirect-on-history.tentative.window.js53
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-reload.tentative.window.js50
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js61
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-replace.tentative.window.js43
-rw-r--r--testing/web-platform/tests/performance-timeline/not-restored-reasons/test-helper.js48
-rw-r--r--testing/web-platform/tests/performance-timeline/observer-buffered-false.any.js12
-rw-r--r--testing/web-platform/tests/performance-timeline/performanceentry-tojson.any.js21
-rw-r--r--testing/web-platform/tests/performance-timeline/performanceobservers.js44
-rw-r--r--testing/web-platform/tests/performance-timeline/po-callback-mutate.any.js66
-rw-r--r--testing/web-platform/tests/performance-timeline/po-disconnect-removes-observed-types.any.js19
-rw-r--r--testing/web-platform/tests/performance-timeline/po-disconnect.any.js37
-rw-r--r--testing/web-platform/tests/performance-timeline/po-entries-sort.any.js64
-rw-r--r--testing/web-platform/tests/performance-timeline/po-getentries.any.js38
-rw-r--r--testing/web-platform/tests/performance-timeline/po-mark-measure.any.js61
-rw-r--r--testing/web-platform/tests/performance-timeline/po-observe-repeated-type.any.js17
-rw-r--r--testing/web-platform/tests/performance-timeline/po-observe-type.any.js64
-rw-r--r--testing/web-platform/tests/performance-timeline/po-observe.any.js63
-rw-r--r--testing/web-platform/tests/performance-timeline/po-observe.html86
-rw-r--r--testing/web-platform/tests/performance-timeline/po-resource.html48
-rw-r--r--testing/web-platform/tests/performance-timeline/po-takeRecords.any.js34
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/child-frame.html7
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/empty.html0
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/going-back.html9
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/include-frames-helper.js60
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/include-frames-subframe.html43
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/json_resource.json4
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/make_long_task.js4
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/navigation-id-detached-frame-page.html21
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/postmessage-entry.html17
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/square.pngbin0 -> 249 bytes
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/worker-invalid-entries.js6
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/worker-navigation-id.js6
-rw-r--r--testing/web-platform/tests/performance-timeline/resources/worker-with-performance-observer.js6
-rw-r--r--testing/web-platform/tests/performance-timeline/supportedEntryTypes-cross-realm-access.html18
-rw-r--r--testing/web-platform/tests/performance-timeline/supportedEntryTypes.any.js19
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/detached-frame.html27
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-A-A.html93
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-A.html76
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-AA.html51
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-AB.html53
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B-A.html91
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B-B.html57
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B.html50
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/performance-entry-source.html37
-rw-r--r--testing/web-platform/tests/performance-timeline/tentative/with-filter-options-originA.html26
-rw-r--r--testing/web-platform/tests/performance-timeline/timing-removed-iframe.html16
-rw-r--r--testing/web-platform/tests/performance-timeline/webtiming-resolution.any.js25
-rw-r--r--testing/web-platform/tests/performance-timeline/worker-with-performance-observer.html18
77 files changed, 2840 insertions, 0 deletions
diff --git a/testing/web-platform/tests/performance-timeline/META.yml b/testing/web-platform/tests/performance-timeline/META.yml
new file mode 100644
index 0000000000..89fae1db0d
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/META.yml
@@ -0,0 +1,4 @@
+spec: https://w3c.github.io/performance-timeline/
+suggested_reviewers:
+ - plehegar
+ - igrigorik
diff --git a/testing/web-platform/tests/performance-timeline/back-forward-cache-restoration.tentative.html b/testing/web-platform/tests/performance-timeline/back-forward-cache-restoration.tentative.html
new file mode 100644
index 0000000000..c673e09cb4
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/back-forward-cache-restoration.tentative.html
@@ -0,0 +1,95 @@
+<!doctype html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/utils.js"></script>
+ <script src="/common/dispatcher/dispatcher.js"></script>
+ <script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+</head>
+
+<body>
+ <script>
+ const BackForwardCacheRestorationName = '';
+ const BackForwardCacheRestorationType = 'back-forward-cache-restoration';
+
+ let getNavigationId = (i) => {
+ let identifier = 'mark' + i;
+ performance.mark(identifier);
+ return window.performance.getEntriesByName(identifier)[0].navigationId;
+ }
+
+ let getNumberofBackForwardCacheRestorationEntries = (BackForwardCacheRestorationType) => {
+ return window.performance.getEntriesByType(BackForwardCacheRestorationType).length;
+ }
+
+ let getBackForwardCacheRestorationByType = (BackForwardCacheRestorationType) => {
+ let entries = window.performance.getEntriesByType(BackForwardCacheRestorationType);
+ return entries[entries.length - 1];
+ }
+
+ let getBackForwardCacheRestorationByGetAllAndFilter = (BackForwardCacheRestorationType) => {
+ let entries = window.performance.getEntries().filter(e => e.entryType == BackForwardCacheRestorationType);
+ return entries[entries.length - 1];
+ }
+
+ let getBackForwardCacheRestorationByPerformanceObserverBuffered = async (BackForwardCacheRestorationType) => {
+ let p = new Promise(resolve => {
+ new PerformanceObserver((list) => {
+ const entries = list.getEntries().filter(e => e.entryType == BackForwardCacheRestorationType);
+ if (entries.length > 0) {
+ resolve(entries[entries.length - 1]);
+ }
+ }).observe({ type: BackForwardCacheRestorationType, buffered: true });
+ });
+ return await p;
+ }
+
+ let checkEntry = (entry, previousNavigationId) => {
+ assert_equals(entry.name, BackForwardCacheRestorationName);
+ assert_equals(entry.entryType, BackForwardCacheRestorationType);
+ assert_not_equals(entry.navigationId, previousNavigationId);
+ assert_true(entry.pageshowEventStart > entry.startTime);
+ assert_true(entry.pageshowEventEnd >= entry.pageshowEventStart);
+ }
+
+ promise_test(async t => {
+ const pageA = new RemoteContext(token());
+ const pageB = new RemoteContext(token());
+
+ const urlA = executorPath + pageA.context_id;
+ const urlB = originCrossSite + executorPath + pageB.context_id;
+ // Open url A.
+ window.open(urlA, '_blank', 'noopener');
+ await pageA.execute_script(waitForPageShow);
+
+ // Assert no instance of BackForwardCacheRestoration exists without back forward cache navigatoin.
+ let size = await pageA.execute_script(getNumberofBackForwardCacheRestorationEntries);
+ assert_equals(0, size);
+
+ let entry;
+ for (i = 0; i < 2; i++) {
+ let curr_nav_id = await pageA.execute_script(getNavigationId, [i]);
+
+ // Navigate away to url B and back.
+ await navigateAndThenBack(pageA, pageB, urlB);
+
+ // Assert Performance Observer API supports BackForwardCacheRestoration.
+ entry = await pageA.execute_script(getBackForwardCacheRestorationByPerformanceObserverBuffered, [BackForwardCacheRestorationType]);
+ // The navigation id after a bfcache restoration should be different
+ // from that before.
+ checkEntry(entry, curr_nav_id);
+
+ // Assert Performance Timeline API supports BackForwardCacheRestoration.
+ entry = await pageA.execute_script(getBackForwardCacheRestorationByType, [BackForwardCacheRestorationType]);
+ checkEntry(entry, curr_nav_id);
+
+ entry = await pageA.execute_script(getBackForwardCacheRestorationByGetAllAndFilter, [BackForwardCacheRestorationType]);
+ checkEntry(entry, curr_nav_id);
+ }
+ }, 'Performance API for the back forward cache restoration entry.');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/performance-timeline/buffered-flag-after-timeout.any.js b/testing/web-platform/tests/performance-timeline/buffered-flag-after-timeout.any.js
new file mode 100644
index 0000000000..08b3e32314
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/buffered-flag-after-timeout.any.js
@@ -0,0 +1,11 @@
+async_test(t => {
+ performance.mark('foo');
+ t.step_timeout(() => {
+ // After a timeout, PerformanceObserver should still receive entry if using the buffered flag.
+ new PerformanceObserver(t.step_func_done(list => {
+ const entries = list.getEntries();
+ assert_equals(entries.length, 1, 'There should be 1 mark entry.');
+ assert_equals(entries[0].entryType, 'mark');
+ })).observe({type: 'mark', buffered: true});
+ }, 100);
+}, 'PerformanceObserver with buffered flag sees entry after timeout');
diff --git a/testing/web-platform/tests/performance-timeline/buffered-flag-observer.any.js b/testing/web-platform/tests/performance-timeline/buffered-flag-observer.any.js
new file mode 100644
index 0000000000..31dc39c128
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/buffered-flag-observer.any.js
@@ -0,0 +1,15 @@
+async_test( t=> {
+ for (let i = 0; i < 50; i++)
+ performance.mark('foo' + i);
+ let marksCreated = 50;
+ let marksReceived = 0;
+ new PerformanceObserver(list => {
+ marksReceived += list.getEntries().length;
+ if (marksCreated < 100) {
+ performance.mark('bar' + marksCreated);
+ marksCreated++;
+ }
+ if (marksReceived == 100)
+ t.done();
+ }).observe({type: 'mark', buffered: true});
+}, 'PerformanceObserver with buffered flag should see past and future entries.');
diff --git a/testing/web-platform/tests/performance-timeline/case-sensitivity.any.js b/testing/web-platform/tests/performance-timeline/case-sensitivity.any.js
new file mode 100644
index 0000000000..3a98505ae6
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/case-sensitivity.any.js
@@ -0,0 +1,64 @@
+ test(function () {
+ assert_equals(typeof self.performance, "object");
+ assert_equals(typeof self.performance.getEntriesByType, "function");
+ var lowerList = self.performance.getEntriesByType("resource");
+ var upperList = self.performance.getEntriesByType("RESOURCE");
+ var mixedList = self.performance.getEntriesByType("ReSoUrCe");
+
+ assert_not_equals(lowerList.length, 0, "Resource entries exist");
+ assert_equals(upperList.length, 0, "getEntriesByType('RESOURCE').length");
+ assert_equals(mixedList.length, 0, "getEntriesByType('ReSoUrCe').length");
+
+ }, "getEntriesByType values are case sensitive");
+
+ test(function () {
+ assert_equals(typeof self.performance, "object");
+ assert_equals(typeof self.performance.getEntriesByName, "function");
+ var origin = self.location.protocol + "//" + self.location.host;
+ var location1 = origin.toUpperCase() + "/resources/testharness.js";
+ var location2 = self.location.protocol + "//"
+ + self.location.host.toUpperCase() + "/resources/testharness.js";
+ var lowerList = self.performance.getEntriesByName(origin + "/resources/testharness.js");
+ var upperList = self.performance.getEntriesByName(location1);
+ var mixedList = self.performance.getEntriesByName(location2);
+
+ assert_equals(lowerList.length, 1, "Resource entry exist");
+ assert_equals(upperList.length, 0, "getEntriesByName('" + location1 + "').length");
+ assert_equals(mixedList.length, 0, "getEntriesByName('" + location2 + "').length");
+
+ }, "getEntriesByName values are case sensitive");
+
+ async_test(function (t) {
+ // Test type/buffered case sensitivity.
+ observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ assert_unreached("Observer(type) should not be called.");
+ })
+ );
+ observer.observe({type: "Mark"});
+ observer.observe({type: "Measure"});
+ observer.observe({type: "MARK"});
+ observer.observe({type: "MEASURE"});
+ observer.observe({type: "Mark", buffered: true});
+ observer.observe({type: "Measure", buffered: true});
+ observer.observe({type: "MARK", buffered: true});
+ observer.observe({type: "MEASURE", buffered: true});
+ self.performance.mark("mark1");
+ self.performance.measure("measure1");
+
+ // Test entryTypes case sensitivity.
+ observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ assert_unreached("Observer(entryTypes) should not be called.");
+ })
+ );
+ observer.observe({entryTypes: ["Mark", "Measure"]});
+ observer.observe({entryTypes: ["MARK", "MEASURE"]});
+ self.performance.mark("mark1");
+ self.performance.measure("measure1");
+
+ t.step_timeout(function() {
+ t.done();
+ }, 1000);
+
+ }, "observe() and case sensitivity for types/entryTypes and buffered.");
diff --git a/testing/web-platform/tests/performance-timeline/droppedentriescount.any.js b/testing/web-platform/tests/performance-timeline/droppedentriescount.any.js
new file mode 100644
index 0000000000..4de816bdc4
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/droppedentriescount.any.js
@@ -0,0 +1,81 @@
+promise_test(t => {
+ // This setup is required for later tests as well.
+ // Await for a dropped entry.
+ return new Promise(res => {
+ // Set a buffer size of 0 so that new resource entries count as dropped.
+ performance.setResourceTimingBufferSize(0);
+ // Use an observer to make sure the promise is resolved only when the
+ // new entry has been created.
+ new PerformanceObserver(res).observe({type: 'resource'});
+ fetch('resources/square.png?id=1');
+ }).then(() => {
+ return new Promise(resolve => {
+ new PerformanceObserver(t.step_func((entries, obs, options) => {
+ assert_equals(options['droppedEntriesCount'], 0);
+ resolve();
+ })).observe({type: 'mark'});
+ performance.mark('test');
+ })});
+}, 'Dropped entries count is 0 when there are no dropped entries of relevant type.');
+
+promise_test(async t => {
+ return new Promise(resolve => {
+ new PerformanceObserver(t.step_func((entries, obs, options) => {
+ assert_equals(options['droppedEntriesCount'], 1);
+ resolve();
+ })).observe({entryTypes: ['mark', 'resource']});
+ performance.mark('meow');
+ });
+}, 'Dropped entries correctly counted with multiple types.');
+
+promise_test(t => {
+ return new Promise(resolve => {
+ new PerformanceObserver(t.step_func((entries, obs, options) => {
+ assert_equals(options['droppedEntriesCount'], 1,
+ 'There should have been some dropped resource timing entries at this point');
+ resolve();
+ })).observe({type: 'resource', buffered: true});
+ });
+}, 'Dropped entries counted even if observer was not registered at the time.');
+
+promise_test(t => {
+ return new Promise(resolve => {
+ let callback_ran = false;
+ new PerformanceObserver(t.step_func((entries, obs, options) => {
+ if (!callback_ran) {
+ assert_equals(options['droppedEntriesCount'], 2,
+ 'There should be two dropped entries right now.');
+ fetch('resources/square.png?id=3');
+ callback_ran = true;
+ } else {
+ assert_equals(options['droppedEntriesCount'], undefined,
+ 'droppedEntriesCount should be unset after the first callback!');
+ resolve();
+ }
+ })).observe({type: 'resource'});
+ fetch('resources/square.png?id=2');
+ });
+}, 'Dropped entries only surfaced on the first callback.');
+
+
+promise_test(t => {
+ return new Promise(resolve => {
+ let callback_ran = false;
+ let droppedEntriesCount = -1;
+ new PerformanceObserver(t.step_func((entries, obs, options) => {
+ if (!callback_ran) {
+ assert_greater_than(options['droppedEntriesCount'], 0,
+ 'There should be several dropped entries right now.');
+ droppedEntriesCount = options['droppedEntriesCount'];
+ callback_ran = true;
+ obs.observe({type: 'mark'});
+ performance.mark('woof');
+ } else {
+ assert_equals(options['droppedEntriesCount'], droppedEntriesCount,
+ 'There should be droppedEntriesCount due to the new observe().');
+ resolve();
+ }
+ })).observe({type: 'resource'});
+ fetch('resources/square.png?id=4');
+ });
+}, 'Dropped entries surfaced after an observe() call!');
diff --git a/testing/web-platform/tests/performance-timeline/get-invalid-entries.html b/testing/web-platform/tests/performance-timeline/get-invalid-entries.html
new file mode 100644
index 0000000000..33d6589e27
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/get-invalid-entries.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+async_test(function(t) {
+ performance.mark('windowMark');
+ const worker = new Worker("resources/worker-invalid-entries.js");
+ worker.onmessage = function(event) {
+ assert_equals(event.data['invalid'], 0, 'The worker must have 0 invalid entries.');
+ assert_equals(event.data['mark'], 1, 'The worker must have 1 mark entry.');
+ assert_equals(event.data['measure'], 0, 'The worker must have 0 measure entries.');
+ assert_equals(performance.getEntriesByType('invalid').length, 0,
+ 'The window must have 0 invalid entries.');
+ assert_equals(performance.getEntriesByType('mark').length, 1,
+ 'The window must have 1 mark entry.');
+ assert_equals(performance.getEntriesByType('measure').length, 0,
+ 'The window must have 0 measure entries.')
+ t.done();
+ }
+}, 'Get invalid entries from worker and window.');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/performance-timeline/idlharness-shadowrealm.window.js b/testing/web-platform/tests/performance-timeline/idlharness-shadowrealm.window.js
new file mode 100644
index 0000000000..6caaa33061
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/idlharness-shadowrealm.window.js
@@ -0,0 +1,2 @@
+// META: script=/resources/idlharness-shadowrealm.js
+idl_test_shadowrealm(["performance-timeline"], ["hr-time", "dom"]);
diff --git a/testing/web-platform/tests/performance-timeline/idlharness.any.js b/testing/web-platform/tests/performance-timeline/idlharness.any.js
new file mode 100644
index 0000000000..32efebe98f
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/idlharness.any.js
@@ -0,0 +1,25 @@
+// META: global=window,worker
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+// https://w3c.github.io/performance-timeline/
+
+'use strict';
+
+idl_test(
+ ['performance-timeline'],
+ ['hr-time', 'dom'],
+ async idl_array => {
+ idl_array.add_objects({
+ Performance: ['performance'],
+ PerformanceObserver: ['observer'],
+ PerformanceObserverEntryList: ['entryList'],
+ });
+
+ self.entryList = await new Promise((resolve, reject) => {
+ self.observer = new PerformanceObserver(resolve);
+ observer.observe({ entryTypes: ['mark'] });
+ performance.mark('test');
+ });
+ }
+);
diff --git a/testing/web-platform/tests/performance-timeline/multiple-buffered-flag-observers.any.js b/testing/web-platform/tests/performance-timeline/multiple-buffered-flag-observers.any.js
new file mode 100644
index 0000000000..5dd44fb18f
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/multiple-buffered-flag-observers.any.js
@@ -0,0 +1,32 @@
+promise_test(() => {
+ // The first promise waits for one buffered flag observer to receive 3 entries.
+ const promise1 = new Promise(resolve1 => {
+ let numObserved1 = 0;
+ new PerformanceObserver((entryList, obs) => {
+ // This buffered flag observer is constructed after a regular observer detects a mark.
+ new PerformanceObserver(list => {
+ numObserved1 += list.getEntries().length;
+ if (numObserved1 == 3)
+ resolve1();
+ }).observe({type: 'mark', buffered: true});
+ obs.disconnect();
+ }).observe({entryTypes: ['mark']});
+ performance.mark('foo');
+ });
+ // The second promise waits for another buffered flag observer to receive 3 entries.
+ const promise2 = new Promise(resolve2 => {
+ step_timeout(() => {
+ let numObserved2 = 0;
+ // This buffered flag observer is constructed after a delay of 100ms.
+ new PerformanceObserver(list => {
+ numObserved2 += list.getEntries().length;
+ if (numObserved2 == 3)
+ resolve2();
+ }).observe({type: 'mark', buffered: true});
+ }, 100);
+ performance.mark('bar');
+ });
+ performance.mark('meow');
+ // Pass if and only if both buffered observers received all 3 mark entries.
+ return Promise.all([promise1, promise2]);
+}, 'Multiple PerformanceObservers with buffered flag see all entries');
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id-detached-frame.tentative.html b/testing/web-platform/tests/performance-timeline/navigation-id-detached-frame.tentative.html
new file mode 100644
index 0000000000..add11255af
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id-detached-frame.tentative.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>The navigation_id Detached iframe Parent Page.</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(t => {
+ return new Promise(resolve => {
+ const frame = document.createElement("iframe");
+ frame.addEventListener("load", async () => {
+ // Wait for iframe to be detached.
+ while (frame.contentWindow) {
+ await new Promise(r => t.step_timeout(r, 10));
+ }
+ resolve();
+ });
+ frame.src = "resources/navigation-id-detached-frame-page.html";
+ document.body.appendChild(frame);
+ });
+ }, "The navigation_id getter does not crash a window of detached frame");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id-element-timing.tentative.html b/testing/web-platform/tests/performance-timeline/navigation-id-element-timing.tentative.html
new file mode 100644
index 0000000000..7ff415530b
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id-element-timing.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+<script src="navigation-id.helper.js"></script>
+<script>
+ runNavigationIdTest({
+ navigationTimes: 3,
+ testName: 'element_timing',
+ }, "Element Timing navigation id test");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id-initial-load.tentative.html b/testing/web-platform/tests/performance-timeline/navigation-id-initial-load.tentative.html
new file mode 100644
index 0000000000..b996f0f117
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id-initial-load.tentative.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<!--
+Navigation timing, LCP and paint timing entries are only emitted during initial
+load, not after a bfcache navigation. Therefore we only verify the existence of
+navigation id, not the increment.
+-->
+
+<body>
+ <p>This text is to trigger a LCP entry emission.</p>
+ <script>
+ async function NavigationIdsFromLCP() {
+ return new Promise(resolve => {
+ new PerformanceObserver((entryList) => {
+ resolve(entryList.getEntries());
+ }).observe({ type: 'largest-contentful-paint', buffered: true });
+ })
+ }
+
+ promise_test(async t => {
+ // Assert navigation id exists in LCP entries and and are all the same.
+ const navigationIdsOfLCP = (await NavigationIdsFromLCP()).map(e => e.navigationId);
+ assert_true(navigationIdsOfLCP.every(e => e == navigationIdsOfLCP[0]),
+ 'Navigation Ids of LCP entries should be the same at initial navigation');
+
+ // Assert navigation id exists in a NavigationTiming entry.
+ const navigationIdOfNavigationTiming =
+ performance.getEntriesByType('navigation')[0].navigationId;
+ assert_true(!!navigationIdOfNavigationTiming,
+ 'Navigation Id of a navigation timing entry should exist at initial navigation');
+
+ // Assert navigation id exists in PaintTiming entries and are all the same.
+ const navigationIdsOfPaintTiming =
+ performance.getEntriesByType('paint').map(e => e.navigationId);
+ assert_true(navigationIdsOfPaintTiming.every(e =>
+ e == navigationIdsOfPaintTiming[0]),
+ 'Navigation Id of PaintTiming entries should be the same as the initial navigation.');
+
+ // Assert navigation ids are all the same.
+ const navigationIdsOfAll =
+ navigationIdsOfLCP.concat(navigationIdsOfPaintTiming, navigationIdOfNavigationTiming);
+ assert_true(navigationIdsOfAll.every(e => e == navigationIdsOfAll[0]),
+ 'Navigation Id of all entries should be the same as the initial navigation.');
+
+ }, 'Navigation Ids should exist and are all the same as the initial navigation.');
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id-long-task-task-attribution.tentative.html b/testing/web-platform/tests/performance-timeline/navigation-id-long-task-task-attribution.tentative.html
new file mode 100644
index 0000000000..e1da9100ae
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id-long-task-task-attribution.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+<script src="navigation-id.helper.js"></script>
+<script>
+ runNavigationIdTest({
+ navigationTimes: 3,
+ testName: 'long_task_task_attribution',
+ }, "Long Task/Task Attribution navigation id test");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id-mark-measure.tentative.html b/testing/web-platform/tests/performance-timeline/navigation-id-mark-measure.tentative.html
new file mode 100644
index 0000000000..30613ebb98
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id-mark-measure.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+<script src="navigation-id.helper.js"></script>
+<script>
+ runNavigationIdTest({
+ navigationTimes: 3,
+ testName: 'mark_measure',
+ }, "Mark/Measure navigation id test");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id-reset.tentative.html b/testing/web-platform/tests/performance-timeline/navigation-id-reset.tentative.html
new file mode 100644
index 0000000000..f5a2428e5f
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id-reset.tentative.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+<script>
+ const reload = () => {
+ window.location.reload();
+ };
+
+ const getNavigationId = () => {
+ window.performance.mark('initial_load');
+ let entries = window.performance.getEntriesByType('mark');
+ return entries[entries.length - 1].navigationId;
+ }
+
+ promise_test(async t => {
+ const pageA = new RemoteContext(token());
+ const pageB = new RemoteContext(token());
+
+ const urlA = executorPath + pageA.context_id;
+ const urlB = originCrossSite + executorPath + pageB.context_id;
+ // Open url A.
+ window.open(urlA, '_blank', 'noopener')
+ await pageA.execute_script(waitForPageShow);
+
+ let navigationIdInitial = await pageA.execute_script(getNavigationId);
+
+ // Navigate away to url B and back.
+ await navigateAndThenBack(pageA, pageB, urlB);
+
+ // Assert navigation id is re-generated and thus different when the
+ // document is load from bfcache.
+ navigationIdAfterBFCacheNav = await pageA.execute_script(getNavigationId);
+ assert_not_equals(navigationIdInitial, navigationIdAfterBFCacheNav, 'Navigation Id should be \
+ re-generated and different from the previous one after back-forward-cache navigation.');
+
+ // Reload page.
+ await pageA.execute_script(reload);
+ await pageA.execute_script(waitForPageShow);
+
+ navigationIdAfterReset = await pageA.execute_script(getNavigationId);
+
+ assert_not_equals(navigationIdAfterReset, navigationIdAfterBFCacheNav, 'Navigation Id should\
+ be re-generated after reload which is different from the previous one.');
+
+ assert_not_equals(navigationIdAfterReset, navigationIdInitial, 'Navigation Id should\
+ be re-generated after reload which is different from the one of the initial load.');
+
+ }, 'Navigation Id should be re-generated after reload.');
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id-resource-timing.tentative.html b/testing/web-platform/tests/performance-timeline/navigation-id-resource-timing.tentative.html
new file mode 100644
index 0000000000..6d0614a6e2
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id-resource-timing.tentative.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+<script src="navigation-id.helper.js"></script>
+<script>
+ runNavigationIdTest({
+ navigationTimes: 3,
+ testName: 'resource_timing',
+ }, "Resource Timing navigation id test");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id-worker-created-entries.html b/testing/web-platform/tests/performance-timeline/navigation-id-worker-created-entries.html
new file mode 100644
index 0000000000..96fc57be1d
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id-worker-created-entries.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async () => {
+ const worker = new Worker("resources/worker-navigation-id.js");
+
+ const navigationId = await new Promise(resolve => {
+ worker.onmessage = (e) => {
+ resolve(e.data);
+ };
+ worker.postMessage('');
+ });
+
+ assert_equals(navigationId.length, 0,
+ 'Navigation id of performance entries created by a worker should be empty.');
+ }, 'Navigation id of performance entries created by workers should be empty');
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/navigation-id.helper.js b/testing/web-platform/tests/performance-timeline/navigation-id.helper.js
new file mode 100644
index 0000000000..1b72fe9908
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/navigation-id.helper.js
@@ -0,0 +1,144 @@
+// The test functions called in the navigation-counter test. They rely on
+// artifacts defined in
+// '/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js'
+// which should be included before this file to use these functions.
+
+// This function is to obtain navigation ids of all performance entries to
+// verify.
+let testInitial = () => {
+ return window.performance.getEntries().map(e => e.navigationId);
+}
+
+let testMarkMeasure = (markId, markName, MeasureName) => {
+ const markName1 = 'test-mark';
+ const markName2 = 'test-mark' + markId;
+ const measureName = 'test-measure' + markId;
+
+ window.performance.mark(markName1);
+ window.performance.mark(markName2);
+ window.performance.measure(measureName, markName1, markName2);
+ return window.performance.getEntriesByName(markName2).concat(
+ window.performance.getEntriesByName(measureName)).map(e => e.navigationId);
+}
+
+let testResourceTiming = async (resourceTimingEntryId) => {
+ let navigationId;
+
+ let p = new Promise(resolve => {
+ new PerformanceObserver((list) => {
+ const entry = list.getEntries().find(
+ e => e.name.includes('json_resource' + resourceTimingEntryId));
+ if (entry) {
+ navigationId = entry.navigationId;
+ resolve();
+ }
+ }).observe({ type: 'resource' });
+ });
+
+ const resp = await fetch(
+ '/performance-timeline/resources/json_resource' + resourceTimingEntryId + '.json');
+ await p;
+ return [navigationId];
+}
+
+let testElementTiming = async (elementTimingEntryId) => {
+ let navigationId;
+ let p = new Promise(resolve => {
+ new PerformanceObserver((list) => {
+ const entry = list.getEntries().find(
+ e => e.entryType === 'element' && e.identifier === 'test-element-timing' + elementTimingEntryId);
+ if (entry) {
+ navigationId = entry.navigationId;
+ resolve();
+ }
+ }).observe({ type: 'element' });
+ });
+
+ let el = document.createElement('p');
+ el.setAttribute('elementtiming', 'test-element-timing' + elementTimingEntryId);
+ el.textContent = 'test element timing text';
+ document.body.appendChild(el);
+ await p;
+ return [navigationId];
+}
+
+let testLongTask = async () => {
+ let navigationIds = [];
+
+ let p = new Promise(resolve => {
+ new PerformanceObserver((list) => {
+ const entry = list.getEntries().find(e => e.entryType === 'longtask')
+ if (entry) {
+ navigationIds.push(entry.navigationId);
+ navigationIds = navigationIds.concat(
+ entry.attribution.map(a => a.navigationId));
+ resolve();
+ }
+ }).observe({ type: 'longtask' });
+ });
+
+ const script = document.createElement('script');
+ script.src = '/performance-timeline/resources/make_long_task.js';
+ document.body.appendChild(script);
+ await p;
+ document.body.removeChild(script);
+ return navigationIds;
+}
+
+const testFunctionMap = {
+ 'mark_measure': testMarkMeasure,
+ 'resource_timing': testResourceTiming,
+ 'element_timing': testElementTiming,
+ 'long_task_task_attribution': testLongTask,
+};
+
+function runNavigationIdTest(params, description) {
+ const defaultParams = {
+ openFunc: url => window.open(url, '_blank', 'noopener'),
+ scripts: [],
+ funcBeforeNavigation: () => { },
+ targetOrigin: originCrossSite,
+ navigationTimes: 4,
+ funcAfterAssertion: () => { },
+ } // Apply defaults.
+ params = { ...defaultParams, ...params };
+
+ promise_test(async t => {
+ const pageA = new RemoteContext(token());
+ const pageB = new RemoteContext(token());
+
+ const urlA = executorPath + pageA.context_id;
+ const urlB = params.targetOrigin + executorPath + pageB.context_id;
+ // Open url A.
+ params.openFunc(urlA);
+ await pageA.execute_script(waitForPageShow);
+
+ // Assert navigation ids of all performance entries are the same.
+ let navigationIds = await pageA.execute_script(testInitial);
+ assert_true(
+ navigationIds.every(t => t === navigationIds[0]),
+ 'Navigation Ids should be the same as the initial load.');
+
+ for (i = 1; i <= params.navigationTimes; i++) {
+ // Navigate away to url B and back.
+ await navigateAndThenBack(pageA, pageB, urlB);
+
+ // Assert new navigation ids are generated when the document is load from bfcache.
+ let nextNavigationIds = await pageA.execute_script(
+ testFunctionMap[params.testName], [i + 1]);
+
+ // Assert navigation ids of all performance entries are the same.
+ assert_true(
+ nextNavigationIds.every(t => t === nextNavigationIds[0]),
+ 'All Navigation Ids should be same after bfcache navigation.');
+
+ // Assert navigation ids after bfcache navigation are different from those before.
+ assert_true(
+ navigationIds[0] !== nextNavigationIds[0],
+ params.testName +
+ ' Navigation Ids should be re-generated and different from the previous ones.');
+
+ navigationIds = nextNavigationIds;
+ }
+ }, description);
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-clonable.html b/testing/web-platform/tests/performance-timeline/not-clonable.html
new file mode 100644
index 0000000000..d651776e5f
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-clonable.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ const t = async_test("Test that a postMessage of a performance entry fails");
+ addEventListener("message", t.step_func_done(e => {
+ assert_equals(e.data, "PASS");
+ }));
+</script>
+<iframe src="resources/postmessage-entry.html"></iframe>
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/abort-block-bfcache.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/abort-block-bfcache.window.js
new file mode 100644
index 0000000000..8488dd60cc
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/abort-block-bfcache.window.js
@@ -0,0 +1,21 @@
+// META: title=Aborting a parser should block bfcache
+// META: timeout=long
+
+
+async_test(t => {
+ if (!sessionStorage.getItem("pageVisited")) {
+ // This is the first time loading the page.
+ sessionStorage.setItem("pageVisited", 1);
+ t.step_timeout(() => {
+ // Go to another page and instantly come back to this page.
+ location.href = new URL("../resources/going-back.html", window.location);
+ }, 0);
+ // Abort parsing in the middle of loading the page.
+ window.stop();
+ } else {
+ const nrr = performance.getEntriesByType('navigation')[0].notRestoredReasons;
+ assert_equals(nrr.reasons.length, 1);
+ assert_equals(nrr.reasons[0], "parser-aborted");
+ t.done();
+ }
+}, "aborting a parser should block bfcache.");
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-attributes.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-attributes.tentative.window.js
new file mode 100644
index 0000000000..44495fa981
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-attributes.tentative.window.js
@@ -0,0 +1,55 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=/websockets/constants.sub.js
+// META: timeout=long
+
+'use strict';
+
+// Ensure that empty attributes are reported as empty strings and missing
+// attributes are reported as null.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+ // Add a cross-origin iframe.
+ const rc1_child = await rc1.addIframe(
+ /*extraConfig=*/ {
+ origin: 'HTTP_REMOTE_ORIGIN',
+ scripts: [],
+ headers: [],
+ },
+ /*attributes=*/ {id: '', name: ''},
+ );
+ // Use WebSocket to block BFCache.
+ await useWebSocket(rc1);
+ const rc1_child_url = await rc1_child.executeScript(() => {
+ return location.href;
+ });
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[{'reason': 'websocket'}],
+ /*children=*/[{
+ 'url': null,
+ 'src': rc1_child_url,
+ // Id and name should be empty.
+ 'id': '',
+ 'name': '',
+ 'reasons': null,
+ 'children': null
+ }]);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache-reasons-stay.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache-reasons-stay.tentative.window.js
new file mode 100644
index 0000000000..28dbc38575
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache-reasons-stay.tentative.window.js
@@ -0,0 +1,47 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=/websockets/constants.sub.js
+// META: timeout=long
+
+'use strict';
+
+// Ensure that notRestoredReasons are only updated after non BFCache navigation.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ // Use WebSocket to block BFCache.
+ await useWebSocket(rc1);
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[{'reason': 'websocket'}],
+ /*children=*/ []);
+
+ // This time no blocking feature is used, so the page is restored
+ // from BFCache. Ensure that the previous reasons stay there.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ true);
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[{'reason': 'websocket'}],
+ /*children=*/ []);
+});
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache.tentative.window.js
new file mode 100644
index 0000000000..bfb685451c
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache.tentative.window.js
@@ -0,0 +1,28 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: timeout=long
+
+'use strict';
+
+// Ensure that notRestoredReasons is empty for successful BFCache restore.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+
+ // Check the BFCache result and verify that no reasons are recorded
+ // for successful restore.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ true);
+ assert_true(await rc1.executeScript(() => {
+ let reasons =
+ performance.getEntriesByType('navigation')[0].notRestoredReasons;
+ return reasons === null;
+ }));
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-cross-origin-bfcache.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-cross-origin-bfcache.tentative.window.js
new file mode 100644
index 0000000000..42bda12919
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-cross-origin-bfcache.tentative.window.js
@@ -0,0 +1,61 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=/websockets/constants.sub.js
+// META: timeout=long
+
+'use strict';
+
+// Ensure that cross-origin subtree's reasons are not exposed to
+// notRestoredReasons.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+ // Add a cross-origin iframe.
+ const rc1_child = await rc1.addIframe(
+ /*extraConfig=*/ {
+ origin: 'HTTP_REMOTE_ORIGIN',
+ scripts: [],
+ headers: [],
+ },
+ /*attributes=*/ {id: 'test-id'},
+ );
+ // Use WebSocket to block BFCache.
+ await useWebSocket(rc1_child);
+ const rc1_child_url = await rc1_child.executeScript(() => {
+ return location.href;
+ });
+ // Add a child to the iframe.
+ const rc1_grand_child = await rc1_child.addIframe();
+ const rc1_grand_child_url = await rc1_grand_child.executeScript(() => {
+ return location.href;
+ });
+
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[{'reason': "masked"}],
+ /*children=*/[{
+ 'url': null,
+ 'src': rc1_child_url,
+ 'id': 'test-id',
+ // Iframes that are generated by addIframe have an empty name.
+ 'name': '',
+ 'reasons': null,
+ 'children': null
+ }]);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-fetch.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-fetch.tentative.window.js
new file mode 100644
index 0000000000..c8f53a660f
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-fetch.tentative.window.js
@@ -0,0 +1,40 @@
+// META: title=Ensure that ongoing fetch upon entering bfcache blocks bfcache and recorded.
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: timeout=long
+
+'use strict';
+
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+ const wavURL = new URL(get_host_info().HTTP_REMOTE_ORIGIN + '/fetch/range/resources/long-wav.py');
+ await rc1.executeScript((wavURL) => {
+ // Register pagehide handler to create a fetch request.
+ addEventListener('pagehide', (wavURL) => {
+ fetch(wavURL, {
+ keepalive: true
+ });
+ })
+ });
+
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[{'reason': 'fetch'}],
+ /*children=*/[]);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-lock.https.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-lock.https.tentative.window.js
new file mode 100644
index 0000000000..46d8752f20
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-lock.https.tentative.window.js
@@ -0,0 +1,32 @@
+// META: title=Ensure that if WebLock is held upon entering bfcache, it cannot enter bfcache and gets reported.
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: timeout=long
+
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+
+ // Request a WebLock.
+ let return_value = await rc1.executeScript(() => {
+ return new Promise((resolve) => {
+ navigator.locks.request('resource', () => {
+ resolve(42);
+ });
+ })
+ });
+ assert_equals(return_value, 42);
+
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredFromBFCache(rc1, ['lock']);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-navigation-failure.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-navigation-failure.tentative.window.js
new file mode 100644
index 0000000000..4022e6e59f
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-navigation-failure.tentative.window.js
@@ -0,0 +1,26 @@
+// META: title=Ensure that navigation failure blocks bfcache and gets recorded.
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/404.py
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: timeout=long
+
+'use strict';
+const {ORIGIN} = get_host_info();
+
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ {status: 404}, /*options=*/ {features: 'noopener'});
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredFromBFCache(rc1, ['error-document']);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-not-bfcached.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-not-bfcached.tentative.window.js
new file mode 100644
index 0000000000..1cf1d55d90
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-not-bfcached.tentative.window.js
@@ -0,0 +1,36 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=/websockets/constants.sub.js
+// META: timeout=long
+
+'use strict';
+
+// Ensure that notRestoredReasons is populated when not restored.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ // Use WebSocket to block BFCache.
+ await useWebSocket(rc1);
+
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[{'reason': 'websocket'}],
+ /*children=*/ []);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-redirect-on-history.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-redirect-on-history.tentative.window.js
new file mode 100644
index 0000000000..7191456cc8
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-redirect-on-history.tentative.window.js
@@ -0,0 +1,53 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=/websockets/constants.sub.js
+// META: timeout=long
+
+'use strict';
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+
+// Ensure that notRestoredReasons reset after the server redirect.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ // Use WebSocket to block BFCache.
+ await useWebSocket(rc1);
+
+ // Create a remote context with the redirected URL.
+ let rc1_redirected =
+ await rcHelper.createContext(/*extraConfig=*/ {
+ origin: 'HTTP_ORIGIN',
+ scripts: [],
+ headers: [],
+ });
+
+ const redirectUrl =
+ `${ORIGIN}/common/redirect.py?location=${encodeURIComponent(rc1_redirected.url)}`;
+ // Replace the history state.
+ await rc1.executeScript((url) => {
+ window.history.replaceState(null, '', url);
+ }, [redirectUrl]);
+
+ // Navigate away.
+ const newRemoteContextHelper = await rc1.navigateToNew();
+
+ // Go back.
+ await newRemoteContextHelper.historyBack();
+
+ const navigation_entry = await rc1_redirected.executeScript(() => {
+ return performance.getEntriesByType('navigation')[0];
+ });
+ assert_equals(
+ navigation_entry.redirectCount, 1, 'Expected redirectCount is 1.');
+ // Becauase of the redirect, notRestoredReasons is reset.
+ assert_equals(
+ navigation_entry.notRestoredReasons, null,
+ 'Expected notRestoredReasons is null.');
+});
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-reload.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-reload.tentative.window.js
new file mode 100644
index 0000000000..1a8972778d
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-reload.tentative.window.js
@@ -0,0 +1,50 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=/websockets/constants.sub.js
+// META: timeout=long
+
+'use strict';
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+
+// Ensure that notRestoredReasons reset after the server redirect.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+ // Use WebSocket to block BFCache.
+ await useWebSocket(rc1);
+
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[{'reason': 'websocket'}],
+ /*children=*/ []);
+
+ // Reload.
+ await rc1.navigate(() => {
+ location.reload();
+ }, []);
+
+ // Becauase of the reload, notRestoredReasons is reset.
+ const navigation_entry = await rc1.executeScript(() => {
+ return performance.getEntriesByType('navigation')[0];
+ });
+
+ assert_equals(
+ navigation_entry.notRestoredReasons, null,
+ 'Expected notRestoredReasons is null.');
+});
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js
new file mode 100644
index 0000000000..89d66b070e
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js
@@ -0,0 +1,61 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=/websockets/constants.sub.js
+// META: timeout=long
+
+
+'use strict';
+
+// Ensure that same-origin subtree's reasons are exposed to notRestoredReasons.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+ // Add a same-origin iframe and use WebSocket.
+ const rc1_child = await rc1.addIframe(
+ /*extra_config=*/ {}, /*attributes=*/ {id: 'test-id'});
+ await useWebSocket(rc1_child);
+
+ const rc1_child_url = await rc1_child.executeScript(() => {
+ return location.href;
+ });
+ // Add a child to the iframe.
+ const rc1_grand_child = await rc1_child.addIframe();
+ const rc1_grand_child_url = await rc1_grand_child.executeScript(() => {
+ return location.href;
+ });
+
+ // Check the BFCache result and the reported reasons.
+ await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[],
+ /*children=*/[{
+ 'url': rc1_child_url,
+ 'src': rc1_child_url,
+ 'id': 'test-id',
+ 'name': '',
+ 'reasons': [{'reason': 'websocket'}],
+ 'children': [{
+ 'url': rc1_grand_child_url,
+ 'src': rc1_grand_child_url,
+ 'id': '',
+ 'name': '',
+ 'reasons': [],
+ 'children': []
+ }]
+ }]);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-replace.tentative.window.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-replace.tentative.window.js
new file mode 100644
index 0000000000..1162bfaf76
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-replace.tentative.window.js
@@ -0,0 +1,43 @@
+// META: title=RemoteContextHelper navigation using BFCache
+// META: script=./test-helper.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=/websockets/constants.sub.js
+// META: timeout=long
+
+'use strict';
+
+// Ensure that notRestoredReasons are accessible after history replace.
+promise_test(async t => {
+ const rcHelper = new RemoteContextHelper();
+ // Open a window with noopener so that BFCache will work.
+ const rc1 = await rcHelper.addWindow(
+ /*config=*/ null, /*options=*/ {features: 'noopener'});
+ const rc1_url = await rc1.executeScript(() => {
+ return location.href;
+ });
+
+ // Use WebSocket to block BFCache.
+ await useWebSocket(rc1);
+ // Navigate away.
+ const newRemoteContextHelper = await rc1.navigateToNew();
+ // Replace the history state to a same-origin site.
+ await newRemoteContextHelper.executeScript((destUrl) => {
+ window.history.replaceState(null, '', '#');
+ });
+ // Go back.
+ await newRemoteContextHelper.historyBack();
+
+ // Reasons are not reset for same-origin replace.
+ await assertNotRestoredReasonsEquals(
+ rc1,
+ /*url=*/ rc1_url,
+ /*src=*/ null,
+ /*id=*/ null,
+ /*name=*/ null,
+ /*reasons=*/[{'reason': 'websocket'}],
+ /*children=*/ []);
+});
diff --git a/testing/web-platform/tests/performance-timeline/not-restored-reasons/test-helper.js b/testing/web-platform/tests/performance-timeline/not-restored-reasons/test-helper.js
new file mode 100644
index 0000000000..97695cd0ac
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/test-helper.js
@@ -0,0 +1,48 @@
+// META: script=../../html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+
+async function assertNotRestoredReasonsEquals(
+ remoteContextHelper, url, src, id, name, reasons, children) {
+ let result = await remoteContextHelper.executeScript(() => {
+ return performance.getEntriesByType('navigation')[0].notRestoredReasons;
+ });
+ assertReasonsStructEquals(
+ result, url, src, id, name, reasons, children);
+}
+
+function assertReasonsStructEquals(
+ result, url, src, id, name, reasons, children) {
+ assert_equals(result.url, url);
+ assert_equals(result.src, src);
+ assert_equals(result.id, id);
+ assert_equals(result.name, name);
+
+ // Reasons should match.
+ let expected = new Set(reasons);
+ let actual = new Set(result.reasons);
+ matchReasons(extractReason(expected), extractReason(actual));
+
+ // Children should match.
+ if (children == null) {
+ assert_equals(result.children, children);
+ } else {
+ for (let j = 0; j < children.length; j++) {
+ assertReasonsStructEquals(
+ result.children[0], children[0].url,
+ children[0].src, children[0].id, children[0].name, children[0].reasons,
+ children[0].children);
+ }
+ }
+}
+
+// Requires:
+// - /websockets/constants.sub.js in the test file and pass the domainPort
+// constant here.
+async function useWebSocket(remoteContextHelper) {
+ let return_value = await remoteContextHelper.executeScript((domain) => {
+ return new Promise((resolve) => {
+ var webSocketInNotRestoredReasonsTests = new WebSocket(domain + '/echo');
+ webSocketInNotRestoredReasonsTests.onopen = () => { resolve(42); };
+ });
+ }, [SCHEME_DOMAIN_PORT]);
+ assert_equals(return_value, 42);
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/observer-buffered-false.any.js b/testing/web-platform/tests/performance-timeline/observer-buffered-false.any.js
new file mode 100644
index 0000000000..a28100b0fd
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/observer-buffered-false.any.js
@@ -0,0 +1,12 @@
+async_test(t => {
+ performance.mark('foo');
+ // Use a timeout to ensure the remainder of the test runs after the entry is created.
+ t.step_timeout(() => {
+ // Observer with buffered flag set to false should not see entry.
+ new PerformanceObserver(() => {
+ assert_unreached('Should not have observed any entry!');
+ }).observe({type: 'mark', buffered: false});
+ // Use a timeout to give time to the observer.
+ t.step_timeout(t.step_func_done(() => {}), 100);
+ }, 0);
+}, 'PerformanceObserver without buffered flag set to false cannot see past entries.');
diff --git a/testing/web-platform/tests/performance-timeline/performanceentry-tojson.any.js b/testing/web-platform/tests/performance-timeline/performanceentry-tojson.any.js
new file mode 100644
index 0000000000..44f0156eec
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/performanceentry-tojson.any.js
@@ -0,0 +1,21 @@
+test(() => {
+ performance.mark('markName');
+ performance.measure('measureName');
+
+ const entries = performance.getEntries();
+ const performanceEntryKeys = [
+ 'name',
+ 'entryType',
+ 'startTime',
+ 'duration'
+ ];
+ for (let i = 0; i < entries.length; ++i) {
+ assert_equals(typeof(entries[i].toJSON), 'function');
+ const json = entries[i].toJSON();
+ assert_equals(typeof(json), 'object');
+ for (const key of performanceEntryKeys) {
+ assert_equals(json[key], entries[i][key],
+ `entries[${i}].toJSON().${key} should match entries[${i}].${key}`);
+ }
+ }
+}, 'Test toJSON() in PerformanceEntry');
diff --git a/testing/web-platform/tests/performance-timeline/performanceobservers.js b/testing/web-platform/tests/performance-timeline/performanceobservers.js
new file mode 100644
index 0000000000..3f357374ef
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/performanceobservers.js
@@ -0,0 +1,44 @@
+// Compares a performance entry to a predefined one
+// perfEntriesToCheck is an array of performance entries from the user agent
+// expectedEntries is an array of performance entries minted by the test
+function checkEntries(perfEntriesToCheck, expectedEntries) {
+ function findMatch(pe) {
+ // we match based on entryType and name
+ for (var i = expectedEntries.length - 1; i >= 0; i--) {
+ var ex = expectedEntries[i];
+ if (ex.entryType === pe.entryType && ex.name === pe.name) {
+ return ex;
+ }
+ }
+ return null;
+ }
+
+ assert_equals(perfEntriesToCheck.length, expectedEntries.length, "performance entries must match");
+
+ perfEntriesToCheck.forEach(function (pe1) {
+ assert_not_equals(findMatch(pe1), null, "Entry matches");
+ });
+}
+
+// Waits for performance.now to advance. Since precision reduction might
+// cause it to return the same value across multiple calls.
+function wait() {
+ var now = performance.now();
+ while (now === performance.now())
+ continue;
+}
+
+// Ensure the entries list is sorted by startTime.
+function checkSorted(entries) {
+ assert_not_equals(entries.length, 0, "entries list must not be empty");
+ if (!entries.length)
+ return;
+
+ var sorted = false;
+ var lastStartTime = entries[0].startTime;
+ for (var i = 1; i < entries.length; ++i) {
+ var currStartTime = entries[i].startTime;
+ assert_less_than_equal(lastStartTime, currStartTime, "entry list must be sorted by startTime");
+ lastStartTime = currStartTime;
+ }
+}
diff --git a/testing/web-platform/tests/performance-timeline/po-callback-mutate.any.js b/testing/web-platform/tests/performance-timeline/po-callback-mutate.any.js
new file mode 100644
index 0000000000..8f1b09bc37
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-callback-mutate.any.js
@@ -0,0 +1,66 @@
+// META: script=performanceobservers.js
+
+ async_test(function (t) {
+ var callbackCount = 0;
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ callbackCount++;
+
+ if (callbackCount === 1) {
+ checkEntries(entryList.getEntries(), [
+ {entryType: "measure", name: "measure1"},
+ ]);
+ observer.observe({entryTypes: ["mark"]});
+ self.performance.mark("mark2");
+ self.performance.measure("measure2");
+ return;
+ }
+
+ if (callbackCount === 2) {
+ checkEntries(entryList.getEntries(), [
+ {entryType: "mark", name: "mark2"},
+ ]);
+ self.performance.mark("mark-before-change-observe-state-to-measure");
+ self.performance.measure("measure-before-change-observe-state-to-measure");
+ observer.observe({entryTypes: ["measure"]});
+ self.performance.mark("mark3");
+ self.performance.measure("measure3");
+ return;
+ }
+
+ if (callbackCount === 3) {
+ checkEntries(entryList.getEntries(), [
+ {entryType: "measure", name: "measure3"},
+ {entryType: "mark", name: "mark-before-change-observe-state-to-measure"},
+ ]);
+ self.performance.mark("mark-before-change-observe-state-to-both");
+ self.performance.measure("measure-before-change-observe-state-to-both");
+ observer.observe({entryTypes: ["mark", "measure"]});
+ self.performance.mark("mark4");
+ self.performance.measure("measure4");
+ return;
+ }
+
+ if (callbackCount === 4) {
+ checkEntries(entryList.getEntries(), [
+ {entryType: "measure", name: "measure-before-change-observe-state-to-both"},
+ {entryType: "measure", name: "measure4"},
+ {entryType: "mark", name: "mark4"},
+ ]);
+ self.performance.mark("mark-before-disconnect");
+ self.performance.measure("measure-before-disconnect");
+ observer.disconnect();
+ self.performance.mark("mark-after-disconnect");
+ self.performance.measure("measure-after-disconnect");
+ t.done();
+ return;
+ }
+
+ assert_unreached("The callback must not be invoked after disconnecting");
+ })
+ );
+
+ observer.observe({entryTypes: ["measure"]});
+ self.performance.mark("mark1");
+ self.performance.measure("measure1");
+ }, "PerformanceObserver modifications inside callback should update filtering and not clear buffer");
diff --git a/testing/web-platform/tests/performance-timeline/po-disconnect-removes-observed-types.any.js b/testing/web-platform/tests/performance-timeline/po-disconnect-removes-observed-types.any.js
new file mode 100644
index 0000000000..cac97bea07
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-disconnect-removes-observed-types.any.js
@@ -0,0 +1,19 @@
+// META: script=performanceobservers.js
+
+async_test(function (t) {
+ const observer = new PerformanceObserver(
+ t.step_func(function (entryList) {
+ // There should be no mark entry.
+ checkEntries(entryList.getEntries(),
+ [{ entryType: "measure", name: "b"}]);
+ t.done();
+ })
+ );
+ observer.observe({type: "mark"});
+ // Disconnect the observer.
+ observer.disconnect();
+ // Now, only observe measure.
+ observer.observe({type: "measure"});
+ performance.mark("a");
+ performance.measure("b");
+}, "Types observed are forgotten when disconnect() is called.");
diff --git a/testing/web-platform/tests/performance-timeline/po-disconnect.any.js b/testing/web-platform/tests/performance-timeline/po-disconnect.any.js
new file mode 100644
index 0000000000..5f5fb5aa43
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-disconnect.any.js
@@ -0,0 +1,37 @@
+// META: script=performanceobservers.js
+
+ async_test(function (t) {
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ assert_unreached("This callback must not be invoked");
+ })
+ );
+ observer.observe({entryTypes: ["mark", "measure", "navigation"]});
+ observer.disconnect();
+ self.performance.mark("mark1");
+ self.performance.measure("measure1");
+ t.step_timeout(function () {
+ t.done();
+ }, 2000);
+ }, "disconnected callbacks must not be invoked");
+
+ test(function () {
+ var obs = new PerformanceObserver(function () { return true; });
+ obs.disconnect();
+ obs.disconnect();
+ }, "disconnecting an unconnected observer is a no-op");
+
+ async_test(function (t) {
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ assert_unreached("This callback must not be invoked");
+ })
+ );
+ observer.observe({entryTypes: ["mark"]});
+ self.performance.mark("mark1");
+ observer.disconnect();
+ self.performance.mark("mark2");
+ t.step_timeout(function () {
+ t.done();
+ }, 2000);
+ }, "An observer disconnected after a mark must not have its callback invoked");
diff --git a/testing/web-platform/tests/performance-timeline/po-entries-sort.any.js b/testing/web-platform/tests/performance-timeline/po-entries-sort.any.js
new file mode 100644
index 0000000000..b0c781a3c0
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-entries-sort.any.js
@@ -0,0 +1,64 @@
+// META: script=performanceobservers.js
+
+ async_test(function (t) {
+ var stored_entries = [];
+ var stored_entries_by_type = [];
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+
+ stored_entries = entryList.getEntries();
+ stored_entries_by_type = entryList.getEntriesByType("mark");
+ stored_entries_by_name = entryList.getEntriesByName("name-repeat");
+ var startTimeOfMark2 = entryList.getEntriesByName("mark2")[0].startTime;
+
+ checkSorted(stored_entries);
+ checkEntries(stored_entries, [
+ {entryType: "measure", name: "measure1"},
+ {entryType: "measure", name: "measure2"},
+ {entryType: "measure", name: "measure3"},
+ {entryType: "measure", name: "name-repeat"},
+ {entryType: "mark", name: "mark1"},
+ {entryType: "mark", name: "mark2"},
+ {entryType: "measure", name: "measure-matching-mark2-1"},
+ {entryType: "measure", name: "measure-matching-mark2-2"},
+ {entryType: "mark", name: "name-repeat"},
+ {entryType: "mark", name: "name-repeat"},
+ ]);
+
+ checkSorted(stored_entries_by_type);
+ checkEntries(stored_entries_by_type, [
+ {entryType: "mark", name: "mark1"},
+ {entryType: "mark", name: "mark2"},
+ {entryType: "mark", name: "name-repeat"},
+ {entryType: "mark", name: "name-repeat"},
+ ]);
+
+ checkSorted(stored_entries_by_name);
+ checkEntries(stored_entries_by_name, [
+ {entryType: "measure", name: "name-repeat"},
+ {entryType: "mark", name: "name-repeat"},
+ {entryType: "mark", name: "name-repeat"},
+ ]);
+
+ observer.disconnect();
+ t.done();
+ })
+ );
+
+ observer.observe({entryTypes: ["mark", "measure"]});
+
+ self.performance.mark("mark1");
+ self.performance.measure("measure1");
+ wait(); // Ensure mark1 !== mark2 startTime by making sure performance.now advances.
+ self.performance.mark("mark2");
+ self.performance.measure("measure2");
+ self.performance.measure("measure-matching-mark2-1", "mark2");
+ wait(); // Ensure mark2 !== mark3 startTime by making sure performance.now advances.
+ self.performance.mark("name-repeat");
+ self.performance.measure("measure3");
+ self.performance.measure("measure-matching-mark2-2", "mark2");
+ wait(); // Ensure name-repeat startTime will differ.
+ self.performance.mark("name-repeat");
+ wait(); // Ensure name-repeat startTime will differ.
+ self.performance.measure("name-repeat");
+ }, "getEntries, getEntriesByType, getEntriesByName sort order");
diff --git a/testing/web-platform/tests/performance-timeline/po-getentries.any.js b/testing/web-platform/tests/performance-timeline/po-getentries.any.js
new file mode 100644
index 0000000000..36169d5dbd
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-getentries.any.js
@@ -0,0 +1,38 @@
+// META: script=performanceobservers.js
+
+ async_test(function (t) {
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ checkEntries(entryList.getEntries(),
+ [{ entryType: "mark", name: "mark1"}], "getEntries");
+
+ checkEntries(entryList.getEntriesByType("mark"),
+ [{ entryType: "mark", name: "mark1"}], "getEntriesByType");
+ assert_equals(entryList.getEntriesByType("measure").length, 0,
+ "getEntriesByType with no expected entry");
+ assert_equals(entryList.getEntriesByType("234567").length, 0,
+ "getEntriesByType with no expected entry");
+
+ checkEntries(entryList.getEntriesByName("mark1"),
+ [{ entryType: "mark", name: "mark1"}], "getEntriesByName");
+ assert_equals(entryList.getEntriesByName("mark2").length, 0,
+ "getEntriesByName with no expected entry");
+ assert_equals(entryList.getEntriesByName("234567").length, 0,
+ "getEntriesByName with no expected entry");
+
+ checkEntries(entryList.getEntriesByName("mark1", "mark"),
+ [{ entryType: "mark", name: "mark1"}], "getEntriesByName with a type");
+ assert_equals(entryList.getEntriesByName("mark1", "measure").length, 0,
+ "getEntriesByName with a type with no expected entry");
+ assert_equals(entryList.getEntriesByName("mark2", "measure").length, 0,
+ "getEntriesByName with a type with no expected entry");
+ assert_equals(entryList.getEntriesByName("mark1", "234567").length, 0,
+ "getEntriesByName with a type with no expected entry");
+
+ observer.disconnect();
+ t.done();
+ })
+ );
+ observer.observe({entryTypes: ["mark"]});
+ self.performance.mark("mark1");
+ }, "getEntries, getEntriesByType and getEntriesByName work");
diff --git a/testing/web-platform/tests/performance-timeline/po-mark-measure.any.js b/testing/web-platform/tests/performance-timeline/po-mark-measure.any.js
new file mode 100644
index 0000000000..0b205e094c
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-mark-measure.any.js
@@ -0,0 +1,61 @@
+// META: script=performanceobservers.js
+
+ async_test(function (t) {
+ var stored_entries = [];
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ stored_entries =
+ stored_entries.concat(entryList.getEntries());
+ if (stored_entries.length >= 4) {
+ checkEntries(stored_entries,
+ [{ entryType: "mark", name: "mark1"},
+ { entryType: "mark", name: "mark2"},
+ { entryType: "measure", name: "measure1"},
+ { entryType: "measure", name: "measure2"}]);
+ observer.disconnect();
+ t.done();
+ }
+ })
+ );
+ observer.observe({entryTypes: ["mark", "measure"]});
+ }, "entries are observable");
+
+ async_test(function (t) {
+ var mark_entries = [];
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ mark_entries =
+ mark_entries.concat(entryList.getEntries());
+ if (mark_entries.length >= 2) {
+ checkEntries(mark_entries,
+ [{ entryType: "mark", name: "mark1"},
+ { entryType: "mark", name: "mark2"}]);
+ observer.disconnect();
+ t.done();
+ }
+ })
+ );
+ observer.observe({entryTypes: ["mark"]});
+ self.performance.mark("mark1");
+ self.performance.mark("mark2");
+ }, "mark entries are observable");
+
+ async_test(function (t) {
+ var measure_entries = [];
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ measure_entries =
+ measure_entries.concat(entryList.getEntries());
+ if (measure_entries.length >= 2) {
+ checkEntries(measure_entries,
+ [{ entryType: "measure", name: "measure1"},
+ { entryType: "measure", name: "measure2"}]);
+ observer.disconnect();
+ t.done();
+ }
+ })
+ );
+ observer.observe({entryTypes: ["measure"]});
+ self.performance.measure("measure1");
+ self.performance.measure("measure2");
+ }, "measure entries are observable");
diff --git a/testing/web-platform/tests/performance-timeline/po-observe-repeated-type.any.js b/testing/web-platform/tests/performance-timeline/po-observe-repeated-type.any.js
new file mode 100644
index 0000000000..2bba396a6b
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-observe-repeated-type.any.js
@@ -0,0 +1,17 @@
+// META: script=performanceobservers.js
+
+async_test(function (t) {
+ const observer = new PerformanceObserver(
+ t.step_func(function (entryList) {
+ checkEntries(entryList.getEntries(),
+ [{ entryType: "mark", name: "early"}]);
+ observer.disconnect();
+ t.done();
+ })
+ );
+ performance.mark("early");
+ // This call will not trigger anything.
+ observer.observe({type: "mark"});
+ // This call should override the previous call and detect the early mark.
+ observer.observe({type: "mark", buffered: true});
+}, "Two calls of observe() with the same 'type' cause override.");
diff --git a/testing/web-platform/tests/performance-timeline/po-observe-type.any.js b/testing/web-platform/tests/performance-timeline/po-observe-type.any.js
new file mode 100644
index 0000000000..b9854cc146
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-observe-type.any.js
@@ -0,0 +1,64 @@
+// META: script=performanceobservers.js
+
+test(function () {
+ const obs = new PerformanceObserver(() => {});
+ assert_throws_js(TypeError, function () {
+ obs.observe({});
+ });
+ assert_throws_js(TypeError, function () {
+ obs.observe({entryType: ['mark', 'measure']});
+ });
+}, "Calling observe() without 'type' or 'entryTypes' throws a TypeError");
+
+test(() => {
+ const obs = new PerformanceObserver(() =>{});
+ obs.observe({entryTypes: ["mark"]});
+ assert_throws_dom('InvalidModificationError', function () {
+ obs.observe({type: "measure"});
+ });
+}, "Calling observe() with entryTypes and then type should throw an InvalidModificationError");
+
+test(() => {
+ const obs = new PerformanceObserver(() =>{});
+ obs.observe({type: "mark"});
+ assert_throws_dom('InvalidModificationError', function () {
+ obs.observe({entryTypes: ["measure"]});
+ });
+}, "Calling observe() with type and then entryTypes should throw an InvalidModificationError");
+
+test(() => {
+ const obs = new PerformanceObserver(() =>{});
+ assert_throws_js(TypeError, function () {
+ obs.observe({type: "mark", entryTypes: ["measure"]});
+ });
+}, "Calling observe() with type and entryTypes should throw a TypeError");
+
+test(function () {
+ const obs = new PerformanceObserver(() =>{});
+ // Definitely not an entry type.
+ obs.observe({type: "this-cannot-match-an-entryType"});
+ // Close to an entry type, but not quite.
+ obs.observe({type: "marks"});
+}, "Passing in unknown values to type does throw an exception.");
+
+async_test(function (t) {
+ let observedMark = false;
+ let observedMeasure = false;
+ const observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ observedMark |= entryList.getEntries().filter(
+ entry => entry.entryType === 'mark').length;
+ observedMeasure |= entryList.getEntries().filter(
+ entry => entry.entryType === 'measure').length
+ // Only conclude the test once we receive both entries!
+ if (observedMark && observedMeasure) {
+ observer.disconnect();
+ t.done();
+ }
+ })
+ );
+ observer.observe({type: "mark"});
+ observer.observe({type: "measure"});
+ self.performance.mark("mark1");
+ self.performance.measure("measure1");
+}, "observe() with different type values stacks.");
diff --git a/testing/web-platform/tests/performance-timeline/po-observe.any.js b/testing/web-platform/tests/performance-timeline/po-observe.any.js
new file mode 100644
index 0000000000..5b593374ba
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-observe.any.js
@@ -0,0 +1,63 @@
+// META: script=performanceobservers.js
+
+ test(function () {
+ const obs = new PerformanceObserver(() => {});
+ assert_throws_js(TypeError, function () {
+ obs.observe({entryTypes: "mark"});
+ });
+ }, "entryTypes must be a sequence or throw a TypeError");
+
+ test(function () {
+ const obs = new PerformanceObserver(() => {});
+ obs.observe({entryTypes: []});
+ }, "Empty sequence entryTypes does not throw an exception.");
+
+ test(function () {
+ const obs = new PerformanceObserver(() => {});
+ obs.observe({entryTypes: ["this-cannot-match-an-entryType"]});
+ obs.observe({entryTypes: ["marks","navigate", "resources"]});
+ }, "Unknown entryTypes do not throw an exception.");
+
+ test(function () {
+ const obs = new PerformanceObserver(() => {});
+ obs.observe({entryTypes: ["mark","this-cannot-match-an-entryType"]});
+ obs.observe({entryTypes: ["this-cannot-match-an-entryType","mark"]});
+ obs.observe({entryTypes: ["mark"], others: true});
+ }, "Filter unsupported entryType entryType names within the entryTypes sequence");
+
+ async_test(function (t) {
+ var finish = t.step_func(function () { t.done(); });
+ var observer = new PerformanceObserver(
+ function (entryList, obs) {
+ var self = this;
+ t.step(function () {
+ assert_true(entryList instanceof PerformanceObserverEntryList, "first callback parameter must be a PerformanceObserverEntryList instance");
+ assert_true(obs instanceof PerformanceObserver, "second callback parameter must be a PerformanceObserver instance");
+ assert_equals(observer, self, "observer is the this value");
+ assert_equals(observer, obs, "observer is second parameter");
+ assert_equals(self, obs, "this and second parameter are the same");
+ observer.disconnect();
+ finish();
+ });
+ }
+ );
+ self.performance.clearMarks();
+ observer.observe({entryTypes: ["mark"]});
+ self.performance.mark("mark1");
+ }, "Check observer callback parameter and this values");
+
+ async_test(function (t) {
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ checkEntries(entryList.getEntries(),
+ [{ entryType: "measure", name: "measure1"}]);
+ observer.disconnect();
+ t.done();
+ })
+ );
+ self.performance.clearMarks();
+ observer.observe({entryTypes: ["mark"]});
+ observer.observe({entryTypes: ["measure"]});
+ self.performance.mark("mark1");
+ self.performance.measure("measure1");
+ }, "replace observer if already present");
diff --git a/testing/web-platform/tests/performance-timeline/po-observe.html b/testing/web-platform/tests/performance-timeline/po-observe.html
new file mode 100644
index 0000000000..a48f0f3764
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-observe.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>PerformanceObservers: PerformanceObserverInit.buffered</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="performanceobservers.js"></script>
+<h1>PerformanceObservers: PerformanceObserverInit.buffered</h1>
+<p>
+PerformanceObserverInit.buffered should retrieve previously buffered entries
+</p>
+<div id="log"></div>
+<script>
+ async_test(function (t) {
+ function initTest() {
+ new PerformanceObserver(function (entryList, observer) {
+ entryList.getEntries().forEach(function(entry) {
+ if (shouldExclude(entry)) {
+ return;
+ }
+
+ observedEntries.push(entry);
+ if (observedEntries.length === entryTypes.length) {
+ observer.disconnect();
+ runTest();
+ }
+ });
+ }).observe({entryTypes});
+
+ // creates a `resource` entry
+ var img = document.createElement("img");
+ img.src = "./resources/square.png";
+ document.body.appendChild(img);
+
+ performance.mark("markName"); // creates a `mark` entry
+ performance.measure("measureName"); // creates a `measure` entry
+ }
+ function shouldExclude(entry) {
+ // exclude all `resource` entries that aren't for "square.png"
+ return entry.entryType === "resource" &&
+ entry.name.indexOf("square.png") === -1;
+ }
+ function runTest() {
+ // this PerformanceObserver is a nop because we've already been notified about all of our `entryTypes`
+ var po_nop = new PerformanceObserver(function (entryList, observer) {
+ if (entryList.getEntries().find(function(entry) {
+ return !shouldExclude(entry);
+ })) {
+ assert_unreached("this PerformanceObserver callback should never be called");
+ }
+ });
+ po_nop.observe({
+ entryTypes,
+ });
+
+ // this PerformanceObserver should be notified about the previously
+ // buffered mark entry only
+ const bufferedEntries = [];
+ new PerformanceObserver(function (entryList, observer) {
+ entryList.getEntries().forEach(function(entry) {
+ if (shouldExclude(entry)) {
+ return;
+ }
+
+ bufferedEntries.push(entry);
+ if (bufferedEntries.length === 1) {
+ observer.disconnect();
+ po_nop.disconnect();
+ for (i = 0; i < bufferedEntries.length; i++) {
+ assert_equals(bufferedEntries[i].entryType, "mark")
+ }
+ t.done();
+ }
+ });
+ }).observe({
+ type: "mark",
+ buffered: true
+ });
+ }
+
+ const entryTypes = ["navigation", "resource", "mark", "measure"];
+ const observedEntries = [];
+ initTest();
+ }, "PerformanceObserverInit.buffered should retrieve previously buffered entries");
+
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/po-resource.html b/testing/web-platform/tests/performance-timeline/po-resource.html
new file mode 100644
index 0000000000..00c173eeae
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-resource.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>PerformanceObservers: resource</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="performanceobservers.js"></script>
+<h1>PerformanceObservers: resource</h1>
+<p>
+New resources will <a href="https://w3c.github.io/performance-timeline/#dfn-queue-a-performanceentry">queue a PerformanceEntry</a>.
+</p>
+<div id="log"></div>
+<script>
+ async_test(function (t) {
+ function path(pathname) {
+ var filename = pathname.substring(pathname.lastIndexOf('/')+1);
+ return pathname.substring(0, pathname.length - filename.length);
+ }
+ var gUniqueCounter = 0;
+ function generateUniqueValues() {
+ return Date.now() + "-" + (++gUniqueCounter);
+ }
+ var stored_entries = [];
+ var img_location = document.location.origin + path(document.location.pathname)
+ + "resources/square.png?random=";
+ var img1 = img_location + generateUniqueValues();
+ var img2 = img_location + generateUniqueValues();
+ var observer = new PerformanceObserver(
+ t.step_func(function (entryList, obs) {
+ stored_entries =
+ stored_entries.concat(entryList.getEntriesByType("resource"));
+ if (stored_entries.length >= 2) {
+ checkEntries(stored_entries,
+ [{ entryType: "resource", name: img1},
+ { entryType: "resource", name: img2}]);
+ observer.disconnect();
+ t.done();
+ }
+ })
+ );
+ observer.observe({entryTypes: ["resource"]});
+ var img = document.createElement("img");
+ img.src = img1;
+ document.body.appendChild(img);
+ img = document.createElement("img");
+ img.src = img2;
+ document.body.appendChild(img);
+ }, "resource entries are observable");
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/po-takeRecords.any.js b/testing/web-platform/tests/performance-timeline/po-takeRecords.any.js
new file mode 100644
index 0000000000..86ad397b0a
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/po-takeRecords.any.js
@@ -0,0 +1,34 @@
+// META: title=PerformanceObserver: takeRecords
+// META: script=performanceobservers.js
+
+async_test(function (t) {
+ const observer = new PerformanceObserver(function (entryList, observer) {
+ assert_unreached('This callback should not have been called.')
+ });
+ let entries = observer.takeRecords();
+ checkEntries(entries, [], 'No records before observe');
+ observer.observe({entryTypes: ['mark']});
+ assert_equals(typeof(observer.takeRecords), 'function');
+ entries = observer.takeRecords();
+ checkEntries(entries, [], 'No records just from observe');
+ performance.mark('a');
+ performance.mark('b');
+ entries = observer.takeRecords();
+ checkEntries(entries, [
+ {entryType: 'mark', name: 'a'},
+ {entryType: 'mark', name: 'b'}
+ ]);
+ performance.mark('c');
+ performance.mark('d');
+ performance.mark('e');
+ entries = observer.takeRecords();
+ checkEntries(entries, [
+ {entryType: 'mark', name: 'c'},
+ {entryType: 'mark', name: 'd'},
+ {entryType: 'mark', name: 'e'}
+ ]);
+ entries = observer.takeRecords();
+ checkEntries(entries, [], 'No entries right after takeRecords');
+ observer.disconnect();
+ t.done();
+ }, "Test PerformanceObserver's takeRecords()");
diff --git a/testing/web-platform/tests/performance-timeline/resources/child-frame.html b/testing/web-platform/tests/performance-timeline/resources/child-frame.html
new file mode 100644
index 0000000000..40c8f72688
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/child-frame.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+
+<head></head>
+<body></body>
+<script>
+ performance.mark('mark_child_frame');
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/resources/empty.html b/testing/web-platform/tests/performance-timeline/resources/empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/empty.html
diff --git a/testing/web-platform/tests/performance-timeline/resources/going-back.html b/testing/web-platform/tests/performance-timeline/resources/going-back.html
new file mode 100644
index 0000000000..f4a26669ba
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/going-back.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+onload = (event) => {
+ history.back();
+};
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/resources/include-frames-helper.js b/testing/web-platform/tests/performance-timeline/resources/include-frames-helper.js
new file mode 100644
index 0000000000..a56489baaf
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/include-frames-helper.js
@@ -0,0 +1,60 @@
+const verifyEntries = (entries, filterOptions) => {
+ for (const filterOption of filterOptions) {
+ let countBeforeFiltering = entries.length;
+
+ // Using negate of the condition so that the next filtering is applied on less entries.
+ entries = entries.filter(
+ e => !(e.entryType == filterOption['entryType'] && e.name.includes(filterOption['name'])));
+
+ assert_equals(
+ countBeforeFiltering - entries.length, filterOption['expectedCount'], filterOption['failureMsg']);
+ }
+}
+
+const createFilterOption = (name, entryType, expectedCount, msgPrefix, description = '') => {
+ if (description) {
+ description = ' ' + description;
+ }
+
+ let failureMsg =
+ `${msgPrefix} should have ${expectedCount} ${entryType} entries for name ${name}` + description;
+
+ return {
+ name: name,
+ entryType: entryType,
+ expectedCount: expectedCount,
+ failureMsg: failureMsg
+ };
+}
+
+const loadChildFrame = (src) => {
+ return new Promise(resolve => {
+
+ const childFrame = document.createElement('iframe');
+
+ childFrame.addEventListener("load", resolve);
+
+ childFrame.src = src;
+
+ document.body.appendChild(childFrame);
+ });
+}
+
+const loadChildFrameAndGrandchildFrame = (src) => {
+ return new Promise(resolve => {
+
+ const crossOriginChildFrame = document.createElement('iframe');
+
+ // Wait for the child frame to send a message. The child frame would send a message
+ // when it loads its child frame.
+ window.addEventListener('message', e => {
+ if (e.data == 'Load completed') {
+ resolve();
+ }
+ });
+
+ crossOriginChildFrame.src = src;
+
+ document.body.appendChild(crossOriginChildFrame)
+ });
+}
diff --git a/testing/web-platform/tests/performance-timeline/resources/include-frames-subframe.html b/testing/web-platform/tests/performance-timeline/resources/include-frames-subframe.html
new file mode 100644
index 0000000000..0d43f418a0
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/include-frames-subframe.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+
+<head>
+</head>
+<!--
+ This html is embedded as a sub-frame in include-frames-originA-B-A.html,
+ include-frames-originA-B-B.html and include-frames-originA-A-A.html. Once embedded,
+ this would take a url parameter named origin which is the origin of the child frame
+ this html is to load in step 3 listed below.
+ It does,
+ 1, waits for load.
+ 2, creates a single mark performance entry.
+ 3, creates and loads a child frame, and waits for it to load.
+ 4. verify entries obtained from this frame.
+-->
+
+<body>
+ <script>
+ (async () => {
+ // Wait for load.
+ await new Promise(resolve => window.addEventListener("load", resolve));
+
+ // Mark.
+ performance.mark("mark_subframe");
+
+ // Create and load an iframe and wait for load.
+ await new Promise(resolve => {
+ const childFrame = document.createElement('iframe');
+
+ childFrame.addEventListener('load', async () => {
+ window.parent.postMessage('Load completed', "*");
+ resolve();
+ });
+
+ childFrame.src = (new URL(document.location)).searchParams.get('origin')
+ + '/performance-timeline/resources/child-frame.html';
+
+ document.body.appendChild(childFrame);
+ }
+ );
+ })();
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/resources/json_resource.json b/testing/web-platform/tests/performance-timeline/resources/json_resource.json
new file mode 100644
index 0000000000..68b6ac1d56
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/json_resource.json
@@ -0,0 +1,4 @@
+{
+ "name": "nav_id_test",
+ "target": "resource_timing"
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/resources/make_long_task.js b/testing/web-platform/tests/performance-timeline/resources/make_long_task.js
new file mode 100644
index 0000000000..a52d6d8392
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/make_long_task.js
@@ -0,0 +1,4 @@
+(function () {
+ let now = window.performance.now();
+ while (window.performance.now() < now + 60);
+}()); \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/resources/navigation-id-detached-frame-page.html b/testing/web-platform/tests/performance-timeline/resources/navigation-id-detached-frame-page.html
new file mode 100644
index 0000000000..02aafbb5c7
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/navigation-id-detached-frame-page.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>The navigation_id Detached iframe Page.</title>
+</head>
+
+<body>
+ <script>
+ window.addEventListener("load", () => {
+ setTimeout(() => {
+ const container = window.frameElement;
+ container.remove();
+ }, 10);
+ performance.mark('mark-window-detached-frame');
+ });
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/performance-timeline/resources/postmessage-entry.html b/testing/web-platform/tests/performance-timeline/resources/postmessage-entry.html
new file mode 100644
index 0000000000..ef5be73395
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/postmessage-entry.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<script>
+addEventListener("load", () => {
+ const entry = performance.getEntriesByType("navigation")[0];
+ try {
+ window.top.postMessage(entry, "*");
+ } catch(error) {
+ if (error.name == "DataCloneError") {
+ window.top.postMessage("PASS", "*");
+ } else {
+ window.top.postMessage("FAIL - Wrong exception name: " + error.name, "*");
+ }
+ }
+ window.top.postMessage("FAIL - No exception thrown", "*");
+});
+
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/resources/square.png b/testing/web-platform/tests/performance-timeline/resources/square.png
new file mode 100644
index 0000000000..be211bc377
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/square.png
Binary files differ
diff --git a/testing/web-platform/tests/performance-timeline/resources/worker-invalid-entries.js b/testing/web-platform/tests/performance-timeline/resources/worker-invalid-entries.js
new file mode 100644
index 0000000000..bd7fba2ccf
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/worker-invalid-entries.js
@@ -0,0 +1,6 @@
+performance.mark('workerMark');
+postMessage({
+ 'invalid' : performance.getEntriesByType('invalid').length,
+ 'mark' : performance.getEntriesByType('mark').length,
+ 'measure' : performance.getEntriesByType('measure').length
+});
diff --git a/testing/web-platform/tests/performance-timeline/resources/worker-navigation-id.js b/testing/web-platform/tests/performance-timeline/resources/worker-navigation-id.js
new file mode 100644
index 0000000000..3a2740d067
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/worker-navigation-id.js
@@ -0,0 +1,6 @@
+self.onmessage = () => {
+ const mark_name = 'user_timig_mark';
+ performance.mark(mark_name);
+ postMessage(performance.getEntriesByName(mark_name)[0].navigationId);
+ self.close();
+}
diff --git a/testing/web-platform/tests/performance-timeline/resources/worker-with-performance-observer.js b/testing/web-platform/tests/performance-timeline/resources/worker-with-performance-observer.js
new file mode 100644
index 0000000000..a72fe81c47
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/resources/worker-with-performance-observer.js
@@ -0,0 +1,6 @@
+try {
+ new PerformanceObserver(() => true);
+ postMessage("SUCCESS");
+} catch (ex) {
+ postMessage("FAILURE");
+}
diff --git a/testing/web-platform/tests/performance-timeline/supportedEntryTypes-cross-realm-access.html b/testing/web-platform/tests/performance-timeline/supportedEntryTypes-cross-realm-access.html
new file mode 100644
index 0000000000..8b86a6398b
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/supportedEntryTypes-cross-realm-access.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cross-realm access of supportedEntryTypes returns Array of another realm</title>
+<link rel="help" href="https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+test(t => {
+ const iframe = document.createElement("iframe");
+ t.add_cleanup(() => { iframe.remove(); });
+ iframe.onload = t.step_func_done(() => {
+ const otherWindow = iframe.contentWindow;
+ assert_true(otherWindow.PerformanceObserver.supportedEntryTypes instanceof otherWindow.Array);
+ });
+ document.body.append(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/supportedEntryTypes.any.js b/testing/web-platform/tests/performance-timeline/supportedEntryTypes.any.js
new file mode 100644
index 0000000000..25f195939e
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/supportedEntryTypes.any.js
@@ -0,0 +1,19 @@
+test(() => {
+ if (typeof PerformanceObserver.supportedEntryTypes === "undefined")
+ assert_unreached("supportedEntryTypes is not supported.");
+ const types = PerformanceObserver.supportedEntryTypes;
+ assert_greater_than(types.length, 0,
+ "There should be at least one entry in supportedEntryTypes.");
+ for (let i = 1; i < types.length; i++) {
+ assert_true(types[i-1] < types[i],
+ "The strings '" + types[i-1] + "' and '" + types[i] +
+ "' are repeated or they are not in alphabetical order.")
+ }
+}, "supportedEntryTypes exists and returns entries in alphabetical order");
+
+test(() => {
+ if (typeof PerformanceObserver.supportedEntryTypes === "undefined")
+ assert_unreached("supportedEntryTypes is not supported.");
+ assert_true(PerformanceObserver.supportedEntryTypes ===
+ PerformanceObserver.supportedEntryTypes);
+}, "supportedEntryTypes caches result");
diff --git a/testing/web-platform/tests/performance-timeline/tentative/detached-frame.html b/testing/web-platform/tests/performance-timeline/tentative/detached-frame.html
new file mode 100644
index 0000000000..70019223a6
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/detached-frame.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(async () => {
+ performance.clearResourceTimings()
+
+ // Create child iframe.
+ const childFrame = document.createElement('iframe')
+ childFrame.src = "../resources/child-frame.html"
+ document.body.appendChild(childFrame)
+
+ // wait until the child frame's onload event fired
+ await new Promise(r => childFrame.addEventListener("load", r));
+
+ const childWindow = childFrame.contentWindow;
+ // Detach the child frame
+ document.body.removeChild(childFrame);
+
+ const entries = childWindow.performance.getEntries({ includeChildFrames: true });
+ const parent_entries = performance.getEntries({ includeChildFrames: true });
+}, "GetEntries of a detached parent frame does not crash");
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-A-A.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-A-A.html
new file mode 100644
index 0000000000..57623e5b33
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-A-A.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src=/common/get-host-info.sub.js></script>
+ <script src="../resources/include-frames-helper.js"></script>
+</head>
+
+<body>
+ <script>
+ const verifyMainFrameEntries = (entries, description = '') => {
+ let filterOptions = [
+ createFilterOption('include-frames-originA-A-A', 'navigation', 1, 'Main Frame', description),
+ createFilterOption('include-frames-subframe', 'resource', 1, 'Main Frame', description),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyChildFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('include-frames-subframe', 'navigation', 1, 'Child Frame'),
+ createFilterOption('child-frame.html', 'resource', 1, 'Child Frame'),
+ createFilterOption('mark_subframe', 'mark', 1, 'Child frame')
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyGrandchildFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('child-frame.html', 'navigation', 1, 'Grandchild Frame'),
+ createFilterOption('mark_child_frame', 'mark', 1, 'Grandchild frame')
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ promise_test(async () => {
+ performance.clearResourceTimings();
+
+ // Load a child frame. The child frame upon loading would load a child frame of its own.
+ await loadChildFrameAndGrandchildFrame(
+ '../resources/include-frames-subframe.html?origin=' + get_host_info().ORIGIN);
+
+ // Verify entries retrieved from main frame.
+ const entries = performance.getEntries({ includeChildFrames: true });
+
+ verifyMainFrameEntries(entries);
+
+ verifyChildFrameEntries(entries);
+
+ verifyGrandchildFrameEntries(entries);
+
+ // 1 entry for parent, 1 for child, 1 for grandchild.
+ const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
+ assert_equals(navigationEntries.length, 3, 'Navigation entries should be 3.');
+
+ const markedChildFrameEntries = performance.getEntries(
+ { name: 'mark_subframe', includeChildFrames: true });
+ assert_equals(markedChildFrameEntries.length, 1, 'Child frame mark entries should be 1.');
+
+ const markedGrandchildFrameEntries = performance.getEntries(
+ { name: 'mark_child_frame', includeChildFrames: true });
+ assert_equals(markedGrandchildFrameEntries.length, 1, 'Grand child frame mark entries should be 1.');
+
+ // Test cases where includeChildFrames is false.
+ const entriesWithNoFitlerOptions = performance.getEntries();
+
+ const entriesWithoutIncludingChildFrames = performance.getEntries({ includeChildFrames: false });
+
+ const navigationEntriesWithoutIncludingChildFrames = performance.getEntries({ entryType: "navigation", includeChildFrames: false });
+
+ const markedEntriesWithoutIncludingChildFrames = performance.getEntries(
+ { name: 'entry-name', includeChildFrames: false });
+
+ verifyMainFrameEntries(entriesWithNoFitlerOptions, 'with no filter options.');
+
+ verifyMainFrameEntries(entriesWithoutIncludingChildFrames, 'with includingChildFrames being false.')
+
+ // 1 entry for main frame.
+ assert_equals(navigationEntriesWithoutIncludingChildFrames.length, 1,
+ 'Navigation entries with includeChildFrame being false should be 1.');
+
+ // 0 entry since grandchild frame is not included.
+ assert_equals(markedEntriesWithoutIncludingChildFrames.length, 0,
+ 'Mark entries with includeChildFrame being false should be 0.');
+
+ }, 'GetEntries of a document of origin A, its child frame of origin B and \
+ its grandchild frame of origin A.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-A.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-A.html
new file mode 100644
index 0000000000..bcb9a81657
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-A.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/include-frames-helper.js"></script>
+</head>
+
+<body>
+ <script>
+ const verifyMainFrameEntries = (entries, description = '') => {
+ let filterOptions = [
+ createFilterOption('include-frames-originA-A', 'navigation', 1, 'Main Frame', description),
+ createFilterOption('child-frame.html', 'resource', 1, 'Main Frame', description),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyChildFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('child-frame.html', 'navigation', 1, 'Child Frame'),
+ createFilterOption('mark_child_frame', 'mark', 1, 'Child Frame'),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ promise_test(async () => {
+ performance.clearResourceTimings();
+
+ // Load a child frame.
+ await loadChildFrame('../resources/child-frame.html');
+
+ const entries = performance.getEntries({ includeChildFrames: true });
+
+ const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
+
+ const markedEntries = performance.getEntries(
+ { name: 'mark_child_frame', includeChildFrames: true });
+
+ verifyMainFrameEntries(entries);
+
+ verifyChildFrameEntries(entries);
+
+ // 1 entry for main frame, 1 for child frame.
+ assert_equals(navigationEntries.length, 2, 'Navigation entries should be 2.');
+
+ // 1 entry for child frame.
+ assert_equals(markedEntries.length, 1, 'Mark entries should be 1.');
+
+ // Test cases where includeChildFrames is false.
+ const entriesWithNoFitlerOptions = performance.getEntries();
+
+ const entriesWithoutIncludingChildFrames = performance.getEntries({ includeChildFrames: false });
+
+ const navigationEntriesWithoutIncludingChildFrames = performance.getEntries({ entryType: "navigation", includeChildFrames: false });
+
+ const markedEntriesWithoutIncludingChildFrames = performance.getEntries(
+ { name: 'mark_child_frame', includeChildFrames: false });
+
+ verifyMainFrameEntries(entriesWithNoFitlerOptions, 'with no filter options.');
+
+ verifyMainFrameEntries(entriesWithoutIncludingChildFrames, 'with includeChildFrame being false.');
+
+ // 1 entry for main frame.
+ assert_equals(navigationEntriesWithoutIncludingChildFrames.length, 1,
+ 'Navigation entries with includeChildFrame being false should be 1.');
+
+ // 0 entry for child frame.
+ assert_equals(markedEntriesWithoutIncludingChildFrames.length, 0,
+ 'Mark entries with includeChildFrame being false should be 0.');
+
+ }, 'GetEntries of a document of origin A and its child frame of origin A.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-AA.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-AA.html
new file mode 100644
index 0000000000..cccbf4100d
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-AA.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/include-frames-helper.js"></script>
+</head>
+
+<body>
+ <script>
+ const verifyMainFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('include-frames-originA-AA', 'navigation', 1, 'Main Frame'),
+ createFilterOption('child-frame.html', 'navigation', 2, 'Child Frames'),
+ createFilterOption('child-frame.html', 'resource', 2, 'Main Frame'),
+ createFilterOption('mark_child_frame', 'mark', 2, 'Child frames')
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyPerformanceEntries = () => {
+ const entries = performance.getEntries({ includeChildFrames: true });
+
+ const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
+
+ const markedEntries = performance.getEntries(
+ { name: 'mark_child_frame', includeChildFrames: true });
+
+ verifyMainFrameEntries(entries);
+
+ // 1 entry for main frame, 1 for each child frame.
+ assert_equals(navigationEntries.length, 3, 'Navigation entries should be 3.');
+
+ // 1 entry for each child frame.
+ assert_equals(markedEntries.length, 2, 'Mark entries should be 2.');
+ }
+
+ promise_test(async () => {
+ performance.clearResourceTimings();
+
+ // Load first child iframe.
+ const promise1 = loadChildFrame('../resources/child-frame.html');
+
+ // Load second child iframe.
+ const promise2 = loadChildFrame('../resources/child-frame.html');
+
+ return Promise.all([promise1, promise2]).then(verifyPerformanceEntries);
+ }, 'GetEntries of a document of origin A and its two child frames both of origin A.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-AB.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-AB.html
new file mode 100644
index 0000000000..d630665b65
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-AB.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src=/common/get-host-info.sub.js></script>
+ <script src="../resources/include-frames-helper.js"></script>
+</head>
+
+<body>
+ <script>
+ const verifyMainFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('include-frames-originA-AB', 'navigation', 1, 'Main Frame'),
+ createFilterOption('child-frame.html', 'navigation', 1, 'Child Frames'),
+ createFilterOption('child-frame.html', 'resource', 2, 'Main Frame'),
+ createFilterOption('mark_child_frame', 'mark', 1, 'Child frames')
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyPerformanceEntries = () => {
+ const entries = performance.getEntries({ includeChildFrames: true });
+
+ const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
+
+ const markedEntries = performance.getEntries(
+ { name: 'mark_child_frame', includeChildFrames: true });
+
+ verifyMainFrameEntries(entries);
+
+ // 1 entry for main frame, 1 for local child frame.
+ assert_equals(navigationEntries.length, 2, 'Navigation entries should be 2.');
+
+ // 1 entry for local child frame.
+ assert_equals(markedEntries.length, 1, 'Mark entries should be 1.');
+ }
+
+ promise_test(() => {
+ performance.clearResourceTimings();
+
+ // Load first child iframe.
+ sameOriginPromise = loadChildFrame('../resources/child-frame.html');
+
+ // Create second child iframe.
+ crossOriginPromise = loadChildFrame(
+ get_host_info().HTTP_REMOTE_ORIGIN + '/resources/child-frame.html');
+
+ return Promise.all([sameOriginPromise, crossOriginPromise]).then(verifyPerformanceEntries);
+ }, 'GetEntries of a document of origin A and its two child frames of origin A and B respectively.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B-A.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B-A.html
new file mode 100644
index 0000000000..58cad40fab
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B-A.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src=/common/get-host-info.sub.js></script>
+ <script src="../resources/include-frames-helper.js"></script>
+</head>
+
+<body>
+ <script>
+ const verifyMainFrameEntries = (entries, description = '') => {
+ let filterOptions = [
+ createFilterOption('include-frames-originA-B-A', 'navigation', 1, 'Main Frame', description),
+ createFilterOption('include-frames-subframe', 'resource', 1, 'Main Frame', description),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyChildFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('include-frames-subframe', 'navigation', 0, 'Child Frame'),
+ createFilterOption('child-frame.html', 'resource', 0, 'Child Frame'),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyGrandchildFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('child-frame.html', 'navigation', 1, 'Grandchild Frame'),
+ createFilterOption('mark_child_frame', 'mark', 1, 'Grandchild frame')
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ promise_test(async () => {
+
+ performance.clearResourceTimings();
+
+ // Load a child frame. The child frame upon loading would load a child frame of its own.
+ await loadChildFrameAndGrandchildFrame(get_host_info().REMOTE_ORIGIN +
+ '/performance-timeline/resources/include-frames-subframe.html?origin=' + get_host_info().ORIGIN);
+
+ const entries = performance.getEntries({ includeChildFrames: true });
+
+ const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
+
+ const markedEntries = performance.getEntries(
+ { name: 'mark_child_frame', includeChildFrames: true });
+
+ verifyMainFrameEntries(entries);
+
+ verifyChildFrameEntries(entries);
+
+ verifyGrandchildFrameEntries(entries);
+
+ // 1 entry for main frame, 1 for grandchild frame.
+ assert_equals(navigationEntries.length, 2, 'Navigation entries should be 2.');
+
+ // 1 entry for grandchild frame.
+ assert_equals(markedEntries.length, 1, 'Mark entries should be 1.');
+
+ // Test cases where includeChildFrames is false.
+ const entriesWithNoFitlerOptions = performance.getEntries();
+
+ const entriesWithoutIncludingChildFrames = performance.getEntries({ includeChildFrames: false });
+
+ const navigationEntriesWithoutIncludingChildFrames = performance.getEntries({ entryType: "navigation", includeChildFrames: false });
+
+ const markedEntriesWithoutIncludingChildFrames = performance.getEntries(
+ { name: 'mark_child_frame', includeChildFrames: false });
+
+ verifyMainFrameEntries(entriesWithNoFitlerOptions, 'with no filter options.');
+
+ verifyMainFrameEntries(entriesWithoutIncludingChildFrames, 'with includeChildFrame being false.');
+
+ // 1 entry for main frame.
+ assert_equals(navigationEntriesWithoutIncludingChildFrames.length, 1,
+ 'Navigation entries with includeChildFrame being false should be 1.');
+
+ // 0 entry since grandchild frame is not included.
+ assert_equals(markedEntriesWithoutIncludingChildFrames.length, 0,
+ 'Mark entries with includeChildFrame being false should be 0.');
+
+ }, 'GetEntries of a document of origin A, its child frame of origin B and \
+ its grandchild frame of origin A.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B-B.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B-B.html
new file mode 100644
index 0000000000..2368a8e881
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B-B.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src=/common/get-host-info.sub.js></script>
+ <script src="../resources/include-frames-helper.js"></script>
+</head>
+
+<body>
+ <script>
+ const verifyMainFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('include-frames-originA-B-B', 'navigation', 1, 'Main Frame'),
+ createFilterOption('include-frames-subframe', 'resource', 1, 'Main Frame'),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyChildFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('include-frames-subframe', 'navigation', 0, 'Child Frame'),
+ createFilterOption('child-frame.html', 'resource', 0, 'Child Frame'),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyGrandchildFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('child-frame.html', 'navigation', 0, 'Grandchild Frame'),
+ createFilterOption('mark_child_frame', 'mark', 0, 'Grandchild frame')
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ promise_test(async () => {
+ performance.clearResourceTimings();
+
+ // Load a origin child frame. The child frame upon loading would load a child frame of its own.
+ await loadChildFrameAndGrandchildFrame(get_host_info().REMOTE_ORIGIN
+ + '/performance-timeline/resources/include-frames-subframe.html?origin='
+ + get_host_info().REMOTE_ORIGIN);
+
+ const entries = performance.getEntries({ includeChildFrames: true });
+
+ verifyMainFrameEntries(entries);
+
+ verifyChildFrameEntries(entries);
+
+ verifyGrandchildFrameEntries(entries);
+ }, 'GetEntries of a document of origin A, its child frame of origin B and \
+ its grandchild frame of origin B.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B.html
new file mode 100644
index 0000000000..b823d6edaa
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-originA-B.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src=/common/get-host-info.sub.js></script>
+ <script src="../resources/include-frames-helper.js"></script>
+</head>
+
+<body>
+ <script>
+ const verifyMainFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('include-frames-originA-B', 'navigation', 1, 'Main Frame'),
+ createFilterOption('child-frame.html', 'resource', 1, 'Main Frame'),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ const verifyChildFrameEntries = (entries) => {
+ let filterOptions = [
+ createFilterOption('child-frame.html', 'navigation', 0, 'Child Frame'),
+ createFilterOption('mark_child_frame', 'mark', 0, 'Child Frame'),
+ ];
+
+ verifyEntries(entries, filterOptions);
+ }
+
+ promise_test(async () => {
+ performance.clearResourceTimings();
+
+ await loadChildFrame(
+ get_host_info().HTTP_REMOTE_ORIGIN + '/performance_timeline/resources/child-frame.html');
+
+ const entries = performance.getEntries({ includeChildFrames: true });
+
+ const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
+
+ const markedEntries = performance.getEntries(
+ { name: 'mark_subframe', includeChildFrames: true });
+
+ // 1 entry for main frame.
+ assert_equals(navigationEntries.length, 1, 'Navigation entries should 1.');
+
+ // 0 entry since child frame is cross origin.
+ assert_equals(markedEntries.length, 0, 'Mark entries should 0.');
+ }, 'GetEntries of a parent Frame of origin A and its child frame of origin B');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/performance-entry-source.html b/testing/web-platform/tests/performance-timeline/tentative/performance-entry-source.html
new file mode 100644
index 0000000000..d10d3c5ed5
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/performance-entry-source.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+</body>
+<script>
+ promise_test(() => {
+ return new Promise(resolve => {
+ const navigationEntries = performance.getEntries({ type: 'navigation' })
+ const parentEntry = navigationEntries[0]
+
+ // Parent NavigationTiming source is current window.
+ assert_equals(parentEntry.source, window)
+
+ // Create child iframe.
+ const childFrame = document.createElement('iframe')
+ childFrame.src = "../resources/child-frame.html"
+ document.body.appendChild(childFrame)
+
+ childFrame.addEventListener('load', () => {
+ const markedEntries = performance.getEntries(
+ { name: 'mark_child_frame', includeChildFrames: true });
+
+ const childEntry = markedEntries[0]
+
+ // Child PerformanceMark source is the child's Window.
+ assert_equals(childEntry.source, childFrame.contentWindow)
+
+ resolve()
+ })
+ })
+ }, "PerformanceEntry source is equal to its respective Window")
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/tentative/with-filter-options-originA.html b/testing/web-platform/tests/performance-timeline/tentative/with-filter-options-originA.html
new file mode 100644
index 0000000000..6c6643df75
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/tentative/with-filter-options-originA.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async () => {
+ performance.clearResourceTimings();
+
+ performance.mark('entry-name');
+
+ const navigationEntries = performance.getEntries({ entryType: 'navigation' });
+
+ const markedEntries = performance.getEntries(
+ { name: 'entry-name', entryType: 'mark' });
+
+ assert_equals(navigationEntries.length, 1, 'navigationEntries should be 1.');
+
+ assert_equals(markedEntries.length, 1, 'markedEntries should be 1.');
+
+ }, 'GetEntries with filter options.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/performance-timeline/timing-removed-iframe.html b/testing/web-platform/tests/performance-timeline/timing-removed-iframe.html
new file mode 100644
index 0000000000..43988b21fb
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/timing-removed-iframe.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+test(() => {
+ const iframe = document.createElement('iframe');
+ iframe.src = "resources/empty.html"
+ document.body.appendChild(iframe);
+ const iframePerformance = iframe.contentWindow.performance;
+ iframe.parentNode.removeChild(iframe);
+ const timing = iframePerformance.timing;
+}, "Test that a removed iframe which timing is accessed does not crash the renderer.");
+</script>
diff --git a/testing/web-platform/tests/performance-timeline/webtiming-resolution.any.js b/testing/web-platform/tests/performance-timeline/webtiming-resolution.any.js
new file mode 100644
index 0000000000..d869c7c52d
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/webtiming-resolution.any.js
@@ -0,0 +1,25 @@
+function testTimeResolution(highResTimeFunc, funcString) {
+ test(() => {
+ const t0 = highResTimeFunc();
+ let t1 = highResTimeFunc();
+ while (t0 == t1) {
+ t1 = highResTimeFunc();
+ }
+ const epsilon = 1e-5;
+ assert_greater_than_equal(t1 - t0, 0.005 - epsilon, 'The second ' + funcString + ' should be much greater than the first');
+ }, 'Verifies the resolution of ' + funcString + ' is at least 5 microseconds.');
+}
+
+function timeByPerformanceNow() {
+ return performance.now();
+}
+
+function timeByUserTiming() {
+ performance.mark('timer');
+ const time = performance.getEntriesByName('timer')[0].startTime;
+ performance.clearMarks('timer');
+ return time;
+}
+
+testTimeResolution(timeByPerformanceNow, 'performance.now()');
+testTimeResolution(timeByUserTiming, 'entry.startTime');
diff --git a/testing/web-platform/tests/performance-timeline/worker-with-performance-observer.html b/testing/web-platform/tests/performance-timeline/worker-with-performance-observer.html
new file mode 100644
index 0000000000..fc92bc9710
--- /dev/null
+++ b/testing/web-platform/tests/performance-timeline/worker-with-performance-observer.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+async_test(function(t) {
+ const worker = new Worker("resources/worker-with-performance-observer.js");
+ worker.onmessage = function(event) {
+ t.step(() => assert_equals(event.data, 'SUCCESS'));
+ t.done();
+ }
+}, 'Worker: Test Performance Observer inside a worker.');
+</script>
+</body>
+</html>