diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /testing/web-platform/tests/service-workers/service-worker | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/web-platform/tests/service-workers/service-worker')
239 files changed, 13898 insertions, 0 deletions
diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html new file mode 100644 index 000000000..f0dad6a7b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>ServiceWorkerGlobalScope: registration</title> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<script src='../resources/test-helpers.sub.js'></script> +<script> + +promise_test(function(t) { + var script = 'resources/registration-attribute-worker.js'; + var scope = 'resources/scope/registration-attribute'; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + var expected_events_seen = [ + 'updatefound', + 'install', + 'statechange(installed)', + 'statechange(activating)', + 'activate', + 'statechange(activated)', + 'fetch', + ]; + + assert_equals( + frame.contentDocument.body.textContent, + expected_events_seen.toString(), + 'Service Worker should respond to fetch'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Verify registration attribute on ServiceWorkerGlobalScope'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js new file mode 100644 index 000000000..c98acbcfb --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js @@ -0,0 +1,132 @@ +importScripts('../../resources/test-helpers.sub.js'); +importScripts('../../resources/worker-testharness.js'); + +var events_seen = []; + +assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On worker script evaluation, registration attribute should be set'); +assert_equals( + self.registration.installing, + null, + 'On worker script evaluation, installing worker should be null'); +assert_equals( + self.registration.waiting, + null, + 'On worker script evaluation, waiting worker should be null'); +assert_equals( + self.registration.active, + null, + 'On worker script evaluation, active worker should be null'); + +self.registration.addEventListener('updatefound', function() { + events_seen.push('updatefound'); + + assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On updatefound event, registration attribute should be set'); + assert_equals( + self.registration.installing.scriptURL, + normalizeURL('registration-attribute-worker.js'), + 'On updatefound event, installing worker should be set'); + assert_equals( + self.registration.waiting, + null, + 'On updatefound event, waiting worker should be null'); + assert_equals( + self.registration.active, + null, + 'On updatefound event, active worker should be null'); + + assert_equals( + self.registration.installing.state, + 'installing', + 'On updatefound event, worker should be in the installing state'); + + var worker = self.registration.installing; + self.registration.installing.addEventListener('statechange', function() { + events_seen.push('statechange(' + worker.state + ')'); + }); + }); + +self.addEventListener('install', function(e) { + events_seen.push('install'); + + assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On install event, registration attribute should be set'); + assert_equals( + self.registration.installing.scriptURL, + normalizeURL('registration-attribute-worker.js'), + 'On install event, installing worker should be set'); + assert_equals( + self.registration.waiting, + null, + 'On install event, waiting worker should be null'); + assert_equals( + self.registration.active, + null, + 'On install event, active worker should be null'); + + assert_equals( + self.registration.installing.state, + 'installing', + 'On install event, worker should be in the installing state'); + }); + +self.addEventListener('activate', function(e) { + events_seen.push('activate'); + + assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On activate event, registration attribute should be set'); + assert_equals( + self.registration.installing, + null, + 'On activate event, installing worker should be null'); + assert_equals( + self.registration.waiting, + null, + 'On activate event, waiting worker should be null'); + assert_equals( + self.registration.active.scriptURL, + normalizeURL('registration-attribute-worker.js'), + 'On activate event, active worker should be set'); + + assert_equals( + self.registration.active.state, + 'activating', + 'On activate event, worker should be in the activating state'); + }); + +self.addEventListener('fetch', function(e) { + events_seen.push('fetch'); + + assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On fetch event, registration attribute should be set'); + assert_equals( + self.registration.installing, + null, + 'On fetch event, installing worker should be null'); + assert_equals( + self.registration.waiting, + null, + 'On fetch event, waiting worker should be null'); + assert_equals( + self.registration.active.scriptURL, + normalizeURL('registration-attribute-worker.js'), + 'On fetch event, active worker should be set'); + + assert_equals( + self.registration.active.state, + 'activated', + 'On fetch event, worker should be in the activated state'); + + e.respondWith(new Response(events_seen)); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js new file mode 100644 index 000000000..6cee53654 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js @@ -0,0 +1,23 @@ +function matchQuery(query) { + return self.location.href.indexOf(query) != -1; +} + +if (matchQuery('?evaluation')) + self.registration.unregister(); + +self.addEventListener('install', function(e) { + if (matchQuery('?install')) + self.registration.unregister(); + }); + +self.addEventListener('activate', function(e) { + if (matchQuery('?activate')) + self.registration.unregister(); + }); + +self.addEventListener('message', function(e) { + self.registration.unregister() + .then(function(result) { + e.data.port.postMessage({result: result}); + }); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js new file mode 100644 index 000000000..63c4534b7 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js @@ -0,0 +1,25 @@ +importScripts('../../resources/test-helpers.sub.js'); +importScripts('../../resources/worker-testharness.js'); + +var events_seen = []; + +self.registration.addEventListener('updatefound', function() { + events_seen.push('updatefound'); + }); + +self.addEventListener('activate', function(e) { + events_seen.push('activate'); + }); + +self.addEventListener('fetch', function(e) { + events_seen.push('fetch'); + e.respondWith(new Response(events_seen)); + }); + +self.addEventListener('message', function(e) { + events_seen.push('message'); + self.registration.update(); + }); + +// update() during the script evaluation should be ignored. +self.registration.update(); diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py new file mode 100644 index 000000000..5158bf251 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py @@ -0,0 +1,14 @@ +import os +import time + +def main(request, response): + # update() does not bypass cache so set the max-age to 0 such that update() + # can find a new version in the network. + headers = [('Cache-Control', 'max-age: 0'), + ('Content-Type', 'application/javascript')] + with open(os.path.join(os.path.dirname(__file__), + 'update-worker.js'), 'r') as file: + script = file.read() + # Return a different script for each access. + return headers, '// %s\n%s' % (time.time(), script) + diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html new file mode 100644 index 000000000..313309188 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> +<title>ServiceWorkerGlobalScope: unregister</title> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<script src='../resources/test-helpers.sub.js'></script> +<script> + +promise_test(function(t) { + var script = 'resources/unregister-worker.js?evaluation'; + var scope = 'resources/scope/unregister-on-script-evaluation'; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'redundant'); + }) + .then(function() { + return navigator.serviceWorker.getRegistration(scope); + }) + .then(function(result) { + assert_equals( + result, + undefined, + 'After unregister(), the registration should not found'); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Unregister on script evaluation'); + +promise_test(function(t) { + var script = 'resources/unregister-worker.js?install'; + var scope = 'resources/scope/unregister-on-install-event'; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'redundant'); + }) + .then(function() { + return navigator.serviceWorker.getRegistration(scope); + }) + .then(function(result) { + assert_equals( + result, + undefined, + 'After unregister(), the registration should not found'); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Unregister on installing event'); + +promise_test(function(t) { + var script = 'resources/unregister-worker.js?activate'; + var scope = 'resources/scope/unregister-on-activate-event'; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'redundant'); + }) + .then(function() { + return navigator.serviceWorker.getRegistration(scope); + }) + .then(function(result) { + assert_equals( + result, + undefined, + 'After unregister(), the registration should not found'); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Unregister on activate event'); + +promise_test(function(t) { + var script = 'resources/unregister-worker.js'; + var scope = 'resources/unregister-controlling-worker.html'; + + var controller; + var frame; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + controller = frame.contentWindow.navigator.serviceWorker.controller; + + assert_equals( + controller.scriptURL, + normalizeURL(script), + 'Service worker should control a new document') + + // Wait for the completion of unregister() on the worker. + var channel = new MessageChannel(); + var promise = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_true(e.data.result, + 'unregister() should successfully finish'); + resolve(); + }); + }); + controller.postMessage({port: channel.port2}, [channel.port2]); + return promise; + }) + .then(function() { + return navigator.serviceWorker.getRegistration(scope); + }) + .then(function(result) { + assert_equals( + result, + undefined, + 'After unregister(), the registration should not found'); + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller, + controller, + 'After unregister(), the worker should still control the document'); + return with_iframe(scope); + }) + .then(function(new_frame) { + assert_equals( + new_frame.contentWindow.navigator.serviceWorker.controller, + null, + 'After unregister(), the worker should not control a new document'); + + frame.remove(); + new_frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + }, 'Unregister controlling service worker'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html new file mode 100644 index 000000000..a9285a1c9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<title>ServiceWorkerGlobalScope: update</title> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<script src='../resources/test-helpers.sub.js'></script> +<script> + +promise_test(function(t) { + var script = 'resources/update-worker.py'; + var scope = 'resources/scope/update'; + var registration; + var frame1; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame1 = f; + registration.active.postMessage('update'); + return wait_for_update(t, registration); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame2) { + var expected_events_seen = [ + 'updatefound', // by register(). + 'activate', + 'fetch', + 'message', + 'updatefound', // by update() in the message handler. + 'fetch', + ]; + assert_equals( + frame2.contentDocument.body.textContent, + expected_events_seen.toString(), + 'events seen by the worker'); + frame1.remove(); + frame2.remove(); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Update a registration on ServiceWorkerGlobalScope'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html b/testing/web-platform/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html new file mode 100644 index 000000000..57fccf137 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Service Worker: registration events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/blank.html'; + var registration; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + var sw = registration.installing; + + return new Promise(t.step_func(function(resolve) { + sw.onstatechange = t.step_func(function() { + if (sw.state === 'installed') { + assert_equals(registration.active, null, + 'installed event should be fired before activating service worker'); + resolve(); + } + }); + })); + }) + .then(function() { + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'installed event should be fired before activating service worker'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/activation-after-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/activation-after-registration.https.html new file mode 100644 index 000000000..ff0990df6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/activation-after-registration.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Service Worker: Activation occurs after registration</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +var t = async_test('activation occurs after registration'); +t.step(function() { + var scope = 'resources/blank.html'; + var registration; + + service_worker_unregister_and_register( + t, 'resources/empty-worker.js', scope) + .then(function(r) { + registration = r; + assert_equals( + r.installing.state, + 'installing', + 'worker should be in the "installing" state upon registration'); + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); +}); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/activation.https.html b/testing/web-platform/tests/service-workers/service-worker/activation.https.html new file mode 100644 index 000000000..75fdff813 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/activation.https.html @@ -0,0 +1,179 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>service worker: activation</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +// Registers, waits for activation, then unregisters on a dummy scope. +// +// This helper can be used in tests that assert that activation doesn't happen. +// It would not be sufficient to check the .waiting/.active properties once, +// since activation could be scheduled and just hasn't happened yet. Since this +// helper shows that activation of another registration completed, we can be +// sure that activation really will not happen. +function wait_for_activation_on_dummy_scope(t) { + var dummy_scope = 'resources/there/is/no/there/there'; + var registration; + return navigator.serviceWorker.register('resources/empty-worker.js', + { scope: dummy_scope }) + .then(r => { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(() => registration.unregister()); +} +// Returns {registration, iframe}, where |registration| has an active and +// waiting worker. The active worker controls |iframe| and has an inflight +// message event that can be finished by calling +// |registration.active.postMessage('go')|. +function setup_activation_test(t, scope, worker_url) { + var registration; + var iframe; + return navigator.serviceWorker.getRegistration(scope) + .then(r => { + if (r) + return r.unregister(); + }) + .then(() => { + // Create an in-scope iframe. Do this prior to registration to avoid + // racing between an update triggered by navigation and the update() + // call below. + return with_iframe(scope); + }) + .then(f => { + iframe = f; + // Create an active worker. + return navigator.serviceWorker.register(worker_url, { scope: scope }); + }) + .then(r => { + registration = r; + add_result_callback(() => registration.unregister()); + return wait_for_state(t, r.installing, 'activated'); + }) + .then(() => { + // Check that the frame was claimed. + assert_not_equals( + iframe.contentWindow.navigator.serviceWorker.controller, null); + // Create an in-flight request. + registration.active.postMessage('wait'); + // Now there is both a controllee and an in-flight request. + // Initiate an update. + return registration.update(); + }) + .then(() => { + // Wait for a waiting worker. + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(() => { + return wait_for_activation_on_dummy_scope(t); + }) + .then(() => { + assert_not_equals(registration.waiting, null); + assert_not_equals(registration.active, null); + return Promise.resolve({registration: registration, iframe: iframe}); + }); +} +promise_test(t => { + var scope = 'resources/no-controllee'; + var worker_url = 'resources/mint-new-worker.py'; + var registration; + var iframe; + var new_worker; + return setup_activation_test(t, scope, worker_url) + .then(result => { + registration = result.registration; + iframe = result.iframe; + // Finish the in-flight request. + registration.active.postMessage('go'); + return wait_for_activation_on_dummy_scope(t); + }) + .then(() => { + // The new worker is still waiting. Remove the frame and it should + // activate. + new_worker = registration.waiting; + assert_equals(new_worker.state, 'installed'); + var reached_active = wait_for_state(t, new_worker, 'activating'); + iframe.remove(); + return reached_active; + }) + .then(() => { + assert_equals(new_worker, registration.active); + }); + }, 'loss of controllees triggers activation'); +promise_test(t => { + var scope = 'resources/no-request'; + var worker_url = 'resources/mint-new-worker.py'; + var registration; + var iframe; + var new_worker; + return setup_activation_test(t, scope, worker_url) + .then(result => { + registration = result.registration; + iframe = result.iframe; + // Remove the iframe. + iframe.remove(); + return new Promise(resolve => setTimeout(resolve, 0)); + }) + .then(() => { + // Finish the request. + new_worker = registration.waiting; + var reached_active = wait_for_state(t, new_worker, 'activating'); + registration.active.postMessage('go'); + return reached_active; + }) + .then(() => { + assert_equals(registration.active, new_worker); + }); + }, 'finishing a request triggers activation'); +promise_test(t => { + var scope = 'resources/skip-waiting'; + var worker_url = 'resources/mint-new-worker.py?skip-waiting'; + var registration; + var new_worker; + return setup_activation_test(t, scope, worker_url) + .then(result => { + registration = result.registration; + // Finish the request. The iframe does not need to be removed because + // skipWaiting() was called. + new_worker = registration.waiting; + var reached_active = wait_for_state(t, new_worker, 'activating'); + registration.active.postMessage('go'); + return reached_active; + }) + .then(() => { + assert_equals(registration.active, new_worker); + }); + }, 'skipWaiting bypasses no controllee requirement'); + +// This test is not really about activation, but otherwise is very +// similar to the other tests here. +promise_test(t => { + var scope = 'resources/unregister'; + var worker_url = 'resources/mint-new-worker.py'; + var registration; + var iframe; + var new_worker; + return setup_activation_test(t, scope, worker_url) + .then(result => { + registration = result.registration; + iframe = result.iframe; + // Remove the iframe. + iframe.remove(); + return registration.unregister(); + }) + .then(() => { + // The unregister operation should wait for the active worker to + // finish processing its events before clearing the registration. + new_worker = registration.waiting; + var reached_redundant = wait_for_state(t, new_worker, 'redundant'); + registration.active.postMessage('go'); + return reached_redundant; + }) + .then(() => { + assert_equals(registration.installing, null); + assert_equals(registration.waiting, null); + assert_equals(registration.active, null); + }); + }, 'finishing a request triggers unregister'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/active.https.html b/testing/web-platform/tests/service-workers/service-worker/active.https.html new file mode 100644 index 000000000..deee6a50e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/active.https.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>ServiceWorker: navigator.serviceWorker.active</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +// "active" is set +async_test(function(t) { + var step = t.step_func.bind(t); + var url = 'resources/empty-worker.js'; + var scope = 'resources/blank.html'; + var frame; + var registration; + + service_worker_unregister(t, scope) + .then(step(function() { return with_iframe(scope); })) + .then(step(function(f) { + frame = f; + return navigator.serviceWorker.register(url, {scope: scope}); + })) + .then(step(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activating'); + })) + .then(step(function() { + var container = frame.contentWindow.navigator.serviceWorker; + assert_equals( + container.controller, + null, + 'On activating state a document should not have a controller'); + assert_equals( + registration.active.scriptURL, + normalizeURL(url), + 'On activating state a document should have an active worker '); + assert_equals( + registration.waiting, + null, + 'On activating state a document should not have a waiting worker'); + assert_equals( + registration.installing, + null, + 'On activating state a document should not have an installing ' + + 'worker'); + + // FIXME: Add a test for a frame created after installation. + // Should the existing frame ("frame") block activation? + })) + .then(step(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + })) + .catch(unreached_rejection(t)); + }, 'active is set'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/appcache-ordering-main.https.html b/testing/web-platform/tests/service-workers/service-worker/appcache-ordering-main.https.html new file mode 100644 index 000000000..609d67e45 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/appcache-ordering-main.https.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> + +var INSTALL_APPCACHE_URL = "resources/appcache-ordering.install.html"; +var IS_APPCACHED_URL = "resources/appcache-ordering.is-appcached.html"; +var SERVICE_WORKER_SCOPE = "resources/appcache-ordering"; +var SERVICE_WORKER_SCRIPT = "resources/empty-worker.js"; + +var resolve_install_appcache = undefined; +var reject_install_appcache = undefined; + +var frames = []; + +// Called by the INSTALL_APPCACHE_URL child frame. +function notify_appcache_installed(success) { + if (success) + resolve_install_appcache(); + else + reject_install_appcache(); +} + +function install_appcache() { + return new Promise(function(resolve, reject) { + var frame = document.createElement('iframe'); + frames.push(frame); + frame.src = INSTALL_APPCACHE_URL; + document.body.appendChild(frame); + resolve_install_appcache = function() { + document.body.removeChild(frame); + resolve(); + }; + reject_install_appcache = function() { + document.body.removeChild(frame); + reject(); + }; + }); +} + +var resolve_is_appcached = undefined; + +// Called by the IS_APPCACHED_URL child frame. +function notify_is_appcached(is) { + resolve_is_appcached(is); +} + +function is_appcached() { + return new Promise(function(resolve) { + var frame = document.createElement('iframe'); + frames.push(frame); + frame.src = IS_APPCACHED_URL; + document.body.appendChild(frame); + resolve_is_appcached = function(is) { + document.body.removeChild(frame); + resolve(is); + }; + }); +} + +async_test(function(t) { + service_worker_unregister(t, SERVICE_WORKER_SCOPE) + .then(function() { + return install_appcache(); + }) + .then(function() { + return is_appcached(); + }) + .then(function(result) { + assert_true(result, 'appcache should initially be utilized'); + return service_worker_unregister_and_register( + t, SERVICE_WORKER_SCRIPT, SERVICE_WORKER_SCOPE); + }) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return is_appcached(); + }) + .then(function(result) { + assert_false(result, 'but serviceworkers should take priority'); + frames.forEach(function(f) { f.remove(); }); + service_worker_unregister_and_done(t, SERVICE_WORKER_SCOPE); + }) + .catch(unreached_rejection(t)); + }, 'serviceworkers take priority over appcaches'); + +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/claim-not-using-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/claim-not-using-registration.https.html new file mode 100644 index 000000000..e18e061cd --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/claim-not-using-registration.https.html @@ -0,0 +1,123 @@ +<!DOCTYPE html> +<title>Service Worker: claim client not using registration</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> + +promise_test(function(t) { + var init_scope = 'resources/blank.html?not-using-init'; + var claim_scope = 'resources/blank.html?not-using'; + var init_worker_url = 'resources/empty.js'; + var claim_worker_url = 'resources/claim-worker.js'; + var claim_worker, claim_registration, frame1, frame2; + return service_worker_unregister_and_register( + t, init_worker_url, init_scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return Promise.all( + [with_iframe(init_scope), with_iframe(claim_scope)]); + }) + .then(function(frames) { + frame1 = frames[0]; + frame2 = frames[1]; + assert_equals( + frame1.contentWindow.navigator.serviceWorker.controller.scriptURL, + normalizeURL(init_worker_url), + 'Frame1 controller should not be null'); + assert_equals( + frame2.contentWindow.navigator.serviceWorker.controller, null, + 'Frame2 controller should be null'); + return navigator.serviceWorker.register(claim_worker_url, + {scope: claim_scope}); + }) + .then(function(registration) { + claim_worker = registration.installing; + claim_registration = registration; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + var saw_controllerchanged = new Promise(function(resolve) { + frame2.contentWindow.navigator.serviceWorker.oncontrollerchange = + function() { resolve(); } + }); + var channel = new MessageChannel(); + var saw_message = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data, 'PASS', + 'Worker call to claim() should fulfill.'); + resolve(); + }); + }); + claim_worker.postMessage({port: channel.port2}, [channel.port2]); + return Promise.all([saw_controllerchanged, saw_message]); + }) + .then(function() { + assert_equals( + frame1.contentWindow.navigator.serviceWorker.controller.scriptURL, + normalizeURL(init_worker_url), + 'Frame1 should not be influenced'); + assert_equals( + frame2.contentWindow.navigator.serviceWorker.controller.scriptURL, + normalizeURL(claim_worker_url), + 'Frame2 should be controlled by the new registration'); + frame1.remove(); + frame2.remove(); + return claim_registration.unregister(); + }) + .then(function() { + return service_worker_unregister_and_done(t, init_scope); + }); + }, 'Test claim client which is not using registration'); + +promise_test(function(t) { + var scope = 'resources/blank.html?longer-matched'; + var claim_scope = 'resources/blank.html?longer'; + var claim_worker_url = 'resources/claim-worker.js'; + var installing_worker_url = 'resources/empty-worker.js'; + var frame, claim_worker; + return with_iframe(scope) + .then(function(f) { + frame = f; + return navigator.serviceWorker.register( + claim_worker_url, {scope: claim_scope}); + }) + .then(function(registration) { + claim_worker = registration.installing; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return navigator.serviceWorker.register( + installing_worker_url, {scope: scope}); + }) + .then(function() { + var channel = new MessageChannel(); + var saw_message = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data, 'PASS', + 'Worker call to claim() should fulfill.'); + resolve(); + }); + }); + claim_worker.postMessage({port: channel.port2}, [channel.port2]); + return saw_message; + }) + .then(function() { + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller, null, + 'Frame should not be claimed when a longer-matched ' + + 'registration exists'); + frame.remove(); + return service_worker_unregister(t, claim_scope); + }) + .then(function() { + return service_worker_unregister_and_done(t, scope); + }); + }, 'Test claim client when there\'s a longer-matched registration not ' + + 'already used by the page'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/claim-using-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/claim-using-registration.https.html new file mode 100644 index 000000000..640b0be3e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/claim-using-registration.https.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<title>Service Worker: claim client using registration</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var scope = 'resources/'; + var frame_url = 'resources/blank.html?using-different-registration'; + var url1 = 'resources/empty.js'; + var url2 = 'resources/claim-worker.js'; + var worker, sw_registration, frame; + return service_worker_unregister_and_register(t, url1, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(frame_url); + }) + .then(function(f) { + frame = f; + return navigator.serviceWorker.register(url2, {scope: frame_url}); + }) + .then(function(registration) { + worker = registration.installing; + sw_registration = registration; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + var saw_controllerchanged = new Promise(function(resolve) { + frame.contentWindow.navigator.serviceWorker.oncontrollerchange = + function() { resolve(); } + }); + var channel = new MessageChannel(); + var saw_message = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data, 'PASS', + 'Worker call to claim() should fulfill.'); + resolve(); + }); + }); + worker.postMessage({port: channel.port2}, [channel.port2]); + return Promise.all([saw_controllerchanged, saw_message]); + }) + .then(function() { + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller.scriptURL, + normalizeURL(url2), + 'Frame1 controller scriptURL should be changed to url2'); + frame.remove(); + return sw_registration.unregister(); + }) + .then(function() { + return service_worker_unregister_and_done(t, scope); + }); + }, 'Test worker claims client which is using another registration'); + +promise_test(function(t) { + var scope = 'resources/blank.html?using-same-registration'; + var url1 = 'resources/empty.js'; + var url2 = 'resources/claim-worker.js'; + var frame, worker; + return service_worker_unregister_and_register(t, url1, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + return navigator.serviceWorker.register(url2, {scope: scope}); + }) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + var channel = new MessageChannel(); + var saw_message = new Promise(function(resolve) { + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data, 'FAIL: exception: InvalidStateError', + 'Worker call to claim() should reject with ' + + 'InvalidStateError'); + resolve(); + }); + }); + worker.postMessage({port: channel.port2}, [channel.port2]); + return saw_message; + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Test for the waiting worker claims a client which is using the the ' + + 'same registration'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/client-navigate.https.html b/testing/web-platform/tests/service-workers/service-worker/client-navigate.https.html new file mode 100644 index 000000000..a505e8722 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/client-navigate.https.html @@ -0,0 +1,117 @@ +<!doctype html> +<meta charset=utf-8> +<title>Service Worker: WindowClient.navigate</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + function wait_for_message(msg) { + return new Promise(function(resolve, reject) { + var get_message_data = function get_message_data(e) { + window.removeEventListener("message", get_message_data); + resolve(e.data); + } + window.addEventListener("message", get_message_data, false); + }); + } + + function run_test(controller, clientId, test) { + return new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = function(e) { + resolve(e.data); + }; + var message = { + port: channel.port2, + test: test, + clientId: clientId, + }; + controller.postMessage( + message, [channel.port2]); + }); + } + + promise_test(function(t) { + var worker = "resources/client-navigate-worker.js"; + var scope = "resources/client-navigate-frame.html"; + var controller, frame, clientId; + + return service_worker_unregister_and_register(t, worker, scope) + .then(reg => wait_for_state(t, reg.installing, "activated")) + .then(___ => with_iframe(scope)) + .then(f => { + frame = f; + controller = frame.contentWindow.navigator.serviceWorker.controller; + fetch_tests_from_worker(controller); + return wait_for_message() + }) + .then(({id}) => clientId = id) + .then(___ => run_test(controller, clientId, "test_client_navigate_success")) + .then(({result, url}) => { + assert_equals(result, "test_client_navigate_success"); + assert_equals( + url, new URL("resources/client-navigated-frame.html", + location).toString()); + assert_equals( + frame.contentWindow.location.href, + new URL("resources/client-navigated-frame.html", + location).toString()); + }) + .catch(unreached_rejection(t)) + .then(___ => service_worker_unregister(t, scope)); + }, "Frame location should update on successful navigation"); + + promise_test(function(t) { + var worker = "resources/client-navigate-worker.js"; + var scope = "resources/client-navigate-frame.html"; + var controller, frame, clientId; + + return service_worker_unregister_and_register(t, worker, scope) + .then(reg => wait_for_state(t, reg.installing, "activated")) + .then(___ => with_iframe(scope)) + .then(f => { + frame = f; + controller = frame.contentWindow.navigator.serviceWorker.controller; + fetch_tests_from_worker(controller); + return wait_for_message() + }) + .then(({id}) => clientId = id) + .then(___ => run_test(controller, clientId, "test_client_navigate_redirect")) + .then(({result, url}) => { + assert_equals(result, "test_client_navigate_redirect"); + assert_equals(url, ""); + assert_throws(null, function() { return frame.contentWindow.location.href }); + }) + .catch(unreached_rejection(t)) + .then(___ => service_worker_unregister(t, scope)); + }, "Frame location should not be accessible after redirect"); + + promise_test(function(t) { + var worker = "resources/client-navigate-worker.js"; + var scope = "resources/client-navigate-frame.html"; + var controller, frame, clientId; + + return service_worker_unregister_and_register(t, worker, scope) + .then(reg => wait_for_state(t, reg.installing, "activated")) + .then(___ => with_iframe(scope)) + .then(f => { + frame = f; + controller = frame.contentWindow.navigator.serviceWorker.controller; + fetch_tests_from_worker(controller); + return wait_for_message() + }) + .then(({id}) => clientId = id) + .then(___ => run_test(controller, clientId, "test_client_navigate_failure")) + .then(({result, url}) => { + assert_equals(result, "test_client_navigate_failure"); + assert_equals( + frame.contentWindow.location.href, + new URL("resources/client-navigate-frame.html", + location).toString()); + frame.contentWindow.document.body.style = "background-color: green" + }) + .catch(unreached_rejection(t)) + .then(___ => service_worker_unregister(t, scope)); + }, "Frame location should not update on failed navigation"); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-get-cross-origin.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-get-cross-origin.https.html new file mode 100644 index 000000000..3413acbf9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/clients-get-cross-origin.https.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>Service Worker: Clients.get across origins</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var host_info = get_host_info(); + +var scope = 'resources/blank.html?clients-get'; +var t = async_test('Test Clients.get() cross origin'); +var other_origin_iframe = host_info['HTTPS_REMOTE_ORIGIN'] + base_path() + + 'resources/clients-get-other-origin.html'; +var myOriginClientId; +t.step(function() { + service_worker_unregister_and_register( + t, 'resources/clients-get-worker.js', scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame1) { + myOriginClientId = frame1.contentDocument.body.textContent; + return with_iframe(other_origin_iframe); + }) + .then(function(frame2) { + window.addEventListener('message', on_message_other_origin, false); + frame2.contentWindow.postMessage( + {clientId: myOriginClientId, + message: 'get_client_id'}, + host_info['HTTPS_REMOTE_ORIGIN']); + }) + .catch(unreached_rejection(t)); + }); + +function on_message_other_origin(e) { + assert_equals(e.data.result, undefined); + t.done(); +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-get.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-get.https.html new file mode 100644 index 000000000..af38502dd --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/clients-get.https.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<title>Service Worker: Clients.get</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var host_info = get_host_info(); + +var scope = 'resources/clients-get-frame.html'; +var t = async_test('Test Clients.get()'); +var clientIds = []; +var frame; +t.step(function() { + service_worker_unregister_and_register( + t, 'resources/clients-get-worker.js', scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope + '#1'); + }) + .then(function(frame1) { + frame1.focus(); + return wait_for_clientId(); + }) + .then(function(clientId) { + clientIds.push(clientId); + return with_iframe(scope + '#2'); + }) + .then(function(frame2) { + frame = frame2; + return wait_for_clientId(); + }) + .then(function(clientId) { + clientIds.push(clientId); + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(on_message); + frame.contentWindow.navigator.serviceWorker.controller.postMessage( + {port:channel.port2, clientIds:clientIds, + message: 'get_client_ids'}, [channel.port2]); + }) + .catch(unreached_rejection(t)); + }); + +function wait_for_clientId() { + return new Promise(function(resolve, reject) { + function get_client_id(e) { + window.removeEventListener("message", get_client_id); + resolve(e.data.clientId); + } + window.addEventListener("message", get_client_id, false); + }); +} + +var expected = [ + /* visibilityState, focused, url, frameType */ + ['visible', true, new URL(scope + '#1', location).toString(), 'nested'], + ['visible', false, new URL(scope + '#2', location).toString(), 'nested'], + undefined +]; + +function on_message(e) { + assert_equals(e.data.length, 3); + assert_array_equals(e.data[0], expected[0]); + assert_array_equals(e.data[1], expected[1]); + assert_equals(e.data[2], expected[2]); + service_worker_unregister_and_done(t, scope); +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-matchall-client-types.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-matchall-client-types.https.html new file mode 100644 index 000000000..3645e8635 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/clients-matchall-client-types.https.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<title>Service Worker: Clients.matchAll with various clientTypes</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var scope = 'resources/clients-matchall-client-types'; +var iframe_url = scope + '-iframe.html'; +var shared_worker_url = scope + '-shared-worker.js'; + +/* visibilityState, focused, url, frameType */ +var expected_without_type = [ + ['visible', true, new URL(iframe_url, location).href, 'nested'] +]; +var expected_with_window = [ + ['visible', true, new URL(iframe_url, location).href, 'nested'] +]; +var expected_with_shared_worker = [ + [,,new URL(shared_worker_url, location).href, 'none'] +]; +var expected_with_all = [ + ['visible', true, new URL(iframe_url, location).href, 'nested'], + [,,new URL(shared_worker_url, location).href, 'none'] +]; + +function test_matchall(frame, expected, query_options) { + // Make sure the frame gets focus. + frame.focus(); + expected.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; }); + return new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = function(e) { + assert_equals(e.data.length, expected.length); + for (var i = 0; i < e.data.length; i++) + assert_array_equals(e.data[i], expected[i]); + resolve(); + }; + frame.contentWindow.navigator.serviceWorker.controller.postMessage( + {port:channel.port2, options:query_options}, + [channel.port2]); + }); +} + +promise_test(function(t) { + var frame; + return service_worker_unregister_and_register( + t, 'resources/clients-matchall-worker.js', scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(iframe_url); }) + .then(function(f) { + frame = f; + return new Promise(function(resolve, reject) { + var w = new SharedWorker(shared_worker_url); + w.port.onmessage = resolve; + }); + }) + .then(function() { + return test_matchall(frame, expected_without_type, {}); + }) + .then(function() { + return test_matchall(frame, expected_with_window, {type:'window'}); + }) + //.then(function() { + // return test_matchall(frame, expected_with_shared_worker, + // {type:'sharedworker'}); + // }) + //.then(function() { + // return test_matchall(frame, expected_with_all, {type:'all'}); + // }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Verify matchAll() with various client types'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html new file mode 100644 index 000000000..9285aef97 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<title>Service Worker: Clients.matchAll with includeUncontrolled</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var base_url = 'resources/blank.html'; // This is out-of-scope. +var scope = base_url + '?clients-matchAll-includeUncontrolled'; +var frames = []; + +// Creates 3 iframes, 2 for in-scope and 1 for out-of-scope. +// The frame opened for scope + '#2' is returned via a promise. +function create_iframes(scope) { + return with_iframe(base_url) + .then(function(frame0) { + frames.push(frame0); + return with_iframe(scope + '#1'); + }) + .then(function(frame1) { + frames.push(frame1); + return with_iframe(scope + '#2'); + }) + .then(function(frame2) { + frames.push(frame2); + return frame2; + }) +} + +var expected_without_include_uncontrolled = [ + /* visibilityState, focused, url, frameType */ + ['visible', false, new URL(scope + '#1', location).toString(), 'nested'], + ['visible', true, new URL(scope + '#2', location).toString(), 'nested'] +]; + +var expected_with_include_uncontrolled = [ + /* visibilityState, focused, url, frameType */ + ['visible', true, location.href, 'top-level'], + ['visible', false, new URL(scope + '#1', location).toString(), 'nested'], + ['visible', true, new URL(scope + '#2', location).toString(), 'nested'], + ['visible', false, new URL(base_url, location).toString(), 'nested'] +]; + +function test_matchall(frame, expected, query_options) { + // Make sure we have focus for '#2' frame and its parent window. + frame.focus(); + frame.contentWindow.focus(); + expected.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; }); + return new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = function(e) { + // Ignore hidden clients which may be coming from background tabs, or + // clients unrelated to this test. + var data = e.data.filter(function(info) { + return info[0] == 'visible' && + info[2].indexOf('service-worker') > -1; + }); + data.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; }); + assert_equals(data.length, expected.length); + for (var i = 0; i < data.length; i++) + assert_array_equals(data[i], expected[i]); + resolve(frame); + }; + frame.contentWindow.navigator.serviceWorker.controller.postMessage( + {port:channel.port2, options:query_options}, + [channel.port2]); + }); +} + +// Run clients.matchAll without and with includeUncontrolled=true. +// (We want to run the two tests sequentially in the same async_test +// so that we can use the same set of iframes without intefering each other. +async_test(function(t) { + service_worker_unregister_and_register( + t, 'resources/clients-matchall-worker.js', scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return create_iframes(scope); }) + .then(function(frame) { + return test_matchall(frame, expected_without_include_uncontrolled); + }) + .then(function(frame) { + return test_matchall(frame, expected_with_include_uncontrolled, + {includeUncontrolled:true}); + }) + .then(function() { + frames.forEach(function(f) { f.remove() }); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Verify matchAll() respect includeUncontrolled'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/clients-matchall.https.html b/testing/web-platform/tests/service-workers/service-worker/clients-matchall.https.html new file mode 100644 index 000000000..12e3da4e6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/clients-matchall.https.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<title>Service Worker: Clients.matchAll</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var scope = 'resources/blank.html?clients-matchAll'; +var t = async_test('Test Clients.matchAll()'); +var frames = []; +t.step(function() { + service_worker_unregister_and_register( + t, 'resources/clients-matchall-worker.js', scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(scope + '#1'); }) + .then(function(frame1) { + frames.push(frame1); + frame1.focus(); + return with_iframe(scope + '#2'); + }) + .then(function(frame2) { + frames.push(frame2); + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(onMessage); + frame2.contentWindow.navigator.serviceWorker.controller.postMessage( + {port:channel.port2}, [channel.port2]); + }) + .catch(unreached_rejection(t)); + }); + +var expected = [ + /* visibilityState, focused, url, frameType */ + ['visible', true, new URL(scope + '#1', location).toString(), 'nested'], + ['visible', false, new URL(scope + '#2', location).toString(), 'nested'] +]; + +function onMessage(e) { + assert_equals(e.data.length, 2); + assert_array_equals(e.data[0], expected[0]); + assert_array_equals(e.data[1], expected[1]); + frames.forEach(function(f) { f.remove(); }); + service_worker_unregister_and_done(t, scope); +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/controller-on-disconnect.https.html b/testing/web-platform/tests/service-workers/service-worker/controller-on-disconnect.https.html new file mode 100644 index 000000000..920d190e3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/controller-on-disconnect.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>Service Worker: Controller on load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +var t = async_test('controller is cleared on disconnected window'); +t.step(function() { + var url = 'resources/empty-worker.js'; + var scope = 'resources/blank.html'; + var registration; + var controller; + var frame; + service_worker_unregister_and_register(t, url, scope) + .then(t.step_func(function(swr) { + registration = swr; + return wait_for_state(t, registration.installing, 'activated'); + })) + .then(t.step_func(function() { + return with_iframe(scope) + })) + .then(t.step_func(function(f) { + frame = f; + var w = frame.contentWindow; + var swc = w.navigator.serviceWorker; + assert_true(swc.controller instanceof w.ServiceWorker, + 'controller should be a ServiceWorker object'); + + frame.remove(); + + assert_equals(swc.controller, null, + 'disconnected frame should not be controlled'); + + service_worker_unregister_and_done(t, scope); + })) + .catch(unreached_rejection(t)); + }); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/controller-on-load.https.html b/testing/web-platform/tests/service-workers/service-worker/controller-on-load.https.html new file mode 100644 index 000000000..ff3b7ce04 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/controller-on-load.https.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<title>Service Worker: Controller on load</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +var t = async_test('controller is set for a controlled document'); +t.step(function() { + var url = 'resources/empty-worker.js'; + var scope = 'resources/blank.html'; + var registration; + var controller; + var frame; + service_worker_unregister_and_register(t, url, scope) + .then(t.step_func(function(swr) { + registration = swr; + return wait_for_state(t, registration.installing, 'activated'); + })) + .then(t.step_func(function() { + return with_iframe(scope) + })) + .then(t.step_func(function(f) { + frame = f; + var w = frame.contentWindow; + controller = w.navigator.serviceWorker.controller; + assert_true(controller instanceof w.ServiceWorker, + 'controller should be a ServiceWorker object'); + assert_equals(controller.scriptURL, normalizeURL(url)); + + // objects from different windows should not be equal + assert_not_equals(controller, registration.active); + + return w.navigator.serviceWorker.getRegistration(); + })) + .then(t.step_func(function(frameRegistration) { + // SW objects from same window should be equal + assert_equals(frameRegistration.active, controller); + frame.remove(); + service_worker_unregister_and_done(t, scope); + })) + .catch(unreached_rejection(t)); + }); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/controller-on-reload.https.html b/testing/web-platform/tests/service-workers/service-worker/controller-on-reload.https.html new file mode 100644 index 000000000..4490c7079 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/controller-on-reload.https.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title>Service Worker: Controller on reload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +promise_test(function(t) { + var scope = 'resources/blank.html'; + var frame; + var registration; + var controller; + return service_worker_unregister(t, scope) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + return frame.contentWindow.navigator.serviceWorker.register( + 'resources/empty-worker.js', {scope: scope}); + }) + .then(function(swr) { + registration = swr; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + var w = frame.contentWindow; + assert_equals(w.navigator.serviceWorker.controller, null, + 'controller should be null until the document is ' + + 'reloaded'); + return new Promise(function(resolve) { + frame.onload = function() { resolve(); } + w.location.reload(); + }); + }) + .then(function() { + var w = frame.contentWindow; + controller = w.navigator.serviceWorker.controller; + assert_true(controller instanceof w.ServiceWorker, + 'controller should be a ServiceWorker object upon reload'); + + // objects from separate windows should not be equal + assert_not_equals(controller, registration.active); + + return w.navigator.serviceWorker.getRegistration(); + }) + .then(function(frameRegistration) { + assert_equals(frameRegistration.active, controller); + frame.remove(); + service_worker_unregister_and_done(t, scope); + }); + }, 'controller is set upon reload after registration'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html b/testing/web-platform/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html new file mode 100644 index 000000000..c06bf84ab --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +promise_test(function(t) { + var script = 'resources/extendable-event-async-waituntil.js'; + var scope = 'resources/async-waituntil'; + var worker; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { + var channel = new MessageChannel(); + var saw_message = new Promise(function(resolve) { + channel.port1.onmessage = function(e) { resolve(e.data); } + }); + worker.postMessage({port: channel.port2}, [channel.port2]); + return saw_message; + }) + .then(function(message) { + assert_equals(message, 'PASS'); + return service_worker_unregister_and_done(t, scope); + }) + }, 'Calling waitUntil asynchronously throws an exception'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/extendable-event-waituntil.https.html b/testing/web-platform/tests/service-workers/service-worker/extendable-event-waituntil.https.html new file mode 100644 index 000000000..003e703b1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/extendable-event-waituntil.https.html @@ -0,0 +1,125 @@ +<!DOCTYPE html> +<title>ExtendableEvent: waitUntil</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +function runTest(test, scope, onRegister) { + var script = 'resources/extendable-event-waituntil.js?' + scope; + service_worker_unregister_and_register(test, script, scope) + .then(function(registration) { + onRegister(registration.installing); + }); +} + +// Sends a SYN to the worker and asynchronously listens for an ACK; sets +// |obj.synced| to true once ack'd. +function syncWorker(test, worker, obj) { + var channel = new MessageChannel(); + channel.port1.onmessage = test.step_func(function(e) { + var message = e.data; + assert_equals(message, 'SYNC', + 'Should receive sync message from worker.'); + obj.synced = true; + channel.port1.postMessage('ACK'); + }); + worker.postMessage({port: channel.port2}, [channel.port2]); +} + +async_test(function(t) { + // Passing scope as the test switch for worker script. + var scope = 'resources/install-fulfilled'; + var onRegister = function(worker) { + var obj = {}; + wait_for_state(t, worker, 'installed') + .then(function() { + assert_true( + obj.synced, + 'state should be "installed" after the waitUntil promise ' + + 'for "oninstall" is fulfilled.'); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + syncWorker(t, worker, obj); + }; + runTest(t, scope, onRegister); + }, 'Test install event waitUntil fulfilled'); + +async_test(function(t) { + var scope = 'resources/install-multiple-fulfilled'; + var onRegister = function(worker) { + var obj1 = {}; + var obj2 = {}; + wait_for_state(t, worker, 'installed') + .then(function() { + assert_true( + obj1.synced && obj2.synced, + 'state should be "installed" after all waitUntil promises ' + + 'for "oninstall" are fulfilled.'); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + syncWorker(t, worker, obj1); + syncWorker(t, worker, obj2); + }; + runTest(t, scope, onRegister); + }, 'Test ExtendableEvent multiple waitUntil fulfilled.'); + +async_test(function(t) { + var scope = 'resources/install-reject-precedence'; + var onRegister = function(worker) { + wait_for_state(t, worker, 'redundant') + .then(function() { + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }; + runTest(t, scope, onRegister); + }, 'Test ExtendableEvent waitUntil reject precedence.'); + +async_test(function(t) { + var scope = 'resources/activate-fulfilled'; + var onRegister = function(worker) { + var obj = {}; + wait_for_state(t, worker, 'activating') + .then(function() { + syncWorker(t, worker, obj); + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { + assert_true( + obj.synced, + 'state should be "activated" after the waitUntil promise ' + + 'for "onactivate" is fulfilled.'); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }; + runTest(t, scope, onRegister); + }, 'Test activate event waitUntil fulfilled'); + +async_test(function(t) { + var scope = 'resources/install-rejected'; + var onRegister = function(worker) { + wait_for_state(t, worker, 'redundant') + .then(function() { + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }; + runTest(t, scope, onRegister); + }, 'Test install event waitUntil rejected'); + +async_test(function(t) { + var scope = 'resources/activate-rejected'; + var onRegister = function(worker) { + wait_for_state(t, worker, 'activated') + .then(function() { + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }; + runTest(t, scope, onRegister); + }, 'Test activate event waitUntil rejected.'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting-cache.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting-cache.https.html new file mode 100644 index 000000000..fdb64e15d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting-cache.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>Service Worker: canvas tainting of the fetched image using cached responses</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<body> +<script> +async_test(function(t) { + var SCOPE = 'resources/fetch-canvas-tainting-iframe.html?cache'; + var SCRIPT = 'resources/fetch-rewrite-worker.js'; + var host_info = get_host_info(); + + login_https(t) + .then(function() { + return service_worker_unregister_and_register(t, SCRIPT, SCOPE); + }) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + return new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data.results, 'finish'); + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }); + }) + .catch(unreached_rejection(t)); + }, 'Verify canvas tainting of fetched image in a Service Worker'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting.https.html new file mode 100644 index 000000000..bb7547392 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-canvas-tainting.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>Service Worker: canvas tainting of the fetched image</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<body> +<script> +async_test(function(t) { + var SCOPE = 'resources/fetch-canvas-tainting-iframe.html'; + var SCRIPT = 'resources/fetch-rewrite-worker.js'; + var host_info = get_host_info(); + + login_https(t) + .then(function() { + return service_worker_unregister_and_register(t, SCRIPT, SCOPE); + }) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + return new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data.results, 'finish'); + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }); + }) + .catch(unreached_rejection(t)); + }, 'Verify canvas tainting of fetched image in a Service Worker'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-cors-xhr.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-cors-xhr.https.html new file mode 100644 index 000000000..3b1f1d6bc --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-cors-xhr.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>Service Worker: CORS XHR of fetch()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<body> +<script> +async_test(function(t) { + var SCOPE = 'resources/fetch-cors-xhr-iframe.html'; + var SCRIPT = 'resources/fetch-rewrite-worker.js'; + var host_info = get_host_info(); + + login_https(t) + .then(function() { + return service_worker_unregister_and_register(t, SCRIPT, SCOPE); + }) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + return new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data.results, 'finish'); + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }); + }) + .catch(unreached_rejection(t)); + }, 'Verify CORS XHR of fetch() in a Service Worker'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-csp.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-csp.https.html new file mode 100644 index 000000000..bdb56c213 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-csp.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Service Worker: CSP control of fetch()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +async_test(function(t) { + var SCOPE = 'resources/fetch-csp-iframe.html'; + var SCRIPT = 'resources/fetch-rewrite-worker.js'; + var host_info = get_host_info(); + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + return new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data.results, 'finish'); + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }); + }) + .catch(unreached_rejection(t)); + }, 'Verify CSP control of fetch() in a Service Worker'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html new file mode 100644 index 000000000..dce1f794f --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<title>ServiceWorker: navigator.serviceWorker.waiting</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> + +promise_test(function(t) { + var scope = + 'resources/fetch-event-after-navigation-within-page-iframe.html' + + '?hashchange'; + var worker = 'resources/simple-intercept-worker.js'; + var frame; + + return service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return frame.contentWindow.fetch_url('simple.txt'); + }) + .then(function(response) { + assert_equals(response, 'intercepted by service worker'); + frame.contentWindow.location.hash = 'foo'; + return frame.contentWindow.fetch_url('simple.txt'); + }) + .then(function(response) { + assert_equals(response, 'intercepted by service worker'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + }, 'Service Worker should respond to fetch event after the hash changes'); + +promise_test(function(t) { + var scope = + 'resources/fetch-event-after-navigation-within-page-iframe.html' + + '?pushState'; + var worker = 'resources/simple-intercept-worker.js'; + var frame; + + return service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return frame.contentWindow.fetch_url('simple.txt'); + }) + .then(function(response) { + assert_equals(response, 'intercepted by service worker'); + frame.contentWindow.history.pushState('', '', 'bar'); + return frame.contentWindow.fetch_url('simple.txt'); + }) + .then(function(response) { + assert_equals(response, 'intercepted by service worker'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + }, 'Service Worker should respond to fetch event after the pushState'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html new file mode 100644 index 000000000..912e709ca --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +promise_test(function(t) { + var script = 'resources/fetch-event-async-respond-with-worker.js'; + var scope = 'resources/simple.html'; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + var channel = new MessageChannel(); + var saw_message = new Promise(function(resolve) { + channel.port1.onmessage = function(e) { resolve(e.data); } + }); + var worker = frame.contentWindow.navigator.serviceWorker.controller; + + worker.postMessage({port: channel.port2}, [channel.port2]); + frame.remove(); + return saw_message; + }) + .then(function(message) { + assert_equals(message, 'PASS'); + return service_worker_unregister_and_done(t, scope); + }) + }, 'Calling respondWith asynchronously throws an exception'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-network-error.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-network-error.https.html new file mode 100644 index 000000000..f6a3d0776 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-network-error.https.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>Service Worker: Fetch event network error</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var resolve_test_done; + +var test_done_promise = new Promise(function(resolve) { + resolve_test_done = resolve; + }); + +// Called by the child frame. +function notify_test_done(result) { + resolve_test_done(result); +} + +promise_test(function(t) { + var scope = 'resources/fetch-event-network-error-controllee-iframe.html'; + var script = 'resources/fetch-event-network-error-worker.js'; + var frame; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + return test_done_promise; + }) + .then(function(result) { + frame.remove(); + assert_equals(result, 'PASS'); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Rejecting the fetch event or using preventDefault() causes a network ' + + 'error'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-redirect.https.html new file mode 100644 index 000000000..17f4cb15e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-redirect.https.html @@ -0,0 +1,1102 @@ +<!DOCTYPE html> +<title>Service Worker: Fetch Event Redirect Handling</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> + +// ------------------------ +// Utilities for testing non-navigation requests that are intercepted with +// a redirect. + +var host_info = get_host_info(); +var worker = 'resources/fetch-rewrite-worker.js'; +var frameURL = host_info['HTTPS_ORIGIN'] + base_path() + + 'resources/fetch-event-redirect-iframe.html'; +var baseScope = 'resources/'; +var redirect = 'redirect.py'; +var success = base_path() + 'resources/success.py'; + +function redirect_fetch_test(t, test) { + var scope = baseScope + test.name; + service_worker_unregister_and_register(t, worker, scope).then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }).then(function() { + return with_iframe(scope + '?url=' + encodeURIComponent(frameURL)); + }).then(function(frame) { + var hostKeySuffix = test['url_credentials'] ? '_WITH_CREDS' : ''; + + var acaorigin = ''; + var host = host_info['HTTPS_ORIGIN' + hostKeySuffix]; + if (test['redirect_dest'] === 'no-cors') { + host = host_info['HTTPS_REMOTE_ORIGIN' + hostKeySuffix] + } else if (test['redirect_dest'] === 'cors') { + acaorigin = '?ACAOrigin=' + encodeURIComponent(host_info['HTTPS_ORIGIN']); + host = host_info['HTTPS_REMOTE_ORIGIN' + hostKeySuffix] + } + + var dest = '?Redirect=' + encodeURIComponent(host + success + acaorigin); + + var expectedTypeParam = test['expected_type'] + ? '&expected_type=' + test['expected_type'] + : ''; + + var expectedRedirectedParam = test['expected_redirected'] + ? '&expected_redirected=' + test['expected_redirected'] + : ''; + + var url = scope + + '?url=' + encodeURIComponent(redirect + dest) + + expectedTypeParam + expectedRedirectedParam + + var p = new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = function(e) { + frame.remove(); + if (e.data.result === 'reject') { + reject(e.data.detail); + } else if (e.data.result === 'success') { + resolve(e.data.result); + } else { + resolve(e.data.detail); + } + }; + frame.contentWindow.postMessage({ + url: url, + request_init: test.request_init, + redirect_dest: test.redirect_dest, + expected_type: test.expected_type, + expected_redirected: test.expected_redirected, + }, '*', [channel.port2]); + }); + + if (test.should_reject) { + return assert_promise_rejects(p); + } + + return p.then(function(result) { + if (result !== 'success') { + throw(new Error(result)); + } + }); + }).then(function() { + return service_worker_unregister_and_done(t, scope); + }).catch(unreached_rejection(t)); +} + +// ------------------------ +// Test every combination of: +// - RequestMode (same-origin, cors, no-cors) +// - RequestRedirect (manual, follow, error) +// - redirect destination origin (same-origin, cors, no-cors) +// - redirect destination credentials (no user/pass, user/pass) +// +// TODO: add navigation requests +// TODO: add redirects to data URI and verify same-origin data-URL flag behavior +// TODO: add test where original redirect URI is cross-origin +// TODO: verify final method is correct for 301, 302, and 303 +// TODO: verify CORS redirect results in all further redirects being +// considered cross origin + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-cors-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'cors' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, cors mode Request redirected to ' + + 'same-origin without credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-cors-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'cors' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, cors mode Request redirected to ' + + 'no-cors without credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-cors-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'cors' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, cors mode Request redirected to ' + + 'cors without credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-sameorigin-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'same-origin' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' + + 'same-origin without credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-sameorigin-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'same-origin' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' + + 'no-cors without credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-sameorigin-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'same-origin' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' + + 'cors without credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-nocors-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'no-cors' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' + + 'same-origin without credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-nocors-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' + + 'no-cors without credentials should succeed interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-nocors-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' + + 'cors without credentials should succeed interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-cors-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'cors' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, cors mode Request redirected to ' + + 'same-origin with credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-cors-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'cors' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, cors mode Request redirected to ' + + 'no-cors with credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-cors-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'cors' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, cors mode Request redirected to ' + + 'cors with credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-sameorigin-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'same-origin' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' + + 'same-origin with credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-sameorigin-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'same-origin' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' + + 'no-cors with credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-sameorigin-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'same-origin' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, same-origin mode Request redirected to ' + + 'cors with credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-nocors-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'no-cors' + }, + // should reject because only navigations can be intercepted with + // opaqueredirect responses + should_reject: true + }); +}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' + + 'same-origin with credentials should fail opaqueredirect interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-nocors-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' + + 'no-cors with credentials should succeed interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-manual-nocors-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'opaqueredirect', + expected_redirected: false, + request_init: { + redirect: 'manual', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, manual redirect, no-cors mode Request redirected to ' + + 'cors with credentials should succeed interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-cors-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'basic', + expected_redirected: true, + request_init: { + redirect: 'follow', + mode: 'cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, cors mode Request redirected to ' + + 'same-origin without credentials should succeed interception ' + + 'and response should be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-cors-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'should-not-get-a-response', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'cors' + }, + // should reject because CORS requests require CORS headers on cross-origin + // resources + should_reject: true + }); +}, 'Non-navigation, follow redirect, cors mode Request redirected to ' + + 'no-cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-cors-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'cors', + expected_redirected: true, + request_init: { + redirect: 'follow', + mode: 'cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, cors mode Request redirected to ' + + 'cors without credentials should succeed interception ' + + 'and response should be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-sameorigin-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'basic', + expected_redirected: true, + request_init: { + redirect: 'follow', + mode: 'same-origin' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' + + 'same-origin without credentials should succeed interception ' + + 'and response should be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-sameorigin-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'should-not-get-a-response', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'same-origin' + }, + // should reject because same-origin requests cannot load cross-origin + // resources + should_reject: true + }); +}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' + + 'no-cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-sameorigin-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'should-not-get-a-response', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'same-origin' + }, + // should reject because same-origin requests cannot load cross-origin + // resources + should_reject: true + }); +}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' + + 'cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-nocors-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'basic', + expected_redirected: true, + request_init: { + redirect: 'follow', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' + + 'same-origin without credentials should succeed interception ' + + 'and response should be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-nocors-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'opaque', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' + + 'no-cors without credentials should succeed interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-nocors-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'opaque', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' + + 'cors without credentials should succeed interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-cors-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'basic', + expected_redirected: true, + request_init: { + redirect: 'follow', + mode: 'cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, cors mode Request redirected to ' + + 'same-origin with credentials should succeed interception ' + + 'and response should be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-cors-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'should-not-get-a-response', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'cors' + }, + // should reject because CORS requests require CORS headers on cross-origin + // resources + should_reject: true + }); +}, 'Non-navigation, follow redirect, cors mode Request redirected to ' + + 'no-cors with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-cors-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'cors', + expected_redirected: true, + request_init: { + redirect: 'follow', + mode: 'cors' + }, + // should reject because CORS requests do not allow user/pass entries in + // cross-origin URLs + // NOTE: https://github.com/whatwg/fetch/issues/112 + should_reject: true + }); +}, 'Non-navigation, follow redirect, cors mode Request redirected to ' + + 'cors with credentials should fail interception ' + + 'and response should be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-sameorigin-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'basic', + expected_redirected: true, + request_init: { + redirect: 'follow', + mode: 'same-origin' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' + + 'same-origin with credentials should succeed interception ' + + 'and response should be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-sameorigin-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'should-not-get-a-response', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'same-origin' + }, + // should reject because same-origin requests cannot load cross-origin + // resources + should_reject: true + }); +}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' + + 'no-cors with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-sameorigin-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'should-not-get-a-response', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'same-origin' + }, + // should reject because same-origin requests cannot load cross-origin + // resources + should_reject: true + }); +}, 'Non-navigation, follow redirect, same-origin mode Request redirected to ' + + 'cors with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-nocors-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'basic', + expected_redirected: true, + request_init: { + redirect: 'follow', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' + + 'same-origin with credentials should succeed interception ' + + 'and response should be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-nocors-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'opaque', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' + + 'no-cors with credentials should succeed interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-follow-nocors-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'opaque', + expected_redirected: false, + request_init: { + redirect: 'follow', + mode: 'no-cors' + }, + should_reject: false + }); +}, 'Non-navigation, follow redirect, no-cors mode Request redirected to ' + + 'cors with credentials should succeed interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-cors-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, cors mode Request redirected to ' + + 'same-origin without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-cors-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, cors mode Request redirected to ' + + 'no-cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-cors-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, cors mode Request redirected to ' + + 'cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-sameorigin-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'same-origin' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' + + 'same-origin without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-sameorigin-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'same-origin' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' + + 'no-cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-sameorigin-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'same-origin' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' + + 'cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-nocors-redirects-to-sameorigin-nocreds', + redirect_dest: 'same-origin', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'no-cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' + + 'same-origin without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-nocors-redirects-to-nocors-nocreds', + redirect_dest: 'no-cors', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'no-cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' + + 'no-cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-nocors-redirects-to-cors-nocreds', + redirect_dest: 'cors', + url_credentials: false, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'no-cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' + + 'cors without credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-cors-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, cors mode Request redirected to ' + + 'same-origin with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-cors-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, cors mode Request redirected to ' + + 'no-cors with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-cors-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, cors mode Request redirected to ' + + 'cors with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-sameorigin-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'same-origin' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' + + 'same-origin with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-sameorigin-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'same-origin' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' + + 'no-cors with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-sameorigin-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'same-origin' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, same-origin mode Request redirected to ' + + 'cors with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-nocors-redirects-to-sameorigin-creds', + redirect_dest: 'same-origin', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'no-cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' + + 'same-origin with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-nocors-redirects-to-nocors-creds', + redirect_dest: 'no-cors', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'no-cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' + + 'no-cors with credentials should fail interception ' + + 'and response should not be redirected'); + +async_test(function(t) { + redirect_fetch_test(t, { + name: 'nonav-error-nocors-redirects-to-cors-creds', + redirect_dest: 'cors', + url_credentials: true, + expected_type: 'error', + expected_redirected: false, + request_init: { + redirect: 'error', + mode: 'no-cors' + }, + // should reject because requests with 'error' RequestRedirect cannot be + // redirected. + should_reject: true + }); +}, 'Non-navigation, error redirect, no-cors mode Request redirected to ' + + 'cors with credentials should fail interception and response should not ' + + 'be redirected'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html new file mode 100644 index 000000000..5d3346e7b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +promise_test(function(t) { + var script = + 'resources/fetch-event-respond-with-stops-propagation-worker.js'; + var scope = 'resources/simple.html'; + + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + var channel = new MessageChannel(); + var saw_message = new Promise(function(resolve) { + channel.port1.onmessage = function(e) { resolve(e.data); } + }); + var worker = frame.contentWindow.navigator.serviceWorker.controller; + + worker.postMessage({port: channel.port2}, [channel.port2]); + frame.remove(); + return saw_message; + }) + .then(function(message) { + assert_equals(message, 'PASS'); + return service_worker_unregister_and_done(t, scope); + }) + }, 'respondWith() invokes stopImmediatePropagation()'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-event.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-event.https.html new file mode 100644 index 000000000..dfa96ebb5 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-event.https.html @@ -0,0 +1,575 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +var worker = 'resources/fetch-event-test-worker.js'; + +async_test(function(t) { + var scope = 'resources/simple.html?string'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + assert_equals( + frame.contentDocument.body.textContent, + 'Test string', + 'Service Worker should respond to fetch with a test string'); + assert_equals( + frame.contentDocument.contentType, + 'text/plain', + 'The content type of the response created with a string should be text/plain'); + assert_equals( + frame.contentDocument.characterSet, + 'UTF-8', + 'The character set of the response created with a string should be UTF-8'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with string'); + +async_test(function(t) { + var scope = 'resources/simple.html?blob'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + assert_equals( + frame.contentDocument.body.textContent, + 'Test blob', + 'Service Worker should respond to fetch with a test string'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with blob body'); + +async_test(function(t) { + var scope = 'resources/simple.html?referrer'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + assert_equals( + frame.contentDocument.body.textContent, + 'Referrer: ' + document.location.href, + 'Service Worker should respond to fetch with the referrer URL'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with the referrer URL'); + +function run_referrer_policy_tests(frame, referrer, href, origin) { + return frame.contentWindow.fetch('resources/simple.html?referrerFull', + {method: "GET", referrer: referrer}) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: ' + href + '\n' + + 'ReferrerPolicy: no-referrer-when-downgrade', + 'Service Worker should respond to fetch with the referrer URL when a member of RequestInit is present'); + var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() + + '/resources/simple.html?referrerFull'; + return frame.contentWindow.fetch(http_url, + {method: "GET", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: about:client\n' + + 'ReferrerPolicy: no-referrer-when-downgrade', + 'Service Worker should respond to fetch with no referrer when a member of RequestInit is present with an HTTP request'); + return frame.contentWindow.fetch('resources/simple.html?referrerFull', + {referrerPolicy: "", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: ' + href + '\n' + + 'ReferrerPolicy: no-referrer-when-downgrade', + 'Service Worker should respond to fetch with the referrer with ""'); + var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() + + '/resources/simple.html?referrerFull'; + return frame.contentWindow.fetch(http_url, + {referrerPolicy: "", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: about:client\n' + + 'ReferrerPolicy: no-referrer-when-downgrade', + 'Service Worker should respond to fetch with no referrer with ""'); + return frame.contentWindow.fetch('resources/simple.html?referrerFull', + {referrerPolicy: "origin", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: ' + origin + '/' + '\n' + + 'ReferrerPolicy: origin', + 'Service Worker should respond to fetch with the referrer origin with "origin" and a same origin request'); + var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() + + '/resources/simple.html?referrerFull'; + return frame.contentWindow.fetch(http_url, + {referrerPolicy: "origin", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: ' + origin + '/' + '\n' + + 'ReferrerPolicy: origin', + 'Service Worker should respond to fetch with the referrer origin with "origin" and a cross origin request'); + return frame.contentWindow.fetch('resources/simple.html?referrerFull', + {referrerPolicy: "origin-when-cross-origin", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: ' + href + '\n' + + 'ReferrerPolicy: origin-when-cross-origin', + 'Service Worker should respond to fetch with the referrer URL with "origin-when-cross-origin" and a same origin request'); + var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() + + '/resources/simple.html?referrerFull'; + return frame.contentWindow.fetch(http_url, + {referrerPolicy: "origin-when-cross-origin", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: ' + origin + '/' + '\n' + + 'ReferrerPolicy: origin-when-cross-origin', + 'Service Worker should respond to fetch with the referrer origin with "origin-when-cross-origin" and a cross origin request'); + return frame.contentWindow.fetch('resources/simple.html?referrerFull', + {referrerPolicy: "no-referrer-when-downgrade", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: ' + href + '\n' + + 'ReferrerPolicy: no-referrer-when-downgrade', + 'Service Worker should respond to fetch with no referrer with "no-referrer-when-downgrade" and a same origin request'); + var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() + + '/resources/simple.html?referrerFull'; + return frame.contentWindow.fetch(http_url, + {referrerPolicy: "no-referrer-when-downgrade", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: about:client\n' + + 'ReferrerPolicy: no-referrer-when-downgrade', + 'Service Worker should respond to fetch with no referrer with "no-referrer-when-downgrade" and an HTTP request'); + var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() + + '/resources/simple.html?referrerFull'; + return frame.contentWindow.fetch(http_url, {referrerPolicy: "unsafe-url", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: ' + href + '\n' + + 'ReferrerPolicy: unsafe-url', + 'Service Worker should respond to fetch with no referrer with "unsafe-url"'); + return frame.contentWindow.fetch('resources/simple.html?referrerFull', + {referrerPolicy: "no-referrer", referrer: referrer}); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + assert_equals( + response_text, + 'Referrer: about:client\n' + + 'ReferrerPolicy: no-referrer', + 'Service Worker should respond to fetch with no referrer URL with "no-referrer"'); + }); +} + +async_test(function(t) { + var scope = 'resources/simple.html?referrerPolicy'; + var frame; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + assert_equals( + frame.contentDocument.body.textContent, + 'ReferrerPolicy: no-referrer-when-downgrade', + 'Service Worker should respond to fetch with the default referrer policy'); + // First, run the referrer policy tests without passing a referrer in RequestInit. + return run_referrer_policy_tests(frame, undefined, frame.contentDocument.location.href, + frame.contentDocument.location.origin); + }) + .then(function() { + // Now, run the referrer policy tests while passing a referrer in RequestInit. + var referrer = get_host_info()['HTTPS_ORIGIN'] + base_path() + 'fake-referrer'; + return run_referrer_policy_tests(frame, 'fake-referrer', referrer, + frame.contentDocument.location.origin); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with the referrer URL'); + +async_test(function(t) { + var scope = 'resources/simple.html?clientId'; + var frame; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + assert_equals( + frame.contentDocument.body.textContent, + 'Client ID Not Found', + 'Service Worker should respond to fetch with a client id'); + return frame.contentWindow.fetch('resources/other.html?clientId'); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + var new_client_id = response_text.substr(17); + assert_equals( + response_text.substr(0, 15), + 'Client ID Found', + 'Service Worker should respond to fetch with an existing client id'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with an existing client id'); + +async_test(function(t) { + var scope = 'resources/simple.html?ignore'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + assert_equals(frame.contentDocument.body.textContent, + 'Here\'s a simple html file.\n', + 'Response should come from fallback to native fetch'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker does not respond to fetch event'); + +async_test(function(t) { + var scope = 'resources/simple.html?null'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + assert_equals(frame.contentDocument.body.textContent, + '', + 'Response should be the empty string'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with null response body'); + +async_test(function(t) { + var scope = 'resources/simple.html?fetch'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + assert_equals(frame.contentDocument.body.textContent, + 'Here\'s an other html file.\n', + 'Response should come from fetched other file'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker fetches other file in fetch event'); + +async_test(function(t) { + var scope = 'resources/simple.html?form-post'; + var frame_name = 'xhr-post-frame'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function(sw) { + return new Promise(function(resolve) { + var frame = document.createElement('iframe'); + frame.name = frame_name; + document.body.appendChild(frame); + var form = document.createElement('form'); + form.target = frame_name; + form.action = scope; + form.method = 'post'; + var input1 = document.createElement('input'); + input1.type = 'text'; + input1.value = 'testValue1'; + input1.name = 'testName1' + form.appendChild(input1); + var input2 = document.createElement('input'); + input2.type = 'text'; + input2.value = 'testValue2'; + input2.name = 'testName2' + form.appendChild(input2); + document.body.appendChild(form); + frame.onload = function() { + document.body.removeChild(form); + resolve(frame); + }; + form.submit(); + }); + }) + .then(function(frame) { + assert_equals(frame.contentDocument.body.textContent, + 'POST:application/x-www-form-urlencoded:' + + 'testName1=testValue1&testName2=testValue2'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with POST form'); + +async_test(function(t) { + var scope = 'resources/simple.html?multiple-respond-with'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + assert_equals( + frame.contentDocument.body.textContent, + '(0)(1)[InvalidStateError](2)[InvalidStateError]', + 'Multiple calls of respondWith must throw InvalidStateErrors.'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Multiple calls of respondWith must throw InvalidStateErrors'); + +async_test(function(t) { + var scope = 'resources/simple.html?used-check'; + var first_frame; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(frame) { + assert_equals(frame.contentDocument.body.textContent, + 'Here\'s an other html file.\n', + 'Response should come from fetched other file'); + first_frame = frame; + return with_iframe(scope); + }) + .then(function(frame) { + // When we access to the scope in the second time, the content of the + // response is generated inside the ServiceWorker. The body contains + // the value of bodyUsed of the first response which is already + // consumed by FetchEvent.respondWith method. + assert_equals( + frame.contentDocument.body.textContent, + 'bodyUsed: true', + 'event.respondWith must set the used flag.'); + first_frame.remove(); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker event.respondWith must set the used flag'); + +async_test(function(t) { + var scope = 'resources/simple.html?fragment-check'; + var fragment = '#/some/fragment'; + var first_frame; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope + fragment); }) + .then(function(frame) { + assert_equals( + frame.contentDocument.body.textContent, + 'Fragment Found :' + fragment, + 'Service worker should expose URL fragments in request.'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker must not expose FetchEvent URL fragments.'); +async_test(function(t) { + var scope = 'resources/simple.html?cache'; + var frame; + var cacheTypes = [ + undefined, 'default', 'no-store', 'reload', 'no-cache', 'force-cache', 'only-if-cached' + ]; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + assert_equals(frame.contentWindow.document.body.textContent, 'default'); + var tests = cacheTypes.map(function(type) { + return new Promise(function(resolve, reject) { + var init = {cache: type}; + if (type === 'only-if-cached') { + // For privacy reasons, for the time being, only-if-cached + // requires the mode to be same-origin. + init.mode = 'same-origin'; + } + return frame.contentWindow.fetch(scope + '=' + type, init) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + var expected = (type === undefined) ? 'default' : type; + assert_equals(response_text, expected, + 'Service Worker should respond to fetch with the correct type'); + }) + .then(resolve) + .catch(reject); + }); + }); + }) + .then(function() { + return new Promise(function(resolve, reject) { + frame.addEventListener('load', function onLoad() { + frame.removeEventListener('load', onLoad); + try { + assert_equals(frame.contentWindow.document.body.textContent, + 'no-cache'); + resolve(); + } catch (e) { + reject(e); + } + }); + frame.contentWindow.location.reload(); + }); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with the correct cache types'); + +async_test(function(t) { + var scope = 'resources/simple.html?eventsource'; + var frame; + + function test_eventsource(opts) { + return new Promise(function(resolve, reject) { + var eventSource = new frame.contentWindow.EventSource(scope, opts); + eventSource.addEventListener('message', function(msg) { + eventSource.close(); + try { + var data = JSON.parse(msg.data); + assert_equals(data.mode, 'cors', + 'EventSource should make CORS requests.'); + assert_equals(data.cache, 'no-store', + 'EventSource should bypass the http cache.'); + var expectedCredentials = opts.withCredentials ? 'include' + : 'same-origin'; + assert_equals(data.credentials, expectedCredentials, + 'EventSource should pass correct credentials mode.'); + resolve(); + } catch (e) { + reject(e); + } + }); + eventSource.addEventListener('error', function(e) { + eventSource.close(); + reject('The EventSource fired an error event.'); + }); + }); + } + + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return test_eventsource({ withCredentials: false }); + }) + .then(function() { + return test_eventsource({ withCredentials: true }); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker should intercept EventSource'); + +async_test(function(t) { + var scope = 'resources/simple.html?integrity'; + var frame; + var integrity_metadata = 'gs0nqru8KbsrIt5YToQqS9fYao4GQJXtcId610g7cCU='; + + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + // A request has associated integrity metadata (a string). + // Unless stated otherwise, it is the empty string. + assert_equals( + frame.contentDocument.body.textContent, ''); + + return new Promise(function(resolve, reject) { + return frame.contentWindow.fetch(scope, + {'integrity': integrity_metadata}) + .then(function(response) { + return response.text(); + }) + .then(function(response_text) { + // Should get the same integrity metadata. + assert_equals(response_text, integrity_metadata, + 'Service Worker should respond to fetch with the correct integrity'); + }) + .then(resolve()) + .catch(reject()); + }); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with the correct integrity_metadata'); + +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html new file mode 100644 index 000000000..cc1dac472 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-frame-resource.https.html @@ -0,0 +1,221 @@ +<!DOCTYPE html> +<title>Service Worker: Fetch for the frame loading.</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +var worker = 'resources/fetch-rewrite-worker.js'; +var path = base_path() + 'resources/fetch-access-control.py'; +var host_info = get_host_info(); + +if (window.testRunner) { + testRunner.setCanOpenWindows(); +} + +function getLoadedObject(win, contentFunc, closeFunc) { + return new Promise(function(resolve) { + function done(contentString) { + var result = null; + // fetch-access-control.py returns a string like "report( <json> )". + // Eval the returned string with a report functionto get the json + // object. + try { + function report(obj) { result = obj }; + eval(contentString); + } catch(e) { + // just resolve null if we get unexpected page content + } + closeFunc(win); + resolve(result); + } + + // We can't catch the network error on window. So we use the timer. + var timeout = setTimeout(function() { + // Failure pages are considered cross-origin in some browsers. This + // means you cannot even .resolve() the window because the check for + // the .then property will throw. Instead, treat cross-origin + // failure pages as the empty string which will fail to parse as the + // expected json result. + var content = ''; + try { + content = contentFunc(win); + } catch(e) { + // use default empty string for cross-domain window + } + done(content); + }, 10000); + + win.onload = function() { + clearTimeout(timeout); + var content = contentFunc(win); + done(content); + }; + }); +} + +function getLoadedFrameAsObject(frame) { + return getLoadedObject(frame, function(f) { + return f.contentDocument.body.textContent; + }, function(f) { + f.parentNode.removeChild(f); + }); +} + +function getLoadedWindowAsObject(win) { + return getLoadedObject(win, function(w) { + return w.document.body.textContent + }, function(w) { + w.close(); + }); +} + +async_test(function(t) { + var scope = 'resources/fetch-frame-resource/frame-basic'; + var frame; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { + frame = document.createElement('iframe'); + frame.src = + scope + '?url=' + + encodeURIComponent(host_info['HTTPS_ORIGIN'] + path); + document.body.appendChild(frame); + return getLoadedFrameAsObject(frame); + }) + .then(function(result) { + assert_equals( + result.jsonpResult, + 'success', + 'Basic type response could be loaded in the iframe.'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Basic type response could be loaded in the iframe.'); + +async_test(function(t) { + var scope = 'resources/fetch-frame-resource/frame-cors'; + var frame; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { + frame = document.createElement('iframe'); + frame.src = + scope + '?mode=cors&url=' + + encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path + + '?ACAOrigin=' + host_info['HTTPS_ORIGIN']); + document.body.appendChild(frame); + return getLoadedFrameAsObject(frame); + }) + .then(function(result) { + assert_equals( + result.jsonpResult, + 'success', + 'CORS type response could be loaded in the iframe.'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'CORS type response could be loaded in the iframe.'); + +async_test(function(t) { + var scope = 'resources/fetch-frame-resource/frame-opaque'; + var frame; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { + frame = document.createElement('iframe'); + frame.src = + scope + '?mode=no-cors&url=' + + encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path); + document.body.appendChild(frame); + return getLoadedFrameAsObject(frame); + }) + .then(function(result) { + assert_equals( + result, + null, + 'Opaque type response could not be loaded in the iframe.'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Opaque type response could not be loaded in the iframe.'); + +async_test(function(t) { + var scope = 'resources/fetch-frame-resource/window-basic'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { + var win = window.open( + scope + '?url=' + + encodeURIComponent(host_info['HTTPS_ORIGIN'] + path)); + return getLoadedWindowAsObject(win); + }) + .then(function(result) { + assert_equals( + result.jsonpResult, + 'success', + 'Basic type response could be loaded in the new window.'); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Basic type response could be loaded in the new window.'); + +async_test(function(t) { + var scope = 'resources/fetch-frame-resource/window-cors'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { + var win = window.open( + scope + '?mode=cors&url=' + + encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path + + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'])); + return getLoadedWindowAsObject(win); + }) + .then(function(result) { + assert_equals( + result.jsonpResult, + 'success', + 'CORS type response could be loaded in the new window.'); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'CORS type response could be loaded in the new window.'); + +async_test(function(t) { + var scope = 'resources/fetch-frame-resource/window-opaque'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { + var win = window.open( + scope + '?mode=no-cors&url=' + + encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + path)); + return getLoadedWindowAsObject(win); + }) + .then(function(result) { + assert_equals( + result, + null, + 'Opaque type response could not be loaded in the new window.'); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Opaque type response could not be loaded in the new window.'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-header-visibility.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-header-visibility.https.html new file mode 100644 index 000000000..36bf16f32 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-header-visibility.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<title>Service Worker: Visibility of headers during fetch.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> + var worker = 'resources/fetch-rewrite-worker.js'; + var path = base_path() + 'resources/fetch-access-control.py'; + var host_info = get_host_info(); + var frame; + + async_test(function(t) { + var scope = 'resources/fetch-header-visibility-iframe.html'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { + frame = document.createElement('iframe'); + frame.src = scope; + document.body.appendChild(frame); + + // Resolve a promise when we recieve 2 success messages + return new Promise(function(resolve, reject) { + var remaining = 4; + function onMessage(e) { + if (e.data == 'PASS') { + remaining--; + if (remaining == 0) { + resolve(); + } else { + return; + } + } else { + reject(e.data); + } + + window.removeEventListener('message', onMessage); + } + window.addEventListener('message', onMessage); + }); + }) + .then(function(result) { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Visibility of defaulted headers during interception'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html new file mode 100644 index 000000000..fb9fa9b7b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Service Worker: Mixed content of fetch()</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<body></body> +<script> +if (window.testRunner) { + // In Chromium we need to change the setting to disallow displaying insecure + // contents. + testRunner.overridePreference('WebKitAllowDisplayingInsecureContent', false); +} + +async_test(function(t) { + var host_info = get_host_info(); + window.addEventListener('message', t.step_func(on_message), false); + with_iframe( + host_info['HTTPS_ORIGIN'] + base_path() + + 'resources/fetch-mixed-content-iframe.html?target=inscope'); + function on_message(e) { + assert_equals(e.data.results, 'finish'); + t.done(); + } + }, 'Verify Mixed content of fetch() in a Service Worker'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html new file mode 100644 index 000000000..cee89ce16 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Service Worker: Mixed content of fetch()</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<body></body> +<script> +if (window.testRunner) { + // In Chromium we need to change the setting to disallow displaying insecure + // contents. + testRunner.overridePreference('WebKitAllowDisplayingInsecureContent', false); +} + +async_test(function(t) { + var host_info = get_host_info(); + window.addEventListener('message', t.step_func(on_message), false); + with_iframe( + host_info['HTTPS_ORIGIN'] + base_path() + + 'resources/fetch-mixed-content-iframe.html?target=outscope'); + function on_message(e) { + assert_equals(e.data.results, 'finish'); + t.done(); + } + }, 'Verify Mixed content of fetch() in a Service Worker'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-base-url.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-base-url.https.html new file mode 100644 index 000000000..0405ed709 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-base-url.https.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<title>Service Worker: CSS's base URL must be the request URL even when fetched from other URL</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +async_test(function(t) { + var SCOPE = 'resources/fetch-request-css-base-url-iframe.html'; + var SCRIPT = 'resources/fetch-request-css-base-url-worker.js'; + var worker; + var testDonePromise; + + return service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { + return new Promise(function(resolve) { + var channel = new MessageChannel(); + testDonePromise = new Promise(function(resolveTestDone) { + channel.port1.onmessage = t.step_func(function(msg) { + if (msg.data.ready) { + resolve(); + return; + } + var result = msg.data; + var base = get_host_info()['HTTPS_ORIGIN'] + base_path(); + assert_equals( + result.url, + base + 'resources/dummy.png', + 'The base URL while loading the images referred from CSS ' + + 'must be the request URL of CSS.'); + assert_equals( + result.referrer, + base + 'resources/fetch-request-css-base-url-style.css', + 'While loading the image defined in CSS the referrer must ' + + 'be the request URL of CSS.'); + resolveTestDone(); + }); + }); + worker.postMessage( + {port: channel.port2}, [channel.port2]); + }); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(f) { + return testDonePromise.then(function() { + f.remove(); + return service_worker_unregister_and_done(t, SCOPE); + }); + }) + .catch(unreached_rejection(t)); + }, 'CSS\'s base URL must be the request URL even when fetched from other URL.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-images.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-images.https.html new file mode 100644 index 000000000..777308241 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-css-images.https.html @@ -0,0 +1,176 @@ +<!DOCTYPE html> +<title>Service Worker: FetchEvent for css image</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +var SCOPE = 'resources/fetch-request-resources-iframe.https.html'; +var SCRIPT = 'resources/fetch-request-resources-worker.js'; +var host_info = get_host_info(); +var LOCAL_URL = + host_info['HTTPS_ORIGIN'] + base_path() + 'resources/dummy?test'; +var REMOTE_URL = + host_info['HTTPS_REMOTE_ORIGIN'] + base_path() + 'resources/dummy?test'; + +function css_image_test(expected_results, frame, url, type, + expected_mode, expected_credentials) { + expected_results[url] = { + url: url, + mode: expected_mode, + credentials: expected_credentials, + message: 'CSSImage load (url:' + url + ' type:' + type + ')' + }; + return frame.contentWindow.load_css_image(url, type); +} + +function css_image_set_test(expected_results, frame, url, type, + expected_mode, expected_credentials) { + expected_results[url] = { + url: url, + mode: expected_mode, + credentials: expected_credentials, + message: 'CSSImageSet load (url:' + url + ' type:' + type + ')' + }; + return frame.contentWindow.load_css_image_set(url, type); +} + +function create_message_promise(t, expected_results, frame, + worker, test_count, scope) { + return new Promise(function(resolve) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(msg) { + if (msg.data.ready) { + resolve(); + return; + } + var result = msg.data; + var expected = expected_results[result.url]; + if (!expected) { + return; + } + assert_equals( + result.mode, expected.mode, + 'mode of ' + expected.message + ' must be ' + + expected.mode + '.'); + assert_equals( + result.credentials, expected.credentials, + 'credentials of ' + expected.message + ' must be ' + + expected.credentials + '.'); + --test_count; + delete expected_results[result.url]; + if (test_count == 0) { + frame.remove(); + service_worker_unregister_and_done(t, scope); + } + }); + worker.postMessage( + {port: channel.port2}, [channel.port2]); + }); +} + +async_test(function(t) { + var scope = SCOPE + "?img=backgroundImage"; + var test_count = 2; + var expected_results = {}; + var worker; + var frame; + service_worker_unregister_and_register(t, SCRIPT, scope) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return create_message_promise(t, expected_results, frame, + worker, test_count, scope); + }) + .then(function() { + css_image_test(expected_results, frame, LOCAL_URL + Date.now(), + 'backgroundImage', 'no-cors', 'include'); + css_image_test(expected_results, frame, REMOTE_URL + Date.now(), + 'backgroundImage', 'no-cors', 'include'); + }) + .catch(unreached_rejection(t)); + }, 'Verify FetchEvent for css image (backgroundImage).'); + +async_test(function(t) { + var scope = SCOPE + "?img=shapeOutside"; + var test_count = 2; + var expected_results = {}; + var worker; + var frame; + service_worker_unregister_and_register(t, SCRIPT, scope) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return create_message_promise(t, expected_results, frame, + worker, test_count, scope); + }) + .then(function() { + css_image_test(expected_results, frame, LOCAL_URL + Date.now(), + 'shapeOutside', 'cors', 'same-origin'); + css_image_test(expected_results, frame, REMOTE_URL + Date.now(), + 'shapeOutside', 'cors', 'same-origin'); + }) + .catch(unreached_rejection(t)); + }, 'Verify FetchEvent for css image (shapeOutside).'); + +async_test(function(t) { + var scope = SCOPE + "?img_set=backgroundImage"; + var test_count = 2; + var expected_results = {}; + var worker; + var frame; + service_worker_unregister_and_register(t, SCRIPT, scope) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return create_message_promise(t, expected_results, frame, + worker, test_count, scope); + }) + .then(function() { + css_image_set_test(expected_results, frame, LOCAL_URL + Date.now(), + 'backgroundImage', 'no-cors', 'include'); + css_image_set_test(expected_results, frame, REMOTE_URL + Date.now(), + 'backgroundImage', 'no-cors', 'include'); + }) + .catch(unreached_rejection(t)); + }, 'Verify FetchEvent for css image-set (backgroundImage).'); + +async_test(function(t) { + var scope = SCOPE + "?img_set=shapeOutside"; + var test_count = 2; + var expected_results = {}; + var worker; + var frame; + service_worker_unregister_and_register(t, SCRIPT, scope) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return create_message_promise(t, expected_results, frame, + worker, test_count, scope); + }) + .then(function() { + css_image_set_test(expected_results, frame, LOCAL_URL + Date.now(), + 'shapeOutside', 'cors', 'same-origin'); + css_image_set_test(expected_results, frame, REMOTE_URL + Date.now(), + 'shapeOutside', 'cors', 'same-origin'); + }) + .catch(unreached_rejection(t)); + }, 'Verify FetchEvent for css image-set (shapeOutside).'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-fallback.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-fallback.https.html new file mode 100644 index 000000000..8290e21dd --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-fallback.https.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<title>Service Worker: the fallback behavior of FetchEvent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +var expected_urls = []; + +function xhr_fail_test(frame, url) { + expected_urls.push(url); + return new Promise(function(resolve, reject) { + frame.contentWindow.xhr(url) + .then(function(){ + reject(url + ' should fail.'); + }) + .catch(function(){ + resolve(); + }); + }); +} + +function xhr_succeed_test(frame, url) { + expected_urls.push(url); + return new Promise(function(resolve, reject) { + frame.contentWindow.xhr(url) + .then(function(){ + resolve(); + }) + .catch(function(){ + reject(url + ' should succeed.'); + }); + }); +} + +async_test(function(t) { + var path = new URL(".", window.location).pathname; + var SCOPE = 'resources/fetch-request-fallback-iframe.html'; + var SCRIPT = 'resources/fetch-request-fallback-worker.js'; + var host_info = get_host_info(); + var BASE_URL = host_info['HTTPS_ORIGIN'] + + path + 'resources/fetch-access-control.py?'; + var OTHER_BASE_URL = host_info['HTTPS_REMOTE_ORIGIN'] + + path + 'resources/fetch-access-control.py?'; + var REDIRECT_URL = host_info['HTTPS_ORIGIN'] + + path + 'resources/redirect.py?Redirect='; + var frame; + var worker; + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(f) { + frame = f; + return xhr_succeed_test(frame, BASE_URL); + }) + .then(function(f) { + return xhr_fail_test(frame, OTHER_BASE_URL); + }) + .then(function(f) { + return xhr_succeed_test(frame, OTHER_BASE_URL + 'ACAOrigin=*'); + }) + .then(function(f) { + return xhr_succeed_test(frame, + REDIRECT_URL + encodeURIComponent(BASE_URL)); + }) + .then(function() { + return xhr_fail_test( + frame, + REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL)); + }) + .then(function() { + return xhr_succeed_test( + frame, + REDIRECT_URL + + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*')); + }) + .then(function() { + return new Promise(function(resolve) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(msg) { + frame.remove(); + resolve(msg); + }); + worker.postMessage({port: channel.port2}, [channel.port2]); + }); + }) + .then(function(msg) { + var requests = msg.data.requests; + assert_equals(requests.length, expected_urls.length + 1, + 'The count of the requests which are passed to the ' + + 'ServiceWorker must be correct.'); + assert_equals(requests[0].url, new URL(SCOPE, location).toString(), + 'The first request to the SW must be the request for ' + + 'the page.'); + assert_equals(requests[0].mode, 'navigate', + 'The mode of the first request to the SW must be ' + + 'navigate'); + for (var i = 0; i < expected_urls.length; ++i) { + assert_equals(requests[i + 1].url, expected_urls[i], + 'The URL of the request which was passed from XHR ' + + 'to the ServiceWorker must be correct.'); + assert_equals(requests[i + 1].mode, 'cors', + 'The mode of the request which was passed from XHR ' + + 'to the ServiceWorker must be cors.'); + } + service_worker_unregister_and_done(t, SCOPE); + }) + .catch(unreached_rejection(t)); + }, 'Verify the fallback behavior of FetchEvent'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html new file mode 100644 index 000000000..829b0cf25 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>Service Worker: the headers of FetchEvent shouldn't contain freshness headers</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +async_test(function(t) { + var SCOPE = 'resources/fetch-request-no-freshness-headers-iframe.html'; + var SCRIPT = 'resources/fetch-request-no-freshness-headers-worker.js'; + var worker; + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + return new Promise(function(resolve) { + frame.onload = function() { + resolve(frame); + }; + frame.contentWindow.location.reload(); + }); + }) + .then(function(frame) { + return new Promise(function(resolve) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(msg) { + frame.remove(); + resolve(msg); + }); + worker.postMessage( + {port: channel.port2}, [channel.port2]); + }); + }) + .then(function(msg) { + var freshness_headers = { + 'if-none-match': true, + 'if-modified-since': true + }; + msg.data.requests.forEach(t.step_func(function(request) { + request.headers.forEach(t.step_func(function(header) { + assert_false( + !!freshness_headers[header[0]], + header[0] + ' header must not be set in the ' + + 'FetchEvent\'s request. (url = ' + request.url + ')'); + })); + })) + service_worker_unregister_and_done(t, SCOPE); + }) + .catch(unreached_rejection(t)); + }, 'The headers of FetchEvent shouldn\'t contain freshness headers.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-redirect.https.html new file mode 100644 index 000000000..25d4a6ef3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-redirect.https.html @@ -0,0 +1,372 @@ +<!DOCTYPE html> +<title>Service Worker: FetchEvent for resources</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +function assert_resolves(promise, description) { + return promise.catch(function(reason) { + throw new Error(description + ' - ' + reason.message); + }); +} + +function assert_rejects(promise, description) { + return promise.then( + function() { throw new Error(description); }, + function() {}); +} + +function iframe_test(url, timeout_enabled) { + return new Promise(function(resolve, reject) { + var frame = document.createElement('iframe'); + frame.src = url; + if (timeout_enabled) { + // We can't catch the network error on iframe. So we use the timer for + // failure detection. + var timer = setTimeout(function() { + reject(new Error('iframe load timeout')); + frame.remove(); + }, 10000); + } + frame.onload = function() { + if (timeout_enabled) + clearTimeout(timer); + if (frame.contentDocument.body.textContent == 'Hello world\n') + resolve(); + else + reject(new Error('content mismatch')); + frame.remove(); + }; + document.body.appendChild(frame); + }); +} + +promise_test(function(t) { + var SCOPE = 'resources/fetch-request-redirect-iframe.html'; + var SCRIPT = 'resources/fetch-rewrite-worker.js'; + var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect='; + var IMAGE_URL = base_path() + 'resources/square.png'; + var AUDIO_URL = base_path() + 'resources/silence.oga'; + var XHR_URL = base_path() + 'resources/simple.txt'; + var HTML_URL = base_path() + 'resources/dummy.html'; + + var REDIRECT_TO_IMAGE_URL = REDIRECT_URL + encodeURIComponent(IMAGE_URL); + var REDIRECT_TO_AUDIO_URL = REDIRECT_URL + encodeURIComponent(AUDIO_URL); + var REDIRECT_TO_XHR_URL = REDIRECT_URL + encodeURIComponent(XHR_URL); + var REDIRECT_TO_HTML_URL = REDIRECT_URL + encodeURIComponent(HTML_URL); + + var worker; + var frame; + return service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(f) { + frame = f; + return Promise.all([ + // XMLHttpRequest tests. + assert_resolves(frame.contentWindow.xhr(XHR_URL), + 'Normal XHR should succeed.'), + assert_resolves(frame.contentWindow.xhr(REDIRECT_TO_XHR_URL), + 'Redirected XHR should succeed.'), + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&redirect-mode=follow'), + 'Redirected XHR with Request.redirect=follow should succeed.'), + assert_rejects( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&redirect-mode=error'), + 'Redirected XHR with Request.redirect=error should fail.'), + assert_rejects( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&redirect-mode=manual'), + 'Redirected XHR with Request.redirect=manual should fail.'), + + // Image loading tests. + assert_resolves(frame.contentWindow.load_image(IMAGE_URL), + 'Normal image resource should be loaded.'), + assert_resolves( + frame.contentWindow.load_image(REDIRECT_TO_IMAGE_URL), + 'Redirected image resource should be loaded.'), + assert_resolves( + frame.contentWindow.load_image( + './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) + + '&redirect-mode=follow'), + 'Loading redirected image with Request.redirect=follow should' + + ' succeed.'), + assert_rejects( + frame.contentWindow.load_image( + './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) + + '&redirect-mode=error'), + 'Loading redirected image with Request.redirect=error should ' + + 'fail.'), + assert_rejects( + frame.contentWindow.load_image( + './?url=' + encodeURIComponent(REDIRECT_TO_IMAGE_URL) + + '&redirect-mode=manual'), + 'Loading redirected image with Request.redirect=manual should' + + ' fail.'), + + // Audio loading tests. + assert_resolves(frame.contentWindow.load_audio(AUDIO_URL), + 'Normal audio resource should be loaded.'), + assert_resolves( + frame.contentWindow.load_audio(REDIRECT_TO_AUDIO_URL), + 'Redirected audio resource should be loaded.'), + assert_resolves( + frame.contentWindow.load_audio( + './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) + + '&redirect-mode=follow'), + 'Loading redirected audio with Request.redirect=follow should' + + ' succeed.'), + assert_rejects( + frame.contentWindow.load_audio( + './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) + + '&redirect-mode=error'), + 'Loading redirected audio with Request.redirect=error should ' + + 'fail.'), + assert_rejects( + frame.contentWindow.load_audio( + './?url=' + encodeURIComponent(REDIRECT_TO_AUDIO_URL) + + '&redirect-mode=manual'), + 'Loading redirected audio with Request.redirect=manual should' + + ' fail.'), + + // Iframe tests. + assert_resolves(iframe_test(HTML_URL), + 'Normal iframe loading should succeed.'), + assert_resolves( + iframe_test(REDIRECT_TO_HTML_URL), + 'Normal redirected iframe loading should succeed.'), + assert_rejects( + iframe_test(SCOPE + '?url=' + + encodeURIComponent(REDIRECT_TO_HTML_URL) + + '&redirect-mode=follow', + true /* timeout_enabled */), + 'Redirected iframe loading with Request.redirect=follow should'+ + ' fail.'), + assert_rejects( + iframe_test(SCOPE + '?url=' + + encodeURIComponent(REDIRECT_TO_HTML_URL) + + '&redirect-mode=error', + true /* timeout_enabled */), + 'Redirected iframe loading with Request.redirect=error should '+ + 'fail.'), + assert_resolves( + iframe_test(SCOPE + '?url=' + + encodeURIComponent(REDIRECT_TO_HTML_URL) + + '&redirect-mode=manual', + true /* timeout_enabled */), + 'Redirected iframe loading with Request.redirect=manual should'+ + ' succeed.'), + ]); + }) + .then(function() { + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + }, 'Verify redirect mode of Fetch API and ServiceWorker FetchEvent.'); + +// test for reponse.redirected +promise_test(function(t) { + var SCOPE = 'resources/fetch-request-redirect-iframe.html'; + var SCRIPT = 'resources/fetch-rewrite-worker.js'; + var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect='; + var XHR_URL = base_path() + 'resources/simple.txt'; + var IMAGE_URL = base_path() + 'resources/square.png'; + + var REDIRECT_TO_XHR_URL = REDIRECT_URL + encodeURIComponent(XHR_URL); + + var host_info = get_host_info(); + + var CROSS_ORIGIN_URL = host_info['HTTPS_REMOTE_ORIGIN'] + IMAGE_URL; + + var REDIRECT_TO_CROSS_ORIGIN = REDIRECT_URL + + encodeURIComponent(CROSS_ORIGIN_URL + '?ACAOrigin=*'); + + var worker; + var frame; + return service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(f) { + frame = f; + return Promise.all([ + // XMLHttpRequest tests. + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(XHR_URL) + + '&expected_redirected=false' + + '&expected_resolves=true'), + 'Normal XHR should be resolved and response should not be ' + + 'redirected.'), + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&expected_redirected=true' + + '&expected_resolves=true'), + 'Redirected XHR should be resolved and response should be ' + + 'redirected.'), + + // tests for request's mode = cors + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(XHR_URL) + + '&mode=cors' + + '&expected_redirected=false' + + '&expected_resolves=true'), + 'Normal XHR should be resolved and response should not be ' + + 'redirected even with CORS mode.'), + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&mode=cors' + + '&redirect-mode=follow' + + '&expected_redirected=true' + + '&expected_resolves=true'), + 'Redirected XHR should be resolved and response.redirected ' + + 'should be redirected with CORS mode.'), + + // tests for request's mode = no-cors + // The response.redirect should be false since we will not add + // redirected url list when redirect-mode is not follow. + assert_rejects( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&mode=no-cors' + + '&redirect-mode=manual' + + '&expected_redirected=false' + + '&expected_resolves=false'), + 'Redirected XHR should be reject and response should be ' + + 'redirected with NO-CORS mode and redirect-mode=manual.'), + + // tests for redirecting to a cors + assert_resolves( + frame.contentWindow.load_image( + './?url=' + encodeURIComponent(REDIRECT_TO_CROSS_ORIGIN) + + '&mode=no-cors' + + '&redirect-mode=follow' + + '&expected_redirected=false' + + '&expected_resolves=true'), + 'Redirected COS image should be reject and response should ' + + 'not be redirected with NO-CORS mode.'), + ]); + }) + .then(function() { + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + }, 'Verify redirected of Response(Fetch API) and ServiceWorker FetchEvent.'); + +// test for reponse.redirected after cached +promise_test(function(t) { + var SCOPE = 'resources/fetch-request-redirect-iframe.html'; + var SCRIPT = 'resources/fetch-rewrite-worker.js'; + var REDIRECT_URL = base_path() + 'resources/redirect.py?Redirect='; + var XHR_URL = base_path() + 'resources/simple.txt'; + var IMAGE_URL = base_path() + 'resources/square.png'; + + var REDIRECT_TO_XHR_URL = REDIRECT_URL + encodeURIComponent(XHR_URL); + + var host_info = get_host_info(); + + var CROSS_ORIGIN_URL = host_info['HTTPS_REMOTE_ORIGIN'] + IMAGE_URL; + + var REDIRECT_TO_CROSS_ORIGIN = REDIRECT_URL + + encodeURIComponent(CROSS_ORIGIN_URL + '?ACAOrigin=*'); + + var worker; + var frame; + return service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(f) { + frame = f; + return Promise.all([ + // XMLHttpRequest tests. + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(XHR_URL) + + '&expected_redirected=false' + + '&expected_resolves=true' + + '&cache'), + 'Normal XHR should be resolved and response should not be ' + + 'redirected.'), + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&expected_redirected=true' + + '&expected_resolves=true' + + '&cache'), + 'Redirected XHR should be resolved and response should be ' + + 'redirected.'), + + // tests for request's mode = cors + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(XHR_URL) + + '&mode=cors' + + '&expected_redirected=false' + + '&expected_resolves=true' + + '&cache'), + 'Normal XHR should be resolved and response should not be ' + + 'redirected even with CORS mode.'), + assert_resolves( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&mode=cors' + + '&redirect-mode=follow' + + '&expected_redirected=true' + + '&expected_resolves=true' + + '&cache'), + 'Redirected XHR should be resolved and response.redirected ' + + 'should be redirected with CORS mode.'), + + // tests for request's mode = no-cors + // The response.redirect should be false since we will not add + // redirected url list when redirect-mode is not follow. + assert_rejects( + frame.contentWindow.xhr( + './?url=' + encodeURIComponent(REDIRECT_TO_XHR_URL) + + '&mode=no-cors' + + '&redirect-mode=manual' + + '&expected_redirected=false' + + '&expected_resolves=false' + + '&cache'), + 'Redirected XHR should be reject and response should be ' + + 'redirected with NO-CORS mode and redirect-mode=manual.'), + + // tests for redirecting to a cors + assert_resolves( + frame.contentWindow.load_image( + './?url=' + encodeURIComponent(REDIRECT_TO_CROSS_ORIGIN) + + '&mode=no-cors' + + '&redirect-mode=follow' + + '&expected_redirected=false' + + '&expected_resolves=true' + + '&cache'), + 'Redirected COS image should be reject and response should ' + + 'not be redirected with NO-CORS mode.'), + ]); + }) + .then(function() { + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + }, 'Verify redirected of Response(Fetch API), Cache API and ServiceWorker ' + + 'FetchEvent.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-resources.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-resources.https.html new file mode 100644 index 000000000..7a64e9444 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-resources.https.html @@ -0,0 +1,189 @@ +<!DOCTYPE html> +<title>Service Worker: FetchEvent for resources</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +var url_count = 0; +var expected_results = {}; + +function image_test(frame, url, cross_origin, expected_mode, + expected_credentials) { + var actual_url = url + (++url_count); + expected_results[actual_url] = { + cross_origin: cross_origin, + mode: expected_mode, + credentials: expected_credentials, + integrity: '', + message: 'Image load (url:' + + actual_url + ' cross_origin:' + cross_origin + ')' + }; + return frame.contentWindow.load_image(actual_url, cross_origin); +} + +function script_test(frame, url, cross_origin, expected_mode, + expected_credentials) { + var actual_url = url + (++url_count); + expected_results[actual_url] = { + cross_origin: cross_origin, + mode: expected_mode, + credentials: expected_credentials, + integrity: '', + message: 'Script load (url:' + + actual_url + ' cross_origin:' + cross_origin + ')' + }; + return frame.contentWindow.load_script(actual_url, cross_origin); +} + +function css_test(frame, url, cross_origin, expected_mode, + expected_credentials) { + var actual_url = url + (++url_count); + expected_results[actual_url] = { + cross_origin: cross_origin, + mode: expected_mode, + credentials: expected_credentials, + integrity: '', + message: 'CSS load (url:' + + actual_url + ' cross_origin:' + cross_origin + ')' + }; + return frame.contentWindow.load_css(actual_url, cross_origin); +} + +function font_face_test(frame, url, expected_mode, expected_credentials) { + var actual_url = url + (++url_count); + expected_results[actual_url] = { + url: actual_url, + mode: expected_mode, + credentials: expected_credentials, + integrity: '', + message: 'FontFace load (url:' + actual_url + ')' + }; + return frame.contentWindow.load_font(actual_url); +} + +function script_integrity_test(frame, url, integrity, expected_integrity) { + var actual_url = url + (++url_count); + expected_results[actual_url] = { + url: actual_url, + mode: 'no-cors', + credentials: 'include', + integrity: expected_integrity, + message: 'Script load (url:' + actual_url + ')' + }; + return frame.contentWindow.load_script_with_integrity(actual_url, integrity); +} + +function css_integrity_test(frame, url, integrity, expected_integrity) { + var actual_url = url + (++url_count); + expected_results[actual_url] = { + url: actual_url, + mode: 'no-cors', + credentials: 'include', + integrity: expected_integrity, + message: 'CSS load (url:' + actual_url + ')' + }; + return frame.contentWindow.load_css_with_integrity(actual_url, integrity); +} + +async_test(function(t) { + var SCOPE = 'resources/fetch-request-resources-iframe.https.html'; + var SCRIPT = 'resources/fetch-request-resources-worker.js'; + var host_info = get_host_info(); + var LOCAL_URL = + host_info['HTTPS_ORIGIN'] + base_path() + 'resources/dummy?test'; + var REMOTE_URL = + host_info['HTTPS_REMOTE_ORIGIN'] + base_path() + 'resources/dummy?test'; + var worker; + var frame; + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + worker = registration.installing; + return wait_for_state(t, worker, 'activated'); + }) + .then(function() { + return new Promise(function(resolve) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(msg) { + if (msg.data.ready) { + resolve(); + return; + } + var result = msg.data; + var expected = expected_results[result.url]; + if (!expected) { + return; + } + assert_equals( + result.mode, expected.mode, + 'mode of ' + expected.message + ' must be ' + + expected.mode + '.'); + assert_equals( + result.credentials, expected.credentials, + 'credentials of ' + expected.message + ' must be ' + + expected.credentials + '.'); + assert_equals( + result.integrity, expected.integrity, + 'integrity of ' + expected.message + ' must be ' + + expected.integrity + '.'); + --url_count; + delete expected_results[result.url]; + if (url_count == 0) { + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + } + }); + worker.postMessage( + {port: channel.port2}, [channel.port2]); + }); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(f) { + frame = f; + + image_test(f, LOCAL_URL, '', 'no-cors', 'include'); + image_test(f, REMOTE_URL, '', 'no-cors', 'include'); + css_test(f, LOCAL_URL, '', 'no-cors', 'include'); + css_test(f, REMOTE_URL, '', 'no-cors', 'include'); + + image_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin'); + image_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include'); + image_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin'); + image_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include'); + + script_test(f, LOCAL_URL, '', 'no-cors', 'include'); + script_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin'); + script_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include'); + script_test(f, REMOTE_URL, '', 'no-cors', 'include'); + script_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin'); + script_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include'); + + css_test(f, LOCAL_URL, 'anonymous', 'cors', 'same-origin'); + css_test(f, LOCAL_URL, 'use-credentials', 'cors', 'include'); + css_test(f, REMOTE_URL, 'anonymous', 'cors', 'same-origin'); + css_test(f, REMOTE_URL, 'use-credentials', 'cors', 'include'); + + font_face_test(f, LOCAL_URL, 'cors', 'same-origin'); + font_face_test(f, REMOTE_URL, 'cors', 'same-origin'); + + script_integrity_test(f, LOCAL_URL, ' ', ' '); + script_integrity_test(f, LOCAL_URL, + 'This is not a valid integrity because it has no dashes', + 'This is not a valid integrity because it has no dashes'); + script_integrity_test(f, LOCAL_URL, 'sha256-', 'sha256-'); + script_integrity_test(f, LOCAL_URL, 'sha256-foo?123', 'sha256-foo?123'); + script_integrity_test(f, LOCAL_URL, 'sha256-foo sha384-abc ', 'sha256-foo sha384-abc '); + script_integrity_test(f, LOCAL_URL, 'sha256-foo sha256-abc', 'sha256-foo sha256-abc'); + + css_integrity_test(f, LOCAL_URL, ' ', ' '); + css_integrity_test(f, LOCAL_URL, + 'This is not a valid integrity because it has no dashes', + 'This is not a valid integrity because it has no dashes'); + css_integrity_test(f, LOCAL_URL, 'sha256-', 'sha256-'); + css_integrity_test(f, LOCAL_URL, 'sha256-foo?123', 'sha256-foo?123'); + css_integrity_test(f, LOCAL_URL, 'sha256-foo sha384-abc ', 'sha256-foo sha384-abc '); + css_integrity_test(f, LOCAL_URL, 'sha256-foo sha256-abc', 'sha256-foo sha256-abc'); + }) + .catch(unreached_rejection(t)); + }, 'Verify FetchEvent for resources.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-request-xhr.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-request-xhr.https.html new file mode 100644 index 000000000..1b80985a3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-request-xhr.https.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Service Worker: the body of FetchEvent using XMLHttpRequest</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +async_test(function(t) { + var SCOPE = 'resources/fetch-request-xhr-iframe.https.html'; + var SCRIPT = 'resources/fetch-request-xhr-worker.js'; + var host_info = get_host_info(); + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + if (e.data.results === 'finish') { + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + } else if (e.data.results == 'equals') { + assert_equals(e.data.got, e.data.expected); + } + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }) + .catch(unreached_rejection(t)); + }, 'Verify the body of FetchEvent using XMLHttpRequest'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-response-xhr.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-response-xhr.https.html new file mode 100644 index 000000000..8b66c6051 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-response-xhr.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Service Worker: the response of FetchEvent using XMLHttpRequest</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +async_test(function(t) { + var SCOPE = 'resources/fetch-response-xhr-iframe.https.html'; + var SCRIPT = 'resources/fetch-response-xhr-worker.js'; + var host_info = get_host_info(); + + window.addEventListener('message', t.step_func(on_message), false); + function on_message(e) { + assert_equals(e.data.results, 'foo, bar'); + t.done(); + } + + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data.results, 'finish'); + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }) + .catch(unreached_rejection(t)); + }, 'Verify the response of FetchEvent using XMLHttpRequest'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html b/testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html new file mode 100644 index 000000000..304680c97 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/fetch-waits-for-activate.https.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<title>Service Worker: Fetch Event Waits for Activate Event</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> + +var worker = 'resources/fetch-waits-for-activate-worker.js'; +var expected_url = normalizeURL(worker); +var scope = 'resources/fetch-waits-for-activate/'; + +async_test(function(t) { + var registration; + var frameLoadPromise; + var frame; + service_worker_unregister_and_register(t, worker, scope).then(function(reg) { + registration = reg; + return wait_for_state(t, reg.installing, 'activating'); + }).then(function() { + assert_equals(registration.active.scriptURL, expected_url, + 'active worker should be present'); + assert_equals(registration.active.state, 'activating', + 'active worker should be in activating state'); + + // This should block until we message the worker to tell it to complete + // the activate event. + frameLoadPromise = with_iframe(scope).then(function(f) { + frame = f; + }); + + // Wait some time to allow frame loading to proceed. It should not, + // however, if the fetch event is blocked on the activate. I don't + // see any way to force this race without a timeout, unfortunately. + return new Promise(function(resolve) { + setTimeout(resolve, 1000); + }); + }).then(function() { + assert_equals(frame, undefined, 'frame should not be loaded'); + assert_equals(registration.active.scriptURL, expected_url, + 'active worker should be present'); + assert_equals(registration.active.state, 'activating', + 'active worker should be in activating state'); + + // This signals the activate event to complete. The frame should now + // load. + registration.active.postMessage('GO'); + return frameLoadPromise; + }).then(function() { + assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL, + expected_url, 'frame should now be loaded and controlled'); + assert_equals(registration.active.state, 'activated', + 'active worker should be in activated state'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }).catch(unreached_rejection(t)); +}, 'Fetch events should wait for the activate event to complete.'); + +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/getregistration.https.html b/testing/web-platform/tests/service-workers/service-worker/getregistration.https.html new file mode 100644 index 000000000..c86fda50d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/getregistration.https.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +async_test(function(t) { + var documentURL = 'no-such-worker'; + navigator.serviceWorker.getRegistration(documentURL) + .then(function(value) { + assert_equals(value, undefined, + 'getRegistration should resolve with undefined'); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'getRegistration'); + +async_test(function(t) { + var scope = 'resources/scope/getregistration/normal'; + var registration; + service_worker_unregister_and_register(t, 'resources/empty-worker.js', + scope) + .then(function(r) { + registration = r; + return navigator.serviceWorker.getRegistration(scope); + }) + .then(function(value) { + assert_equals( + value, registration, + 'getRegistration should resolve to the same registration object'); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Register then getRegistration'); + +async_test(function(t) { + var scope = 'resources/scope/getregistration/url-with-fragment'; + var documentURL = scope + '#ref'; + var registration; + service_worker_unregister_and_register(t, 'resources/empty-worker.js', + scope) + .then(function(r) { + registration = r; + return navigator.serviceWorker.getRegistration(documentURL); + }) + .then(function(value) { + assert_equals( + value, registration, + 'getRegistration should resolve to the same registration object'); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Register then getRegistration with a URL having a fragment'); + +async_test(function(t) { + var documentURL = 'http://example.com/'; + navigator.serviceWorker.getRegistration(documentURL) + .then(function() { + assert_unreached( + 'getRegistration with an out of origin URL should fail'); + }, function(reason) { + assert_equals( + reason.name, 'SecurityError', + 'getRegistration with an out of origin URL should fail'); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'getRegistration with a cross origin URL'); + +async_test(function(t) { + var scope = 'resources/scope/getregistration/register-unregister'; + service_worker_unregister_and_register(t, 'resources/empty-worker.js', + scope) + .then(function(registration) { + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.getRegistration(scope); + }) + .then(function(value) { + assert_equals(value, undefined, + 'getRegistration should resolve with undefined'); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Register then Unregister then getRegistration'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/getregistrations.https.html b/testing/web-platform/tests/service-workers/service-worker/getregistrations.https.html new file mode 100644 index 000000000..b65a97869 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/getregistrations.https.html @@ -0,0 +1,191 @@ +<!DOCTYPE html> +<title>Service Worker: getRegistrations()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="../fetch/resources/fetch-test-helpers.sub.js"></script> +<script> +// Purge the existing registrations for the origin. +// getRegistrations() is used in order to avoid adding additional complexity +// e.g. adding an internal function. +promise_test(function(t) { + return navigator.serviceWorker.getRegistrations() + .then(function(registrations) { + return registrations.reduce(function(sequence, registration) { + return sequence.then(function() { + return registration.unregister(); + }); + }, Promise.resolve()); + }); + }, 'Purge the existing registrations.'); + +promise_test(function(t) { + return navigator.serviceWorker.getRegistrations() + .then(function(value) { + assert_array_equals( + value, + [], + 'getRegistrations should resolve with an empty array.'); + }); + }, 'getRegistrations'); + +promise_test(function(t) { + var scope = 'resources/scope/getregistrations/normal'; + var script = 'resources/empty-worker.js'; + var registrations = []; + return service_worker_unregister_and_register(t, script, scope) + .then(function(r) { + registrations.push(r); + return navigator.serviceWorker.getRegistrations(); + }) + .then(function(value) { + assert_array_equals( + value, + registrations, + 'getRegistrations should resolve with array of registrations.'); + return service_worker_unregister(t, scope); + }); + }, 'Register then getRegistrations'); + +promise_test(function(t) { + var scope1 = 'resources/scope/getregistrations/scope1'; + var scope2 = 'resources/scope/getregistrations/scope2'; + var script = 'resources/empty-worker.js'; + var registrations = []; + return service_worker_unregister_and_register(t, script, scope1) + .then(function(r) { + registrations.push(r); + return service_worker_unregister_and_register(t, script, scope2); + }) + .then(function(r) { + registrations.push(r); + return navigator.serviceWorker.getRegistrations(); + }) + .then(function(value) { + assert_array_equals( + value, + registrations, + 'getRegistrations should resolve with array of registrations.'); + return service_worker_unregister(t, scope1); + }) + .then(function() { + return service_worker_unregister(t, scope2); + }); + }, 'Register multiple times then getRegistrations'); + +promise_test(function(t) { + var scope = 'resources/scope/getregistrations/register-unregister'; + var script = 'resources/empty-worker.js'; + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.getRegistrations(); + }) + .then(function(value) { + assert_array_equals( + value, + [], + 'getRegistrations should resolve with an empty array.'); + }); + }, 'Register then Unregister then getRegistrations'); + +promise_test(function(t) { + var scope = 'resources/scope/getregistrations/register-unregister-controlled'; + var script = 'resources/empty-worker.js'; + var registrations; + var frame; + return service_worker_unregister_and_register(t, script, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.getRegistrations(); + }) + .then(function(value) { + assert_array_equals( + value, + [], + 'getRegistrations should resolve with an empty array.'); + assert_equals(registration.installing, null); + assert_equals(registration.waiting, null); + assert_equals(registration.active.state, 'activated'); + frame.remove(); + }); + }, 'Register then Unregister with controlled frame then getRegistrations'); + +promise_test(function(t) { + var host_info = get_host_info(); + // Rewrite the url to point to remote origin. + var frame_same_origin_url = new URL("resources/frame-for-getregistrations.html", window.location); + var frame_url = host_info['HTTPS_REMOTE_ORIGIN'] + frame_same_origin_url.pathname; + var scope = 'resources/scope-for-getregistrations'; + var script = 'resources/empty-worker.js'; + var frame; + var registrations = []; + + // Loads an iframe and waits for 'ready' message from it to resolve promise. + // Caller is responsible for removing frame. + function with_iframe_ready(url) { + return new Promise(function(resolve) { + var frame = document.createElement('iframe'); + frame.src = url; + window.addEventListener('message', function onMessage(e) { + window.removeEventListener('message', onMessage); + if (e.data == 'ready') { + resolve(frame); + } + }); + document.body.appendChild(frame); + }); + } + + // We need this special frame loading function because the frame is going + // to register it's own service worker and there is the possibility that that + // register() finishes after the register() for the same domain later in the + // test. So we have to wait until the cross origin register() is done, and not + // just until the frame loads. + return with_iframe_ready(frame_url) + .then(function(f) { + frame = f; + return service_worker_unregister_and_register(t, script, scope); + }) + .then(function(r) { + registrations.push(r); + return navigator.serviceWorker.getRegistrations(); + }) + .then(function(value) { + assert_array_equals( + value, + registrations, + 'getRegistrations should only return same origin registrations.'); + + var channel = new MessageChannel(); + var resolve; + var p = new Promise(function(r) { resolve = r; }); + + channel.port1.onmessage = function(e) { + if (e.data == 'unregistered') + resolve(); + }; + frame.contentWindow.postMessage('unregister', '*', [channel.port2]); + return p; + }) + .then(function() { + frame.remove(); + return service_worker_unregister(t, scope); + }); + }, 'getRegistrations promise resolves only with same origin registrations.'); + +done(); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/indexeddb.https.html b/testing/web-platform/tests/service-workers/service-worker/indexeddb.https.html new file mode 100644 index 000000000..4de2bc43e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/indexeddb.https.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Service Worker: Indexed DB</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +async_test(function(t) { + var scope = 'resources/blank.html'; + service_worker_unregister_and_register( + t, 'resources/indexeddb-worker.js', scope) + .then(function(registration) { + var sw = registration.installing; + var messageChannel = new MessageChannel(); + messageChannel.port1.onmessage = t.step_func(onMessage); + sw.postMessage({port: messageChannel.port2}, [messageChannel.port2]); + }) + .catch(unreached_rejection(t)); + + function onMessage() { + var openRequest = indexedDB.open('db'); + openRequest.onsuccess = t.step_func(function() { + var db = openRequest.result; + var tx = db.transaction('store'); + var store = tx.objectStore('store'); + var getRequest = store.get('key'); + getRequest.onsuccess = t.step_func(function() { + assert_equals( + getRequest.result, 'value', + 'The get() result should match what the worker put().'); + service_worker_unregister_and_done(t, scope); + }); + }); + } + }, 'Verify Indexed DB operation in a Service Worker'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/install-event-type.https.html b/testing/web-platform/tests/service-workers/service-worker/install-event-type.https.html new file mode 100644 index 000000000..7e74af85c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/install-event-type.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +function wait_for_install_event(worker) { + return new Promise(function(resolve) { + worker.addEventListener('statechange', function(event) { + if (worker.state == 'installed') + resolve(true); + else if (worker.state == 'redundant') + resolve(false); + }); + }); +} + +promise_test(function(t) { + var script = 'resources/install-event-type-worker.js'; + var scope = 'resources/install-event-type'; + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_install_event(registration.installing); + }) + .then(function(did_install) { + assert_true(did_install, 'The worker was installed'); + }) + }, 'install event type'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/installing.https.html b/testing/web-platform/tests/service-workers/service-worker/installing.https.html new file mode 100644 index 000000000..57d878111 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/installing.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>ServiceWorker: navigator.serviceWorker.installing</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +// "installing" is set +async_test(function(t) { + var step = t.step_func.bind(t); + var url = 'resources/empty-worker.js'; + var scope = 'resources/blank.html'; + var frame; + + service_worker_unregister(t, scope) + .then(step(function() { return with_iframe(scope); })) + .then(step(function(f) { + frame = f; + return navigator.serviceWorker.register(url, {scope: scope}); + })) + .then(step(function(registration) { + var container = frame.contentWindow.navigator.serviceWorker; + assert_equals(container.controller, null); + assert_equals(registration.active, null); + assert_equals(registration.waiting, null); + assert_equals(registration.installing.scriptURL, normalizeURL(url)); + + // FIXME: Add a test for a frame created after installation. + // Should the existing frame ("frame") block activation? + })) + .then(step(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + })) + .catch(unreached_rejection(t)); +}, 'installing is set'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/interfaces.https.html b/testing/web-platform/tests/service-workers/service-worker/interfaces.https.html new file mode 100644 index 000000000..403a00534 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/interfaces.https.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<title>Service Worker: Interfaces</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/interfaces.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +test(function() { + var EVENT_HANDLER = 'object'; + verify_interface( + 'ServiceWorkerContainer', navigator.serviceWorker, + { + register: 'function', + getRegistration: 'function', + oncontrollerchange: EVENT_HANDLER + }); + }, 'Interfaces and attributes of ServiceWorkerContainer'); + +async_test(function(t) { + var EVENT_HANDLER = 'object'; + var scope = 'resources/scope/interfaces-and-attributes'; + + service_worker_unregister_and_register( + t, 'resources/empty-worker.js', scope) + .then(function(registration) { + verify_interface( + 'ServiceWorkerRegistration', registration, + { + installing: 'object', + waiting: 'object', + active: 'object', + scope: 'string', + unregister: 'function', + onupdatefound: EVENT_HANDLER + }); + verify_interface( + 'ServiceWorker', registration.installing, + { + scriptURL: 'string', + state: 'string', + onstatechange: EVENT_HANDLER + }); + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Interfaces and attributes of ServiceWorker'); + +service_worker_test( + 'resources/interfaces-worker.sub.js', + 'Interfaces and attributes in ServiceWorkerGlobalScope'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/invalid-blobtype.https.html b/testing/web-platform/tests/service-workers/service-worker/invalid-blobtype.https.html new file mode 100644 index 000000000..f1f2d1bdc --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/invalid-blobtype.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Service Worker: respondWith with header value containing a null byte</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +async_test(function(t) { + var SCOPE = 'resources/invalid-blobtype-iframe.https.html'; + var SCRIPT = 'resources/invalid-blobtype-worker.js'; + var host_info = get_host_info(); + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data.results, 'finish'); + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }) + .catch(unreached_rejection(t)); + }, 'Verify the response of FetchEvent using XMLHttpRequest'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/invalid-header.https.html b/testing/web-platform/tests/service-workers/service-worker/invalid-header.https.html new file mode 100644 index 000000000..794695264 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/invalid-header.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Service Worker: respondWith with header value containing a null byte</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +async_test(function(t) { + var SCOPE = 'resources/invalid-header-iframe.https.html'; + var SCRIPT = 'resources/invalid-header-worker.js'; + var host_info = get_host_info(); + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data.results, 'finish'); + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }) + .catch(unreached_rejection(t)); + }, 'Verify the response of FetchEvent using XMLHttpRequest'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/multiple-register.https.html b/testing/web-platform/tests/service-workers/service-worker/multiple-register.https.html new file mode 100644 index 000000000..1089cffda --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/multiple-register.https.html @@ -0,0 +1,116 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +var worker_url = 'resources/empty-worker.js'; + +async_test(function(t) { + var scope = 'resources/scope/subsequent-register-from-same-window'; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return navigator.serviceWorker.register(worker_url, { scope: scope }); + }) + .then(function(new_registration) { + assert_equals(new_registration, registration, + 'register should resolve to the same registration'); + assert_equals(new_registration.active, registration.active, + 'register should resolve to the same worker'); + assert_equals(new_registration.active.state, 'activated', + 'the worker should be in state "activated"'); + return registration.unregister(); + }) + .then(function() { t.done(); }) + .catch(unreached_rejection(t)); +}, 'Subsequent registrations resolve to the same registration object'); + +async_test(function(t) { + var scope = 'resources/scope/subsequent-register-from-different-iframe'; + var frame; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { return with_iframe('resources/404.py'); }) + .then(function(f) { + frame = f; + return frame.contentWindow.navigator.serviceWorker.register( + worker_url, { scope: scope }); + }) + .then(function(new_registration) { + assert_not_equals( + registration, new_registration, + 'register should resolve to a different registration'); + assert_equals( + registration.scope, new_registration.scope, + 'registrations should have the same scope'); + + assert_equals( + registration.installing, null, + 'installing worker should be null'); + assert_equals( + new_registration.installing, null, + 'installing worker should be null'); + assert_equals( + registration.waiting, null, + 'waiting worker should be null') + assert_equals( + new_registration.waiting, null, + 'waiting worker should be null') + + assert_not_equals( + registration.active, new_registration.active, + 'registration should have the different active worker'); + assert_equals( + registration.active.scriptURL, + new_registration.active.scriptURL, + 'active workers should have the same script URL'); + assert_equals( + registration.active.state, + new_registration.active.state, + 'active workers should be in the same state'); + + frame.remove(); + return registration.unregister(); + }) + .then(function() { t.done(); }) + .catch(unreached_rejection(t)); +}, 'Subsequent registrations from a different iframe resolve to the ' + + 'different registration object but they refer to the same ' + + 'registration and workers'); + +async_test(function(t) { + var scope = 'resources/scope/concurrent-register'; + + service_worker_unregister(t, scope) + .then(function() { + var promises = []; + for (var i = 0; i < 10; ++i) { + promises.push(navigator.serviceWorker.register(worker_url, + { scope: scope })); + } + return Promise.all(promises); + }) + .then(function(registrations) { + registrations.forEach(function(registration) { + assert_equals(registration, registrations[0], + 'register should resolve to the same registration'); + }); + return registrations[0].unregister(); + }) + .then(function() { t.done(); }) + .catch(unreached_rejection(t)); +}, 'Concurrent registrations resolve to the same registration object'); + +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/multiple-update.https.html b/testing/web-platform/tests/service-workers/service-worker/multiple-update.https.html new file mode 100644 index 000000000..84aac9583 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/multiple-update.https.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<!-- In Bug 1217367, we will try to merge update events for same registration + if possible. This testcase is used to make sure the optimization algorithm + doesn't go wrong. --> +<title>Service Worker: Trigger multiple updates</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +promise_test(function(t) { + var script = 'resources/update-nocookie-worker.py'; + var scope = 'resources/scope/update'; + var expected_url = normalizeURL(script); + var registration; + + return service_worker_unregister_and_register(t, expected_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + // Test single update works before triggering multiple update events + return Promise.all([registration.update(), + wait_for_update(t, registration)]); + }) + .then(function() { + assert_equals(registration.installing.scriptURL, expected_url, + 'new installing should be set after update resolves.'); + assert_equals(registration.waiting, null, + 'waiting should still be null after update resolves.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after update found.'); + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'installing should be null after installing.'); + if (registration.waiting) { + assert_equals(registration.waiting.scriptURL, expected_url, + 'waiting should be set after installing.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after installing.'); + return wait_for_state(t, registration.waiting, 'activated'); + } + }) + .then(function() { + // Test triggering multiple update events at the same time. + var promiseList = []; + const burstUpdateCount = 10; + for (var i = 0; i < burstUpdateCount; i++) { + promiseList.push(registration.update()); + } + promiseList.push(wait_for_update(t, registration)); + return Promise.all(promiseList); + }) + .then(function() { + assert_equals(registration.installing.scriptURL, expected_url, + 'new installing should be set after update resolves.'); + assert_equals(registration.waiting, null, + 'waiting should still be null after update resolves.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after update found.'); + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'installing should be null after installing.'); + if (registration.waiting) { + assert_equals(registration.waiting.scriptURL, expected_url, + 'waiting should be set after installing.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after installing.'); + return wait_for_state(t, registration.waiting, 'activated'); + } + }) + .then(function() { + // Test update still works after handling update event burst. + return Promise.all([registration.update(), + wait_for_update(t, registration)]); + }) + .then(function() { + assert_equals(registration.installing.scriptURL, expected_url, + 'new installing should be set after update resolves.'); + assert_equals(registration.waiting, null, + 'waiting should be null after activated.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after update found.'); + + return service_worker_unregister_and_done(t, scope); + }); + }, 'Trigger multiple updates.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html b/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html new file mode 100644 index 000000000..e3aaf4c5c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html @@ -0,0 +1,141 @@ +<!DOCTYPE html> +<title>Service Worker: Navigate a Window</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +var host_info = get_host_info(); +var BASE_URL = host_info['HTTPS_ORIGIN'] + base_path(); + +function wait_for_message(msg) { + return new Promise(function(resolve, reject) { + window.addEventListener('message', function onMsg(evt) { + if (evt.data.type === msg) { + resolve(); + } + }); + }); +} + +function with_window(url) { + var win = window.open(url); + return wait_for_message('LOADED').then(_ => win); +} + +function navigate_window(win, url) { + win.location = url; + return wait_for_message('LOADED').then(_ => win); +} + +function reload_window(win) { + win.location.reload(); + return wait_for_message('LOADED').then(_ => win); +} + +function go_back(win) { + win.history.back(); + return wait_for_message('PAGESHOW').then(_ => win); +} + +function go_forward(win) { + win.history.forward(); + return wait_for_message('PAGESHOW').then(_ => win); +} + +function get_clients(win, sw, opts) { + return new Promise((resolve, reject) => { + win.navigator.serviceWorker.addEventListener('message', function onMsg(evt) { + win.navigator.serviceWorker.removeEventListener('message', onMsg); + if (evt.data.type === 'success') { + resolve(evt.data.detail); + } else { + reject(evt.data.detail); + } + }); + sw.postMessage({ type: 'GET_CLIENTS', opts: (opts || {}) }); + }); +} + +function validate_window(win, url, opts) { + return win.navigator.serviceWorker.getRegistration(url) + .then(reg => { + // In order to compare service worker instances we need to + // make sure the DOM object is owned by the same global; the + // opened window in this case. + assert_equals(win.navigator.serviceWorker.controller, reg.active, + 'window should be controlled by service worker'); + return get_clients(win, reg.active, opts); + }) + .then(resultList => { + // We should always see our controlled window. + var expected = [ + { url: url, frameType: 'auxiliary' } + ]; + // If we are including uncontrolled windows, then we might see the + // test window itself and the test harness. + if (opts.includeUncontrolled) { + expected.push({ url: BASE_URL + 'navigate-window.https.html', + frameType: 'auxiliary' }); + expected.push({ url: host_info['HTTPS_ORIGIN'] + '/testharness_runner.html', + frameType: 'top-level' }); + } + assert_equals(resultList.length, expected.length, + 'expected number of clients'); + for (var i = 0; i < resultList.length; ++i) { + assert_equals(resultList[i].url, expected[i].url, + 'client should have expected url'); + assert_equals(resultList[i].frameType, expected[i].frameType, + ' client should have expected frame type'); + } + return win; + }) +} + +promise_test(function(t) { + var worker = BASE_URL + 'resources/navigate-window-worker.js'; + var scope = BASE_URL + 'resources/loaded.html?navigate-window-controlled'; + var url1 = scope + '&q=1'; + var url2 = scope + '&q=2'; + return service_worker_unregister_and_register(t, worker, scope) + .then(reg => wait_for_state(t, reg.installing, 'activated') ) + .then(___ => with_window(url1)) + .then(win => validate_window(win, url1, { includeUncontrolled: false })) + .then(win => navigate_window(win, url2)) + .then(win => validate_window(win, url2, { includeUncontrolled: false })) + .then(win => go_back(win)) + .then(win => validate_window(win, url1, { includeUncontrolled: false })) + .then(win => go_forward(win)) + .then(win => validate_window(win, url2, { includeUncontrolled: false })) + .then(win => reload_window(win)) + .then(win => validate_window(win, url2, { includeUncontrolled: false })) + .then(win => win.close()) + .catch(unreached_rejection(t)) + .then(___ => service_worker_unregister(t, scope)) + }, 'Clients.matchAll() should not show an old window as controlled after ' + + 'it navigates.'); + +promise_test(function(t) { + var worker = BASE_URL + 'resources/navigate-window-worker.js'; + var scope = BASE_URL + 'resources/loaded.html?navigate-window-uncontrolled'; + var url1 = scope + '&q=1'; + var url2 = scope + '&q=2'; + return service_worker_unregister_and_register(t, worker, scope) + .then(reg => wait_for_state(t, reg.installing, 'activated') ) + .then(___ => with_window(url1)) + .then(win => validate_window(win, url1, { includeUncontrolled: true })) + .then(win => navigate_window(win, url2)) + .then(win => validate_window(win, url2, { includeUncontrolled: true })) + .then(win => go_back(win)) + .then(win => validate_window(win, url1, { includeUncontrolled: true })) + .then(win => go_forward(win)) + .then(win => validate_window(win, url2, { includeUncontrolled: true })) + .then(win => reload_window(win)) + .then(win => validate_window(win, url2, { includeUncontrolled: true })) + .then(win => win.close()) + .catch(unreached_rejection(t)) + .then(___ => service_worker_unregister(t, scope)) + }, 'Clients.matchAll() should not show an old window after it navigates.'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html b/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html new file mode 100644 index 000000000..7b606cf0c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigation-redirect.https.html @@ -0,0 +1,449 @@ +<!DOCTYPE html> +<title>Service Worker: Navigation redirection</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +var 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. +var BASE_URL = host_info['HTTPS_ORIGIN'] + base_path(); +var OTHER_BASE_URL = host_info['HTTPS_REMOTE_ORIGIN'] + base_path(); + +var SCOPE1 = BASE_URL + 'resources/navigation-redirect-scope1.py?'; +var SCOPE2 = BASE_URL + 'resources/navigation-redirect-scope2.py?'; +var OUT_SCOPE = BASE_URL + 'resources/navigation-redirect-out-scope.py?'; +var SCRIPT = 'resources/navigation-redirect-worker.js'; + +var OTHER_ORIGIN_IFRAME_URL = + OTHER_BASE_URL + 'resources/navigation-redirect-other-origin.html'; +var OTHER_ORIGIN_SCOPE = + OTHER_BASE_URL + 'resources/navigation-redirect-scope1.py?'; +var OTHER_ORIGIN_OUT_SCOPE = + OTHER_BASE_URL + 'resources/navigation-redirect-out-scope.py?'; + +var workers; +var other_origin_frame; +var setup_environment_promise; +var message_resolvers = {}; +var next_message_id = 0; + +function setup_environment(t) { + if (setup_environment_promise) + return setup_environment_promise; + setup_environment_promise = + with_iframe(OTHER_ORIGIN_IFRAME_URL) + .then(function(f) { + // 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 = f; + return Promise.all( + [service_worker_unregister_and_register(t, SCRIPT, SCOPE1), + service_worker_unregister_and_register(t, SCRIPT, SCOPE2)]); + }) + .then(function(registrations) { + add_completion_callback(function() { + registrations[0].unregister(); + registrations[1].unregister(); + send_to_iframe(other_origin_frame, 'unregister') + .then(function() { other_origin_frame.remove(); }); + }); + 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, 'wait_for_worker')]); + }); + return setup_environment_promise; +} + +function get_effective_worker(registration) { + if (registration.active) + return registration.active; + if (registration.waiting) + return registration.waiting; + if (registration.installing) + return registration.installing; +} + +function check_all_intercepted_urls(expected_urls) { + var urls = []; + return get_intercepted_urls(workers[0]) + .then(function(url) { + urls.push(url); + return get_intercepted_urls(workers[1]); + }).then(function(url) { + urls.push(url); + // Gets the request URLs which are intercepted by OTHER_ORIGIN_SCOPE's + // SW. This promise will resolve when get_intercepted_urls() in + // OTHER_ORIGIN_IFRAME_URL resolves. + return send_to_iframe(other_origin_frame, 'get_intercepted_urls'); + }).then(function(url) { + urls.push(url); + return urls; + }).then(function(urls) { + assert_object_equals( + urls, expected_urls, + 'Intercepted URLs should match.'); + }); +} + +function test_redirect(url, expected_last_url, + expected_intercepted_urls) { + var message_promise = new Promise(function(resolve) { + // A message which ID is 'last_url' will be sent from the iframe. + message_resolvers['last_url'] = resolve; + }); + return with_iframe(url) + .then(function(f) { + f.remove(); + return check_all_intercepted_urls(expected_intercepted_urls); + }) + .then(function() { return message_promise; }) + .then(function(last_url) { + assert_equals( + last_url, expected_last_url, + 'Last URL should match.'); + }); +} + +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(function(resolve) { + message_resolvers[message_id] = resolve; + frame.contentWindow.postMessage( + {id: message_id, message: message}, + host_info['HTTPS_REMOTE_ORIGIN']); + }); +} + +function get_intercepted_urls(worker) { + return new Promise(function(resolve) { + var channel = new MessageChannel(); + channel.port1.onmessage = function(msg) { resolve(msg.data.urls); }; + worker.postMessage({port: channel.port2}, [channel.port2]); + }); +} + +// Normal redirect. +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + OUT_SCOPE + 'url=' + encodeURIComponent(SCOPE1), + SCOPE1, + [[SCOPE1], [], []]); + }); + }, 'Normal redirect to same-origin scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + OUT_SCOPE + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE), + OTHER_ORIGIN_SCOPE, + [[], [], [OTHER_ORIGIN_SCOPE]]); + }); + }, 'Normal redirect to other-origin scope.'); + +// SW fallbacked redirect. SW doesn't handle the fetch request. +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'url=' + encodeURIComponent(OUT_SCOPE), + OUT_SCOPE, + [[SCOPE1 + 'url=' + encodeURIComponent(OUT_SCOPE)], [], []]); + }); + }, 'SW-fallbacked redirect to same-origin out-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'url=' + encodeURIComponent(SCOPE1), + SCOPE1, + [[SCOPE1 + 'url=' + encodeURIComponent(SCOPE1)], [], []]); + }); + }, 'SW-fallbacked redirect to same-origin same-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'url=' + encodeURIComponent(SCOPE2), + SCOPE2, + [[SCOPE1 + 'url=' + encodeURIComponent(SCOPE2)], [], []]); + }); + }, 'SW-fallbacked redirect to same-origin other-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE), + OTHER_ORIGIN_OUT_SCOPE, + [[SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)], + [], + []]); + }); + }, 'SW-fallbacked redirect to other-origin out-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE), + OTHER_ORIGIN_SCOPE, + [[SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE)], + [], + []]); + }); + }, 'SW-fallbacked redirect to other-origin in-scope.'); + +// SW generated redirect. +// SW: event.respondWith(Response.redirect(params['url'])); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE), + OUT_SCOPE, + [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE)], [], []]); + }); + }, 'SW-generated redirect to same-origin out-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE1), + SCOPE1, + [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE1), SCOPE1], + [], + []]); + }); + }, 'SW-generated redirect to same-origin same-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE2), + SCOPE2, + [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE2)], + [SCOPE2], + []]); + }); + }, 'SW-generated redirect to same-origin other-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE), + OTHER_ORIGIN_OUT_SCOPE, + [[SCOPE1 + 'sw=gen&url=' + + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)], + [], + []]); + }); + }, 'SW-generated redirect to other-origin out-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE), + OTHER_ORIGIN_SCOPE, + [[SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE)], + [], + [OTHER_ORIGIN_SCOPE]]); + }); + }, 'SW-generated redirect to other-origin in-scope.'); + +// SW fetched redirect. +// SW: event.respondWith(fetch(event.request)); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OUT_SCOPE), + OUT_SCOPE, + [[SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OUT_SCOPE)], + [], + []]); + }); + }, 'SW-fetched redirect to same-origin out-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE1), + SCOPE1, + [[SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE1), SCOPE1], + [], + []]); + }); + }, 'SW-fetched redirect to same-origin same-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE2), + SCOPE2, + [[SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE2)], + [SCOPE2], + []]); + }); + }, 'SW-fetched redirect to same-origin other-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=fetch&url=' + + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE), + OTHER_ORIGIN_OUT_SCOPE, + [[SCOPE1 + 'sw=fetch&url=' + + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)], + [], + []]); + }); + }, 'SW-fetched redirect to other-origin out-scope.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE), + OTHER_ORIGIN_SCOPE, + [[SCOPE1 + 'sw=fetch&url=' + + encodeURIComponent(OTHER_ORIGIN_SCOPE)], + [], + [OTHER_ORIGIN_SCOPE]]); + }); + }, 'SW-fetched redirect to other-origin in-scope.'); + +// Opaque redirect. +// SW: event.respondWith(fetch( +// new Request(event.request.url, {redirect: 'manual'}))); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(OUT_SCOPE), + OUT_SCOPE, + [[SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(OUT_SCOPE)], + [], + []]); + }); + }, 'Redirect to same-origin out-scope with opaque redirect response.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(SCOPE1), + SCOPE1, + [[SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(SCOPE1), SCOPE1], + [], + []]); + }); + }, 'Redirect to same-origin same-scope with opaque redirect response.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(SCOPE2), + SCOPE2, + [[SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(SCOPE2)], + [SCOPE2], + []]); + }); + }, 'Redirect to same-origin other-scope with opaque redirect response.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaque&url=' + + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE), + OTHER_ORIGIN_OUT_SCOPE, + [[SCOPE1 + 'sw=opaque&url=' + + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)], + [], + []]); + }); + }, 'Redirect to other-origin out-scope with opaque redirect response.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaque&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE), + OTHER_ORIGIN_SCOPE, + [[SCOPE1 + 'sw=opaque&url=' + + encodeURIComponent(OTHER_ORIGIN_SCOPE)], + [], + [OTHER_ORIGIN_SCOPE]]); + }); + }, 'Redirect to other-origin in-scope with opaque redirect response.'); + +// Opaque redirect passed through Cache. +// SW responds with an opaque redirectresponse from the Cache API. +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(OUT_SCOPE), + OUT_SCOPE, + [[SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(OUT_SCOPE)], + [], + []]); + }); + }, + 'Redirect to same-origin out-scope with opaque redirect response which ' + + 'is passed through Cache.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(SCOPE1), + SCOPE1, + [[SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(SCOPE1), SCOPE1], + [], + []]); + }); + }, + 'Redirect to same-origin same-scope with opaque redirect response which ' + + 'is passed through Cache.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(SCOPE2), + SCOPE2, + [[SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(SCOPE2)], + [SCOPE2], + []]); + }); + }, + 'Redirect to same-origin other-scope with opaque redirect response which ' + + 'is passed through Cache.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE), + OTHER_ORIGIN_OUT_SCOPE, + [[SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE)], + [], + []]); + }); + }, + 'Redirect to other-origin out-scope with opaque redirect response which ' + + 'is passed through Cache.'); +promise_test(function(t) { + return setup_environment(t).then(function() { + return test_redirect( + SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(OTHER_ORIGIN_SCOPE), + OTHER_ORIGIN_SCOPE, + [[SCOPE1 + 'sw=opaqueThroughCache&url=' + + encodeURIComponent(OTHER_ORIGIN_SCOPE)], + [], + [OTHER_ORIGIN_SCOPE]]); + }); + }, + 'Redirect to other-origin in-scope with opaque redirect response which ' + + 'is passed through Cache.'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/onactivate-script-error.https.html b/testing/web-platform/tests/service-workers/service-worker/onactivate-script-error.https.html new file mode 100644 index 000000000..23a7f2c86 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/onactivate-script-error.https.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +function wait_for_install(worker) { + return new Promise(function(resolve, reject) { + worker.addEventListener('statechange', function(event) { + if (worker.state == 'installed') + resolve(); + else if (worker.state == 'redundant') + reject(); + }); + }); +} + +function wait_for_activate(worker) { + return new Promise(function(resolve, reject) { + worker.addEventListener('statechange', function(event) { + if (worker.state == 'activated') + resolve(); + else if (worker.state == 'redundant') + reject(); + }); + }); +} + +function make_test(name, script) { + promise_test(function(t) { + var scope = script; + var registration; + return service_worker_unregister_and_register(t, script, scope) + .then(function(r) { + registration = r; + return wait_for_install(registration.installing); + }) + .then(function() { + // Activate should succeed regardless of script errors. + if (registration.active && registration.active.state == 'activated') { + return Promise.resolve(); + } else if (registration.active) { + return wait_for_activate(registration.active); + } + + return wait_for_activate(registration.waiting); + }); + }, name); +} + +[ + { + name: 'activate handler throws an error', + script: 'resources/onactivate-throw-error-worker.js', + }, + { + name: 'activate handler throws an error, error handler does not cancel', + script: 'resources/onactivate-throw-error-with-empty-onerror-worker.js', + }, + { + name: 'activate handler dispatches an event that throws an error', + script: 'resources/onactivate-throw-error-from-nested-event-worker.js', + }, + { + name: 'activate handler throws an error that is cancelled', + script: 'resources/onactivate-throw-error-then-cancel-worker.js', + }, + { + name: 'activate handler throws an error and prevents default', + script: 'resources/onactivate-throw-error-then-prevent-default-worker.js', + } +].forEach(function(test_case) { + make_test(test_case.name, test_case.script); + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/oninstall-script-error.https.html b/testing/web-platform/tests/service-workers/service-worker/oninstall-script-error.https.html new file mode 100644 index 000000000..a9ca19cab --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/oninstall-script-error.https.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +function wait_for_install_event(worker) { + return new Promise(function(resolve) { + worker.addEventListener('statechange', function(event) { + if (worker.state == 'installed') + resolve(true); + else if (worker.state == 'redundant') + resolve(false); + }); + }); +} + +function make_test(name, script, expect_install) { + promise_test(function(t) { + var scope = script; + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return wait_for_install_event(registration.installing); + }) + .then(function(did_install) { + assert_equals(did_install, expect_install, + 'The worker was installed'); + }) + }, name); +} + +[ + { + name: 'install handler throws an error', + script: 'resources/oninstall-throw-error-worker.js', + expect_install: false + }, + { + name: 'install handler throws an error, error handler does not cancel', + script: 'resources/oninstall-throw-error-with-empty-onerror-worker.js', + expect_install: false + }, + { + name: 'install handler dispatches an event that throws an error', + script: 'resources/oninstall-throw-error-from-nested-event-worker.js', + expect_install: true + }, + + // The following two cases test what happens when the ServiceWorkerGlobalScope + // 'error' event handler cancels the resulting error event. Since the + // original 'install' event handler through, the installation should still + // be stopped in this case. See: + // https://github.com/slightlyoff/ServiceWorker/issues/778 + { + name: 'install handler throws an error that is cancelled', + script: 'resources/oninstall-throw-error-then-cancel-worker.js', + expect_install: false + }, + { + name: 'install handler throws an error and prevents default', + script: 'resources/oninstall-throw-error-then-prevent-default-worker.js', + expect_install: false + } +].forEach(function(test_case) { + make_test(test_case.name, test_case.script, test_case.expect_install); + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/performance-timeline.https.html b/testing/web-platform/tests/service-workers/service-worker/performance-timeline.https.html new file mode 100644 index 000000000..182076baa --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/performance-timeline.https.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +service_worker_test( + 'resources/performance-timeline-worker.js', + 'Test Performance Timeline API in Service Worker'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html b/testing/web-platform/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html new file mode 100644 index 000000000..38b4f56e7 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<title>Service Worker: postMessage to Client</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var frame; +var t = async_test('postMessage MessagePorts from ServiceWorker to Client'); +t.step(function() { + var scope = 'resources/blank.html' + service_worker_unregister_and_register( + t, 'resources/postmessage-msgport-to-client-worker.js', scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + var w = frame.contentWindow; + w.navigator.serviceWorker.onmessage = t.step_func(onMessage); + w.navigator.serviceWorker.controller.postMessage('ping'); + }) + .catch(unreached_rejection(t)); + + var result = []; + var expected = [ + 'Acking value: 1', + 'Acking value: 2', + ]; + + function onMessage(e) { + var message = e.data; + if ('port' in message) { + var port = message.port; + port.postMessage({value: 1}); + port.postMessage({value: 2}); + port.postMessage({done: true}); + } else if ('ack' in message) { + result.push(message.ack); + } else if ('done' in message) { + assert_array_equals( + result, expected, + 'Worker should post back expected values via MessagePort.'); + frame.remove(); + service_worker_unregister_and_done(t, scope); + } else { + assert_unreached('Got unexpected message from ServiceWorker'); + } + } + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/postmessage-to-client.https.html b/testing/web-platform/tests/service-workers/service-worker/postmessage-to-client.https.html new file mode 100644 index 000000000..a031ee2ed --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/postmessage-to-client.https.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title>Service Worker: postMessage to Client</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var frame; +var t = async_test('postMessage from ServiceWorker to Client'); +t.step(function() { + var scope = 'resources/blank.html'; + var host_info = get_host_info(); + var sw; + service_worker_unregister_and_register( + t, 'resources/postmessage-to-client-worker.js', scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + sw = frame.contentWindow.navigator.serviceWorker; + sw.onmessage = t.step_func(onMessage); + sw.controller.postMessage('ping'); + }) + .catch(unreached_rejection(t)); + + var result = []; + var expected = ['Sending message via clients']; + + function onMessage(e) { + assert_equals(e.bubbles, false, 'message events should not bubble.'); + assert_equals(e.cancelable, false, 'message events should not be cancelable.'); + assert_equals(e.origin, host_info['HTTPS_ORIGIN'], 'message event\'s origin should be set correctly.'); +// XXXkhuey fixme! +// assert_equals(e.source, sw.controller, 'source should be ServiceWorker.'); + + var message = e.data; + if (message === 'quit') { + assert_array_equals(result, expected, + 'Worker should post back expected messages.'); + frame.remove(); + service_worker_unregister_and_done(t, scope); + } else { + result.push(message); + } + } + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/postmessage.https.html b/testing/web-platform/tests/service-workers/service-worker/postmessage.https.html new file mode 100644 index 000000000..a6f665179 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/postmessage.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Service Worker: postMessage</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +async_test(function(t) { + var scope = 'resources/blank.html'; + var registration; + var worker; + service_worker_unregister_and_register( + t, 'resources/postmessage-worker.js', scope) + .then(function(r) { + registration = r; + worker = registration.installing; + var messageChannel = new MessageChannel(); + messageChannel.port1.onmessage = t.step_func(onMessage); + worker.postMessage({port: messageChannel.port2}, + [messageChannel.port2]); + worker.postMessage({value: 1}); + worker.postMessage({value: 2}); + worker.postMessage({done: true}); + }) + .catch(unreached_rejection(t)); + + var result = []; + var expected = [ + 'Acking value: 1', + 'Acking value: 2', + ]; + + function onMessage(e) { + var message = e.data; + if (message === 'quit') { + assert_array_equals(result, expected, + 'Worker should post back expected values.'); + postMessageToRedundantWorker(); + } else { + result.push(message); + } + }; + + function postMessageToRedundantWorker() { + registration.unregister(scope) + .then(function() { + return wait_for_state(t, worker, 'redundant'); + }) + .then(function() { + assert_equals(worker.state, 'redundant'); + assert_throws( + {name:'InvalidStateError'}, + function() { worker.postMessage(''); }, + 'Calling postMessage on a redundant ServiceWorker should ' + + 'throw InvalidStateError.'); + t.done(); + }) + .catch(unreached_rejection(t)); + } + }, 'postMessage to a ServiceWorker (and back via MessagePort)'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/ready.https.html b/testing/web-platform/tests/service-workers/service-worker/ready.https.html new file mode 100644 index 000000000..ee6a97ca8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/ready.https.html @@ -0,0 +1,172 @@ +<!DOCTYPE html> +<title>Service Worker: navigator.serviceWorker.ready</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +test(function() { + var promise = navigator.serviceWorker.ready; + assert_equals(promise, navigator.serviceWorker.ready, + 'repeated access to ready without intervening ' + + 'registrations should return the same Promise object'); + }, 'ready returns the same Promise object'); + +async_test(function(t) { + with_iframe('resources/blank.html?uncontrolled') + .then(t.step_func(function(frame) { + var promise = frame.contentWindow.navigator.serviceWorker.ready; + assert_equals(Object.getPrototypeOf(promise), + frame.contentWindow.Promise.prototype, + 'the Promise should be in the context of the ' + + 'related document'); + frame.remove(); + t.done(); + })); + }, 'ready returns a Promise object in the context of the related document'); + +async_test(function(t) { + var url = 'resources/empty-worker.js'; + var scope = 'resources/blank.html?ready-controlled'; + var expected_url = normalizeURL(url); + var frame; + + service_worker_unregister_and_register(t, url, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return frame.contentWindow.navigator.serviceWorker.ready; + }) + .then(function(registration) { + assert_equals(registration.installing, null, + 'installing should be null'); + assert_equals(registration.waiting, null, + 'waiting should be null'); + assert_equals(registration.active.scriptURL, expected_url, + 'active after ready should not be null'); + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller.scriptURL, + expected_url, + 'controlled document should have a controller'); + + frame.remove(); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'ready on a controlled document'); + +async_test(function(t) { + var url = 'resources/empty-worker.js'; + var scope = 'resources/blank.html?ready-potential-controlled'; + var expected_url = normalizeURL(url); + var frame; + + with_iframe(scope) + .then(function(f) { + frame = f; + return navigator.serviceWorker.register(url, {scope:scope}); + }) + .then(function() { + return frame.contentWindow.navigator.serviceWorker.ready; + }) + .then(function(registration) { + assert_equals(registration.installing, null, + 'installing should be null'); + assert_equals(registration.waiting, null, + 'waiting should be null.') + assert_equals(registration.active.scriptURL, expected_url, + 'active after ready should not be null'); + assert_equals(frame.contentWindow.navigator.serviceWorker.controller, + null, + 'uncontrolled document should not have a controller'); + + frame.remove(); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'ready on a potential controlled document'); + +async_test(function(t) { + var url = 'resources/empty-worker.js'; + var matched_scope = 'resources/blank.html?ready-after-match'; + var longer_matched_scope = 'resources/blank.html?ready-after-match-longer'; + var frame, registration; + + Promise.all([service_worker_unregister(t, matched_scope), + service_worker_unregister(t, longer_matched_scope)]) + .then(function() { + return with_iframe(longer_matched_scope); + }) + .then(function(f) { + frame = f; + return navigator.serviceWorker.register(url, {scope: matched_scope}); + }) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return navigator.serviceWorker.register( + url, {scope: longer_matched_scope}); + }) + .then(function() { + return frame.contentWindow.navigator.serviceWorker.ready; + }) + .then(function(r) { + assert_equals(r.scope, normalizeURL(longer_matched_scope), + 'longer matched registration should be returned'); + assert_equals(frame.contentWindow.navigator.serviceWorker.controller, + null, 'controller should be null'); + return registration.unregister(); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, longer_matched_scope); + }) + .catch(unreached_rejection(t)); + }, 'ready after a longer matched registration registered'); + +async_test(function(t) { + var url = 'resources/empty-worker.js'; + var matched_scope = 'resources/blank.html?ready-after-resolve'; + var longer_matched_scope = + 'resources/blank.html?ready-after-resolve-longer'; + var frame, registration; + + service_worker_unregister_and_register(t, url, matched_scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(longer_matched_scope); + }) + .then(function(f) { + frame = f; + return f.contentWindow.navigator.serviceWorker.ready; + }) + .then(function(r) { + assert_equals(r.scope, normalizeURL(matched_scope), + 'matched registration should be returned'); + return navigator.serviceWorker.register( + url, {scope: longer_matched_scope}); + }) + .then(function() { + return frame.contentWindow.navigator.serviceWorker.ready; + }) + .then(function(r) { + assert_equals(r.scope, normalizeURL(matched_scope), + 'ready should only be resolved once'); + return registration.unregister(); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, longer_matched_scope); + }) + .catch(unreached_rejection(t)); + }, 'access ready after it has been resolved'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/referer.https.html b/testing/web-platform/tests/service-workers/service-worker/referer.https.html new file mode 100644 index 000000000..9b3565329 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/referer.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Service Worker: check referer of fetch()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +async_test(function(t) { + var SCOPE = 'resources/referer-iframe.html'; + var SCRIPT = 'resources/fetch-rewrite-worker.js'; + var host_info = get_host_info(); + service_worker_unregister_and_register(t, SCRIPT, SCOPE) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(function(frame) { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(function(e) { + assert_equals(e.data.results, 'finish'); + frame.remove(); + service_worker_unregister_and_done(t, SCOPE); + }); + frame.contentWindow.postMessage({}, + host_info['HTTPS_ORIGIN'], + [channel.port2]); + }) + .catch(unreached_rejection(t)); + }, 'Verify the referer'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/register-closed-window.https.html b/testing/web-platform/tests/service-workers/service-worker/register-closed-window.https.html new file mode 100644 index 000000000..221356716 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/register-closed-window.https.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Service Worker: Register() on Closed Window</title> +<meta name=timeout content=long> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> + +var host_info = get_host_info(); +var frameURL = host_info['HTTPS_ORIGIN'] + base_path() + + 'resources/register-closed-window-iframe.html'; + +async_test(function(t) { + var frame; + with_iframe(frameURL).then(function(f) { + frame = f; + return new Promise(function(resolve) { + window.addEventListener('message', function messageHandler(evt) { + window.removeEventListener('message', messageHandler); + resolve(evt.data); + }); + frame.contentWindow.postMessage('START', '*'); + }); + }).then(function(result) { + assert_equals(result, 'OK', 'frame should complete without crashing'); + frame.remove(); + t.done(); + }).catch(unreached_rejection(t)); +}, 'Call register() on ServiceWorkerContainer owned by closed window.'); + +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/register-default-scope.https.html b/testing/web-platform/tests/service-workers/service-worker/register-default-scope.https.html new file mode 100644 index 000000000..dc136d4e8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/register-default-scope.https.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<title>register() and scope</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var script_url = new URL(script, location.href); + var expected_scope = new URL('./', script_url).href; + return service_worker_unregister(t, expected_scope) + .then(function() { + return navigator.serviceWorker.register('resources/empty-worker.js'); + }).then(function(registration) { + assert_equals(registration.scope, expected_scope, + 'The default scope should be URL("./", script_url)'); + return registration.unregister(); + }).then(function() { + t.done(); + }); + }, 'default scope'); + +promise_test(function(t) { + // This script must be different than the 'default scope' test, or else + // the scopes will collide. + var script = 'resources/empty.js'; + var script_url = new URL(script, location.href); + var expected_scope = new URL('./', script_url).href; + return service_worker_unregister(t, expected_scope) + .then(function() { + return navigator.serviceWorker.register('resources/empty.js', + { scope: undefined }); + }).then(function(registration) { + assert_equals(registration.scope, expected_scope, + 'The default scope should be URL("./", script_url)'); + return registration.unregister(); + }).then(function() { + t.done(); + }); + }, 'undefined scope'); + +promise_test(function(t) { + var script = 'resources/simple-fetch-worker.js'; + var script_url = new URL(script, location.href); + var expected_scope = new URL('./', script_url).href; + return service_worker_unregister(t, expected_scope) + .then(function() { + return navigator.serviceWorker.register('resources/empty.js', + { scope: null }); + }) + .then( + function(registration) { + assert_unreached('register should fail'); + service_worker_unregister_and_done(t, registration.scope); + }, + function(error) { + assert_equals(error.name, 'SecurityError', + 'passing a null scope should be interpreted as ' + + 'scope="null" which violates the path restriction'); + t.done(); + }); + }, 'null scope'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html b/testing/web-platform/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html new file mode 100644 index 000000000..445be7409 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html @@ -0,0 +1,240 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var script1 = normalizeURL('resources/empty-worker.js'); +var script2 = normalizeURL('resources/empty-worker.js?new'); + +async_test(function(t) { + var scope = 'resources/scope/register-new-script-concurrently'; + var register_promise1; + var register_promise2; + + service_worker_unregister(t, scope) + .then(function() { + register_promise1 = navigator.serviceWorker.register(script1, + {scope: scope}); + register_promise2 = navigator.serviceWorker.register(script2, + {scope: scope}); + return register_promise1; + }) + .then(function(registration) { + assert_equals(registration.installing.scriptURL, script1, + 'on first register, first script should be installing'); + assert_equals(registration.waiting, null, + 'on first register, waiting should be null'); + assert_equals(registration.active, null, + 'on first register, active should be null'); + return register_promise2; + }) + .then(function(registration) { + assert_equals( + registration.installing.scriptURL, script2, + 'on second register, second script should be installing'); + // Spec allows racing: the first register may have finished + // or the second one could have terminated the installing worker. + assert_true(registration.waiting == null || + registration.waiting.scriptURL == script1, + 'on second register, .waiting should be null or the ' + + 'first script'); + assert_true(registration.active == null || + (registration.waiting == null && + registration.active.scriptURL == script1), + 'on second register, .active should be null or the ' + + 'first script'); + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Register different scripts concurrently'); + +async_test(function(t) { + var scope = 'resources/scope/register-then-register-new-script'; + var registration; + + service_worker_unregister_and_register(t, script1, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'on activated, installing should be null'); + assert_equals(registration.waiting, null, + 'on activated, waiting should be null'); + assert_equals(registration.active.scriptURL, script1, + 'on activated, the first script should be active'); + return navigator.serviceWorker.register(script2, {scope:scope}); + }) + .then(function(r) { + registration = r; + assert_equals(registration.installing.scriptURL, script2, + 'on second register, the second script should be ' + + 'installing'); + assert_equals(registration.waiting, null, + 'on second register, waiting should be null'); + assert_equals(registration.active.scriptURL, script1, + 'on second register, the first script should be ' + + 'active'); + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'on installed, installing should be null'); + // Since the registration is not controlling any document, the new + // worker can immediately transition to active. + if (registration.waiting) { + assert_equals(registration.waiting.scriptURL, script2, + 'on installed, the second script may still be waiting'); + assert_equals(registration.active.scriptURL, script1, + 'on installed, the first script may be active'); + } else { + assert_equals(registration.active.scriptURL, script2, + 'on installed, the second script may be active'); + } + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Register then register new script URL'); + +async_test(function(t) { + var scope = 'resources/scope/register-then-register-new-script-404'; + var registration; + + service_worker_unregister_and_register(t, script1, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'on activated, installing should be null'); + assert_equals(registration.waiting, null, + 'on activated, waiting should be null'); + assert_equals(registration.active.scriptURL, script1, + 'on activated, the first script should be active'); + return navigator.serviceWorker.register('this-will-404.js', + {scope:scope}); + }) + .then( + function() { assert_unreached('register should reject'); }, + function(error) { + assert_equals(registration.installing, null, + 'on rejected, installing should be null'); + assert_equals(registration.waiting, null, + 'on rejected, waiting should be null'); + assert_equals(registration.active.scriptURL, script1, + 'on rejected, the first script should be active'); + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Register then register new script URL that 404s'); + +async_test(function(t) { + var scope = 'resources/scope/register-then-register-new-script-reject-install'; + var reject_script = normalizeURL('resources/reject-install-worker.js'); + var registration; + + service_worker_unregister_and_register(t, script1, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'on activated, installing should be null'); + assert_equals(registration.waiting, null, + 'on activated, waiting should be null'); + assert_equals(registration.active.scriptURL, script1, + 'on activated, the first script should be active'); + return navigator.serviceWorker.register(reject_script, {scope:scope}); + }) + .then(function(r) { + registration = r; + assert_equals(registration.installing.scriptURL, reject_script, + 'on update, the second script should be installing'); + assert_equals(registration.waiting, null, + 'on update, waiting should be null'); + assert_equals(registration.active.scriptURL, script1, + 'on update, the first script should be active'); + return wait_for_state(t, registration.installing, 'redundant'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'on redundant, installing should be null'); + assert_equals(registration.waiting, null, + 'on redundant, waiting should be null'); + assert_equals(registration.active.scriptURL, script1, + 'on redundant, the first script should be active'); + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Register then register new script that does not install'); + +async_test(function(t) { + var scope = 'resources/scope/register-new-script-controller'; + var iframe; + var registration; + + service_worker_unregister_and_register(t, script1, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + iframe = frame; + return navigator.serviceWorker.register(script2, { scope: scope }) + }) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + var sw_container = iframe.contentWindow.navigator.serviceWorker; + assert_equals(sw_container.controller.scriptURL, script1, + 'the old version should control the old doc'); + return with_iframe(scope); + }) + .then(function(frame) { + var sw_container = frame.contentWindow.navigator.serviceWorker; + assert_equals(sw_container.controller.scriptURL, script1, + 'the old version should control a new doc'); + var onactivated_promise = wait_for_state(t, + registration.waiting, + 'activated'); + frame.remove(); + iframe.remove(); + return onactivated_promise; + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + var sw_container = frame.contentWindow.navigator.serviceWorker; + assert_equals(sw_container.controller.scriptURL, script2, + 'the new version should control a new doc'); + frame.remove(); + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Register same-scope new script url effect on controller'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html b/testing/web-platform/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html new file mode 100644 index 000000000..e23f9f4fc --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<title>Service Worker: Register wait-forever-in-install-worker</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var bad_script = 'resources/wait-forever-in-install-worker.js'; + var good_script = 'resources/empty-worker.js'; + var scope = 'resources/wait-forever-in-install-worker'; + var other_scope = 'resources/wait-forever-in-install-worker-other'; + var registration; + var registerPromise; + + return navigator.serviceWorker.register(bad_script, {scope: scope}) + .then(function(r) { + registration = r; + assert_equals(registration.installing.scriptURL, + normalizeURL(bad_script)); + + // This register job should not start until the first + // register for the same scope completes. + registerPromise = + navigator.serviceWorker.register(good_script, {scope: scope}); + + // In order to test that the above register does not complete + // we will perform a register() on a different scope. The + // assumption here is that the previous register call would + // have completed in the same timeframe if it was able to do + // so. + return navigator.serviceWorker.register(good_script, + {scope: other_scope}); + }) + .then(function(swr) { + return swr.unregister(); + }) + .then(function() { + assert_equals(registration.installing.scriptURL, + normalizeURL(bad_script)); + registration.installing.postMessage('STOP_WAITING'); + return registerPromise; + }) + .then(function(swr) { + assert_equals(registration.installing.scriptURL, + normalizeURL(good_script)); + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return service_worker_unregister_and_done(t, scope); + }) + }, 'register worker that calls waitUntil with a promise that never ' + + 'resolves in oninstall'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/registration-end-to-end.https.html b/testing/web-platform/tests/service-workers/service-worker/registration-end-to-end.https.html new file mode 100644 index 000000000..e92b6502f --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/registration-end-to-end.https.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<title>Service Worker: registration end-to-end</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var t = async_test('Registration: end-to-end'); +t.step(function() { + + var scope = 'resources/in-scope/'; + var serviceWorkerStates = []; + var lastServiceWorkerState = ''; + var receivedMessageFromPort = ''; + var currentChangeCount = 0; + + assert_true(navigator.serviceWorker instanceof ServiceWorkerContainer); + assert_equals(typeof navigator.serviceWorker.register, 'function'); + assert_equals(typeof navigator.serviceWorker.getRegistration, 'function'); + + navigator.serviceWorker.oncurrentchange = function() { + ++currentChangeCount; + }; + + service_worker_unregister_and_register( + t, 'resources/end-to-end-worker.js', scope) + .then(onRegister) + .catch(unreached_rejection(t)); + + function sendMessagePort(worker, from) { + var messageChannel = new MessageChannel(); + worker.postMessage({from:from, port:messageChannel.port2}, [messageChannel.port2]); + return messageChannel.port1; + } + + function onRegister(registration) { + var sw = registration.installing; + serviceWorkerStates.push(sw.state); + lastServiceWorkerState = sw.state; + + var sawMessage = new Promise(t.step_func(function(resolve) { + sendMessagePort(sw, 'registering doc').onmessage = t.step_func(function (e) { + receivedMessageFromPort = e.data; + resolve(); + }); + })); + + var sawActive = new Promise(t.step_func(function(resolve) { + sw.onstatechange = t.step_func(function() { + serviceWorkerStates.push(sw.state); + + switch (sw.state) { + case 'installed': + assert_equals(lastServiceWorkerState, 'installing'); + break; + case 'activating': + assert_equals(lastServiceWorkerState, 'installed'); + break; + case 'activated': + assert_equals(lastServiceWorkerState, 'activating'); + break; + default: + // We won't see 'redundant' because onstatechange is + // overwritten before calling unregister. + assert_unreached('Unexpected state: ' + sw.state); + } + + lastServiceWorkerState = sw.state; + if (sw.state === 'activated') + resolve(); + }); + })); + + Promise.all([sawMessage, sawActive]).then(t.step_func(function() { + assert_array_equals(serviceWorkerStates, + ['installing', 'installed', 'activating', 'activated'], + 'Service worker should pass through all states'); + + assert_equals(currentChangeCount, 0, + 'Should not see current changes since document is out of scope'); + + assert_equals(receivedMessageFromPort, 'Ack for: registering doc'); + + var sawRedundant = new Promise(t.step_func(function(resolve) { + sw.onstatechange = t.step_func(function() { + assert_equals(sw.state, 'redundant'); + resolve(); + }); + })); + registration.unregister(); + sawRedundant.then(t.step_func(function() { + t.done(); + })); + })); + } +}); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/registration-events.https.html b/testing/web-platform/tests/service-workers/service-worker/registration-events.https.html new file mode 100644 index 000000000..972ce7410 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/registration-events.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Service Worker: registration events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var t = async_test('Registration: events'); +t.step(function() { + var scope = 'resources/in-scope/'; + service_worker_unregister_and_register( + t, 'resources/events-worker.js', scope) + .then(t.step_func(function(registration) { + onRegister(registration.installing); + })) + .catch(unreached_rejection(t)); + + function sendMessagePort(worker, from) { + var messageChannel = new MessageChannel(); + worker.postMessage({from:from, port:messageChannel.port2}, [messageChannel.port2]); + return messageChannel.port1; + } + + function onRegister(sw) { + sw.onstatechange = t.step_func(function() { + if (sw.state !== 'activated') + return; + + sendMessagePort(sw, 'registering doc').onmessage = t.step_func(function (e) { + assert_array_equals(e.data.events, + ['install', 'activate'], + 'Worker should see install then activate events'); + service_worker_unregister_and_done(t, scope); + }); + }); + } +}); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/registration-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/registration-iframe.https.html new file mode 100644 index 000000000..fb60afe84 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/registration-iframe.https.html @@ -0,0 +1,108 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Service Worker: Registration for iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +// Set script url and scope url relative to the calling frame's document's url. +// Assert the implementation parses the urls against the calling frame's +// document's url. +async_test(function(t) { + var url = 'resources/blank.html'; + var scope = 'resources/registration-for-iframe-from-calling-frame'; + var parsed_scope = normalizeURL(scope); + var script = 'resources/empty-worker.js'; + var parsed_script = normalizeURL(script); + var frame; + var registration; + + service_worker_unregister(t, scope) + .then(function() { return with_iframe(url); }) + .then(function(f) { + frame = f; + return frame.contentWindow.navigator.serviceWorker.register( + script, + { scope: scope }); + }) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + assert_equals( + registration.scope, parsed_scope, + 'registration\'s scope must be the scope parsed against calling ' + + 'document\'s url'); + assert_equals( + registration.active.scriptURL, parsed_script, + 'worker\'s script must be the url parsed against calling ' + + 'document\'s url'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Subframe\'s container\'s register method should use calling frame\'s ' + + 'document\'s url as a base url for parsing its script url and scope url ' + + '- normal case'); + +// Set script url and scope url relative to the iframe's document's url. +// Assert the implementation throws a NetworkError exception. +async_test(function(t) { + var url = 'resources/blank.html'; + var scope = 'registration-for-iframe-from-calling-frame'; + var script = 'empty-worker.js'; + var frame; + var registration; + + service_worker_unregister(t, scope) + .then(function() { return with_iframe(url); }) + .then(function(f) { + frame = f; + return frame.contentWindow.navigator.serviceWorker.register( + script, + { scope: scope }); + }) + .then( + function() { + assert_unreached('register() should reject'); + }, + function(e) { + assert_equals(e.name, 'TypeError'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Subframe\'s container\'s register method should use calling frame\'s ' + + 'document\'s url as a base url for parsing its script url and scope url ' + + '- error case'); + +// Set the scope url to a non-subdirectory of the script url. +// Assert the implementation throws a SecurityError exception. +async_test(function(t) { + var url = 'resources/blank.html'; + var scope = 'registration-for-iframe-from-calling-frame'; + var script = 'resources/empty-worker.js'; + var frame; + var registration; + + service_worker_unregister(t, scope) + .then(function() { return with_iframe(url); }) + .then(function(f) { + frame = f; + return frame.contentWindow.navigator.serviceWorker.register( + script, + { scope: scope }); + }) + .then( + function() { + assert_unreached('register() should reject'); + }, + function(e) { + assert_equals(e.name, 'SecurityError'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'A scope url should start with the given script url'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/registration-service-worker-attributes.https.html b/testing/web-platform/tests/service-workers/service-worker/registration-service-worker-attributes.https.html new file mode 100644 index 000000000..a0dea5428 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/registration-service-worker-attributes.https.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +async_test(function(t) { + var scope = 'resources/scope/installing-waiting-active-after-registration'; + var worker_url = 'resources/empty-worker.js'; + var expected_url = normalizeURL(worker_url); + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + assert_equals(registration.installing.scriptURL, expected_url, + 'installing before updatefound'); + assert_equals(registration.waiting, null, + 'waiting before updatefound'); + assert_equals(registration.active, null, + 'active before updatefound'); + return wait_for_update(t, registration); + }) + .then(function(worker) { + assert_equals(registration.installing.scriptURL, expected_url, + 'installing after updatefound'); + assert_equals(registration.waiting, null, + 'waiting after updatefound'); + assert_equals(registration.active, null, + 'active after updatefound'); + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'installing after installed'); + var newest = registration.waiting || registration.active; + assert_equals(newest.scriptURL, expected_url, + 'waiting or active after installed'); + if (registration.waiting) { + return wait_for_state(t, registration.waiting, 'activated') + .then(function() { + assert_equals(registration.installing, null, + 'installing after activated'); + assert_equals(registration.waiting, null, + 'waiting after activated'); + assert_equals(registration.active.scriptURL, expected_url, + 'active after activated'); + }); + } + }) + .then(function() { + return Promise.all([ + wait_for_state(t, registration.active, 'redundant'), + registration.unregister() + ]); + }) + .then(function() { + assert_equals(registration.installing, null, + 'installing after redundant'); + assert_equals(registration.waiting, null, + 'waiting after redundant'); + // According to spec, Clear Registration runs Update State which is + // immediately followed by setting active to null, which means by the + // time the event loop turns and the Promise for statechange is + // resolved, this will be gone. + assert_equals(registration.active, null, + 'active should be null after redundant'); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'installing/waiting/active after registration'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/registration.https.html b/testing/web-platform/tests/service-workers/service-worker/registration.https.html new file mode 100644 index 000000000..ae9f85fb2 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/registration.https.html @@ -0,0 +1,368 @@ +<!DOCTYPE html> +<title>Service Worker: Registration</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources/registration/normal'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_true(registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + service_worker_unregister_and_done(t, scope); + }) + }, 'Registering normal scope'); + +promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources/registration/scope-with-fragment#ref'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + assert_equals( + registration.scope, + normalizeURL('resources/registration/scope-with-fragment'), + 'A fragment should be removed from scope') + service_worker_unregister_and_done(t, scope); + }) + }, 'Registering scope with fragment'); + +promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources/'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_true(registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + service_worker_unregister_and_done(t, scope); + }) + }, 'Registering same scope as the script directory'); + +promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registering same scope as the script directory without the last ' + + 'slash should fail with SecurityError.'); + }, 'Registering same scope as the script directory without the last slash'); + +promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'different-directory/'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registration scope outside the script directory should fail ' + + 'with SecurityError.'); + }, 'Registration scope outside the script directory'); + +promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'http://example.com/'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registration scope outside domain should fail with SecurityError.'); + }, 'Registering scope outside domain'); + +promise_test(function(t) { + var script = 'http://example.com/worker.js'; + var scope = 'http://example.com/scope/'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registration script outside domain should fail with SecurityError.'); + }, 'Registering script outside domain'); + +promise_test(function(t) { + var script = 'resources/no-such-worker.js'; + var scope = 'resources/scope/no-such-worker'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'Registration of non-existent script should fail.'); + }, 'Registering non-existent script'); + +promise_test(function(t) { + var script = 'resources/invalid-chunked-encoding.py'; + var scope = 'resources/scope/invalid-chunked-encoding/'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'Registration of invalid chunked encoding script should fail.'); + }, 'Registering invalid chunked encoding script'); + +promise_test(function(t) { + var script = 'resources/invalid-chunked-encoding-with-flush.py'; + var scope = 'resources/scope/invalid-chunked-encoding-with-flush/'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'Registration of invalid chunked encoding script should fail.'); + }, 'Registering invalid chunked encoding script with flush'); + +promise_test(function(t) { + var script = 'resources/mime-type-worker.py'; + var scope = 'resources/scope/no-mime-type-worker/'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registration of no MIME type script should fail.'); + }, 'Registering script with no MIME type'); + +promise_test(function(t) { + var script = 'resources/mime-type-worker.py?mime=text/plain'; + var scope = 'resources/scope/bad-mime-type-worker/'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registration of plain text script should fail.'); + }, 'Registering script with bad MIME type'); + +promise_test(function(t) { + var script = 'resources/redirect.py?Redirect=' + + encodeURIComponent('/resources/registration-worker.js'); + var scope = 'resources/scope/redirect/'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registration of redirected script should fail.'); + }, 'Registering redirected script'); + +promise_test(function(t) { + var script = 'resources/malformed-worker.py?parse-error'; + var scope = 'resources/scope/parse-error'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'Registration of script including parse error should fail.'); + }, 'Registering script including parse error'); + +promise_test(function(t) { + var script = 'resources/malformed-worker.py?undefined-error'; + var scope = 'resources/scope/undefined-error'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'Registration of script including undefined error should fail.'); + }, 'Registering script including undefined error'); + +promise_test(function(t) { + var script = 'resources/malformed-worker.py?uncaught-exception'; + var scope = 'resources/scope/uncaught-exception'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'Registration of script including uncaught exception should fail.'); + }, 'Registering script including uncaught exception'); + +promise_test(function(t) { + var script = 'resources/malformed-worker.py?caught-exception'; + var scope = 'resources/scope/caught-exception'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_true(registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + service_worker_unregister_and_done(t, scope); + }) + }, 'Registering script including caught exception'); + +promise_test(function(t) { + var script = 'resources/malformed-worker.py?import-malformed-script'; + var scope = 'resources/scope/import-malformed-script'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'Registration of script importing malformed script should fail.'); + }, 'Registering script importing malformed script'); + +promise_test(function(t) { + var script = 'resources/malformed-worker.py?import-no-such-script'; + var scope = 'resources/scope/import-no-such-script'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'Registration of script importing non-existent script should fail.'); + }, 'Registering script importing non-existent script'); + +promise_test(function(t) { + // URL-encoded full-width 'scope'. + var name = '%ef%bd%93%ef%bd%83%ef%bd%8f%ef%bd%90%ef%bd%85'; + var script = 'resources/empty-worker.js'; + var scope = 'resources/' + name + '/escaped-multibyte-character-scope'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL(scope), + 'URL-encoded multibyte characters should be available.'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Scope including URL-encoded multibyte characters'); + +promise_test(function(t) { + // Non-URL-encoded full-width "scope". + var name = String.fromCodePoint(0xff53, 0xff43, 0xff4f, 0xff50, 0xff45); + var script = 'resources/empty-worker.js'; + var scope = 'resources/' + name + '/non-escaped-multibyte-character-scope'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL(scope), + 'Non-URL-encoded multibyte characters should be available.'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Scope including non-escaped multibyte characters'); + +promise_test(function(t) { + var script = 'resources%2fempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'URL-encoded slash in the script URL should be rejected.'); + }, 'Script URL including URL-encoded slash'); + +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope%2fencoded-slash-in-scope'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'URL-encoded slash in the scope should be rejected.'); + }, 'Scope including URL-encoded slash'); + +promise_test(function(t) { + var script = 'resources%5cempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'URL-encoded backslash in the script URL should be rejected.'); + }, 'Script URL including URL-encoded backslash'); + +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope%5cencoded-slash-in-scope'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + new TypeError(), + 'URL-encoded backslash in the scope should be rejected.'); + }, 'Scope including URL-encoded backslash'); + +promise_test(function(t) { + var script = 'resources/././empty-worker.js'; + var scope = 'resources/scope/parent-reference-in-script-url'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.installing.scriptURL, + normalizeURL('resources/empty-worker.js'), + 'Script URL including self-reference should be normalized.'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Script URL including self-reference'); + +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/././scope/self-reference-in-scope'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL('resources/scope/self-reference-in-scope'), + 'Scope including self-reference should be normalized.'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Scope including self-reference'); + +promise_test(function(t) { + var script = 'resources/../resources/empty-worker.js'; + var scope = 'resources/scope/parent-reference-in-script-url'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.installing.scriptURL, + normalizeURL('resources/empty-worker.js'), + 'Script URL including parent-reference should be normalized.'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Script URL including parent-reference'); + +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/../resources/scope/parent-reference-in-scope'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL('resources/scope/parent-reference-in-scope'), + 'Scope including parent-reference should be normalized.'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Scope including parent-reference'); + +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/../scope/parent-reference-in-scope'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Scope not under the script directory should be rejected.'); + }, 'Scope including parent-reference and not under the script directory'); + +promise_test(function(t) { + var script = 'resources////empty-worker.js'; + var scope = 'resources/scope/consecutive-slashes-in-script-url'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Consecutive slashes in the script url should not be unified.'); + }, 'Script URL including consecutive slashes'); + +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope////consecutive-slashes-in-scope'; + return navigator.serviceWorker.register(script, {scope: scope}) + .then(function(registration) { + // Although consecutive slashes in the scope are not unified, the + // scope is under the script directory and registration should + // succeed. + assert_equals( + registration.scope, + normalizeURL(scope), + 'Should successfully be registered.'); + service_worker_unregister_and_done(t, scope); + }) + }, 'Scope including consecutive slashes'); + +promise_test(function(t) { + var script = 'filesystem:' + normalizeURL('resources/empty-worker.js'); + var scope = 'resources/scope/filesystem-script-url'; + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registering a script which has same-origin filesystem: URL should ' + + 'fail with SecurityError.'); + }, 'Script URL is same-origin filesystem: URL'); + +promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'filesystem:' + normalizeURL('resources/scope/filesystem-scope-url'); + return assert_promise_rejects( + navigator.serviceWorker.register(script, {scope: scope}), + 'SecurityError', + 'Registering with the scope that has same-origin filesystem: URL ' + + 'should fail with SecurityError.'); + }, 'Scope URL is same-origin filesystem: URL'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/rejections.https.html b/testing/web-platform/tests/service-workers/service-worker/rejections.https.html new file mode 100644 index 000000000..8002ad9a8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/rejections.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Service Worker: Rejection Types</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +(function() { + var t = async_test('Rejections are DOMExceptions'); + t.step(function() { + + navigator.serviceWorker.register('http://example.com').then( + t.step_func(function() { assert_unreached('Registration should fail'); }), + t.step_func(function(reason) { + assert_true(reason instanceof DOMException); + assert_true(reason instanceof Error); + t.done(); + })); + }); +}()); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/request-end-to-end.https.html b/testing/web-platform/tests/service-workers/service-worker/request-end-to-end.https.html new file mode 100644 index 000000000..c9c3b3046 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/request-end-to-end.https.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<title>Service Worker: Request end-to-end</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var t = async_test('Request: end-to-end'); +t.step(function() { + var url = 'resources/request-end-to-end-worker.js'; + var scope = 'resources/blank.html'; + var frames = []; + + service_worker_unregister_and_register(t, url, scope) + .then(onRegister) + .catch(unreached_rejection(t)); + + function sendMessagePort(worker) { + var messageChannel = new MessageChannel(); + worker.postMessage({port:messageChannel.port2}, [messageChannel.port2]); + return messageChannel.port1; + } + + function onRegister(registration) { + var sw = registration.installing; + var port = sendMessagePort(sw); + port.addEventListener('message', t.step_func(function(event) { + onMessage(event); + }), false); + port.start(); + sw.addEventListener('statechange', t.step_func(function(event) { + if (event.target.state == 'activated') + onActive(); + })); + } + + function onActive() { + with_iframe(scope).then(function(f) { frames.push(f); }); + } + + function onMessage(event) { + assert_equals( + event.data.url, + location.href.substring(0, location.href.lastIndexOf('/') + 1) + + scope, + 'request.url should be passed to onfetch event.'); + assert_equals(event.data.mode, 'navigate', + 'request.mode should be passed to onfetch event.'); + assert_equals(event.data.method, 'GET', + 'request.method should be passed to onfetch event.'); + assert_equals(event.data.referrer, location.href, + 'request.referrer should be passed to onfetch event.'); + assert_equals(event.data.headers['user-agent'], undefined, + 'Default User-Agent header should not be passed to onfetch event.') + assert_equals(event.data.errorNameWhileAppendingHeader, 'TypeError', + 'Appending a new header to the request must throw a ' + + 'TypeError.') + frames.forEach(function(f) { f.remove(); }); + service_worker_unregister_and_done(t, scope); + } +}); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html b/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html new file mode 100644 index 000000000..f33c41d71 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resource-timing.https.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +function resourceUrl(path) { + return get_host_info()['HTTP_ORIGIN'] + base_path() + path; +} + +function verify(performance, resource, description) { + var entry = performance.getEntriesByName(resourceUrl(resource))[0]; + assert_greater_than(entry.workerStart, 0, description); + assert_greater_than_equal(entry.workerStart, entry.startTime, description); + assert_less_than_equal(entry.workerStart, entry.fetchStart, description); + if (resource.indexOf('redirect.py') != -1) { + assert_less_than_equal(entry.workerStart, entry.redirectStart, + description); + } else { + assert_equals(entry.redirectStart, 0, description); + } +} + +async_test(function(t) { + var worker_url = 'resources/resource-timing-worker.js'; + var scope = 'resources/resource-timing-iframe.html'; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + var performance = frame.contentWindow.performance; + verify(performance, 'resources/dummy.js', 'Generated response'); + verify(performance, 'resources/empty.js', 'Network fallback'); + verify(performance, 'resources/redirect.py?Redirect=empty.js', + 'Redirect'); + frame.remove(); + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); +}, 'Controlled resource loads'); + +test(function() { + var url = resourceUrl('resources/test-helpers.sub.js'); + var entry = window.performance.getEntriesByName(url)[0]; + assert_equals(entry.workerStart, 0, 'Non-controlled'); +}, 'Non-controlled resource loads'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/404.py b/testing/web-platform/tests/service-workers/service-worker/resources/404.py new file mode 100644 index 000000000..235a3d4ff --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/404.py @@ -0,0 +1,5 @@ +# iframe does not fire onload event if the response's content-type is not +# text/plain or text/html so this script exists if you want to test a 404 load +# in an iframe. +def main(req, res): + return 404, [('Content-Type', 'text/plain')], "Page not found" diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.install.html b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.install.html new file mode 100644 index 000000000..428ad92c7 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.install.html @@ -0,0 +1,26 @@ +<html manifest="appcache-ordering.manifest"> +<script> +var handled = false; + +function installComplete() { + if (handled) + return; + handled = true; + window.parent.notify_appcache_installed(true); +} + +function installFailed() { + if (handled) + return; + handled = true; + window.parent.notify_appcache_installed(false); +} + +applicationCache.oncached = installComplete; +applicationCache.onnoupdate = installComplete; +applicationCache.onupdateready = installFailed; +applicationCache.onerror = installFailed; +applicationCache.onobsolete = installFailed; + +</script> +</html> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.html b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.html new file mode 100644 index 000000000..485ab1771 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.html @@ -0,0 +1,13 @@ +<html> <!-- Intentionally does NOT include a manifest attribute --> +<body> +<!-- This should FALLBACK to ordering.is_appcached.js as specified in manifest + when the appcache is present --> +<script src="appcache-ordering.is-appcached404.js"></script> +<script> + +// If the script of the fallback resource loaded, is_appcached will be defined. +window.parent.notify_is_appcached(typeof is_appcached != 'undefined'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.js b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.js new file mode 100644 index 000000000..a562b6f1c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.is-appcached.js @@ -0,0 +1 @@ +var is_appcached = true; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.manifest b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.manifest new file mode 100644 index 000000000..0deed0e91 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/appcache-ordering.manifest @@ -0,0 +1,7 @@ +CACHE MANIFEST + +appcache-ordering.is-appcached.html + +FALLBACK: +appcache-ordering.is-appcached404.js appcache-ordering.is-appcached.js + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/blank.html b/testing/web-platform/tests/service-workers/service-worker/resources/blank.html new file mode 100644 index 000000000..a3c3a4689 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/blank.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<title>Empty doc</title> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/claim-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/claim-worker.js new file mode 100644 index 000000000..317feb1a0 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/claim-worker.js @@ -0,0 +1,14 @@ +self.addEventListener('message', function(event) { + self.clients.claim() + .then(function(result) { + if (result !== undefined) { + event.data.port.postMessage( + 'FAIL: claim() should be resolved with undefined'); + return; + } + event.data.port.postMessage('PASS'); + }) + .catch(function(error) { + event.data.port.postMessage('FAIL: exception: ' + error.name); + }); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-frame.html b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-frame.html new file mode 100644 index 000000000..7e186f8ee --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-frame.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<script> + fetch("clientId") + .then(function(response) { + return response.text(); + }) + .then(function(text) { + parent.postMessage({id: text}, "*"); + }); +</script> +<body style="background-color: red;"></body> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-worker.js new file mode 100644 index 000000000..3f2424078 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigate-worker.js @@ -0,0 +1,71 @@ +importScripts("worker-testharness.js"); +importScripts("test-helpers.sub.js"); +importScripts("get-host-info.sub.js") +importScripts("testharness-helpers.js") + +self.onfetch = function(e) { + if (e.request.url.indexOf("client-navigate-frame.html") >= 0) { + if (e.clientId === null) { + e.respondWith(fetch(e.request)); + } else { + e.respondWith(Response.error()); + } + return; + } + e.respondWith(new Response(e.clientId)); +}; + +function pass(test, url) { + return { result: test, + url: url }; +} + +function fail(test, reason) { + return { result: "FAILED " + test + " " + reason } +} + +self.onmessage = function(e) { + var port = e.data.port; + var test = e.data.test; + var clientId = e.data.clientId; + var clientUrl = ""; + if (test === "test_client_navigate_success") { + promise_test(function(t) { + this.add_cleanup(() => port.postMessage(pass(test, clientUrl))); + return self.clients.get(clientId) + .then(client => client.navigate("client-navigated-frame.html")) + .then(client => { + clientUrl = client.url; + assert_true(client instanceof WindowClient); + }) + .catch(unreached_rejection(t)); + }, "Return value should be instance of WindowClient"); + } else if (test === "test_client_navigate_failure") { + promise_test(function(t) { + return self.clients.get(clientId) + .then(client => assert_promise_rejects(client.navigate("http://example.com"))) + .catch(unreached_rejection(t)); + }, "Navigating to different origin should reject"); + + promise_test(function(t) { + this.add_cleanup(function() { port.postMessage(pass(test, "")); }); + return self.clients.get(clientId) + .then(client => promise_rejects(t, new TypeError(), client.navigate("about:blank"))) + .catch(unreached_rejection(t)); + }, "Navigating to about:blank should reject with TypeError") + } else if (test === "test_client_navigate_redirect") { + var host_info = get_host_info(); + var url = new URL(host_info['HTTPS_REMOTE_ORIGIN']).toString() + + new URL("client-navigated-frame.html", location).pathname.substring(1); + promise_test(function(t) { + this.add_cleanup(() => port.postMessage(pass(test, clientUrl))); + return self.clients.get(clientId) + .then(client => client.navigate("redirect.py?Redirect=" + url)) + .then(client => { + clientUrl = (client && client.url) || "" + assert_true(client === null); + }) + .catch(unreached_rejection(t)); + }, "Redirecting to another origin should resolve with null"); + } +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/client-navigated-frame.html b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigated-frame.html new file mode 100644 index 000000000..307f7f9ac --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/client-navigated-frame.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<body style="background-color: green;"></body> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-frame.html b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-frame.html new file mode 100644 index 000000000..27143d4b9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-frame.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script> + + fetch("clientId") + .then(function(response) { + return response.text(); + }) + .then(function(text) { + parent.postMessage({clientId: text}, "*"); + }); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-other-origin.html b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-other-origin.html new file mode 100644 index 000000000..cbd3dcc6f --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-other-origin.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<script src="get-host-info.sub.js"></script> +<script src="test-helpers.sub.js"></script> +<script> +var host_info = get_host_info(); +var SCOPE = 'blank.html?clients-get'; +var SCRIPT = 'clients-get-worker.js'; + +var registration; +var worker; +var wait_for_worker_promise = navigator.serviceWorker.getRegistration(SCOPE) + .then(function(reg) { + if (reg) + return reg.unregister(); + }) + .then(function() { + return navigator.serviceWorker.register(SCRIPT, {scope: SCOPE}); + }) + .then(function(reg) { + registration = reg; + worker = reg.installing; + return new Promise(function(resolve) { + worker.addEventListener('statechange', function() { + if (worker.state == 'activated') + resolve(); + }); + }); + }); + +function send_result(result) { + window.parent.postMessage( + {result: result}, + host_info['HTTPS_ORIGIN']); +} + +window.addEventListener("message", on_message, false); + +function on_message(e) { + if (e.origin != host_info['HTTPS_ORIGIN']) { + console.error('invalid origin: ' + e.origin); + return; + } + if (e.data.message == 'get_client_id') { + var otherOriginClientId = e.data.clientId; + wait_for_worker_promise + .then(function() { + return with_iframe(SCOPE); + }) + .then(function(iframe) { + var channel = new MessageChannel(); + channel.port1.onmessage = function(e) { + navigator.serviceWorker.getRegistration(SCOPE) + .then(function(reg) { + reg.unregister(); + send_result(e.data); + }); + }; + iframe.contentWindow.navigator.serviceWorker.controller.postMessage( + {port:channel.port2, clientId: otherOriginClientId, + message: 'get_other_client_id'}, [channel.port2]); + }) + } +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-worker.js new file mode 100644 index 000000000..9ac2c2264 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-get-worker.js @@ -0,0 +1,53 @@ +self.onfetch = function(e) { + if (e.request.url.indexOf("clients-get-frame.html") >= 0) { + if (e.clientId === null) { + e.respondWith(fetch(e.request)); + } else { + e.respondWith(Response.error()); + } + return; + } + e.respondWith(new Response(e.clientId)); +}; + +self.onmessage = function(e) { + var port = e.data.port; + if (e.data.message == 'get_client_ids') { + var clientIds = e.data.clientIds; + var message = []; + + Promise.all( + clientIds.map(function(clientId) { + return self.clients.get(clientId); + }).concat(self.clients.get("invalid-id")) + ).then(function(clients) { + clients.forEach(function(client) { + if (client instanceof Client) { + message.push([client.visibilityState, + client.focused, + client.url, + client.frameType]); + } else { + message.push(client); + } + }); + port.postMessage(message); + }); + } else if (e.data.message == 'get_other_client_id') { + var clientId = e.data.clientId; + var message; + + self.clients.get(clientId) + .then(function(client) { + if (client instanceof Client) { + message = [client.visibilityState, + client.focused, + client.url, + client.frameType]; + } else { + message = client; + } + port.postMessage(message); + }); + } +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html new file mode 100644 index 000000000..51b4dca03 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<title>Empty doc</title> +<!-- + Change the page URL using the History API to ensure that ServiceWorkerClient + uses the creation URL. +--> +<body onload="history.pushState({}, 'title', 'new-url.html')"> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js new file mode 100644 index 000000000..1ae72fb89 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js @@ -0,0 +1,4 @@ +onconnect = function(e) { + var port = e.ports[0]; + port.postMessage('started'); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-worker.js new file mode 100644 index 000000000..f0ae90d81 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/clients-matchall-worker.js @@ -0,0 +1,24 @@ +self.onmessage = function(e) { + var port = e.data.port; + var options = e.data.options; + + self.clients.matchAll(options).then(function(clients) { + var message = []; + clients.forEach(function(client) { + var frame_type = client.frameType; + if (client.url.indexOf('clients-matchall-include-uncontrolled.https.html') > -1 && + client.frameType == 'auxiliary') { + // The test tab might be opened using window.open() by the test framework. + // In that case, just pretend it's top-level! + frame_type = 'top-level'; + } + message.push([client.visibilityState, + client.focused, + client.url, + frame_type]); + }); + // Sort by url + message.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; }); + port.postMessage(message); + }); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js new file mode 100644 index 000000000..82b24459b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-shared-worker-interceptor.js @@ -0,0 +1,7 @@ +var worker_text = 'onconnect = function(e) { e.ports[0].postMessage("worker loading intercepted by service worker"); };'; + +self.onfetch = function(event) { + if (event.request.url.indexOf('dummy-shared-worker.js') != -1) + event.respondWith(new Response(worker_text)); +}; + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-interceptor.js b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-interceptor.js new file mode 100644 index 000000000..43244e1d9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-interceptor.js @@ -0,0 +1,21 @@ +importScripts('get-host-info.sub.js'); + +var worker_text = 'postMessage("worker loading intercepted by service worker"); '; + +self.onfetch = function(event) { + if (event.request.url.indexOf('synthesized') != -1) { + event.respondWith(new Response(worker_text)); + } else if (event.request.url.indexOf('same-origin') != -1) { + event.respondWith(fetch('dummy-worker-script.py')); + } else if (event.request.url.indexOf('cors') != -1) { + var path = (new URL('dummy-worker-script.py', self.location)).pathname; + var url = get_host_info()['HTTPS_REMOTE_ORIGIN'] + path; + var mode = "no-cors"; + if (event.request.url.indexOf('no-cors') == -1) { + url += '?ACAOrigin=*'; + mode = "cors"; + } + event.respondWith(fetch(url, { mode: mode })); + } +}; + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-script.py b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-script.py new file mode 100644 index 000000000..6f40b5ed6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy-worker-script.py @@ -0,0 +1,9 @@ +def main(request, response): + headers = [] + + if "ACAOrigin" in request.GET: + for item in request.GET["ACAOrigin"].split(","): + headers.append(("Access-Control-Allow-Origin", item)) + + return headers, "postMessage('dummy-worker-script loaded');" + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy.html b/testing/web-platform/tests/service-workers/service-worker/resources/dummy.html new file mode 100644 index 000000000..12a179980 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<body>Hello world diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/dummy.txt b/testing/web-platform/tests/service-workers/service-worker/resources/dummy.txt new file mode 100644 index 000000000..802992c42 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/dummy.txt @@ -0,0 +1 @@ +Hello world diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/empty-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/empty-worker.js new file mode 100644 index 000000000..49ceb2648 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/empty-worker.js @@ -0,0 +1 @@ +// Do nothing. diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/empty.js b/testing/web-platform/tests/service-workers/service-worker/resources/empty.js new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/empty.js diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/end-to-end-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/end-to-end-worker.js new file mode 100644 index 000000000..d45a50556 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/end-to-end-worker.js @@ -0,0 +1,7 @@ +onmessage = function(e) { + var message = e.data; + if (typeof message === 'object' && 'port' in message) { + var response = 'Ack for: ' + message.from; + message.port.postMessage(response); + } +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/events-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/events-worker.js new file mode 100644 index 000000000..80a218867 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/events-worker.js @@ -0,0 +1,12 @@ +var eventsSeen = []; + +function handler(event) { eventsSeen.push(event.type); } + +['activate', 'install'].forEach(function(type) { + self.addEventListener(type, handler); + }); + +onmessage = function(e) { + var message = e.data; + message.port.postMessage({events: eventsSeen}); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js b/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js new file mode 100644 index 000000000..d77238d93 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js @@ -0,0 +1,20 @@ +var result = 'FAIL: did not throw.'; + +self.addEventListener('message', function(event) { + event.data.port.postMessage(result); + }); + +self.addEventListener('install', function(event) { + self.installEvent = event; + }); + +self.addEventListener('activate', function(event) { + try { + self.installEvent.waitUntil(new Promise(function(){})); + } catch (error) { + if (error.name == 'InvalidStateError') + result = 'PASS'; + else + result = 'FAIL: unexpected exception: ' + error; + } + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-waituntil.js b/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-waituntil.js new file mode 100644 index 000000000..48fdf1b99 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/extendable-event-waituntil.js @@ -0,0 +1,75 @@ +var pendingPorts = []; +var portResolves = []; + +onmessage = function(e) { + var message = e.data; + if ('port' in message) { + var resolve = self.portResolves.shift(); + if (resolve) + resolve(message.port); + else + self.pendingPorts.push(message.port); + } +}; + +function fulfillPromise() { + return new Promise(function(resolve) { + // Make sure the oninstall/onactivate callback returns first. + Promise.resolve().then(function() { + var port = self.pendingPorts.shift(); + if (port) + resolve(port); + else + self.portResolves.push(resolve); + }); + }).then(function(port) { + port.postMessage('SYNC'); + return new Promise(function(resolve) { + port.onmessage = function(e) { + if (e.data == 'ACK') + resolve(); + }; + }); + }); +} + +function rejectPromise() { + return new Promise(function(resolve, reject) { + // Make sure the oninstall/onactivate callback returns first. + Promise.resolve().then(reject); + }); +} + +function stripScopeName(url) { + return url.split('/').slice(-1)[0]; +} + +oninstall = function(e) { + switch (stripScopeName(self.location.href)) { + case 'install-fulfilled': + e.waitUntil(fulfillPromise()); + break; + case 'install-rejected': + e.waitUntil(rejectPromise()); + break; + case 'install-multiple-fulfilled': + e.waitUntil(fulfillPromise()); + e.waitUntil(fulfillPromise()); + break; + case 'install-reject-precedence': + e.waitUntil(fulfillPromise()); + e.waitUntil(rejectPromise()); + break; + } +}; + +onactivate = function(e) { + switch (stripScopeName(self.location.href)) { + case 'activate-fulfilled': + e.waitUntil(fulfillPromise()); + break; + case 'activate-rejected': + e.waitUntil(rejectPromise()); + break; + } +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js new file mode 100644 index 000000000..517f289fb --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js @@ -0,0 +1,5 @@ +importScripts('worker-testharness.js'); + +this.addEventListener('fetch', function(event) { + event.respondWith(new Response('ERROR')); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control-login.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control-login.html new file mode 100644 index 000000000..0ffab1af5 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control-login.html @@ -0,0 +1,16 @@ +<script> +// Set authentication info +window.addEventListener("message", function(evt) { + var port = evt.ports[0]; + document.cookie = 'cookie=' + evt.data.cookie; + var xhr = new XMLHttpRequest(); + xhr.addEventListener('load', function() { + port.postMessage({msg: 'LOGIN FINISHED'}); + }, false); + xhr.open('GET', + './fetch-access-control.py?Auth', + true, + evt.data.username, evt.data.password); + xhr.send(); + }, false); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control.py b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control.py new file mode 100644 index 000000000..862718ad0 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-access-control.py @@ -0,0 +1,69 @@ +import base64 +import json + +def main(request, response): + headers = [] + headers.append(('X-ServiceWorker-ServerHeader', 'SetInTheServer')) + + if "ACAOrigin" in request.GET: + for item in request.GET["ACAOrigin"].split(","): + headers.append(("Access-Control-Allow-Origin", item)) + + for suffix in ["Headers", "Methods", "Credentials"]: + query = "ACA%s" % suffix + header = "Access-Control-Allow-%s" % suffix + if query in request.GET: + headers.append((header, request.GET[query])) + + if "ACEHeaders" in request.GET: + headers.append(("Access-Control-Expose-Headers", request.GET[query])) + + if ("Auth" in request.GET and not request.auth.username) or "AuthFail" in request.GET: + status = 401 + headers.append(('WWW-Authenticate', 'Basic realm="Restricted"')) + body = 'Authentication canceled' + return status, headers, body + + if "PNGIMAGE" in request.GET: + headers.append(("Content-Type", "image/png")) + body = base64.decodestring("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAhSURBVDhPY3wro/KfgQLABKXJBqMG" + "jBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII=") + return headers, body + + + username = request.auth.username if request.auth.username else "undefined" + password = request.auth.password if request.auth.username else "undefined" + cookie = request.cookies['cookie'].value if 'cookie' in request.cookies else "undefined" + + files = [] + for key, values in request.POST.iteritems(): + assert len(values) == 1 + value = values[0] + if not hasattr(value, "file"): + continue + data = value.file.read() + files.append({"key": key, + "name": value.file.name, + "type": value.type, + "error": 0, #TODO, + "size": len(data), + "content": data}) + + get_data = {key:request.GET[key] for key,value in request.GET.iteritems()} + post_data = {key:request.POST[key] for key,value in request.POST.iteritems() + if not hasattr(request.POST[key], "file")} + headers_data = {key:request.headers[key] for key,value in request.headers.iteritems()} + + data = {"jsonpResult": "success", + "method": request.method, + "headers": headers_data, + "body": request.body, + "files": files, + "GET": get_data, + "POST": post_data, + "username": username, + "password": password, + "cookie": cookie} + + return headers, "report( %s )" % json.dumps(data) diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html new file mode 100644 index 000000000..3822971e8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html @@ -0,0 +1,294 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE'; +var host_info = get_host_info(); +var params = get_query_params(location.href); + +var NOT_TAINTED = 'NOT_TAINTED'; +var TAINTED = 'TAINTED'; +var LOAD_ERROR = 'LOAD_ERROR'; + +function get_query_params(url) { + var search = (new URL(url)).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split('&'); + params.forEach(function(param) { + var element = param.split('='); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} + +function create_test_case_promise(url, cross_origin) { + return new Promise(function(resolve) { + var img = new Image(); + if (cross_origin != '') { + img.crossOrigin = cross_origin; + } + img.onload = function() { + try { + var canvas = document.createElement('canvas'); + canvas.width = 100; + canvas.height = 100; + var context = canvas.getContext('2d'); + context.drawImage(img, 0, 0); + context.getImageData(0, 0, 100, 100); + resolve(NOT_TAINTED); + } catch (e) { + resolve(TAINTED); + } + }; + img.onerror = function() { + resolve(LOAD_ERROR); + } + img.src = url; + }); +} + +function create_test_promise(url, cross_origin, expected_result) { + if (params['cache']) { + url += "&cache"; + } + + return new Promise(function(resolve, reject) { + create_test_case_promise(url, cross_origin) + .then(function(result) { + if (result == expected_result) { + resolve(); + } else { + reject('Result of url:' + url + ' ' + + ' cross_origin: ' + cross_origin + ' must be ' + + expected_result + ' but ' + result); + } + }) + }); +} + +window.addEventListener('message', function(evt) { + var port = evt.ports[0]; + var image_url = host_info['HTTPS_ORIGIN'] + image_path; + var remote_image_url = host_info['HTTPS_REMOTE_ORIGIN'] + image_path; + Promise.all([ + // Reject tests + create_test_promise(image_url + '&reject', '', LOAD_ERROR), + create_test_promise(image_url + '&reject', 'anonymous', LOAD_ERROR), + create_test_promise( + image_url + '&reject', 'use-credentials', LOAD_ERROR), + // Fallback tests + create_test_promise( + image_url + '&ignore', + '', + NOT_TAINTED), + create_test_promise( + remote_image_url + '&ignore', + '', + TAINTED), + create_test_promise( + remote_image_url + '&ignore', + 'anonymous', + LOAD_ERROR), + create_test_promise( + remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ignore', + 'anonymous', + NOT_TAINTED), + create_test_promise( + remote_image_url + '&ignore', + 'use-credentials', + LOAD_ERROR), + create_test_promise( + remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ignore', + 'use-credentials', + LOAD_ERROR), + create_test_promise( + remote_image_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true&ignore', + 'use-credentials', + NOT_TAINTED), + + // Credential test (fallback) + create_test_promise( + image_url + '&Auth&ignore', + '', + NOT_TAINTED), + create_test_promise( + remote_image_url + '&Auth&ignore', + '', + TAINTED), + create_test_promise( + remote_image_url + '&Auth&ignore', + 'anonymous', + LOAD_ERROR), + create_test_promise( + remote_image_url + '&Auth&ignore', + 'use-credentials', + LOAD_ERROR), + create_test_promise( + remote_image_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ignore', + 'use-credentials', + LOAD_ERROR), + create_test_promise( + remote_image_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true&ignore', + 'use-credentials', + NOT_TAINTED), + + // Basic response + create_test_promise( + image_url + + '&mode=same-origin&url=' + encodeURIComponent(image_url), + '', + NOT_TAINTED), + create_test_promise( + image_url + + '&mode=same-origin&url=' + encodeURIComponent(image_url), + 'anonymous', + NOT_TAINTED), + create_test_promise( + image_url + + '&mode=same-origin&url=' + encodeURIComponent(image_url), + 'use-credentials', + NOT_TAINTED), + create_test_promise( + remote_image_url + + '&mode=same-origin&url=' + encodeURIComponent(image_url), + '', + TAINTED), + create_test_promise( + remote_image_url + + '&mode=same-origin&url=' + encodeURIComponent(image_url), + 'anonymous', + NOT_TAINTED), + create_test_promise( + remote_image_url + + '&mode=same-origin&url=' + encodeURIComponent(image_url), + 'use-credentials', + NOT_TAINTED), + + // Opaque response + create_test_promise( + image_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_image_url), + '', + TAINTED), + create_test_promise( + image_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_image_url), + 'anonymous', + LOAD_ERROR), + create_test_promise( + image_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_image_url), + 'use-credentials', + LOAD_ERROR), + create_test_promise( + remote_image_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_image_url), + '', + TAINTED), + create_test_promise( + remote_image_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_image_url), + 'anonymous', + LOAD_ERROR), + create_test_promise( + remote_image_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_image_url), + 'use-credentials', + LOAD_ERROR), + + // CORS response + create_test_promise( + image_url + + '&mode=cors&url=' + + encodeURIComponent(remote_image_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + '', + LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond + // with an Access-Control-Allow-Credentials header. + create_test_promise( + image_url + + '&mode=cors&credentials=same-origin&url=' + + encodeURIComponent(remote_image_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + '', + NOT_TAINTED), + create_test_promise( + image_url + + '&mode=cors&url=' + + encodeURIComponent(remote_image_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'anonymous', + NOT_TAINTED), + create_test_promise( + image_url + + '&mode=cors&url=' + + encodeURIComponent(remote_image_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'use-credentials', + LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond + // with an Access-Control-Allow-Credentials header. + create_test_promise( + image_url + + '&mode=cors&url=' + + encodeURIComponent( + remote_image_url + + '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'use-credentials', + NOT_TAINTED), + create_test_promise( + remote_image_url + + '&mode=cors&url=' + + encodeURIComponent(remote_image_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + '', + LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond + // with an Access-Control-Allow-Credentials header. + create_test_promise( + remote_image_url + + '&mode=cors&credentials=same-origin&url=' + + encodeURIComponent(remote_image_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + '', + TAINTED), // The cross-origin no-cors request is immediately tainted. + // Since this happens before the service worker interception, + // it does not matter what kind of response it returns. + // The result will always be tainted. + create_test_promise( + remote_image_url + + '&mode=cors&url=' + + encodeURIComponent(remote_image_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'anonymous', + NOT_TAINTED), + create_test_promise( + remote_image_url + + '&mode=cors&url=' + + encodeURIComponent(remote_image_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'use-credentials', + LOAD_ERROR), // We expect LOAD_ERROR since the server doesn't respond + // with an Access-Control-Allow-Credentials header. + create_test_promise( + remote_image_url + + '&mode=cors&url=' + + encodeURIComponent( + remote_image_url + + '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'use-credentials', + NOT_TAINTED) + ]) + .then(function() { + port.postMessage({results: 'finish'}); + }) + .catch(function(e) { + port.postMessage({results: 'failure:' + e}); + }); + }, false); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html new file mode 100644 index 000000000..48f618397 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html @@ -0,0 +1,210 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var path = base_path() + 'fetch-access-control.py'; +var host_info = get_host_info(); +var SUCCESS = 'SUCCESS'; +var FAIL = 'FAIL'; + +function create_test_case_promise(url, with_credentials) { + return new Promise(function(resolve) { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.status == 200) { + resolve(SUCCESS); + } else { + resolve("STATUS" + xhr.status); + } + } + xhr.onerror = function() { + resolve(FAIL); + } + xhr.responseType = 'text'; + xhr.withCredentials = with_credentials; + xhr.open('GET', url, true); + xhr.send(); + }); +} + + +function create_test_promise(url, with_credentials, expected_result) { + return new Promise(function(resolve, reject) { + create_test_case_promise(url, with_credentials) + .then(function(result) { + if (result == expected_result) { + resolve(); + } else { + reject('Result of url:' + url + ' ' + + ' with_credentials: ' + with_credentials + ' must be ' + + expected_result + ' but ' + result); + } + }) + }); +} + +function create_serial_promise(test_cases) { + var promise = Promise.resolve(); + test_cases.forEach(function(test_case) { + promise = promise.then(function() { + return create_test_promise(test_case[0], test_case[1], test_case[2]); + }); + }); + return promise; +} + +window.addEventListener('message', function(evt) { + var port = evt.ports[0]; + var url = host_info['HTTPS_ORIGIN'] + path; + var remote_url = host_info['HTTPS_REMOTE_ORIGIN'] + path; + // If the 4th value of the item of TEST_CASES is true, the test case outputs + // warning messages. So such tests must be executed in serial to match the + // expected output text. + var TEST_CASES = [ + // Reject tests + [url + '?reject', false, FAIL], + [url + '?reject', true, FAIL], + [remote_url + '?reject', false, FAIL], + [remote_url + '?reject', true, FAIL], + // Event handler exception tests + [url + '?throw', false, FAIL], + [url + '?throw', true, FAIL], + [remote_url + '?throw', false, FAIL], + [remote_url + '?throw', true, FAIL], + // Reject(resolve-null) tests + [url + '?resolve-null', false, FAIL], + [url + '?resolve-null', true, FAIL], + [remote_url + '?resolve-null', false, FAIL], + [remote_url + '?resolve-null', true, FAIL], + // Fallback tests + [url + '?ignore', false, SUCCESS], + [url + '?ignore', true, SUCCESS], + [remote_url + '?ignore', false, FAIL, true], // Executed in serial. + [remote_url + '?ignore', true, FAIL, true], // Executed in serial. + [ + remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore', + false, SUCCESS + ], + [ + remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore', + true, FAIL, true // Executed in serial. + ], + [ + remote_url + '?ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true&ignore', + true, SUCCESS + ], + // Credential test (fallback) + [url + '?Auth&ignore', false, SUCCESS], + [url + '?Auth&ignore', true, SUCCESS], + [remote_url + '?Auth&ignore', false, FAIL, true], // Executed in serial. + [remote_url + '?Auth&ignore', true, FAIL, true], // Executed in serial. + [ + remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore', + false, 'STATUS401' + ], + [ + remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + '&ignore', + true, FAIL, true // Executed in serial. + ], + [ + remote_url + '?Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true&ignore', + true, SUCCESS + ], + // Basic response + [ + url + '?mode=same-origin&url=' + encodeURIComponent(url), + false, SUCCESS + ], + [ + url + '?mode=same-origin&url=' + encodeURIComponent(url), + false, SUCCESS + ], + [ + remote_url + '?mode=same-origin&url=' + encodeURIComponent(url), + false, SUCCESS + ], + [ + remote_url + '?mode=same-origin&url=' + encodeURIComponent(url), + false, SUCCESS + ], + // Opaque response + [ + url + '?mode=no-cors&url=' + encodeURIComponent(remote_url), + false, FAIL + ], + [ + url + '?mode=no-cors&url=' + encodeURIComponent(remote_url), + false, FAIL + ], + [ + remote_url + '?mode=no-cors&url=' + encodeURIComponent(remote_url), + false, FAIL + ], + [ + remote_url + '?mode=no-cors&url=' + encodeURIComponent(remote_url), + false, FAIL + ], + // CORS response + [ + url + '?mode=cors&url=' + + encodeURIComponent(remote_url + '?ACAOrigin=' + + host_info['HTTPS_ORIGIN']), + false, SUCCESS + ], + [ + url + '?mode=cors&url=' + + encodeURIComponent(remote_url + '?ACAOrigin=' + + host_info['HTTPS_ORIGIN']), + true, FAIL + ], + [ + url + '?mode=cors&url=' + + encodeURIComponent(remote_url + '?ACAOrigin=' + + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true'), + true, SUCCESS + ], + [ + remote_url + '?mode=cors&url=' + + encodeURIComponent(remote_url + '?ACAOrigin=' + + host_info['HTTPS_ORIGIN']), + false, SUCCESS + ], + [ + remote_url + + '?mode=cors&url=' + + encodeURIComponent(remote_url + '?ACAOrigin=' + + host_info['HTTPS_ORIGIN']), + true, FAIL + ], + [ + remote_url + + '?mode=cors&url=' + + encodeURIComponent(remote_url + '?ACAOrigin=' + + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true'), + true, SUCCESS + ] + ]; + var promises = []; + var serial_tests = []; + for (var i = 0; i < TEST_CASES.length ; ++i) { + if (!TEST_CASES[i][3]) { + promises.push(create_test_promise(TEST_CASES[i][0], + TEST_CASES[i][1], + TEST_CASES[i][2])); + } else { + serial_tests.push(TEST_CASES[i]); + } + } + promises.push(create_serial_promise(serial_tests)); + Promise.all(promises) + .then(function() { + port.postMessage({results: 'finish'}); + }) + .catch(function(e) { + port.postMessage({results: 'failure:' + e}); + }); + }, false); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html new file mode 100644 index 000000000..cf7175bc8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html @@ -0,0 +1,72 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE'; +var host_info = get_host_info(); +var results = ''; +var port = undefined; + +function test1() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + test2(); + }; + img.onerror = function() { + results += 'FAIL(1)'; + test2(); + }; + img.src = host_info['HTTPS_ORIGIN'] + image_path; +} + +function test2() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + results += 'FAIL(2)'; + test3(); + }; + img.onerror = function() { + test3(); + }; + img.src = host_info['HTTPS_REMOTE_ORIGIN'] + image_path; +} + +function test3() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + test4(); + }; + img.onerror = function() { + results += 'FAIL(3)'; + test4(); + }; + img.src = './dummy?url=' + + encodeURIComponent(host_info['HTTPS_ORIGIN'] + image_path); +} + +function test4() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + results += 'FAIL(4)'; + finish(); + }; + img.onerror = function() { + finish(); + }; + img.src = './dummy?mode=no-cors&url=' + + encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + image_path); +} + +function finish() { + results += 'finish'; + port.postMessage({results: results}); +} + +window.addEventListener('message', function(evt) { + port = evt.ports[0]; + test1(); + }, false); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers new file mode 100644 index 000000000..300efe049 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers @@ -0,0 +1 @@ +Content-Security-Policy: img-src https://{{host}}:{{ports[https][0]}} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html new file mode 100644 index 000000000..bf8a6d5ce --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<script> +function fetch_url(url) { + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.addEventListener('load', function(event) { + if (request.status == 200) + resolve(request.response); + else + reject(new Error('fetch_url: ' + request.statusText + " : " + url)); + }); + request.addEventListener('error', function(event) { + reject(new Error('fetch_url encountered an error: ' + url)); + }); + request.addEventListener('abort', function(event) { + reject(new Error('fetch_url was aborted: ' + url)); + }); + request.open('GET', url); + request.send(); + }); +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js new file mode 100644 index 000000000..7f66d20df --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js @@ -0,0 +1,19 @@ +var result; + +self.addEventListener('message', function(event) { + event.data.port.postMessage(result); + }); + +self.addEventListener('fetch', function(event) { + setTimeout(function() { + try { + event.respondWith(new Response()); + result = 'FAIL: did not throw'; + } catch (error) { + if (error.name == 'InvalidStateError') + result = 'PASS'; + else + result = 'FAIL: Unexpected exception: ' + error; + } + }, 0); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html new file mode 100644 index 000000000..a4c9307e7 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<script> +function fetch_url(url) { + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.addEventListener('load', function(event) { + resolve(); + }); + request.addEventListener('error', function(event) { + reject(); + }); + request.open('GET', url); + request.send(); + }); +} + +function make_test(testcase) { + var name = testcase.name; + return fetch_url(window.location.href + '?' + name) + .then( + function() { + if (testcase.expect_load) + return Promise.resolve(); + return Promise.reject(new Error( + name + ': expected network error but loaded')); + }, + function() { + if (!testcase.expect_load) + return Promise.resolve(); + return Promise.reject(new Error( + name + ': expected to load but got network error')); + }); +} + +function run_tests() { + var tests = [ + { name: 'prevent-default-and-respond-with', expect_load: true }, + { name: 'prevent-default', expect_load: false }, + { name: 'reject', expect_load: false }, + { name: 'unused-body', expect_load: true }, + { name: 'used-body', expect_load: false }, + { name: 'unused-fetched-body', expect_load: true }, + { name: 'used-fetched-body', expect_load: false } + ].map(make_test); + + Promise.all(tests) + .then(function() { + window.parent.notify_test_done('PASS'); + }) + .catch(function(error) { + window.parent.notify_test_done('FAIL: ' + error.message); + }); +} + +if (!navigator.serviceWorker.controller) + window.parent.notify_done('FAIL: no controller'); +else + run_tests(); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js new file mode 100644 index 000000000..52d4c8e86 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js @@ -0,0 +1,46 @@ +// Test that multiple fetch handlers do not confuse the implementation. +self.addEventListener('fetch', function(event) {}); + +self.addEventListener('fetch', function(event) { + var testcase = new URL(event.request.url).search; + switch (testcase) { + case '?reject': + event.respondWith(Promise.reject()); + break; + case '?prevent-default': + event.preventDefault(); + break; + case '?prevent-default-and-respond-with': + event.preventDefault(); + break; + case '?unused-body': + event.respondWith(new Response('body')); + break; + case '?used-body': + var res = new Response('body'); + res.text(); + event.respondWith(res); + break; + case '?unused-fetched-body': + event.respondWith(fetch('other.html').then(function(res){ + return res; + })); + break; + case '?used-fetched-body': + event.respondWith(fetch('other.html').then(function(res){ + res.text(); + return res; + })); + break; + } + }); + +self.addEventListener('fetch', function(event) {}); + +self.addEventListener('fetch', function(event) { + var testcase = new URL(event.request.url).search; + if (testcase == '?prevent-default-and-respond-with') + event.respondWith(new Response('responding!')); + }); + +self.addEventListener('fetch', function(event) {}); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html new file mode 100644 index 000000000..7548d8af7 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html @@ -0,0 +1,25 @@ +<script> +window.addEventListener('message', function(evt) { + var port = evt.ports[0]; + var data = evt.data; + fetch(new Request(data.url, data.request_init)).then(function(response) { + if (data.request_init.mode === 'no-cors' && data.redirect_dest != 'same-origin') { + if (response.type === data.expected_type && + (response.type === 'opaque' || response.type === 'opaqueredirect') && + response.redirected === data.expected_redirected) { + return {result: 'success', detail: ''}; + } else { + return {result: 'failure', + detail: 'expected type ' + data.expected_type + + ', expected redirected ' + data.expected_redirected + + ' response'}; + } + } + return response.json(); + }).then(function(body) { + port.postMessage({result: body.result, detail: body.detail}); + }).catch(function(e) { + port.postMessage({result: 'reject', detail: e.toString()}); + }); +}); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js new file mode 100644 index 000000000..18da049d6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js @@ -0,0 +1,15 @@ +var result = null; + +self.addEventListener('message', function(event) { + event.data.port.postMessage(result); + }); + +self.addEventListener('fetch', function(event) { + if (!result) + result = 'PASS'; + event.respondWith(new Response()); + }); + +self.addEventListener('fetch', function(event) { + result = 'FAIL: fetch event propagated'; + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-test-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-test-worker.js new file mode 100644 index 000000000..55ba4ab4d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-event-test-worker.js @@ -0,0 +1,151 @@ +function handleString(event) { + event.respondWith(new Response('Test string')); +} + +function handleBlob(event) { + event.respondWith(new Response(new Blob(['Test blob']))); +} + +function handleReferrer(event) { + event.respondWith(new Response(new Blob( + ['Referrer: ' + event.request.referrer]))); +} + +function handleReferrerPolicy(event) { + event.respondWith(new Response(new Blob( + ['ReferrerPolicy: ' + event.request.referrerPolicy]))); +} + +function handleReferrerFull(event) { + event.respondWith(new Response(new Blob( + ['Referrer: ' + event.request.referrer + '\n' + + 'ReferrerPolicy: ' + event.request.referrerPolicy]))); +} + +function handleClientId(event) { + var body; + if (event.clientId !== null) { + body = 'Client ID Found: ' + event.clientId; + } else { + body = 'Client ID Not Found'; + } + event.respondWith(new Response(body)); +} + +function handleNullBody(event) { + event.respondWith(new Response()); +} + +function handleFetch(event) { + event.respondWith(fetch('other.html')); +} + +function handleFormPost(event) { + event.respondWith(new Promise(function(resolve) { + event.request.text() + .then(function(result) { + resolve(new Response(event.request.method + ':' + + event.request.headers.get('Content-Type') + ':' + + result)); + }); + })); +} + +function handleMultipleRespondWith(event) { + var logForMultipleRespondWith = ''; + for (var i = 0; i < 3; ++i) { + logForMultipleRespondWith += '(' + i + ')'; + try { + event.respondWith(new Promise(function(resolve) { + setTimeout(function() { + resolve(new Response(logForMultipleRespondWith)); + }, 0); + })); + } catch (e) { + logForMultipleRespondWith += '[' + e.name + ']'; + } + } +} + +var lastResponseForUsedCheck = undefined; + +function handleUsedCheck(event) { + if (!lastResponseForUsedCheck) { + event.respondWith(fetch('other.html').then(function(response) { + lastResponseForUsedCheck = response; + return response; + })); + } else { + event.respondWith(new Response( + 'bodyUsed: ' + lastResponseForUsedCheck.bodyUsed)); + } +} +function handleFragmentCheck(event) { + var body; + if (event.request.url.indexOf('#') === -1) { + body = 'Fragment Not Found'; + } else { + body = 'Fragment Found :' + + event.request.url.substring(event.request.url.indexOf('#')); + } + event.respondWith(new Response(body)); +} +function handleCache(event) { + event.respondWith(new Response(event.request.cache)); +} +function handleEventSource(event) { + if (event.request.mode === 'navigate') { + return; + } + var data = { + mode: event.request.mode, + cache: event.request.cache, + credentials: event.request.credentials + }; + var body = 'data:' + JSON.stringify(data) + '\n\n'; + event.respondWith(new Response(body, { + headers: { 'Content-Type': 'text/event-stream' } + } + )); +} + +function handleIntegrity(event) { + event.respondWith(new Response(event.request.integrity)); +} + +self.addEventListener('fetch', function(event) { + var url = event.request.url; + var handlers = [ + { pattern: '?string', fn: handleString }, + { pattern: '?blob', fn: handleBlob }, + { pattern: '?referrerFull', fn: handleReferrerFull }, + { pattern: '?referrerPolicy', fn: handleReferrerPolicy }, + { pattern: '?referrer', fn: handleReferrer }, + { pattern: '?clientId', fn: handleClientId }, + { pattern: '?ignore', fn: function() {} }, + { pattern: '?null', fn: handleNullBody }, + { pattern: '?fetch', fn: handleFetch }, + { pattern: '?form-post', fn: handleFormPost }, + { pattern: '?multiple-respond-with', fn: handleMultipleRespondWith }, + { pattern: '?used-check', fn: handleUsedCheck }, + { pattern: '?fragment-check', fn: handleFragmentCheck }, + { pattern: '?cache', fn: handleCache }, + { pattern: '?eventsource', fn: handleEventSource }, + { pattern: '?integrity', fn: handleIntegrity }, + ]; + + var handler = null; + for (var i = 0; i < handlers.length; ++i) { + if (url.indexOf(handlers[i].pattern) != -1) { + handler = handlers[i]; + break; + } + } + + if (handler) { + handler.fn(event); + } else { + event.respondWith(new Response(new Blob( + ['Service Worker got an unexpected request: ' + url]))); + } + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html new file mode 100644 index 000000000..e0f32f754 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html @@ -0,0 +1,66 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> + var host_info = get_host_info(); + var uri = document.location + '?check-ua-header'; + + var headers = new Headers(); + headers.set('User-Agent', 'custom_ua'); + + // Check the custom UA case + fetch(uri, { headers: headers }).then(function(response) { + return response.text(); + }).then(function(text) { + if (text == 'custom_ua') { + parent.postMessage('PASS', '*'); + } else { + parent.postMessage('withUA FAIL - expected "custom_ua", got "' + text + '"', '*'); + } + }).catch(function(err) { + parent.postMessage('withUA FAIL - unexpected error: ' + err, '*'); + }); + + // Check the default UA case + fetch(uri, {}).then(function(response) { + return response.text(); + }).then(function(text) { + if (text == 'NO_UA') { + parent.postMessage('PASS', '*'); + } else { + parent.postMessage('noUA FAIL - expected "NO_UA", got "' + text + '"', '*'); + } + }).catch(function(err) { + parent.postMessage('noUA FAIL - unexpected error: ' + err, '*'); + }); + + var uri = document.location + '?check-accept-header'; + var headers = new Headers(); + headers.set('Accept', 'hmm'); + + // Check for custom accept header + fetch(uri, { headers: headers }).then(function(response) { + return response.text(); + }).then(function(text) { + if (text === headers.get('Accept')) { + parent.postMessage('PASS', '*'); + } else { + parent.postMessage('custom accept FAIL - expected ' + headers.get('Accept') + + ' got "' + text + '"', '*'); + } + }).catch(function(err) { + parent.postMessage('custom accept FAIL - unexpected error: ' + err, '*'); + }); + + // Check for default accept header + fetch(uri).then(function(response) { + return response.text(); + }).then(function(text) { + if (text === '*/*') { + parent.postMessage('PASS', '*'); + } else { + parent.postMessage('accept FAIL - expected */* got "' + text + '"', '*'); + } + }).catch(function(err) { + parent.postMessage('accept FAIL - unexpected error: ' + err, '*'); + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html new file mode 100644 index 000000000..a7db229ce --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html @@ -0,0 +1,71 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE'; +var host_info = get_host_info(); +var results = ''; + +function test1() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + test2(); + }; + img.onerror = function() { + results += 'FAIL(1)'; + test2(); + }; + img.src = './dummy?url=' + + encodeURIComponent(host_info['HTTPS_ORIGIN'] + image_path); +} + +function test2() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + test3(); + }; + img.onerror = function() { + results += 'FAIL(2)'; + test3(); + }; + img.src = './dummy?mode=no-cors&url=' + + encodeURIComponent(host_info['HTTPS_REMOTE_ORIGIN'] + image_path); +} + +function test3() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + results += 'FAIL(3)'; + test4(); + }; + img.onerror = function() { + test4(); + }; + img.src = './dummy?mode=no-cors&url=' + + encodeURIComponent(host_info['HTTP_ORIGIN'] + image_path); +} + +function test4() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + results += 'FAIL(4)'; + finish(); + }; + img.onerror = function() { + finish(); + }; + img.src = './dummy?mode=no-cors&url=' + + encodeURIComponent(host_info['HTTP_REMOTE_ORIGIN'] + image_path); +} + +function finish() { + results += 'finish'; + window.parent.postMessage({results: results}, host_info['HTTPS_ORIGIN']); +} +</script> + +<body onload='test1();'> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html new file mode 100644 index 000000000..cec00cb25 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html @@ -0,0 +1,80 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var image_path = base_path() + 'fetch-access-control.py?PNGIMAGE'; +var host_info = get_host_info(); +var results = ''; + +function test1() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + test2(); + }; + img.onerror = function() { + results += 'FAIL(1)'; + test2(); + }; + img.src = host_info['HTTPS_ORIGIN'] + image_path; +} + +function test2() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + test3(); + }; + img.onerror = function() { + results += 'FAIL(2)'; + test3(); + }; + img.src = host_info['HTTPS_REMOTE_ORIGIN'] + image_path; +} + +function test3() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + results += 'FAIL(3)'; + test4(); + }; + img.onerror = function() { + test4(); + }; + img.src = host_info['HTTP_ORIGIN'] + image_path; +} + +function test4() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + results += 'FAIL(4)'; + test5(); + }; + img.onerror = function() { + test5(); + }; + img.src = host_info['HTTP_REMOTE_ORIGIN'] + image_path; +} + +function test5() { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = function() { + finish(); + }; + img.onerror = function() { + results += 'FAIL(5)'; + finish(); + }; + img.src = './dummy?generate-png'; +} + +function finish() { + results += 'finish'; + window.parent.postMessage({results: results}, host_info['HTTPS_ORIGIN']); +} +</script> + +<body onload='test1();'> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html new file mode 100644 index 000000000..961fd2ab6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var params = get_query_params(location.href); +var SCOPE = 'fetch-mixed-content-iframe-inscope-to-' + params['target'] + '.html'; +var URL = 'fetch-rewrite-worker.js'; +var host_info = get_host_info(); + +window.addEventListener('message', on_message, false); + +navigator.serviceWorker.getRegistration(SCOPE) + .then(function(registration) { + if (registration) + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.register(URL, {scope: SCOPE}); + }) + .then(function(registration) { + return new Promise(function(resolve) { + registration.addEventListener('updatefound', function() { + resolve(registration.installing); + }); + }); + }) + .then(function(worker) { + worker.addEventListener('statechange', on_state_change); + }) + .catch(function(reason) { + window.parent.postMessage({results: 'FAILURE: ' + reason.message}, + host_info['HTTPS_ORIGIN']); + }); + +function on_state_change(event) { + if (event.target.state != 'activated') + return; + var frame = document.createElement('iframe'); + frame.src = SCOPE; + document.body.appendChild(frame); +} + +function on_message(e) { + navigator.serviceWorker.getRegistration(SCOPE) + .then(function(registration) { + if (registration) + return registration.unregister(); + }) + .then(function() { + window.parent.postMessage(e.data, host_info['HTTPS_ORIGIN']); + }) + .catch(function(reason) { + window.parent.postMessage({results: 'FAILURE: ' + reason.message}, + host_info['HTTPS_ORIGIN']); + }); +} + +function get_query_params(url) { + var search = (new URL(url)).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split('&'); + params.forEach(function(param) { + var element = param.split('='); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html new file mode 100644 index 000000000..0edf2e7f9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html @@ -0,0 +1 @@ +<link href="./fetch-request-css-base-url-style.css" rel="stylesheet" type="text/css"> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css new file mode 100644 index 000000000..7643f667a --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css @@ -0,0 +1 @@ +body { background-image: url("./dummy.png");} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js new file mode 100644 index 000000000..0d9244ec7 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js @@ -0,0 +1,27 @@ +importScripts('../resources/get-host-info.sub.js'); +importScripts('test-helpers.sub.js'); + +var port = undefined; + +self.onmessage = function(e) { + var message = e.data; + if ('port' in message) { + port = message.port; + port.postMessage({ready: true}); + } +}; + +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('fetch-request-css-base-url-style.css') != -1) { + event.respondWith(fetch( + get_host_info()['HTTPS_REMOTE_ORIGIN'] + base_path() + + 'fetch-request-css-base-url-style.css', + {mode: 'no-cors'})); + } else if (url.indexOf('dummy.png') != -1) { + port.postMessage({ + url: event.request.url, + referrer: event.request.referrer + }); + } + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html new file mode 100644 index 000000000..f00d24e37 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html @@ -0,0 +1,15 @@ +<script> +function xhr(url) { + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.addEventListener( + 'error', + function(event) { reject(event); }); + request.addEventListener( + 'load', + function(event) { resolve(request.response); }); + request.open('GET', url); + request.send(); + }); +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js new file mode 100644 index 000000000..3b028b24b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js @@ -0,0 +1,13 @@ +var requests = []; + +self.addEventListener('message', function(event) { + event.data.port.postMessage({requests: requests}); + requests = []; + }); + +self.addEventListener('fetch', function(event) { + requests.push({ + url: event.request.url, + mode: event.request.mode + }); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html new file mode 100644 index 000000000..68a1a8687 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html @@ -0,0 +1 @@ +<script src="empty.js"></script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js new file mode 100644 index 000000000..2bd59d739 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js @@ -0,0 +1,18 @@ +var requests = []; + +self.addEventListener('message', function(event) { + event.data.port.postMessage({requests: requests}); + }); + +self.addEventListener('fetch', function(event) { + var url = event.request.url; + var headers = []; + for (var header of event.request.headers) { + headers.push(header); + } + requests.push({ + url: url, + headers: headers + }); + event.respondWith(fetch(event.request)); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html new file mode 100644 index 000000000..ffd76bfc4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html @@ -0,0 +1,35 @@ +<script> +function xhr(url) { + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.addEventListener( + 'error', + function(event) { reject(event); }); + request.addEventListener( + 'load', + function(event) { resolve(request.response); }); + request.open('GET', url); + request.send(); + }); +} + +function load_image(url) { + return new Promise(function(resolve, reject) { + var img = document.createElement('img'); + document.body.appendChild(img); + img.onload = resolve; + img.onerror = reject; + img.src = url; + }); +} + +function load_audio(url) { + return new Promise(function(resolve, reject) { + var audio = document.createElement('audio'); + document.body.appendChild(audio); + audio.oncanplay = resolve; + audio.onerror = reject; + audio.src = url; + }); +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html new file mode 100644 index 000000000..93b038dd6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html @@ -0,0 +1,67 @@ +<script src="test-helpers.sub.js?pipe=sub"></script> +<body> +<script> + +function load_image(url, cross_origin) { + var img = document.createElement('img'); + if (cross_origin != '') { + img.crossOrigin = cross_origin; + } + img.src = url; +} + +function load_script(url, cross_origin) { + var script = document.createElement('script'); + script.src = url; + if (cross_origin != '') { + script.crossOrigin = cross_origin; + } + document.body.appendChild(script); +} + +function load_css(url, cross_origin) { + var link = document.createElement('link'); + link.rel = 'stylesheet' + link.href = url; + link.type = 'text/css'; + if (cross_origin != '') { + link.crossOrigin = cross_origin; + } + document.body.appendChild(link); +} + +function load_font(url) { + var fontFace = new FontFace('test', 'url(' + url + ')'); + fontFace.load(); +} + +function load_css_image(url, type) { + var div = document.createElement('div'); + document.body.appendChild(div); + div.style[type] = 'url(' + url + ')'; +} + +function load_css_image_set(url, type) { + var div = document.createElement('div'); + document.body.appendChild(div); + div.style[type] = '-webkit-image-set(url(' + url + ') 1x)'; +} + +function load_script_with_integrity(url, integrity) { + var script = document.createElement('script'); + script.src = url; + script.integrity = integrity; + document.body.appendChild(script); +} + +function load_css_with_integrity(url, integrity) { + var link = document.createElement('link'); + link.rel = 'stylesheet' + link.href = url; + link.type = 'text/css'; + link.integrity = integrity; + document.body.appendChild(link); +} + +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js new file mode 100644 index 000000000..900b63c62 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js @@ -0,0 +1,24 @@ +var requests = []; +var port = undefined; + +self.onmessage = function(e) { + var message = e.data; + if ('port' in message) { + port = message.port; + port.postMessage({ready: true}); + } +}; + +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('dummy?test') == -1) { + return; + } + port.postMessage({ + url: url, + mode: event.request.mode, + credentials: event.request.credentials, + integrity: event.request.integrity + }); + event.respondWith(Promise.reject()); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html new file mode 100644 index 000000000..26c6b7344 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html @@ -0,0 +1,179 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var port; +var host_info = get_host_info(); + +function assert_equals(a, b) { + port.postMessage({results: 'equals', got: a, expected: b}); +} + +function get_boundary(headers) { + var reg = new RegExp('multipart\/form-data; boundary=(.*)'); + for (var i = 0; i < headers.length; ++i) { + if (headers[i][0] != 'content-type') { + continue; + } + var regResult = reg.exec(headers[i][1]); + if (!regResult) { + continue; + } + return regResult[1]; + } + return ''; +} + +function create_file_system_file(file_name, data) { + return new Promise(function(resolve, reject) { + webkitRequestFileSystem(TEMPORARY, 1024, function(fs) { + fs.root.getFile( + file_name, {create: true, exclusive: true}, + function(fileEntry) { + fileEntry.createWriter(function(fileWriter) { + fileWriter.onwriteend = function(e) { + fileEntry.file(function(file) { resolve(file); }); + }; + var blob = new Blob([data], {type: 'text/plain'}); + fileWriter.write(blob); + }); + }, function(e) { reject(e); }); + }, function(e) { reject(e); }); + }); +} + +function xhr_send(url_base, method, data, with_credentials) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + resolve(JSON.parse(xhr.response)); + }; + xhr.onerror = function() { + reject('XHR should succeed.'); + }; + xhr.responseType = 'text'; + if (with_credentials) { + xhr.withCredentials = true; + } + xhr.open(method, url_base + '/dummy?test', true); + xhr.send(data); + }); +} + +function string_test() { + return xhr_send(host_info['HTTPS_ORIGIN'], 'POST', 'test string', false) + .then(function(response) { + assert_equals(response.method, 'POST'); + assert_equals(response.body, 'test string'); + }); +} + +function blob_test() { + return xhr_send(host_info['HTTPS_ORIGIN'], 'POST', new Blob(['test blob']), + false) + .then(function(response) { + assert_equals(response.method, 'POST'); + assert_equals(response.body, 'test blob'); + }); +} + +function custom_method_test() { + return xhr_send(host_info['HTTPS_ORIGIN'], 'XXX', 'test string xxx', false) + .then(function(response) { + assert_equals(response.method, 'XXX'); + assert_equals(response.body, 'test string xxx'); + }); +} + +function options_method_test() { + return xhr_send(host_info['HTTPS_ORIGIN'], 'OPTIONS', 'test string xxx', false) + .then(function(response) { + assert_equals(response.method, 'OPTIONS'); + assert_equals(response.body, 'test string xxx'); + }); +} + +function form_data_test() { + var formData = new FormData(); + formData.append('sample string', '1234567890'); + formData.append('sample blob', new Blob(['blob content'])); + formData.append('sample file', new File(['file content'], 'file.dat')); + return xhr_send(host_info['HTTPS_ORIGIN'], 'POST', formData, false) + .then(function(response) { + assert_equals(response.method, 'POST'); + var boundary = get_boundary(response.headers); + var expected_body = + '--' + boundary + '\r\n' + + 'Content-Disposition: form-data; name="sample string"\r\n' + + '\r\n' + + '1234567890\r\n' + + '--' + boundary + '\r\n' + + 'Content-Disposition: form-data; name="sample blob"; ' + + 'filename="blob"\r\n' + + 'Content-Type: application/octet-stream\r\n' + + '\r\n' + + 'blob content\r\n' + + '--' + boundary + '\r\n' + + 'Content-Disposition: form-data; name="sample file"; ' + + 'filename="file.dat"\r\n' + + 'Content-Type: application/octet-stream\r\n' + + '\r\n' + + 'file content\r\n' + + '--' + boundary + '--\r\n'; + assert_equals(response.body, expected_body); + }); +} + +function mode_credentials_test() { + return xhr_send(host_info['HTTPS_ORIGIN'], 'GET', '', false) + .then(function(response){ + assert_equals(response.mode, 'cors'); + assert_equals(response.credentials, 'same-origin'); + return xhr_send(host_info['HTTPS_ORIGIN'], 'GET', '', true); + }) + .then(function(response){ + assert_equals(response.mode, 'cors'); + assert_equals(response.credentials, 'include'); + return xhr_send(host_info['HTTPS_REMOTE_ORIGIN'], 'GET', '', false); + }) + .then(function(response){ + assert_equals(response.mode, 'cors'); + assert_equals(response.credentials, 'same-origin'); + return xhr_send(host_info['HTTPS_REMOTE_ORIGIN'], 'GET', '', true); + }) + .then(function(response){ + assert_equals(response.mode, 'cors'); + assert_equals(response.credentials, 'include'); + }); +} + +function data_url_test() { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + resolve(xhr.response); + }; + xhr.onerror = function() { + reject('XHR should succeed.'); + }; + xhr.responseType = 'text'; + xhr.open('GET', 'data:text/html,Foobar', true); + xhr.send(); + }) + .then(function(data) { + assert_equals(data, 'Foobar'); + }); +} + +window.addEventListener('message', function(evt) { + port = evt.ports[0]; + string_test() + .then(blob_test) + .then(custom_method_test) + .then(options_method_test) + .then(form_data_test) + .then(mode_credentials_test) + .then(data_url_test) + .then(function() { port.postMessage({results: 'finish'}); }) + .catch(function(e) { port.postMessage({results: 'failure:' + e}); }); + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js new file mode 100644 index 000000000..91b3abb14 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js @@ -0,0 +1,22 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('dummy?test') == -1) { + return; + } + event.respondWith(new Promise(function(resolve) { + var headers = []; + for (var header of event.request.headers) { + headers.push(header); + } + event.request.text() + .then(function(result) { + resolve(new Response(JSON.stringify({ + method: event.request.method, + mode: event.request.mode, + credentials: event.request.credentials, + headers: headers, + body: result + }))); + }); + })); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html new file mode 100644 index 000000000..3391381e3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html @@ -0,0 +1,35 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var host_info = get_host_info(); + +function xhr_send(method, data) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + resolve(xhr); + }; + xhr.onerror = function() { + reject('XHR should succeed.'); + }; + xhr.responseType = 'text'; + xhr.open(method, './dummy?test', true); + xhr.send(data); + }); +} + +function coalesce_headers_test() { + return xhr_send('POST', 'test string') + .then(function(xhr) { + window.parent.postMessage({results: xhr.getResponseHeader('foo')}, + host_info['HTTPS_ORIGIN']); + }); +} + +window.addEventListener('message', function(evt) { + var port = evt.ports[0]; + coalesce_headers_test() + .then(function() { port.postMessage({results: 'finish'}); }) + .catch(function(e) { port.postMessage({results: 'failure:' + e}); }); + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js new file mode 100644 index 000000000..465fbc91b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js @@ -0,0 +1,12 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('dummy?test') == -1) { + return; + } + event.respondWith(new Promise(function(resolve) { + var headers = new Headers; + headers.append('foo', 'foo'); + headers.append('foo', 'bar'); + resolve(new Response('hello world', {'headers': headers})); + })); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js new file mode 100644 index 000000000..9806f2b5b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js @@ -0,0 +1,149 @@ +function get_query_params(url) { + var search = (new URL(url)).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split('&'); + params.forEach(function(param) { + var element = param.split('='); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} + +function get_request_init(base, params) { + var init = {}; + init['method'] = params['method'] || base['method']; + init['mode'] = params['mode'] || base['mode']; + if (init['mode'] == 'navigate') { + init['mode'] = 'same-origin'; + } + init['credentials'] = params['credentials'] || base['credentials']; + init['redirect'] = params['redirect-mode'] || base['redirect']; + return init; +} + +self.addEventListener('fetch', function(event) { + var params = get_query_params(event.request.url); + var init = get_request_init(event.request, params); + var url = params['url']; + if (params['ignore']) { + return; + } + if (params['throw']) { + throw new Error('boom'); + } + if (params['reject']) { + event.respondWith(new Promise(function(resolve, reject) { + reject(); + })); + return; + } + if (params['resolve-null']) { + event.respondWith(new Promise(function(resolve) { + resolve(null); + })); + return; + } + if (params['generate-png']) { + var binary = atob( + 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAA' + + 'RnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAhSURBVDhPY3wro/Kf' + + 'gQLABKXJBqMGjBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII='); + var array = new Uint8Array(binary.length); + for(var i = 0; i < binary.length; i++) { + array[i] = binary.charCodeAt(i); + }; + event.respondWith(new Response(new Blob([array], {type: 'image/png'}))); + return; + } + if (params['check-ua-header']) { + var ua = event.request.headers.get('User-Agent'); + if (ua) { + // We have a user agent! + event.respondWith(new Response(new Blob([ua]))); + } else { + // We don't have a user-agent! + event.respondWith(new Response(new Blob(["NO_UA"]))); + } + return; + } + if (params['check-accept-header']) { + var accept = event.request.headers.get('Accept'); + if (accept) { + event.respondWith(new Response(accept)); + } else { + event.respondWith(new Response('NO_ACCEPT')); + } + return; + } + event.respondWith(new Promise(function(resolve, reject) { + var request = event.request; + if (url) { + request = new Request(url, init); + } + fetch(request).then(function(response) { + var expectedType = params['expected_type']; + if (expectedType && response.type !== expectedType) { + // Resolve a JSON object with a failure instead of rejecting + // in order to distinguish this from a NetworkError, which + // may be expected even if the type is correct. + resolve(new Response(JSON.stringify({ + result: 'failure', + detail: 'got ' + response.type + ' Response.type instead of ' + + expectedType + }))); + } + + var expectedRedirected = params['expected_redirected']; + if (typeof expectedRedirected !== 'undefined') { + var expected_redirected = (expectedRedirected === 'true'); + if(response.redirected !== expected_redirected) { + // This is simply determining how to pass an error to the outer + // test case(fetch-request-redirect.https.html). + var execptedResolves = params['expected_resolves']; + if (execptedResolves === 'true') { + // Reject a JSON object with a failure since promise is expected + // to be resolved. + reject(new Response(JSON.stringify({ + result: 'failure', + detail: 'got '+ response.redirected + + ' Response.redirected instead of ' + + expectedRedirected + }))); + } else { + // Resolve a JSON object with a failure since promise is + // expected to be rejected. + resolve(new Response(JSON.stringify({ + result: 'failure', + detail: 'got '+ response.redirected + + ' Response.redirected instead of ' + + expectedRedirected + }))); + } + } + } + + if (params['cache']) { + var cacheName = "cached-fetches-" + performance.now() + "-" + + event.request.url; + var cache; + var cachedResponse; + return self.caches.open(cacheName).then(function(opened) { + cache = opened; + return cache.put(request, response); + }).then(function() { + return cache.match(request); + }).then(function(cached) { + cachedResponse = cached; + return self.caches.delete(cacheName); + }).then(function() { + resolve(cachedResponse); + }); + } else { + resolve(response); + } + }, reject) + })); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js new file mode 100644 index 000000000..66f3e5936 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js @@ -0,0 +1,17 @@ +var activatePromiseResolve; + +addEventListener('activate', function(evt) { + evt.waitUntil(new Promise(function(resolve) { + activatePromiseResolve = resolve; + })); +}); + +addEventListener('message', function(evt) { + if (typeof activatePromiseResolve === 'function') { + activatePromiseResolve(); + } +}); + +addEventListener('fetch', function(evt) { + evt.respondWith(new Response('Hello world')); +}); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/frame-for-getregistrations.html b/testing/web-platform/tests/service-workers/service-worker/resources/frame-for-getregistrations.html new file mode 100644 index 000000000..7fc35f189 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/frame-for-getregistrations.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<title>Service Worker: frame for getRegistrations()</title> +<script> +var scope = 'scope-for-getregistrations'; +var script = 'empty-worker.js'; +var registration; + +navigator.serviceWorker.register(script, { scope: scope }) + .then(function(r) { registration = r; window.parent.postMessage('ready', '*'); }) + +self.onmessage = function(e) { + if (e.data == 'unregister') { + registration.unregister() + .then(function() { + e.ports[0].postMessage('unregistered'); + }); + } +}; +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/get-host-info.sub.js b/testing/web-platform/tests/service-workers/service-worker/resources/get-host-info.sub.js new file mode 100644 index 000000000..b64334df6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/get-host-info.sub.js @@ -0,0 +1,26 @@ +function get_host_info() { + var ORIGINAL_HOST = '127.0.0.1'; + var REMOTE_HOST = 'localhost'; + var UNAUTHENTICATED_HOST = 'example.test'; + var HTTP_PORT = 8000; + var HTTPS_PORT = 8443; + try { + // In W3C test, we can get the hostname and port number in config.json + // using wptserve's built-in pipe. + // http://wptserve.readthedocs.org/en/latest/pipes.html#built-in-pipes + HTTP_PORT = eval('{{ports[http][0]}}'); + HTTPS_PORT = eval('{{ports[https][0]}}'); + ORIGINAL_HOST = eval('\'{{host}}\''); + REMOTE_HOST = 'www1.' + ORIGINAL_HOST; + } catch (e) { + } + return { + HTTP_ORIGIN: 'http://' + ORIGINAL_HOST + ':' + HTTP_PORT, + HTTPS_ORIGIN: 'https://' + ORIGINAL_HOST + ':' + HTTPS_PORT, + HTTPS_ORIGIN_WITH_CREDS: 'https://foo:bar@' + ORIGINAL_HOST + ':' + HTTPS_PORT, + HTTP_REMOTE_ORIGIN: 'http://' + REMOTE_HOST + ':' + HTTP_PORT, + HTTPS_REMOTE_ORIGIN: 'https://' + REMOTE_HOST + ':' + HTTPS_PORT, + HTTPS_REMOTE_ORIGIN_WITH_CREDS: 'https://foo:bar@' + REMOTE_HOST + ':' + HTTPS_PORT, + UNAUTHENTICATED_ORIGIN: 'http://' + UNAUTHENTICATED_HOST + ':' + HTTP_PORT + }; +} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/indexeddb-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/indexeddb-worker.js new file mode 100644 index 000000000..ef89550b3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/indexeddb-worker.js @@ -0,0 +1,26 @@ +var port; +self.addEventListener('message', function(e) { + var message = e.data; + if ('port' in message) + doIndexedDBTest(message.port); + }); + +function doIndexedDBTest(port) { + var delete_request = indexedDB.deleteDatabase('db'); + delete_request.onsuccess = function() { + var open_request = indexedDB.open('db'); + open_request.onupgradeneeded = function() { + var db = open_request.result; + db.createObjectStore('store'); + }; + open_request.onsuccess = function() { + var db = open_request.result; + var tx = db.transaction('store', 'readwrite'); + var store = tx.objectStore('store'); + store.put('value', 'key'); + tx.oncomplete = function() { + port.postMessage('done'); + }; + }; + }; +} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/install-event-type-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/install-event-type-worker.js new file mode 100644 index 000000000..d729afa09 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/install-event-type-worker.js @@ -0,0 +1,8 @@ +importScripts('worker-testharness.js'); + +self.oninstall = function(event) { + assert_true(event instanceof ExtendableEvent); + assert_equals(event.type, 'install'); + assert_false(event.cancelable); + assert_false(event.bubbles); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/interfaces-worker.sub.js b/testing/web-platform/tests/service-workers/service-worker/resources/interfaces-worker.sub.js new file mode 100644 index 000000000..e5ed36fce --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/interfaces-worker.sub.js @@ -0,0 +1,107 @@ +importScripts('interfaces.js'); +importScripts('worker-testharness.js'); +importScripts('../resources/testharness-helpers.js'); + +var EVENT_HANDLER = 'object'; + +test(function() { + verify_interface('ServiceWorkerGlobalScope', + self, + { + clients: 'object', + registration: 'object', + skipWaiting: 'function', + + onactivate: EVENT_HANDLER, + onfetch: EVENT_HANDLER, + oninstall: EVENT_HANDLER, + onmessage: EVENT_HANDLER + }); + }, 'ServiceWorkerGlobalScope'); + +test(function() { + verify_interface('Clients', + self.clients, + { + claim: 'function', + matchAll: 'function' + }); + }, 'Clients'); + +test(function() { + verify_interface('Client'); + // FIXME: Get an instance and test it, or ensure property exists on + // prototype. + }, 'Client'); + +test(function() { + verify_interface('WindowClient'); + // FIXME: Get an instance and test it, or ensure property exists on + // prototype. + }, 'WindowClient'); + +test(function() { + verify_interface('CacheStorage', + self.caches, + { + match: 'function', + has: 'function', + open: 'function', + delete: 'function', + keys: 'function' + }); + }, 'CacheStorage'); + +promise_test(function(t) { + return create_temporary_cache(t) + .then(function(cache) { + verify_interface('Cache', + cache, + { + match: 'function', + matchAll: 'function', + add: 'function', + addAll: 'function', + put: 'function', + delete: 'function', + keys: 'function' + }); + }); + }, 'Cache'); + +test(function() { + var req = new Request('http://{{host}}/', + {method: 'POST', + headers: [['Content-Type', 'Text/Html']]}); + assert_equals( + new ExtendableEvent('ExtendableEvent').type, + 'ExtendableEvent', 'Type of ExtendableEvent should be ExtendableEvent'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).type, + 'FetchEvent', 'Type of FetchEvent should be FetchEvent'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).cancelable, + false, 'Default FetchEvent.cancelable should be false'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).bubbles, + false, 'Default FetchEvent.bubbles should be false'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).clientId, + null, 'Default FetchEvent.clientId should be null'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).isReload, + false, 'Default FetchEvent.isReload should be false'); + assert_equals( + new FetchEvent('FetchEvent', {request: req, cancelable: false}).cancelable, + false, 'FetchEvent.cancelable should be false'); + assert_equals( + new FetchEvent('FetchEvent', {request: req, clientId : 'test-client-id'}).clientId, 'test-client-id', + 'FetchEvent.clientId with option {clientId : "test-client-id"} should be "test-client-id"'); + assert_equals( + new FetchEvent('FetchEvent', {request: req, isReload : true}).isReload, true, + 'FetchEvent.isReload with option {isReload : true} should be true'); + assert_equals( + new FetchEvent('FetchEvent', {request : req, isReload : true}).request.url, + 'http://{{host}}/', + 'FetchEvent.request.url should return the value it was initialized to'); + }, 'Event constructors'); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/interfaces.js b/testing/web-platform/tests/service-workers/service-worker/resources/interfaces.js new file mode 100644 index 000000000..eb00df65f --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/interfaces.js @@ -0,0 +1,15 @@ +function verify_interface(name, instance, attributes) { + assert_true(name in self, + name + ' should be an defined type'); + if (instance) { + assert_true(instance instanceof self[name], + instance + ' should be an instance of ' + name); + Object.keys(attributes || {}).forEach(function(attribute) { + var type = attributes[attribute]; + assert_true(attribute in instance, + attribute + ' should be an attribute of ' + name); + assert_equals(typeof instance[attribute], type, + attribute + ' should be of type ' + type); + }); + } +} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html new file mode 100644 index 000000000..f111bd924 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html @@ -0,0 +1,29 @@ +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> + +function xhr_send(method, data) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.getResponseHeader('Content-Type') !== null) { + reject('Content-Type must be null.'); + } + resolve(); + }; + xhr.onerror = function() { + reject('XHR must succeed.'); + }; + xhr.responseType = 'text'; + xhr.open(method, './dummy?test', true); + xhr.send(data); + }); +} + + +window.addEventListener('message', function(evt) { + var port = evt.ports[0]; + xhr_send('POST', 'test string') + .then(function() { port.postMessage({results: 'finish'}); }) + .catch(function(e) { port.postMessage({results: 'failure:' + e}); }); + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js new file mode 100644 index 000000000..93f496ef4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js @@ -0,0 +1,10 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('dummy?test') == -1) { + return; + } + event.respondWith(new Promise(function(resolve) { + // null byte in blob type + resolve(new Response(new Blob([],{type: 'a\0b'}))); + })); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py new file mode 100644 index 000000000..c91250a9d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py @@ -0,0 +1,11 @@ +import time +def main(request, response): + response.headers.set("Content-Type", "application/javascript") + response.headers.set("Transfer-encoding", "chunked") + response.write_status_headers() + + time.sleep(1) + response.explicit_flush = True + + response.writer.write("XX\r\n\r\n") + response.writer.flush() diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py new file mode 100644 index 000000000..ae2c1f21b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py @@ -0,0 +1,2 @@ +def main(request, response): + return [("Content-Type", "application/javascript"), ("Transfer-encoding", "chunked")], "XX\r\n\r\n" diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html new file mode 100644 index 000000000..19f302c35 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html @@ -0,0 +1,26 @@ +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> + +function xhr_send(method, data) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + reject('XHR must fail.'); + }; + xhr.onerror = function() { + resolve(); + }; + xhr.responseType = 'text'; + xhr.open(method, './dummy?test', true); + xhr.send(data); + }); +} + + +window.addEventListener('message', function(evt) { + var port = evt.ports[0]; + xhr_send('POST', 'test string') + .then(function() { port.postMessage({results: 'finish'}); }) + .catch(function(e) { port.postMessage({results: 'failure:' + e}); }); + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-worker.js new file mode 100644 index 000000000..31e7f29d0 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/invalid-header-worker.js @@ -0,0 +1,12 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('dummy?test') == -1) { + return; + } + event.respondWith(new Promise(function(resolve) { + var headers = new Headers; + headers.append('foo', 'foo'); + headers.append('foo', 'b\0r'); // header value with a null byte + resolve(new Response('hello world', {'headers': headers})); + })); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/load_worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/load_worker.js new file mode 100644 index 000000000..2c80f25a3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/load_worker.js @@ -0,0 +1,29 @@ +self.onmessage = function (evt) { + if (evt.data == "xhr") { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "synthesized-response.txt", true); + xhr.responseType = "text"; + xhr.send(); + xhr.onload = function (evt) { + postMessage(xhr.responseText); + }; + xhr.onerror = function() { + postMessage("XHR failed!"); + }; + } else if (evt.data == "fetch") { + fetch("synthesized-response.txt") + .then(function(response) { + return response.text(); + }) + .then(function(data) { + postMessage(data); + }) + .catch(function(error) { + postMessage("Fetch failed!"); + }); + } else if (evt.data == "importScripts") { + importScripts("synthesized-response.js"); + } else { + throw "Unexpected message! " + evt.data; + } +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html b/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html new file mode 100644 index 000000000..0cabce69f --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html @@ -0,0 +1,9 @@ +<script> +addEventListener('load', function() { + opener.postMessage({ type: 'LOADED' }, '*'); +}); + +addEventListener('pageshow', function() { + opener.postMessage({ type: 'PAGESHOW' }, '*'); +}); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/malformed-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/malformed-worker.py new file mode 100644 index 000000000..501521ff3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/malformed-worker.py @@ -0,0 +1,10 @@ +def main(request, response): + headers = [("Content-Type", "application/javascript")] + + body = {'parse-error': 'var foo = function() {;', + 'undefined-error': 'foo.bar = 42;', + 'uncaught-exception': 'throw new DOMException("AbortError");', + 'caught-exception': 'try { throw new Error; } catch(e) {}', + 'import-malformed-script': 'importScripts("malformed-worker.py?parse-error");', + 'import-no-such-script': 'importScripts("no-such-script.js");'}[request.url_parts.query] + return headers, body diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/mime-type-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/mime-type-worker.py new file mode 100644 index 000000000..a16684de5 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/mime-type-worker.py @@ -0,0 +1,4 @@ +def main(request, response): + if 'mime' in request.GET: + return [('Content-Type', request.GET['mime'])], "" + return [], "" diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/mint-new-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/mint-new-worker.py new file mode 100644 index 000000000..cbe70304d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/mint-new-worker.py @@ -0,0 +1,25 @@ +import time + +body = ''' +onactivate = (e) => e.waitUntil(clients.claim()); +var resolve_wait_until; +var wait_until = new Promise(resolve => { + resolve_wait_until = resolve; + }); +onmessage = (e) => { + if (e.data == 'wait') + e.waitUntil(wait_until); + if (e.data == 'go') + resolve_wait_until(); + };''' + +def main(request, response): + headers = [('Cache-Control', 'no-cache, must-revalidate'), + ('Pragma', 'no-cache'), + ('Content-Type', 'application/javascript')] + + skipWaiting = '' + if 'skip-waiting' in request.GET: + skipWaiting = 'skipWaiting();' + + return headers, '/* %s %s */ %s %s' % (time.time(), time.clock(), skipWaiting, body) diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js new file mode 100644 index 000000000..f9617439f --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js @@ -0,0 +1,21 @@ +addEventListener('message', function(evt) { + if (evt.data.type === 'GET_CLIENTS') { + clients.matchAll(evt.data.opts).then(function(clientList) { + var resultList = clientList.map(function(c) { + return { url: c.url, frameType: c.frameType, id: c.id }; + }); + evt.source.postMessage({ type: 'success', detail: resultList }); + }).catch(function(err) { + evt.source.postMessage({ + type: 'failure', + detail: 'matchAll() rejected with "' + err + '"' + }); + }); + return; + } + + evt.source.postMessage({ + type: 'failure', + detail: 'Unexpected message type "' + evt.data.type + '"' + }); +}); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html new file mode 100644 index 000000000..c1441ba68 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<script src="get-host-info.sub.js"></script> +<script src="test-helpers.sub.js"></script> +<script> +var host_info = get_host_info(); +var SCOPE = 'navigation-redirect-scope1.py'; +var SCRIPT = 'navigation-redirect-worker.js'; + +var registration; +var worker; +var wait_for_worker_promise = navigator.serviceWorker.getRegistration(SCOPE) + .then(function(reg) { + if (reg) + return reg.unregister(); + }) + .then(function() { + return navigator.serviceWorker.register(SCRIPT, {scope: SCOPE}); + }) + .then(function(reg) { + registration = reg; + worker = reg.installing; + return new Promise(function(resolve) { + worker.addEventListener('statechange', function() { + if (worker.state == 'activated') + resolve(); + }); + }); + }); + +function send_result(message_id, result) { + window.parent.postMessage( + {id: message_id, result: result}, + host_info['HTTPS_ORIGIN']); +} + +function get_intercepted_urls(worker) { + return new Promise(function(resolve) { + var channel = new MessageChannel(); + channel.port1.onmessage = function(msg) { resolve(msg.data.urls); }; + worker.postMessage({port: channel.port2}, [channel.port2]); + }); +} + +window.addEventListener('message', on_message, false); + +function on_message(e) { + if (e.origin != host_info['HTTPS_ORIGIN']) { + console.error('invalid origin: ' + e.origin); + return; + } + if (e.data.message == 'wait_for_worker') { + wait_for_worker_promise.then(function() { send_result(e.data.id, 'ok'); }); + } else if (e.data.message == 'get_intercepted_urls') { + get_intercepted_urls(worker) + .then(function(urls) { + send_result(e.data.id, urls); + }); + } else if (e.data.message == 'unregister') { + registration.unregister() + .then(function() { + send_result(e.data.id, 'ok'); + }); + } +} + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py new file mode 100644 index 000000000..4b40762d8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py @@ -0,0 +1,15 @@ +def main(request, response): + if "url" in request.GET: + headers = [("Location", request.GET["url"])] + return 302, headers, '' + + return [], ''' +<!DOCTYPE html> +<script> + window.parent.postMessage( + { + id: 'last_url', + result: location.href + }, '*'); +</script> +''' diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py new file mode 100644 index 000000000..4b40762d8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py @@ -0,0 +1,15 @@ +def main(request, response): + if "url" in request.GET: + headers = [("Location", request.GET["url"])] + return 302, headers, '' + + return [], ''' +<!DOCTYPE html> +<script> + window.parent.postMessage( + { + id: 'last_url', + result: location.href + }, '*'); +</script> +''' diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py new file mode 100644 index 000000000..4b40762d8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py @@ -0,0 +1,15 @@ +def main(request, response): + if "url" in request.GET: + headers = [("Location", request.GET["url"])] + return 302, headers, '' + + return [], ''' +<!DOCTYPE html> +<script> + window.parent.postMessage( + { + id: 'last_url', + result: location.href + }, '*'); +</script> +''' diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-worker.js new file mode 100644 index 000000000..cb15b3ff1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigation-redirect-worker.js @@ -0,0 +1,75 @@ +// We store an empty response for each fetch event request we see +// in this Cache object so we can get the list of urls in the +// message event. +var cacheName = 'urls-' + self.registration.scope; + +var waitUntilPromiseList = []; + +self.addEventListener('message', function(event) { + var urls; + event.waitUntil(Promise.all(waitUntilPromiseList).then(function() { + waitUntilPromiseList = []; + return caches.open(cacheName); + }).then(function(cache) { + return cache.keys(); + }).then(function(requestList) { + urls = requestList.map(function(request) { return request.url; }); + return caches.delete(cacheName); + }).then(function() { + event.data.port.postMessage({urls: urls}); + })); + }); + +function get_query_params(url) { + var search = (new URL(url)).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split('&'); + params.forEach(function(param) { + var element = param.split('='); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} + +self.addEventListener('fetch', function(event) { + var waitUntilPromise = caches.open(cacheName).then(function(cache) { + return cache.put(event.request, new Response()); + }); + event.waitUntil(waitUntilPromise); + + var params = get_query_params(event.request.url); + if (!params['sw']) { + // To avoid races, add the waitUntil() promise to our global list. + // If we get a message event before we finish here, it will wait + // these promises to complete before proceeding to read from the + // cache. + waitUntilPromiseList.push(waitUntilPromise); + return; + } + + event.respondWith(waitUntilPromise.then(function() { + if (params['sw'] == 'gen') { + return Response.redirect(params['url']); + } else if (params['sw'] == 'fetch') { + return fetch(event.request); + } else if (params['sw'] == 'opaque') { + return fetch(new Request(event.request.url, {redirect: 'manual'})); + } else if (params['sw'] == 'opaqueThroughCache') { + var url = event.request.url; + var cache; + return caches.delete(url) + .then(function() { return self.caches.open(url); }) + .then(function(c) { + cache = c; + return fetch(new Request(url, {redirect: 'manual'})); + }) + .then(function(res) { return cache.put(event.request, res); }) + .then(function() { return cache.match(url); }); + } + + // unexpected... trigger an interception failure + })); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js new file mode 100644 index 000000000..7c97014fd --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js @@ -0,0 +1,13 @@ +var max_nesting_level = 8; + +self.addEventListener('message', function(event) { + var level = event.data; + if (level < max_nesting_level) + dispatchEvent(new MessageEvent('message', { data: level + 1 })); + throw Error('error at level ' + level); + }); + +self.addEventListener('activate', function(event) { + dispatchEvent(new MessageEvent('message', { data: 1 })); + }); + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js new file mode 100644 index 000000000..0bd9d318b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js @@ -0,0 +1,3 @@ +self.onerror = function(event) { return true; }; + +self.addEventListener('activate', function(event) { throw new Error(); }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js new file mode 100644 index 000000000..d56c95113 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js @@ -0,0 +1,7 @@ +// Ensure we can handle multiple error handlers. One error handler +// calling preventDefault should cause the event to be treated as +// handled. +self.addEventListener('error', function(event) {}); +self.addEventListener('error', function(event) { event.preventDefault(); }); +self.addEventListener('error', function(event) {}); +self.addEventListener('activate', function(event) { throw new Error(); }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js new file mode 100644 index 000000000..eb12ae862 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js @@ -0,0 +1,2 @@ +self.addEventListener('error', function(event) {}); +self.addEventListener('activate', function(event) { throw new Error(); }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js new file mode 100644 index 000000000..1e88ac5c4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js @@ -0,0 +1,7 @@ +// Ensure we can handle multiple activate handlers. One handler throwing an +// error should cause the event dispatch to be treated as having unhandled +// errors. +self.addEventListener('activate', function(event) {}); +self.addEventListener('activate', function(event) {}); +self.addEventListener('activate', function(event) { throw new Error(); }); +self.addEventListener('activate', function(event) {}); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js new file mode 100644 index 000000000..6729ab61a --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js @@ -0,0 +1,12 @@ +var max_nesting_level = 8; + +self.addEventListener('message', function(event) { + var level = event.data; + if (level < max_nesting_level) + dispatchEvent(new MessageEvent('message', { data: level + 1 })); + throw Error('error at level ' + level); + }); + +self.addEventListener('install', function(event) { + dispatchEvent(new MessageEvent('message', { data: 1 })); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js new file mode 100644 index 000000000..c2c499ab1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js @@ -0,0 +1,3 @@ +self.onerror = function(event) { return true; }; + +self.addEventListener('install', function(event) { throw new Error(); }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js new file mode 100644 index 000000000..7667c2781 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js @@ -0,0 +1,7 @@ +// Ensure we can handle multiple error handlers. One error handler +// calling preventDefault should cause the event to be treated as +// handled. +self.addEventListener('error', function(event) {}); +self.addEventListener('error', function(event) { event.preventDefault(); }); +self.addEventListener('error', function(event) {}); +self.addEventListener('install', function(event) { throw new Error(); }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js new file mode 100644 index 000000000..8f56d1bf1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js @@ -0,0 +1,2 @@ +self.addEventListener('error', function(event) {}); +self.addEventListener('install', function(event) { throw new Error(); }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js new file mode 100644 index 000000000..cc2f6d7e5 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js @@ -0,0 +1,7 @@ +// Ensure we can handle multiple install handlers. One handler throwing an +// error should cause the event dispatch to be treated as having unhandled +// errors. +self.addEventListener('install', function(event) {}); +self.addEventListener('install', function(event) {}); +self.addEventListener('install', function(event) { throw new Error(); }); +self.addEventListener('install', function(event) {}); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/other.html b/testing/web-platform/tests/service-workers/service-worker/resources/other.html new file mode 100644 index 000000000..b9f350438 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/other.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<title>Other</title> +Here's an other html file. diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/override_assert_object_equals.js b/testing/web-platform/tests/service-workers/service-worker/resources/override_assert_object_equals.js new file mode 100644 index 000000000..835046d47 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/override_assert_object_equals.js @@ -0,0 +1,58 @@ +// .body attribute of Request and Response object are experimental feture. It is +// enabled when --enable-experimental-web-platform-features flag is set. +// Touching this attribute can change the behavior of the objects. To avoid +// touching it while comparing the objects in LayoutTest, we overwrite +// assert_object_equals method. + +(function() { + var original_assert_object_equals = self.assert_object_equals; + function _brand(object) { + return Object.prototype.toString.call(object).match(/^\[object (.*)\]$/)[1]; + } + var assert_request_equals = function(actual, expected, prefix) { + if (typeof actual !== 'object') { + assert_equals(actual, expected, prefix); + return; + } + assert_true(actual instanceof Request, prefix); + assert_true(expected instanceof Request, prefix); + assert_equals(actual.bodyUsed, expected.bodyUsed, prefix + '.bodyUsed'); + assert_equals(actual.method, expected.method, prefix + '.method'); + assert_equals(actual.url, expected.url, prefix + '.url'); + original_assert_object_equals(actual.headers, expected.headers, + prefix + '.headers'); + assert_equals(actual.context, expected.context, prefix + '.context'); + assert_equals(actual.referrer, expected.referrer, prefix + '.referrer'); + assert_equals(actual.mode, expected.mode, prefix + '.mode'); + assert_equals(actual.credentials, expected.credentials, + prefix + '.credentials'); + assert_equals(actual.cache, expected.cache, prefix + '.cache'); + }; + var assert_response_equals = function(actual, expected, prefix) { + if (typeof actual !== 'object') { + assert_equals(actual, expected, prefix); + return; + } + assert_true(actual instanceof Response, prefix); + assert_true(expected instanceof Response, prefix); + assert_equals(actual.bodyUsed, expected.bodyUsed, prefix + '.bodyUsed'); + assert_equals(actual.type, expected.type, prefix + '.type'); + assert_equals(actual.url, expected.url, prefix + '.url'); + assert_equals(actual.status, expected.status, prefix + '.status'); + assert_equals(actual.statusText, expected.statusText, + prefix + '.statusText'); + original_assert_object_equals(actual.headers, expected.headers, + prefix + '.headers'); + }; + var assert_object_equals = function(actual, expected, description) { + var prefix = (description ? description + ': ' : '') + _brand(expected); + if (expected instanceof Request) { + assert_request_equals(actual, expected, prefix); + } else if (expected instanceof Response) { + assert_response_equals(actual, expected, prefix); + } else { + original_assert_object_equals(actual, expected, description); + } + }; + self.assert_object_equals = assert_object_equals; +})(); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/performance-timeline-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/performance-timeline-worker.js new file mode 100644 index 000000000..6f7df75e9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/performance-timeline-worker.js @@ -0,0 +1,58 @@ +importScripts('/resources/testharness.js'); + +promise_test(function(test) { + var durationMsec = 100; + // There are limits to our accuracy here. Timers may fire up to a + // millisecond early due to platform-dependent rounding. In addition + // the performance API introduces some rounding as well to prevent + // timing attacks. + var accuracy = 1.5; + return new Promise(function(resolve) { + performance.mark('startMark'); + setTimeout(resolve, durationMsec); + }).then(function() { + performance.mark('endMark'); + performance.measure('measure', 'startMark', 'endMark'); + var startMark = performance.getEntriesByName('startMark')[0]; + var endMark = performance.getEntriesByName('endMark')[0]; + var measure = performance.getEntriesByType('measure')[0]; + assert_equals(measure.startTime, startMark.startTime); + assert_approx_equals(endMark.startTime - startMark.startTime, + measure.duration, 0.001); + assert_greater_than(measure.duration, durationMsec - accuracy); + assert_equals(performance.getEntriesByType('mark').length, 2); + assert_equals(performance.getEntriesByType('measure').length, 1); + performance.clearMarks('startMark'); + performance.clearMeasures('measure'); + assert_equals(performance.getEntriesByType('mark').length, 1); + assert_equals(performance.getEntriesByType('measure').length, 0); + }); + }, 'User Timing'); + +promise_test(function(test) { + return fetch('dummy.txt') + .then(function(resp) { + return resp.text(); + }) + .then(function(text) { + var expectedResources = ['testharness.js', 'dummy.txt']; + assert_equals(performance.getEntriesByType('resource').length, expectedResources.length); + for (var i = 0; i < expectedResources.length; i++) { + var entry = performance.getEntriesByType('resource')[i]; + assert_true(entry.name.endsWith(expectedResources[i])); + assert_equals(entry.workerStart, 0); + assert_greater_than(entry.startTime, 0); + assert_greater_than(entry.responseEnd, entry.startTime); + } + return new Promise(function(resolve) { + performance.onresourcetimingbufferfull = resolve; + performance.setResourceTimingBufferSize(expectedResources.length); + }); + }) + .then(function() { + performance.clearResourceTimings(); + assert_equals(performance.getEntriesByType('resource').length, 0); + }) + }, 'Resource Timing'); + +done(); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js new file mode 100644 index 000000000..3475321f4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js @@ -0,0 +1,20 @@ +self.onmessage = function(e) { + self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + var messageChannel = new MessageChannel(); + messageChannel.port1.onmessage = + onMessageViaMessagePort.bind(null, client); + client.postMessage({port: messageChannel.port2}, + [messageChannel.port2]); + }); + }); +}; + +function onMessageViaMessagePort(client, e) { + var message = e.data; + if ('value' in message) { + client.postMessage({ack: 'Acking value: ' + message.value}); + } else if ('done' in message) { + client.postMessage({done: true}); + } +} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js new file mode 100644 index 000000000..290a4a9b3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js @@ -0,0 +1,10 @@ +self.onmessage = function(e) { + self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + client.postMessage('Sending message via clients'); + if (!Array.isArray(clients)) + client.postMessage('clients is not an array'); + client.postMessage('quit'); + }); + }); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-worker.js new file mode 100644 index 000000000..858cf0426 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/postmessage-worker.js @@ -0,0 +1,19 @@ +var port; + +// Exercise the 'onmessage' handler: +self.onmessage = function(e) { + var message = e.data; + if ('port' in message) { + port = message.port; + } +}; + +// And an event listener: +self.addEventListener('message', function(e) { + var message = e.data; + if ('value' in message) { + port.postMessage('Acking value: ' + message.value); + } else if ('done' in message) { + port.postMessage('quit'); + } + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/redirect.py b/testing/web-platform/tests/service-workers/service-worker/resources/redirect.py new file mode 100644 index 000000000..20521b00c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/redirect.py @@ -0,0 +1,25 @@ +def main(request, response): + if 'Status' in request.GET: + status = int(request.GET["Status"]) + else: + status = 302 + + headers = [] + + url = request.GET['Redirect'] + headers.append(("Location", url)) + + if "ACAOrigin" in request.GET: + for item in request.GET["ACAOrigin"].split(","): + headers.append(("Access-Control-Allow-Origin", item)) + + for suffix in ["Headers", "Methods", "Credentials"]: + query = "ACA%s" % suffix + header = "Access-Control-Allow-%s" % suffix + if query in request.GET: + headers.append((header, request.GET[query])) + + if "ACEHeaders" in request.GET: + headers.append(("Access-Control-Expose-Headers", request.GET["ACEHeaders"])) + + return status, headers, "" diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/referer-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/referer-iframe.html new file mode 100644 index 000000000..491262e9d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/referer-iframe.html @@ -0,0 +1,39 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js"></script> +<script> +function check_referer(url, expected_referer) { + return fetch(url) + .then(function(res) { return res.json(); }) + .then(function(headers) { + if (headers['referer'] === expected_referer) { + return Promise.resolve(); + } else { + return Promise.reject('Referer for ' + url + ' must be ' + + expected_referer + ' but got ' + + headers['referer']); + } + }); +} + +window.addEventListener('message', function(evt) { + var host_info = get_host_info(); + var port = evt.ports[0]; + check_referer('request-headers.py?ignore=true', + host_info['HTTPS_ORIGIN'] + + base_path() + 'referer-iframe.html') + .then(function() { + return check_referer( + 'request-headers.py', + host_info['HTTPS_ORIGIN'] + + base_path() + 'referer-iframe.html'); + }) + .then(function() { + return check_referer( + 'request-headers.py?url=request-headers.py', + host_info['HTTPS_ORIGIN'] + + base_path() + 'fetch-rewrite-worker.js'); + }) + .then(function() { port.postMessage({results: 'finish'}); }) + .catch(function(e) { port.postMessage({results: 'failure:' + e}); }); + }); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html new file mode 100644 index 000000000..ed743ea05 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/register-closed-window-iframe.html @@ -0,0 +1,16 @@ +<html> +<head> +<script> +window.addEventListener('message', function(evt) { + if (evt.data === 'START') { + var w = window.open('./'); + var sw = w.navigator.serviceWorker; + w.close(); + w = null; + sw.register('doesntmatter.js'); + parent.postMessage('OK', '*'); + } +}); +</script> +</head> +</html> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/registration-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/registration-worker.js new file mode 100644 index 000000000..44d1d2774 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/registration-worker.js @@ -0,0 +1 @@ +// empty for now diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/reject-install-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/reject-install-worker.js new file mode 100644 index 000000000..41f07fd5d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/reject-install-worker.js @@ -0,0 +1,3 @@ +self.oninstall = function(event) { + event.waitUntil(Promise.reject()); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/request-end-to-end-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/request-end-to-end-worker.js new file mode 100644 index 000000000..323c7f243 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/request-end-to-end-worker.js @@ -0,0 +1,32 @@ +var port = undefined; + +onmessage = function(e) { + var message = e.data; + if (typeof message === 'object' && 'port' in message) { + port = message.port; + } +}; + +onfetch = function(e) { + var headers = {}; + var errorNameWhileAppendingHeader; + for (var header of e.request.headers) { + var key = header[0], value = header[1]; + headers[key] = value; + } + var errorNameWhileAddingHeader = ''; + try { + e.request.headers.append('Test-Header', 'TestValue'); + } catch (e) { + errorNameWhileAppendingHeader = e.name; + } + port.postMessage({ + url: e.request.url, + mode: e.request.mode, + method: e.request.method, + referrer: e.request.referrer, + headers: headers, + headerSize: e.request.headers.size, + errorNameWhileAppendingHeader: errorNameWhileAppendingHeader + }); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/request-headers.py b/testing/web-platform/tests/service-workers/service-worker/resources/request-headers.py new file mode 100644 index 000000000..29897f4ec --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/request-headers.py @@ -0,0 +1,6 @@ +import json + +def main(request, response): + data = {key:request.headers[key] for key,value in request.headers.iteritems()} + + return [("Content-Type", "application/json")], json.dumps(data) diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-iframe.html b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-iframe.html new file mode 100644 index 000000000..2af679369 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-iframe.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script src="empty.js"></script> +<script src="dummy.js"></script> +<script src="redirect.py?Redirect=empty.js"></script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js new file mode 100644 index 000000000..481a6536a --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/resource-timing-worker.js @@ -0,0 +1,5 @@ +self.addEventListener('fetch', function(event) { + if (event.request.url.indexOf('dummy.js') != -1) { + event.respondWith(new Response()); + } + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/service-worker-csp-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/service-worker-csp-worker.py new file mode 100644 index 000000000..4e5c6f3b6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/service-worker-csp-worker.py @@ -0,0 +1,153 @@ +bodyDefault = ''' +importScripts('worker-testharness.js'); +importScripts('test-helpers.sub.js'); +importScripts('../resources/get-host-info.sub.js'); + +var host_info = get_host_info(); + +test(function() { + var import_script_failed = false; + try { + importScripts(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'empty.js'); + } catch(e) { + import_script_failed = true; + } + assert_true(import_script_failed, + 'Importing the other origins script should fail.'); + }, 'importScripts test for default-src'); + +async_test(function(t) { + fetch(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?ACAOrigin=*', + {mode: 'cors'}) + .then(function(response){ + assert_unreached('fetch should fail.'); + }, function(){ + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Fetch test for default-src'); + +async_test(function(t) { + var REDIRECT_URL = host_info.HTTPS_ORIGIN + + base_path() + 'redirect.py?Redirect='; + var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?' + fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'), + {mode: 'cors'}) + .then(function(response){ + assert_unreached('Redirected fetch should fail.'); + }, function(){ + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Redirected fetch test for default-src');''' + +bodyScript = ''' +importScripts('worker-testharness.js'); +importScripts('test-helpers.sub.js'); +importScripts('../resources/get-host-info.sub.js'); + +var host_info = get_host_info(); + +test(function() { + var import_script_failed = false; + try { + importScripts(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'empty.js'); + } catch(e) { + import_script_failed = true; + } + assert_true(import_script_failed, + 'Importing the other origins script should fail.'); + }, 'importScripts test for script-src'); + +async_test(function(t) { + fetch(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?ACAOrigin=*', + {mode: 'cors'}) + .then(function(response){ + t.done(); + }, function(){ + assert_unreached('fetch should not fail.'); + }) + .catch(unreached_rejection(t)); + }, 'Fetch test for script-src'); + +async_test(function(t) { + var REDIRECT_URL = host_info.HTTPS_ORIGIN + + base_path() + 'redirect.py?Redirect='; + var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?' + fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'), + {mode: 'cors'}) + .then(function(response){ + t.done(); + }, function(){ + assert_unreached('Redirected fetch should not fail.'); + }) + .catch(unreached_rejection(t)); + }, 'Redirected fetch test for script-src');''' + +bodyConnect = ''' +importScripts('worker-testharness.js'); +importScripts('test-helpers.sub.js'); +importScripts('../resources/get-host-info.sub.js'); + +var host_info = get_host_info(); + +test(function() { + var import_script_failed = false; + try { + importScripts(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'empty.js'); + } catch(e) { + import_script_failed = true; + } + assert_false(import_script_failed, + 'Importing the other origins script should not fail.'); + }, 'importScripts test for connect-src'); + +async_test(function(t) { + fetch(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?ACAOrigin=*', + {mode: 'cors'}) + .then(function(response){ + assert_unreached('fetch should fail.'); + }, function(){ + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Fetch test for connect-src'); + +async_test(function(t) { + var REDIRECT_URL = host_info.HTTPS_ORIGIN + + base_path() + 'redirect.py?Redirect='; + var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?' + fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'), + {mode: 'cors'}) + .then(function(response){ + assert_unreached('Redirected fetch should fail.'); + }, function(){ + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Redirected fetch test for connect-src');''' + +def main(request, response): + headers = [] + headers.append(('Content-Type', 'application/javascript')) + directive = request.GET['directive'] + body = 'ERROR: Unknown directive' + if directive == 'default': + headers.append(('Content-Security-Policy', "default-src 'self'")) + body = bodyDefault + elif directive == 'script': + headers.append(('Content-Security-Policy', "script-src 'self'")) + body = bodyScript + elif directive == 'connect': + headers.append(('Content-Security-Policy', "connect-src 'self'")) + body = bodyConnect + return headers, body diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-controlled.js b/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-controlled.js new file mode 100644 index 000000000..1ccc2fe3b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-controlled.js @@ -0,0 +1,8 @@ +onconnect = function(e) { + var port = e.ports[0]; + var xhr = new XMLHttpRequest(); + xhr.onload = function() { port.postMessage(this.responseText); }; + xhr.onerror = function(e) { port.postMessage(e); }; + xhr.open('GET', 'dummy.txt?simple', true); + xhr.send(); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-import.js b/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-import.js new file mode 100644 index 000000000..7c554bd74 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/shared-worker-import.js @@ -0,0 +1 @@ +importScripts('import-dummy-shared-worker.js'); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/silence.oga b/testing/web-platform/tests/service-workers/service-worker/resources/silence.oga Binary files differnew file mode 100644 index 000000000..af5918804 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/silence.oga diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/simple-intercept-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/simple-intercept-worker.js new file mode 100644 index 000000000..f8b5f8c5c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/simple-intercept-worker.js @@ -0,0 +1,5 @@ +self.onfetch = function(event) { + if (event.request.url.indexOf('simple') != -1) + event.respondWith( + new Response(new Blob(['intercepted by service worker']))); +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/simple.html b/testing/web-platform/tests/service-workers/service-worker/resources/simple.html new file mode 100644 index 000000000..0c3e3e787 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/simple.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<title>Simple</title> +Here's a simple html file. diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/simple.txt b/testing/web-platform/tests/service-workers/service-worker/resources/simple.txt new file mode 100644 index 000000000..9e3cb91fb --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/simple.txt @@ -0,0 +1 @@ +a simple text file diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js new file mode 100644 index 000000000..bf582c770 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js @@ -0,0 +1,24 @@ +self.state = 'starting'; + +self.addEventListener('install', function() { + self.state = 'installing'; + }); + +self.addEventListener('message', function(event) { + var port = event.data.port; + if (self.state !== 'installing') { + port.postMessage('FAIL: Worker should be waiting in installed state'); + return; + } + self.skipWaiting() + .then(function(result) { + if (result !== undefined) { + port.postMessage('FAIL: Promise should be resolved with undefined'); + return; + } + port.postMessage('PASS'); + }) + .catch(function(e) { + port.postMessage('FAIL: unexpected exception: ' + e); + }); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-worker.js new file mode 100644 index 000000000..3fc1d1e23 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/skip-waiting-worker.js @@ -0,0 +1,21 @@ +importScripts('worker-testharness.js'); + +promise_test(function() { + return skipWaiting() + .then(function(result) { + assert_equals(result, undefined, + 'Promise should be resolved with undefined'); + }) + .then(function() { + var promises = []; + for (var i = 0; i < 8; ++i) + promises.push(self.skipWaiting()); + return Promise.all(promises); + }) + .then(function(results) { + results.forEach(function(r) { + assert_equals(r, undefined, + 'Promises should be resolved with undefined'); + }); + }); + }, 'skipWaiting'); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/square.png b/testing/web-platform/tests/service-workers/service-worker/resources/square.png Binary files differnew file mode 100644 index 000000000..01c9666a8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/square.png diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/success.py b/testing/web-platform/tests/service-workers/service-worker/resources/success.py new file mode 100644 index 000000000..bcbb487d2 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/success.py @@ -0,0 +1,8 @@ +def main(request, response): + headers = [] + + if "ACAOrigin" in request.GET: + for item in request.GET["ACAOrigin"].split(","): + headers.append(("Access-Control-Allow-Origin", item)) + + return headers, "{ \"result\": \"success\" }" diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/test-helpers.sub.js b/testing/web-platform/tests/service-workers/service-worker/resources/test-helpers.sub.js new file mode 100644 index 000000000..b0ffbd406 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/test-helpers.sub.js @@ -0,0 +1,227 @@ +// Adapter for testharness.js-style tests with Service Workers + +function service_worker_unregister_and_register(test, url, scope) { + if (!scope || scope.length == 0) + return Promise.reject(new Error('tests must define a scope')); + + var options = { scope: scope }; + return service_worker_unregister(test, scope) + .then(function() { + return navigator.serviceWorker.register(url, options); + }) + .catch(unreached_rejection(test, + 'unregister and register should not fail')); +} + +// This unregisters the registration that precisely matches scope. Use this +// when unregistering by scope. If no registration is found, it just resolves. +function service_worker_unregister(test, scope) { + var absoluteScope = (new URL(scope, window.location).href); + return navigator.serviceWorker.getRegistration(scope) + .then(function(registration) { + if (registration && registration.scope === absoluteScope) + return registration.unregister(); + }) + .catch(unreached_rejection(test, 'unregister should not fail')); +} + +function service_worker_unregister_and_done(test, scope) { + return service_worker_unregister(test, scope) + .then(test.done.bind(test)); +} + +function unreached_fulfillment(test, prefix) { + return test.step_func(function(result) { + var error_prefix = prefix || 'unexpected fulfillment'; + assert_unreached(error_prefix + ': ' + result); + }); +} + +// Rejection-specific helper that provides more details +function unreached_rejection(test, prefix) { + return test.step_func(function(error) { + var reason = error.message || error.name || error; + var error_prefix = prefix || 'unexpected rejection'; + assert_unreached(error_prefix + ': ' + reason); + }); +} + +// Adds an iframe to the document and returns a promise that resolves to the +// iframe when it finishes loading. The caller is responsible for removing the +// iframe later if needed. +function with_iframe(url) { + return new Promise(function(resolve) { + var frame = document.createElement('iframe'); + frame.src = url; + frame.onload = function() { resolve(frame); }; + document.body.appendChild(frame); + }); +} + +function normalizeURL(url) { + return new URL(url, self.location).toString().replace(/#.*$/, ''); +} + +function wait_for_update(test, registration) { + if (!registration || registration.unregister == undefined) { + return Promise.reject(new Error( + 'wait_for_update must be passed a ServiceWorkerRegistration')); + } + + return new Promise(test.step_func(function(resolve) { + registration.addEventListener('updatefound', test.step_func(function() { + resolve(registration.installing); + })); + })); +} + +function wait_for_state(test, worker, state) { + if (!worker || worker.state == undefined) { + return Promise.reject(new Error( + 'wait_for_state must be passed a ServiceWorker')); + } + if (worker.state === state) + return Promise.resolve(state); + + if (state === 'installing') { + switch (worker.state) { + case 'installed': + case 'activating': + case 'activated': + case 'redundant': + return Promise.reject(new Error( + 'worker is ' + worker.state + ' but waiting for ' + state)); + } + } + + if (state === 'installed') { + switch (worker.state) { + case 'activating': + case 'activated': + case 'redundant': + return Promise.reject(new Error( + 'worker is ' + worker.state + ' but waiting for ' + state)); + } + } + + if (state === 'activating') { + switch (worker.state) { + case 'activated': + case 'redundant': + return Promise.reject(new Error( + 'worker is ' + worker.state + ' but waiting for ' + state)); + } + } + + if (state === 'activated') { + switch (worker.state) { + case 'redundant': + return Promise.reject(new Error( + 'worker is ' + worker.state + ' but waiting for ' + state)); + } + } + + return new Promise(test.step_func(function(resolve) { + worker.addEventListener('statechange', test.step_func(function() { + if (worker.state === state) + resolve(state); + })); + })); +} + +// Declare a test that runs entirely in the ServiceWorkerGlobalScope. The |url| +// is the service worker script URL. This function: +// - Instantiates a new test with the description specified in |description|. +// The test will succeed if the specified service worker can be successfully +// registered and installed. +// - Creates a new ServiceWorker registration with a scope unique to the current +// document URL. Note that this doesn't allow more than one +// service_worker_test() to be run from the same document. +// - Waits for the new worker to begin installing. +// - Imports tests results from tests running inside the ServiceWorker. +function service_worker_test(url, description) { + // If the document URL is https://example.com/document and the script URL is + // https://example.com/script/worker.js, then the scope would be + // https://example.com/script/scope/document. + var scope = new URL('scope' + window.location.pathname, + new URL(url, window.location)).toString(); + promise_test(function(test) { + return service_worker_unregister_and_register(test, url, scope) + .then(function(registration) { + add_completion_callback(function() { + registration.unregister(); + }); + return wait_for_update(test, registration) + .then(function(worker) { + return fetch_tests_from_worker(worker); + }); + }); + }, description); +} + +function base_path() { + return location.pathname.replace(/\/[^\/]*$/, '/'); +} + +function test_login(test, origin, username, password, cookie) { + return new Promise(function(resolve, reject) { + with_iframe( + origin + base_path() + + 'resources/fetch-access-control-login.html') + .then(test.step_func(function(frame) { + var channel = new MessageChannel(); + channel.port1.onmessage = test.step_func(function() { + frame.remove(); + resolve(); + }); + frame.contentWindow.postMessage( + {username: username, password: password, cookie: cookie}, + origin, [channel.port2]); + })); + }); +} + +function test_websocket(test, frame, url) { + return new Promise(function(resolve, reject) { + var ws = new frame.contentWindow.WebSocket(url, ['echo', 'chat']); + var openCalled = false; + ws.addEventListener('open', test.step_func(function(e) { + assert_equals(ws.readyState, 1, "The WebSocket should be open"); + openCalled = true; + ws.close(); + }), true); + + ws.addEventListener('close', test.step_func(function(e) { + assert_true(openCalled, "The WebSocket should be closed after being opened"); + resolve(); + }), true); + + ws.addEventListener('error', reject); + }); +} + +function login(test) { + return test_login(test, 'http://{{domains[www1]}}:{{ports[http][0]}}', + 'username1', 'password1', 'cookie1') + .then(function() { + return test_login(test, 'http://{{host}}:{{ports[http][0]}}', + 'username2', 'password2', 'cookie2'); + }); +} + +function login_https(test) { + return test_login(test, 'https://{{domains[www1]}}:{{ports[https][0]}}', + 'username1s', 'password1s', 'cookie1') + .then(function() { + return test_login(test, 'https://{{host}}:{{ports[https][0]}}', + 'username2s', 'password2s', 'cookie2'); + }); +} + +function websocket(test, frame) { + return test_websocket(test, frame, get_websocket_url()); +} + +function get_websocket_url() { + return 'wss://{{host}}:{{ports[wss][0]}}/echo'; +} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/testharness-helpers.js b/testing/web-platform/tests/service-workers/service-worker/resources/testharness-helpers.js new file mode 100644 index 000000000..4d7af1ff9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/testharness-helpers.js @@ -0,0 +1,163 @@ +/* + * testharness-helpers contains various useful extensions to testharness.js to + * allow them to be used across multiple tests before they have been + * upstreamed. This file is intended to be usable from both document and worker + * environments, so code should for example not rely on the DOM. + */ + +// Returns a promise that fulfills after the provided |promise| is fulfilled. +// The |test| succeeds only if |promise| rejects with an exception matching +// |code|. Accepted values for |code| follow those accepted for assert_throws(). +// The optional |description| describes the test being performed. +// +// E.g.: +// assert_promise_rejects( +// new Promise(...), // something that should throw an exception. +// 'NotFoundError', +// 'Should throw NotFoundError.'); +// +// assert_promise_rejects( +// new Promise(...), +// new TypeError(), +// 'Should throw TypeError'); +function assert_promise_rejects(promise, code, description) { + return promise.then( + function() { + throw 'assert_promise_rejects: ' + description + ' Promise did not reject.'; + }, + function(e) { + if (code !== undefined) { + assert_throws(code, function() { throw e; }, description); + } + }); +} + +// Asserts that two objects |actual| and |expected| are weakly equal under the +// following definition: +// +// |a| and |b| are weakly equal if any of the following are true: +// 1. If |a| is not an 'object', and |a| === |b|. +// 2. If |a| is an 'object', and all of the following are true: +// 2.1 |a.p| is weakly equal to |b.p| for all own properties |p| of |a|. +// 2.2 Every own property of |b| is an own property of |a|. +// +// This is a replacement for the the version of assert_object_equals() in +// testharness.js. The latter doesn't handle own properties correctly. I.e. if +// |a.p| is not an own property, it still requires that |b.p| be an own +// property. +// +// Note that |actual| must not contain cyclic references. +self.assert_object_equals = function(actual, expected, description) { + var object_stack = []; + + function _is_equal(actual, expected, prefix) { + if (typeof actual !== 'object') { + assert_equals(actual, expected, prefix); + return; + } + assert_true(typeof expected === 'object', prefix); + assert_equals(object_stack.indexOf(actual), -1, + prefix + ' must not contain cyclic references.'); + + object_stack.push(actual); + + Object.getOwnPropertyNames(expected).forEach(function(property) { + assert_own_property(actual, property, prefix); + _is_equal(actual[property], expected[property], + prefix + '.' + property); + }); + Object.getOwnPropertyNames(actual).forEach(function(property) { + assert_own_property(expected, property, prefix); + }); + + object_stack.pop(); + } + + function _brand(object) { + return Object.prototype.toString.call(object).match(/^\[object (.*)\]$/)[1]; + } + + _is_equal(actual, expected, + (description ? description + ': ' : '') + _brand(expected)); +}; + +// Equivalent to assert_in_array, but uses a weaker equivalence relation +// (assert_object_equals) than '==='. +function assert_object_in_array(actual, expected_array, description) { + assert_true(expected_array.some(function(element) { + try { + assert_object_equals(actual, element); + return true; + } catch (e) { + return false; + } + }), description); +} + +// Assert that the two arrays |actual| and |expected| contain the same set of +// elements as determined by assert_object_equals. The order is not significant. +// +// |expected| is assumed to not contain any duplicates as determined by +// assert_object_equals(). +function assert_array_equivalent(actual, expected, description) { + assert_true(Array.isArray(actual), description); + assert_equals(actual.length, expected.length, description); + expected.forEach(function(expected_element) { + // assert_in_array treats the first argument as being 'actual', and the + // second as being 'expected array'. We are switching them around because + // we want to be resilient against the |actual| array containing + // duplicates. + assert_object_in_array(expected_element, actual, description); + }); +} + +// Asserts that two arrays |actual| and |expected| contain the same set of +// elements as determined by assert_object_equals(). The corresponding elements +// must occupy corresponding indices in their respective arrays. +function assert_array_objects_equals(actual, expected, description) { + assert_true(Array.isArray(actual), description); + assert_equals(actual.length, expected.length, description); + actual.forEach(function(value, index) { + assert_object_equals(value, expected[index], + description + ' : object[' + index + ']'); + }); +} + +// Asserts that |object| that is an instance of some interface has the attribute +// |attribute_name| following the conditions specified by WebIDL, but it's +// acceptable that the attribute |attribute_name| is an own property of the +// object because we're in the middle of moving the attribute to a prototype +// chain. Once we complete the transition to prototype chains, +// assert_will_be_idl_attribute must be replaced with assert_idl_attribute +// defined in testharness.js. +// +// FIXME: Remove assert_will_be_idl_attribute once we complete the transition +// of moving the DOM attributes to prototype chains. (http://crbug.com/43394) +function assert_will_be_idl_attribute(object, attribute_name, description) { + assert_true(typeof object === "object", description); + + assert_true("hasOwnProperty" in object, description); + + // Do not test if |attribute_name| is not an own property because + // |attribute_name| is in the middle of the transition to a prototype + // chain. (http://crbug.com/43394) + + assert_true(attribute_name in object, description); +} + +// Stringifies a DOM object. This function stringifies not only own properties +// but also DOM attributes which are on a prototype chain. Note that +// JSON.stringify only stringifies own properties. +function stringifyDOMObject(object) +{ + function deepCopy(src) { + if (typeof src != "object") + return src; + var dst = Array.isArray(src) ? [] : {}; + for (var property in src) { + dst[property] = deepCopy(src[property]); + } + return dst; + } + return JSON.stringify(deepCopy(object)); +} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/unregister-controller-page.html b/testing/web-platform/tests/service-workers/service-worker/resources/unregister-controller-page.html new file mode 100644 index 000000000..18a95ee89 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/unregister-controller-page.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<script> +function fetch_url(url) { + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.addEventListener('load', function(event) { + if (request.status == 200) + resolve(request.response); + else + reject(Error(request.statusText)); + }); + request.open('GET', url); + request.send(); + }); +} +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update-nocookie-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/update-nocookie-worker.py new file mode 100644 index 000000000..0f09b7e32 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/update-nocookie-worker.py @@ -0,0 +1,15 @@ +import time + +def main(request, response): + # no-cache itself to ensure the user agent finds a new version for each update. + headers = [('Cache-Control', 'no-cache, must-revalidate'), + ('Pragma', 'no-cache')] + + # Set a normal mimetype. + content_type = 'application/javascript' + + headers.append(('Content-Type', content_type)) + # Return a different script for each access. Use .time() and .clock() for + # best time resolution across different platforms. + return headers, '// %s %s' % (time.time(), time.clock()) + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py new file mode 100644 index 000000000..8aaa5ca93 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/update-recovery-worker.py @@ -0,0 +1,25 @@ +def main(request, response): + # Set mode to 'init' for initial fetch. + mode = 'init' + if 'update-recovery-mode' in request.cookies: + mode = request.cookies['update-recovery-mode'].value + + # no-cache itself to ensure the user agent finds a new version for each update. + headers = [('Cache-Control', 'no-cache, must-revalidate'), + ('Pragma', 'no-cache')] + + extra_body = '' + + if mode == 'init': + # Install a bad service worker that will break the controlled + # document navigation. + response.set_cookie('update-recovery-mode', 'bad') + extra_body = "addEventListener('fetch', function(e) { e.respondWith(Promise.reject()); });" + elif mode == 'bad': + # When the update tries to pull the script again, update to + # a worker service worker that does not break document + # navigation. Serve the same script from then on. + response.delete_cookie('update-recovery-mode') + + headers.append(('Content-Type', 'application/javascript')) + return headers, '%s' % (extra_body) diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py b/testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py new file mode 100644 index 000000000..bc9b32ad3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/update-worker.py @@ -0,0 +1,46 @@ +import time + +def main(request, response): + # Set mode to 'init' for initial fetch. + mode = 'init' + if 'mode' in request.cookies: + mode = request.cookies['mode'].value + + # no-cache itself to ensure the user agent finds a new version for each update. + headers = [('Cache-Control', 'no-cache, must-revalidate'), + ('Pragma', 'no-cache')] + + content_type = '' + extra_body = '' + + if mode == 'init': + # Set a normal mimetype. + # Set cookie value to 'normal' so the next fetch will work in 'normal' mode. + content_type = 'application/javascript' + response.set_cookie('mode', 'normal') + elif mode == 'normal': + # Set a normal mimetype. + # Set cookie value to 'error' so the next fetch will work in 'error' mode. + content_type = 'application/javascript' + response.set_cookie('mode', 'error'); + elif mode == 'error': + # Set a disallowed mimetype. + # Set cookie value to 'syntax-error' so the next fetch will work in 'syntax-error' mode. + content_type = 'text/html' + response.set_cookie('mode', 'syntax-error'); + elif mode == 'syntax-error': + # Set cookie value to 'throw-install' so the next fetch will work in 'throw-install' mode. + content_type = 'application/javascript' + response.set_cookie('mode', 'throw-install'); + extra_body = 'badsyntax(isbad;' + elif mode == 'throw-install': + # Unset and delete cookie to clean up the test setting. + content_type = 'application/javascript' + response.delete_cookie('mode') + extra_body = "addEventListener('install', function(e) { throw new Error('boom'); });" + + headers.append(('Content-Type', content_type)) + # Return a different script for each access. Use .time() and .clock() for + # best time resolution across different platforms. + return headers, '/* %s %s */ %s' % (time.time(), time.clock(), extra_body) + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/update/update-after-oneday.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/update/update-after-oneday.https.html new file mode 100644 index 000000000..9d4c98272 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/update/update-after-oneday.https.html @@ -0,0 +1,8 @@ +<body> +<script> +function load_image(url) { + var img = document.createElement('img'); + img.src = url; +} +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/wait-forever-in-install-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/wait-forever-in-install-worker.js new file mode 100644 index 000000000..af85a73ad --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/wait-forever-in-install-worker.js @@ -0,0 +1,12 @@ +var waitUntilResolve; +self.addEventListener('install', function(event) { + event.waitUntil(new Promise(function(resolve) { + waitUntilResolve = resolve; + })); + }); + +self.addEventListener('message', function(event) { + if (event.data === 'STOP_WAITING') { + waitUntilResolve(); + } + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/websocket.js b/testing/web-platform/tests/service-workers/service-worker/resources/websocket.js new file mode 100644 index 000000000..fc6abd283 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/websocket.js @@ -0,0 +1,7 @@ +self.urls = []; +self.addEventListener('fetch', function(event) { + self.urls.push(event.request.url); + }); +self.addEventListener('message', function(event) { + event.data.port.postMessage({urls: self.urls}); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/worker-interception-iframe.https.html b/testing/web-platform/tests/service-workers/service-worker/resources/worker-interception-iframe.https.html new file mode 100644 index 000000000..12a461ea5 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/worker-interception-iframe.https.html @@ -0,0 +1,39 @@ +<script src="../resources/get-host-info.sub.js"></script> +<script src="test-helpers.sub.js?pipe=sub"></script> +<script> +var host_info = get_host_info(); + +function boilerplate_test(msg) { + return new Promise(function(resolve, reject) { + var worker = new Worker("load_worker.js"); + worker.onmessage = function(e) { resolve(e.data) }; + worker.onerror = function(e) { reject(e) }; + worker.postMessage(msg); + }) + .then(function(data) { + window.parent.postMessage({results: data}, host_info['HTTPS_ORIGIN']); + }); +} + +function xhr_test() { + return boilerplate_test("xhr"); +} + +function fetch_test() { + return boilerplate_test("fetch"); +} + +function importScripts_test() { + return boilerplate_test("importScripts"); +} + +window.addEventListener('message', function(evt) { + var port = evt.ports[0]; + xhr_test() + .then(fetch_test) + .then(importScripts_test) + .then(function() { port.postMessage({results: 'finish'}); }) + .catch(function(e) { port.postMessage({results: 'failure:' + e}); }); + }); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/worker-load-interceptor.js b/testing/web-platform/tests/service-workers/service-worker/resources/worker-load-interceptor.js new file mode 100644 index 000000000..960c6328c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/worker-load-interceptor.js @@ -0,0 +1,13 @@ +importScripts('get-host-info.sub.js'); + +var response_text = "This load was successfully intercepted."; +var response_script = "postMessage(\"This load was successfully intercepted.\");"; + +self.onfetch = function(event) { + var url = event.request.url; + if (url.indexOf("synthesized-response.txt") != -1) { + event.respondWith(new Response(response_text)); + } else if (url.indexOf("synthesized-response.js") != -1) { + event.respondWith(new Response(response_script)); + } +}; diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/worker-testharness.js b/testing/web-platform/tests/service-workers/service-worker/resources/worker-testharness.js new file mode 100644 index 000000000..fdf5868e3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/worker-testharness.js @@ -0,0 +1,49 @@ +/* + * worker-test-harness should be considered a temporary polyfill around + * testharness.js for supporting Service Worker based tests. It should not be + * necessary once the test harness is able to drive worker based tests natively. + * See https://github.com/w3c/testharness.js/pull/82 for status of effort to + * update upstream testharness.js. Once the upstreaming is complete, tests that + * reference worker-test-harness should be updated to directly import + * testharness.js. + */ + +importScripts('/resources/testharness.js'); + +(function() { + var next_cache_index = 1; + + // Returns a promise that resolves to a newly created Cache object. The + // returned Cache will be destroyed when |test| completes. + function create_temporary_cache(test) { + var uniquifier = String(++next_cache_index); + var cache_name = self.location.pathname + '/' + uniquifier; + + test.add_cleanup(function() { + self.caches.delete(cache_name); + }); + + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }); + } + + self.create_temporary_cache = create_temporary_cache; +})(); + +// Runs |test_function| with a temporary unique Cache passed in as the only +// argument. The function is run as a part of Promise chain owned by +// promise_test(). As such, it is expected to behave in a manner identical (with +// the exception of the argument) to a function passed into promise_test(). +// +// E.g.: +// cache_test(function(cache) { +// // Do something with |cache|, which is a Cache object. +// }, "Some Cache test"); +function cache_test(test_function, description) { + promise_test(function(test) { + return create_temporary_cache(test) + .then(test_function); + }, description); +} diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/xhr.js b/testing/web-platform/tests/service-workers/service-worker/resources/xhr.js new file mode 100644 index 000000000..387c4a48e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/xhr.js @@ -0,0 +1,6 @@ +self.addEventListener('activate', function(event) { + event.waitUntil(clients.claim()); + }); +self.addEventListener('message', function(event) { + event.data.port.postMessage({xhr: !!("XMLHttpRequest" in self)}); + }); diff --git a/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-connect.https.html b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-connect.https.html new file mode 100644 index 000000000..226f4a40e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-connect.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Service Worker: CSP connect directive for ServiceWorker script</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +service_worker_test( + 'resources/service-worker-csp-worker.py?directive=connect', + 'CSP test for connect-src in ServiceWorkerGlobalScope'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-default.https.html b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-default.https.html new file mode 100644 index 000000000..1d4e7624d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-default.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Service Worker: CSP default directive for ServiceWorker script</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +service_worker_test( + 'resources/service-worker-csp-worker.py?directive=default', + 'CSP test for default-src in ServiceWorkerGlobalScope'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-script.https.html b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-script.https.html new file mode 100644 index 000000000..14c2eb72b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/service-worker-csp-script.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Service Worker: CSP script directive for ServiceWorker script</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> +service_worker_test( + 'resources/service-worker-csp-worker.py?directive=script', + 'CSP test for script-src in ServiceWorkerGlobalScope'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html b/testing/web-platform/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html new file mode 100644 index 000000000..95587a5a4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>ServiceWorker object: scriptURL property</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +function url_test(name, url) { + var scope = 'resources/scope/' + name; + async_test(function(t) { + var expectedURL = (new URL(url, window.location)).toString(); + service_worker_unregister_and_register(t, url, scope) + .then(function(registration) { + var worker = registration.installing; + assert_equals(worker.scriptURL, expectedURL, + 'Returned ServiceWorker object should have scriptURL'); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Verify the scriptURL property: ' + name); +} + +url_test('relative', 'resources/empty-worker.js'); +url_test('absolute', (new URL('./resources/empty-worker.js', window.location)).href); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/shared-worker-controlled.https.html b/testing/web-platform/tests/service-workers/service-worker/shared-worker-controlled.https.html new file mode 100644 index 000000000..33d52e011 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/shared-worker-controlled.https.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<title>Service Worker: controlling a SharedWorker</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +promise_test(function(t) { + var shared_worker = 'resources/shared-worker-controlled.js'; + var service_worker = 'resources/simple-intercept-worker.js'; + var scope = shared_worker; + + return service_worker_unregister_and_register(t, service_worker, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return new Promise(function(resolve, reject) { + var w = new SharedWorker(shared_worker); + w.port.onmessage = function(e) { + resolve(e.data); + } + }); + }) + .then(function(data) { + assert_equals(data, 'intercepted by service worker'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Verify subresource loads in SharedWorker are controlled by a Service Worker'); + +promise_test(function(t) { + var shared_worker = 'resources/dummy-shared-worker.js'; + var service_worker = 'resources/dummy-shared-worker-interceptor.js'; + var scope = shared_worker; + + return service_worker_unregister_and_register(t, service_worker, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return new Promise(function(resolve, reject) { + var w = new SharedWorker(shared_worker); + w.port.onmessage = function(e) { + resolve(e.data); + } + }); + }) + .then(function(data) { + assert_equals(data, 'worker loading intercepted by service worker'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Verify SharedWorker construction is controlled by a Service Worker'); + +promise_test(function(t) { + var shared_worker = 'resources/shared-worker-import.js'; + var service_worker = 'resources/dummy-shared-worker-interceptor.js'; + var scope = shared_worker; + + return service_worker_unregister_and_register(t, service_worker, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return new Promise(function(resolve, reject) { + var w = new SharedWorker(shared_worker); + w.port.onmessage = function(e) { + resolve(e.data); + } + }); + }) + .then(function(data) { + assert_equals(data, 'worker loading intercepted by service worker'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Verify importScripts from SharedWorker is controlled by a Service Worker'); +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting-installed.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-installed.https.html new file mode 100644 index 000000000..42e4000b1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-installed.https.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<title>Service Worker: Skip waiting installed worker</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var scope = 'resources/blank.html'; + var url1 = 'resources/empty.js'; + var url2 = 'resources/skip-waiting-installed-worker.js'; + var frame, frame_sw, service_worker, registration, onmessage, oncontrollerchanged; + var saw_message = new Promise(function(resolve) { + onmessage = function(e) { + var message = e.data; + assert_equals( + message, 'PASS', + 'skipWaiting promise should be resolved with undefined'); + + assert_equals(registration.active.scriptURL, normalizeURL(url2), + "skipWaiting should make worker become active"); + resolve(); + }; + }); + var saw_controllerchanged = new Promise(function(resolve) { + oncontrollerchanged = function() { + assert_equals( + frame_sw.controller.scriptURL, normalizeURL(url2), + 'Controller scriptURL should change to the second one'); + resolve(); + }; + }); + return service_worker_unregister_and_register(t, url1, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + frame_sw = f.contentWindow.navigator.serviceWorker; + assert_equals( + frame_sw.controller.scriptURL, normalizeURL(url1), + 'Document controller scriptURL should equal to the first one'); + frame_sw.oncontrollerchange = t.step_func(oncontrollerchanged); + return navigator.serviceWorker.register(url2, {scope: scope}); + }) + .then(function(r) { + registration = r; + service_worker = r.installing; + return wait_for_state(t, service_worker, 'installed'); + }) + .then(function() { + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(onmessage); + service_worker.postMessage({port: channel.port2}, [channel.port2]); + return Promise.all([saw_message, saw_controllerchanged]); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Test skipWaiting when a installed worker is waiting'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting-using-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-using-registration.https.html new file mode 100644 index 000000000..5f84f0b8e --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-using-registration.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Service Worker: Skip waiting using registration</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var scope = 'resources/blank.html'; + var url1 = 'resources/empty.js'; + var url2 = 'resources/skip-waiting-worker.js'; + var frame, frame_sw, sw_registration, oncontrollerchanged; + var saw_controllerchanged = new Promise(function(resolve) { + oncontrollerchanged = function(e) { + assert_equals(e.type, 'controllerchange', + 'Event name should be "controllerchange"'); + assert_true( + e.target instanceof frame.contentWindow.ServiceWorkerContainer, + 'Event target should be a ServiceWorkerContainer'); + assert_equals(e.target.controller.state, 'activating', + 'Controller state should be activating'); + assert_equals( + frame_sw.controller.scriptURL, normalizeURL(url2), + 'Controller scriptURL should change to the second one'); + resolve(); + }; + }); + return service_worker_unregister_and_register(t, url1, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + frame_sw = f.contentWindow.navigator.serviceWorker; + assert_equals( + frame_sw.controller.scriptURL, normalizeURL(url1), + 'Document controller scriptURL should equal to the first one'); + frame_sw.oncontrollerchange = t.step_func(oncontrollerchanged); + return navigator.serviceWorker.register(url2, {scope: scope}); + }) + .then(function(registration) { + sw_registration = registration; + add_completion_callback(function() { + frame.remove(); + registration.unregister(); + }); + return saw_controllerchanged; + }) + .then(function() { + assert_not_equals(sw_registration.active, null, + 'Registration active worker should not be null'); + fetch_tests_from_worker(sw_registration.active); + }); + }, 'Test skipWaiting while a client is using the registration'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-client.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-client.https.html new file mode 100644 index 000000000..38fca1726 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-client.https.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<title>Service Worker: Skip waiting without client</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +service_worker_test( + 'resources/skip-waiting-worker.js', + 'Test single skipWaiting() when no client attached'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-using-registration.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-using-registration.https.html new file mode 100644 index 000000000..2535ffe09 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting-without-using-registration.https.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>Service Worker: Skip waiting without using registration</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var scope = 'resources/blank.html'; + var url = 'resources/skip-waiting-worker.js'; + var frame, frame_sw, sw_registration; + + return service_worker_unregister(t, scope) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + frame_sw = f.contentWindow.navigator.serviceWorker; + assert_equals(frame_sw.controller, null, + 'Document controller should be null'); + return navigator.serviceWorker.register(url, {scope: scope}); + }) + .then(function(registration) { + sw_registration = registration; + add_completion_callback(function() { + frame.remove(); + registration.unregister(); + }); + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + assert_equals(frame_sw.controller, null, + 'Document controller should still be null'); + assert_not_equals(sw_registration.active, null, + 'Registration active worker should not be null'); + fetch_tests_from_worker(sw_registration.active); + }); + }, 'Test skipWaiting while a client is not being controlled'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/skip-waiting.https.html b/testing/web-platform/tests/service-workers/service-worker/skip-waiting.https.html new file mode 100644 index 000000000..7c1c41f3f --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/skip-waiting.https.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title>Service Worker: Skip waiting</title> +<script src="/resources/testharness.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var scope = 'resources/blank.html'; + var url1 = 'resources/empty.js'; + var url2 = 'resources/empty-worker.js'; + var url3 = 'resources/skip-waiting-worker.js'; + var frame, sw_registration, activated_worker, waiting_worker; + return service_worker_unregister_and_register(t, url1, scope) + .then(function(registration) { + sw_registration = registration; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + return navigator.serviceWorker.register(url2, {scope: scope}); + }) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + activated_worker = sw_registration.active; + waiting_worker = sw_registration.waiting; + assert_equals(activated_worker.scriptURL, normalizeURL(url1), + 'Worker with url1 should be activated'); + assert_equals(waiting_worker.scriptURL, normalizeURL(url2), + 'Worker with url2 should be waiting'); + return navigator.serviceWorker.register(url3, {scope: scope}); + }) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + assert_equals(activated_worker.state, 'redundant', + 'Worker with url1 should be redundant'); + assert_equals(waiting_worker.state, 'redundant', + 'Worker with url2 should be redundant'); + assert_equals(sw_registration.active.scriptURL, normalizeURL(url3), + 'Worker with url3 should be activated'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }); + }, 'Test skipWaiting with both active and waiting workers'); + +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/state.https.html b/testing/web-platform/tests/service-workers/service-worker/state.https.html new file mode 100644 index 000000000..3810362e0 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/state.https.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +(function () { + var t = async_test('Service Worker state property and "statechange" event'); + var currentState = 'test-is-starting'; + var scope = 'resources/state/'; + + service_worker_unregister_and_register( + t, 'resources/empty-worker.js', scope) + .then(t.step_func(function(registration) { + var sw = registration.installing; + sw.addEventListener('statechange', t.step_func(onStateChange(sw))); + assert_equals(sw.state, 'installing', + 'the service worker should be in "installing" state.'); + checkStateTransition(sw.state); + })) + .catch(unreached_rejection(t)); + + function checkStateTransition(newState) { + switch (currentState) { + case 'test-is-starting': + break; // anything goes + case 'installing': + assert_in_array(newState, ['installed', 'redundant']); + break; + case 'installed': + assert_in_array(newState, ['activating', 'redundant']); + break; + case 'activating': + assert_in_array(newState, ['activated', 'redundant']); + break; + case 'activated': + assert_equals(newState, 'redundant'); + break; + case 'redundant': + assert_unreached('a ServiceWorker should not transition out of ' + + 'the "redundant" state'); + break; + default: + assert_unreached('should not transition into unknown state "' + + newState + '"'); + break; + } + currentState = newState; + } + + function onStateChange(expectedTarget) { + return function(event) { + assert_true(event.target instanceof ServiceWorker, + 'the target of the statechange event should be a ' + + 'ServiceWorker.'); + assert_equals(event.target, expectedTarget, + 'the target of the statechange event should be ' + + 'the installing ServiceWorker'); + assert_equals(event.type, 'statechange', + 'the type of the event should be "statechange".'); + + checkStateTransition(event.target.state); + + if (event.target.state == 'activated') + service_worker_unregister_and_done(t, scope); + }; + } +}()); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/synced-state.https.html b/testing/web-platform/tests/service-workers/service-worker/synced-state.https.html new file mode 100644 index 000000000..d842378be --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/synced-state.https.html @@ -0,0 +1,64 @@ +<!doctype html> +<title>ServiceWorker: worker objects have synced state</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +// Tests that ServiceWorker objects representing the same Service Worker +// entity have the same state. JS object equality is not tested, since the spec +// does not require it. +promise_test(function(t) { + var scope = 'resources/synced-state'; + var script = 'resources/empty-worker.js'; + return service_worker_unregister_and_register(t, script, scope) + .then(function(registration) { + return new Promise(function(resolve) { + var step = 0; + registration.installing.addEventListener('statechange', + function(e) { + step++; + if (step == 1) { + assert_equals(e.currentTarget.state, 'installed', + 'original SW should be installed'); + assert_equals(registration.installing, null, + 'in installed, .installing should be null'); + // The Activate algorithm may have cleared the waiting worker + // by now. + if (registration.waiting) { + assert_equals(registration.waiting.state, 'installed', + 'in installed, .waiting should be installed'); + assert_equals(registration.active, null, + 'in installed, .active should be null'); + } else { + assert_equals(registration.active.state, 'activating', + 'in installed, .active should be activating'); + } + } else if (step == 2) { + assert_equals(e.currentTarget.state, 'activating', + 'original SW should be activating'); + assert_equals(registration.installing, null, + 'in activating, .installing should be null'); + assert_equals(registration.waiting, null, + 'in activating, .waiting should be null'); + assert_equals( + registration.active.state, 'activating', + 'in activating, .active should be activating'); + } else if (step == 3) { + assert_equals(e.currentTarget.state, 'activated', + 'original SW should be activated'); + assert_equals(registration.installing, null, + 'in activated, .installing should be null'); + assert_equals(registration.waiting, null, + 'in activated, .waiting should be null'); + assert_equals(registration.active.state, 'activated', + 'in activated .active should be activated'); + resolve(); + } + }) + }) + }) + .then(function() { + return service_worker_unregister_and_done(t, scope); + }); + }, 'worker objects for the same entity have the same state'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/uncontrolled-page.https.html b/testing/web-platform/tests/service-workers/service-worker/uncontrolled-page.https.html new file mode 100644 index 000000000..a9523a54c --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/uncontrolled-page.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>Service Worker: Registration</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +function fetch_url(url) { + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.addEventListener('load', function(event) { + if (request.status == 200) + resolve(request.response); + else + reject(Error(request.statusText)); + }); + request.open('GET', url); + request.send(); + }); +} +var worker = 'resources/fail-on-fetch-worker.js'; + +async_test(function(t) { + var scope = 'resources/scope/uncontrolled-page/'; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { + return fetch_url('resources/simple.txt'); + }) + .then(function(text) { + assert_equals(text, 'a simple text file\n'); + service_worker_unregister_and_done(t, scope); + }) + .catch(t.step_func(function(reason) { + assert_unreached(reason.message); + })); + }, 'Fetch events should not go through uncontrolled page.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/unregister-controller.https.html b/testing/web-platform/tests/service-workers/service-worker/unregister-controller.https.html new file mode 100644 index 000000000..3bf4cff72 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/unregister-controller.https.html @@ -0,0 +1,108 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var worker_url = 'resources/simple-intercept-worker.js'; + +async_test(function(t) { + var scope = + 'resources/unregister-controller-page.html?load-before-unregister'; + var frame_window; + var controller; + var registration; + var frame; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + frame_window = frame.contentWindow; + controller = frame_window.navigator.serviceWorker.controller; + assert_true(controller instanceof frame_window.ServiceWorker, + 'document should load with a controller'); + return registration.unregister(); + }) + .then(function() { + assert_equals(frame_window.navigator.serviceWorker.controller, + controller, + 'unregistration should not modify controller'); + return frame_window.fetch_url('simple.txt'); + }) + .then(function(response) { + assert_equals(response, 'intercepted by service worker', + 'controller should intercept requests'); + frame.remove(); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Unregister does not affect existing controller'); + +async_test(function(t) { + var scope = + 'resources/unregister-controller-page.html?load-after-unregister'; + var registration; + var frame; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return registration.unregister(); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + var frame_window = frame.contentWindow; + assert_equals(frame_window.navigator.serviceWorker.controller, null, + 'document should not have a controller'); + return frame_window.fetch_url('simple.txt'); + }) + .then(function(response) { + assert_equals(response, 'a simple text file\n', + 'requests should not be intercepted'); + frame.remove(); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Unregister prevents control of subsequent navigations'); + +async_test(function(t) { + var scope = + 'resources/scope/no-new-controllee-even-if-registration-is-still-used'; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + return registration.unregister(); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + assert_equals(frame.contentWindow.navigator.serviceWorker.controller, + null, + 'document should not have a controller'); + frame.remove(); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Unregister prevents new controllee even if registration is still in use'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/unregister-then-register-new-script.https.html b/testing/web-platform/tests/service-workers/service-worker/unregister-then-register-new-script.https.html new file mode 100644 index 000000000..385430c2d --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/unregister-then-register-new-script.https.html @@ -0,0 +1,159 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var worker_url = 'resources/empty-worker.js'; + +async_test(function(t) { + var scope = 'resources/scope/unregister-then-register-new-script-that-exists'; + var new_worker_url = worker_url + '?new'; + var iframe; + var registration; + var new_registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + iframe = frame; + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.register(new_worker_url, + { scope: scope }); + }) + .then(function(r) { + new_registration = r; + assert_equals(registration.installing.scriptURL, + normalizeURL(new_worker_url), + 'before activated registration.installing'); + assert_equals(registration.waiting, null, + 'before activated registration.waiting'); + assert_equals(registration.active.scriptURL, normalizeURL(worker_url), + 'before activated registration.active'); + assert_equals(new_registration.installing.scriptURL, + normalizeURL(new_worker_url), + 'before activated new_registration.installing'); + assert_equals(new_registration.waiting, null, + 'before activated new_registration.waiting'); + assert_equals(new_registration.active.scriptURL, + normalizeURL(worker_url), + 'before activated new_registration.active'); + iframe.remove(); + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + assert_equals(new_registration.installing, null, + 'after activated new_registration.installing'); + assert_equals(new_registration.waiting, null, + 'after activated new_registration.waiting'); + assert_equals(new_registration.active.scriptURL, + normalizeURL(new_worker_url), + 'after activated new_registration.active'); + return with_iframe(scope); + }) + .then(function(frame) { + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller.scriptURL, + normalizeURL(new_worker_url), + 'the new worker should control a new document'); + frame.remove(); + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); +}, 'Registering a new script URL while an unregistered registration is in use'); + +async_test(function(t) { + var scope = 'resources/scope/unregister-then-register-new-script-that-404s'; + var iframe; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + iframe = frame; + return registration.unregister(); + }) + .then(function() { + var promise = navigator.serviceWorker.register('this-will-404', + { scope: scope }); + iframe.remove(); + return promise; + }) + .then( + function() { + assert_unreached('register should reject the promise'); + }, + function() { + return with_iframe(scope); + }) + .then(function(frame) { + assert_equals(frame.contentWindow.navigator.serviceWorker.controller, + null, + 'document should not load with a controller'); + frame.remove(); + t.done(); + }) + .catch(unreached_rejection(t)); +}, 'Registering a new script URL that 404s does not resurrect an ' + + 'unregistered registration'); + +async_test(function(t) { + var scope = 'resources/scope/unregister-then-register-reject-install-worker'; + var iframe; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + iframe = frame; + return registration.unregister(); + }) + .then(function() { + var promise = navigator.serviceWorker.register( + 'resources/reject-install-worker.js', { scope: scope }); + iframe.remove(); + return promise; + }) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'redundant'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + assert_equals(frame.contentWindow.navigator.serviceWorker.controller, + null, + 'document should not load with a controller'); + frame.remove(); + return registration.unregister(); + }) + .then(function() { + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Registering a new script URL that fails to install does not resurrect ' + + 'an unregistered registration'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/unregister-then-register.https.html b/testing/web-platform/tests/service-workers/service-worker/unregister-then-register.https.html new file mode 100644 index 000000000..d75904d15 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/unregister-then-register.https.html @@ -0,0 +1,129 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +var worker_url = 'resources/empty-worker.js'; + +async_test(function(t) { + var scope = 'resources/scope/re-register-resolves-to-new-value'; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.register(worker_url, { scope: scope }); + }) + .then(function(new_registration) { + assert_not_equals(registration, new_registration, + 'register should resolve to a new value'); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Unregister then register resolves to a new value'); + +async_test(function(t) { + var scope = 'resources/scope/re-register-while-old-registration-in-use'; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.register(worker_url, { scope: scope }); + }) + .then(function(new_registration) { + assert_equals(registration, new_registration, + 'new registration should resolve to the same registration'); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Unregister then register resolves to the original value if the ' + + 'registration is in use.'); + +async_test(function(t) { + var scope = 'resources/scope/re-register-does-not-affect-existing-controllee'; + var iframe; + var registration; + var controller; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + iframe = frame; + controller = iframe.contentWindow.navigator.serviceWorker.controller; + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.register(worker_url, { scope: scope }); + }) + .then(function(registration) { + assert_equals(registration.installing, null, + 'installing version is null'); + assert_equals(registration.waiting, null, 'waiting version is null'); + assert_equals( + iframe.contentWindow.navigator.serviceWorker.controller, + controller, + 'the worker from the first registration is the controller'); + iframe.remove(); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Unregister then register does not affect existing controllee'); + +async_test(function(t) { + var scope = 'resources/scope/resurrection'; + var iframe; + var registration; + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + iframe = frame; + return registration.unregister(); + }) + .then(function() { + return navigator.serviceWorker.register(worker_url, { scope: scope }); + }) + .then(function() { + iframe.remove(); + return with_iframe(scope); + }) + .then(function(frame) { + // FIXME: When crbug.com/400602 is fixed, assert that controller + // equals the original worker. + assert_not_equals( + frame.contentWindow.navigator.serviceWorker.controller, null, + 'document should have a controller'); + frame.remove(); + service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Unregister then register resurrects the registration'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/unregister.https.html b/testing/web-platform/tests/service-workers/service-worker/unregister.https.html new file mode 100644 index 000000000..492aecb21 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/unregister.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +async_test(function(t) { + var scope = 'resources/scope/unregister-twice'; + var registration; + navigator.serviceWorker.register('resources/empty-worker.js', + {scope: scope}) + .then(function(r) { + registration = r; + return registration.unregister(); + }) + .then(function() { + return registration.unregister(); + }) + .then(function(value) { + assert_equals(value, false, + 'unregistering twice should resolve with false'); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Unregister twice'); + +async_test(function(t) { + var scope = 'resources/scope/successful-unregister/'; + navigator.serviceWorker.register('resources/empty-worker.js', + {scope: scope}) + .then(function(registration) { + return registration.unregister(); + }) + .then(function(value) { + assert_equals(value, true, + 'unregistration should resolve with true'); + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Register then unregister'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/update-after-navigation-fetch-event.https.html b/testing/web-platform/tests/service-workers/service-worker/update-after-navigation-fetch-event.https.html new file mode 100644 index 000000000..04cd9960f --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/update-after-navigation-fetch-event.https.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>Service Worker: Update should be triggered after a navigation fetch event.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var script = 'resources/update-nocookie-worker.py'; + var scope = 'resources/scope/update'; + var parsed_url = normalizeURL(script); + var registration; + var frame; + + return service_worker_unregister_and_register(t, parsed_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + // Navigation to the iframe. + return with_iframe(scope); + }) + .then(function(f) { + frame = f; + // Navigation fetch event should trigger update. + return wait_for_update(t, registration); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + }, 'Update should be triggered after a navigation fetch event.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/update-after-oneday.https.html b/testing/web-platform/tests/service-workers/service-worker/update-after-oneday.https.html new file mode 100644 index 000000000..151a59ebc --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/update-after-oneday.https.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<!-- This test requires browser to treat all registrations are older than 24 hours. + Preference 'dom.serviceWorkers.testUpdateOverOneDay' should be enabled during + the execution of the test --> +<title>Service Worker: Functional events should trigger update if last update time is over 24 hours</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> + +promise_test(function(t) { + var script = 'resources/update-nocookie-worker.py'; + var scope = 'resources/update/update-after-oneday.https.html'; + var expected_url = normalizeURL(script); + var registration; + var frame; + + return service_worker_unregister_and_register(t, expected_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return wait_for_update(t, registration); + }) + .then(function() { + assert_equals(registration.installing.scriptURL, expected_url, + 'new installing should be set after update resolves.'); + assert_equals(registration.waiting, null, + 'waiting should still be null after update resolves.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after update found.'); + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + // Trigger a non-navigation fetch event + frame.contentWindow.load_image(normalizeURL('resources/update/dummy')); + return wait_for_update(t, registration); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + }, 'Update should be triggered after a functional event when last update time is over 24 hours'); + +</script> + + diff --git a/testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html b/testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html new file mode 100644 index 000000000..3b3d955b1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/update-recovery.https.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<title>Service Worker: recovery by navigation update</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +async_test(function(t) { + var scope = 'resources/simple.txt'; + var worker_url = 'resources/update-recovery-worker.py'; + var expected_url = normalizeURL(worker_url); + var registration; + + function with_bad_iframe(url) { + return new Promise(function(resolve, reject) { + var frame = document.createElement('iframe'); + + // There is no cross-browser event to listen for to detect an + // iframe that fails to load due to a bad interception. Unfortunately + // we have to use a timeout. + var timeout = setTimeout(function() { + frame.remove(); + resolve(); + }, 5000); + + // If we do get a load event, though, we know something went wrong. + frame.addEventListener('load', function() { + clearTimeout(timeout); + frame.remove(); + reject('expected bad iframe should not fire a load event!'); + }); + + frame.src = url; + document.body.appendChild(frame); + }); + } + + function with_update(t) { + return new Promise(function(resolve, reject) { + registration.addEventListener('updatefound', function onUpdate() { + registration.removeEventListener('updatefound', onUpdate); + wait_for_state(t, registration.installing, 'activated').then(function() { + resolve(); + }); + }); + }); + } + + service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + return Promise.all([ + with_update(t), + with_bad_iframe(scope) + ]); + }) + .then(function() { + return with_iframe(scope); + }) + .then(function(frame) { + assert_equals(frame.contentWindow.navigator.serviceWorker.controller.scriptURL, + expected_url); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Recover from a bad service worker by updating after a failed navigation.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/update.https.html b/testing/web-platform/tests/service-workers/service-worker/update.https.html new file mode 100644 index 000000000..213b72ac8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/update.https.html @@ -0,0 +1,123 @@ +<!DOCTYPE html> +<title>Service Worker: Registration update()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/testharness-helpers.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<script> +promise_test(function(t) { + var scope = 'resources/simple.txt'; + var worker_url = 'resources/update-worker.py'; + var expected_url = normalizeURL(worker_url); + var registration; + var iframe; + return service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'installing should be null in the initial state.'); + assert_equals(registration.waiting, null, + 'waiting should be null in the initial state.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should exist in the initial state.'); + // A new worker (generated by update-worker.py) should be found. + // The returned promise should resolve when a new worker script is + // fetched and starts installing. + return Promise.all([registration.update(), + wait_for_update(t, registration)]); + }) + .then(function() { + assert_equals(registration.installing.scriptURL, expected_url, + 'new installing should be set after update resolves.'); + assert_equals(registration.waiting, null, + 'waiting should still be null after update resolves.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after update found.'); + return wait_for_state(t, registration.installing, 'installed'); + }) + .then(function() { + assert_equals(registration.installing, null, + 'installing should be null after installing.'); + if (registration.waiting) { + assert_equals(registration.waiting.scriptURL, expected_url, + 'waiting should be set after installing.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after installing.'); + return wait_for_state(t, registration.waiting, 'activated'); + } + }) + .then(function() { + assert_equals(registration.installing, null, + 'installing should be null after activated.'); + assert_equals(registration.waiting, null, + 'waiting should be null after activated.'); + assert_equals(registration.active.scriptURL, expected_url, + 'new worker should be promoted to active.'); + }) + .then(function() { + // A new worker(generated by update-worker.py) should be found. + // The returned promise should reject as update-worker.py sets the + // mimetype to a disallowed value for this attempt. + return registration.update(); + }) + .then( + function() { assert_unreached("update() should reject."); }, + function(e) { + assert_throws('SecurityError', function() { throw e; }, + 'Using a disallowed mimetype should make update() ' + + 'promise reject with a SecurityError.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after update failure.'); + + // A new worker(generated by update-worker.py) should be found. + // The returned promise should reject as update-worker.py returns + // a worker script with a syntax error. + return registration.update(); + }) + .then( + function() { assert_unreached("update() should reject."); }, + function(e) { + assert_throws({name: 'TypeError'}, function () { throw e; }, + 'A script syntax error should make update() ' + + 'promise reject with a TypeError.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after update failure.'); + + // A new worker(generated by update-worker.py) should be found. + // The returned promise should not reject, even though + // update-worker.py returns a worker script that throws in the + // install event handler. + return Promise.all([registration.update(), + wait_for_update(t, registration)]); + }) + .then(function() { + assert_equals(registration.installing.scriptURL, expected_url, + 'new installing should be set after update resolves.'); + assert_equals(registration.waiting, null, + 'waiting should be null after activated.'); + assert_equals(registration.active.scriptURL, expected_url, + 'active should still exist after update found.'); + + // We need to hold a client alive so that unregister() below doesn't + // remove the registration before update() has had a chance to look + // at the pending uninstall flag. + return with_iframe(scope); + }) + .then(function(frame) { + iframe = frame; + + return assert_promise_rejects( + Promise.all([registration.unregister(), registration.update()]), + new TypeError(), + 'Calling update() while the uninstalling flag is set ' + + 'should return a promise that rejects with a TypeError.'); + }) + .then(function() { + iframe.remove(); + return t.done(); + }); + }, 'Update a registration.'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/waiting.https.html b/testing/web-platform/tests/service-workers/service-worker/waiting.https.html new file mode 100644 index 000000000..eff9c80a4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/waiting.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<title>ServiceWorker: navigator.serviceWorker.waiting</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +// "waiting" is set +async_test(function(t) { + var step = t.step_func.bind(t); + var url = 'resources/empty-worker.js'; + var scope = 'resources/blank.html'; + var frame; + var registration; + + service_worker_unregister(t, scope) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + return navigator.serviceWorker.register(url, {scope: scope}); + }) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, 'installed'); + }, unreached_rejection(t, 'Registration should not fail')) + .then(function() { + var controller = frame.contentWindow.navigator.serviceWorker.controller; + assert_equals(controller, null); + // Nothing in the spec prohibits a worker from going to active + // immediately. + // Step 26 of the [[Install]] algorithm + // "If registration's waiting worker waitingWorker is not null and + // waitingWorker's skip waiting flag is not set, invoke Activate + // algorithm, or its equivalent, with registration as its argument." + var w = registration.waiting || registration.active; + assert_equals(w.scriptURL, normalizeURL(url)); + assert_equals(registration.installing, null); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }); +}, 'waiting or active is set'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/websocket.https.html b/testing/web-platform/tests/service-workers/service-worker/websocket.https.html new file mode 100644 index 000000000..3dfa6e514 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/websocket.https.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title>Service Worker: WebSocket handshake channel is not intercepted</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> + +async_test(function(t) { + var path = new URL(".", window.location).pathname + var url = 'resources/websocket.js'; + var scope = 'resources/blank.html?websocket'; + var host_info = get_host_info(); + var frameURL = host_info['HTTPS_ORIGIN'] + path + scope; + var frame; + + service_worker_unregister_and_register(t, url, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(frameURL); }) + .then(function(f) { + frame = f; + return websocket(t, frame); + }) + .then(function() { + return new Promise(function(resolve, reject) { + function onMessage(e) { + for (var url in e.data.urls) { + assert_equals(url.indexOf(get_websocket_url()), -1, + "Observed an unexpected FetchEvent for the WebSocket handshake"); + } + t.done(); + } + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(onMessage); + frame.contentWindow.navigator.serviceWorker.controller.postMessage({port: channel.port2}, [channel.port2]); + }); + }) + .then(function() { + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Verify WebSocket handshake channel does not get intercepted'); +</script> diff --git a/testing/web-platform/tests/service-workers/service-worker/worker-interception.https.html b/testing/web-platform/tests/service-workers/service-worker/worker-interception.https.html new file mode 100644 index 000000000..3ec66a54b --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/worker-interception.https.html @@ -0,0 +1,155 @@ +<!DOCTYPE html> +<title>Service Worker: intercepting Worker script loads</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +promise_test(function(t) { + var worker_url = 'resources/dummy-synthesized-worker.js'; + var service_worker = 'resources/dummy-worker-interceptor.js'; + var scope = worker_url; + + return service_worker_unregister_and_register(t, service_worker, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return new Promise(function(resolve, reject) { + var w = new Worker(worker_url); + w.onmessage = function(e) { + resolve(e.data); + } + + w.onerror = function(e) { + reject(e.message); + } + }); + }) + .then(function(data) { + assert_equals(data, 'worker loading intercepted by service worker'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Verify worker script from uncontrolled document is intercepted by Service Worker'); + +promise_test(function(t) { + var worker_url = 'resources/dummy-same-origin-worker.js'; + var service_worker = 'resources/dummy-worker-interceptor.js'; + var scope = worker_url; + + return service_worker_unregister_and_register(t, service_worker, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return new Promise(function(resolve, reject) { + var w = new Worker(worker_url); + w.onmessage = function(e) { + resolve(e.data); + } + + w.onerror = function(e) { + reject(e.message); + } + }); + }) + .then(function(data) { + assert_equals(data, 'dummy-worker-script loaded'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Verify worker script intercepted by same-origin response succeeds'); + +promise_test(function(t) { + var worker_url = 'resources/dummy-cors-worker.js'; + var service_worker = 'resources/dummy-worker-interceptor.js'; + var scope = worker_url; + + return service_worker_unregister_and_register(t, service_worker, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return new Promise(function(resolve, reject) { + var w = new Worker(worker_url); + w.onmessage = function(e) { + resolve(e.data); + } + + w.onerror = function(e) { + reject(e.message); + } + }); + }) + .then(function(data) { + assert_equals(data, 'dummy-worker-script loaded'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Verify worker script intercepted by cors response succeeds'); + +promise_test(function(t) { + var worker_url = 'resources/dummy-no-cors-worker.js'; + var service_worker = 'resources/dummy-worker-interceptor.js'; + var scope = worker_url; + + return service_worker_unregister_and_register(t, service_worker, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { + return new Promise(function(resolve, reject) { + var w = new Worker(worker_url); + w.onmessage = function(e) { + resolve(e.data); + } + + w.onerror = function(e) { + reject(e); + return true; + } + }); + }) + .then(function(data) { + assert_unreached('intercepted no-cors worker load should fail'); + service_worker_unregister_and_done(t, scope); + }) + .catch(function(e) { + assert_true(true, 'intercepted no-cors worker load should fail'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Verify worker script intercepted by no-cors cross-origin response fails'); + +promise_test(function(t) { + var subdoc_url = 'resources/worker-interception-iframe.https.html?bypass'; + var service_worker = 'resources/worker-load-interceptor.js'; + var scope = 'resources/'; + + window.addEventListener('message', t.step_func(on_message), false); + function on_message(e) { + assert_equals(e.data.results, "This load was successfully intercepted."); + t.done(); + } + + return service_worker_unregister_and_register(t, service_worker, scope) + .then(function(r) { + return wait_for_state(t, r.installing, 'activated'); + }) + .then(function() { return with_iframe(subdoc_url); }) + .then(function(frame) { + return new Promise(function(resolve, reject) { + var channel = new MessageChannel(); + channel.port1.onmessage = function(e) { + frame.remove(); + resolve(e.data); + } + + frame.contentWindow.postMessage("GO", "*", [channel.port2]); + }); + }) + .then(function(data) { + assert_equals(data.results, 'finish'); + service_worker_unregister_and_done(t, scope); + }); + }, 'Verify worker loads from controlled document are intercepted by Service Worker'); + +</script> +</body> diff --git a/testing/web-platform/tests/service-workers/service-worker/xhr.https.html b/testing/web-platform/tests/service-workers/service-worker/xhr.https.html new file mode 100644 index 000000000..776e61db1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/xhr.https.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Service Worker: XHR doesn't exist</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/get-host-info.sub.js"></script> +<script src="resources/test-helpers.sub.js?pipe=sub"></script> +<script> + +async_test(function(t) { + var path = new URL(".", window.location).pathname + var url = 'resources/xhr.js'; + var scope = 'resources/blank.html?xhr'; + var host_info = get_host_info(); + var frameURL = host_info['HTTPS_ORIGIN'] + path + scope; + + service_worker_unregister_and_register(t, url, scope) + .then(function(registration) { + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(frameURL); }) + .then(function(frame) { + return new Promise(function(resolve, reject) { + function onMessage(e) { + assert_false(e.data.xhr); + frame.remove(); + service_worker_unregister_and_done(t, scope); + } + var channel = new MessageChannel(); + channel.port1.onmessage = t.step_func(onMessage); + frame.contentWindow.navigator.serviceWorker.controller.postMessage({port: channel.port2}, [channel.port2]); + }) + }) + .catch(unreached_rejection(t)); + }, 'Verify XHR does not exist'); +</script> |