0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-21 13:09:21 +01:00

test: update performance-timeline wpt

PR-URL: https://github.com/nodejs/node/pull/55197
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
This commit is contained in:
RedYetiDev 2024-09-30 15:56:18 -04:00 committed by Antoine du Hamel
parent c185e11623
commit 1aa71351fa
No known key found for this signature in database
GPG Key ID: 21D900FFDB233756
51 changed files with 2053 additions and 2 deletions

View File

@ -24,7 +24,7 @@ Last update:
- html/webappapis/structured-clone: https://github.com/web-platform-tests/wpt/tree/47d3fb280c/html/webappapis/structured-clone
- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers
- interfaces: https://github.com/web-platform-tests/wpt/tree/e90ece61d6/interfaces
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/17ebc3aea0/performance-timeline
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/94caab7038/performance-timeline
- resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing
- resources: https://github.com/web-platform-tests/wpt/tree/1e140d63ec/resources
- streams: https://github.com/web-platform-tests/wpt/tree/2bd26e124c/streams

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,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,2 @@
// META: script=/resources/idlharness-shadowrealm.js
idl_test_shadowrealm(["performance-timeline"], ["hr-time", "dom"]);

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

@ -56,7 +56,7 @@
"path": "interfaces"
},
"performance-timeline": {
"commit": "17ebc3aea0d6321e69554067c39ab5855e6fb67e",
"commit": "94caab7038b27c16d605fa3547dacbee3a2fde4e",
"path": "performance-timeline"
},
"resource-timing": {

View File

@ -21,5 +21,58 @@
},
"webtiming-resolution.any.js": {
"skip": "flaky"
},
"not-restored-reasons/performance-navigation-timing-attributes.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-bfcache-reasons-stay.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-bfcache.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-cross-origin-bfcache.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-fetch.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-iframes-without-attributes.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-lock.https.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-navigation-failure.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-not-bfcached.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-redirect-on-history.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-reload.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/performance-navigation-timing-same-origin-replace.tentative.window.js": {
"skip": "Depends on HTML WPT"
},
"not-restored-reasons/abort-block-bfcache.window.js": {
"fail": {
"note": "Requires window.stop()",
"expected": [
"aborting a parser should block bfcache."
]
}
},
"idlharness-shadowrealm.window.js": {
"skip": "ShadowRealm support is not enabled"
},
"droppedentriescount.any.js": {
"skip": "WPTRunner does not support fetch()"
}
}