diff options
Diffstat (limited to 'testing/web-platform/tests/service-workers/service-worker/worker-interception-redirect.https.html')
-rw-r--r-- | testing/web-platform/tests/service-workers/service-worker/worker-interception-redirect.https.html | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/testing/web-platform/tests/service-workers/service-worker/worker-interception-redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/worker-interception-redirect.https.html new file mode 100644 index 0000000000..8d566b9c15 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/worker-interception-redirect.https.html @@ -0,0 +1,212 @@ +<!DOCTYPE html> +<title>Service Worker: controlling Worker/SharedWorker</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +// This tests service worker interception for worker clients, when the request +// for the worker script goes through redirects. For example, a request can go +// through a chain of URLs like A -> B -> C -> D and each URL might fall in the +// scope of a different service worker, if any. +// The two key questions are: +// 1. Upon a redirect from A -> B, should a service worker for scope B +// intercept the request? +// 2. After the final response, which service worker controls the resulting +// client? +// +// The standard prescribes the following: +// 1. The service worker for scope B intercepts the redirect. *However*, once a +// request falls back to network (i.e., a service worker did not call +// respondWith()) and a redirect is then received from network, no service +// worker should intercept that redirect or any subsequent redirects. +// 2. The final service worker that got a fetch event (or would have, in the +// case of a non-fetch-event worker) becomes the controller of the client. +// +// The standard may change later, see: +// https://github.com/w3c/ServiceWorker/issues/1289 +// +// The basic test setup is: +// 1. Page registers service workers for scope1 and scope2. +// 2. Page requests a worker from scope1. +// 3. The request is redirected to scope2 or out-of-scope. +// 4. The worker posts message to the page describing where the final response +// was served from (service worker or network). +// 5. The worker does an importScripts() and fetch(), and posts back the +// responses, which describe where the responses where served from. + +// Globals for easier cleanup. +const scope1 = 'resources/scope1'; +const scope2 = 'resources/scope2'; +let frame; + +function get_message_from_worker(port) { + return new Promise(resolve => { + port.onmessage = evt => { + resolve(evt.data); + } + }); +} + +async function cleanup() { + if (frame) + frame.remove(); + + const reg1 = await navigator.serviceWorker.getRegistration(scope1); + if (reg1) + await reg1.unregister(); + const reg2 = await navigator.serviceWorker.getRegistration(scope2); + if (reg2) + await reg2.unregister(); +} + +// Builds the worker script URL, which encodes information about where +// to redirect to. The URL falls in sw1's scope. +// +// - |redirector| is "network" or "serviceworker". If "serviceworker", sw1 will +// respondWith() a redirect. Otherwise, it falls back to network and the server +// responds with a redirect. +// - |redirect_location| is "scope2" or "out-of-scope". If "scope2", the +// redirect ends up in sw2's scope2. Otherwise it's out of scope. +function build_worker_url(redirector, redirect_location) { + let redirect_path; + // Set path to redirect.py, a file on the server that serves + // a redirect. When sw1 sees this URL, it falls back to network. + if (redirector == 'network') + redirector_path = 'redirect.py'; + // Set path to 'sw-redirect', to tell the service worker + // to respond with redirect. + else if (redirector == 'serviceworker') + redirector_path = 'sw-redirect'; + + let redirect_to = base_path() + 'resources/'; + // Append "scope2/" to redirect_to, so the redirect falls in scope2. + // Otherwise no change is needed, as the parent "resources/" directory is + // used, and is out-of-scope. + if (redirect_location == 'scope2') + redirect_to += 'scope2/'; + // Append the name of the file which serves the worker script. + redirect_to += 'worker_interception_redirect_webworker.py'; + + return `scope1/${redirector_path}?Redirect=${redirect_to}` +} + +promise_test(async t => { + await cleanup(); + const service_worker = 'resources/worker-interception-redirect-serviceworker.js'; + const registration1 = await navigator.serviceWorker.register(service_worker, {scope: scope1}); + await wait_for_state(t, registration1.installing, 'activated'); + const registration2 = await navigator.serviceWorker.register(service_worker, {scope: scope2}); + await wait_for_state(t, registration2.installing, 'activated'); + + promise_test(t => { + return cleanup(); + }, 'cleanup global state'); +}, 'initialize global state'); + +async function worker_redirect_test(worker_request_url, + worker_expected_url, + expected_main_resource_message, + expected_import_scripts_message, + expected_fetch_message, + description) { + for (const workerType of ['DedicatedWorker', 'SharedWorker']) { + for (const type of ['classic', 'module']) { + promise_test(async t => { + // Create a frame to load the worker from. This way we can remove the + // frame to destroy the worker client when the test is done. + frame = await with_iframe('resources/blank.html'); + t.add_cleanup(() => { frame.remove(); }); + + // Start the worker. + let w; + let port; + if (workerType === 'DedicatedWorker') { + w = new frame.contentWindow.Worker(worker_request_url, {type}); + port = w; + } else { + w = new frame.contentWindow.SharedWorker(worker_request_url, {type}); + port = w.port; + w.port.start(); + } + w.onerror = t.unreached_func('Worker error'); + + // Expect a message from the worker indicating which service worker + // provided the response for the worker script request, if any. + const data = await get_message_from_worker(port); + + // The worker does an importScripts(). Expect a message from the worker + // indicating which service worker provided the response for the + // importScripts(), if any. + const import_scripts_message = await get_message_from_worker(port); + test(() => { + if (type === 'classic') { + assert_equals(import_scripts_message, + expected_import_scripts_message); + } else { + assert_equals(import_scripts_message, 'importScripts failed'); + } + }, `${description} (${type} ${workerType}, importScripts())`); + + // The worker does a fetch(). Expect a message from the worker + // indicating which service worker provided the response for the + // fetch(), if any. + const fetch_message = await get_message_from_worker(port); + test(() => { + assert_equals(fetch_message, expected_fetch_message); + }, `${description} (${type} ${workerType}, fetch())`); + + // Expect a message from the worker indicating |self.location|. + const worker_actual_url = await get_message_from_worker(port); + test(() => { + assert_equals( + worker_actual_url, + (new URL(worker_expected_url, location.href)).toString(), + 'location.href'); + }, `${description} (${type} ${workerType}, location.href)`); + + assert_equals(data, expected_main_resource_message); + + }, `${description} (${type} ${workerType})`); + } + } +} + +// request to sw1 scope gets network redirect to sw2 scope +worker_redirect_test( + build_worker_url('network', 'scope2'), + 'resources/scope2/worker_interception_redirect_webworker.py', + 'the worker script was served from network', + 'sw1 saw importScripts from the worker: /service-workers/service-worker/resources/scope2/import-scripts-echo.py', + 'fetch(): sw1 saw the fetch from the worker: /service-workers/service-worker/resources/scope2/simple.txt', + 'Case #1: network scope1->scope2'); + +// request to sw1 scope gets network redirect to out-of-scope +worker_redirect_test( + build_worker_url('network', 'out-scope'), + 'resources/worker_interception_redirect_webworker.py', + 'the worker script was served from network', + 'sw1 saw importScripts from the worker: /service-workers/service-worker/resources/import-scripts-echo.py', + 'fetch(): sw1 saw the fetch from the worker: /service-workers/service-worker/resources/simple.txt', + 'Case #2: network scope1->out-scope'); + +// request to sw1 scope gets service-worker redirect to sw2 scope +worker_redirect_test( + build_worker_url('serviceworker', 'scope2'), + 'resources/subdir/worker_interception_redirect_webworker.py?greeting=sw2%20saw%20the%20request%20for%20the%20worker%20script', + 'sw2 saw the request for the worker script', + 'sw2 saw importScripts from the worker: /service-workers/service-worker/resources/subdir/import-scripts-echo.py', + 'fetch(): sw2 saw the fetch from the worker: /service-workers/service-worker/resources/subdir/simple.txt', + 'Case #3: sw scope1->scope2'); + +// request to sw1 scope gets service-worker redirect to out-of-scope +worker_redirect_test( + build_worker_url('serviceworker', 'out-scope'), + 'resources/worker_interception_redirect_webworker.py', + 'the worker script was served from network', + 'sw1 saw importScripts from the worker: /service-workers/service-worker/resources/import-scripts-echo.py', + 'fetch(): sw1 saw the fetch from the worker: /service-workers/service-worker/resources/simple.txt', + 'Case #4: sw scope1->out-scope'); +</script> +</body> |