-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1523562 [wpt PR 15035] - WPT: service worker: test clients.get(re…
…sultingClientId) for cross-origin., a=testonly Automatic update from web-platform-tests WPT: service worker: test clients.get(resultingClientId) for cross-origin. See w3c/ServiceWorker#1385 Bug: 924959 Change-Id: I5e2850b743d0702b36f1f20a84c87591c3baab19 Reviewed-on: https://chromium-review.googlesource.com/c/1433657 Reviewed-by: Ben Kelly <wanderview@chromium.org> Commit-Queue: Matt Falkenhagen <falken@chromium.org> Cr-Commit-Position: refs/heads/master@{#626265} -- wpt-commits: 0e0e65d5a43926e584537ea0878b0c2fbc1b694c wpt-pr: 15035
- Loading branch information
Showing
2 changed files
with
284 additions
and
0 deletions.
There are no files selected for viewing
177 changes: 177 additions & 0 deletions
177
...eb-platform/tests/service-workers/service-worker/clients-get-resultingClientId.https.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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> |
107 changes: 107 additions & 0 deletions
107
...b-platform/tests/service-workers/service-worker/resources/get-resultingClientId-worker.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// This worker expects a fetch event for a navigation and messages back the | ||
// result of clients.get(event.resultingClientId). | ||
|
||
// Resolves when the test finishes. | ||
let testFinishPromise; | ||
let resolveTestFinishPromise; | ||
let rejectTestFinishPromise; | ||
|
||
// Resolves to clients.get(event.resultingClientId) from the fetch event. | ||
let getPromise; | ||
let resolveGetPromise; | ||
let rejectGetPromise; | ||
|
||
let resultingClientId; | ||
|
||
function startTest() { | ||
testFinishPromise = new Promise((resolve, reject) => { | ||
resolveTestFinishPromise = resolve; | ||
rejectTestFinishPromise = reject; | ||
}); | ||
|
||
getPromise = new Promise((resolve, reject) => { | ||
resolveGetPromise = resolve; | ||
rejectGetPromise = reject; | ||
}); | ||
} | ||
|
||
async function describeGetPromiseResult(promise) { | ||
const result = {}; | ||
|
||
await promise.then( | ||
(client) => { | ||
result.promiseState = 'fulfilled'; | ||
if (client === undefined) { | ||
result.promiseValue = 'undefinedValue'; | ||
} else if (client instanceof Client) { | ||
result.promiseValue = 'client'; | ||
result.client = { | ||
id: client.id, | ||
url: client.url | ||
}; | ||
} else { | ||
result.promiseValue = 'unknown'; | ||
} | ||
}, | ||
(error) => { | ||
result.promiseState = 'rejected'; | ||
}); | ||
|
||
return result; | ||
} | ||
|
||
async function handleGetResultingClient(event) { | ||
// Note that this message can arrive before |resultingClientId| is populated. | ||
const result = await describeGetPromiseResult(getPromise); | ||
// |resultingClientId| must be populated by now. | ||
result.queriedId = resultingClientId; | ||
event.source.postMessage(result); | ||
}; | ||
|
||
async function handleGetClient(event) { | ||
const id = event.data.id; | ||
const result = await describeGetPromiseResult(self.clients.get(id)); | ||
result.queriedId = id; | ||
event.source.postMessage(result); | ||
}; | ||
|
||
self.addEventListener('message', (event) => { | ||
if (event.data.command == 'startTest') { | ||
startTest(); | ||
event.waitUntil(testFinishPromise); | ||
event.source.postMessage('ok'); | ||
return; | ||
} | ||
|
||
if (event.data.command == 'finishTest') { | ||
resolveTestFinishPromise(); | ||
event.source.postMessage('ok'); | ||
return; | ||
} | ||
|
||
if (event.data.command == 'getResultingClient') { | ||
event.waitUntil(handleGetResultingClient(event)); | ||
return; | ||
} | ||
|
||
if (event.data.command == 'getClient') { | ||
event.waitUntil(handleGetClient(event)); | ||
return; | ||
} | ||
}); | ||
|
||
async function handleFetch(event) { | ||
try { | ||
resultingClientId = event.resultingClientId; | ||
const client = await self.clients.get(resultingClientId); | ||
resolveGetPromise(client); | ||
} catch (error) { | ||
rejectGetPromise(error); | ||
} | ||
} | ||
|
||
self.addEventListener('fetch', (event) => { | ||
if (event.request.mode != 'navigate') | ||
return; | ||
event.waitUntil(handleFetch(event)); | ||
}); |