diff options
Diffstat (limited to 'testing/web-platform/tests/performance-timeline')
68 files changed, 2289 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..733642fd03 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/back-forward-cache-restoration.tentative.html @@ -0,0 +1,84 @@ +<!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 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, expectedNavigationId) => { + assert_equals(entry.name, BackForwardCacheRestorationName); + assert_equals(entry.entryType, BackForwardCacheRestorationType); + assert_equals(entry.navigationId, expectedNavigationId); + 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++) { + // 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]); + checkEntry(entry, i + 2); // The expected navigation id of the entry created at i-th navigating away and back is i+2 because navigation id starts from 1 and increments before an instance of BackForwardRestoration is created. + // Assert Performance Timeline API supports BackForwardCacheRestoration. + entry = await pageA.execute_script(getBackForwardCacheRestorationByType, [BackForwardCacheRestorationType]); + checkEntry(entry, i + 2); + + entry = await pageA.execute_script(getBackForwardCacheRestorationByGetAllAndFilter, [BackForwardCacheRestorationType]); + checkEntry(entry, i + 2); + } + }, '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..bc52f208b0 --- /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: 4, + 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..3228e12778 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/navigation-id-initial-load.tentative.html @@ -0,0 +1,43 @@ +<!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 is the default value 1. + const navigationIds = await NavigationIdsFromLCP(); + assert_true(navigationIds.every(e => e.navigationId == 1), 'Navigation Id\ + of LCP entries should be default value 1 at initial navigations'); + + // Assert navigation id exists in a NavigationTiming entry and is the + // default value 1. + const navigationId = performance.getEntriesByType('navigation')[0].navigationId; + assert_equals(navigationId, 1, 'Navigation Id of an navigation timing\ + entry should be default value 1 at initial navigations'); + + // Assert navigation id exists in PaintTiming entries and is the default + // value 1. + assert_true(performance.getEntriesByType('paint').every(e => + e.navigationId == 1), + 'Navigation Id of PaintTiming entries should be default value 1 at\ + initial navigations.'); + + }, 'Navigation Id should be 1 at initial navigations.'); + </script> +</body> 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..662e17508b --- /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: 4, + 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..42795f94a9 --- /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: 4, + 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..7386331d26 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/navigation-id-reset.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> +<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); + + // Assert navigation id is 1 when the document is loaded first time. + + let navigationId = await pageA.execute_script(getNavigationId); + assert_equals(navigationId, 1, 'Navigation Id should be 1 initially.'); + + // Navigate away to url B and back. + await navigateAndThenBack(pageA, pageB, urlB); + + // Assert navigation id increments to 2 when the document is load from bfcache. + navigationId = await pageA.execute_script(getNavigationId); + assert_equals(navigationId, 2, 'Navigation Id should be 2 after increment.'); + + // Reload page. + await pageA.execute_script(reload); + await pageA.execute_script(waitForPageShow); + + // Assert navigation id is reset to 1 after reload. + navigationId = await pageA.execute_script(getNavigationId); + assert_equals(navigationId, 1, 'Navigation Id should be 1 after reload.'); + }, 'Navigation Id should be reset to 1 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..1ec906ebbb --- /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: 4, + 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.helper.js b/testing/web-platform/tests/performance-timeline/navigation-id.helper.js new file mode 100644 index 0000000000..53099cadb2 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/navigation-id.helper.js @@ -0,0 +1,130 @@ +// 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 = (expectedNavigationId, markName, MeasureName) => { + const markName1 = 'test-mark'; + const markName2 = 'test-mark' + expectedNavigationId; + const measureName = 'test-measure' + expectedNavigationId; + + 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 (expectedNavigationId) => { + let navigationId = -1; + + let p = new Promise(resolve => { + new PerformanceObserver((list) => { + const entry = list.getEntries().find(e => e.name.includes('json_resource') && e.navigationId == expectedNavigationId); + if (entry) { + navigationId = entry.navigationId; + resolve(); + } + }).observe({ type: 'resource' }); + }); + + const resp = await fetch('/performance-timeline/resources/json_resource.json'); + await p; + return [navigationId]; +} + +let testElementTiming = async (expectedNavigationId) => { + let navigationId = -1; + let p = new Promise(resolve => { + new PerformanceObserver((list) => { + const entry = list.getEntries().find(e => e.entryType === 'element' && e.identifier === 'test-element-timing' + expectedNavigationId); + if (entry) { + navigationId = entry.navigationId; + resolve(); + } + }).observe({ type: 'element' }); + }); + + let el = document.createElement('p'); + el.setAttribute('elementtiming', 'test-element-timing' + expectedNavigationId); + 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 id is 1 when the document is loaded first time. + let navigationIds = await pageA.execute_script(testInitial); + assert_true( + navigationIds.every(t => t === 1), 'All Navigation Ids should be 1.'); + + for (i = 1; i <= params.navigationTimes; i++) { + // Navigate away to url B and back. + await navigateAndThenBack(pageA, pageB, urlB); + + // Assert navigation id increments when the document is load from bfcache. + navigationIds = await pageA.execute_script( + testFunctionMap[params.testName], [i + 1]); + assert_true( + navigationIds.every(t => t === (i + 1)), + params.testName + ' Navigation Id should all be ' + (i + 1) + '.'); + } + }, description); +} 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-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..a39663f3d8 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache-reasons-stay.tentative.window.js @@ -0,0 +1,48 @@ +// 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 + +'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 assertBFCache(rc1, /*shouldRestoreFromBFCache=*/ false); + await assertNotRestoredReasonsEquals( + rc1, + /*blocked=*/ true, + /*url=*/ rc1_url, + /*src=*/ '', + /*id=*/ '', + /*name=*/ '', + /*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 assertBFCache(rc1, /*shouldRestoreFromBFCache=*/ true); + await assertNotRestoredReasonsEquals( + rc1, + /*blocked=*/ true, + /*url=*/ rc1_url, + /*src=*/ '', + /*id=*/ '', + /*name=*/ '', + /*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..82c4c9bdd8 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-bfcache.tentative.window.js @@ -0,0 +1,27 @@ +// 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 + +'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 assertBFCache(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..222ed3b54a --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-cross-origin-bfcache.tentative.window.js @@ -0,0 +1,62 @@ +// 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 + +'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 and use BroadcastChannel. + 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 assertBFCache(rc1, /*shouldRestoreFromBFCache=*/ false); + await assertNotRestoredReasonsEquals( + rc1, + /*blocked=*/ false, + /*url=*/ rc1_url, + /*src=*/ '', + /*id=*/ '', + /*name=*/ '', + /*reasons=*/[], + /*children=*/[{ + 'blocked': true, + 'url': '', + 'src': '', + '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-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..7bbc59e94b --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-not-bfcached.tentative.window.js @@ -0,0 +1,36 @@ +// META: title=RemoteContextHelper navigation using BFCache +// META: script=./test-helper.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=/websockets/constants.sub.js + +'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 assertBFCache(rc1, /*shouldRestoreFromBFCache=*/ false); + await assertNotRestoredReasonsEquals( + rc1, + /*blocked=*/ true, + /*url=*/ rc1_url, + /*src=*/ '', + /*id=*/ '', + /*name=*/ '', + /*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..a9d9facde8 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-redirect-on-history.tentative.window.js @@ -0,0 +1,52 @@ +// 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 + +'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..36ec53364b --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js @@ -0,0 +1,62 @@ +// 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 + +'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 assertBFCache(rc1, /*shouldRestoreFromBFCache=*/ false); + await assertNotRestoredReasonsEquals( + rc1, + /*blocked=*/ false, + /*url=*/ rc1_url, + /*src=*/ '', + /*id=*/ '', + /*name=*/ '', + /*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..5ee3c30c65 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-replace.tentative.window.js @@ -0,0 +1,46 @@ +// 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 + +'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=*/ '', + /*id=*/ '', + /*name=*/ '', + /*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..03ae1df3d3 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/not-restored-reasons/test-helper.js @@ -0,0 +1,43 @@ +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) { + await remoteContextHelper.executeScript((domain) => { + var webSocketInNotRestoredReasonsTests = new WebSocket(domain + '/echo'); + }, [SCHEME_DOMAIN_PORT]); +} 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..846979358e --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/resources/child-frame.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<head></head> +<body></body> +<script> + performance.mark("entry-name") +</script>
\ No newline at end of file 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/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/parent-frame-with-cross-origin-child.sub.html b/testing/web-platform/tests/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html new file mode 100644 index 0000000000..4be0df872c --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<head></head> +<body></body> +<script> + // Create child frame that is cross-origin with its parent. + const childFrame = document.createElement('iframe') + childFrame.src = "http://{{hosts[][]}}:{{ports[http][0]}}/performance-timeline/resources/child-frame.html" + document.body.appendChild(childFrame) + + performance.mark("entry-name") + + childFrame.addEventListener('load', () => { + const entries = performance.getEntries(true) + window.parent.postMessage(entries.length, "*") + }) +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/performance-timeline/resources/parent-frame-with-same-origin-child.html b/testing/web-platform/tests/performance-timeline/resources/parent-frame-with-same-origin-child.html new file mode 100644 index 0000000000..c9248a4e8b --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/resources/parent-frame-with-same-origin-child.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<head></head> +<body></body> +<script> + // Create child frame that is same-origin with its parent. + const childFrame = document.createElement('iframe') + childFrame.src = "child-frame.html" + document.body.appendChild(childFrame) + + performance.mark("entry-name") + + childFrame.addEventListener('load', () => { + const entries = performance.getEntries(true) + window.parent.postMessage(entries.length, "*") + }) +</script>
\ 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-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..d5753dbcd7 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/detached-frame.html @@ -0,0 +1,28 @@ +<!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(true); + const parent_entries = performance.getEntries(true); +}, "GetEntries of a detached parent frame does not crash"); +</script> + diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-from-child-cross-origin-grandchild.sub.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-from-child-cross-origin-grandchild.sub.html new file mode 100644 index 0000000000..1886f040ec --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-from-child-cross-origin-grandchild.sub.html @@ -0,0 +1,32 @@ +<!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 => { + performance.clearResourceTimings() + + // Create child iframe with an embedded frame that is same-origin with its parent but cross-origin with the current frame. + const crossOriginChildFrame = document.createElement('iframe') + crossOriginChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/performance-timeline/resources/parent-frame-with-same-origin-child.html" + document.body.appendChild(crossOriginChildFrame) + + // Listen for postMessage() from child frame. + window.addEventListener("message", e => { + // 0 entries for parent, 4 for child, 2 for grandchild. + assert_equals(e.data, 6) + + const entries = performance.getEntries(true) + + // 3 entries for parent, 0 for child, 0 for grandchild. + assert_equals(entries.length, 3) + + resolve() + }) + }) +}, "GetEntries of a Cross-Origin child frame with one Cross-Origin grandchild frame") +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-from-child-same-origin-grandchild.sub.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-from-child-same-origin-grandchild.sub.html new file mode 100644 index 0000000000..16142ca953 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-from-child-same-origin-grandchild.sub.html @@ -0,0 +1,32 @@ +<!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 => { + performance.clearResourceTimings() + + // Create child iframe with an embedded frame that is cross-origin with its parent but same-origin with the current frame. + const crossOriginChildFrame = document.createElement('iframe') + crossOriginChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html" + document.body.appendChild(crossOriginChildFrame) + + // Listen for postMessage() from child frame. + window.addEventListener("message", e => { + // 0 entries for parent, 4 for child, 0 for grandchild. + assert_equals(e.data, 4) + + const entries = performance.getEntries(true) + + // 3 entries for parent, 0 for child, 2 for grandchild. + assert_equals(entries.length, 5) + + resolve() + }) + }) +}, "GetEntries of a Cross-Origin child frame with one Same-Origin grandchild frame") +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-cross-origin-child-one-same-origin-grandchild.sub.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-cross-origin-child-one-same-origin-grandchild.sub.html new file mode 100644 index 0000000000..8aa5df778f --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-cross-origin-child-one-same-origin-grandchild.sub.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 => { + performance.clearResourceTimings() + + // Create child iframe with an embedded frame that is cross-origin with its parent, but same-origin with the current frame. + const crossOriginChildFrame = document.createElement('iframe') + crossOriginChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html" + document.body.appendChild(crossOriginChildFrame) + + // Listen for postMessage() from grandchild frame. + window.addEventListener("message", () => { + const entries = performance.getEntries(true) + const navigationEntries = performance.getEntriesByType("navigation", true) + const markedEntries = performance.getEntriesByName("entry-name", undefined, true) + + // 3 entries for parent, 0 for child, 2 for grandchild. + assert_equals(entries.length, 5) + + // 1 entry for parent, 1 for grandchild. + assert_equals(navigationEntries.length, 2) + + // 1 entry for grandchild. + assert_equals(markedEntries.length, 1) + + resolve() + }) + }) +}, "GetEntries of a parent Frame with one Cross-Origin child and one Same-Origin grandchild") +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-remote-child.sub.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-remote-child.sub.html new file mode 100644 index 0000000000..61e7a1f648 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-remote-child.sub.html @@ -0,0 +1,36 @@ +<!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 => { + performance.clearResourceTimings() + + // Create Remote child iframe. + const childFrame = document.createElement('iframe') + childFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/resources/child-frame.html" + document.body.appendChild(childFrame) + + childFrame.addEventListener("load", () => { + const entries = performance.getEntries(true) + const navigationEntries = performance.getEntriesByType("navigation", true) + const markedEntries = performance.getEntriesByName("entry-name", undefined, true) + + // 3 entries for parent, 0 for child. + assert_equals(entries.length, 3) + + // 1 entry for parent. + assert_equals(navigationEntries.length, 1) + + // 0 entries. + assert_equals(markedEntries.length, 0) + + resolve() + }) + }) +}, "GetEntries of a parent Frame with one Cross-Origin child") +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child-one-cross-origin-child.sub.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child-one-cross-origin-child.sub.html new file mode 100644 index 0000000000..b20bde93ee --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child-one-cross-origin-child.sub.html @@ -0,0 +1,56 @@ +<!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 => { + performance.clearResourceTimings() + + let sameOriginFrameLoaded = false + let crossOriginFrameLoaded = false + + // Create first child iframe. + const sameOriginChildFrame = document.createElement('iframe') + sameOriginChildFrame.src = "../resources/child-frame.html" + document.body.appendChild(sameOriginChildFrame) + + sameOriginChildFrame.addEventListener("load", () => { + sameOriginFrameLoaded = true + if (crossOriginFrameLoaded) + verifyPerformanceEntries() + }) + + // Create second child iframe. + const crossOriginChildFrame = document.createElement('iframe') + crossOriginChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/resources/child-frame.html" + document.body.appendChild(crossOriginChildFrame) + + crossOriginChildFrame.addEventListener("load", () => { + crossOriginFrameLoaded = true + if (sameOriginFrameLoaded) + verifyPerformanceEntries() + }) + + function verifyPerformanceEntries() { + const entries = performance.getEntries(true) + const navigationEntries = performance.getEntriesByType("navigation", true) + const markedEntries = performance.getEntriesByName("entry-name", undefined, true) + + // 4 entries for parent, 2 for local child, 0 for remote child. + assert_equals(entries.length, 6) + + // 1 entry for parent, 1 for local child. + assert_equals(navigationEntries.length, 2) + + // 1 entry for local child. + assert_equals(markedEntries.length, 1) + + resolve() + } + }) +}, "GetEntries of a parent Frame with one Cross-Origin child and one Same-Origin child") +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child-one-same-origin-grandchild.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child-one-same-origin-grandchild.html new file mode 100644 index 0000000000..10ccea3da8 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child-one-same-origin-grandchild.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 => { + performance.clearResourceTimings() + + // Create child iframe with an embedded frame that is cross-origin with its parent, but same-origin with the current frame. + const childFrame = document.createElement('iframe') + childFrame.src = "../resources/parent-frame-with-cross-origin-child.sub.html" + document.body.appendChild(childFrame) + + // Listen for postMessage() from grandchild frame. + window.addEventListener("message", () => { + const entries = performance.getEntries(true) + const navigationEntries = performance.getEntriesByType("navigation", true) + const markedEntries = performance.getEntriesByName("entry-name", undefined, true) + + // 4 entries for parent, 3 for child, 2 for grandchild. + assert_equals(entries.length, 9) + + // 1 entry for parent, 1 for child, 1 for grandchild. + assert_equals(navigationEntries.length, 3) + + // 1 entry for child, 1 for grandchild. + assert_equals(markedEntries.length, 2) + + resolve() + }) + }) +}, "GetEntries of a parent Frame with one Same-Origin child and one Same-Origin grandchild") +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child.html new file mode 100644 index 0000000000..73dc417fe6 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-one-same-origin-child.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 => { + performance.clearResourceTimings() + + // Create child iframe. + const childFrame = document.createElement('iframe') + childFrame.src = "../resources/child-frame.html" + document.body.appendChild(childFrame) + + // On-load event handler to assert size of entries. + childFrame.addEventListener("load", () => { + const entries = performance.getEntries(true) + const navigationEntries = performance.getEntriesByType("navigation", true) + const markedEntries = performance.getEntriesByName("entry-name", undefined, true) + + // 3 entries for parent, 2 for child. + assert_equals(entries.length, 5) + + // 1 entry for parent, 1 for child. + assert_equals(navigationEntries.length, 2) + + // 1 entry for child. + assert_equals(markedEntries.length, 1) + + resolve() + }) + }) +}, "GetEntries of a parent Frame with one Same-Origin child"); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/performance-timeline/tentative/include-frames-two-local-children.html b/testing/web-platform/tests/performance-timeline/tentative/include-frames-two-local-children.html new file mode 100644 index 0000000000..01d4844522 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/include-frames-two-local-children.html @@ -0,0 +1,56 @@ +<!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 => { + performance.clearResourceTimings() + + let childFrameOneLoaded = false + let childFrameTwoLoaded = false + + // Create first child iframe. + const childFrameOne = document.createElement('iframe') + childFrameOne.src = "../resources/child-frame.html" + document.body.appendChild(childFrameOne) + + childFrameOne.addEventListener("load", () => { + childFrameOneLoaded = true + if (childFrameTwoLoaded) + verifyPerformanceEntries() + }) + + // Create second child iframe. + const childFrameTwo = document.createElement('iframe') + childFrameTwo.src = "../resources/child-frame.html" + document.body.appendChild(childFrameTwo) + + childFrameTwo.addEventListener("load", () => { + childFrameTwoLoaded = true + if (childFrameOneLoaded) + verifyPerformanceEntries() + }) + + function verifyPerformanceEntries() { + const entries = performance.getEntries(true) + const navigationEntries = performance.getEntriesByType("navigation", true) + const markedEntries = performance.getEntriesByName("entry-name", undefined, true) + + // 4 entries for parent, 2 for each child + assert_equals(entries.length, 8) + + // 1 entry for parent, 1 for each child. + assert_equals(navigationEntries.length, 3) + + // 1 entry for each child. + assert_equals(markedEntries.length, 2) + + resolve() + } + }) +}, "GetEntries of a parent Frame with two Same-Origin children") +</script>
\ No newline at end of file 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..5e51ddbbe4 --- /dev/null +++ b/testing/web-platform/tests/performance-timeline/tentative/performance-entry-source.html @@ -0,0 +1,33 @@ +<!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.getEntriesByType("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.getEntriesByName("entry-name", undefined, 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>
\ No newline at end of file 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> |