Source code
Revision control
Copy as Markdown
Other Tools
// Helpers called on the main test HTMLs.
// Functions in `RemoteContext.execute_script()`'s 1st argument are evaluated
// on the executors (`executor.html`), and helpers available on the executors
// are defined in `executor.html`.
const originSameOrigin =
location.protocol === 'http:' ?
const originSameSite =
location.protocol === 'http:' ?
const originCrossSite =
location.protocol === 'http:' ?
const executorPath =
'/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html?uuid=';
// Asserts that the executor `target` is (or isn't, respectively)
// restored from BFCache. These should be used in the following fashion:
// 1. Call prepareNavigation() on the executor `target`.
// 2. Navigate the executor to another page.
// 3. Navigate back to the executor `target`.
// 4. Call assert_bfcached() or assert_not_bfcached() on the main test HTML.
async function assert_bfcached(target) {
const status = await getBFCachedStatus(target);
assert_implements_optional(status === 'BFCached',
"Could have been BFCached but actually wasn't");
}
async function assert_not_bfcached(target) {
const status = await getBFCachedStatus(target);
assert_implements(status !== 'BFCached',
'Should not be BFCached but actually was');
}
async function getBFCachedStatus(target) {
const [loadCount, isPageshowFired, isPageshowPersisted] =
await target.execute_script(() => [
window.loadCount, window.isPageshowFired, window.isPageshowPersisted]);
if (loadCount === 1 && isPageshowFired === true &&
isPageshowPersisted === true) {
return 'BFCached';
} else if (loadCount === 2 && isPageshowFired === false) {
return 'Not BFCached';
} else {
// This can occur for example when this is called before first navigating
// away (loadCount = 1, isPageshowFired = false), e.g. when
// 1. sending a script for navigation and then
// 2. calling getBFCachedStatus() without waiting for the completion of
// the script on the `target` page.
assert_unreached(
`Got unexpected BFCache status: loadCount = ${loadCount}, ` +
`isPageshowFired = ${isPageshowFired}, ` +
`isPageshowPersisted = ${isPageshowPersisted}`);
}
}
// Always call `await remoteContext.execute_script(waitForPageShow);` after
// triggering to navigation to the page, to wait for pageshow event on the
// remote context.
const waitForPageShow = () => window.pageShowPromise;
// Run a test that navigates A->B->A:
// 1. Page A is opened by `params.openFunc(url)`.
// 2. `params.funcBeforeNavigation(params.argsBeforeNavigation)` is executed
// on page A.
// 3. The window is navigated to page B on `params.targetOrigin`.
// 4. The window is back navigated to page A (expecting BFCached).
//
// Events `params.events` (an array of strings) are observed on page A and
// `params.expectedEvents` (an array of strings) is expected to be recorded.
// See `event-recorder.js` for event recording.
//
// Parameters can be omitted. See `defaultParams` below for default.
function runEventTest(params, description) {
const defaultParams = {
openFunc(url) {
window.open(
`${url}&events=${this.events.join(',')}`,
'_blank',
'noopener'
)
},
events: ['pagehide', 'pageshow', 'load'],
expectedEvents: [
'window.load',
'window.pageshow',
'window.pagehide.persisted',
'window.pageshow.persisted'
],
async funcAfterAssertion(pageA) {
assert_array_equals(
await pageA.execute_script(() => getRecordedEvents()),
this.expectedEvents);
}
}
// Apply defaults.
params = { ...defaultParams, ...params };
runBfcacheTest(params, description);
}
async function navigateAndThenBack(pageA, pageB, urlB,
funcBeforeBackNavigation,
argsBeforeBackNavigation) {
await pageA.execute_script(
(url) => {
prepareNavigation(() => {
location.href = url;
});
},
[urlB]
);
await pageB.execute_script(waitForPageShow);
if (funcBeforeBackNavigation) {
await pageB.execute_script(funcBeforeBackNavigation,
argsBeforeBackNavigation);
}
await pageB.execute_script(
() => {
prepareNavigation(() => { history.back(); });
}
);
await pageA.execute_script(waitForPageShow);
}
function runBfcacheTest(params, description) {
const defaultParams = {
openFunc: url => window.open(url, '_blank', 'noopener'),
scripts: [],
funcBeforeNavigation: () => {},
argsBeforeNavigation: [],
targetOrigin: originCrossSite,
funcBeforeBackNavigation: () => {},
argsBeforeBackNavigation: [],
shouldBeCached: true,
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;
// So that tests can refer to these URLs for assertions if necessary.
pageA.url = originSameOrigin + urlA;
pageB.url = urlB;
params.openFunc(urlA);
await pageA.execute_script(waitForPageShow);
for (const src of params.scripts) {
await pageA.execute_script((src) => {
const script = document.createElement("script");
script.src = src;
document.head.append(script);
return new Promise(resolve => script.onload = resolve);
}, [src]);
}
await pageA.execute_script(params.funcBeforeNavigation,
params.argsBeforeNavigation);
await navigateAndThenBack(pageA, pageB, urlB,
params.funcBeforeBackNavigation,
params.argsBeforeBackNavigation);
if (params.shouldBeCached) {
await assert_bfcached(pageA);
} else {
await assert_not_bfcached(pageA);
}
if (params.funcAfterAssertion) {
await params.funcAfterAssertion(pageA, pageB, t);
}
}, description);
}
// Call clients.claim() on the service worker
async function claim(t, worker) {
const channel = new MessageChannel();
const saw_message = new Promise(function(resolve) {
channel.port1.onmessage = t.step_func(function(e) {
assert_equals(e.data, 'PASS', 'Worker call to claim() should fulfill.');
resolve();
});
});
worker.postMessage({type: "claim", port: channel.port2}, [channel.port2]);
await saw_message;
}
// Assigns the current client to a local variable on the service worker.
async function storeClients(t, worker) {
const channel = new MessageChannel();
const saw_message = new Promise(function(resolve) {
channel.port1.onmessage = t.step_func(function(e) {
assert_equals(e.data, 'PASS', 'storeClients');
resolve();
});
});
worker.postMessage({type: "storeClients", port: channel.port2}, [channel.port2]);
await saw_message;
}
// Call storedClients.postMessage("") on the service worker
async function postMessageToStoredClients(t, worker) {
const channel = new MessageChannel();
const saw_message = new Promise(function(resolve) {
channel.port1.onmessage = t.step_func(function(e) {
assert_equals(e.data, 'PASS', 'postMessageToStoredClients');
resolve();
});
});
worker.postMessage({type: "postMessageToStoredClients",
port: channel.port2}, [channel.port2]);
await saw_message;
}