Skip to content

Commit 467bf8a

Browse files
[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 #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. Bug: 1107415 Change-Id: I034f9f5376dc3f9f32ca0b936dbd06e458c9160b
1 parent 3d3e869 commit 467bf8a

File tree

6 files changed

+190
-0
lines changed

6 files changed

+190
-0
lines changed

common/PrefixedLocalStorage.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,24 @@ PrefixedLocalStorage.prototype.setItem = function (baseKey, value) {
4848
localStorage.setItem(this.prefixedKey(baseKey), value);
4949
};
5050

51+
PrefixedLocalStorage.prototype.getItem = function (baseKey) {
52+
return localStorage.getItem(this.prefixedKey(baseKey));
53+
};
54+
55+
PrefixedLocalStorage.prototype.pushItem = function (baseKey, value) {
56+
const array = this.getPushedItems(baseKey);
57+
array.push(value);
58+
this.setItem(baseKey, JSON.stringify(array));
59+
};
60+
61+
PrefixedLocalStorage.prototype.getPushedItems = function (baseKey) {
62+
const value = this.getItem(baseKey);
63+
if (!value) {
64+
return [];
65+
}
66+
return JSON.parse(value);
67+
};
68+
5169
/**
5270
* Listen for `storage` events pertaining to a particular key,
5371
* prefixed with this object's prefix. Ignore when value is being set to null
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE HTML>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
<script src="/common/PrefixedLocalStorage.js"></script>
5+
<title>Events fired during BFCached back navigation (cross-site)</title>
6+
<script>
7+
const prefixedLocalStorage = new PrefixedLocalStorageTest();
8+
fetch_tests_from_prefixed_local_storage(prefixedLocalStorage);
9+
window.open(prefixedLocalStorage.url('resources/events.html'),
10+
'_blank',
11+
'noopener');
12+
</script>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<!DOCTYPE HTML>
2+
<script>
3+
window.onload = () => history.back();
4+
</script>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE HTML>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/common/PrefixedLocalStorage.js"></script>
4+
<script src="helper.sub.js"></script>
5+
<script>
6+
startRecordingEvents(['visibilitychange', 'pagehide', 'pageshow', 'load']);
7+
8+
const t = async_test('Events');
9+
runTest(
10+
t,
11+
() => location.href = backUrl,
12+
(isBFCached, observedEvents) => {
13+
assert_implements_optional(isBFCached, 'Should be BFCached');
14+
assert_array_equals(observedEvents, [
15+
'window.load',
16+
'window.pageshow',
17+
'window.pagehide.persisted',
18+
'document.visibilitychange.hidden',
19+
'window.visibilitychange.hidden',
20+
'document.visibilitychange.visible',
21+
'window.visibilitychange.visible',
22+
'window.pageshow.persisted']);
23+
t.done();
24+
}
25+
);
26+
</script>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// A helper script for simple A->B->A navigation scenarios like:
2+
// 1. Initial navigation to `A.html`.
3+
// 2. Navigation to `B.html`.
4+
// 3. Back navigation to `A.html`, assuming `A.html` is (or is not) in BFCache.
5+
6+
// This script is loaded from `A.html`.
7+
8+
// `A.html` should be opened using `PrefixedLocalStorage.url()`, because
9+
// `/common/PrefixedLocalStorage.js` is used to save states across navigations.
10+
11+
window.prefixedLocalStorage = new PrefixedLocalStorageResource({
12+
close_on_cleanup: true
13+
});
14+
15+
// Starts an A->B->A navigation test. This should be called on `A.html`.
16+
// `onStart()` is called on the initial navigation, which is expected to
17+
// initiate a navigation to a page (`B.html`) that will eventually back
18+
// navigate to `A.html`.
19+
// `onBackNavigated(isBFCached, observedEvents)` is called on back navigation.
20+
// - `isBFCached` indicates whether the back navigation is from BFCache or not,
21+
// based on events fired.
22+
// - `observedEvents` is an array of event labels fired on `A.html`,
23+
// if `startRecordingEvents()` is called.
24+
function runTest(test, onStart, onBackNavigated) {
25+
window.addEventListener('load', () => {
26+
if (prefixedLocalStorage.getItem('state') === null) {
27+
// Initial navigation.
28+
prefixedLocalStorage.setItem('state', 'started');
29+
30+
// Call `onStart()` (and thus starting navigation) after this document
31+
// is fully loaded.
32+
// `step_timeout()` is used here because starting the navigation
33+
// synchronously inside the window load event handler seems to
34+
// cause back navigation to this page to fail on Firefox.
35+
test.step_timeout(() => {
36+
window.addEventListener('pageshow', (() => {
37+
// Back navigation, from BFCache.
38+
test.step(
39+
onBackNavigated,
40+
undefined,
41+
true,
42+
prefixedLocalStorage.getPushedItems('observedEvents'));
43+
}));
44+
test.step(onStart);
45+
}, 0);
46+
} else {
47+
// Back navigation, not from BFCache.
48+
test.step(
49+
onBackNavigated,
50+
undefined,
51+
false,
52+
prefixedLocalStorage.getPushedItems('observedEvents'));
53+
}
54+
});
55+
}
56+
57+
// Records events fired on `window` and `document`, with names listed in
58+
// `eventNames`.
59+
// The recorded events are stored in localStorage and used later in the
60+
// runTest() callback.
61+
function startRecordingEvents(eventNames) {
62+
window.testObservedEvents = [];
63+
for (const eventName of eventNames) {
64+
window.addEventListener(eventName, event => {
65+
let result = eventName;
66+
if (event.persisted) {
67+
result += '.persisted';
68+
}
69+
if (eventName === 'visibilitychange') {
70+
result += '.' + document.visibilityState;
71+
}
72+
prefixedLocalStorage.pushItem('observedEvents', 'window.' + result);
73+
});
74+
document.addEventListener(eventName, () => {
75+
let result = eventName;
76+
if (eventName === 'visibilitychange') {
77+
result += '.' + document.visibilityState;
78+
}
79+
prefixedLocalStorage.pushItem('observedEvents', 'document.' + result);
80+
});
81+
}
82+
}
83+
84+
const origin =
85+
'http://{{hosts[alt][www]}}:{{ports[http][0]}}'; // cross-site
86+
87+
const backUrl =
88+
origin +
89+
'/html/browsers/browsing-the-web/back-forward-cache/resources/back.html';

resources/testharness.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@
130130
w.postMessage(message_arg, "*");
131131
}
132132
});
133+
if (window.prefixedLocalStorage) {
134+
window.prefixedLocalStorage.setItem('dispatched_messages.' + Math.random(),
135+
JSON.stringify(message_arg));
136+
}
133137
};
134138

135139
WindowTestEnvironment.prototype._forEach_windows = function(callback) {
@@ -3168,6 +3172,25 @@
31683172
);
31693173
};
31703174

3175+
/*
3176+
* Constructs a RemoteContext that tracks tests from prefixed local storage.
3177+
* Test results from a window where
3178+
* - `window.prefixedLocalStorage` is defined using `/common/PrefixedLocalStorage.js`
3179+
* like `window.prefixedLocalStorage = new PrefixedLocalStorageResource();` and
3180+
* - testharness.js is loaded
3181+
* are received via `prefixedLocalStorage`.
3182+
*/
3183+
Tests.prototype.create_remote_prefixed_local_storage = function(prefixedLocalStorage) {
3184+
const channel = new MessageChannel();
3185+
// This receives the random keys prefixed by 'dispatched_messages' sent by the remote
3186+
// window's `WindowTestEnvironment.prototype._dispatch`.
3187+
prefixedLocalStorage.onSet('dispatched_messages', e => {
3188+
channel.port1.postMessage(JSON.parse(e.newValue));
3189+
});
3190+
channel.port2.start();
3191+
return new RemoteContext(null, channel.port2);
3192+
};
3193+
31713194
Tests.prototype.fetch_tests_from_worker = function(worker) {
31723195
if (this.phase >= this.phases.COMPLETE) {
31733196
return;
@@ -3196,6 +3219,24 @@
31963219
}
31973220
expose(fetch_tests_from_window, 'fetch_tests_from_window');
31983221

3222+
3223+
Tests.prototype.fetch_tests_from_prefixed_local_storage = function(prefixedLocalStorage) {
3224+
if (this.phase >= this.phases.COMPLETE) {
3225+
return;
3226+
}
3227+
3228+
var remoteContext = this.create_remote_prefixed_local_storage(prefixedLocalStorage);
3229+
this.pending_remotes.push(remoteContext);
3230+
return remoteContext.done.then(() => {
3231+
prefixedLocalStorage.cleanup();
3232+
});
3233+
};
3234+
3235+
function fetch_tests_from_prefixed_local_storage(prefixedLocalStorage) {
3236+
return tests.fetch_tests_from_prefixed_local_storage(prefixedLocalStorage);
3237+
}
3238+
expose(fetch_tests_from_prefixed_local_storage, 'fetch_tests_from_prefixed_local_storage');
3239+
31993240
function timeout() {
32003241
if (tests.timeout_length === null) {
32013242
tests.timeout();

0 commit comments

Comments
 (0)