diff --git a/common/PrefixedLocalStorage.js b/common/PrefixedLocalStorage.js index 2f4e7b6a055caa..24f19e1d2fceb3 100644 --- a/common/PrefixedLocalStorage.js +++ b/common/PrefixedLocalStorage.js @@ -48,6 +48,24 @@ PrefixedLocalStorage.prototype.setItem = function (baseKey, value) { localStorage.setItem(this.prefixedKey(baseKey), value); }; +PrefixedLocalStorage.prototype.getItem = function (baseKey) { + return localStorage.getItem(this.prefixedKey(baseKey)); +}; + +PrefixedLocalStorage.prototype.pushItem = function (baseKey, value) { + const array = this.getPushedItems(baseKey); + array.push(value); + this.setItem(baseKey, JSON.stringify(array)); +}; + +PrefixedLocalStorage.prototype.getPushedItems = function (baseKey) { + const value = this.getItem(baseKey); + if (!value) { + return []; + } + return JSON.parse(value); +}; + /** * Listen for `storage` events pertaining to a particular key, * prefixed with this object's prefix. Ignore when value is being set to null 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 00000000000000..91585d94339c60 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/events.html @@ -0,0 +1,12 @@ + + + + +Events fired during BFCached back navigation (cross-site) + 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 00000000000000..39a0a73b68cd61 --- /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/events.html b/html/browsers/browsing-the-web/back-forward-cache/resources/events.html new file mode 100644 index 00000000000000..8384893f3c7ac4 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/resources/events.html @@ -0,0 +1,26 @@ + + + + + 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 00000000000000..ddb670a4b2a92f --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js @@ -0,0 +1,89 @@ +// A helper script for simple A->B->A navigation scenarios like: +// 1. Initial navigation to `A.html`. +// 2. Navigation to `B.html`. +// 3. Back navigation to `A.html`, assuming `A.html` is (or is not) in BFCache. + +// This script is loaded from `A.html`. + +// `A.html` should be opened using `PrefixedLocalStorage.url()`, because +// `/common/PrefixedLocalStorage.js` is used to save states across navigations. + +window.prefixedLocalStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); + +// Starts an A->B->A navigation test. This should be called on `A.html`. +// `onStart()` is called on the initial navigation, which is expected to +// initiate a navigation to a page (`B.html`) that will eventually back +// navigate to `A.html`. +// `onBackNavigated(isBFCached, observedEvents)` is called on back navigation. +// - `isBFCached` indicates whether the back navigation is from BFCache or not, +// based on events fired. +// - `observedEvents` is an array of event labels fired on `A.html`, +// if `startRecordingEvents()` is called. +function runTest(test, onStart, onBackNavigated) { + window.addEventListener('load', () => { + if (prefixedLocalStorage.getItem('state') === null) { + // Initial navigation. + prefixedLocalStorage.setItem('state', 'started'); + + // Call `onStart()` (and thus starting navigation) after this document + // is fully loaded. + // `step_timeout()` is used here because starting the navigation + // synchronously inside the window load event handler seems to + // cause back navigation to this page to fail on Firefox. + test.step_timeout(() => { + window.addEventListener('pageshow', (() => { + // Back navigation, from BFCache. + test.step( + onBackNavigated, + undefined, + true, + prefixedLocalStorage.getPushedItems('observedEvents')); + })); + test.step(onStart); + }, 0); + } else { + // Back navigation, not from BFCache. + test.step( + onBackNavigated, + undefined, + false, + prefixedLocalStorage.getPushedItems('observedEvents')); + } + }); +} + +// Records events fired on `window` and `document`, with names listed in +// `eventNames`. +// The recorded events are stored in localStorage and used later in the +// runTest() callback. +function startRecordingEvents(eventNames) { + window.testObservedEvents = []; + for (const eventName of eventNames) { + window.addEventListener(eventName, event => { + let result = eventName; + if (event.persisted) { + result += '.persisted'; + } + if (eventName === 'visibilitychange') { + result += '.' + document.visibilityState; + } + prefixedLocalStorage.pushItem('observedEvents', 'window.' + result); + }); + document.addEventListener(eventName, () => { + let result = eventName; + if (eventName === 'visibilitychange') { + result += '.' + document.visibilityState; + } + prefixedLocalStorage.pushItem('observedEvents', 'document.' + result); + }); + } +} + +const origin = + 'http://{{hosts[alt][www]}}:{{ports[http][0]}}'; // cross-site + +const backUrl = + origin + + '/html/browsers/browsing-the-web/back-forward-cache/resources/back.html'; diff --git a/resources/testharness.js b/resources/testharness.js index f85b19fd9bd90c..f54808ce35592d 100644 --- a/resources/testharness.js +++ b/resources/testharness.js @@ -130,6 +130,10 @@ w.postMessage(message_arg, "*"); } }); + if (window.prefixedLocalStorage) { + window.prefixedLocalStorage.setItem('dispatched_messages.' + Math.random(), + JSON.stringify(message_arg)); + } }; WindowTestEnvironment.prototype._forEach_windows = function(callback) { @@ -3168,6 +3172,25 @@ ); }; + /* + * Constructs a RemoteContext that tracks tests from prefixed local storage. + * Test results from a window where + * - `window.prefixedLocalStorage` is defined using `/common/PrefixedLocalStorage.js` + * like `window.prefixedLocalStorage = new PrefixedLocalStorageResource();` and + * - testharness.js is loaded + * are received via `prefixedLocalStorage`. + */ + Tests.prototype.create_remote_prefixed_local_storage = function(prefixedLocalStorage) { + const channel = new MessageChannel(); + // This receives the random keys prefixed by 'dispatched_messages' sent by the remote + // window's `WindowTestEnvironment.prototype._dispatch`. + prefixedLocalStorage.onSet('dispatched_messages', e => { + channel.port1.postMessage(JSON.parse(e.newValue)); + }); + channel.port2.start(); + return new RemoteContext(null, channel.port2); + }; + Tests.prototype.fetch_tests_from_worker = function(worker) { if (this.phase >= this.phases.COMPLETE) { return; @@ -3196,6 +3219,24 @@ } expose(fetch_tests_from_window, 'fetch_tests_from_window'); + + Tests.prototype.fetch_tests_from_prefixed_local_storage = function(prefixedLocalStorage) { + if (this.phase >= this.phases.COMPLETE) { + return; + } + + var remoteContext = this.create_remote_prefixed_local_storage(prefixedLocalStorage); + this.pending_remotes.push(remoteContext); + return remoteContext.done.then(() => { + prefixedLocalStorage.cleanup(); + }); + }; + + function fetch_tests_from_prefixed_local_storage(prefixedLocalStorage) { + return tests.fetch_tests_from_prefixed_local_storage(prefixedLocalStorage); + } + expose(fetch_tests_from_prefixed_local_storage, 'fetch_tests_from_prefixed_local_storage'); + function timeout() { if (tests.timeout_length === null) { tests.timeout();