blob: 0490f2b4058ce808115442b09859fe7a350b2303 [file] [log] [blame]
<!DOCTYPE html>
<title>Service Worker: Navigation redirection</title>
<meta name="timeout" content="long">
<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.');
}
// 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:
// [
// [{url: url1}, {url: url2}], // requests from workers[0],
// [{url: url1}, // requests from workers[1],
// [{url: url1}, {url: url2}] // requests from cross-origin worker
// ]
function redirect_test(url,
expected_last_url,
expected_request_infos,
test_name) {
promise_test(async t => {
const message_promise = new Promise(resolve => {
// A message with ID 'last_url' will be sent from the iframe.
message_resolvers['last_url'] = resolve;
});
const frame = await with_iframe(url);
t.add_cleanup(() => { frame.remove(); });
const expected_intercepted_urls = expected_request_infos.map(requests => {
return requests.map(info => {
return info.url;
});
});
await check_all_intercepted_urls(expected_intercepted_urls);
const last_url = await message_promise;
assert_equals(last_url, expected_last_url, 'Last URL should match.');
}, test_name);
}
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},
host_info['HTTPS_REMOTE_ORIGIN']);
});
}
// 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}, {url: url2}]
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]);
});
}
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}], [], []],
'Normal redirect to same-origin scope.');
url = SCOPE1 + '#ref';
redirect_test(
OUT_SCOPE + 'url=' + encodeURIComponent(SCOPE1) + '#ref',
url,
[[{url}], [], []],
'Normal redirect to same-origin scope with a hash fragment.');
url = SCOPE1 + '#ref2';
redirect_test(
OUT_SCOPE + 'url=' + encodeURIComponent(url) + '#ref',
url,
[[{url}], [], []],
'Normal redirect to same-origin scope with different hash fragments.');
url = OTHER_ORIGIN_SCOPE;
redirect_test(
OUT_SCOPE + 'url=' + encodeURIComponent(url),
url,
[[], [], [{url}]],
'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}], [], []],
'SW-fallbacked redirect to same-origin out-scope.');
url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1);
url2 = SCOPE1;
redirect_test(
url1,
url2,
[[{url: url1}, {url: url2}], [], []],
'SW-fallbacked redirect to same-origin same-scope.');
url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1) + '#ref';
url2 = SCOPE1 + '#ref';
redirect_test(
url1,
url2,
[[{url: url1}, {url: url2}], [], []],
'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}, {url: url2}], [], []],
'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}], [{url: url2}], []],
'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}], [], []],
'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}], [], [{url: url2}]],
'SW-fallbacked redirect to other-origin in-scope.');
// 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}], [], []],
'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}], [], []],
'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}], [], []],
'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}, {url: url2}], [], []],
'SW-generated redirect to same-origin same-scope.');
url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE2);
url2 = SCOPE2;
redirect_test(
url1,
url2,
[[{url: url1}], [{url: url2}], []],
'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}], [], []],
'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}],
[],
[{url: url2}]
],
'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}], [], []],
'SW-fetched redirect to same-origin out-scope.');
url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE1);
url2 = SCOPE1;
redirect_test(
url1,
url2,
[[{url: url1}, {url: url2}], [], []],
'SW-fetched redirect to same-origin same-scope.');
url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE2);
url2 = SCOPE2;
redirect_test(
url1,
url2,
[
[{url: url1}],
[{url: url2}],
[]
],
'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}], [], []],
'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}],
[],
[{url: url2}]
],
'SW-fetched redirect to other-origin in-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}], [], []],
'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}, {url: url2}], [], []],
'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}], [{url: url2}], []],
'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}], [], []],
'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}], [], [{url: url2}]],
'Redirect to other-origin in-scope with opaque redirect response.');
url= SCOPE1 + 'sw=manual&noLocationRedirect';
redirect_test(
url, url, [[{url}], [], []],
'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}], [], []],
'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}, {url: url2}],
[],
[]
],
'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}],
[{url: url2}],
[]
],
'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}], [], []],
'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}],
[],
[{url: url2}],
],
'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}], [], []],
'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>