Prerender: upstream wpt_internal/prerender/visibility-state.html
This behavior is described in
https://jeremyroman.github.io/alternate-loading-modes/prerendering.html#interaction-with-visibility-state
Bug: 1253158
Change-Id: I70c36276c7e0a3c63d48b3adb4d8f8c857a83152
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3229327
Commit-Queue: Lingqi Chi <lingqi@chromium.org>
Reviewed-by: Hiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/main@{#932962}
diff --git a/lint.ignore b/lint.ignore
index 8798fa3..ac67bd0 100644
--- a/lint.ignore
+++ b/lint.ignore
@@ -335,6 +335,7 @@
SET TIMEOUT: scheduler/tentative/current-task-signal-async-abort.any.js
SET TIMEOUT: scheduler/tentative/current-task-signal-async-priority.any.js
SET TIMEOUT: speculation-rules/prerender/resources/activation-start.html
+SET TIMEOUT: speculation-rules/prerender/resources/deferred-promise-utils.js
SET TIMEOUT: speculation-rules/prerender/resources/utils.js
# setTimeout use in reftests
diff --git a/speculation-rules/prerender/resources/deferred-promise-utils.js b/speculation-rules/prerender/resources/deferred-promise-utils.js
new file mode 100644
index 0000000..02bd7ec
--- /dev/null
+++ b/speculation-rules/prerender/resources/deferred-promise-utils.js
@@ -0,0 +1,72 @@
+/**
+ * This file co-works with a html file and utils.js to test a promise that
+ * should be deferred during prerendering.
+ *
+ * Usage example:
+ * Suppose the html is "prerender-promise-test.html"
+ * On prerendering page, prerender-promise-test.html?prerendering:
+ * const prerenderEventCollector = new PrerenderEventCollector();
+ * const promise = {a promise that should be deferred during prerendering};
+ * prerenderEventCollector.start(promise, {promise name});
+ *
+ * On the initiator page, prerender-promise-test.html:
+ * execute
+ * `loadInitiatorPage();`
+ */
+
+// Collects events that happen relevant to a prerendering page.
+// An event is added when:
+// 1. start() is called.
+// 2. a prerenderingchange event is dispatched on this document.
+// 3. the promise passed to start() is resolved.
+// 4. addEvent() is called manually.
+class PrerenderEventCollector {
+ constructor() {
+ this.eventsSeen_ = [];
+ }
+
+ // Adds an event to `eventsSeen_` along with the prerendering state of the
+ // page.
+ addEvent(eventMessage) {
+ this.eventsSeen_.push(
+ {event: eventMessage, prerendering: document.prerendering});
+ }
+
+ // Starts collecting events until the promise resolves. Triggers activation by
+ // telling the initiator page that it is ready for activation.
+ async start(promise, promiseName) {
+ assert_true(document.prerendering);
+ this.addEvent(`started waiting ${promiseName}`);
+ promise
+ .then(
+ () => {
+ this.addEvent(`finished waiting ${promiseName}`);
+ },
+ (error) => {
+ if (error instanceof Error)
+ error = error.name;
+ this.addEvent(`${promiseName} rejected: ${error}`);
+ })
+ .finally(() => {
+ // Used to communicate with the main test page.
+ const testChannel = new BroadcastChannel('test-channel');
+ // Send the observed events back to the main test page.
+ testChannel.postMessage(this.eventsSeen_);
+ testChannel.close();
+ window.close();
+ });
+ document.addEventListener('prerenderingchange', () => {
+ this.addEvent('prerendering change');
+ });
+
+ // Post a task to give the implementation a chance to fail in case it
+ // resolves a promise without waiting for activation.
+ setTimeout(() => {
+ // Used to communicate with the initiator page.
+ const prerenderChannel = new BroadcastChannel('prerender-channel');
+ // Inform the initiator page that this page is ready to be activated.
+ prerenderChannel.postMessage('readyToActivate');
+ prerenderChannel.close();
+ }, 0);
+ }
+}
diff --git a/speculation-rules/prerender/resources/visibility-state-check.html b/speculation-rules/prerender/resources/visibility-state-check.html
new file mode 100644
index 0000000..3c1e488
--- /dev/null
+++ b/speculation-rules/prerender/resources/visibility-state-check.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="deferred-promise-utils.js"></script>
+<script>
+const params = new URLSearchParams(location.search);
+
+// The main test page (visibility-state.html) loads the initiator page,
+// then the initiator page will prerender itself with the `prerendering`
+// parameter.
+const isPrerendering = params.has('prerendering');
+
+if (!isPrerendering) {
+ loadInitiatorPage();
+} else {
+ const prerenderEventCollector = new PrerenderEventCollector();
+ const promise = new Promise((resolve) => {
+ prerenderEventCollector.addEvent(
+ 'Initial visibilityState: ' + document.visibilityState);
+
+ document.addEventListener('prerenderingchange', () => {
+ prerenderEventCollector.addEvent(
+ 'visibilityState after prerenderingchange: ' +
+ document.visibilityState);
+ resolve();
+ });
+ });
+ prerenderEventCollector.start(promise, 'visibilityState check');
+}
+
+</script>
diff --git a/speculation-rules/prerender/visibility-state.html b/speculation-rules/prerender/visibility-state.html
new file mode 100644
index 0000000..1affe54
--- /dev/null
+++ b/speculation-rules/prerender/visibility-state.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>visibilityState must be updated after prerendering</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<body>
+<script>
+
+setup(() => assertSpeculationRulesIsSupported());
+
+promise_test(async t => {
+ const bc = new BroadcastChannel('test-channel');
+
+ const gotMessage = new Promise(resolve => {
+ bc.addEventListener('message', e => {
+ resolve(e.data);
+ }, {
+ once: true
+ });
+ });
+ const url = `resources/visibility-state-check.html`;
+ window.open(url, '_blank', 'noopener');
+
+ const result = await gotMessage;
+ const expected = [
+ {
+ event: 'Initial visibilityState: hidden',
+ prerendering: true
+ },
+ {
+ event: 'started waiting visibilityState check',
+ prerendering: true
+ },
+ {
+ event: 'visibilityState after prerenderingchange: visible',
+ prerendering: false
+ },
+ {
+ event: 'finished waiting visibilityState check',
+ prerendering: false
+ }
+ ];
+ assert_equals(result.length, expected.length);
+ for (let i = 0; i < result.length; i++) {
+ assert_equals(result[i].event, expected[i].event, `event${i}`);
+ assert_equals(result[i].prerendering, expected[i].prerendering,
+ `prerendering${i}`);
+ }
+}, 'The visibilityState must be updated after prerendering.');
+
+</script>
+</body>