From 5e2f7cbf429ba4b546e421543fee7922c5ad1aac Mon Sep 17 00:00:00 2001 From: Hiroshige Hayashizaki Date: Thu, 8 Jul 2021 17:26:51 -0700 Subject: [PATCH] [BFCache] Add basic event tests + helpers for BFCache WPT back-forward-cache/resources/helper.sub.js: Helper for A->B->A navigation scenarios. BFCache state is detected by observing `pageshow` events. We might want to use more explicit APIs like `isPreviousPageInBFCache` discussed at https://github.com/web-platform-tests/wpt/issues/16359 in the future / in more complicated scenarios, but so far `pageshow`-based detection seems to work without hurting ergonomics. back-forward-cache/resources/events.html: The file that loads `helper.sub.js` and contains test logic and assertions. We navigate to `back-forward-cache/resources/back.html` and then back-navigate to `back-forward-cache/resources/events.html`. In the case of BFCache is not used, two async_test objects are created: - The first one during the initial navigation, which never completes, and - The second one during the back navigation, which will execute test assertions and then complete. This doesn't affect the behavior (the first one seems just ignored), but might look awkward. back-forward-cache/events.html: The main test Document that opens `back-forward-cache/resources/events.html` using `window.open()` with 'noopener' option. This is because (at least) Chromium requires top-level navigations to trigger BFCache and thus `back-forward-cache/resources/events.html` is navigated away during the test, but the WPT test infrastructure doesn't support navigating the main test Document. testharness.js: `fetch_tests_from_prefixed_local_storage()` is introduced to communicate test results from `back-forward-cache/resources/events.html` in a similar way to `fetch_tests_from_window()`. PrefixedLocalStorage.js: Some basic utility methods for `helper.sub.js` are added. Design doc: https://docs.google.com/document/d/1p3G-qNYMTHf5LU9hykaXcYtJ0k3wYOwcdVKGeps6EkU/edit?usp=sharing Bug: 1107415 Change-Id: I034f9f5376dc3f9f32ca0b936dbd06e458c9160b --- .../back-forward-cache/README.md | 31 +++++ .../back-forward-cache/events.html | 34 ++++++ .../back-forward-cache/resources/back.html | 4 + .../resources/executor.html | 112 ++++++++++++++++++ .../resources/helper.sub.js | 94 +++++++++++++++ lint.ignore | 1 + 6 files changed, 276 insertions(+) create mode 100644 html/browsers/browsing-the-web/back-forward-cache/README.md create mode 100644 html/browsers/browsing-the-web/back-forward-cache/events.html create mode 100644 html/browsers/browsing-the-web/back-forward-cache/resources/back.html create mode 100644 html/browsers/browsing-the-web/back-forward-cache/resources/executor.html create mode 100644 html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js 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