diff --git a/common/PrefixedLocalStorage.js b/common/PrefixedLocalStorage.js index 2f4e7b6a055caa..4c8095521ed7b0 100644 --- a/common/PrefixedLocalStorage.js +++ b/common/PrefixedLocalStorage.js @@ -48,6 +48,33 @@ PrefixedLocalStorage.prototype.setItem = function (baseKey, value) { localStorage.setItem(this.prefixedKey(baseKey), value); }; +PrefixedLocalStorage.prototype.removeItem = function (baseKey) { + localStorage.removeItem(this.prefixedKey(baseKey)); +}; + +PrefixedLocalStorage.prototype.getItem = function (baseKey) { + return localStorage.getItem(this.prefixedKey(baseKey)); +}; + +PrefixedLocalStorage.prototype.pushItem = function (baseKey, value) { + let index = this.getItem(baseKey); + if (!index) { index = 0; } else { index = parseInt(index); } + this.setItem(baseKey + '.' + index, JSON.stringify(value)); + this.setItem(baseKey, (index + 1)); +}; + +PrefixedLocalStorage.prototype.getPushedItems = function (baseKey, startIndex) { + let index = this.getItem(baseKey); + if (!index) { index = 0; } + if (!startIndex) { startIndex = 0; } + const array = []; + for (let i = startIndex; i < index; ++i) { + const value = JSON.parse(this.getItem(baseKey + '.' + i)); + array.push(value); + } + return array; +}; + /** * 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..bce7ba0d385ac2 --- /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..8e79ea0b42639a --- /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..188278b851c92d --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/resources/events.html @@ -0,0 +1,25 @@ + + + + + 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..09da6f49f4edab --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js @@ -0,0 +1,69 @@ +// This Document is opened using `window.open()` with 'noopener' option by +// the main testharness Window and +// writes the result back to the main Window via `localStorage`. +// This is because the current test runners expect the top-level testharness +// Document is never unloaded in the middle of a test. + +window.prefixedLocalStorage = new PrefixedLocalStorageResource({ + close_on_cleanup: true +}); + +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); + }); + } +} + +function runTest(onStart, onBackNavigated) { + window.addEventListener('load', () => { + if (prefixedLocalStorage.getItem('state') === null) { + prefixedLocalStorage.setItem('state', 'started'); + + // Navigate after this document is fully loaded. + // Calling + // location.href = 'resources/back.html'; + // synchronously here seems to cause `history.back()` on `back.html` to go + // back to the previous page of this page, not this page, on Firefox. + setTimeout(() => { + window.addEventListener('pageshow', (() => { + onBackNavigated( + true, + prefixedLocalStorage.getPushedItems('observedEvents')); + })); + onStart(); + }, 100); + } else { + onBackNavigated( + false, + prefixedLocalStorage.getPushedItems('observedEvents')); + } + }); +} + +const originParam = new URL(location.href).searchParams.get('origin'); + +const origin = + originParam === 'same-origin' ? 'http://{{host}}:{{ports[http][0]}}' : + originParam === 'same-site' ? 'http://{{host}}:{{ports[http][1]}}' : + '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..7f97364c2f07a1 100644 --- a/resources/testharness.js +++ b/resources/testharness.js @@ -110,6 +110,9 @@ WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) { this.dispatched_messages.push(message_arg); + if (window.prefixedLocalStorage) { + window.prefixedLocalStorage.setItem('dispatched_messages.' + Math.random(), JSON.stringify(message_arg)); + } this._forEach_windows( function(w, same_origin) { if (same_origin) { @@ -3168,6 +3171,19 @@ ); }; + /* + * Constructs a RemoteContext that tracks tests from prefixed local storage. + */ + Tests.prototype.create_remote_prefixed_local_storage = function(prefixedLocalStorage) { + const channel = new MessageChannel(); + let index = 0; + prefixedLocalStorage.onSet('dispatched_messages', e => { + channel.port1.postMessage(JSON.parse(e.newValue)); + }); + channel.port2.onmessage = e => {}; // FOXME + return new RemoteContext(null, channel.port2); + }; + Tests.prototype.fetch_tests_from_worker = function(worker) { if (this.phase >= this.phases.COMPLETE) { return; @@ -3196,6 +3212,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();