Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 4cff3b8

Browse files
mfalkenjgraham
authored andcommitted
Bug 1451994 [wpt PR 10340] - service worker: Add tests for inteception of workers after redirects., a=testonly
Automatic update from web-platform-testsservice worker: Add tests for inteception of workers after redirects. This tests behavior discussed here: w3c/ServiceWorker#1289 Namely it tests when a request for a worker goes through a redirect chain: 1) On redirect from A -> B, whether the service worker at B sees the request. 2) After the final redirect, which service worker controls the resulting client. The tests are written as specified today. Therefore, Firefox passes this test (verified in Nightly) and Chrome does not. (Actually a small change is required to the test to make Firefox pass it, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1452528) Currently it only tests shared worker but dedicated worker can be added in a follow-up patch. Bug: 829720 Change-Id: Id3b1ea8b952760be0ef9917f2c6a3afe60ca1fb5 Reviewed-on: https://chromium-review.googlesource.com/999241 Commit-Queue: Matt Falkenhagen <[email protected]> Reviewed-by: Hiroki Nakagawa <[email protected]> Cr-Commit-Position: refs/heads/master@{#549125} wpt-commits: 6fe36d79072d5261ea504435b0dfedaf39f5805a wpt-pr: 10340 wpt-commits: 6fe36d79072d5261ea504435b0dfedaf39f5805a wpt-pr: 10340
1 parent 02d309b commit 4cff3b8

File tree

7 files changed

+322
-0
lines changed

7 files changed

+322
-0
lines changed

testing/web-platform/meta/MANIFEST.json

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294129,6 +294129,16 @@
294129294129
{}
294130294130
]
294131294131
],
294132+
"service-workers/service-worker/resources/scope1/redirect.py": [
294133+
[
294134+
{}
294135+
]
294136+
],
294137+
"service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py": [
294138+
[
294139+
{}
294140+
]
294141+
],
294132294142
"service-workers/service-worker/resources/service-worker-csp-worker.py": [
294133294143
[
294134294144
{}
@@ -294284,6 +294294,16 @@
294284294294
{}
294285294295
]
294286294296
],
294297+
"service-workers/service-worker/resources/worker-interception-redirect-serviceworker.js": [
294298+
[
294299+
{}
294300+
]
294301+
],
294302+
"service-workers/service-worker/resources/worker-interception-redirect-webworker.js": [
294303+
[
294304+
{}
294305+
]
294306+
],
294287294307
"service-workers/service-worker/resources/worker-load-interceptor.js": [
294288294308
[
294289294309
{}
@@ -294294,6 +294314,11 @@
294294294314
{}
294295294315
]
294296294316
],
294317+
"service-workers/service-worker/resources/worker_interception_redirect_webworker.py": [
294318+
[
294319+
{}
294320+
]
294321+
],
294297294322
"service-workers/specgen.json": [
294298294323
[
294299294324
{}
@@ -363277,6 +363302,12 @@
363277363302
{}
363278363303
]
363279363304
],
363305+
"service-workers/service-worker/worker-interception-redirect.https.html": [
363306+
[
363307+
"/service-workers/service-worker/worker-interception-redirect.https.html",
363308+
{}
363309+
]
363310+
],
363280363311
"service-workers/service-worker/worker-interception.https.html": [
363281363312
[
363282363313
"/service-workers/service-worker/worker-interception.https.html",
@@ -597238,6 +597269,14 @@
597238597269
"b935f6d8fea362c4aceb713fcde91343d3e4bfa7",
597239597270
"support"
597240597271
],
597272+
"service-workers/service-worker/resources/scope1/redirect.py": [
597273+
"7741f204c67ca71087df3b182826bc28c14dbe37",
597274+
"support"
597275+
],
597276+
"service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py": [
597277+
"c482ed2a7ff5ed2d663bd54ee2ecd2f6609d77cd",
597278+
"support"
597279+
],
597241597280
"service-workers/service-worker/resources/service-worker-csp-worker.py": [
597242597281
"f7c6bb3ba222dc35a09ef806a7c6d145339f9eb2",
597243597282
"support"
@@ -597362,6 +597401,14 @@
597362597401
"b9fd4ea281b328c1f21573d1563c968dac52cf22",
597363597402
"support"
597364597403
],
597404+
"service-workers/service-worker/resources/worker-interception-redirect-serviceworker.js": [
597405+
"05a2f3a4ad034b9cece10b5abc7ffff71bfcb4aa",
597406+
"support"
597407+
],
597408+
"service-workers/service-worker/resources/worker-interception-redirect-webworker.js": [
597409+
"615f6dd41ee6f9f141132a48544f976ce0403d27",
597410+
"support"
597411+
],
597365597412
"service-workers/service-worker/resources/worker-load-interceptor.js": [
597366597413
"de9cfcbef6528a4b5e6b2552f260501e4d165ea4",
597367597414
"support"
@@ -597370,6 +597417,10 @@
597370597417
"8624b498e37d44bf3a3a74ccd46f928f7b0338cb",
597371597418
"support"
597372597419
],
597420+
"service-workers/service-worker/resources/worker_interception_redirect_webworker.py": [
597421+
"37a8a7bd94cc529420eba8838e1e20056caf2945",
597422+
"support"
597423+
],
597373597424
"service-workers/service-worker/respond-with-body-accessed-response.https.html": [
597374597425
"e2e9b5d70f141e71f3617a11a336affea66662db",
597375597426
"testharness"
@@ -597506,6 +597557,10 @@
597506597557
"c9cff3f5fb5c205020ad39df6d22ad19ce0cbb0a",
597507597558
"testharness"
597508597559
],
597560+
"service-workers/service-worker/worker-interception-redirect.https.html": [
597561+
"8654d94e9797ac332bedcadeed61837ccf13b944",
597562+
"testharness"
597563+
],
597509597564
"service-workers/service-worker/worker-interception.https.html": [
597510597565
"41f7e5e8da2c305370fddad83518cd0fa57547eb",
597511597566
"testharness"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import os
2+
import sys
3+
# Use the file from the parent directory.
4+
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
5+
from redirect import main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import os
2+
import sys
3+
# Use the file from the parent directory.
4+
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
5+
from worker_interception_redirect_webworker import main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
let name;
2+
if (self.registration.scope.indexOf('scope1') != -1)
3+
name = 'sw1';
4+
if (self.registration.scope.indexOf('scope2') != -1)
5+
name = 'sw2';
6+
7+
8+
self.addEventListener('fetch', evt => {
9+
// There are three types of requests this service worker handles.
10+
11+
// (1) The first request for the worker, which will redirect elsewhere.
12+
// "redirect.py" means to test network redirect, so let network handle it.
13+
if (evt.request.url.indexOf('redirect.py') != -1) {
14+
return;
15+
}
16+
// "sw-redirect" means to test service worker redirect, so respond with a
17+
// redirect.
18+
if (evt.request.url.indexOf('sw-redirect') != -1) {
19+
const url = new URL(evt.request.url);
20+
const redirect_to = url.searchParams.get('Redirect');
21+
evt.respondWith(Response.redirect(redirect_to));
22+
return;
23+
}
24+
25+
// (2) After redirect, the request is for a "webworker.py" URL.
26+
// Add a search parameter to indicate this service worker handled the
27+
// final request for the worker.
28+
if (evt.request.url.indexOf('webworker.py') != -1) {
29+
const greeting = encodeURIComponent(`${name} saw the request for the worker script`);
30+
evt.respondWith(fetch(`worker_interception_redirect_webworker.py?greeting=${greeting}`));
31+
return;
32+
}
33+
34+
// (3) The worker does a fetch() to simple.txt. Indicate that this service
35+
// worker handled the request.
36+
if (evt.request.url.indexOf('simple.txt') != -1) {
37+
evt.respondWith(new Response(`${name} saw the fetch from the worker`));
38+
}
39+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This is the (shared or dedicated) worker file for the
2+
// worker-interception-redirect test. It should be served by the corresponding
3+
// .py file instead of being served directly.
4+
//
5+
// This file is served from both resources/*webworker.py and
6+
// resources/scope2/*webworker.py, hence some of the complexity
7+
// below about paths.
8+
const resources_url = new URL("/service-workers/service-worker/resources/",
9+
self.location);
10+
11+
// This greeting text is meant to be injected by the Python script that serves
12+
// this file, to indicate how the script was served (from network or from
13+
// service worker).
14+
//
15+
// We can't just use a sub pipe and name this file .sub.js since we want
16+
// to serve the file from multiple URLs (see above).
17+
let greeting = '%GREETING_TEXT%';
18+
if (!greeting)
19+
greeting = 'the shared worker script was served from network';
20+
21+
self.onconnect = async function(e) {
22+
const port = e.ports[0];
23+
port.start();
24+
port.postMessage(greeting);
25+
26+
const fetch_url = new URL('simple.txt', resources_url);
27+
const response = await fetch(fetch_url);
28+
const text = await response.text();
29+
port.postMessage('fetch(): ' + text);
30+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This serves the worker JavaScript file. It takes a |greeting| request
2+
# parameter to inject into the JavaScript to indicate how the request
3+
# reached the server.
4+
import os
5+
import sys
6+
7+
def main(request, response):
8+
path = os.path.join(os.path.dirname(__file__),
9+
"worker-interception-redirect-webworker.js")
10+
body = open(path, "rb").read()
11+
if "greeting" in request.GET:
12+
body = body.replace("%GREETING_TEXT%", request.GET["greeting"])
13+
else:
14+
body = body.replace("%GREETING_TEXT%", "")
15+
16+
headers = []
17+
headers.append(("Content-Type", "text/javascript"))
18+
19+
return headers, body
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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

Comments
 (0)