diff --git a/service-workers/service-worker/navigation-redirect.https.html b/service-workers/service-worker/navigation-redirect.https.html index b6281b9e6c3054..6f03fd8f2f0db5 100644 --- a/service-workers/service-worker/navigation-redirect.https.html +++ b/service-workers/service-worker/navigation-redirect.https.html @@ -1,6 +1,11 @@ Service Worker: Navigation redirection + + + + + @@ -76,36 +81,208 @@ 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(requestInfos[0], expected_id, + `resultingClientId for ${url} request to ${worker}`); + } else { + actual_ids[key] = 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: // [ -// [{url: url1}, {url: url2}], // requests from workers[0], -// [{url: url1}, // requests from workers[1], -// [{url: url1}, {url: url2}] // requests from cross-origin worker +// [ +// // 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(); }); - 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 send_to_iframe(frame, 'getLocation'); - assert_equals(last_url, expected_last_url, 'Last URL should match.'); + // 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) { @@ -129,6 +306,27 @@ }); } +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) { @@ -138,7 +336,10 @@ // Returns the requests that |worker| received fetch events for. The return // value is an array of format: -// [{url: url1}, {url: url2}] +// [ +// {url: url1, resultingClientId: id}, +// {url: url2, resultingClientId: id} +// ] function get_request_infos(worker) { return new Promise(resolve => { var channel = new MessageChannel(); @@ -150,6 +351,29 @@ }); } +// 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; @@ -159,7 +383,8 @@ redirect_test( OUT_SCOPE + 'url=' + encodeURIComponent(url), url, - [[{url}], [], []], + [[{url, resultingClientIdTag: 'x'}], [], []], + 'x', 'Normal redirect to same-origin scope.'); @@ -167,30 +392,33 @@ redirect_test( OUT_SCOPE + 'url=' + encodeURIComponent(SCOPE1) + '#ref', url, - [[{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}], [], []], + [[{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}]], + [[], [], [{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}], [], []], + [[{url, resultingClientIdTag: 'x'}], [], []], + 'x', 'SW-fallbacked redirect to same-origin out-scope.'); url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1); @@ -198,7 +426,15 @@ redirect_test( url1, url2, - [[{url: url1}, {url: url2}], [], []], + [ + [ + {url: url1, resultingClientIdTag: 'x'}, + {url: url2, resultingClientIdTag: 'x'} + ], + [], + [] + ], + 'x', 'SW-fallbacked redirect to same-origin same-scope.'); url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1) + '#ref'; @@ -206,7 +442,15 @@ redirect_test( url1, url2, - [[{url: url1}, {url: 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'; @@ -214,7 +458,15 @@ redirect_test( url1, url2, - [[{url: url1}, {url: url2}], [], []], + [ + [ + {url: url1, resultingClientIdTag: 'x'}, + {url: url2, resultingClientIdTag: 'x'} + ], + [], + [] + ], + 'x', 'SW-fallbacked redirect to same-origin same-scope with different hash ' + 'fragments.'); @@ -223,7 +475,12 @@ redirect_test( url1, url2, - [[{url: url1}], [{url: 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); @@ -231,7 +488,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'a'}], [], []], + 'x', 'SW-fallbacked redirect to other-origin out-scope.'); url1 = SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE); @@ -239,10 +497,32 @@ redirect_test( url1, url2, - [[{url: url1}], [], [{url: 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); @@ -250,7 +530,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'a'}], [], []], + null, 'SW-generated redirect to same-origin out-scope.'); url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE) + '#ref'; @@ -258,7 +539,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{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'; @@ -266,7 +548,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'x'}], [], []], + 'x', 'SW-generated redirect to same-origin out-scope with different hash ' + 'fragments.'); @@ -275,7 +558,15 @@ redirect_test( url1, url2, - [[{url: url1}, {url: 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); @@ -283,7 +574,12 @@ redirect_test( url1, url2, - [[{url: url1}], [{url: 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); @@ -291,7 +587,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'a'}], [], []], + null, 'SW-generated redirect to other-origin out-scope.'); url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE); @@ -300,10 +597,11 @@ url1, url2, [ - [{url: url1}], + [{url: url1, resultingClientIdTag: 'a'}], [], - [{url: url2}] + [{url: url2, resultingClientIdTag: 'x'}] ], + 'x', 'SW-generated redirect to other-origin in-scope.'); @@ -314,7 +612,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'x'}], [], []], + 'x', 'SW-fetched redirect to same-origin out-scope.'); url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE1); @@ -322,7 +621,15 @@ redirect_test( url1, url2, - [[{url: url1}, {url: 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); @@ -331,10 +638,11 @@ url1, url2, [ - [{url: url1}], - [{url: 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); @@ -342,7 +650,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'a'}], [], []], + null, 'SW-fetched redirect to other-origin out-scope.'); url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE); @@ -351,10 +660,11 @@ url1, url2, [ - [{url: url1}], + [{url: url1, resultingClientIdTag: 'a'}], [], - [{url: url2}] + [{url: url2, resultingClientIdTag: 'x'}] ], + 'x', 'SW-fetched redirect to other-origin in-scope.'); @@ -366,7 +676,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'x'}], [], []], + 'x', 'Redirect to same-origin out-scope with opaque redirect response.'); url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(SCOPE1); @@ -374,7 +685,15 @@ redirect_test( url1, url2, - [[{url: url1}, {url: 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); @@ -382,7 +701,12 @@ redirect_test( url1, url2, - [[{url: url1}], [{url: 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); @@ -390,7 +714,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'a'}], [], []], + null, 'Redirect to other-origin out-scope with opaque redirect response.'); url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE); @@ -398,12 +723,18 @@ redirect_test( url1, url2, - [[{url: url1}], [], [{url: 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}], [], []], + url, url, [[{url, resultingClientIdTag: 'x'}], [], []], + 'x', 'No location redirect response.'); @@ -414,7 +745,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'x'}], [], []], + 'x', 'Redirect to same-origin out-scope with opaque redirect response which ' + 'is passed through Cache.'); @@ -424,10 +756,14 @@ url1, url2, [ - [{url: url1}, {url: 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.'); @@ -437,10 +773,11 @@ url1, url2, [ - [{url: url1}], - [{url: 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.'); @@ -450,7 +787,8 @@ redirect_test( url1, url2, - [[{url: url1}], [], []], + [[{url: url1, resultingClientIdTag: 'a'}], [], []], + null, 'Redirect to other-origin out-scope with opaque redirect response which ' + 'is passed through Cache.'); @@ -461,10 +799,11 @@ url1, url2, [ - [{url: url1}], + [{url: url1, resultingClientIdTag: 'a'}], [], - [{url: url2}], + [{url: url2, resultingClientIdTag: 'x'}], ], + 'x', 'Redirect to other-origin in-scope with opaque redirect response which ' + 'is passed through Cache.'); @@ -472,7 +811,8 @@ redirect_test( url, 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. diff --git a/service-workers/service-worker/resources/navigation-redirect-other-origin.html b/service-workers/service-worker/resources/navigation-redirect-other-origin.html index 0d2825f31093f8..d82571d1a3c71e 100644 --- a/service-workers/service-worker/resources/navigation-redirect-other-origin.html +++ b/service-workers/service-worker/resources/navigation-redirect-other-origin.html @@ -44,6 +44,20 @@ }); } +function get_clients(worker, actual_ids) { + return new Promise(function(resolve) { + var channel = new MessageChannel(); + channel.port1.onmessage = (msg) => { + resolve(msg.data.clients); + }; + worker.postMessage({ + command: 'getClients', + actual_ids, + port: channel.port2 + }, [channel.port2]); + }); +} + window.addEventListener('message', on_message, false); function on_message(e) { @@ -59,6 +73,11 @@ .then(function(data) { send_result(e.data.id, data); }); + } else if (command == 'get_clients') { + get_clients(worker, e.data.message.actual_ids) + .then(function(data) { + send_result(e.data.id, data); + }); } else if (command == 'unregister') { registration.unregister() .then(function() { diff --git a/service-workers/service-worker/resources/redirect-worker.js b/service-workers/service-worker/resources/redirect-worker.js index bf0a3a8d9e0486..0c5bc3bd9aa5bf 100644 --- a/service-workers/service-worker/resources/redirect-worker.js +++ b/service-workers/service-worker/resources/redirect-worker.js @@ -5,6 +5,13 @@ var cacheName = 'urls-' + self.registration.scope; var waitUntilPromiseList = []; +// Sends the requests seen by this worker. The output is: +// { +// requestInfos: [ +// {url: url1, resultingClientId: id1}, +// {url: url2, resultingClientId: id2}, +// ] +// } async function getRequestInfos(event) { // Wait for fetch events to finish. await Promise.all(waitUntilPromiseList); @@ -15,8 +22,11 @@ async function getRequestInfos(event) { const requestList = await cache.keys(); const requestInfos = []; for (let i = 0; i < requestList.length; i++) { + const response = await cache.match(requestList[i]); + const body = await response.json(); requestInfos[i] = { url: requestList[i].url, + resultingClientId: body.resultingClientId }; } await caches.delete(cacheName); @@ -24,13 +34,50 @@ async function getRequestInfos(event) { event.data.port.postMessage({requestInfos}); } +// Sends the results of clients.get(id) from this worker. The +// input is: +// { +// actual_ids: {a: id1, b: id2, x: id3} +// } +// +// The output is: +// { +// clients: { +// a: {found: false}, +// b: {found: false}, +// x: { +// id: id3, +// url: url1, +// found: true +// } +// } +// } +async function getClients(event) { + // |actual_ids| is like: + // {a: id1, b: id2, x: id3} + const actual_ids = event.data.actual_ids; + const result = {} + for (let key of Object.keys(actual_ids)) { + const id = actual_ids[key]; + const client = await self.clients.get(id); + if (client === undefined) + result[key] = {found: false}; + else + result[key] = {found: true, url: client.url, id: client.id}; + } + event.data.port.postMessage({clients: result}); +} + self.addEventListener('message', async function(event) { if (event.data.command == 'getRequestInfos') { event.waitUntil(getRequestInfos(event)); return; } - // TODO(falken): Add a getClientInfos command to test Clients API. + if (event.data.command == 'getClients') { + event.waitUntil(getClients(event)); + return; + } }); function get_query_params(url) { @@ -49,7 +96,11 @@ function get_query_params(url) { self.addEventListener('fetch', function(event) { var waitUntilPromise = caches.open(cacheName).then(function(cache) { - return cache.put(event.request, new Response()); + const responseBody = {}; + responseBody['resultingClientId'] = event.resultingClientId; + const headers = new Headers({'Content-Type': 'application/json'}); + const response = new Response(JSON.stringify(responseBody), {headers}); + return cache.put(event.request, response); }); event.waitUntil(waitUntilPromise);