1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test clients.get(resultingClientId)</title>
<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>
<script>
const scope = "resources/";
let worker;
// Setup. Keep this as the first promise_test.
promise_test(async (t) => {
const registration = await service_worker_unregister_and_register(
t, 'resources/get-resultingClientId-worker.js',
scope);
worker = registration.installing;
await wait_for_state(t, worker, 'activated');
}, 'global setup');
// Sends |command| to the worker and returns a promise that resolves to its
// response. There should only be one inflight command at a time.
async function sendCommand(command) {
const saw_message = new Promise((resolve) => {
navigator.serviceWorker.onmessage = (event) => {
resolve(event.data);
};
});
worker.postMessage(command);
return saw_message;
}
// Wrapper for 'startTest' command. Tells the worker a test is starting,
// so it resets state and keeps itself alive until 'finishTest'.
async function startTest(t) {
const result = await sendCommand({command: 'startTest'});
assert_equals(result, 'ok', 'startTest');
t.add_cleanup(async () => {
return finishTest();
});
}
// Wrapper for 'finishTest' command.
async function finishTest() {
const result = await sendCommand({command: 'finishTest'});
assert_equals(result, 'ok', 'finishTest');
}
// Wrapper for 'getResultingClient' command. Tells the worker to return
// clients.get(event.resultingClientId) for the navigation that occurs
// during this test.
//
// The return value describes how clients.get() settled. It also includes
// |queriedId| which is the id passed to clients.get() (the resultingClientId
// in this case).
//
// Example value:
// {
// queriedId: 'abc',
// promiseState: fulfilled,
// promiseValue: client,
// client: {
// id: 'abc',
// url: '//example.com/client'
// }
// }
async function getResultingClient() {
return sendCommand({command: 'getResultingClient'});
}
// Wrapper for 'getClient' command. Tells the worker to return
// clients.get(|id|). The return value is as in the getResultingClient()
// documentation.
async function getClient(id) {
return sendCommand({command: 'getClient', id: id});
}
// Navigates to |url|. Returns the result of clients.get() on the
// resultingClientId.
async function navigateAndGetResultingClient(t, url) {
const resultPromise = getResultingClient();
const frame = await with_iframe(url);
t.add_cleanup(() => {
frame.remove();
});
const result = await resultPromise;
const resultingClientId = result.queriedId;
// First test clients.get(event.resultingClientId) inside the fetch event. The
// behavior of this is subtle due to the use of iframes and about:blank
// replacement. The spec probably requires that it resolve to the original
// about:blank client, and that later that client should be discarded after
// load if the load was to another origin. Implementations might differ. For
// now, this test just asserts that the promise resolves. See
// https://github.com/w3c/ServiceWorker/issues/1385.
assert_equals(result.promiseState, 'fulfilled',
'get(event.resultingClientId) in the fetch event should fulfill');
// Test clients.get() on the previous resultingClientId again. By this
// time the load finished, so it's more straightforward how this promise
// should settle. Return the result of this promise.
return await getClient(resultingClientId);
}
// Test get(resultingClientId) in the basic same-origin case.
promise_test(async (t) => {
await startTest(t);
const url = new URL('resources/empty.html', window.location);
const result = await navigateAndGetResultingClient(t, url);
assert_equals(result.promiseState, 'fulfilled', 'promiseState');
assert_equals(result.promiseValue, 'client', 'promiseValue');
assert_equals(result.client.url, url.href, 'client.url',);
assert_equals(result.client.id, result.queriedId, 'client.id');
}, 'get(resultingClientId) for same-origin document');
// Test get(resultingClientId) when the response redirects to another origin.
promise_test(async (t) => {
await startTest(t);
// Navigate to a URL that redirects to another origin.
const base_url = new URL('.', window.location);
const host_info = get_host_info();
const other_origin_url = new URL(base_url.pathname + 'resources/empty.html',
host_info['HTTPS_REMOTE_ORIGIN']);
const url = new URL('resources/empty.html', window.location);
const pipe = `status(302)|header(Location, ${other_origin_url})`;
url.searchParams.set('pipe', pipe);
// The original reserved client should have been discarded on cross-origin
// redirect.
const result = await navigateAndGetResultingClient(t, url);
assert_equals(result.promiseState, 'fulfilled', 'promiseState');
assert_equals(result.promiseValue, 'undefinedValue', 'promiseValue');
}, 'get(resultingClientId) on cross-origin redirect');
// Test get(resultingClientId) when the document is sandboxed to a unique
// origin using a CSP HTTP response header.
promise_test(async (t) => {
await startTest(t);
// Navigate to a URL that has CSP sandboxing set in the HTTP response header.
const url = new URL('resources/empty.html', window.location);
const pipe = 'header(Content-Security-Policy, sandbox)';
url.searchParams.set('pipe', pipe);
// The original reserved client should have been discarded upon loading
// the sandboxed document.
const result = await navigateAndGetResultingClient(t, url);
assert_equals(result.promiseState, 'fulfilled', 'promiseState');
assert_equals(result.promiseValue, 'undefinedValue', 'promiseValue');
}, 'get(resultingClientId) for document sandboxed by CSP header');
// Test get(resultingClientId) when the document is sandboxed with
// allow-same-origin.
promise_test(async (t) => {
await startTest(t);
// Navigate to a URL that has CSP sandboxing set in the HTTP response header.
const url = new URL('resources/empty.html', window.location);
const pipe = 'header(Content-Security-Policy, sandbox allow-same-origin)';
url.searchParams.set('pipe', pipe);
// The client should be the original reserved client, as it's same-origin.
const result = await navigateAndGetResultingClient(t, url);
assert_equals(result.promiseState, 'fulfilled', 'promiseState');
assert_equals(result.promiseValue, 'client', 'promiseValue');
assert_equals(result.client.url, url.href, 'client.url',);
assert_equals(result.client.id, result.queriedId, 'client.id');
}, 'get(resultingClientId) for document sandboxed by CSP header with allow-same-origin');
// Cleanup. Keep this as the last promise_test.
promise_test(async (t) => {
return service_worker_unregister(t, scope);
}, 'global cleanup');
</script>
|