diff --git a/html/browsers/browsing-the-web/back-forward-cache/README.md b/html/browsers/browsing-the-web/back-forward-cache/README.md new file mode 100644 index 000000000000000..99dff58b7be3820 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/README.md @@ -0,0 +1,31 @@ +# Dispatcher/executor framework + +In the BFCache tests, the main test HTML + +1. Opens new executor Windows using `window.open()` + `noopener` option, and +2. Injects scripts to / receives values from the executor Windows via send()/receive() methods provided by + [the dispatcher/executor framework of COEP credentialless](../../../cross-origin-embedder-policy/credentialless/README.md) + +because less isolated Windows (e.g. iframes and `window.open()` without `noopener` option) are often not eligible for BFCache (e.g. in Chromium). + +# BFCache-specific helpers + +- [resources/executor.html](resources/executor.html) is the BFCache-specific executor and contains helpers for executors. +- [resources/helper.sub.js](resources/helper.sub.js) contains helpers for main test HTMLs. + +In typical A-B-A scenarios (where we navigate from Page A to Page B and then navigate back to Page A, assuming Page A is (or isn't) in BFCache), + +- Call `prepareNavigation()` on the executor, and then navigate to B, and then navigate back to Page A. +- Call `assert_bfcached()` or `assert_not_bfcached()` on the main test HTML, to check the BFCache status. +- Check other test expectations on the main test HTML. + +Note that + +- `await`ing `send()` calls (and other wrapper methods) is needed to serialize injected scripts. +- `send()`/`receive()` uses Fetch API + server-side stash. + `prepareNavigation()` suspends Fetch API calls until we navigate back to the page, to avoid conflicts with BFCache eligibility. + +# Asserting PRECONDITION_FAILED for unexpected BFCache eligibility + +To distinguish failures due to unexpected BFCache eligibility (which might be acceptable due to different BFCache eligibility criteria across browsers), +`assert_bfcached()` and `assert_not_bfcached()` asserts `PRECONDITION_FAILED` rather than ordinal failures. diff --git a/html/browsers/browsing-the-web/back-forward-cache/events.html b/html/browsers/browsing-the-web/back-forward-cache/events.html new file mode 100644 index 000000000000000..159d494dc11226b --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/events.html @@ -0,0 +1,34 @@ + + + + + + + diff --git a/html/browsers/browsing-the-web/back-forward-cache/resources/back.html b/html/browsers/browsing-the-web/back-forward-cache/resources/back.html new file mode 100644 index 000000000000000..39a0a73b68cd611 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/resources/back.html @@ -0,0 +1,4 @@ + + diff --git a/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html b/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html new file mode 100644 index 000000000000000..1ed8ae056f36576 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html @@ -0,0 +1,112 @@ + + + diff --git a/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js b/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js new file mode 100644 index 000000000000000..f5757188bc03a01 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js @@ -0,0 +1,94 @@ +// Helpers called on the main test HTMLs. +// Scripts in `send()` arguments are evaluated on the executors +// (`executor.html`), and helpers available on the executors are defined in +// `executor.html`. + +const idThis = token(); + +const originSameOrigin = + location.protocol === 'http:' ? + 'http://{{host}}:{{ports[http][0]}}' : + 'https://{{host}}:{{ports[https][0]}}'; +const originSameSite = + location.protocol === 'http:' ? + 'http://{{host}}:{{ports[http][1]}}' : + 'https://{{host}}:{{ports[https][1]}}'; +const originCrossSite = + location.protocol === 'http:' ? + 'http://{{hosts[alt][www]}}:{{ports[http][0]}}' : + 'https://{{hosts[alt][www]}}:{{ports[https][0]}}'; + +const executorPath = + '/html/browsers/browsing-the-web/back-forward-cache/resources/executor.html?uuid='; +const backPath = + '/html/browsers/browsing-the-web/back-forward-cache/resources/back.html'; + +// On the executor with uuid `idTarget`: Evaluates the script `expr`, and +// On the caller: returns a Promise resolved with the result of `expr`. +// This assumes the result can be serialized by JSON.stringify(). +async function evalOn(idTarget, expr) { + await send(idTarget, `await send('${idThis}', JSON.stringify(${expr}));`); + const result = await receive(idThis); + return JSON.parse(result); +} + +// On the executor with uuid `idTarget`: +// Evaluates `script` that returns a Promise resolved with `result`. +// On the caller: +// Returns a Promise resolved with `result` +// (or 'Error' when the promise is rejected). +// This assumes `result` can be serialized by JSON.stringify(). +async function asyncEvalOn(idTarget, script) { + send(idTarget, ` + try { + const result = await async function() { ${script} }(); + await send('${idThis}', JSON.stringify(result)); + } + catch (error) { + await send('${idThis}', '"Error"'); + }`); + const result = await receive(idThis); + return JSON.parse(result); +} + +async function runEligibilityCheck(script) { + const idA = token(); + window.open(executorPath + idA, '_blank', 'noopener'); + await send(idA, script); + await send(idA, ` + prepareNavigation(); + location.href = '${originCrossSite + backPath}'; + `); + await assert_bfcached(idA); +} + +async function getBFCachedStatus(idTarget) { + const [loadCount, isPageshowFired] = + await evalOn(idTarget, '[window.loadCount, window.isPageshowFired]'); + if (loadCount === 1 && isPageshowFired === 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 `idTarget` page. + assert_unreached( + `Got unexpected BFCache status: loadCount = ${loadCount}, ` + + `isPageshowFired = ${isPageshowFired}`); + } +} + +// Asserts that the executor with uuid `idTarget` is (or isn't, respectively) +// restored from BFCache. +// These should be used with prepareNavigation() (see `../README.md`). +async function assert_bfcached(idTarget) { + const status = await getBFCachedStatus(idTarget); + assert_implements_optional(status === 'BFCached', 'Should be BFCached'); +} +async function assert_not_bfcached(idTarget) { + const status = await getBFCachedStatus(idTarget); + assert_implements_optional(status !== 'BFCached', 'Should not be BFCached'); +} diff --git a/lint.ignore b/lint.ignore index 1ca2a941aabee0c..3d68876598cd083 100644 --- a/lint.ignore +++ b/lint.ignore @@ -337,6 +337,7 @@ SET TIMEOUT: resources/test/tests/unit/promise_setup.html SET TIMEOUT: resources/testharness.js SET TIMEOUT: scheduler/tentative/current-task-signal-async-abort.any.js SET TIMEOUT: scheduler/tentative/current-task-signal-async-priority.any.js +SET TIMEOUT: html/browsers/browsing-the-web/back-forward-cache/resources/executor.html # setTimeout use in reftests SET TIMEOUT: acid/acid3/test.html