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();