Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,4 @@
spec: https://w3c.github.io/performance-timeline/
suggested_reviewers:
- plehegar
- igrigorik

View File

@ -0,0 +1,95 @@
<!doctype html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
</head>
<body>
<script>
const BackForwardCacheRestorationName = '';
const BackForwardCacheRestorationType = 'back-forward-cache-restoration';
let getNavigationId = (i) => {
let identifier = 'mark' + i;
performance.mark(identifier);
return window.performance.getEntriesByName(identifier)[0].navigationId;
}
let getNumberofBackForwardCacheRestorationEntries = (BackForwardCacheRestorationType) => {
return window.performance.getEntriesByType(BackForwardCacheRestorationType).length;
}
let getBackForwardCacheRestorationByType = (BackForwardCacheRestorationType) => {
let entries = window.performance.getEntriesByType(BackForwardCacheRestorationType);
return entries[entries.length - 1];
}
let getBackForwardCacheRestorationByGetAllAndFilter = (BackForwardCacheRestorationType) => {
let entries = window.performance.getEntries().filter(e => e.entryType == BackForwardCacheRestorationType);
return entries[entries.length - 1];
}
let getBackForwardCacheRestorationByPerformanceObserverBuffered = async (BackForwardCacheRestorationType) => {
let p = new Promise(resolve => {
new PerformanceObserver((list) => {
const entries = list.getEntries().filter(e => e.entryType == BackForwardCacheRestorationType);
if (entries.length > 0) {
resolve(entries[entries.length - 1]);
}
}).observe({ type: BackForwardCacheRestorationType, buffered: true });
});
return await p;
}
let checkEntry = (entry, previousNavigationId) => {
assert_equals(entry.name, BackForwardCacheRestorationName);
assert_equals(entry.entryType, BackForwardCacheRestorationType);
assert_not_equals(entry.navigationId, previousNavigationId);
assert_true(entry.pageshowEventStart > entry.startTime);
assert_true(entry.pageshowEventEnd >= entry.pageshowEventStart);
}
promise_test(async t => {
const pageA = new RemoteContext(token());
const pageB = new RemoteContext(token());
const urlA = executorPath + pageA.context_id;
const urlB = originCrossSite + executorPath + pageB.context_id;
// Open url A.
window.open(urlA, '_blank', 'noopener');
await pageA.execute_script(waitForPageShow);
// Assert no instance of BackForwardCacheRestoration exists without back forward cache navigatoin.
let size = await pageA.execute_script(getNumberofBackForwardCacheRestorationEntries);
assert_equals(0, size);
let entry;
for (i = 0; i < 2; i++) {
let curr_nav_id = await pageA.execute_script(getNavigationId, [i]);
// Navigate away to url B and back.
await navigateAndThenBack(pageA, pageB, urlB);
// Assert Performance Observer API supports BackForwardCacheRestoration.
entry = await pageA.execute_script(getBackForwardCacheRestorationByPerformanceObserverBuffered, [BackForwardCacheRestorationType]);
// The navigation id after a bfcache restoration should be different
// from that before.
checkEntry(entry, curr_nav_id);
// Assert Performance Timeline API supports BackForwardCacheRestoration.
entry = await pageA.execute_script(getBackForwardCacheRestorationByType, [BackForwardCacheRestorationType]);
checkEntry(entry, curr_nav_id);
entry = await pageA.execute_script(getBackForwardCacheRestorationByGetAllAndFilter, [BackForwardCacheRestorationType]);
checkEntry(entry, curr_nav_id);
}
}, 'Performance API for the back forward cache restoration entry.');
</script>
</body>
</html>

View File

@ -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');

View File

@ -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.');

View File

@ -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.");

View File

@ -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!');

View File

@ -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>

View File

@ -0,0 +1,2 @@
// META: script=/resources/idlharness-shadowrealm.js
idl_test_shadowrealm(["performance-timeline"], ["hr-time", "dom"]);

View File

@ -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');
});
}
);

View File

@ -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');

View File

@ -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>

View File

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
<script src="navigation-id.helper.js"></script>
<script>
runNavigationIdTest({
navigationTimes: 3,
testName: 'element_timing',
}, "Element Timing navigation id test");
</script>

View File

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!--
Navigation timing, LCP and paint timing entries are only emitted during initial
load, not after a bfcache navigation. Therefore we only verify the existence of
navigation id, not the increment.
-->
<body>
<p>This text is to trigger a LCP entry emission.</p>
<script>
async function NavigationIdsFromLCP() {
return new Promise(resolve => {
new PerformanceObserver((entryList) => {
resolve(entryList.getEntries());
}).observe({ type: 'largest-contentful-paint', buffered: true });
})
}
promise_test(async t => {
// Assert navigation id exists in LCP entries and and are all the same.
const navigationIdsOfLCP = (await NavigationIdsFromLCP()).map(e => e.navigationId);
assert_true(navigationIdsOfLCP.every(e => e == navigationIdsOfLCP[0]),
'Navigation Ids of LCP entries should be the same at initial navigation');
// Assert navigation id exists in a NavigationTiming entry.
const navigationIdOfNavigationTiming =
performance.getEntriesByType('navigation')[0].navigationId;
assert_true(!!navigationIdOfNavigationTiming,
'Navigation Id of a navigation timing entry should exist at initial navigation');
// Assert navigation id exists in PaintTiming entries and are all the same.
const navigationIdsOfPaintTiming =
performance.getEntriesByType('paint').map(e => e.navigationId);
assert_true(navigationIdsOfPaintTiming.every(e =>
e == navigationIdsOfPaintTiming[0]),
'Navigation Id of PaintTiming entries should be the same as the initial navigation.');
// Assert navigation ids are all the same.
const navigationIdsOfAll =
navigationIdsOfLCP.concat(navigationIdsOfPaintTiming, navigationIdOfNavigationTiming);
assert_true(navigationIdsOfAll.every(e => e == navigationIdsOfAll[0]),
'Navigation Id of all entries should be the same as the initial navigation.');
}, 'Navigation Ids should exist and are all the same as the initial navigation.');
</script>
</body>

View File

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
<script src="navigation-id.helper.js"></script>
<script>
runNavigationIdTest({
navigationTimes: 3,
testName: 'long_task_task_attribution',
}, "Long Task/Task Attribution navigation id test");
</script>

View File

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
<script src="navigation-id.helper.js"></script>
<script>
runNavigationIdTest({
navigationTimes: 3,
testName: 'mark_measure',
}, "Mark/Measure navigation id test");
</script>

View File

@ -0,0 +1,53 @@
<!DOCTYPE HTML>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
<script>
const reload = () => {
window.location.reload();
};
const getNavigationId = () => {
window.performance.mark('initial_load');
let entries = window.performance.getEntriesByType('mark');
return entries[entries.length - 1].navigationId;
}
promise_test(async t => {
const pageA = new RemoteContext(token());
const pageB = new RemoteContext(token());
const urlA = executorPath + pageA.context_id;
const urlB = originCrossSite + executorPath + pageB.context_id;
// Open url A.
window.open(urlA, '_blank', 'noopener')
await pageA.execute_script(waitForPageShow);
let navigationIdInitial = await pageA.execute_script(getNavigationId);
// Navigate away to url B and back.
await navigateAndThenBack(pageA, pageB, urlB);
// Assert navigation id is re-generated and thus different when the
// document is load from bfcache.
navigationIdAfterBFCacheNav = await pageA.execute_script(getNavigationId);
assert_not_equals(navigationIdInitial, navigationIdAfterBFCacheNav, 'Navigation Id should be \
re-generated and different from the previous one after back-forward-cache navigation.');
// Reload page.
await pageA.execute_script(reload);
await pageA.execute_script(waitForPageShow);
navigationIdAfterReset = await pageA.execute_script(getNavigationId);
assert_not_equals(navigationIdAfterReset, navigationIdAfterBFCacheNav, 'Navigation Id should\
be re-generated after reload which is different from the previous one.');
assert_not_equals(navigationIdAfterReset, navigationIdInitial, 'Navigation Id should\
be re-generated after reload which is different from the one of the initial load.');
}, 'Navigation Id should be re-generated after reload.');
</script>

View File

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
<script src="navigation-id.helper.js"></script>
<script>
runNavigationIdTest({
navigationTimes: 3,
testName: 'resource_timing',
}, "Resource Timing navigation id test");
</script>

View File

@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
promise_test(async () => {
const worker = new Worker("resources/worker-navigation-id.js");
const navigationId = await new Promise(resolve => {
worker.onmessage = (e) => {
resolve(e.data);
};
worker.postMessage('');
});
assert_equals(navigationId.length, 0,
'Navigation id of performance entries created by a worker should be empty.');
}, 'Navigation id of performance entries created by workers should be empty');
</script>
</body>
</html>

View File

@ -0,0 +1,144 @@
// The test functions called in the navigation-counter test. They rely on
// artifacts defined in
// '/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js'
// which should be included before this file to use these functions.
// This function is to obtain navigation ids of all performance entries to
// verify.
let testInitial = () => {
return window.performance.getEntries().map(e => e.navigationId);
}
let testMarkMeasure = (markId, markName, MeasureName) => {
const markName1 = 'test-mark';
const markName2 = 'test-mark' + markId;
const measureName = 'test-measure' + markId;
window.performance.mark(markName1);
window.performance.mark(markName2);
window.performance.measure(measureName, markName1, markName2);
return window.performance.getEntriesByName(markName2).concat(
window.performance.getEntriesByName(measureName)).map(e => e.navigationId);
}
let testResourceTiming = async (resourceTimingEntryId) => {
let navigationId;
let p = new Promise(resolve => {
new PerformanceObserver((list) => {
const entry = list.getEntries().find(
e => e.name.includes('json_resource' + resourceTimingEntryId));
if (entry) {
navigationId = entry.navigationId;
resolve();
}
}).observe({ type: 'resource' });
});
const resp = await fetch(
'/performance-timeline/resources/json_resource' + resourceTimingEntryId + '.json');
await p;
return [navigationId];
}
let testElementTiming = async (elementTimingEntryId) => {
let navigationId;
let p = new Promise(resolve => {
new PerformanceObserver((list) => {
const entry = list.getEntries().find(
e => e.entryType === 'element' && e.identifier === 'test-element-timing' + elementTimingEntryId);
if (entry) {
navigationId = entry.navigationId;
resolve();
}
}).observe({ type: 'element' });
});
let el = document.createElement('p');
el.setAttribute('elementtiming', 'test-element-timing' + elementTimingEntryId);
el.textContent = 'test element timing text';
document.body.appendChild(el);
await p;
return [navigationId];
}
let testLongTask = async () => {
let navigationIds = [];
let p = new Promise(resolve => {
new PerformanceObserver((list) => {
const entry = list.getEntries().find(e => e.entryType === 'longtask')
if (entry) {
navigationIds.push(entry.navigationId);
navigationIds = navigationIds.concat(
entry.attribution.map(a => a.navigationId));
resolve();
}
}).observe({ type: 'longtask' });
});
const script = document.createElement('script');
script.src = '/performance-timeline/resources/make_long_task.js';
document.body.appendChild(script);
await p;
document.body.removeChild(script);
return navigationIds;
}
const testFunctionMap = {
'mark_measure': testMarkMeasure,
'resource_timing': testResourceTiming,
'element_timing': testElementTiming,
'long_task_task_attribution': testLongTask,
};
function runNavigationIdTest(params, description) {
const defaultParams = {
openFunc: url => window.open(url, '_blank', 'noopener'),
scripts: [],
funcBeforeNavigation: () => { },
targetOrigin: originCrossSite,
navigationTimes: 4,
funcAfterAssertion: () => { },
} // Apply defaults.
params = { ...defaultParams, ...params };
promise_test(async t => {
const pageA = new RemoteContext(token());
const pageB = new RemoteContext(token());
const urlA = executorPath + pageA.context_id;
const urlB = params.targetOrigin + executorPath + pageB.context_id;
// Open url A.
params.openFunc(urlA);
await pageA.execute_script(waitForPageShow);
// Assert navigation ids of all performance entries are the same.
let navigationIds = await pageA.execute_script(testInitial);
assert_true(
navigationIds.every(t => t === navigationIds[0]),
'Navigation Ids should be the same as the initial load.');
for (i = 1; i <= params.navigationTimes; i++) {
// Navigate away to url B and back.
await navigateAndThenBack(pageA, pageB, urlB);
// Assert new navigation ids are generated when the document is load from bfcache.
let nextNavigationIds = await pageA.execute_script(
testFunctionMap[params.testName], [i + 1]);
// Assert navigation ids of all performance entries are the same.
assert_true(
nextNavigationIds.every(t => t === nextNavigationIds[0]),
'All Navigation Ids should be same after bfcache navigation.');
// Assert navigation ids after bfcache navigation are different from those before.
assert_true(
navigationIds[0] !== nextNavigationIds[0],
params.testName +
' Navigation Ids should be re-generated and different from the previous ones.');
navigationIds = nextNavigationIds;
}
}, description);
}

View File

@ -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>

View File

@ -0,0 +1,21 @@
// META: title=Aborting a parser should block bfcache
// META: script=./test-helper.js
// META: timeout=long
async_test(t => {
if (!sessionStorage.getItem("pageVisited")) {
// This is the first time loading the page.
sessionStorage.setItem("pageVisited", 1);
t.step_timeout(() => {
// Go to another page and instantly come back to this page.
location.href = new URL("../resources/going-back.html", window.location);
}, 0);
// Abort parsing in the middle of loading the page.
window.stop();
} else {
const nrr = performance.getEntriesByType('navigation')[0].notRestoredReasons;
assert_true(ReasonsInclude(nrr.reasons, "parser-aborted"));
t.done();
}
}, "aborting a parser should block bfcache.");

View File

@ -0,0 +1,55 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
// Ensure that empty attributes are reported as empty strings and missing
// attributes are reported as null.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Add a cross-origin iframe.
const rc1_child = await rc1.addIframe(
/*extraConfig=*/ {
origin: 'HTTP_REMOTE_ORIGIN',
scripts: [],
headers: [],
},
/*attributes=*/ {id: '', name: ''},
);
// Use WebSocket to block BFCache.
await useWebSocket(rc1);
const rc1_child_url = await rc1_child.executeScript(() => {
return location.href;
});
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': 'websocket'}],
/*children=*/[{
'url': null,
'src': rc1_child_url,
// Id and name should be empty.
'id': '',
'name': '',
'reasons': null,
'children': null
}]);
});

View File

@ -0,0 +1,47 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
// Ensure that notRestoredReasons are only updated after non BFCache navigation.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
// Use WebSocket to block BFCache.
await useWebSocket(rc1);
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': 'websocket'}],
/*children=*/ []);
// This time no blocking feature is used, so the page is restored
// from BFCache. Ensure that the previous reasons stay there.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ true);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': 'websocket'}],
/*children=*/ []);
});

View File

@ -0,0 +1,28 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: timeout=long
'use strict';
// Ensure that notRestoredReasons is empty for successful BFCache restore.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
// Check the BFCache result and verify that no reasons are recorded
// for successful restore.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ true);
assert_true(await rc1.executeScript(() => {
let reasons =
performance.getEntriesByType('navigation')[0].notRestoredReasons;
return reasons === null;
}));
});

View File

@ -0,0 +1,60 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
// Ensure that cross-origin subtree's reasons are not exposed to
// notRestoredReasons.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Add a cross-origin iframe.
const rc1_child = await rc1.addIframe(
/*extraConfig=*/ {
origin: 'HTTP_REMOTE_ORIGIN',
scripts: [],
headers: [],
},
/*attributes=*/ {id: 'test-id'},
);
// Use WebSocket to block BFCache.
await useWebSocket(rc1_child);
const rc1_child_url = await rc1_child.executeScript(() => {
return location.href;
});
// Add a child to the iframe.
const rc1_grand_child = await rc1_child.addIframe();
const rc1_grand_child_url = await rc1_grand_child.executeScript(() => {
return location.href;
});
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': "masked"}],
/*children=*/[{
'url': null,
'src': rc1_child_url,
'id': 'test-id',
'name': null,
'reasons': null,
'children': null
}]);
});

View File

@ -0,0 +1,40 @@
// META: title=Ensure that ongoing fetch upon entering bfcache blocks bfcache and recorded.
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: timeout=long
'use strict';
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
const wavURL = new URL(get_host_info().HTTP_REMOTE_ORIGIN + '/fetch/range/resources/long-wav.py');
await rc1.executeScript((wavURL) => {
// Register pagehide handler to create a fetch request.
addEventListener('pagehide', (wavURL) => {
fetch(wavURL, {
keepalive: true
});
})
});
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': 'fetch'}],
/*children=*/[]);
});

View File

@ -0,0 +1,103 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
// Ensure that empty attributes are reported as empty strings and missing
// attributes are reported as null.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Add a cross-origin iframe.
const rc1_child = await rc1.addIframe(
/*extraConfig=*/ {
origin: 'HTTP_REMOTE_ORIGIN',
scripts: [],
headers: [],
},
/*attributes=*/ {id: '', name: ''},
);
const rc2_child = await rc1.addIframe(
/*extraConfig=*/ {
origin: 'HTTP_REMOTE_ORIGIN',
scripts: [],
headers: [],
},
/*attributes=*/ {},
);
const rc3_child = await rc1.addIframe(
/*extraConfig=*/ {},
/*attributes=*/ {},
);
const rc4_child = await rc1.addIframe(
/*extraConfig=*/ {},
/*attributes=*/ {id: '', name: ''},
);
// Use WebSocket to block BFCache.
await useWebSocket(rc1);
const rc1_child_url = await rc1_child.executeScript(() => {
return location.href;
});
const rc2_child_url = await rc2_child.executeScript(() => {
return location.href;
});
const rc3_child_url = await rc3_child.executeScript(() => {
return location.href;
});
const rc4_child_url = await rc4_child.executeScript(() => {
return location.href;
});
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': 'websocket'}],
/*children=*/[{
'url': null,
'src': rc1_child_url,
// Id and name should be empty.
'id': '',
'name': '',
'reasons': null,
'children': null
}, {
'url': null,
'src': rc2_child_url,
// Id and name should be null.
'id': null,
'name': null,
'reasons': null,
'children': null
},{
'url': rc3_child_url,
'src': rc3_child_url,
// Id and name should be null.
'id': null,
'name': null,
'reasons': [],
'children': []
}, {
'url': rc4_child_url,
'src': rc4_child_url,
'id': '',
'name': '',
'reasons': [],
'children': []
}]);
});

View File

@ -0,0 +1,32 @@
// META: title=Ensure that if WebLock is held upon entering bfcache, it cannot enter bfcache and gets reported.
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: timeout=long
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Request a WebLock.
let return_value = await rc1.executeScript(() => {
return new Promise((resolve) => {
navigator.locks.request('resource', () => {
resolve(42);
});
})
});
assert_equals(return_value, 42);
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredFromBFCache(rc1, ['lock']);
});

View File

@ -0,0 +1,26 @@
// META: title=Ensure that navigation failure blocks bfcache and gets recorded.
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/404.py
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: timeout=long
'use strict';
const {ORIGIN} = get_host_info();
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ {status: 404}, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredFromBFCache(rc1, ['response-status-not-ok']);
});

View File

@ -0,0 +1,36 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
// Ensure that notRestoredReasons is populated when not restored.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
// Use WebSocket to block BFCache.
await useWebSocket(rc1);
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': 'websocket'}],
/*children=*/ []);
});

View File

@ -0,0 +1,53 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
// Ensure that notRestoredReasons reset after the server redirect.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
// Use WebSocket to block BFCache.
await useWebSocket(rc1);
// Create a remote context with the redirected URL.
let rc1_redirected =
await rcHelper.createContext(/*extraConfig=*/ {
origin: 'HTTP_ORIGIN',
scripts: [],
headers: [],
});
const redirectUrl =
`${ORIGIN}/common/redirect.py?location=${encodeURIComponent(rc1_redirected.url)}`;
// Replace the history state.
await rc1.executeScript((url) => {
window.history.replaceState(null, '', url);
}, [redirectUrl]);
// Navigate away.
const newRemoteContextHelper = await rc1.navigateToNew();
// Go back.
await newRemoteContextHelper.historyBack();
const navigation_entry = await rc1_redirected.executeScript(() => {
return performance.getEntriesByType('navigation')[0];
});
assert_equals(
navigation_entry.redirectCount, 1, 'Expected redirectCount is 1.');
// Becauase of the redirect, notRestoredReasons is reset.
assert_equals(
navigation_entry.notRestoredReasons, null,
'Expected notRestoredReasons is null.');
});

View File

@ -0,0 +1,50 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
// Ensure that notRestoredReasons reset after the server redirect.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Use WebSocket to block BFCache.
await useWebSocket(rc1);
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': 'websocket'}],
/*children=*/ []);
// Reload.
await rc1.navigate(() => {
location.reload();
}, []);
// Becauase of the reload, notRestoredReasons is reset.
const navigation_entry = await rc1.executeScript(() => {
return performance.getEntriesByType('navigation')[0];
});
assert_equals(
navigation_entry.notRestoredReasons, null,
'Expected notRestoredReasons is null.');
});

View File

@ -0,0 +1,61 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
// Ensure that same-origin subtree's reasons are exposed to notRestoredReasons.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Add a same-origin iframe and use WebSocket.
const rc1_child = await rc1.addIframe(
/*extra_config=*/ {}, /*attributes=*/ {id: 'test-id'});
await useWebSocket(rc1_child);
const rc1_child_url = await rc1_child.executeScript(() => {
return location.href;
});
// Add a child to the iframe.
const rc1_grand_child = await rc1_child.addIframe();
const rc1_grand_child_url = await rc1_grand_child.executeScript(() => {
return location.href;
});
// Check the BFCache result and the reported reasons.
await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false);
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[],
/*children=*/[{
'url': rc1_child_url,
'src': rc1_child_url,
'id': 'test-id',
'name': '',
'reasons': [{'reason': 'websocket'}],
'children': [{
'url': rc1_grand_child_url,
'src': rc1_grand_child_url,
'id': '',
'name': '',
'reasons': [],
'children': []
}]
}]);
});

View File

@ -0,0 +1,43 @@
// META: title=RemoteContextHelper navigation using BFCache
// META: script=./test-helper.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
// META: script=/websockets/constants.sub.js
// META: timeout=long
'use strict';
// Ensure that notRestoredReasons are accessible after history replace.
promise_test(async t => {
const rcHelper = new RemoteContextHelper();
// Open a window with noopener so that BFCache will work.
const rc1 = await rcHelper.addWindow(
/*config=*/ null, /*options=*/ {features: 'noopener'});
const rc1_url = await rc1.executeScript(() => {
return location.href;
});
// Use WebSocket to block BFCache.
await useWebSocket(rc1);
// Navigate away.
const newRemoteContextHelper = await rc1.navigateToNew();
// Replace the history state to a same-origin site.
await newRemoteContextHelper.executeScript((destUrl) => {
window.history.replaceState(null, '', '#');
});
// Go back.
await newRemoteContextHelper.historyBack();
// Reasons are not reset for same-origin replace.
await assertNotRestoredReasonsEquals(
rc1,
/*url=*/ rc1_url,
/*src=*/ null,
/*id=*/ null,
/*name=*/ null,
/*reasons=*/[{'reason': 'websocket'}],
/*children=*/ []);
});

View File

@ -0,0 +1,57 @@
// META: script=../../html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
async function assertNotRestoredReasonsEquals(
remoteContextHelper, url, src, id, name, reasons, children) {
let result = await remoteContextHelper.executeScript(() => {
return performance.getEntriesByType('navigation')[0].notRestoredReasons;
});
assertReasonsStructEquals(
result, url, src, id, name, reasons, children);
}
function assertReasonsStructEquals(
result, url, src, id, name, reasons, children) {
assert_equals(result.url, url);
assert_equals(result.src, src);
assert_equals(result.id, id);
assert_equals(result.name, name);
// Reasons should match.
let expected = new Set(reasons);
let actual = new Set(result.reasons);
matchReasons(extractReason(expected), extractReason(actual));
// Children should match.
if (children == null) {
assert_equals(result.children, children);
} else {
for (let j = 0; j < children.length; j++) {
assertReasonsStructEquals(
result.children[j], children[j].url,
children[j].src, children[j].id, children[j].name, children[j].reasons,
children[j].children);
}
}
}
function ReasonsInclude(reasons, targetReason) {
for (const reason of reasons) {
if (reason.reason == targetReason) {
return true;
}
}
return false;
}
// Requires:
// - /websockets/constants.sub.js in the test file and pass the domainPort
// constant here.
async function useWebSocket(remoteContextHelper) {
let return_value = await remoteContextHelper.executeScript((domain) => {
return new Promise((resolve) => {
var webSocketInNotRestoredReasonsTests = new WebSocket(domain + '/echo');
webSocketInNotRestoredReasonsTests.onopen = () => { resolve(42); };
});
}, [SCHEME_DOMAIN_PORT]);
assert_equals(return_value, 42);
}

View File

@ -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.');

View File

@ -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');

View File

@ -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;
}
}

View File

@ -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");

View File

@ -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.");

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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.");

View File

@ -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.");

View File

@ -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");

View File

@ -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>

View File

@ -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>

View File

@ -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()");

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<head></head>
<body></body>
<script>
performance.mark('mark_child_frame');
</script>

View File

@ -0,0 +1,9 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
onload = (event) => {
history.back();
};
</script>

View File

@ -0,0 +1,60 @@
const verifyEntries = (entries, filterOptions) => {
for (const filterOption of filterOptions) {
let countBeforeFiltering = entries.length;
// Using negate of the condition so that the next filtering is applied on less entries.
entries = entries.filter(
e => !(e.entryType == filterOption['entryType'] && e.name.includes(filterOption['name'])));
assert_equals(
countBeforeFiltering - entries.length, filterOption['expectedCount'], filterOption['failureMsg']);
}
}
const createFilterOption = (name, entryType, expectedCount, msgPrefix, description = '') => {
if (description) {
description = ' ' + description;
}
let failureMsg =
`${msgPrefix} should have ${expectedCount} ${entryType} entries for name ${name}` + description;
return {
name: name,
entryType: entryType,
expectedCount: expectedCount,
failureMsg: failureMsg
};
}
const loadChildFrame = (src) => {
return new Promise(resolve => {
const childFrame = document.createElement('iframe');
childFrame.addEventListener("load", resolve);
childFrame.src = src;
document.body.appendChild(childFrame);
});
}
const loadChildFrameAndGrandchildFrame = (src) => {
return new Promise(resolve => {
const crossOriginChildFrame = document.createElement('iframe');
// Wait for the child frame to send a message. The child frame would send a message
// when it loads its child frame.
window.addEventListener('message', e => {
if (e.data == 'Load completed') {
resolve();
}
});
crossOriginChildFrame.src = src;
document.body.appendChild(crossOriginChildFrame)
});
}

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<head>
</head>
<!--
This html is embedded as a sub-frame in include-frames-originA-B-A.html,
include-frames-originA-B-B.html and include-frames-originA-A-A.html. Once embedded,
this would take a url parameter named origin which is the origin of the child frame
this html is to load in step 3 listed below.
It does,
1, waits for load.
2, creates a single mark performance entry.
3, creates and loads a child frame, and waits for it to load.
4. verify entries obtained from this frame.
-->
<body>
<script>
(async () => {
// Wait for load.
await new Promise(resolve => window.addEventListener("load", resolve));
// Mark.
performance.mark("mark_subframe");
// Create and load an iframe and wait for load.
await new Promise(resolve => {
const childFrame = document.createElement('iframe');
childFrame.addEventListener('load', async () => {
window.parent.postMessage('Load completed', "*");
resolve();
});
childFrame.src = (new URL(document.location)).searchParams.get('origin')
+ '/performance-timeline/resources/child-frame.html';
document.body.appendChild(childFrame);
}
);
})();
</script>
</body>

View File

@ -0,0 +1,4 @@
{
"name": "nav_id_test",
"target": "resource_timing"
}

View File

@ -0,0 +1,4 @@
(function () {
let now = window.performance.now();
while (window.performance.now() < now + 60);
}());

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

View File

@ -0,0 +1,6 @@
performance.mark('workerMark');
postMessage({
'invalid' : performance.getEntriesByType('invalid').length,
'mark' : performance.getEntriesByType('mark').length,
'measure' : performance.getEntriesByType('measure').length
});

View File

@ -0,0 +1,6 @@
self.onmessage = () => {
const mark_name = 'user_timig_mark';
performance.mark(mark_name);
postMessage(performance.getEntriesByName(mark_name)[0].navigationId);
self.close();
}

View File

@ -0,0 +1,6 @@
try {
new PerformanceObserver(() => true);
postMessage("SUCCESS");
} catch (ex) {
postMessage("FAILURE");
}

View File

@ -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>

View File

@ -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");

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
</body>
<script>
promise_test(async () => {
performance.clearResourceTimings()
// Create child iframe.
const childFrame = document.createElement('iframe')
childFrame.src = "../resources/child-frame.html"
document.body.appendChild(childFrame)
// wait until the child frame's onload event fired
await new Promise(r => childFrame.addEventListener("load", r));
const childWindow = childFrame.contentWindow;
// Detach the child frame
document.body.removeChild(childFrame);
const entries = childWindow.performance.getEntries({ includeChildFrames: true });
const parent_entries = performance.getEntries({ includeChildFrames: true });
}, "GetEntries of a detached parent frame does not crash");
</script>

View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src=/common/get-host-info.sub.js></script>
<script src="../resources/include-frames-helper.js"></script>
</head>
<body>
<script>
const verifyMainFrameEntries = (entries, description = '') => {
let filterOptions = [
createFilterOption('include-frames-originA-A-A', 'navigation', 1, 'Main Frame', description),
createFilterOption('include-frames-subframe', 'resource', 1, 'Main Frame', description),
];
verifyEntries(entries, filterOptions);
}
const verifyChildFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('include-frames-subframe', 'navigation', 1, 'Child Frame'),
createFilterOption('child-frame.html', 'resource', 1, 'Child Frame'),
createFilterOption('mark_subframe', 'mark', 1, 'Child frame')
];
verifyEntries(entries, filterOptions);
}
const verifyGrandchildFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('child-frame.html', 'navigation', 1, 'Grandchild Frame'),
createFilterOption('mark_child_frame', 'mark', 1, 'Grandchild frame')
];
verifyEntries(entries, filterOptions);
}
promise_test(async () => {
performance.clearResourceTimings();
// Load a child frame. The child frame upon loading would load a child frame of its own.
await loadChildFrameAndGrandchildFrame(
'../resources/include-frames-subframe.html?origin=' + get_host_info().ORIGIN);
// Verify entries retrieved from main frame.
const entries = performance.getEntries({ includeChildFrames: true });
verifyMainFrameEntries(entries);
verifyChildFrameEntries(entries);
verifyGrandchildFrameEntries(entries);
// 1 entry for parent, 1 for child, 1 for grandchild.
const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
assert_equals(navigationEntries.length, 3, 'Navigation entries should be 3.');
const markedChildFrameEntries = performance.getEntries(
{ name: 'mark_subframe', includeChildFrames: true });
assert_equals(markedChildFrameEntries.length, 1, 'Child frame mark entries should be 1.');
const markedGrandchildFrameEntries = performance.getEntries(
{ name: 'mark_child_frame', includeChildFrames: true });
assert_equals(markedGrandchildFrameEntries.length, 1, 'Grand child frame mark entries should be 1.');
// Test cases where includeChildFrames is false.
const entriesWithNoFitlerOptions = performance.getEntries();
const entriesWithoutIncludingChildFrames = performance.getEntries({ includeChildFrames: false });
const navigationEntriesWithoutIncludingChildFrames = performance.getEntries({ entryType: "navigation", includeChildFrames: false });
const markedEntriesWithoutIncludingChildFrames = performance.getEntries(
{ name: 'entry-name', includeChildFrames: false });
verifyMainFrameEntries(entriesWithNoFitlerOptions, 'with no filter options.');
verifyMainFrameEntries(entriesWithoutIncludingChildFrames, 'with includingChildFrames being false.')
// 1 entry for main frame.
assert_equals(navigationEntriesWithoutIncludingChildFrames.length, 1,
'Navigation entries with includeChildFrame being false should be 1.');
// 0 entry since grandchild frame is not included.
assert_equals(markedEntriesWithoutIncludingChildFrames.length, 0,
'Mark entries with includeChildFrame being false should be 0.');
}, 'GetEntries of a document of origin A, its child frame of origin B and \
its grandchild frame of origin A.');
</script>
</body>

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/include-frames-helper.js"></script>
</head>
<body>
<script>
const verifyMainFrameEntries = (entries, description = '') => {
let filterOptions = [
createFilterOption('include-frames-originA-A', 'navigation', 1, 'Main Frame', description),
createFilterOption('child-frame.html', 'resource', 1, 'Main Frame', description),
];
verifyEntries(entries, filterOptions);
}
const verifyChildFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('child-frame.html', 'navigation', 1, 'Child Frame'),
createFilterOption('mark_child_frame', 'mark', 1, 'Child Frame'),
];
verifyEntries(entries, filterOptions);
}
promise_test(async () => {
performance.clearResourceTimings();
// Load a child frame.
await loadChildFrame('../resources/child-frame.html');
const entries = performance.getEntries({ includeChildFrames: true });
const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
const markedEntries = performance.getEntries(
{ name: 'mark_child_frame', includeChildFrames: true });
verifyMainFrameEntries(entries);
verifyChildFrameEntries(entries);
// 1 entry for main frame, 1 for child frame.
assert_equals(navigationEntries.length, 2, 'Navigation entries should be 2.');
// 1 entry for child frame.
assert_equals(markedEntries.length, 1, 'Mark entries should be 1.');
// Test cases where includeChildFrames is false.
const entriesWithNoFitlerOptions = performance.getEntries();
const entriesWithoutIncludingChildFrames = performance.getEntries({ includeChildFrames: false });
const navigationEntriesWithoutIncludingChildFrames = performance.getEntries({ entryType: "navigation", includeChildFrames: false });
const markedEntriesWithoutIncludingChildFrames = performance.getEntries(
{ name: 'mark_child_frame', includeChildFrames: false });
verifyMainFrameEntries(entriesWithNoFitlerOptions, 'with no filter options.');
verifyMainFrameEntries(entriesWithoutIncludingChildFrames, 'with includeChildFrame being false.');
// 1 entry for main frame.
assert_equals(navigationEntriesWithoutIncludingChildFrames.length, 1,
'Navigation entries with includeChildFrame being false should be 1.');
// 0 entry for child frame.
assert_equals(markedEntriesWithoutIncludingChildFrames.length, 0,
'Mark entries with includeChildFrame being false should be 0.');
}, 'GetEntries of a document of origin A and its child frame of origin A.');
</script>
</body>

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/include-frames-helper.js"></script>
</head>
<body>
<script>
const verifyMainFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('include-frames-originA-AA', 'navigation', 1, 'Main Frame'),
createFilterOption('child-frame.html', 'navigation', 2, 'Child Frames'),
createFilterOption('child-frame.html', 'resource', 2, 'Main Frame'),
createFilterOption('mark_child_frame', 'mark', 2, 'Child frames')
];
verifyEntries(entries, filterOptions);
}
const verifyPerformanceEntries = () => {
const entries = performance.getEntries({ includeChildFrames: true });
const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
const markedEntries = performance.getEntries(
{ name: 'mark_child_frame', includeChildFrames: true });
verifyMainFrameEntries(entries);
// 1 entry for main frame, 1 for each child frame.
assert_equals(navigationEntries.length, 3, 'Navigation entries should be 3.');
// 1 entry for each child frame.
assert_equals(markedEntries.length, 2, 'Mark entries should be 2.');
}
promise_test(async () => {
performance.clearResourceTimings();
// Load first child iframe.
const promise1 = loadChildFrame('../resources/child-frame.html');
// Load second child iframe.
const promise2 = loadChildFrame('../resources/child-frame.html');
return Promise.all([promise1, promise2]).then(verifyPerformanceEntries);
}, 'GetEntries of a document of origin A and its two child frames both of origin A.');
</script>
</body>

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src=/common/get-host-info.sub.js></script>
<script src="../resources/include-frames-helper.js"></script>
</head>
<body>
<script>
const verifyMainFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('include-frames-originA-AB', 'navigation', 1, 'Main Frame'),
createFilterOption('child-frame.html', 'navigation', 1, 'Child Frames'),
createFilterOption('child-frame.html', 'resource', 2, 'Main Frame'),
createFilterOption('mark_child_frame', 'mark', 1, 'Child frames')
];
verifyEntries(entries, filterOptions);
}
const verifyPerformanceEntries = () => {
const entries = performance.getEntries({ includeChildFrames: true });
const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
const markedEntries = performance.getEntries(
{ name: 'mark_child_frame', includeChildFrames: true });
verifyMainFrameEntries(entries);
// 1 entry for main frame, 1 for local child frame.
assert_equals(navigationEntries.length, 2, 'Navigation entries should be 2.');
// 1 entry for local child frame.
assert_equals(markedEntries.length, 1, 'Mark entries should be 1.');
}
promise_test(() => {
performance.clearResourceTimings();
// Load first child iframe.
sameOriginPromise = loadChildFrame('../resources/child-frame.html');
// Create second child iframe.
crossOriginPromise = loadChildFrame(
get_host_info().HTTP_REMOTE_ORIGIN + '/resources/child-frame.html');
return Promise.all([sameOriginPromise, crossOriginPromise]).then(verifyPerformanceEntries);
}, 'GetEntries of a document of origin A and its two child frames of origin A and B respectively.');
</script>
</body>

View File

@ -0,0 +1,91 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src=/common/get-host-info.sub.js></script>
<script src="../resources/include-frames-helper.js"></script>
</head>
<body>
<script>
const verifyMainFrameEntries = (entries, description = '') => {
let filterOptions = [
createFilterOption('include-frames-originA-B-A', 'navigation', 1, 'Main Frame', description),
createFilterOption('include-frames-subframe', 'resource', 1, 'Main Frame', description),
];
verifyEntries(entries, filterOptions);
}
const verifyChildFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('include-frames-subframe', 'navigation', 0, 'Child Frame'),
createFilterOption('child-frame.html', 'resource', 0, 'Child Frame'),
];
verifyEntries(entries, filterOptions);
}
const verifyGrandchildFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('child-frame.html', 'navigation', 1, 'Grandchild Frame'),
createFilterOption('mark_child_frame', 'mark', 1, 'Grandchild frame')
];
verifyEntries(entries, filterOptions);
}
promise_test(async () => {
performance.clearResourceTimings();
// Load a child frame. The child frame upon loading would load a child frame of its own.
await loadChildFrameAndGrandchildFrame(get_host_info().REMOTE_ORIGIN +
'/performance-timeline/resources/include-frames-subframe.html?origin=' + get_host_info().ORIGIN);
const entries = performance.getEntries({ includeChildFrames: true });
const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
const markedEntries = performance.getEntries(
{ name: 'mark_child_frame', includeChildFrames: true });
verifyMainFrameEntries(entries);
verifyChildFrameEntries(entries);
verifyGrandchildFrameEntries(entries);
// 1 entry for main frame, 1 for grandchild frame.
assert_equals(navigationEntries.length, 2, 'Navigation entries should be 2.');
// 1 entry for grandchild frame.
assert_equals(markedEntries.length, 1, 'Mark entries should be 1.');
// Test cases where includeChildFrames is false.
const entriesWithNoFitlerOptions = performance.getEntries();
const entriesWithoutIncludingChildFrames = performance.getEntries({ includeChildFrames: false });
const navigationEntriesWithoutIncludingChildFrames = performance.getEntries({ entryType: "navigation", includeChildFrames: false });
const markedEntriesWithoutIncludingChildFrames = performance.getEntries(
{ name: 'mark_child_frame', includeChildFrames: false });
verifyMainFrameEntries(entriesWithNoFitlerOptions, 'with no filter options.');
verifyMainFrameEntries(entriesWithoutIncludingChildFrames, 'with includeChildFrame being false.');
// 1 entry for main frame.
assert_equals(navigationEntriesWithoutIncludingChildFrames.length, 1,
'Navigation entries with includeChildFrame being false should be 1.');
// 0 entry since grandchild frame is not included.
assert_equals(markedEntriesWithoutIncludingChildFrames.length, 0,
'Mark entries with includeChildFrame being false should be 0.');
}, 'GetEntries of a document of origin A, its child frame of origin B and \
its grandchild frame of origin A.');
</script>
</body>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src=/common/get-host-info.sub.js></script>
<script src="../resources/include-frames-helper.js"></script>
</head>
<body>
<script>
const verifyMainFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('include-frames-originA-B-B', 'navigation', 1, 'Main Frame'),
createFilterOption('include-frames-subframe', 'resource', 1, 'Main Frame'),
];
verifyEntries(entries, filterOptions);
}
const verifyChildFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('include-frames-subframe', 'navigation', 0, 'Child Frame'),
createFilterOption('child-frame.html', 'resource', 0, 'Child Frame'),
];
verifyEntries(entries, filterOptions);
}
const verifyGrandchildFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('child-frame.html', 'navigation', 0, 'Grandchild Frame'),
createFilterOption('mark_child_frame', 'mark', 0, 'Grandchild frame')
];
verifyEntries(entries, filterOptions);
}
promise_test(async () => {
performance.clearResourceTimings();
// Load a origin child frame. The child frame upon loading would load a child frame of its own.
await loadChildFrameAndGrandchildFrame(get_host_info().REMOTE_ORIGIN
+ '/performance-timeline/resources/include-frames-subframe.html?origin='
+ get_host_info().REMOTE_ORIGIN);
const entries = performance.getEntries({ includeChildFrames: true });
verifyMainFrameEntries(entries);
verifyChildFrameEntries(entries);
verifyGrandchildFrameEntries(entries);
}, 'GetEntries of a document of origin A, its child frame of origin B and \
its grandchild frame of origin B.');
</script>
</body>

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src=/common/get-host-info.sub.js></script>
<script src="../resources/include-frames-helper.js"></script>
</head>
<body>
<script>
const verifyMainFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('include-frames-originA-B', 'navigation', 1, 'Main Frame'),
createFilterOption('child-frame.html', 'resource', 1, 'Main Frame'),
];
verifyEntries(entries, filterOptions);
}
const verifyChildFrameEntries = (entries) => {
let filterOptions = [
createFilterOption('child-frame.html', 'navigation', 0, 'Child Frame'),
createFilterOption('mark_child_frame', 'mark', 0, 'Child Frame'),
];
verifyEntries(entries, filterOptions);
}
promise_test(async () => {
performance.clearResourceTimings();
await loadChildFrame(
get_host_info().HTTP_REMOTE_ORIGIN + '/performance_timeline/resources/child-frame.html');
const entries = performance.getEntries({ includeChildFrames: true });
const navigationEntries = performance.getEntries({ entryType: "navigation", includeChildFrames: true });
const markedEntries = performance.getEntries(
{ name: 'mark_subframe', includeChildFrames: true });
// 1 entry for main frame.
assert_equals(navigationEntries.length, 1, 'Navigation entries should 1.');
// 0 entry since child frame is cross origin.
assert_equals(markedEntries.length, 0, 'Mark entries should 0.');
}, 'GetEntries of a parent Frame of origin A and its child frame of origin B');
</script>
</body>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
</body>
<script>
promise_test(() => {
return new Promise(resolve => {
const navigationEntries = performance.getEntries({ type: 'navigation' })
const parentEntry = navigationEntries[0]
// Parent NavigationTiming source is current window.
assert_equals(parentEntry.source, window)
// Create child iframe.
const childFrame = document.createElement('iframe')
childFrame.src = "../resources/child-frame.html"
document.body.appendChild(childFrame)
childFrame.addEventListener('load', () => {
const markedEntries = performance.getEntries(
{ name: 'mark_child_frame', includeChildFrames: true });
const childEntry = markedEntries[0]
// Child PerformanceMark source is the child's Window.
assert_equals(childEntry.source, childFrame.contentWindow)
resolve()
})
})
}, "PerformanceEntry source is equal to its respective Window")
</script>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
promise_test(async () => {
performance.clearResourceTimings();
performance.mark('entry-name');
const navigationEntries = performance.getEntries({ entryType: 'navigation' });
const markedEntries = performance.getEntries(
{ name: 'entry-name', entryType: 'mark' });
assert_equals(navigationEntries.length, 1, 'navigationEntries should be 1.');
assert_equals(markedEntries.length, 1, 'markedEntries should be 1.');
}, 'GetEntries with filter options.');
</script>
</body>

View File

@ -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>

View File

@ -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');

View File

@ -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>