summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html')
-rw-r--r--testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html846
1 files changed, 846 insertions, 0 deletions
diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html
new file mode 100644
index 0000000000..a87de56931
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html
@@ -0,0 +1,846 @@
+<!DOCTYPE html>
+<title>Service Worker: Navigation redirection</title>
+<meta name="timeout" content="long">
+<!-- default variant tests document.location and intercepted URLs -->
+<meta name="variant" content="?default">
+<!-- client variant tests the Clients API (resultingClientId and Client.url) -->
+<meta name="variant" content="?client">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+const host_info = get_host_info();
+
+// This test registers three Service Workers at SCOPE1, SCOPE2 and
+// OTHER_ORIGIN_SCOPE. And checks the redirected page's URL and the requests
+// which are intercepted by Service Worker while loading redirect page.
+const BASE_URL = host_info['HTTPS_ORIGIN'] + base_path();
+const OTHER_BASE_URL = host_info['HTTPS_REMOTE_ORIGIN'] + base_path();
+
+const SCOPE1 = BASE_URL + 'resources/navigation-redirect-scope1.py?';
+const SCOPE2 = BASE_URL + 'resources/navigation-redirect-scope2.py?';
+const OUT_SCOPE = BASE_URL + 'resources/navigation-redirect-out-scope.py?';
+const SCRIPT = 'resources/redirect-worker.js';
+
+const OTHER_ORIGIN_IFRAME_URL =
+ OTHER_BASE_URL + 'resources/navigation-redirect-other-origin.html';
+const OTHER_ORIGIN_SCOPE =
+ OTHER_BASE_URL + 'resources/navigation-redirect-scope1.py?';
+const OTHER_ORIGIN_OUT_SCOPE =
+ OTHER_BASE_URL + 'resources/navigation-redirect-out-scope.py?';
+
+let registrations;
+let workers;
+let other_origin_frame;
+let message_resolvers = {};
+let next_message_id = 0;
+
+promise_test(async t => {
+ // In this frame we register a service worker at OTHER_ORIGIN_SCOPE.
+ // And will use this frame to communicate with the worker.
+ other_origin_frame = await with_iframe(OTHER_ORIGIN_IFRAME_URL);
+
+ // Register same-origin service workers.
+ registrations = await Promise.all([
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE1),
+ service_worker_unregister_and_register(t, SCRIPT, SCOPE2)]);
+
+ // Wait for all workers to activate.
+ workers = registrations.map(get_effective_worker);
+ return Promise.all([
+ wait_for_state(t, workers[0], 'activated'),
+ wait_for_state(t, workers[1], 'activated'),
+ // This promise will resolve when |wait_for_worker_promise|
+ // in OTHER_ORIGIN_IFRAME_URL resolves.
+ send_to_iframe(other_origin_frame, {command: 'wait_for_worker'})]);
+}, 'initialize global state');
+
+function get_effective_worker(registration) {
+ if (registration.active)
+ return registration.active;
+ if (registration.waiting)
+ return registration.waiting;
+ if (registration.installing)
+ return registration.installing;
+}
+
+async function check_all_intercepted_urls(expected_urls) {
+ const urls = [];
+ urls.push(await get_intercepted_urls(workers[0]));
+ urls.push(await get_intercepted_urls(workers[1]));
+ // Gets the request URLs which are intercepted by OTHER_ORIGIN_SCOPE's
+ // SW. This promise will resolve when get_request_infos() in
+ // OTHER_ORIGIN_IFRAME_URL resolves.
+ const request_infos = await send_to_iframe(other_origin_frame,
+ {command: 'get_request_infos'});
+ urls.push(request_infos.map(info => { return info.url; }));
+
+ assert_object_equals(urls, expected_urls, 'Intercepted URLs should match.');
+}
+
+// Checks |clients| returned from a worker. Only the client matching
+// |expected_final_client_tag| should be found. Returns true if a client was
+// found. Note that the final client is not necessarily found by this worker,
+// if the client is cross-origin.
+//
+// |clients| is an object like:
+// {x: {found: true, id: id1, url: url1}, b: {found: false}}
+function check_clients(clients,
+ expected_id,
+ expected_url,
+ expected_final_client_tag,
+ worker_name) {
+ let found = false;
+ Object.keys(clients).forEach(key => {
+ const info = clients[key];
+ if (info.found) {
+ assert_true(!!expected_final_client_tag,
+ `${worker_name} client tag exists`);
+ assert_equals(key, expected_final_client_tag,
+ `${worker_name} client tag matches`);
+ assert_equals(info.id, expected_id, `${worker_name} client id`);
+ assert_equals(info.url, expected_url, `${worker_name} client url`);
+ found = true;
+ }
+ });
+ return found;
+}
+
+function check_resulting_client_ids(infos, expected_infos, actual_ids, worker) {
+ assert_equals(infos.length, expected_infos.length,
+ `request length for ${worker}`);
+ for (var i = 0; i < infos.length; i++) {
+ const tag = expected_infos[i].resultingClientIdTag;
+ const url = expected_infos[i].url;
+ const actual_id = infos[i].resultingClientId;
+ const expected_id = actual_ids[tag];
+ assert_equals(typeof(actual_id), 'string',
+ `resultingClientId for ${url} request to ${worker}`);
+ if (expected_id) {
+ assert_equals(actual_id, expected_id,
+ `resultingClientId for ${url} request to ${worker}`);
+ } else {
+ actual_ids[tag] = actual_id;
+ }
+ }
+}
+
+// Creates an iframe and navigates to |url|, which is expected to start a chain
+// of redirects.
+// - |expected_last_url| is the expected window.location after the
+// navigation.
+//
+// - |expected_request_infos| is the expected requests that the service workers
+// were dispatched fetch events for. The format is:
+// [
+// [
+// // Requests received by workers[0].
+// {url: url1, resultingClientIdTag: 'a'},
+// {url: url2, resultingClientIdTag: 'a'}
+// ],
+// [
+// // Requests received by workers[1].
+// {url: url3, resultingClientIdTag: 'a'}
+// ],
+// [
+// // Requests received by the cross-origin worker.
+// {url: url4, resultingClientIdTag: 'x'}
+// {url: url5, resultingClientIdTag: 'x'}
+// ]
+// ]
+// Here, |url| is |event.request.url| and |resultingClientIdTag| represents
+// |event.resultingClientId|. Since the actual client ids are not known
+// beforehand, the expectation isn't the literal expected value, but all equal
+// tags must map to the same actual id.
+//
+// - |expected_final_client_tag| is the resultingClientIdTag that is
+// expected to map to the created client's id. This is null if there
+// is no such tag, which can happen when the final request was a cross-origin
+// redirect to out-scope, so no worker received a fetch event whose
+// resultingClientId is the id of the resulting client.
+//
+// In the example above:
+// - workers[0] receives two requests with the same resultingClientId.
+// - workers[1] receives one request also with that resultingClientId.
+// - The cross-origin worker receives two requests with the same
+// resultingClientId which differs from the previous one.
+// - Assuming |expected_final_client_tag| is 'x', then the created
+// client has the id seen by the cross-origin worker above.
+function redirect_test(url,
+ expected_last_url,
+ expected_request_infos,
+ expected_final_client_tag,
+ test_name) {
+ promise_test(async t => {
+ const frame = await with_iframe(url);
+ t.add_cleanup(() => { frame.remove(); });
+
+ // Switch on variant.
+ if (document.location.search == '?client') {
+ return client_variant_test(url, expected_last_url, expected_request_infos,
+ expected_final_client_tag, test_name);
+ }
+
+ return default_variant_test(url, expected_last_url, expected_request_infos,
+ frame, test_name);
+ }, test_name);
+}
+
+// The default variant tests the request interception chain and
+// resulting document.location.
+async function default_variant_test(url,
+ expected_last_url,
+ expected_request_infos,
+ frame,
+ test_name) {
+ const expected_intercepted_urls = expected_request_infos.map(
+ requests_for_worker => {
+ return requests_for_worker.map(info => {
+ return info.url;
+ });
+ });
+ await check_all_intercepted_urls(expected_intercepted_urls);
+ const last_url = await send_to_iframe(frame, 'getLocation');
+ assert_equals(last_url, expected_last_url, 'Last URL should match.');
+}
+
+// The "client" variant tests the Clients API using resultingClientId.
+async function client_variant_test(url,
+ expected_last_url,
+ expected_request_infos,
+ expected_final_client_tag,
+ test_name) {
+ // Request infos is an array like:
+ // [
+ // [{url: url1, resultingClientIdTag: tag1}],
+ // [{url: url2, resultingClientIdTag: tag2}],
+ // [{url: url3: resultingClientIdTag: tag3}]
+ // ]
+ const requestInfos = await get_all_request_infos();
+
+ // We check the actual infos against the expected ones, and learn the
+ // actual ids as we go.
+ const actual_ids = {};
+ check_resulting_client_ids(requestInfos[0],
+ expected_request_infos[0],
+ actual_ids,
+ 'worker0');
+ check_resulting_client_ids(requestInfos[1],
+ expected_request_infos[1],
+ actual_ids,
+ 'worker1');
+ check_resulting_client_ids(requestInfos[2],
+ expected_request_infos[2],
+ actual_ids,
+ 'crossOriginWorker');
+
+ // Now |actual_ids| maps tag to actual id:
+ // {x: id1, b: id2, c: id3}
+ // Ask each worker to try to resolve the actual ids to clients.
+ // Only |expected_final_client_tag| should resolve to a client.
+ const client_infos = await get_all_clients(actual_ids);
+
+ // Client infos is an object like:
+ // {
+ // worker0: {x: {found: true, id: id1, url: url1}, b: {found: false}},
+ // worker1: {x: {found: true, id: id1, url: url1}},
+ // crossOriginWorker: {x: {found: false}}, {b: {found: false}}
+ // }
+ //
+ // Now check each client info. check_clients() verifies each info: only
+ // |expected_final_client_tag| should ever be found and the found client
+ // should have the expected url and id. A wrinkle is that not all workers
+ // will find the client, if they are cross-origin to the client. This
+ // means check_clients() trivially passes if no clients are found. So
+ // additionally check that at least one worker found the client (|found|),
+ // if that was expected (|expect_found|).
+ let found = false;
+ const expect_found = !!expected_final_client_tag;
+ const expected_id = actual_ids[expected_final_client_tag];
+ found = check_clients(client_infos.worker0,
+ expected_id,
+ expected_last_url,
+ expected_final_client_tag,
+ 'worker0');
+ found = check_clients(client_infos.worker1,
+ expected_id,
+ expected_last_url,
+ expected_final_client_tag,
+ 'worker1') || found;
+ found = check_clients(client_infos.crossOriginWorker,
+ expected_id,
+ expected_last_url,
+ expected_final_client_tag,
+ 'crossOriginWorker') || found;
+ assert_equals(found, expect_found, 'client found');
+
+ if (!expect_found) {
+ // TODO(falken): Ask the other origin frame if it has a client of the
+ // expected URL.
+ }
+}
+
+window.addEventListener('message', on_message, false);
+
+function on_message(e) {
+ if (e.origin != host_info['HTTPS_REMOTE_ORIGIN'] &&
+ e.origin != host_info['HTTPS_ORIGIN'] ) {
+ console.error('invalid origin: ' + e.origin);
+ return;
+ }
+ var resolve = message_resolvers[e.data.id];
+ delete message_resolvers[e.data.id];
+ resolve(e.data.result);
+}
+
+function send_to_iframe(frame, message) {
+ var message_id = next_message_id++;
+ return new Promise(resolve => {
+ message_resolvers[message_id] = resolve;
+ frame.contentWindow.postMessage(
+ {id: message_id, message},
+ '*');
+ });
+}
+
+async function get_all_clients(actual_ids) {
+ const client_infos = {};
+ client_infos['worker0'] = await get_clients(workers[0], actual_ids);
+ client_infos['worker1'] = await get_clients(workers[1], actual_ids);
+ client_infos['crossOriginWorker'] =
+ await send_to_iframe(other_origin_frame,
+ {command: 'get_clients', actual_ids});
+ return client_infos;
+}
+
+function get_clients(worker, actual_ids) {
+ return new Promise(resolve => {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = (msg) => {
+ resolve(msg.data.clients);
+ };
+ worker.postMessage({command: 'getClients', actual_ids, port: channel.port2},
+ [channel.port2]);
+ });
+}
+
+// Returns an array of the URLs that |worker| received fetch events for:
+// [url1, url2]
+async function get_intercepted_urls(worker) {
+ const infos = await get_request_infos(worker);
+ return infos.map(info => { return info.url; });
+}
+
+// Returns the requests that |worker| received fetch events for. The return
+// value is an array of format:
+// [
+// {url: url1, resultingClientId: id},
+// {url: url2, resultingClientId: id}
+// ]
+function get_request_infos(worker) {
+ return new Promise(resolve => {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = (msg) => {
+ resolve(msg.data.requestInfos);
+ };
+ worker.postMessage({command: 'getRequestInfos', port: channel.port2},
+ [channel.port2]);
+ });
+}
+
+// Returns an array of the requests the workers received fetch events for:
+// [
+// // Requests from workers[0].
+// [
+// {url: url1, resultingClientIdTag: tag1},
+// {url: url2, resultingClientIdTag: tag1}
+// ],
+//
+// // Requests from workers[1].
+// [{url: url3, resultingClientIdTag: tag2}],
+//
+// // Requests from the cross-origin worker.
+// []
+// ]
+async function get_all_request_infos() {
+ const request_infos = [];
+ request_infos.push(await get_request_infos(workers[0]));
+ request_infos.push(await get_request_infos(workers[1]));
+ request_infos.push(await send_to_iframe(other_origin_frame,
+ {command: 'get_request_infos'}));
+ return request_infos;
+}
+
+let url;
+let url1;
+let url2;
+
+// Normal redirect (from out-scope to in-scope).
+url = SCOPE1;
+redirect_test(
+ OUT_SCOPE + 'url=' + encodeURIComponent(url),
+ url,
+ [[{url, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'Normal redirect to same-origin scope.');
+
+
+url = SCOPE1 + '#ref';
+redirect_test(
+ OUT_SCOPE + 'url=' + encodeURIComponent(SCOPE1) + '#ref',
+ url,
+ [[{url, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'Normal redirect to same-origin scope with a hash fragment.');
+
+url = SCOPE1 + '#ref2';
+redirect_test(
+ OUT_SCOPE + 'url=' + encodeURIComponent(url) + '#ref',
+ url,
+ [[{url, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'Normal redirect to same-origin scope with different hash fragments.');
+
+url = OTHER_ORIGIN_SCOPE;
+redirect_test(
+ OUT_SCOPE + 'url=' + encodeURIComponent(url),
+ url,
+ [[], [], [{url, resultingClientIdTag: 'x'}]],
+ 'x',
+ 'Normal redirect to other-origin scope.');
+
+// SW fallbacked redirect. SW doesn't handle the fetch request.
+url = SCOPE1 + 'url=' + encodeURIComponent(OUT_SCOPE);
+redirect_test(
+ url,
+ OUT_SCOPE,
+ [[{url, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'SW-fallbacked redirect to same-origin out-scope.');
+
+url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1);
+url2 = SCOPE1;
+redirect_test(
+ url1,
+ url2,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'x'},
+ {url: url2, resultingClientIdTag: 'x'}
+ ],
+ [],
+ []
+ ],
+ 'x',
+ 'SW-fallbacked redirect to same-origin same-scope.');
+
+url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1) + '#ref';
+url2 = SCOPE1 + '#ref';
+redirect_test(
+ url1,
+ url2,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'x'},
+ {url: url2, resultingClientIdTag: 'x'}
+ ],
+ [],
+ []
+ ],
+ 'x',
+ 'SW-fallbacked redirect to same-origin same-scope with a hash fragment.');
+
+url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1 + '#ref2') + '#ref';
+url2 = SCOPE1 + '#ref2';
+redirect_test(
+ url1,
+ url2,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'x'},
+ {url: url2, resultingClientIdTag: 'x'}
+ ],
+ [],
+ []
+ ],
+ 'x',
+ 'SW-fallbacked redirect to same-origin same-scope with different hash ' +
+ 'fragments.');
+
+url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE2);
+url2 = SCOPE2;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'x'}],
+ [{url: url2, resultingClientIdTag: 'x'}],
+ []
+ ],
+ 'x',
+ 'SW-fallbacked redirect to same-origin other-scope.');
+
+url1 = SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
+url2 = OTHER_ORIGIN_OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+ null,
+ 'SW-fallbacked redirect to other-origin out-scope.');
+
+url1 = SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
+url2 = OTHER_ORIGIN_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'a'}],
+ [],
+ [{url: url2, resultingClientIdTag: 'x'}]
+ ],
+ 'x',
+ 'SW-fallbacked redirect to other-origin in-scope.');
+
+
+url3 = SCOPE1;
+url2 = OTHER_ORIGIN_SCOPE + 'url=' + encodeURIComponent(url3);
+url1 = SCOPE1 + 'url=' + encodeURIComponent(url2);
+redirect_test(
+ url1,
+ url3,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'a'},
+ {url: url3, resultingClientIdTag: 'x'}
+ ],
+ [],
+ [{url: url2, resultingClientIdTag: 'b'}]
+ ],
+ 'x',
+ 'SW-fallbacked redirect to other-origin and back to same-origin.');
+
+// SW generated redirect.
+// SW: event.respondWith(Response.redirect(params['url']));
+url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE);
+url2 = OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'SW-generated redirect to same-origin out-scope.');
+
+url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE) + '#ref';
+url2 = OUT_SCOPE + '#ref';
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'SW-generated redirect to same-origin out-scope with a hash fragment.');
+
+url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE + '#ref2') + '#ref';
+url2 = OUT_SCOPE + '#ref2';
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'SW-generated redirect to same-origin out-scope with different hash ' +
+ 'fragments.');
+
+url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE1);
+url2 = SCOPE1;
+redirect_test(
+ url1,
+ url2,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'x'},
+ {url: url2, resultingClientIdTag: 'x'}
+ ],
+ [],
+ []
+ ],
+ 'x',
+ 'SW-generated redirect to same-origin same-scope.');
+
+url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE2);
+url2 = SCOPE2;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'x'}],
+ [{url: url2, resultingClientIdTag: 'x'}],
+ []
+ ],
+ 'x',
+ 'SW-generated redirect to same-origin other-scope.');
+
+url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
+url2 = OTHER_ORIGIN_OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+ null,
+ 'SW-generated redirect to other-origin out-scope.');
+
+url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
+url2 = OTHER_ORIGIN_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'a'}],
+ [],
+ [{url: url2, resultingClientIdTag: 'x'}]
+ ],
+ 'x',
+ 'SW-generated redirect to other-origin in-scope.');
+
+
+// SW fetched redirect.
+// SW: event.respondWith(fetch(event.request));
+url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OUT_SCOPE)
+url2 = OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'SW-fetched redirect to same-origin out-scope.');
+
+url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE1);
+url2 = SCOPE1;
+redirect_test(
+ url1,
+ url2,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'x'},
+ {url: url2, resultingClientIdTag: 'x'}
+ ],
+ [],
+ []
+ ],
+ 'x',
+ 'SW-fetched redirect to same-origin same-scope.');
+
+url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE2);
+url2 = SCOPE2;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'x'}],
+ [{url: url2, resultingClientIdTag: 'x'}],
+ []
+ ],
+ 'x',
+ 'SW-fetched redirect to same-origin other-scope.');
+
+url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
+url2 = OTHER_ORIGIN_OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+ null,
+ 'SW-fetched redirect to other-origin out-scope.');
+
+url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
+url2 = OTHER_ORIGIN_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'a'}],
+ [],
+ [{url: url2, resultingClientIdTag: 'x'}]
+ ],
+ 'x',
+ 'SW-fetched redirect to other-origin in-scope.');
+
+
+// SW responds with a fetch from a different url.
+// SW: event.respondWith(fetch(params['url']));
+url2 = SCOPE1;
+url1 = SCOPE1 + 'sw=fetch-url&url=' + encodeURIComponent(url2);
+redirect_test(
+ url1,
+ url1,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'x'}
+ ],
+ [],
+ []
+ ],
+ 'x',
+ 'SW-fetched response from different URL, same-origin same-scope.');
+
+
+// Opaque redirect.
+// SW: event.respondWith(fetch(
+// new Request(event.request.url, {redirect: 'manual'})));
+url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(OUT_SCOPE);
+url2 = OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'Redirect to same-origin out-scope with opaque redirect response.');
+
+url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(SCOPE1);
+url2 = SCOPE1;
+redirect_test(
+ url1,
+ url2,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'x'},
+ {url: url2, resultingClientIdTag: 'x'}
+ ],
+ [],
+ []
+ ],
+ 'x',
+ 'Redirect to same-origin same-scope with opaque redirect response.');
+
+url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(SCOPE2);
+url2 = SCOPE2;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'x'}],
+ [{url: url2, resultingClientIdTag: 'x'}],
+ []
+ ],
+ 'x',
+ 'Redirect to same-origin other-scope with opaque redirect response.');
+
+url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
+url2 = OTHER_ORIGIN_OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+ null,
+ 'Redirect to other-origin out-scope with opaque redirect response.');
+
+url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
+url2 = OTHER_ORIGIN_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'a'}],
+ [],
+ [{url: url2, resultingClientIdTag: 'x'}]
+ ],
+ 'x',
+ 'Redirect to other-origin in-scope with opaque redirect response.');
+
+url= SCOPE1 + 'sw=manual&noLocationRedirect';
+redirect_test(
+ url, url, [[{url, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'No location redirect response.');
+
+
+// Opaque redirect passed through Cache.
+// SW responds with an opaque redirectresponse from the Cache API.
+url1 = SCOPE1 + 'sw=manualThroughCache&url=' + encodeURIComponent(OUT_SCOPE);
+url2 = OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'Redirect to same-origin out-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+
+url1 = SCOPE1 + 'sw=manualThroughCache&url=' + encodeURIComponent(SCOPE1);
+url2 = SCOPE1;
+redirect_test(
+ url1,
+ url2,
+ [
+ [
+ {url: url1, resultingClientIdTag: 'x'},
+ {url: url2, resultingClientIdTag: 'x'}
+ ],
+ [],
+ []
+ ],
+ 'x',
+ 'Redirect to same-origin same-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+
+url1 = SCOPE1 + 'sw=manualThroughCache&url=' + encodeURIComponent(SCOPE2);
+url2 = SCOPE2;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'x'}],
+ [{url: url2, resultingClientIdTag: 'x'}],
+ []
+ ],
+ 'x',
+ 'Redirect to same-origin other-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+
+url1 = SCOPE1 + 'sw=manualThroughCache&url=' +
+ encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
+url2 = OTHER_ORIGIN_OUT_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+ null,
+ 'Redirect to other-origin out-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+
+url1 = SCOPE1 + 'sw=manualThroughCache&url=' +
+ encodeURIComponent(OTHER_ORIGIN_SCOPE);
+url2 = OTHER_ORIGIN_SCOPE;
+redirect_test(
+ url1,
+ url2,
+ [
+ [{url: url1, resultingClientIdTag: 'a'}],
+ [],
+ [{url: url2, resultingClientIdTag: 'x'}],
+ ],
+ 'x',
+ 'Redirect to other-origin in-scope with opaque redirect response which ' +
+ 'is passed through Cache.');
+
+url = SCOPE1 + 'sw=manualThroughCache&noLocationRedirect';
+redirect_test(
+ url,
+ url,
+ [[{url, resultingClientIdTag: 'x'}], [], []],
+ 'x',
+ 'No location redirect response via Cache.');
+
+// Clean up the test environment. This promise_test() needs to be the last one.
+promise_test(async t => {
+ registrations.forEach(async registration => {
+ if (registration)
+ await registration.unregister();
+ });
+ await send_to_iframe(other_origin_frame, {command: 'unregister'});
+ other_origin_frame.remove();
+}, 'clean up global state');
+</script>
+</body>