From c6fafada408bbd361a5b78ab38f84637927f3abf Mon Sep 17 00:00:00 2001 From: Hiroshige Hayashizaki <hiroshige@chromium.org> Date: Wed, 13 Oct 2021 18:12:42 -0700 Subject: [PATCH] [WPT] BFCache: service worker clients This CL adds tests for BFCache + Clients.claim(), Clients.matchAll() and unregister(). Bug: 1107415, 1204228, https://github.com/w3c/ServiceWorker/issues/1594 Change-Id: I73233cf917e31dd91b974823d5490d0190f0eade --- .../resources/helper.sub.js | 13 +++ .../resources/service-worker.js | 31 +++++++ .../service-worker-clients-claim.https.html | 86 +++++++++++++++++++ ...service-worker-clients-matchall.https.html | 63 ++++++++++++++ .../service-worker-unregister.https.html | 52 +++++++++++ 5 files changed, 245 insertions(+) create mode 100644 html/browsers/browsing-the-web/back-forward-cache/resources/service-worker.js create mode 100644 html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-claim.https.html create mode 100644 html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-matchall.https.html create mode 100644 html/browsers/browsing-the-web/back-forward-cache/service-worker-unregister.https.html 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 index d61e68a325f036a..836934b7c0673c9 100644 --- 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 @@ -128,3 +128,16 @@ function runEventTest(params, description) { params.expectedEvents); }, description); } + +// Call clients.claim() on the service worker +async function claim(t, worker) { + const channel = new MessageChannel(); + const saw_message = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data, 'PASS', 'Worker call to claim() should fulfill.'); + resolve(); + }); + }); + worker.postMessage({port: channel.port2}, [channel.port2]); + await saw_message; +} diff --git a/html/browsers/browsing-the-web/back-forward-cache/resources/service-worker.js b/html/browsers/browsing-the-web/back-forward-cache/resources/service-worker.js new file mode 100644 index 000000000000000..60dceb0a6a6f5f2 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/resources/service-worker.js @@ -0,0 +1,31 @@ +self.addEventListener('message', function(event) { + self.clients.claim() + .then(function(result) { + if (result !== undefined) { + event.data.port.postMessage( + 'FAIL: claim() should be resolved with undefined'); + return; + } + event.data.port.postMessage('PASS'); + }) + .catch(function(error) { + event.data.port.postMessage('FAIL: exception: ' + error.name); + }); + }); + +self.addEventListener('fetch', e => { + if (e.request.url.match(/\/is-controlled/)) { + e.respondWith(new Response('controlled')); + } + else if (e.request.url.match(/\/get-clients-matchall/)) { + const options = { includeUncontrolled: true, type: 'all' }; + e.respondWith( + self.clients.matchAll(options) + .then(clients => { + const client_urls = []; + clients.forEach(client => client_urls.push(client.url)); + return new Response(JSON.stringify(client_urls)); + }) + ); + } + }); diff --git a/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-claim.https.html b/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-claim.https.html new file mode 100644 index 000000000000000..58748ead5f738e7 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-claim.https.html @@ -0,0 +1,86 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/helper.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +promise_test(async t => { + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = location.origin + executorPath + pageA.context_id + '&events=pagehide,pageshow,load'; + const urlB = originCrossSite + executorPath + pageB.context_id; + + window.open(urlA, '_blank', 'noopener'); + + await pageA.execute_script(waitForPageShow); + + await pageA.execute_script( + (url) => { + navigator.serviceWorker.oncontrollerchange = + () => recordEvent('controllerchange'); + }); + + // Register a service worker after `pageA` is loaded to make `pageA` + // uncontrolled at this time. + const workerUrl = 'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)'; + const registration = await service_worker_unregister_and_register(t, workerUrl, './'); + t.add_cleanup(_ => registration.unregister()); + await wait_for_state(t, registration.installing, 'activated'); + + await pageA.execute_script( + (url) => { + prepareNavigation(() => { location.href = url; }); + }, + [urlB]); + + await pageB.execute_script(waitForPageShow); + + // Call clients.claim() when `pageA` is in BFCache. + await claim(t, registration.active); + + const clients1 = await (await fetch('/get-clients-matchall')).json(); + + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + await pageA.execute_script(waitForPageShow); + await assert_bfcached(pageA); + + const clients2 = await (await fetch('/get-clients-matchall')).json(); + assert_equals( + await pageA.execute_script( + () => fetch('/is-controlled?claim-2').then(r => r.text())), + 'controlled', + '2: pageA should be controlled'); + + assert_array_equals( + await pageA.execute_script(() => getRecordedEvents()), + [ + 'window.load', + 'window.pageshow', + 'window.pagehide.persisted', + 'window.pageshow.persisted', + 'controllerchange', + ]); + + // Call clients.claim() again when `pageA` is not in BFCache. + await claim(t, registration.active); + + assert_equals( + await pageA.execute_script( + () => fetch('/is-controlled?claim-3').then(r => r.text())), + 'controlled', + '3: pageA should be controlled'); + const clients3 = await (await fetch('/get-clients-matchall')).json(); + + assert_true(clients1.indexOf(urlA) < 0, '1: page should not be controlled when claim() is called while BFCached'); + assert_true(clients2.indexOf(urlA) >= 0, '2: page should be controlled when claim() is called while BFCached and the page is restored'); + assert_true(clients3.indexOf(urlA) >= 0, '3: page should be controlled when claim()ed after restored'); +}, 'Serviceworker Clients.claim()'); +</script> diff --git a/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-matchall.https.html b/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-matchall.https.html new file mode 100644 index 000000000000000..2e5bb1bac5f68e6 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-matchall.https.html @@ -0,0 +1,63 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/helper.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +promise_test(async t => { + const workerUrl = 'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)'; + const registration = await service_worker_unregister_and_register(t, workerUrl, './'); + t.add_cleanup(_ => registration.unregister()); + await wait_for_state(t, registration.installing, 'activated'); + claim(t, registration.active); + + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = location.origin + executorPath + pageA.context_id; + const urlB = originCrossSite + executorPath + pageB.context_id; + + window.open(urlA, '_blank', 'noopener'); + + await pageA.execute_script(waitForPageShow); + + const clients1 = await (await fetch('/get-clients-matchall')).json(); + const controlled1 = await pageA.execute_script( + () => fetch('/is-controlled?matchall-1').then(r => r.text())); + + await pageA.execute_script( + (url) => prepareNavigation(() => { + location.href = url; + }), + [urlB]); + + await pageB.execute_script(waitForPageShow); + + const clients2 = await (await fetch('/get-clients-matchall')).json(); + + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + + await pageA.execute_script(waitForPageShow); + await assert_bfcached(pageA); + + const clients3 = await (await fetch('/get-clients-matchall')).json(); + const controlled3 = await pageA.execute_script( + () => fetch('/is-controlled?matchall-3').then(r => r.text())); + + assert_true(clients1.indexOf(urlA) >= 0, '1: Before navigation'); + assert_true(clients2.indexOf(urlA) < 0, '2: Before back navigation'); + assert_true(clients3.indexOf(urlA) >= 0, '3: After back navigation'); + + assert_equals(controlled1, 'controlled', + 'pageA should be controlled before BFCached'); + assert_equals(controlled3, 'controlled', + 'pageA should be controlled after restored'); +}, 'Serviceworker Clients.matchAll()'); +</script> diff --git a/html/browsers/browsing-the-web/back-forward-cache/service-worker-unregister.https.html b/html/browsers/browsing-the-web/back-forward-cache/service-worker-unregister.https.html new file mode 100644 index 000000000000000..a704c96c74b9dc0 --- /dev/null +++ b/html/browsers/browsing-the-web/back-forward-cache/service-worker-unregister.https.html @@ -0,0 +1,52 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/helper.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +promise_test(async t => { + const workerUrl = 'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)'; + const registration = await service_worker_unregister_and_register(t, workerUrl, './'); + t.add_cleanup(_ => registration.unregister()); + await wait_for_state(t, registration.installing, 'activated'); + await claim(t, registration.active); + + const pageA = new RemoteContext(token()); + const pageB = new RemoteContext(token()); + + const urlA = location.origin + executorPath + pageA.context_id; + const urlB = originCrossSite + executorPath + pageB.context_id; + + window.open(urlA, '_blank', 'noopener'); + + await pageA.execute_script(waitForPageShow); + await pageA.execute_script( + (url) => prepareNavigation(() => { + location.href = url; + }), + [urlB]); + + await pageB.execute_script(waitForPageShow); + + await registration.unregister(); + + await pageB.execute_script( + () => { + prepareNavigation(() => { history.back(); }); + } + ); + + await pageA.execute_script(waitForPageShow); + await assert_bfcached(pageA); + + assert_equals( + await pageA.execute_script( + () => fetch('/is-controlled').then(r => r.text())), + 'controlled', + 'pageA should be still controlled'); + +}, 'Unregister service worker while a controlled page is in BFCache should not crash'); +</script>