| <!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> |