diff options
Diffstat (limited to 'testing/web-platform/tests/performance-timeline')
71 files changed, 2675 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..93ddcff062 --- /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_not_equals(navigationIdOfNavigationTiming, null, + '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/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..95ca3dd6b4 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-attributes.tentative.window.js @@ -0,0 +1,57 @@ +// 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, + /*blocked=*/ true, + /*url=*/ rc1_url, + /*src=*/ null, + /*id=*/ null, + /*name=*/ null, + /*reasons=*/['WebSocket'], + /*children=*/[{ + 'blocked': false, + 'url': null, + 'src': rc1_child_url, + // Id and name should be empty. + '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-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..36c1b92dca --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache-reasons-stay.tentative.window.js @@ -0,0 +1,49 @@ +// 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, + /*blocked=*/ true, + /*url=*/ rc1_url, + /*src=*/ null, + /*id=*/ null, + /*name=*/ null, + /*reasons=*/['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, + /*blocked=*/ true, + /*url=*/ rc1_url, + /*src=*/ null, + /*id=*/ null, + /*name=*/ null, + /*reasons=*/['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..80adec007a --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-cross-origin-bfcache.tentative.window.js @@ -0,0 +1,63 @@ +// 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, + /*blocked=*/ false, + /*url=*/ rc1_url, + /*src=*/ null, + /*id=*/ null, + /*name=*/ null, + /*reasons=*/[], + /*children=*/[{ + 'blocked': true, + 'url': null, + 'src': rc1_child_url, + 'id': 'test-id', + // Iframes that are generated by addIframe have an empty name. + 'name': '', + 'reasons': [], + 'children': [] + }]); +});
\ 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..3afce4b975 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-not-bfcached.tentative.window.js @@ -0,0 +1,37 @@ +// 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, + /*blocked=*/ true, + /*url=*/ rc1_url, + /*src=*/ null, + /*id=*/ null, + /*name=*/ null, + /*reasons=*/['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..acb70ebfae --- /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, saveUrl] = + await rcHelper.createContextWithUrl(/*extraConfig=*/ { + origin: 'HTTP_ORIGIN', + scripts: [], + headers: [], + }); + + const redirectUrl = + `${ORIGIN}/common/redirect.py?location=${encodeURIComponent(saveUrl)}`; + // 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-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..26fea56bbb --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js @@ -0,0 +1,64 @@ +// 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, + /*blocked=*/ false, + /*url=*/ rc1_url, + /*src=*/ null, + /*id=*/ null, + /*name=*/ null, + /*reasons=*/[], + /*children=*/[{ + 'blocked': true, + 'url': rc1_child_url, + 'src': rc1_child_url, + 'id': 'test-id', + 'name': '', + 'reasons': ['WebSocket'], + 'children': [{ + 'blocked': false, + '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..e1bfafd779 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-replace.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 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(); + + const navigation_entry = await rc1.executeScript(() => { + return performance.getEntriesByType('navigation')[0]; + }); + // Reasons are not reset for same-origin replace. + await assertNotRestoredReasonsEquals( + rc1, + /*blocked=*/ true, + /*url=*/ rc1_url, + /*src=*/ null, + /*id=*/ null, + /*name=*/ null, + /*reasons=*/['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..0737f719bf --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/test-helper.js @@ -0,0 +1,47 @@ +async function assertNotRestoredReasonsEquals( + remoteContextHelper, blocked, url, src, id, name, reasons, children) { + let result = await remoteContextHelper.executeScript(() => { + return performance.getEntriesByType('navigation')[0].notRestoredReasons; + }); + assertReasonsStructEquals( + result, blocked, url, src, id, name, reasons, children); +} + +function assertReasonsStructEquals( + result, blocked, url, src, id, name, reasons, children) { + assert_equals(result.blocked, blocked); + assert_equals(result.url, url); + assert_equals(result.src, src); + assert_equals(result.id, id); + assert_equals(result.name, name); + // Reasons should match. + assert_equals(result.reasons.length, reasons.length); + reasons.sort(); + result.reasons.sort(); + for (let i = 0; i < reasons.length; i++) { + assert_equals(result.reasons[i], reasons[i]); + } + // Children should match. + assert_equals(result.children.length, children.length); + children.sort(); + result.children.sort(); + for (let j = 0; j < children.length; j++) { + assertReasonsStructEquals( + result.children[0], children[0].blocked, 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/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 Binary files differnew file mode 100644 index 0000000000..be211bc377 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/resources/square.png 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> |