|
| 1 | +<!DOCTYPE html> |
| 2 | +<title>Service Worker: controlling a SharedWorker</title> |
| 3 | +<script src="/resources/testharness.js"></script> |
| 4 | +<script src="/resources/testharnessreport.js"></script> |
| 5 | +<script src="resources/test-helpers.sub.js"></script> |
| 6 | +<body> |
| 7 | +<script> |
| 8 | +// This tests service worker interception for worker clients, when the request |
| 9 | +// for the worker script goes through redirects. For example, a request can go |
| 10 | +// through a chain of URLs like A -> B -> C -> D and each URL might fall in the |
| 11 | +// scope of a different service worker, if any. |
| 12 | +// The two key questions are: |
| 13 | +// 1. Upon a redirect from A -> B, should a service worker for scope B |
| 14 | +// intercept the request? |
| 15 | +// 2. After the final response, which service worker controls the resulting |
| 16 | +// client? |
| 17 | +// |
| 18 | +// The standard prescribes the following: |
| 19 | +// 1. The service worker for scope B intercepts the redirect. *However*, once a |
| 20 | +// request falls back to network (i.e., a service worker did not call |
| 21 | +// respondWith()) and a redirect is then received from network, no service |
| 22 | +// worker should intercept that redirect or any subsequent redirects. |
| 23 | +// 2. The final service worker that got a fetch event (or would have, in the |
| 24 | +// case of a non-fetch-event worker) becomes the controller of the client. |
| 25 | +// |
| 26 | +// The standard may change later, see: |
| 27 | +// https://github.com/w3c/ServiceWorker/issues/1289 |
| 28 | +// |
| 29 | +// The basic test setup is: |
| 30 | +// 1. Page registers service workers for scope1 and scope2. |
| 31 | +// 2. Page requests a worker from scope1. |
| 32 | +// 3. The request is redirected to scope2 or out-of-scope. |
| 33 | +// 4. The worker posts message to the page describing where the final response |
| 34 | +// was served from (service worker or network). |
| 35 | +// 5. The worker does a fetch(), and posts back the response, which describes |
| 36 | +// where the fetch response was served from. |
| 37 | +// |
| 38 | +// Currently this only tests shared worker but dedicated worker tests should be |
| 39 | +// added in a future patch. |
| 40 | + |
| 41 | +// Globals for easier cleanup. |
| 42 | +const scope1 = 'resources/scope1'; |
| 43 | +const scope2 = 'resources/scope2'; |
| 44 | +let frame; |
| 45 | + |
| 46 | +function get_message_from_worker(worker) { |
| 47 | + return new Promise(resolve => { |
| 48 | + worker.port.onmessage = evt => { |
| 49 | + resolve(evt.data); |
| 50 | + } |
| 51 | + }); |
| 52 | +} |
| 53 | + |
| 54 | +async function cleanup() { |
| 55 | + if (frame) |
| 56 | + frame.remove(); |
| 57 | + |
| 58 | + const reg1 = await navigator.serviceWorker.getRegistration(scope1); |
| 59 | + if (reg1) |
| 60 | + await reg1.unregister(); |
| 61 | + const reg2 = await navigator.serviceWorker.getRegistration(scope2); |
| 62 | + if (reg2) |
| 63 | + await reg2.unregister(); |
| 64 | +} |
| 65 | + |
| 66 | +// Builds the worker script URL, which encodes information about where |
| 67 | +// to redirect to. The URL falls in sw1's scope. |
| 68 | +// |
| 69 | +// - |redirector| is "network" or "serviceworker". If "serviceworker", sw1 will |
| 70 | +// respondWith() a redirect. Otherwise, it falls back to network and the server |
| 71 | +// responds with a redirect. |
| 72 | +// - |redirect_location| is "scope2" or "out-of-scope". If "scope2", the |
| 73 | +// redirect ends up in sw2's scope2. Otherwise it's out of scope. |
| 74 | +function build_worker_url(redirector, redirect_location) { |
| 75 | + let redirect_path; |
| 76 | + // Set path to redirect.py, a file on the server that serves |
| 77 | + // a redirect. When sw1 sees this URL, it falls back to network. |
| 78 | + if (redirector == 'network') |
| 79 | + redirector_path = 'redirect.py'; |
| 80 | + // Set path to 'sw-redirect', to tell the service worker |
| 81 | + // to respond with redirect. |
| 82 | + else if (redirector == 'serviceworker') |
| 83 | + redirector_path = 'sw-redirect'; |
| 84 | + |
| 85 | + let redirect_to = base_path() + 'resources/'; |
| 86 | + // Append "scope2/" to redirect_to, so the redirect falls in scope2. |
| 87 | + // Otherwise no change is needed, as the parent "resources/" directory is |
| 88 | + // used, and is out-of-scope. |
| 89 | + if (redirect_location == 'scope2') |
| 90 | + redirect_to += 'scope2/'; |
| 91 | + // Append the name of the file which serves the worker script. |
| 92 | + redirect_to += 'worker_interception_redirect_webworker.py'; |
| 93 | + |
| 94 | + return `scope1/${redirector_path}?Redirect=${redirect_to}` |
| 95 | +} |
| 96 | + |
| 97 | +promise_test(async t => { |
| 98 | + await cleanup(); |
| 99 | + const service_worker = 'resources/worker-interception-redirect-serviceworker.js'; |
| 100 | + const registration1 = await navigator.serviceWorker.register(service_worker, {scope: scope1}); |
| 101 | + await wait_for_state(t, registration1.installing, 'activated'); |
| 102 | + const registration2 = await navigator.serviceWorker.register(service_worker, {scope: scope2}); |
| 103 | + await wait_for_state(t, registration2.installing, 'activated'); |
| 104 | + |
| 105 | + promise_test(t => { |
| 106 | + return cleanup(); |
| 107 | + }, 'cleanup global state'); |
| 108 | +}, 'initialize global state'); |
| 109 | + |
| 110 | +function worker_redirect_test(worker_url, |
| 111 | + expected_main_resource_message, |
| 112 | + expected_subresource_message, |
| 113 | + description) { |
| 114 | + promise_test(async t => { |
| 115 | + // Create a frame to load the worker from. This way we can remove the frame |
| 116 | + // to destroy the worker client when the test is done. |
| 117 | + frame = await with_iframe('resources/blank.html'); |
| 118 | + t.add_cleanup(() => { frame.remove(); }); |
| 119 | + |
| 120 | + // Start the worker. |
| 121 | + const w = new frame.contentWindow.SharedWorker(worker_url); |
| 122 | + w.port.start(); |
| 123 | + |
| 124 | + // Expect a message from the worker indicating which service worker |
| 125 | + // provided the response for the worker script request, if any. |
| 126 | + const data = await get_message_from_worker(w); |
| 127 | + assert_equals(data, expected_main_resource_message); |
| 128 | + |
| 129 | + // The worker does a fetch() after it starts up. Expect a message from the |
| 130 | + // worker indicating which service worker provided the response for the |
| 131 | + // fetch(), if any. |
| 132 | + // |
| 133 | + // Note: for some reason, Firefox would pass all these tests if a |
| 134 | + // postMessage ping/pong step is added before the fetch(). I.e., if the |
| 135 | + // page does postMessage() and the worker does fetch() in response to the |
| 136 | + // ping, the fetch() is properly intercepted. See |
| 137 | + // https://bugzilla.mozilla.org/show_bug.cgi?id=1452528. (Chrome can't pass |
| 138 | + // the tests either way.) |
| 139 | + const message = get_message_from_worker(w); |
| 140 | + const data2 = await message; |
| 141 | + assert_equals(data2, expected_subresource_message); |
| 142 | + }, description); |
| 143 | +} |
| 144 | + |
| 145 | +worker_redirect_test( |
| 146 | + build_worker_url('network', 'scope2'), |
| 147 | + 'the shared worker script was served from network', |
| 148 | + 'fetch(): sw1 saw the fetch from the worker', |
| 149 | + 'request to sw1 scope gets network redirect to sw2 scope'); |
| 150 | + |
| 151 | +worker_redirect_test( |
| 152 | + build_worker_url('network', 'out-scope'), |
| 153 | + 'the shared worker script was served from network', |
| 154 | + 'fetch(): sw1 saw the fetch from the worker', |
| 155 | + 'request to sw1 scope gets network redirect to out-of-scope'); |
| 156 | + |
| 157 | +worker_redirect_test( |
| 158 | + build_worker_url('serviceworker', 'scope2'), |
| 159 | + 'sw2 saw the request for the worker script', |
| 160 | + 'fetch(): sw2 saw the fetch from the worker', |
| 161 | + 'request to sw1 scope gets service-worker redirect to sw2 scope'); |
| 162 | + |
| 163 | +worker_redirect_test( |
| 164 | + build_worker_url('serviceworker', 'out-scope'), |
| 165 | + 'the shared worker script was served from network', |
| 166 | + 'fetch(): sw1 saw the fetch from the worker', |
| 167 | + 'request to sw1 scope gets service-worker redirect to out-of-scope'); |
| 168 | +</script> |
| 169 | +</body> |
0 commit comments