diff options
Diffstat (limited to 'testing/web-platform/tests/service-workers')
337 files changed, 19208 insertions, 0 deletions
diff --git a/testing/web-platform/tests/service-workers/OWNERS b/testing/web-platform/tests/service-workers/OWNERS new file mode 100644 index 000000000..e210b8f2d --- /dev/null +++ b/testing/web-platform/tests/service-workers/OWNERS @@ -0,0 +1 @@ +@ehsan diff --git a/testing/web-platform/tests/service-workers/cache-storage/OWNERS b/testing/web-platform/tests/service-workers/cache-storage/OWNERS new file mode 100644 index 000000000..2e63dd220 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/OWNERS @@ -0,0 +1,2 @@ +@inexorabletash +@wanderview diff --git a/testing/web-platform/tests/service-workers/cache-storage/common.https.html b/testing/web-platform/tests/service-workers/cache-storage/common.https.html new file mode 100644 index 000000000..b00199844 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/common.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<title>Cache Storage: Verify that Window and Workers see same storage</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script> + +function wait_for_message(worker) { + return new Promise(function(resolve) { + worker.addEventListener('message', function listener(e) { + resolve(e.data); + worker.removeEventListener('message', listener); + }); + }); +} + +promise_test(function(t) { + var cache_name = 'common-test'; + return self.caches.delete(cache_name) + .then(function() { + var worker = new Worker('resources/common-worker.js'); + worker.postMessage({name: cache_name}); + return wait_for_message(worker); + }) + .then(function(message) { + return self.caches.open(cache_name); + }) + .then(function(cache) { + return Promise.all([ + cache.match('https://example.com/a'), + cache.match('https://example.com/b'), + cache.match('https://example.com/c') + ]); + }) + .then(function(responses) { + return Promise.all(responses.map( + function(response) { return response.text(); } + )); + }) + .then(function(bodies) { + assert_equals(bodies[0], 'a', + 'Body should match response put by worker'); + assert_equals(bodies[1], 'b', + 'Body should match response put by worker'); + assert_equals(bodies[2], 'c', + 'Body should match response put by worker'); + }); +}, 'Window sees cache puts by Worker'); + +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/blank.html b/testing/web-platform/tests/service-workers/cache-storage/resources/blank.html new file mode 100644 index 000000000..a3c3a4689 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/blank.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<title>Empty doc</title> diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/common-worker.js b/testing/web-platform/tests/service-workers/cache-storage/resources/common-worker.js new file mode 100644 index 000000000..d0e8544b5 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/common-worker.js @@ -0,0 +1,15 @@ +self.onmessage = function(e) { + var cache_name = e.data.name; + + self.caches.open(cache_name) + .then(function(cache) { + return Promise.all([ + cache.put('https://example.com/a', new Response('a')), + cache.put('https://example.com/b', new Response('b')), + cache.put('https://example.com/c', new Response('c')) + ]); + }) + .then(function() { + self.postMessage('ok'); + }); +}; diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-iframe.html b/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-iframe.html new file mode 100644 index 000000000..00702df9e --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-iframe.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Controlled frame for Cache API test with credentials</title> +<script> + +function xhr(url, username, password) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(), async = true; + xhr.open('GET', url, async, username, password); + xhr.send(); + xhr.onreadystatechange = function() { + if (xhr.readyState !== XMLHttpRequest.DONE) + return; + if (xhr.status === 200) { + resolve(xhr.responseText); + } else { + reject(new Error(xhr.statusText)); + } + }; + }); +} + +window.onmessage = function(e) { + Promise.all(e.data.map(function(item) { + return xhr(item.name, item.username, item.password); + })) + .then(function() { + navigator.serviceWorker.controller.postMessage('keys'); + navigator.serviceWorker.onmessage = function(e) { + window.parent.postMessage(e.data, '*'); + }; + }); +}; + +</script> +<body> +Hello? Yes, this is iframe. +</body> diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-worker.js b/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-worker.js new file mode 100644 index 000000000..43965b5fe --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/credentials-worker.js @@ -0,0 +1,59 @@ +var cache_name = 'credentials'; + +function assert_equals(actual, expected, message) { + if (!Object.is(actual, expected)) + throw Error(message + ': expected: ' + expected + ', actual: ' + actual); +} + +self.onfetch = function(e) { + if (!/\.txt$/.test(e.request.url)) return; + var content = e.request.url; + var cache; + e.respondWith( + self.caches.open(cache_name) + .then(function(result) { + cache = result; + return cache.put(e.request, new Response(content)); + }) + + .then(function() { return cache.match(e.request); }) + .then(function(result) { return result.text(); }) + .then(function(text) { + assert_equals(text, content, 'Cache.match() body should match'); + }) + + .then(function() { return cache.matchAll(e.request); }) + .then(function(results) { + assert_equals(results.length, 1, 'Should have one response'); + return results[0].text(); + }) + .then(function(text) { + assert_equals(text, content, 'Cache.matchAll() body should match'); + }) + + .then(function() { return self.caches.match(e.request); }) + .then(function(result) { return result.text(); }) + .then(function(text) { + assert_equals(text, content, 'CacheStorage.match() body should match'); + }) + + .then(function() { + return new Response('dummy'); + }) + ); +}; + +self.onmessage = function(e) { + if (e.data === 'keys') { + self.caches.open(cache_name) + .then(function(cache) { return cache.keys(); }) + .then(function(requests) { + var urls = requests.map(function(request) { return request.url; }); + self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + client.postMessage(urls); + }); + }); + }); + } +}; diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/fetch-status.py b/testing/web-platform/tests/service-workers/cache-storage/resources/fetch-status.py new file mode 100644 index 000000000..71f13ebc3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/fetch-status.py @@ -0,0 +1,2 @@ +def main(request, response): + return int(request.GET["status"]), [], "" diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/iframe.html b/testing/web-platform/tests/service-workers/cache-storage/resources/iframe.html new file mode 100644 index 000000000..a2f1e502b --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/iframe.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<title>ok</title> +<script> +window.onmessage = function(e) { + var id = e.data.id; + try { + var name = 'checkallowed'; + self.caches.open(name).then(function (cache) { + self.caches.delete(name); + window.parent.postMessage({id: id, result: 'allowed'}, '*'); + }).catch(function(e) { + window.parent.postMessage({id: id, result: 'denied', name: e.name, message: e.message}, '*'); + }); + } catch (e) { + window.parent.postMessage({id: id, result: 'unexpecteddenied', name: e.name, message: e.message}, '*'); + } +}; +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/simple.txt b/testing/web-platform/tests/service-workers/cache-storage/resources/simple.txt new file mode 100644 index 000000000..9e3cb91fb --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/simple.txt @@ -0,0 +1 @@ +a simple text file diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/test-helpers.js b/testing/web-platform/tests/service-workers/cache-storage/resources/test-helpers.js new file mode 100644 index 000000000..f4145e621 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/test-helpers.js @@ -0,0 +1,237 @@ +(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); +} + +// A set of Request/Response pairs to be used with prepopulated_cache_test(). +var simple_entries = [ + { + name: 'a', + request: new Request('http://example.com/a'), + response: new Response('') + }, + + { + name: 'b', + request: new Request('http://example.com/b'), + response: new Response('') + }, + + { + name: 'a_with_query', + request: new Request('http://example.com/a?q=r'), + response: new Response('') + }, + + { + name: 'A', + request: new Request('http://example.com/A'), + response: new Response('') + }, + + { + name: 'a_https', + request: new Request('https://example.com/a'), + response: new Response('') + }, + + { + name: 'a_org', + request: new Request('http://example.org/a'), + response: new Response('') + }, + + { + name: 'cat', + request: new Request('http://example.com/cat'), + response: new Response('') + }, + + { + name: 'catmandu', + request: new Request('http://example.com/catmandu'), + response: new Response('') + }, + + { + name: 'cat_num_lives', + request: new Request('http://example.com/cat?lives=9'), + response: new Response('') + }, + + { + name: 'cat_in_the_hat', + request: new Request('http://example.com/cat/in/the/hat'), + response: new Response('') + }, + + { + name: 'non_2xx_response', + request: new Request('http://example.com/non2xx'), + response: new Response('', {status: 404, statusText: 'nope'}) + }, + + { + name: 'error_response', + request: new Request('http://example.com/error'), + response: Response.error() + }, +]; + +// A set of Request/Response pairs to be used with prepopulated_cache_test(). +// These contain a mix of test cases that use Vary headers. +var vary_entries = [ + { + name: 'vary_cookie_is_cookie', + request: new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}}), + response: new Response('', + {headers: {'Vary': 'Cookies'}}) + }, + + { + name: 'vary_cookie_is_good', + request: new Request('http://example.com/c', + {headers: {'Cookies': 'is-good-enough-for-me'}}), + response: new Response('', + {headers: {'Vary': 'Cookies'}}) + }, + + { + name: 'vary_cookie_absent', + request: new Request('http://example.com/c'), + response: new Response('', + {headers: {'Vary': 'Cookies'}}) + } +]; + +// Run |test_function| with a Cache object and a map of entries. Prior to the +// call, the Cache is populated by cache entries from |entries|. The latter is +// expected to be an Object mapping arbitrary keys to objects of the form +// {request: <Request object>, response: <Response object>}. There's no +// guarantee on the order in which entries will be added to the cache. +// +// |test_function| should return a Promise that can be used with promise_test. +function prepopulated_cache_test(entries, test_function, description) { + cache_test(function(cache) { + var p = Promise.resolve(); + var hash = {}; + return Promise.all(entries.map(function(entry) { + hash[entry.name] = entry; + return cache.put(entry.request.clone(), + entry.response.clone()) + .catch(function(e) { + assert_unreached( + 'Test setup failed for entry ' + entry.name + ': ' + e); + }); + })) + .then(function() { + assert_equals(Object.keys(hash).length, entries.length); + }) + .then(function() { + return test_function(cache, hash); + }); + }, description); +} + +// Helper for testing with Headers objects. Compares Headers instances +// by serializing |expected| and |actual| to arrays and comparing. +function assert_header_equals(actual, expected, description) { + assert_class_string(actual, "Headers", description); + var header; + var actual_headers = []; + var expected_headers = []; + for (header of actual) + actual_headers.push(header[0] + ": " + header[1]); + for (header of expected) + expected_headers.push(header[0] + ": " + header[1]); + assert_array_equals(actual_headers, expected_headers, + description + " Headers differ."); +} + +// Helper for testing with Response objects. Compares simple +// attributes defined on the interfaces, as well as the headers. It +// does not compare the response bodies. +function assert_response_equals(actual, expected, description) { + assert_class_string(actual, "Response", description); + ["type", "url", "status", "ok", "statusText"].forEach(function(attribute) { + assert_equals(actual[attribute], expected[attribute], + description + " Attributes differ: " + attribute + "."); + }); + assert_header_equals(actual.headers, expected.headers, description); +} + +// Assert that the two arrays |actual| and |expected| contain the same +// set of Responses as determined by assert_response_equals. The order +// is not significant. +// +// |expected| is assumed to not contain any duplicates. +function assert_response_array_equivalent(actual, expected, description) { + assert_true(Array.isArray(actual), description); + assert_equals(actual.length, expected.length, description); + expected.forEach(function(expected_element) { + // assert_response_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_response_in_array(expected_element, actual, description); + }); +} + +// Asserts that two arrays |actual| and |expected| contain the same +// set of Responses as determined by assert_response_equals(). The +// corresponding elements must occupy corresponding indices in their +// respective arrays. +function assert_response_array_equals(actual, expected, description) { + assert_true(Array.isArray(actual), description); + assert_equals(actual.length, expected.length, description); + actual.forEach(function(value, index) { + assert_response_equals(value, expected[index], + description + " : object[" + index + "]"); + }); +} + +// Equivalent to assert_in_array, but uses assert_response_equals. +function assert_response_in_array(actual, expected_array, description) { + assert_true(expected_array.some(function(element) { + try { + assert_response_equals(actual, element); + return true; + } catch (e) { + return false; + } + }), description); +} diff --git a/testing/web-platform/tests/service-workers/cache-storage/resources/testharness-helpers.js b/testing/web-platform/tests/service-workers/cache-storage/resources/testharness-helpers.js new file mode 100644 index 000000000..e4885727b --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/resources/testharness-helpers.js @@ -0,0 +1,33 @@ +/* + * 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); + } + }); +} diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-add.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-add.js new file mode 100644 index 000000000..01fd605ad --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-add.js @@ -0,0 +1,238 @@ +if (self.importScripts) { + importScripts('/resources/testharness.js'); + importScripts('../resources/testharness-helpers.js'); + importScripts('../resources/test-helpers.js'); +} + +cache_test(function(cache) { + return assert_promise_rejects( + cache.add(), + new TypeError(), + 'Cache.add should throw a TypeError when no arguments are given.'); + }, 'Cache.add called with no arguments'); + +cache_test(function(cache) { + return cache.add('../resources/simple.txt') + .then(function(result) { + assert_equals(result, undefined, + 'Cache.add should resolve with undefined on success.'); + return cache.match('../resources/simple.txt'); + }) + .then(function(response) { + assert_class_string(response, 'Response', + 'Cache.add should put a resource in the cache.'); + return response.text(); + }) + .then(function(body) { + assert_equals(body, 'a simple text file\n', + 'Cache.add should retrieve the correct body.'); + }); + }, 'Cache.add called with relative URL specified as a string'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.add('javascript://this-is-not-http-mmkay'), + new TypeError(), + 'Cache.add should throw a TypeError for non-HTTP/HTTPS URLs.'); + }, 'Cache.add called with non-HTTP/HTTPS URL'); + +cache_test(function(cache) { + var request = new Request('../resources/simple.txt'); + return cache.add(request) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.add should resolve with undefined on success.'); + }); + }, 'Cache.add called with Request object'); + +cache_test(function(cache) { + var request = new Request('../resources/simple.txt', + {method: 'POST', body: 'This is a body.'}); + return assert_promise_rejects( + cache.add(request), + new TypeError(), + 'Cache.add should throw a TypeError for non-GET requests.'); + }, 'Cache.add called with POST request'); + +cache_test(function(cache) { + var request = new Request('../resources/simple.txt'); + return cache.add(request) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.add should resolve with undefined on success.'); + }) + .then(function() { + return cache.add(request); + }) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.add should resolve with undefined on success.'); + }); + }, 'Cache.add called twice with the same Request object'); + +cache_test(function(cache) { + var request = new Request('../resources/simple.txt'); + return request.text() + .then(function() { + assert_false(request.bodyUsed); + }) + .then(function() { + return cache.add(request); + }); + }, 'Cache.add with request with null body (not consumed)'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.add('this-does-not-exist-please-dont-create-it'), + new TypeError(), + 'Cache.add should reject if response is !ok'); + }, 'Cache.add with request that results in a status of 404'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.add('../resources/fetch-status.php?status=500'), + new TypeError(), + 'Cache.add should reject if response is !ok'); + }, 'Cache.add with request that results in a status of 500'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.addAll(), + new TypeError(), + 'Cache.addAll with no arguments should throw TypeError.'); + }, 'Cache.addAll with no arguments'); + +cache_test(function(cache) { + // Assumes the existence of ../resources/simple.txt and ../resources/blank.html + var urls = ['../resources/simple.txt', undefined, '../resources/blank.html']; + return assert_promise_rejects( + cache.addAll(), + new TypeError(), + 'Cache.addAll should throw TypeError for an undefined argument.'); + }, 'Cache.addAll with a mix of valid and undefined arguments'); + +cache_test(function(cache) { + return cache.addAll([]) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.addAll should resolve with undefined on ' + + 'success.'); + return cache.keys(); + }) + .then(function(result) { + assert_equals(result.length, 0, + 'There should be no entry in the cache.'); + }); + }, 'Cache.addAll with an empty array'); + +cache_test(function(cache) { + // Assumes the existence of ../resources/simple.txt and + // ../resources/blank.html + var urls = ['../resources/simple.txt', + self.location.href, + '../resources/blank.html']; + return cache.addAll(urls) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.addAll should resolve with undefined on ' + + 'success.'); + return Promise.all( + urls.map(function(url) { return cache.match(url); })); + }) + .then(function(responses) { + assert_class_string( + responses[0], 'Response', + 'Cache.addAll should put a resource in the cache.'); + assert_class_string( + responses[1], 'Response', + 'Cache.addAll should put a resource in the cache.'); + assert_class_string( + responses[2], 'Response', + 'Cache.addAll should put a resource in the cache.'); + return Promise.all( + responses.map(function(response) { return response.text(); })); + }) + .then(function(bodies) { + assert_equals( + bodies[0], 'a simple text file\n', + 'Cache.add should retrieve the correct body.'); + assert_equals( + bodies[2], '<!DOCTYPE html>\n<title>Empty doc</title>\n', + 'Cache.add should retrieve the correct body.'); + }); + }, 'Cache.addAll with string URL arguments'); + +cache_test(function(cache) { + // Assumes the existence of ../resources/simple.txt and + // ../resources/blank.html + var urls = ['../resources/simple.txt', + self.location.href, + '../resources/blank.html']; + var requests = urls.map(function(url) { + return new Request(url); + }); + return cache.addAll(requests) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.addAll should resolve with undefined on ' + + 'success.'); + return Promise.all( + urls.map(function(url) { return cache.match(url); })); + }) + .then(function(responses) { + assert_class_string( + responses[0], 'Response', + 'Cache.addAll should put a resource in the cache.'); + assert_class_string( + responses[1], 'Response', + 'Cache.addAll should put a resource in the cache.'); + assert_class_string( + responses[2], 'Response', + 'Cache.addAll should put a resource in the cache.'); + return Promise.all( + responses.map(function(response) { return response.text(); })); + }) + .then(function(bodies) { + assert_equals( + bodies[0], 'a simple text file\n', + 'Cache.add should retrieve the correct body.'); + assert_equals( + bodies[2], '<!DOCTYPE html>\n<title>Empty doc</title>\n', + 'Cache.add should retrieve the correct body.'); + }); + }, 'Cache.addAll with Request arguments'); + +cache_test(function(cache) { + // Assumes that ../resources/simple.txt and ../resources/blank.html exist. + // The second resource does not. + var urls = ['../resources/simple.txt', + 'this-resource-should-not-exist', + '../resources/blank.html']; + var requests = urls.map(function(url) { + return new Request(url); + }); + return assert_promise_rejects( + cache.addAll(requests), + new TypeError(), + 'Cache.addAll should reject with TypeError if any request fails') + .then(function() { + return Promise.all(urls.map(function(url) { return cache.match(url); })); + }) + .then(function(matches) { + assert_array_equals( + matches, + [undefined, undefined, undefined], + 'If any response fails, no response should be added to cache'); + }); + }, 'Cache.addAll with a mix of succeeding and failing requests'); + +cache_test(function(cache) { + var request = new Request('../resources/simple.txt'); + return assert_promise_rejects( + cache.addAll([request, request]), + 'InvalidStateError', + 'Cache.addAll should throw InvalidStateError if the same request is added ' + + 'twice.'); + }, 'Cache.addAll called with the same Request object specified twice'); + +done(); diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-delete.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-delete.js new file mode 100644 index 000000000..75a474c2b --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-delete.js @@ -0,0 +1,96 @@ +if (self.importScripts) { + importScripts('/resources/testharness.js'); + importScripts('../resources/testharness-helpers.js'); + importScripts('../resources/test-helpers.js'); +} + +var test_url = 'https://example.com/foo'; + +// Construct a generic Request object. The URL is |test_url|. All other fields +// are defaults. +function new_test_request() { + return new Request(test_url); +} + +// Construct a generic Response object. +function new_test_response() { + return new Response('Hello world!', { status: 200 }); +} + +cache_test(function(cache) { + return assert_promise_rejects( + cache.delete(), + new TypeError(), + 'Cache.delete should reject with a TypeError when called with no ' + + 'arguments.'); + }, 'Cache.delete with no arguments'); + +cache_test(function(cache) { + return cache.put(new_test_request(), new_test_response()) + .then(function() { + return cache.delete(test_url); + }) + .then(function(result) { + assert_true(result, + 'Cache.delete should resolve with "true" if an entry ' + + 'was successfully deleted.'); + return cache.match(test_url); + }) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.delete should remove matching entries from cache.'); + }); + }, 'Cache.delete called with a string URL'); + +cache_test(function(cache) { + var request = new Request(test_url); + return cache.put(request, new_test_response()) + .then(function() { + return cache.delete(request); + }) + .then(function(result) { + assert_true(result, + 'Cache.delete should resolve with "true" if an entry ' + + 'was successfully deleted.'); + }); + }, 'Cache.delete called with a Request object'); + +cache_test(function(cache) { + return cache.delete(test_url) + .then(function(result) { + assert_false(result, + 'Cache.delete should resolve with "false" if there ' + + 'are no matching entries.'); + }); + }, 'Cache.delete with a non-existent entry'); + +var cache_entries = { + a: { + request: new Request('http://example.com/abc'), + response: new Response('') + }, + + b: { + request: new Request('http://example.com/b'), + response: new Response('') + }, + + a_with_query: { + request: new Request('http://example.com/abc?q=r'), + response: new Response('') + } +}; + +function prepopulated_cache_test(test_function, description) { + cache_test(function(cache) { + return Promise.all(Object.keys(cache_entries).map(function(k) { + return cache.put(cache_entries[k].request.clone(), + cache_entries[k].response.clone()); + })) + .then(function() { + return test_function(cache); + }); + }, description); +} + +done(); diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-match.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-match.js new file mode 100644 index 000000000..716bfe5a6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-match.js @@ -0,0 +1,193 @@ +if (self.importScripts) { + importScripts('/resources/testharness.js'); + importScripts('../resources/testharness-helpers.js'); + importScripts('../resources/test-helpers.js'); +} + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match('not-present-in-the-cache') + .then(function(result) { + assert_equals(result, undefined, + 'Cache.match failures should resolve with undefined.'); + }); + }, 'Cache.match with no matching entries'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.a.request.url) + .then(function(result) { + assert_response_equals(result, entries.a.response, + 'Cache.match should match by URL.'); + }); + }, 'Cache.match with URL'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.a.request) + .then(function(result) { + assert_response_equals(result, entries.a.response, + 'Cache.match should match by Request.'); + }); + }, 'Cache.match with Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(new Request(entries.a.request.url)) + .then(function(result) { + assert_response_equals(result, entries.a.response, + 'Cache.match should match by Request.'); + }); + }, 'Cache.match with new Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.a.request, + {ignoreSearch: true}) + .then(function(result) { + assert_response_in_array( + result, + [ + entries.a.response, + entries.a_with_query.response + ], + 'Cache.match with ignoreSearch should ignore the ' + + 'search parameters of cached request.'); + }); + }, + 'Cache.match with ignoreSearch option (request with no search ' + + 'parameters)'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.a_with_query.request, + {ignoreSearch: true}) + .then(function(result) { + assert_response_in_array( + result, + [ + entries.a.response, + entries.a_with_query.response + ], + 'Cache.match with ignoreSearch should ignore the ' + + 'search parameters of request.'); + }); + }, + 'Cache.match with ignoreSearch option (request with search parameter)'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.cat.request.url + '#mouse') + .then(function(result) { + assert_response_equals(result, entries.cat.response, + 'Cache.match should ignore URL fragment.'); + }); + }, 'Cache.match with URL containing fragment'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match('http') + .then(function(result) { + assert_equals( + result, undefined, + 'Cache.match should treat query as a URL and not ' + + 'just a string fragment.'); + }); + }, 'Cache.match with string fragment "http" as query'); + +prepopulated_cache_test(vary_entries, function(cache, entries) { + return cache.match('http://example.com/c') + .then(function(result) { + assert_response_in_array( + result, + [ + entries.vary_cookie_absent.response + ], + 'Cache.match should honor "Vary" header.'); + }); + }, 'Cache.match with responses containing "Vary" header'); + +cache_test(function(cache) { + var request = new Request('http://example.com'); + var response; + var request_url = new URL('../resources/simple.txt', location.href).href; + return fetch(request_url) + .then(function(fetch_result) { + response = fetch_result; + assert_equals( + response.url, request_url, + '[https://fetch.spec.whatwg.org/#dom-response-url] ' + + 'Reponse.url should return the URL of the response.'); + return cache.put(request, response.clone()); + }) + .then(function() { + return cache.match(request.url); + }) + .then(function(result) { + assert_response_equals( + result, response, + 'Cache.match should return a Response object that has the same ' + + 'properties as the stored response.'); + return cache.match(response.url); + }) + .then(function(result) { + assert_equals( + result, undefined, + 'Cache.match should not match cache entry based on response URL.'); + }); + }, 'Cache.match with Request and Response objects with different URLs'); + +cache_test(function(cache) { + var request_url = new URL('../resources/simple.txt', location.href).href; + return fetch(request_url) + .then(function(fetch_result) { + return cache.put(new Request(request_url), fetch_result); + }) + .then(function() { + return cache.match(request_url); + }) + .then(function(result) { + return result.text(); + }) + .then(function(body_text) { + assert_equals(body_text, 'a simple text file\n', + 'Cache.match should return a Response object with a ' + + 'valid body.'); + }) + .then(function() { + return cache.match(request_url); + }) + .then(function(result) { + return result.text(); + }) + .then(function(body_text) { + assert_equals(body_text, 'a simple text file\n', + 'Cache.match should return a Response object with a ' + + 'valid body each time it is called.'); + }); + }, 'Cache.match invoked multiple times for the same Request/Response'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + var request = new Request(entries.a.request.clone(), {method: 'POST'}); + return cache.match(request) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.match should not find a match'); + }); + }, 'Cache.match with POST Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + var response = entries.non_2xx_response.response; + return cache.match(entries.non_2xx_response.request.url) + .then(function(result) { + assert_response_equals( + result, entries.non_2xx_response.response, + 'Cache.match should return a Response object that has the ' + + 'same properties as a stored non-2xx response.'); + }); + }, 'Cache.match with a non-2xx Response'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + var response = entries.error_response.response; + return cache.match(entries.error_response.request.url) + .then(function(result) { + assert_response_equals( + result, entries.error_response.response, + 'Cache.match should return a Response object that has the ' + + 'same properties as a stored network error response.'); + }); + }, 'Cache.match with a network error Response'); + +done(); diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-matchAll.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-matchAll.js new file mode 100644 index 000000000..2bc661a5c --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-matchAll.js @@ -0,0 +1,154 @@ +if (self.importScripts) { + importScripts('/resources/testharness.js'); + importScripts('../resources/testharness-helpers.js'); + importScripts('../resources/test-helpers.js'); +} + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll('not-present-in-the-cache') + .then(function(result) { + assert_response_array_equivalent( + result, [], + 'Cache.matchAll should resolve with an empty array on failure.'); + }); + }, 'Cache.matchAll with no matching entries'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a.request.url) + .then(function(result) { + assert_response_array_equals(result, [entries.a.response], + 'Cache.matchAll should match by URL.'); + }); + }, 'Cache.matchAll with URL'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a.request) + .then(function(result) { + assert_response_array_equals( + result, [entries.a.response], + 'Cache.matchAll should match by Request.'); + }); + }, 'Cache.matchAll with Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(new Request(entries.a.request.url)) + .then(function(result) { + assert_response_array_equals( + result, [entries.a.response], + 'Cache.matchAll should match by Request.'); + }); + }, 'Cache.matchAll with new Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a.request, + {ignoreSearch: true}) + .then(function(result) { + assert_response_array_equivalent( + result, + [ + entries.a.response, + entries.a_with_query.response + ], + 'Cache.matchAll with ignoreSearch should ignore the ' + + 'search parameters of cached request.'); + }); + }, + 'Cache.matchAll with ignoreSearch option (request with no search ' + + 'parameters)'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a_with_query.request, + {ignoreSearch: true}) + .then(function(result) { + assert_response_array_equivalent( + result, + [ + entries.a.response, + entries.a_with_query.response + ], + 'Cache.matchAll with ignoreSearch should ignore the ' + + 'search parameters of request.'); + }); + }, + 'Cache.matchAll with ignoreSearch option (request with search parameter)'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.cat.request.url + '#mouse') + .then(function(result) { + assert_response_array_equivalent( + result, + [ + entries.cat.response, + ], + 'Cache.matchAll should ignore URL fragment.'); + }); + }, 'Cache.matchAll with URL containing fragment'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll('http') + .then(function(result) { + assert_response_array_equivalent( + result, [], + 'Cache.matchAll should treat query as a URL and not ' + + 'just a string fragment.'); + }); + }, 'Cache.matchAll with string fragment "http" as query'); + +prepopulated_cache_test(vary_entries, function(cache, entries) { + return cache.matchAll('http://example.com/c') + .then(function(result) { + assert_response_array_equivalent( + result, + [ + entries.vary_cookie_absent.response + ], + 'Cache.matchAll should exclude matches if a vary header is ' + + 'missing in the query request, but is present in the cached ' + + 'request.'); + }) + + .then(function() { + return cache.matchAll( + new Request('http://example.com/c', + {headers: {'Cookies': 'none-of-the-above'}})); + }) + .then(function(result) { + assert_response_array_equivalent( + result, + [ + ], + 'Cache.matchAll should exclude matches if a vary header is ' + + 'missing in the cached request, but is present in the query ' + + 'request.'); + }) + + .then(function() { + return cache.matchAll( + new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}})); + }) + .then(function(result) { + assert_response_array_equivalent( + result, + [entries.vary_cookie_is_cookie.response], + 'Cache.matchAll should match the entire header if a vary header ' + + 'is present in both the query and cached requests.'); + }); + }, 'Cache.matchAll with responses containing "Vary" header'); + +prepopulated_cache_test(vary_entries, function(cache, entries) { + return cache.matchAll('http://example.com/c', + {ignoreVary: true}) + .then(function(result) { + assert_response_array_equivalent( + result, + [ + entries.vary_cookie_is_cookie.response, + entries.vary_cookie_is_good.response, + entries.vary_cookie_absent.response + ], + 'Cache.matchAll should honor "ignoreVary" parameter.'); + }); + }, 'Cache.matchAll with "ignoreVary" parameter'); + +done(); diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-put.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-put.js new file mode 100644 index 000000000..bcc8541ad --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-put.js @@ -0,0 +1,305 @@ +if (self.importScripts) { + importScripts('/resources/testharness.js'); + importScripts('../resources/testharness-helpers.js'); + importScripts('../resources/test-helpers.js'); +} + +var test_url = 'https://example.com/foo'; +var test_body = 'Hello world!'; + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response(test_body); + return cache.put(request, response) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.put should resolve with undefined on success.'); + }); + }, 'Cache.put called with simple Request and Response'); + +cache_test(function(cache) { + var test_url = new URL('../resources/simple.txt', location.href).href; + var request = new Request(test_url); + var response; + return fetch(test_url) + .then(function(fetch_result) { + response = fetch_result.clone(); + return cache.put(request, fetch_result); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.put should update the cache with ' + + 'new request and response.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, 'a simple text file\n', + 'Cache.put should store response body.'); + }); + }, 'Cache.put called with Request and Response from fetch()'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response(test_body); + assert_false(request.bodyUsed, + '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' + + 'Request.bodyUsed should be initially false.'); + return cache.put(request, response) + .then(function() { + assert_false(request.bodyUsed, + 'Cache.put should not mark empty request\'s body used'); + }); + }, 'Cache.put with Request without a body'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response(); + assert_false(response.bodyUsed, + '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' + + 'Response.bodyUsed should be initially false.'); + return cache.put(request, response) + .then(function() { + assert_false(response.bodyUsed, + 'Cache.put should not mark empty response\'s body used'); + }); + }, 'Cache.put with Response without a body'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response(test_body); + return cache.put(request, response.clone()) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.put should update the cache with ' + + 'new Request and Response.'); + }); + }, 'Cache.put with a Response containing an empty URL'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response('', { + status: 200, + headers: [['Content-Type', 'text/plain']] + }); + return cache.put(request, response) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_equals(result.status, 200, 'Cache.put should store status.'); + assert_equals(result.headers.get('Content-Type'), 'text/plain', + 'Cache.put should store headers.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, '', + 'Cache.put should store response body.'); + }); + }, 'Cache.put with an empty response body'); + +cache_test(function(cache) { + var test_url = new URL('../resources/fetch-status.py?status=500', location.href).href; + var request = new Request(test_url); + var response; + return fetch(test_url) + .then(function(fetch_result) { + assert_equals(fetch_result.status, 500, + 'Test framework error: The status code should be 500.'); + response = fetch_result.clone(); + return cache.put(request, fetch_result); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.put should update the cache with ' + + 'new request and response.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, '', + 'Cache.put should store response body.'); + }); + }, 'Cache.put with HTTP 500 response'); + +cache_test(function(cache) { + var alternate_response_body = 'New body'; + var alternate_response = new Response(alternate_response_body, + { statusText: 'New status' }); + return cache.put(new Request(test_url), + new Response('Old body', { statusText: 'Old status' })) + .then(function() { + return cache.put(new Request(test_url), alternate_response.clone()); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, alternate_response, + 'Cache.put should replace existing ' + + 'response with new response.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, alternate_response_body, + 'Cache put should store new response body.'); + }); + }, 'Cache.put called twice with matching Requests and different Responses'); + +cache_test(function(cache) { + var first_url = test_url; + var second_url = first_url + '#(O_o)'; + var alternate_response_body = 'New body'; + var alternate_response = new Response(alternate_response_body, + { statusText: 'New status' }); + return cache.put(new Request(first_url), + new Response('Old body', { statusText: 'Old status' })) + .then(function() { + return cache.put(new Request(second_url), alternate_response.clone()); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, alternate_response, + 'Cache.put should replace existing ' + + 'response with new response.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, alternate_response_body, + 'Cache put should store new response body.'); + }); + }, 'Cache.put called twice with request URLs that differ only by a fragment'); + +cache_test(function(cache) { + var url = 'http://example.com/foo'; + return cache.put(url, new Response('some body')) + .then(function() { return cache.match(url); }) + .then(function(response) { return response.text(); }) + .then(function(body) { + assert_equals(body, 'some body', + 'Cache.put should accept a string as request.'); + }); + }, 'Cache.put with a string request'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.put(new Request(test_url), 'Hello world!'), + new TypeError(), + 'Cache.put should only accept a Response object as the response.'); + }, 'Cache.put with an invalid response'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.put(new Request('file:///etc/passwd'), + new Response(test_body)), + new TypeError(), + 'Cache.put should reject non-HTTP/HTTPS requests with a TypeError.'); + }, 'Cache.put with a non-HTTP/HTTPS request'); + +cache_test(function(cache) { + var response = new Response(test_body); + return cache.put(new Request('relative-url'), response.clone()) + .then(function() { + return cache.match(new URL('relative-url', location.href).href); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.put should accept a relative URL ' + + 'as the request.'); + }); + }, 'Cache.put with a relative URL'); + +cache_test(function(cache) { + var request = new Request('http://example.com/foo', { method: 'HEAD' }); + return assert_promise_rejects( + cache.put(request, new Response(test_body)), + new TypeError(), + 'Cache.put should throw a TypeError for non-GET requests.'); + }, 'Cache.put with a non-GET request'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.put(new Request(test_url), null), + new TypeError(), + 'Cache.put should throw a TypeError for a null response.'); + }, 'Cache.put with a null response'); + +cache_test(function(cache) { + var request = new Request(test_url, {method: 'POST', body: test_body}); + return assert_promise_rejects( + cache.put(request, new Response(test_body)), + new TypeError(), + 'Cache.put should throw a TypeError for a POST request.'); + }, 'Cache.put with a POST request'); + +cache_test(function(cache) { + var response = new Response(test_body); + assert_false(response.bodyUsed, + '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' + + 'Response.bodyUsed should be initially false.'); + return response.text().then(function() { + assert_true( + response.bodyUsed, + '[https://fetch.spec.whatwg.org/#concept-body-consume-body] ' + + 'The text() method should make the body disturbed.'); + var request = new Request(test_url); + return cache.put(request, response).then(() => { + assert_unreached('cache.put should be rejected'); + }, () => {}); + }); + }, 'Cache.put with a used response body'); + +cache_test(function(cache) { + var response = new Response(test_body); + return cache.put(new Request(test_url), response) + .then(function() { + assert_throws(new TypeError(), () => response.body.getReader()); + }); + }, 'getReader() after Cache.put'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.put(new Request(test_url), + new Response(test_body, { headers: { VARY: '*' }})), + new TypeError(), + 'Cache.put should reject VARY:* Responses with a TypeError.'); + }, 'Cache.put with a VARY:* Response'); + +cache_test(function(cache) { + return assert_promise_rejects( + cache.put(new Request(test_url), + new Response(test_body, + { headers: { VARY: 'Accept-Language,*' }})), + new TypeError(), + 'Cache.put should reject Responses with an embedded VARY:* with a ' + + 'TypeError.'); + }, 'Cache.put with an embedded VARY:* Response'); + +cache_test(function(cache) { + var url = 'foo.html'; + var redirectURL = 'http://example.com/foo-bar.html'; + var redirectResponse = Response.redirect(redirectURL); + assert_equals(redirectResponse.headers.get('Location'), redirectURL, + 'Response.redirect() should set Location header.'); + return cache.put(url, redirectResponse.clone()) + .then(function() { + return cache.match(url); + }) + .then(function(response) { + assert_response_equals(response, redirectResponse, + 'Redirect response is reproduced by the Cache API'); + assert_equals(response.headers.get('Location'), redirectURL, + 'Location header is preserved by Cache API.'); + }); + }, 'Cache.put should store Response.redirect() correctly'); + +done(); diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-keys.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-keys.js new file mode 100644 index 000000000..4d7bc623f --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-keys.js @@ -0,0 +1,36 @@ +if (self.importScripts) { + importScripts('/resources/testharness.js'); + importScripts('../resources/testharness-helpers.js'); + importScripts('../resources/test-helpers.js'); +} + +var test_cache_list = + ['', 'example', 'Another cache name', 'A', 'a', 'ex ample']; + +promise_test(function(test) { + return self.caches.keys() + .then(function(keys) { + assert_true(Array.isArray(keys), + 'CacheStorage.keys should return an Array.'); + return Promise.all(keys.map(function(key) { + return self.caches.delete(key); + })); + }) + .then(function() { + return Promise.all(test_cache_list.map(function(key) { + return self.caches.open(key); + })); + }) + + .then(function() { return self.caches.keys(); }) + .then(function(keys) { + assert_true(Array.isArray(keys), + 'CacheStorage.keys should return an Array.'); + assert_array_equals(keys, + test_cache_list, + 'CacheStorage.keys should only return ' + + 'existing caches.'); + }); + }, 'CacheStorage keys'); + +done(); diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-match.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-match.js new file mode 100644 index 000000000..0052e43ca --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage-match.js @@ -0,0 +1,124 @@ +if (self.importScripts) { + importScripts('/resources/testharness.js'); + importScripts('../resources/testharness-helpers.js'); + importScripts('../resources/test-helpers.js'); +} + +(function() { + var next_index = 1; + + // Returns a transaction (request, response, and url) for a unique URL. + function create_unique_transaction(test) { + var uniquifier = String(next_index++); + var url = 'http://example.com/' + uniquifier; + + return { + request: new Request(url), + response: new Response('hello'), + url: url + }; + } + + self.create_unique_transaction = create_unique_transaction; +})(); + +cache_test(function(cache) { + var transaction = create_unique_transaction(); + + return cache.put(transaction.request.clone(), transaction.response.clone()) + .then(function() { + return self.caches.match(transaction.request); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should not have changed.'); + }); +}, 'CacheStorageMatch with no cache name provided'); + +cache_test(function(cache) { + var transaction = create_unique_transaction(); + + var test_cache_list = ['a', 'b', 'c']; + return cache.put(transaction.request.clone(), transaction.response.clone()) + .then(function() { + return Promise.all(test_cache_list.map(function(key) { + return self.caches.open(key); + })); + }) + .then(function() { + return self.caches.match(transaction.request); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should not have changed.'); + }); +}, 'CacheStorageMatch from one of many caches'); + +promise_test(function(test) { + var transaction = create_unique_transaction(); + + var test_cache_list = ['x', 'y', 'z']; + return Promise.all(test_cache_list.map(function(key) { + return self.caches.open(key); + })) + .then(function() { return caches.open('x'); }) + .then(function(cache) { + return cache.put(transaction.request.clone(), + transaction.response.clone()); + }) + .then(function() { + return self.caches.match(transaction.request, {cacheName: 'x'}); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should not have changed.'); + }) + .then(function() { + return self.caches.match(transaction.request, {cacheName: 'y'}); + }) + .then(function(response) { + assert_equals(response, undefined, + 'Cache y should not have a response for the request.'); + }); +}, 'CacheStorageMatch from one of many caches by name'); + +cache_test(function(cache) { + var transaction = create_unique_transaction(); + return cache.put(transaction.url, transaction.response.clone()) + .then(function() { + return self.caches.match(transaction.request); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should not have changed.'); + }); +}, 'CacheStorageMatch a string request'); + +promise_test(function(test) { + var transaction = create_unique_transaction(); + return self.caches.match(transaction.request) + .then(function(response) { + assert_equals(response, undefined, + 'The response should not be found.'); + }) +}, 'CacheStorageMatch with no cached entry'); + +promise_test(function(test) { + var transaction = create_unique_transaction(); + return self.caches.has('foo') + .then(function(has_foo) { + assert_false(has_foo, "The cache should not exist."); + return self.caches.match(transaction.request, {cacheName: 'foo'}); + }) + .then(function(response) { + assert_equals(response, undefined, + 'The match with bad cache name should resolve to ' + + 'undefined.'); + return self.caches.has('foo'); + }) + .then(function(has_foo) { + assert_false(has_foo, "The cache should still not exist."); + }) +}, 'CacheStorageMatch with no caches available but name provided'); + +done(); diff --git a/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage.js b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage.js new file mode 100644 index 000000000..594b01b58 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/script-tests/cache-storage.js @@ -0,0 +1,197 @@ +if (self.importScripts) { + importScripts('/resources/testharness.js'); + importScripts('../resources/testharness-helpers.js'); + importScripts('../resources/test-helpers.js'); +} + +promise_test(function(t) { + var cache_name = 'cache-storage/foo'; + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(cache) { + assert_true(cache instanceof Cache, + 'CacheStorage.open should return a Cache.'); + }); + }, 'CacheStorage.open'); + +promise_test(function(t) { + // Note that this test may collide with other tests running in the same + // origin that also uses an empty cache name. + var cache_name = ''; + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(cache) { + assert_true(cache instanceof Cache, + 'CacheStorage.open should accept an empty name.'); + }); + }, 'CacheStorage.open with an empty name'); + +promise_test(function(t) { + return assert_promise_rejects( + self.caches.open(), + new TypeError(), + 'CacheStorage.open should throw TypeError if called with no arguments.'); + }, 'CacheStorage.open with no arguments'); + +promise_test(function(t) { + var test_cases = [ + { + name: 'cache-storage/lowercase', + should_not_match: + [ + 'cache-storage/Lowercase', + ' cache-storage/lowercase', + 'cache-storage/lowercase ' + ] + }, + { + name: 'cache-storage/has a space', + should_not_match: + [ + 'cache-storage/has' + ] + }, + { + name: 'cache-storage/has\000_in_the_name', + should_not_match: + [ + 'cache-storage/has', + 'cache-storage/has_in_the_name' + ] + } + ]; + return Promise.all(test_cases.map(function(testcase) { + var cache_name = testcase.name; + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function() { + return self.caches.has(cache_name); + }) + .then(function(result) { + assert_true(result, + 'CacheStorage.has should return true for existing ' + + 'cache.'); + }) + .then(function() { + return Promise.all( + testcase.should_not_match.map(function(cache_name) { + return self.caches.has(cache_name) + .then(function(result) { + assert_false(result, + 'CacheStorage.has should only perform ' + + 'exact matches on cache names.'); + }); + })); + }) + .then(function() { + return self.caches.delete(cache_name); + }); + })); + }, 'CacheStorage.has with existing cache'); + +promise_test(function(t) { + return self.caches.has('cheezburger') + .then(function(result) { + assert_false(result, + 'CacheStorage.has should return false for ' + + 'nonexistent cache.'); + }); + }, 'CacheStorage.has with nonexistent cache'); + +promise_test(function(t) { + var cache_name = 'cache-storage/open'; + var cache; + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(result) { + cache = result; + }) + .then(function() { + return cache.add('../resources/simple.txt'); + }) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(result) { + assert_true(result instanceof Cache, + 'CacheStorage.open should return a Cache object'); + assert_not_equals(result, cache, + 'CacheStorage.open should return a new Cache ' + + 'object each time its called.'); + return Promise.all([cache.keys(), result.keys()]); + }) + .then(function(results) { + var expected_urls = results[0].map(function(r) { return r.url }); + var actual_urls = results[1].map(function(r) { return r.url }); + assert_array_equals(actual_urls, expected_urls, + 'CacheStorage.open should return a new Cache ' + + 'object for the same backing store.'); + }); + }, 'CacheStorage.open with existing cache'); + +promise_test(function(t) { + var cache_name = 'cache-storage/delete'; + + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function() { return self.caches.delete(cache_name); }) + .then(function(result) { + assert_true(result, + 'CacheStorage.delete should return true after ' + + 'deleting an existing cache.'); + }) + + .then(function() { return self.caches.has(cache_name); }) + .then(function(cache_exists) { + assert_false(cache_exists, + 'CacheStorage.has should return false after ' + + 'fulfillment of CacheStorage.delete promise.'); + }); + }, 'CacheStorage.delete with existing cache'); + +promise_test(function(t) { + return self.caches.delete('cheezburger') + .then(function(result) { + assert_false(result, + 'CacheStorage.delete should return false for a ' + + 'nonexistent cache.'); + }); + }, 'CacheStorage.delete with nonexistent cache'); + +promise_test(function(t) { + var bad_name = 'unpaired\uD800'; + var converted_name = 'unpaired\uFFFD'; // Don't create cache with this name. + return self.caches.has(converted_name) + .then(function(cache_exists) { + assert_false(cache_exists, + 'Test setup failure: cache should not exist'); + }) + .then(function() { return self.caches.open(bad_name); }) + .then(function() { return self.caches.keys(); }) + .then(function(keys) { + assert_true(keys.indexOf(bad_name) !== -1, + 'keys should include cache with bad name'); + }) + .then(function() { return self.caches.has(bad_name); }) + .then(function(cache_exists) { + assert_true(cache_exists, + 'CacheStorage names should be not be converted.'); + }) + .then(function() { return self.caches.has(converted_name); }) + .then(function(cache_exists) { + assert_false(cache_exists, + 'CacheStorage names should be not be converted.'); + }); + }, 'CacheStorage names are DOMStrings not USVStrings'); + +done(); diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-add.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-add.https.html new file mode 100644 index 000000000..232de668e --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-add.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Cache.add and Cache.addAll</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-add"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<script> +service_worker_test('../script-tests/cache-add.js'); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-delete.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-delete.https.html new file mode 100644 index 000000000..6ff27be30 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-delete.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Cache.delete</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-delete"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<script> +service_worker_test('../script-tests/cache-delete.js'); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-match.https.html new file mode 100644 index 000000000..81db70943 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-match.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Cache.match</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-match"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<script> +service_worker_test('../script-tests/cache-match.js'); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-matchAll.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-matchAll.https.html new file mode 100644 index 000000000..f1a38ff07 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-matchAll.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Cache.matchAll</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-matchall"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<script> +service_worker_test('../script-tests/cache-matchAll.js'); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-put.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-put.https.html new file mode 100644 index 000000000..961602386 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-put.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Cache.put</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-put"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<script> +service_worker_test('../script-tests/cache-put.js'); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html new file mode 100644 index 000000000..adde95eb8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>CacheStorage.keys</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<script> +service_worker_test('../script-tests/cache-storage-keys.js'); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-match.https.html new file mode 100644 index 000000000..fef14074d --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage-match.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>CacheStorage.match</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage-match"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<script> +service_worker_test('../script-tests/cache-storage-match.js'); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage.https.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage.https.html new file mode 100644 index 000000000..3934258f2 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/cache-storage.https.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>CacheStorage</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<script> +service_worker_test('../script-tests/cache-storage.js'); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/serviceworker/credentials.html b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/credentials.html new file mode 100644 index 000000000..7bc494eb1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/serviceworker/credentials.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Cache Storage: Verify credentials are respected by Cache operations</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../service-worker/resources/test-helpers.sub.js"></script> +<style>iframe { display: none; }</style> +<script> + +var worker = "../resources/credentials-worker.js"; +var scope = "../resources/credentials-iframe.html"; +promise_test(function(t) { + return self.caches.delete('credentials') + .then(function() { + 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(frame) { + frame.contentWindow.postMessage([ + {name: 'file.txt', username: 'aa', password: 'bb'}, + {name: 'file.txt', username: 'cc', password: 'dd'}, + {name: 'file.txt'} + ], '*'); + return new Promise(function(resolve, reject) { + window.onmessage = t.step_func(function(e) { + resolve(e.data); + }); + }); + }) + .then(function(data) { + assert_equals(data.length, 3, 'three entries should be present'); + assert_equals(data.filter(function(url) { return /@/.test(url); }).length, 2, + 'two entries should contain credentials'); + assert_true(data.some(function(url) { return /aa:bb@/.test(url); }), + 'entry with credentials aa:bb should be present'); + assert_true(data.some(function(url) { return /cc:dd@/.test(url); }), + 'entry with credentials cc:dd should be present'); + }); +}, "Cache API matching includes credentials"); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-add.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-add.https.html new file mode 100644 index 000000000..d9945f9ae --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-add.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache Storage: Cache.add and Cache.addAll</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-add"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script src="../resources/test-helpers.js"></script> +<script src="../script-tests/cache-add.js"></script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-delete.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-delete.https.html new file mode 100644 index 000000000..735d2cdb4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-delete.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache Storage: Cache.delete</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-delete"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script src="../resources/test-helpers.js"></script> +<script src="../script-tests/cache-delete.js"></script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-match.https.html new file mode 100644 index 000000000..0ff5c6799 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-match.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache Storage: Cache.match</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-match"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script src="../resources/test-helpers.js"></script> +<script src="../script-tests/cache-match.js"></script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-matchAll.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-matchAll.https.html new file mode 100644 index 000000000..372e641a5 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-matchAll.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache.matchAll</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-matchall"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script src="../resources/test-helpers.js"></script> +<script src="../script-tests/cache-matchAll.js"></script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-put.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-put.https.html new file mode 100644 index 000000000..1d9e02ba6 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-put.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache Storage: Cache.put</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-put"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script src="../resources/test-helpers.js"></script> +<script src="../script-tests/cache-put.js"></script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-keys.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-keys.https.html new file mode 100644 index 000000000..b4f94bade --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-keys.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache Storage: CacheStorage.keys</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script src="../resources/test-helpers.js"></script> +<script src="../script-tests/cache-storage-keys.js"></script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-match.https.html new file mode 100644 index 000000000..72005f43a --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage-match.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache Storage: CacheStorage.match</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage-match"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script src="../resources/test-helpers.js"></script> +<script src="../script-tests/cache-storage-match.js"></script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage.https.html new file mode 100644 index 000000000..6c4f4af7f --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/cache-storage.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache Storage: CacheStorage</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script src="../resources/test-helpers.js"></script> +<script src="../script-tests/cache-storage.js"></script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/window/sandboxed-iframes.https.html b/testing/web-platform/tests/service-workers/cache-storage/window/sandboxed-iframes.https.html new file mode 100644 index 000000000..061858521 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/window/sandboxed-iframes.https.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<title>Cache Storage: Verify access in sandboxed iframes</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/testharness-helpers.js"></script> +<script> + +function load_iframe(src, sandbox) { + return new Promise(function(resolve, reject) { + var iframe = document.createElement('iframe'); + iframe.onload = function() { resolve(iframe); }; + + iframe.sandbox = sandbox; + iframe.src = src; + + document.documentElement.appendChild(iframe); + }); +} + +function wait_for_message(id) { + return new Promise(function(resolve) { + self.addEventListener('message', function listener(e) { + if (e.data.id === id) { + resolve(e.data); + self.removeEventListener('message', listener); + } + }); + }); +} + +var counter = 0; + +promise_test(function(t) { + return load_iframe('../resources/iframe.html', + 'allow-scripts allow-same-origin') + .then(function(iframe) { + var id = ++counter; + iframe.contentWindow.postMessage({id: id}, '*'); + return wait_for_message(id); + }) + .then(function(message) { + assert_equals( + message.result, 'allowed', + 'Access should be allowed if sandbox has allow-same-origin'); + }); +}, 'Sandboxed iframe with allow-same-origin is allowed access'); + +promise_test(function(t) { + return load_iframe('../resources/iframe.html', + 'allow-scripts') + .then(function(iframe) { + var id = ++counter; + iframe.contentWindow.postMessage({id: id}, '*'); + return wait_for_message(id); + }) + .then(function(message) { + assert_equals( + message.result, 'denied', + 'Access should be denied if sandbox lacks allow-same-origin'); + assert_equals(message.name, 'SecurityError', + 'Failure should be a SecurityError'); + }); +}, 'Sandboxed iframe without allow-same-origin is denied access'); + +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-add.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-add.https.html new file mode 100644 index 000000000..2658e1e50 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-add.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache.add and Cache.addAll</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-add"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +fetch_tests_from_worker(new Worker('../script-tests/cache-add.js')); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-delete.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-delete.https.html new file mode 100644 index 000000000..3d63a2f7f --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-delete.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache.delete</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-delete"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +fetch_tests_from_worker(new Worker('../script-tests/cache-delete.js')); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-match.https.html new file mode 100644 index 000000000..479a29d1e --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-match.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache.match</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-match"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +fetch_tests_from_worker(new Worker('../script-tests/cache-match.js')); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-matchAll.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-matchAll.https.html new file mode 100644 index 000000000..c7e893a23 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-matchAll.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache.matchAll</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-matchall"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +fetch_tests_from_worker(new Worker('../script-tests/cache-matchAll.js')); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-put.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-put.https.html new file mode 100644 index 000000000..20aeb2351 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-put.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>Cache.put</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-put"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +fetch_tests_from_worker(new Worker('../script-tests/cache-put.js')); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-keys.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-keys.https.html new file mode 100644 index 000000000..71d995bc9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-keys.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CacheStorage.keys</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +fetch_tests_from_worker(new Worker('../script-tests/cache-storage-keys.js')); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-match.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-match.https.html new file mode 100644 index 000000000..cd93410d2 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage-match.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CacheStorage.match</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage-match"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +fetch_tests_from_worker(new Worker('../script-tests/cache-storage-match.js')); +</script> diff --git a/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage.https.html b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage.https.html new file mode 100644 index 000000000..0899609e4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/cache-storage/worker/cache-storage.https.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CacheStorage</title> +<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +fetch_tests_from_worker(new Worker('../script-tests/cache-storage.js')); +</script> 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> diff --git a/testing/web-platform/tests/service-workers/specgen.json b/testing/web-platform/tests/service-workers/specgen.json new file mode 100644 index 000000000..88c36ed04 --- /dev/null +++ b/testing/web-platform/tests/service-workers/specgen.json @@ -0,0 +1,658 @@ +{ + "sections": [ + { + "href": "#introduction", + "id": "introduction", + "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "secno": "1", + "testable": false + }, + { + "href": "#about", + "id": "about", + "hash": "8d3cf149aa73cff52328509ebbaffd933e8fb6af", + "secno": "1.1", + "testable": false + }, + { + "href": "#dependencies", + "id": "dependencies", + "hash": "1355f2d7ec9bf4e617ee632c0db44f834c96435b", + "secno": "1.2", + "testable": false + }, + { + "href": "#motivations", + "id": "motivations", + "hash": "92d899bc1e63a170d2324638d16f580b97b4f4d6", + "secno": "1.3", + "testable": false + }, + { + "href": "#concepts", + "id": "concepts", + "hash": "589023372dc033b0a77be1cd01f54f5f8c3ebfa8", + "secno": "2", + "testable": false + }, + { + "href": "#document-context", + "id": "document-context", + "hash": "34feeb18dea978349a2f76e6b17c127123b3db74", + "secno": "3", + "testable": false + }, + { + "href": "#service-worker-obj", + "id": "service-worker-obj", + "hash": "6cbd0107199072ab86b36e72d08d5465b42e6da8", + "secno": "3.1", + "testPageHash": "8dbbc9aa4300f0203524f3e405dbf7ca462e7164", + "testPagePath": "stub-3.1-service-worker-obj.html", + "testable": true + }, + { + "href": "#service-worker-scope", + "id": "service-worker-scope", + "hash": "136f25ef227515a7be9b32c44967f68b34ad8924", + "secno": "3.1.1", + "testPageHash": "965a00b32d56192330aa9f6337072bb3633ad382", + "testPagePath": "stub-3.1.1-service-worker-scope.html", + "testable": true + }, + { + "href": "#service-worker-url", + "id": "service-worker-url", + "hash": "df66a1b4b3bfa3e7ab96fd491a6829fab1d18a88", + "secno": "3.1.2", + "testPageHash": "92f6aed1437bb39c5941b495ac6c5f342c025b38", + "testPagePath": "stub-3.1.2-service-worker-url.html", + "testable": true + }, + { + "href": "#service-worker-state", + "id": "service-worker-state", + "hash": "8f80f2b4cbb1c228867c9dd90c05cbecfc92dd77", + "secno": "3.1.3", + "testPageHash": "4aad1dc47572879fdc2c79a814ad21e1ef9a64ec", + "testPagePath": "stub-3.1.3-service-worker-state.html", + "testable": true + }, + { + "href": "#service-worker-on-state-change", + "id": "service-worker-on-state-change", + "hash": "0f8fd9d1431deacea72fe739f42992ab5a396bf2", + "secno": "3.1.4", + "testPageHash": "6bb309bccc1e7c74ade7fc4c6e400bafb60daceb", + "testPagePath": "stub-3.1.4-service-worker-on-state-change.html", + "testable": true + }, + { + "href": "#navigator-service-worker", + "id": "navigator-service-worker", + "hash": "22f1ebbafca6976d0f4814b0fbb8f173bf919f06", + "secno": "3.2", + "testPageHash": "6d597735816a09ec774150029ed5136198f52ab7", + "testPagePath": "stub-3.2-navigator-service-worker.html", + "testable": true + }, + { + "href": "#navigator-service-worker-installing", + "id": "navigator-service-worker-installing", + "hash": "9675c3cdf5ba4b4155284e06a19e4de631645509", + "secno": "3.2.1", + "testPageHash": "2c8e56e74c130104e395de46bad20fb5d3021d95", + "testPagePath": "stub-3.2.1-navigator-service-worker-installing.html", + "testable": true + }, + { + "href": "#navigator-service-worker-waiting", + "id": "navigator-service-worker-waiting", + "hash": "88b4db92cc49109e6a15ddebdd219690d9648e76", + "secno": "3.2.2", + "testPageHash": "1cf6ed58bf5ecf963fed8c3d9211b853dab564e2", + "testPagePath": "stub-3.2.2-navigator-service-worker-waiting.html", + "testable": true + }, + { + "href": "#navigator-service-worker-active", + "id": "navigator-service-worker-active", + "hash": "0da48e885c77da60d1837197780049904789e3cb", + "secno": "3.2.3", + "testPageHash": "f5dca8c6eb5f29a0f9a5f06e25861e7f3106cc67", + "testPagePath": "stub-3.2.3-navigator-service-worker-active.html", + "testable": true + }, + { + "href": "#navigator-service-worker-controller", + "id": "navigator-service-worker-controller", + "hash": "293433ccb7bb2a22d8d5a81e788892e071b25e65", + "secno": "3.2.4", + "testPageHash": "6452f431d0765d7aa3135d18fee43e6664dcbb12", + "testPagePath": "stub-3.2.4-navigator-service-worker-controller.html", + "testable": true + }, + { + "href": "#navigator-service-worker-ready", + "id": "navigator-service-worker-ready", + "hash": "6240fde8d7168beeb95f4f36aa9e143319b2061b", + "secno": "3.2.5", + "testPageHash": "ae4fd694c88bab72f338d97bf96b7d23e2e83e87", + "testPagePath": "stub-3.2.5-navigator-service-worker-ready.html", + "testable": true + }, + { + "href": "#navigator-service-worker-getAll", + "id": "navigator-service-worker-getAll", + "hash": "292ee3af2cc8fadc24302446809d04bf2e9811a5", + "secno": "3.2.6", + "testPageHash": "4096ae712cc3e753456fbe05bb4d0cfc4399d2c9", + "testPagePath": "stub-3.2.6-navigator-service-worker-getAll.html", + "testable": true + }, + { + "href": "#navigator-service-worker-register", + "id": "navigator-service-worker-register", + "hash": "c999dc5f67126c9f0f02b25fd943a34b48cff618", + "secno": "3.2.7", + "testPageHash": "bde900b97dbb08b053ff8115775ea3b79a124b6e", + "testPagePath": "stub-3.2.7-navigator-service-worker-register.html", + "testable": true + }, + { + "href": "#navigator-service-worker-unregister", + "id": "navigator-service-worker-unregister", + "hash": "fd196f926f181563855e4683cc995405c1e611d0", + "secno": "3.2.8", + "testPageHash": "dbd99a1dcbcb629431617790a305e840495049eb", + "testPagePath": "stub-3.2.8-navigator-service-worker-unregister.html", + "testable": true + }, + { + "href": "#navigator-service-worker-onupdatefound", + "id": "navigator-service-worker-onupdatefound", + "hash": "2bb5aabaca24a68f9e6b4c4443968178eb1ccfe8", + "secno": "3.2.9", + "testPageHash": "eef0c1c39577abefb3654a6e9917ff2da657871b", + "testPagePath": "stub-3.2.9-navigator-service-worker-onupdatefound.html", + "testable": true + }, + { + "href": "#navigator-service-worker-oncontrollerchange", + "id": "navigator-service-worker-oncontrollerchange", + "hash": "c89a4ffba10d9285e07c38c28718719d87053994", + "secno": "3.2.10", + "testPageHash": "35e0ce2b8f4527ebbd75d4dfa3436fd7f8c79792", + "testPagePath": "stub-3.2.10-navigator-service-worker-oncontrollerchange.html", + "testable": true + }, + { + "href": "#navigator-service-worker-onreloadpage", + "id": "navigator-service-worker-onreloadpage", + "hash": "424441910abf2e1bdc3db658fe46827f7abe60a4", + "secno": "3.2.11", + "testPageHash": "ae614de17e5f339b65f77cafa6e0f5625491abfb", + "testPagePath": "stub-3.2.11-navigator-service-worker-onreloadpage.html", + "testable": true + }, + { + "href": "#navigator-service-worker-onerror", + "id": "navigator-service-worker-onerror", + "hash": "710f7fcd2f5340147b9e030bc5932b8242cef828", + "secno": "3.2.12", + "testPageHash": "cd62779e27151d55f14ac6ab7aa41dcf723e0ac7", + "testPagePath": "stub-3.2.12-navigator-service-worker-onerror.html", + "testable": true + }, + { + "href": "#execution-context", + "id": "execution-context", + "hash": "ddf24f0adf58237e264c3c43cb7ab07af3013c9d", + "secno": "4", + "testable": false + }, + { + "href": "#service-worker-global-scope", + "id": "service-worker-global-scope", + "hash": "e6b8bb7f99c125f4226fc5b6c51cf03a7437f2ef", + "secno": "4.1", + "testPageHash": "2f596b6b07bcfb71c01d75f725eb52c84e9c69dd", + "testPagePath": "stub-4.1-service-worker-global-scope.html", + "testable": true + }, + { + "href": "#service-worker-global-scope-caches", + "id": "service-worker-global-scope-caches", + "hash": "43d3c9f441b3a7abd0d3a7f55d93faaceeb7d97d", + "secno": "4.1.1", + "testPageHash": "f19b91c887f6312688b66b1988147a599cd9470f", + "testPagePath": "stub-4.1.1-service-worker-global-scope-caches.html", + "testable": true + }, + { + "href": "#service-worker-global-scope-clients", + "id": "service-worker-global-scope-clients", + "hash": "cb83230107645229da9776ed0fc9f7bc6fcce747", + "secno": "4.1.2", + "testPageHash": "45b3aae572f7161748fa98e97b4f2b738c3dcfef", + "testPagePath": "stub-4.1.2-service-worker-global-scope-clients.html", + "testable": true + }, + { + "href": "#service-worker-global-scope-scope", + "id": "service-worker-global-scope-scope", + "hash": "08c808048b647aa9d4cc0b0a0f70b06ca89af4a3", + "secno": "4.1.3", + "testPageHash": "bfe7eaf8deb8de7d2ccfbba97640478b1c81d6c7", + "testPagePath": "stub-4.1.3-service-worker-global-scope-scope.html", + "testable": true + }, + { + "href": "#service-worker-global-scope-fetch", + "id": "service-worker-global-scope-fetch", + "hash": "b66133d8a2c67f9b10c274d5b05383ff76d2cd42", + "secno": "4.1.4", + "testPageHash": "2b1ffa915afddeb099dfff23f4ecf555b0710ed4", + "testPagePath": "stub-4.1.4-service-worker-global-scope-fetch.html", + "testable": true + }, + { + "href": "#service-worker-global-scope-update", + "id": "service-worker-global-scope-update", + "hash": "3ddf48cecb4d4a67a329248787dd220ce17f4eff", + "secno": "4.1.5", + "testPageHash": "15879bf45f460c0ab0c02793655096c1bca418a7", + "testPagePath": "stub-4.1.5-service-worker-global-scope-update.html", + "testable": true + }, + { + "href": "#service-worker-global-scope-unregister", + "id": "service-worker-global-scope-unregister", + "hash": "fff9ef2daa5689b38a17eeb9a6bd7071098ca778", + "secno": "4.1.6", + "testPageHash": "c4bf327228628b794db9c6f2eb17519e37cea6b9", + "testPagePath": "stub-4.1.6-service-worker-global-scope-unregister.html", + "testable": true + }, + { + "href": "#service-worker-global-scope-onmessage", + "id": "service-worker-global-scope-onmessage", + "hash": "bc8f6aed2d515dc7f6b0757afa02f37899082668", + "secno": "4.1.7", + "testPageHash": "9e6f2732d21871ec06e9541ea881baf962f7cdf4", + "testPagePath": "stub-4.1.7-service-worker-global-scope-onmessage.html", + "testable": true + }, + { + "href": "#client", + "id": "client", + "hash": "47a1c10cd9e4db9a5c86d9bcf80477f771ea954c", + "secno": "4.2", + "testPageHash": "21d74c1af0b3176b029c9b62b37fe73436e0f197", + "testPagePath": "stub-4.2-client.html", + "testable": true + }, + { + "href": "#service-worker-clients", + "id": "service-worker-clients", + "hash": "c2c6f4873f07b53705a46b2bd44ba10f84dd2b56", + "secno": "4.3", + "testPageHash": "9c0366e6cfd28caaeaf940bad2b3c7ace93037f6", + "testPagePath": "stub-4.3-service-worker-clients.html", + "testable": true + }, + { + "href": "#get-serviced-method", + "id": "get-serviced-method", + "hash": "299abaa21cf096e423edfa19755987986f742a1f", + "secno": "4.3.1", + "testPageHash": "efeb1c2dc8144c30e6628cb56b3e532531ee1e88", + "testPagePath": "stub-4.3.1-get-serviced-method.html", + "testable": true + }, + { + "href": "#reloadall-method", + "id": "reloadall-method", + "hash": "bb4d775d261e69cbeaf65c123e949c24cf542ae7", + "secno": "4.3.2", + "testPageHash": "d1a4dde873b77201b4de745d2083bf63549b0b8b", + "testPagePath": "stub-4.3.2-reloadall-method.html", + "testable": true + }, + { + "href": "#request-objects", + "id": "request-objects", + "hash": "65ae6c08f720a2eedb7b140f5635a5ac46ddadfc", + "secno": "4.4", + "testPageHash": "ec493c70e8a0d8d3eeb0ecaef59610aed97d298e", + "testPagePath": "stub-4.4-request-objects.html", + "testable": true + }, + { + "href": "#response-objects", + "id": "response-objects", + "hash": "2efbff63c70ab92f93e4acd021409b9df4776882", + "secno": "4.5", + "testPageHash": "8340b69d62f111f56095c5fe9047d9215fa7aefc", + "testPagePath": "stub-4.5-response-objects.html", + "testable": true + }, + { + "href": "#abstract-response", + "id": "abstract-response", + "hash": "bddc306a9892c0bca43e8b361c1ee22b87759e23", + "secno": "4.5.1", + "testable": false + }, + { + "href": "#response", + "id": "response", + "hash": "6471d25755bdab0d4f72413f9367b7bb36c53a6f", + "secno": "4.5.2", + "testPageHash": "346d63cc7eb8ee412f5f704ba241205c8d437540", + "testPagePath": "stub-4.5.2-response.html", + "testable": true + }, + { + "href": "#header", + "id": "header", + "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "secno": "4.5.3", + "testable": false + }, + { + "href": "#opaque-response", + "id": "opaque-response", + "hash": "df5431f4fbd26d81f2d4f567309c6a7a26dbfd4a", + "secno": "4.5.4", + "testPageHash": "85373f290cf594f0f09eb0a76bc6ef6299be595f", + "testPagePath": "stub-4.5.4-opaque-response.html", + "testable": true + }, + { + "href": "#cors-response", + "id": "cors-response", + "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "secno": "4.5.5", + "testable": false + }, + { + "href": "#cache-objects", + "id": "cache-objects", + "hash": "001d0dfb8fbcbcb6443d1be2b722c9a84d6fd95b", + "secno": "4.6", + "testPageHash": "c1ef341d15a8c76d015eef57842ed10e62c02927", + "testPagePath": "stub-4.6-cache-objects.html", + "testable": true + }, + { + "href": "#cache-lifetimes", + "id": "cache-lifetimes", + "hash": "7c73698ca9b686a0314ddf368bf8ad4ca6af392f", + "secno": "4.6.1", + "testPageHash": "f3524320a98f2fbdc5d711de82770957a7f5ec4b", + "testPagePath": "stub-4.6.1-cache-lifetimes.html", + "testable": true + }, + { + "href": "#cache", + "id": "cache", + "hash": "bf1fe844577ab57a60eb550be24335a3321ca2ee", + "secno": "4.6.2", + "testPageHash": "c55b7b05c8e2f4b65722e16cdbcd78ffdfe1e4bf", + "testPagePath": "stub-4.6.2-cache.html", + "testable": true + }, + { + "href": "#cache-storage", + "id": "cache-storage", + "hash": "9cdaac070f56e55d66a89cd4b6e669a04aa73b82", + "secno": "4.6.3", + "testPageHash": "ee6902f170d94cc1e3a4a00f4c90e7e19c4dff95", + "testPagePath": "stub-4.6.3-cache-storage.html", + "testable": true + }, + { + "href": "#events", + "id": "events", + "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "secno": "4.7", + "testable": false + }, + { + "href": "#install-phase-event", + "id": "install-phase-event", + "hash": "8495382b418adbbed436b2002ab0155a3a295ef2", + "secno": "4.7.1", + "testPageHash": "e48e98d51936bd57d21903615203f2b78d3f4b12", + "testPagePath": "stub-4.7.1-install-phase-event.html", + "testable": true + }, + { + "href": "#wait-until-method", + "id": "wait-until-method", + "hash": "295fb5d4932396fd13365ed2fe57aa672f1f2a56", + "secno": "4.7.1.1", + "testPageHash": "c3769e51852b8438a97c39c50fa62351a73c4ee6", + "testPagePath": "stub-4.7.1.1-wait-until-method.html", + "testable": true + }, + { + "href": "#install-event", + "id": "install-event", + "hash": "3a0f6da1771c22ab21ddc00729433a4d95ac6782", + "secno": "4.7.2", + "testPageHash": "9a103cc461eaca3da75db583ce08f13ecd2b1a98", + "testPagePath": "stub-4.7.2-install-event.html", + "testable": true + }, + { + "href": "#install-event-section", + "id": "install-event-section", + "hash": "4631577df2efc1a4350000461629bc1ca93dbd14", + "secno": "4.7.2.1", + "testPageHash": "32f54e74bef784d2f0ac772b44abeee06573062d", + "testPagePath": "stub-4.7.2.1-install-event-section.html", + "testable": true + }, + { + "href": "#replace-method", + "id": "replace-method", + "hash": "b9093b05204d09748311023b4c737ede02ff8115", + "secno": "4.7.2.2", + "testPageHash": "372bed923f8c35c4923634ae27fa121919ac0fec", + "testPagePath": "stub-4.7.2.2-replace-method.html", + "testable": true + }, + { + "href": "#activate-event", + "id": "activate-event", + "hash": "ac3d03aa0ed961fb1122850aeab92c302c55ecd0", + "secno": "4.7.3", + "testPageHash": "6241762ab1d6f430fa9b7cc8f02a00e6591c6bc6", + "testPagePath": "stub-4.7.3-activate-event.html", + "testable": true + }, + { + "href": "#fetch-event", + "id": "fetch-event", + "hash": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "secno": "4.7.4", + "testable": false + }, + { + "href": "#fetch-event-section", + "id": "fetch-event-section", + "hash": "ae24fda9664a3bd7b7fe2a8712ac469c3ee7128e", + "secno": "4.7.4.1", + "testPageHash": "393fc7b65e9f5afd18da666b6b206ccd639397cd", + "testPagePath": "stub-4.7.4.1-fetch-event-section.html", + "testable": true + }, + { + "href": "#respond-with-method", + "id": "respond-with-method", + "hash": "7e4f010e2ec1ea0500b435cf599ba58942164457", + "secno": "4.7.4.2", + "testPageHash": "31e0acd058b9a5b722ae9f405b50bc94d31596b8", + "testPagePath": "stub-4.7.4.2-respond-with-method.html", + "testable": true + }, + { + "href": "#default-method", + "id": "default-method", + "hash": "4d6f8f93b2e10ab0e486dbf464ff107ec1a6aa4c", + "secno": "4.7.4.3", + "testPageHash": "34e015c973887e2b3bf8b6db62f75d5d417a43cc", + "testPagePath": "stub-4.7.4.3-default-method.html", + "testable": true + }, + { + "href": "#is-reload-attribute", + "id": "is-reload-attribute", + "hash": "6e1afd9e8940e9cd38aa7de1ed57e8c5b1a60e3d", + "secno": "4.7.4.4", + "testPageHash": "703a6469782d37be3c25e2214f897d1064acca47", + "testPagePath": "stub-4.7.4.4-is-reload-attribute.html", + "testable": true + }, + { + "href": "#security-considerations", + "id": "security-considerations", + "hash": "5b02b143172647dd7f74f0464dffa7ec7d0e8f94", + "secno": "5", + "testable": false + }, + { + "href": "#origin-relativity", + "id": "origin-relativity", + "hash": "72bbbd7d3d43a859af6ff9f19353210ddfcc26de", + "secno": "5.1", + "testPageHash": "1c92607dfac57b0f59654d059a4a67e0f984b84d", + "testPagePath": "stub-5.1-origin-relativity.html", + "testable": true + }, + { + "href": "#cross-origin-resources", + "id": "cross-origin-resources", + "hash": "6176879ecfb5ae769679ceef4ee1e8889be8df92", + "secno": "5.2", + "testPageHash": "bcf85ba278c70c086645c416cee729ce753bc528", + "testPagePath": "stub-5.2-cross-origin-resources.html", + "testable": true + }, + { + "href": "#storage-considerations", + "id": "storage-considerations", + "hash": "e101cee2062749b1a73086492377458251a5e875", + "secno": "6", + "testable": false + }, + { + "href": "#extensibility", + "id": "extensibility", + "hash": "ef1b382bb89c52e01edad421b02b237765a21ce7", + "secno": "7", + "testable": false + }, + { + "href": "#algorithms", + "id": "algorithms", + "hash": "d130247eab1d368efea646ff369e65f6c0c19481", + "secno": "8", + "testable": false + }, + { + "href": "#registration-algorithm", + "id": "registration-algorithm", + "hash": "b688d090671c08ca17ea7cadc561e6d471ee099e", + "secno": "8.1", + "testable": false + }, + { + "href": "#update-algorithm", + "id": "update-algorithm", + "hash": "679a19fef428affc83103c1eec0dbd3be40c4e2a", + "secno": "8.2", + "testable": false + }, + { + "href": "#soft-update-algorithm", + "id": "soft-update-algorithm", + "hash": "8eb103f5cd0e595ee5e25f075e8c6239211e482a", + "secno": "8.3", + "testable": false + }, + { + "href": "#installation-algorithm", + "id": "installation-algorithm", + "hash": "5874d9247d979009b67aedf964ae097837cfb3d9", + "secno": "8.4", + "testable": false + }, + { + "href": "#activation-algorithm", + "id": "activation-algorithm", + "hash": "648b34baf6e7c2096a842e6d367949117843108e", + "secno": "8.5", + "testable": false + }, + { + "href": "#on-fetch-request-algorithm", + "id": "on-fetch-request-algorithm", + "hash": "e1da43671071ec307f99cd781fc9b46353f3adfd", + "secno": "8.6", + "testable": false + }, + { + "href": "#on-document-unload-algorithm", + "id": "on-document-unload-algorithm", + "hash": "8a7196b5dd04ad4fb9b96e16a52f4f7ac1906763", + "secno": "8.7", + "testable": false + }, + { + "href": "#unregistration-algorithm", + "id": "unregistration-algorithm", + "hash": "0114db166d42211d0d7ab4b8e77de64a9fc97517", + "secno": "8.8", + "testable": false + }, + { + "href": "#update-state-algorithm", + "id": "update-state-algorithm", + "hash": "2ed8a1e7479f1a8ad038aa44ccdd5e4f6b65cf05", + "secno": "8.9", + "testable": false + }, + { + "href": "#scope-match-algorithm", + "id": "scope-match-algorithm", + "hash": "a2117fb34a8fa4ca3e832d9276477cfc1318dd1a", + "secno": "8.10", + "testable": false + }, + { + "href": "#get-registration-algorithm", + "id": "get-registration-algorithm", + "hash": "b20332db952ba8f4b7e5f65b740a18da4a199c2e", + "secno": "8.11", + "testable": false + }, + { + "href": "#get-newest-worker-algorithm", + "id": "get-newest-worker-algorithm", + "hash": "72dc1cbee8c98501931c411018fd1cad4376142b", + "secno": "8.12", + "testable": false + }, + { + "href": "#acknowledgements", + "id": "acknowledgements", + "hash": "6347067ca5a574f8cc80c76d95dee568042d059b", + "secno": "9", + "testable": false + } + ], + "specUrl": "https://w3c.github.io/ServiceWorker/" +}
\ No newline at end of file diff --git a/testing/web-platform/tests/service-workers/stub-3.1-service-worker-obj.html b/testing/web-platform/tests/service-workers/stub-3.1-service-worker-obj.html new file mode 100644 index 000000000..b1c7f1ad0 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.1-service-worker-obj.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: ServiceWorker</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-obj"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +[Constructor()] // no-op constructor +interface ServiceWorker : Worker { + readonly attribute DOMString scope; + readonly attribute DOMString url; + readonly attribute ServiceWorkerState state; + + // event + attribute EventHandler onstatechange; +}; + +enum ServiceWorkerState { + "installing", + "installed", + "activating", + "activated", + "redundant" +}; +</pre> + +<!-- +The `ServiceWorker` interface represents the document-side view of a Service +Worker. This object provides a no-op constructor. Callers should note that only +`ServiceWorker` objects created by the user agent (see +`navigator.serviceWorker.installing`, `navigator.serviceWorker.waiting`, +`navigator.serviceWorker.active` and `navigator.serviceWorker.controller`) will +provide meaningful functionality. +--> + + + <script type=text/plain id="untested_idls"> + interface EventHandler {}; + interface Worker {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + ServiceWorker: ["throw new Error ('No object defined for the ServiceWorker interface')"], + ServiceWorkerState: ["throw new Error ('No object defined for the ServiceWorkerState enum')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.1.1-service-worker-scope.html b/testing/web-platform/tests/service-workers/stub-3.1.1-service-worker-scope.html new file mode 100644 index 000000000..8c75c6082 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.1.1-service-worker-scope.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: scope</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-scope"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +The `scope` of a `ServiceWorker` object reflects the [URL scope][1] of the +associated Service Worker [registration][2]. The `scope` attribute must return +the [serialization][3] of the URL representing the [URL scope][1] of the +associated Service Worker [registration][2]. + +For example, consider a document created by a navigation to +`https://example.com/app.html` which [matches][4] via the following +registration call which has been previously executed: +// Script on the page https://example.com/app.html +navigator.serviceWorker.register("/service_worker.js", { scope: "/*" }); +The value of `navigator.serviceWorker.controller.scope` will be +`"https://example.com/*"`. + + + +[1]: #url-scope +[2]: #registration +[3]: http://url.spec.whatwg.org/#concept-url-serializer +[4]: #on-fetch-request-algorithm + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section scope so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.1.2-service-worker-url.html b/testing/web-platform/tests/service-workers/stub-3.1.2-service-worker-url.html new file mode 100644 index 000000000..5674df7fc --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.1.2-service-worker-url.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: url</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-url"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +The `url` attribute must return the [serialization][1] of the URL of the script +of the Service Worker, identified by its [URL scope][2], that is associated +with the [ServiceWorkerGlobalScope][3] object. The `url` attribute is always an +[absolute URL][4] corresponding to the script file which the Service Worker +evaluates. + +In the example in section 3.1.1, the value of +`navigator.serviceWorker.controller.url` will be +`"https://example.com/service_worker.js"`. + + + +[1]: http://url.spec.whatwg.org/#concept-url-serializer +[2]: #url-scope +[3]: #service-worker-global-scope-interface +[4]: http://url.spec.whatwg.org/#concept-absolute-url + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section url so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.1.3-service-worker-state.html b/testing/web-platform/tests/service-workers/stub-3.1.3-service-worker-state.html new file mode 100644 index 000000000..8e729baf8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.1.3-service-worker-state.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: state</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-state"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +The [ServiceWorker][1] object can be in several states. The `state` attribute +must return the current state, which must be one of the following values +defined in the [ServiceWorkerState][2] enumeration: + +`"installing"`: + The Service Worker represented by the [ServiceWorker][1] object has entered + and is running the steps in the [installation process][3]. During this + state, `e.waitUntil(p)` can be called inside the `oninstall` event handler + of the associcated [ServiceWorkerGloberScope][4] object to extend the life + of the [installing worker][5] until the passed [Promise][6] resolves + successfully. This is primarily used to ensure that the Service Worker is + not active until all of the core caches are populated. +`"installed"`: + The Service Worker represented by the [ServiceWorker][1] object has + completed the steps in the [installation process][3]. The Service Worker in + this state is considered the [worker in waiting][7]. +`"activating"`: + The Service Worker represented by the [ServiceWorker][1] object has entered + and is running the steps in the [activation process][8]. During this state, + `e.waitUntil(p)` can be called inside the `onactivate` event handler of the + associated [ServiceWorkerGloberScope][9] object to extend the life of the + activating [active worker][10] until the passed [Promise][6] resolves + successfully. Note that no [functional events][11] are dispatched until the + state becomes `"activated"`. +`"activated"`: + The Service Worker represented by the [ServiceWorker][1] object has + completed the steps in the [activation process][8]. The Service Worker in + this state is considered the [active worker][10] ready to [control][12] the + documents in matching scope upon subsequence [navigation][13]. +`"redundant"`: + A newly created Service Worker [registration][14] is replacing the current + [registration][14] of the Service Worker. + + + +[1]: #service-worker-interface +[2]: #service-worker-state-enum +[3]: #installation-process +[4]: #service-worker-glober-scope-interface +[5]: #installing-worker +[6]: http://goo.gl/3TobQS +[7]: #worker-in-waiting +[8]: #activation-process +[9]: #service-worker-global-scope-interface +[10]: #active-worker +[11]: #functional-events +[12]: #document-control +[13]: http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#navigate +[14]: #registration + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section state so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.1.4-service-worker-on-state-change.html b/testing/web-platform/tests/service-workers/stub-3.1.4-service-worker-on-state-change.html new file mode 100644 index 000000000..c87dce601 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.1.4-service-worker-on-state-change.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: onstatechange</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-on-state-change"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`onstatechange` is the [event handler][1] that must be supported as attribute +by the `[ServiceWorker][2]` object. A `statechange` event using the +`[Event][3]` interface is dispatched on `[ServiceWorker][2]` object when the +`state` attribute of the `ServiceWorker` object is changed. + +[1]: http://goo.gl/rBfiz0 +[2]: #service-worker-interface +[3]: http://goo.gl/Mzv7Dv + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section onstatechange so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2-navigator-service-worker.html b/testing/web-platform/tests/service-workers/stub-3.2-navigator-service-worker.html new file mode 100644 index 000000000..2654c60f0 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2-navigator-service-worker.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: navigator.serviceWorker</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<!-- +The `serviceWorker` attribute of the [Navigator][1] interface must return an +instance of the `ServiceWorkerContainer` interface, which provides access to +registration, removal, upgrade, and communication with Service Workers that are +(or will become) active for the current document. Communication with these +workers is provided via standard [HTML5 messaging APIs][2], and [messaging +occurs as per usual with Web Workers][3]. +--> +<script type=text/plain id="idl_0"> +partial interface Navigator { + readonly attribute ServiceWorkerContainer serviceWorker; +}; + +interface ServiceWorkerContainer : EventTarget { + [Unforgeable] readonly attribute ServiceWorker? installing; + [Unforgeable] readonly attribute ServiceWorker? waiting; + [Unforgeable] readonly attribute ServiceWorker? active; + [Unforgeable] readonly attribute ServiceWorker? controller; + readonly attribute Promise<ServiceWorker> ready; + + Promise<sequence<ServiceWorker>?> getAll(); + Promise<ServiceWorker> register(DOMString url, optional RegistrationOptionList options); + Promise<any> unregister(DOMString? scope); + + // events + attribute EventHandler onupdatefound; + attribute EventHandler oncontrollerchange; + attribute EventHandler onreloadpage; + attribute EventHandler onerror; +}; + +dictionary RegistrationOptionList { + DOMString scope = "/*"; +}; + +interface ReloadPageEvent : Event { + void waitUntil(Promise<any> f); +}; +</script> + +<!-- +[1]: http://goo.gl/I7WAhg +[2]: http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html +[3]: http://www.w3.org/TR/workers/#dom-worker-postmessage +--> + + + <script type=text/plain id="untested_idls"> + interface ServiceWorker {}; + interface EventHandler {}; + interface EventTarget {}; + interface Event {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + Navigator: ["throw new Error ('No object defined for the Navigator interface')"], + ServiceWorkerContainer: ["throw new Error ('No object defined for the ServiceWorkerContainer interface')"], + RegistrationOptionList: ["throw new Error ('No object defined for the RegistrationOptionList dictionary')"], + ReloadPageEvent: ["throw new Error ('No object defined for the ReloadPageEvent interface')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.1-navigator-service-worker-installing.html b/testing/web-platform/tests/service-workers/stub-3.2.1-navigator-service-worker-installing.html new file mode 100644 index 000000000..59e4f3d6a --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.1-navigator-service-worker-installing.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: installing</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-installing"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.installing` must return a [ServiceWorker][1] object +representing the [installing worker][2] that is currently undergoing the +installation process (from step 1 to step 7 of the [_Installation +algorithm][3]) for the given [URL scope][4] in which the document may be +[controlled][5] when the Service Worker becomes the [active worker][6]. +`navigator.serviceWorker.installing` returns `null` if no Service Worker +[registration][7] is in the [installation process][8]. + +[1]: #service-worker-interface +[2]: #installing-worker +[3]: #installation-algorithm +[4]: #url-scope +[5]: #document-control +[6]: #active-worker +[7]: #service-worker-registration-internal-interface +[8]: #installation-process + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section installing so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.10-navigator-service-worker-oncontrollerchange.html b/testing/web-platform/tests/service-workers/stub-3.2.10-navigator-service-worker-oncontrollerchange.html new file mode 100644 index 000000000..478860146 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.10-navigator-service-worker-oncontrollerchange.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: oncontrollerchange</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-oncontrollerchange"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.oncontrollerchange` is the [event handler][1] that +must be supported as attribute by the `[ServiceWorkerContainer][2]` object. A +`controllerchange` event using the `[Event][3]` interface is dispatched on +`[ServiceWorkerContainer][2]` object (See step 7 of the [_Activation +algorithm][4]) when the associated Service Worker [registration][5] for the +document enters the [activation process][6]. When the [activation process][6] +is triggered by `replace()` method call within the event handler of the +`install` event, `navigator.serviceWorker.controller` immediately reflects the +[active worker][7] as the Service Worker that [controls][8] the document. + +[1]: http://goo.gl/rBfiz0 +[2]: #service-worker-container-interface +[3]: http://goo.gl/Mzv7Dv +[4]: #activation-algorithm +[5]: #registration +[6]: #activation-process +[7]: #active-worker +[8]: #document-control + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section oncontrollerchange so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.11-navigator-service-worker-onreloadpage.html b/testing/web-platform/tests/service-workers/stub-3.2.11-navigator-service-worker-onreloadpage.html new file mode 100644 index 000000000..e2207db10 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.11-navigator-service-worker-onreloadpage.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: onreloadpage</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-onreloadpage"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.onreloadpage` is the [event handler][1] that must be +supported as attribute by the `[ServiceWorkerContainer][2]` object. An event +named `reloadpage` using the `[ReloadPageEvent][3]` interface is dispatched on +`[ServiceWorkerContainer][2]` object when the page reload is triggered by the +`[self.clients.reloadAll()][4]` method call from the [active worker][5], +represented by its associated [ServiceWorkerGlobalScope][6] object, for the +document. + +[1]: http://goo.gl/rBfiz0 +[2]: #service-worker-container-interface +[3]: #reload-page-event-interface +[4]: #reloadall-method +[5]: #active-worker +[6]: #service-worker-global-scope-interface + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section onreloadpage so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.12-navigator-service-worker-onerror.html b/testing/web-platform/tests/service-workers/stub-3.2.12-navigator-service-worker-onerror.html new file mode 100644 index 000000000..313f0bdfc --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.12-navigator-service-worker-onerror.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: onerror</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-onerror"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.onerror` is the [event handler][1] that must be +supported as attribute by the `[ServiceWorkerContainer][2]` object. An event +named `error` using the `[ErrorEvent][3]` interface is dispatched on +`[ServiceWorkerContainer][2]` object for any error from the associated +`[ServiceWorker][4]` objects. + +[1]: http://goo.gl/rBfiz0 +[2]: #service-worker-container-interface +[3]: http://goo.gl/FKuWgu +[4]: #service-worker-interface + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section onerror so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.2-navigator-service-worker-waiting.html b/testing/web-platform/tests/service-workers/stub-3.2.2-navigator-service-worker-waiting.html new file mode 100644 index 000000000..663ce82f9 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.2-navigator-service-worker-waiting.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: waiting</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-waiting"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.waiting` must return a [ServiceWorker][1] object +representing the waiting Service Worker that is considered the [worker in +waiting][2] for the document. `navigator.serviceWorker.waiting` returns `null` +if there is no [worker in waiting][2] for the document. + +[1]: #service-worker-interface +[2]: #worker-in-waiting + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section waiting so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.3-navigator-service-worker-active.html b/testing/web-platform/tests/service-workers/stub-3.2.3-navigator-service-worker-active.html new file mode 100644 index 000000000..f7406f590 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.3-navigator-service-worker-active.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: active</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-active"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.active` must return a [ServiceWorker][1] object +representing the [active worker][2] that is currently undergoing or completed +the activation process (from step 4 to step 9 of the [_Activation +algorithm][3]) for the given [URL scope][4] in which the document is controlled +(or to be controlled). `navigator.serviceWorker.active` returns `null` if no +Service Worker [registration][5] is in the [activation process][6]. + +[1]: #service-worker-interface +[2]: #active-worker +[3]: #activation-algorithm +[4]: #url-scope +[5]: #service-worker-registration-internal-interface +[6]: #activation-process + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section active so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.4-navigator-service-worker-controller.html b/testing/web-platform/tests/service-workers/stub-3.2.4-navigator-service-worker-controller.html new file mode 100644 index 000000000..1a26cce6d --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.4-navigator-service-worker-controller.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: controller</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-controller"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.controller` must return a [ServiceWorker][1] object +representing the [active worker][2] that currently handles resource requests +for the document. `navigator.serviceWorker.controller` returns `null` if the +current document was not [created under a Service Worker][3] (See step 6-1 of +[_OnFetchRequest][3] algorithm) or the request is a force refresh +(shift+refresh). + +[1]: #service-worker-interface +[2]: #active-worker +[3]: #on-fetch-request-algorithm + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section controller so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.5-navigator-service-worker-ready.html b/testing/web-platform/tests/service-workers/stub-3.2.5-navigator-service-worker-ready.html new file mode 100644 index 000000000..67a690ddc --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.5-navigator-service-worker-ready.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: ready</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-ready"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.ready` attribute must return the result of running +these steps: + +1. Let `promise` be a newly-created [promise][1]. +2. Return `promise`. +3. Run the following steps asynchronously: + 1. Let `registration` be the result of running [_ScopeMatch + algorithm][2] with document's url as its argument. + 2. If `registration` is null, then: + 1. Wait for the document to have a matching [registration][3]. + 3. If the [registration][3], represented by `registration`, for the + document has an [active worker][4], then: + 1. Resolve `promise` with the [ServiceWorker][5] object associated + with the [active worker][4]. + 2. Abort these steps. + 4. If the [registration][3], represented by `registration`, for the + document has a [worker in waiting][6], then: + 1. Resolve `promise` with the [ServiceWorker][5] object associated + with the [worker in waiting][6]. + 2. Abort these steps. + 5. Wait until the [registration][3], represented by `registration`, + for the document acquires a [worker in waiting][6] through a new + [installation process][7]. + 6. Resolve `promise` with the [ServiceWorker][5] object associated + with the [worker in waiting][6]. +Note that `ready` attribute is desinged in a way that the returned [promise][1] +will never reject. Instead, it waits until the [promise][1] resolves with a +newly installed [worker in waiting][6]. Hence, the `state` of the acquired +[`ServiceWorker`][8] object is either `installed`, `activating` or `activated`. + + + +[1]: http://goo.gl/3TobQS +[2]: #scope-match-algorithm +[3]: #registration +[4]: #active-worker +[5]: #service-worker-interface +[6]: #worker-in-waiting +[7]: #installation-process +[8]: #service-worker + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section ready so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.6-navigator-service-worker-getAll.html b/testing/web-platform/tests/service-workers/stub-3.2.6-navigator-service-worker-getAll.html new file mode 100644 index 000000000..3c2afe99c --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.6-navigator-service-worker-getAll.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: getAll()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-getAll"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.getAll()` method must return a promise that resolves +with the array of the ServiceWorker objects in `installing`, `installed`, +`activating` and `activated` states. + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section getAll() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.7-navigator-service-worker-register.html b/testing/web-platform/tests/service-workers/stub-3.2.7-navigator-service-worker-register.html new file mode 100644 index 000000000..df469de42 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.7-navigator-service-worker-register.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: register()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-register"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.register(url, options)` method must run the +[Registration algorithm][1] passing `url` and `options`.`scope` as the +arguments. + +[1]: #registration-algorithm + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section register() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.8-navigator-service-worker-unregister.html b/testing/web-platform/tests/service-workers/stub-3.2.8-navigator-service-worker-unregister.html new file mode 100644 index 000000000..6f1b43b6b --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.8-navigator-service-worker-unregister.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: unregister()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-unregister"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.unregister(scope)` method must run the [Unregistration +algorithm][1] passing `scope` as the argument. + +[1]: #unregistration-algorithm + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section unregister() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-3.2.9-navigator-service-worker-onupdatefound.html b/testing/web-platform/tests/service-workers/stub-3.2.9-navigator-service-worker-onupdatefound.html new file mode 100644 index 000000000..7babe7c24 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-3.2.9-navigator-service-worker-onupdatefound.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: onupdatefound</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#navigator-service-worker-onupdatefound"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`navigator.serviceWorker.onupdatefound` is the [event handler][1] that must be +supported as attribute by the `[ServiceWorkerContainer][2]` object. An +`updatefound` event using the `[Event][3]` interface is dispatched on +`[ServiceWorkerContainer][2]` object (See step 4 of the [_Installation +algorithm][4]) when the associated Service Worker [registration][5] for the +document enters the [installation process][6] such that +`navigator.serviceWorker.installing` becomes the new [installing worker][7]. + +[1]: http://goo.gl/rBfiz0 +[2]: #service-worker-container-interface +[3]: http://goo.gl/Mzv7Dv +[4]: #installation-algorithm +[5]: #registration +[6]: #installation-process +[7]: #installing-worker + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section onupdatefound so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html b/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html new file mode 100644 index 000000000..48697f01e --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.1-service-worker-global-scope.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: ServiceWorkerGlobalScope</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +[Global] +interface ServiceWorkerGlobalScope : WorkerGlobalScope { + readonly attribute CacheStorage caches; + // A container for a list of window objects, identifiable by ID, that + // correspond to windows (or workers) that are "controlled" by this SW + readonly attribute ServiceWorkerClients clients; + [Unforgeable] readonly attribute DOMString scope; + + Promise<any> fetch((Request or ScalarValueString) request); + + void update(); + void unregister(); + + attribute EventHandler oninstall; + attribute EventHandler onactivate; + attribute EventHandler onfetch; + attribute EventHandler onbeforeevicted; + attribute EventHandler onevicted; + + // The event.source of these MessageEvents are instances of Client + attribute EventHandler onmessage; + + // close() method inherited from WorkerGlobalScope is not exposed. +}; +</pre> + +<!-- +The `ServiceWorkerGlobalScope` interface represents the global execution +context of a Service Worker. `ServiceWorkerGlobalScope` object provides +generic, event-driven, time-limited script execution contexts that run at an +origin. Once successfully [registered][1], a Service Worker is started, kept +alive and killed by their relationship to events, not documents. Any type of +synchronous requests MUST NOT be initiated inside of a Service Worker. + +[1]: #navigator-service-worker-register +--> + + + <script type=text/plain id="untested_idls"> + interface CacheStorage {}; + interface ServiceWorkerClients {}; + interface Request {}; + interface ScalarValueString {}; + interface EventHandler {}; + interface WorkerGlobalScope {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + ServiceWorkerGlobalScope: ["throw new Error ('No object defined for the ServiceWorkerGlobalScope interface')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.1.1-service-worker-global-scope-caches.html b/testing/web-platform/tests/service-workers/stub-4.1.1-service-worker-global-scope-caches.html new file mode 100644 index 000000000..f1fce5036 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.1.1-service-worker-global-scope-caches.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: caches</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-caches"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`self.caches` must return the `[CacheStorage][1]` object that is the global +asynchronous map object for the `[ServiceWorkerGlobalScope][2]` execution +context containing the cache objects keyed by the name of the caches. Caches +are always enumerable via `self.caches` in insertion order (per [ECMAScript 6 +Map objects][3].) + +[1]: #cache-storage-interface +[2]: #service-worker-global-scope-interface +[3]: http://goo.gl/gNnDPO + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section caches so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.1.2-service-worker-global-scope-clients.html b/testing/web-platform/tests/service-workers/stub-4.1.2-service-worker-global-scope-clients.html new file mode 100644 index 000000000..cd5d28353 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.1.2-service-worker-global-scope-clients.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: clients</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-clients"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`self.clients` must return the `[ServiceWorkerClients][1]` object containing a +list of client objects, identifiable by ID, that correspond to windows or +workers that are [controlled][2] by this Service Worker. + +[1]: #service-worker-clients-interface +[2]: #document-control + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section clients so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.1.3-service-worker-global-scope-scope.html b/testing/web-platform/tests/service-workers/stub-4.1.3-service-worker-global-scope-scope.html new file mode 100644 index 000000000..7b6ce78fa --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.1.3-service-worker-global-scope-scope.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: scope</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-scope"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +The `scope` attribute of a [ServiceWorkerGlobalScope][1] object reflects the +[URL scope][2] of the associated Service Worker [registration][3]. The `scope` +attribute must return the [serialization][4] of the URL representing the [URL +scope][2] of the associated Service Worker [registration][3]. + +[1]: #service-worker-global-scope-interface +[2]: #url-scope +[3]: #registration +[4]: http://url.spec.whatwg.org/#concept-url-serializer + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section scope so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.1.4-service-worker-global-scope-fetch.html b/testing/web-platform/tests/service-workers/stub-4.1.4-service-worker-global-scope-fetch.html new file mode 100644 index 000000000..98345471f --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.1.4-service-worker-global-scope-fetch.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: fetch(request)</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-fetch"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`self.fetch(request)` method must run these steps: + +1. Let `request` be a [request][1] represented by `request`. +2. Set [`client`][2] of `request` to the [JavaScript global + environment][3] represented by `self` object. +3. Let `promise` be a newly-created [promise][4]. +4. Return `promise.` +5. Run the following steps asynchronously: + 1. Let `response` be the result of running [fetch algorithm][5] with + `request` as its argument. + 2. If `response` is a [network error][6], then: + 1. Reject `promise` with a new [DOMException][7] whose name is + "[NetworkError][8]". + 3. Else, + 1. Resolve `promise` with a new [Response][9] object associated + with `response`. + + + +[1]: http://goo.gl/ucOuXl +[2]: http://goo.gl/Oxj4xQ +[3]: http://goo.gl/ifwwCC +[4]: http://goo.gl/3TobQS +[5]: http://goo.gl/fGMifs +[6]: http://goo.gl/jprjjc +[7]: http://goo.gl/A0U8qC +[8]: http://goo.gl/lud5HB +[9]: http://goo.gl/Deazjv + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section fetch(request) so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.1.5-service-worker-global-scope-update.html b/testing/web-platform/tests/service-workers/stub-4.1.5-service-worker-global-scope-update.html new file mode 100644 index 000000000..26e255dd4 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.1.5-service-worker-global-scope-update.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: update()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-update"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`update()` pings the server for an updated version of this script without +consulting caches. `self.update()` method must run the [_SoftUpdate +algorithm][1] passing its serviceWorkerRegistration object as the argument +which is the result of running the [_GetRegistration algorithm][2] with +`self.scope` as the argument. (This is conceptually the same operation that UA +does maximum once per every 24 hours.) + +[1]: #soft-update-algorithm +[2]: #get-registration-algorithm + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section update() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.1.6-service-worker-global-scope-unregister.html b/testing/web-platform/tests/service-workers/stub-4.1.6-service-worker-global-scope-unregister.html new file mode 100644 index 000000000..de1d64a6e --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.1.6-service-worker-global-scope-unregister.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: unregister()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-unregister"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`self.unregister()` method must run the [Unregistration algorithm][1] +implicitly passing `self.scope` as the argument. + +[1]: #unregistration-algorithm + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section unregister() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.1.7-service-worker-global-scope-onmessage.html b/testing/web-platform/tests/service-workers/stub-4.1.7-service-worker-global-scope-onmessage.html new file mode 100644 index 000000000..b93043948 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.1.7-service-worker-global-scope-onmessage.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: onmessage</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-global-scope-onmessage"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`self.onmessage` is the [event handler][1] that must be supported as attribute +by the `ServiceWorkerGlobalScope` object. `ServiceWorkerGlobalScope` objects +act as if they had an implicit `[MessagePort][2]` associated with them. This +port is part of a channel that is set up when the worker is created, but it is +not exposed. This object must never be garbage collected before the +`ServiceWorkerGlobalScope` object. + +All messages received by that port must immediately be retargeted at the +`ServiceWorkerGlobalScope` object. That is, an event named `message` using the +`[MessageEvent][3]` interface is dispatched on ServiceWorkerGlobalScope object. +The `event.source` of these `[MessageEvent][3]`s are instances of `[Client][4]`. + + + +[1]: http://goo.gl/rBfiz0 +[2]: http://goo.gl/tHBrI6 +[3]: http://goo.gl/S5e0b6 +[4]: #client-interface + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section onmessage so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.2-client.html b/testing/web-platform/tests/service-workers/stub-4.2-client.html new file mode 100644 index 000000000..4e8c3639d --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.2-client.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Client</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#client"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +[Constructor()] // no-op constructor +interface Client { + readonly attribute unsigned long id; + void postMessage(any message, DOMString targetOrigin, + optional sequence<Transferable> transfer); +}; +</pre> + +<!-- +The `Client` interface represents the window or the worker (defined as client) +that is [controlled][1] by the Service Worker. This object provides a no-op +constructor. Callers should note that only `Client` objects created by the user +agent (see [`this.clients.getServiced()`][2]) will provide meaningful +functionality. + +The `id` of a `Client` identifies the specific client object from the list of +client objects serviced by the Service Worker. The `postMessage(message, +targetOrigin, transfer)` method of a `[Client][3]`, when called, causes a +`[MessageEvent][4]` to be dispatched at the client object. + + + +[1]: #document-control +[2]: #get-serviced-method +[3]: #client-interface +[4]: http://goo.gl/4SLWiH +--> + + + <script type=text/plain id="untested_idls"> + interface Transferable {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + Client: ["throw new Error ('No object defined for the Client interface')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.3-service-worker-clients.html b/testing/web-platform/tests/service-workers/stub-4.3-service-worker-clients.html new file mode 100644 index 000000000..c8a967a73 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.3-service-worker-clients.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: ServiceWorkerClients</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#service-worker-clients"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +interface ServiceWorkerClients { + // A list of client objects, identifiable by ID, that correspond to windows + // (or workers) that are "controlled" by this SW + Promise<sequence<Client>?> getServiced(); + Promise<any> reloadAll(); +}; +</pre> + +<!-- +The `ServiceWorkerClients` interface represents a container for a list of +`[Client][1]` objects. + +[1]: #client-interface +--> + + + <script type=text/plain id="untested_idls"> + interface Client {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + ServiceWorkerClients: ["throw new Error ('No object defined for the ServiceWorkerClients interface')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.3.1-get-serviced-method.html b/testing/web-platform/tests/service-workers/stub-4.3.1-get-serviced-method.html new file mode 100644 index 000000000..b3cd06fdf --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.3.1-get-serviced-method.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: getServiced()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#get-serviced-method"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +The `getServiced()` method of a `ServiceWorkerClients`, when called, returns a +[Promise][1] that will resolve with a list of `[Client][2]` objects that are +[controlled][3] by this Service Worker. + +[1]: http://goo.gl/3TobQS +[2]: #client-interface +[3]: #document-control + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section getServiced() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.3.2-reloadall-method.html b/testing/web-platform/tests/service-workers/stub-4.3.2-reloadall-method.html new file mode 100644 index 000000000..c5a9dd454 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.3.2-reloadall-method.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: reloadAll()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#reloadall-method"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`reloadAll()` provides a mechanism for the worker to request synchronized +re-fetch of all documents whose URLs match the registration's [URL scope][1]. +An event named `reloadpage` is dispatched on the `navigator.serviceWorker` +object of each document. The in-document handlers may allow the event to +continue, request an extension (via [`e.waitUntil()`][2]), or cancel the +collective reload by calling [`e.preventDefault()`][3]. + +[1]: #url-scope +[2]: #wait-until-method +[3]: http://goo.gl/2zH6ie + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section reloadAll() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.4-request-objects.html b/testing/web-platform/tests/service-workers/stub-4.4-request-objects.html new file mode 100644 index 000000000..bad4ea159 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.4-request-objects.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Request Objects</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#request-objects"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +[Constructor(optional RequestInit init)] +interface Request { + attribute unsigned long timeout; + attribute DOMString url; + attribute ByteString method; + readonly attribute DOMString origin; + readonly attribute Mode mode; + attribute boolean synchronous; + attribute boolean forcePreflight; + attribute boolean omitCredentials; + readonly attribute DOMString referrer; + readonly attribute HeaderMap headers; // alternative: sequence<Header> headers; + attribute any body; +}; + +dictionary RequestInit { + unsigned long timeout = 0; + DOMString url; + boolean synchronous = false; + boolean forcePreflight = false; + boolean omitCredentials = false; + ByteString method = "GET"; + HeaderMap headers; + any body; +}; + +enum Mode { + "same origin", + "tainted cross-origin", + "CORS", + "CORS-with-forced-preflight" +}; + +[MapClass(DOMString, DOMString)] +interface HeaderMap { +}; +</pre> + + + + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + Request: ["throw new Error ('No object defined for the Request interface')"], + RequestInit: ["throw new Error ('No object defined for the RequestInit dictionary')"], + Mode: ["throw new Error ('No object defined for the Mode enum')"], + HeaderMap: ["throw new Error ('No object defined for the HeaderMap interface')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.5-response-objects.html b/testing/web-platform/tests/service-workers/stub-4.5-response-objects.html new file mode 100644 index 000000000..5ebf34314 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.5-response-objects.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Response Objects</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#response-objects"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<!-- +`Response` objects model HTTP responses. +--> +<script type=text/plain id="idl_0"> +[Constructor] +interface AbstractResponse { +}; + +interface OpaqueResponse : AbstractResponse { + readonly attribute unsigned short status; + readonly attribute ByteString statusText; + // Returns a filtered list of headers. See prose for details. + readonly attribute HeaderMap headers; + // No setter for headers + readonly attribute DOMString url; +}; + +interface CORSResponse : Response { + readonly attribute HeaderMap headers; +}; + +[Constructor(optional ResponseInit responseInitDict)] +interface Response : AbstractResponse { + attribute unsigned short status; + attribute ByteString statusText; + readonly attribute HeaderMap headers; + attribute DOMString url; + Promise<Blob> toBlob(); +}; + +dictionary ResponseInit { + unsigned short status = 200; + ByteString statusText = "OK"; + HeaderMap headers; +}; +</pre> + + + + <script type=text/plain id="untested_idls"> + interface HeaderMap {}; + interface Blob {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + AbstractResponse: ["throw new Error ('No object defined for the AbstractResponse interface')"], + OpaqueResponse: ["throw new Error ('No object defined for the OpaqueResponse interface')"], + CORSResponse: ["throw new Error ('No object defined for the CORSResponse interface')"], + Response: ["throw new Error ('No object defined for the Response interface')"], + ResponseInit: ["throw new Error ('No object defined for the ResponseInit dictionary')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.5.2-response.html b/testing/web-platform/tests/service-workers/stub-4.5.2-response.html new file mode 100644 index 000000000..fbc72f30a --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.5.2-response.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Response</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#response"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`Response` objects are mutable and constructable. They model HTTP responses. +The `fetch()` API returns this type for same-origin responses. + +It may be possible to set the `Location` header of a `Response` object to +someplace not in the current origin but this is not a security issue. +Cross-origin response bodies are opaque to script, and since only same-origin +documents will encounter these responses, the only systems the Service Worker +can "lie to" are same-origin (and therefore safe from the perspective of other +origins). + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section Response so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.5.4-opaque-response.html b/testing/web-platform/tests/service-workers/stub-4.5.4-opaque-response.html new file mode 100644 index 000000000..a91306f4c --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.5.4-opaque-response.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: OpaqueResponse</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#opaque-response"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`OpaqueResponse` objects are immutable but constructable. The `fetch()` API +returns this type for cross-origin responses. + +Their role is to encapsulate the security properties of the web platform. As +such, their `body` attribute will always be `undefined` and the list of +readable `headers` is heavily filtered. + +`OpaqueResponse` objects may be forwarded on to rendering documents in exactly +the same way as mutable `Response` objects. + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section OpaqueResponse so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.6-cache-objects.html b/testing/web-platform/tests/service-workers/stub-4.6-cache-objects.html new file mode 100644 index 000000000..befd67cb3 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.6-cache-objects.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Caches</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-objects"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +To allow authors to fully manage their content caches for offline use, the +`[ServiceWorkerGlobalScope][1]` execution context provides the caching methods +largely conforming to [ECMAScript 6 Map objects][2] with additional convenience +methods. A domain can have multiple, named `[Cache][3]` objects, whose contents +are entirely under the control of scripts. Caches are not shared across +domains, and they are completely isolated from the browser's HTTP cache. + +[1]: #service-worker-global-scope-interface +[2]: http://goo.gl/gNnDPO +[3]: #cache-interface + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section Caches so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.6.1-cache-lifetimes.html b/testing/web-platform/tests/service-workers/stub-4.6.1-cache-lifetimes.html new file mode 100644 index 000000000..f6c9ecbd1 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.6.1-cache-lifetimes.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Understanding Cache Lifetimes</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-lifetimes"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +The `[Cache][1]` instances are not part of the browser's HTTP cache. The +`[Cache][1]` objects are exactly what authors have to manage themselves. The +`[Cache][1]` objects do not get updated unless authors explicitly request them +to be. The `[Cache][1]` objects do not expire unless authors delete the +entries. The `[Cache][1]` objects do not disappear just because the Service +Worker script is updated. That is, caches are not updated automatically. +Updates must be manually managed. This implies that authors should version +their caches by name and make sure to use the caches only from the version of +the ServiceWorker that can safely operate on. + +[1]: #cache-interface + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section Understanding Cache Lifetimes so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html b/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html new file mode 100644 index 000000000..8999a4dd5 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.6.2-cache.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Cache</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#cache"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +[Exposed=(Window,Worker)] +interface Cache { + Promise<Response> match(RequestInfo request, optional CacheQueryOptions options); + Promise<sequence<Response>> matchAll(optional RequestInfo request, optional CacheQueryOptions options); + Promise<void> add(RequestInfo request); + Promise<void> addAll(sequence<RequestInfo> requests); + Promise<void> put(RequestInfo request, Response response); + Promise<boolean> delete(RequestInfo request, optional CacheQueryOptions options); + Promise<sequence<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options); +}; + +dictionary CacheQueryOptions { + boolean ignoreSearch = false; + boolean ignoreMethod = false; + boolean ignoreVary = false; + DOMString cacheName; +}; + +dictionary CacheBatchOperation { + DOMString type; + Request request; + Response response; + CacheQueryOptions options; +}; +</pre> + + + + <script type=text/plain id="untested_idls"> + interface AbstractResponse {}; + interface Request {}; + interface ScalarValueString {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + Cache: ["throw new Error ('No object defined for the Cache interface')"], + QueryParams: ["throw new Error ('No object defined for the QueryParams dictionary')"], + CacheIterationCallback: ["throw new Error ('No object defined for the CacheIterationCallback callback')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.6.3-cache-storage.html b/testing/web-platform/tests/service-workers/stub-4.6.3-cache-storage.html new file mode 100644 index 000000000..3f9e9b438 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.6.3-cache-storage.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: CacheStorage</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-storage"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +[Constructor(sequence<any> iterable)] +interface CacheStorage { + Promise<any> match(ScalarValueString url, optional DOMString cacheName); + Promise<Cache> get(DOMString key); + Promise<boolean> has(DOMString key); + Promise<any> set(DOMString key, Cache val); + Promise<any> clear(); + Promise<any> delete(DOMString key); + void forEach(CacheStorageIterationCallback callback, optional object thisArg); + Promise<sequence<any>> entries(); + Promise<sequence<DOMString>> keys(); + Promise<sequence<Cache>> values(); + Promise<unsigned long> size(); +}; + +callback CacheStorageIterationCallback = void (Cache value, DOMString key, CacheStorage map); +</pre> + +<!-- +**Note**:[CacheStorage][1]interface is designed to largely conform +to[ECMAScript 6 Map objects][2]but entirely async, and with additional +convenience methods. + +[1]: #cache-storage-interface +[2]: http://goo.gl/gNnDPO +--> + + + <script type=text/plain id="untested_idls"> + interface ScalarValueString {}; + interface Cache {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + CacheStorage: ["throw new Error ('No object defined for the CacheStorage interface')"], + CacheStorageIterationCallback: ["throw new Error ('No object defined for the CacheStorageIterationCallback callback')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.1-install-phase-event.html b/testing/web-platform/tests/service-workers/stub-4.7.1-install-phase-event.html new file mode 100644 index 000000000..994da0200 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.1-install-phase-event.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: InstallPhaseEvent</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#install-phase-event"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +interface InstallPhaseEvent : Event { + Promise<any> waitUntil(Promise<any> f); +}; +</pre> + +<!-- +Service Workers have two [Lifecycle events][1], `[install][2]` and +`[activate][3]`. Service Workers use the `[InstallPhaseEvent][4]` interface for +`[activate][3]` event and the `[InstallEvent][5]` interface, which inherits +from the `[InstallPhaseEvent][4]` interface, for `[install][2]` event. + +[1]: #lifecycle-events +[2]: #install-event +[3]: #activate-event +[4]: #install-phase-event-interface +[5]: #install-event-interface +--> + + + <script type=text/plain id="untested_idls"> + interface Event {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + InstallPhaseEvent: ["throw new Error ('No object defined for the InstallPhaseEvent interface')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.1.1-wait-until-method.html b/testing/web-platform/tests/service-workers/stub-4.7.1.1-wait-until-method.html new file mode 100644 index 000000000..318318b13 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.1.1-wait-until-method.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: event.waitUntil(f)</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#wait-until-method"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`event.waitUntil(f)` method, when called in `oninstall` or `onactivate`, +extends the lifetime of the event. When called in `oninstall`, it delays +treating the installing worker until the passed [Promise][1] resolves +successfully. This is primarily used to ensure that a `ServiceWorker` is not +active until all of the core caches it depends on are populated. When called in +`onactivate`, it delays treating the activating worker until the passed +[Promise][1] resolves successfully. This is primarily used to ensure that any +[Functional events][2] are not dispatched to the `ServiceWorker` until it +upgrades database schemas and deletes the outdated cache entries. + +[1]: http://goo.gl/3TobQS +[2]: #functional-events + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section event.waitUntil(f) so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.2-install-event.html b/testing/web-platform/tests/service-workers/stub-4.7.2-install-event.html new file mode 100644 index 000000000..77702ed02 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.2-install-event.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: install Event</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#install-event"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +An event named `[install][1]` using the `[InstallEvent][2]` interface is +dispatched on `ServiceWorkerGlobalScope` object when the state of the +associated `ServiceWorker` changes its value to `installing`. (See step 3 of +[_Installation algorithm][3]) + +[1]: #install-event +[2]: #install-event-interface +[3]: #installation-algorithm + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section install Event so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.2.1-install-event-section.html b/testing/web-platform/tests/service-workers/stub-4.7.2.1-install-event-section.html new file mode 100644 index 000000000..17d52c6b0 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.2.1-install-event-section.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: InstallEvent</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#install-event-section"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +interface InstallEvent : InstallPhaseEvent { + readonly attribute ServiceWorker? activeWorker; + void replace(); +}; +</pre> + +<!-- +Service Workers use the `[InstallEvent][1]` interface for `[install][2]` event. + +[1]: #install-event-interface +[2]: #install-event +--> + + + <script type=text/plain id="untested_idls"> + interface ServiceWorker {}; + interface InstallPhaseEvent {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + InstallEvent: ["throw new Error ('No object defined for the InstallEvent interface')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.2.2-replace-method.html b/testing/web-platform/tests/service-workers/stub-4.7.2.2-replace-method.html new file mode 100644 index 000000000..6981d3079 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.2.2-replace-method.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: event.replace()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#replace-method"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`replace()` interacts with `waitUntil` method in the following way: + +- Successful installation can be delayed by `waitUntil`, perhaps by + subsequent event handlers. +- Replacement only happens upon successful installation +- Therefore, replacement of the [active worker][1] (if any) is not + immediate, however it may occur as soon as the end of the current turn. + + + +[1]: #navigator-service-worker-active + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section event.replace() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.3-activate-event.html b/testing/web-platform/tests/service-workers/stub-4.7.3-activate-event.html new file mode 100644 index 000000000..2a0162e5f --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.3-activate-event.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: activate Event</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#activate-event"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +An event named `[activate][1]` using the `[InstallPhaseEvent][2]` interface is +dispatched on `ServiceWorkerGlobalScope` object when the state of the +associated `ServiceWorker` changes its value to `activating`. (See step 6 of +[_Activation algorithm][3]) + +Service Workers use the `[InstallPhaseEvent][4]` interface for `[activate][1]` +event. + + + +[1]: #activate-event +[2]: #install-phase-event +[3]: #activation-algorithm +[4]: #install-phase-event-interface + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section activate Event so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.4.1-fetch-event-section.html b/testing/web-platform/tests/service-workers/stub-4.7.4.1-fetch-event-section.html new file mode 100644 index 000000000..84d8c3880 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.4.1-fetch-event-section.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: FetchEvent</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#fetch-event-section"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + <script src=/resources/WebIDLParser.js></script> + <script src=/resources/idlharness.js></script> + + </head> + <body> + +<script type=text/plain id="idl_0"> +[Constructor] +interface FetchEvent : Event { + readonly attribute Request request; + readonly attribute Client client; // The window issuing the request. + readonly attribute Context context; + readonly attribute boolean isReload; + + void respondWith(Promise<AbstractResponse> r); + Promise<any> forwardTo(ScalarValueString url); + Promise<any> default(); +}; + +enum Context { + "connect", + "font", + "img", + "object", + "script", + "style", + "worker", + "popup", + "child", + "navigate" +}; +</pre> + +<!-- +Service Workers use the `[FetchEvent][1]` interface for `[fetch][2]` event. + +[1]: #fetch-event-interface +[2]: #fetch-event +--> + + + <script type=text/plain id="untested_idls"> + interface Request {}; + interface Client {}; + interface AbstractResponse {}; + interface ScalarValueString {}; + interface Event {}; + </pre> + + <script> + var idl_array = new IdlArray(); + idl_array.add_untested_idls(document.getElementById("untested_idls").textContent); + idl_array.add_idls(document.getElementById("idl_0").textContent); + idl_array.add_objects({ + FetchEvent: ["throw new Error ('No object defined for the FetchEvent interface')"], + Context: ["throw new Error ('No object defined for the Context enum')"] + }); + idl_array.test(); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.4.2-respond-with-method.html b/testing/web-platform/tests/service-workers/stub-4.7.4.2-respond-with-method.html new file mode 100644 index 000000000..416b8ef51 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.4.2-respond-with-method.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: event.respondWith(r)</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#respond-with-method"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`event.respondWith(r)` method must run the steps, from step 10 to step 15, +defined in the [_OnFetchRequest algorithm][1]. + +The `r` argument must resolve with a [AbstractResponse][2], else a +[NetworkError][3] is thrown. If the request is a top-level navigation and the +return value is a [OpaqueResponse][4] (an opaque response body), a +[NetworkError][3] is thrown. The final URL of all successful (non +network-error) responses is the [requested][5] URL. Renderer-side security +checks about tainting for cross-origin content are tied to the transparency (or +opacity) of the [Response][6] body, not URLs. + + + +[1]: #on-fetch-request-algorithm +[2]: #abstract-response-interface +[3]: http://w3c.github.io/dom/#networkerror +[4]: #opaque-response-interface +[5]: #request-objects +[6]: #response-interface + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section event.respondWith(r) so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.4.3-default-method.html b/testing/web-platform/tests/service-workers/stub-4.7.4.3-default-method.html new file mode 100644 index 000000000..deff7ac42 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.4.3-default-method.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: event.default()</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#default-method"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +`event.default()` method must run these steps: + +1. Let `promise` be a newly-created [promise][1]. +2. Return `promise.` +3. Run the following steps asynchronously: + 1. Let `request` be `event`'s `request`. + 2. Set `request`'s [skip service worker flag][2]. + 3. Let `response` be the result of running [fetch algorithm][3] with + `request` as its argument. + 4. If `response` is a [network error][4], then: + 1. Reject `promise` with a new [DOMException][5] whose name is + "[NetworkError][6]". + 5. Else, + 1. Resolve `promise` with a new [Response][7] object associated + with `response`. + + + +[1]: http://goo.gl/3TobQS +[2]: http://goo.gl/gP7IWW +[3]: http://goo.gl/fGMifs +[4]: http://goo.gl/jprjjc +[5]: http://goo.gl/A0U8qC +[6]: http://goo.gl/lud5HB +[7]: http://goo.gl/Deazjv + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section event.default() so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-4.7.4.4-is-reload-attribute.html b/testing/web-platform/tests/service-workers/stub-4.7.4.4-is-reload-attribute.html new file mode 100644 index 000000000..fffe5d5b2 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-4.7.4.4-is-reload-attribute.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: event.isReload</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#is-reload-attribute"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +Returns true if `event` was dispatched with the user's intention for the page +reload, and false otherwise. Pressing the refresh button should be considered a +reload while clicking a link and pressing the back button should not. The +behavior of the `Ctrl+l enter` is left to the implementations of the user +agents. + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section event.isReload so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-5.1-origin-relativity.html b/testing/web-platform/tests/service-workers/stub-5.1-origin-relativity.html new file mode 100644 index 000000000..469ce2975 --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-5.1-origin-relativity.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Origin Relativity</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#origin-relativity"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +One of the advanced concerns that major applications would encounter is whether +they can be hosted from a CDN. By definition, these are servers in other +places, often on other domains. Therefore, Service Workers cannot be hosted on +CDNs. But they can include resources via [importScripts()][1]. The reason for +this restriction is that Service Workers create the opportunity for a bad actor +to turn a bad day into a bad eternity. + +[1]: http://goo.gl/Owcfs2 + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section Origin Relativity so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/stub-5.2-cross-origin-resources.html b/testing/web-platform/tests/service-workers/stub-5.2-cross-origin-resources.html new file mode 100644 index 000000000..42c685b1d --- /dev/null +++ b/testing/web-platform/tests/service-workers/stub-5.2-cross-origin-resources.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<title>Service Workers: Cross-Origin Resources & CORS</title> + <head> + <link rel="help" href="https://w3c.github.io/ServiceWorker/#cross-origin-resources"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + + </head> + <body> + +<!-- + +Applications tend to cache items that come from a CDN or other domain. It is +possible to request many of them directly using <script>, <img>, <video> and +<link> elements. It would be hugely limiting if this sort of runtime +collaboration broke when offline. Similarly, it is possible to XHR many sorts +of off-domain resources when appropriate CORS headers are set. + +ServiceWorkers enable this by allowing `Cache`s to fetch and cache off-origin +items. Some restrictions apply, however. First, unlike same-origin resources +which are managed in the `Cache` as `[Promise][1]`s for `Response` instances, +the objects stored are `[Promise][1]`s for `OpaqueResponse` instances. +`OpaqueResponse` provides a much less expressive API than `Response`; the +bodies and headers cannot be read or set, nor many of the other aspects of +their content inspected. They can be passed to `respondWith()` and +`forwardTo()` in the same manner as `Response`s, but cannot be meaningfully +created programmatically. These limitations are necessary to preserve the +security invariants of the platform. Allowing `Cache`s to store them allows +applications to avoid re-architecting in most cases. + + + +[1]: http://goo.gl/3TobQS + +--> + + + + <script> + test(function() { + // not_implemented(); + }, "There are no tests for section Cross-Origin Resources & CORS so far."); + </script> + + </body> +</html> + diff --git a/testing/web-platform/tests/service-workers/tools/blink-import.py b/testing/web-platform/tests/service-workers/tools/blink-import.py new file mode 100644 index 000000000..355df0796 --- /dev/null +++ b/testing/web-platform/tests/service-workers/tools/blink-import.py @@ -0,0 +1,204 @@ +import os +import re +import shutil +import glob +import tempfile +import sys +from collections import defaultdict + +here = os.path.abspath(os.path.split(__file__)[0]) + +def get_extra_files(chromium_root): + return [(os.path.join(chromium_root, "LayoutTests", "http", "tests", "resources", "testharness-helpers.js"), + os.path.join("resources", "testharness-helpers.js"))] + +resources_re = re.compile("/?(?:\.\./)*resources/(testharness(?:report)?)\.js") + +def resources_path(line, depth): + return False, resources_re.sub(r"/resources/\1.js", line) + +php_re = re.compile("\.php") + +def python_to_php(line, depth): + return False, php_re.sub(".py", line) + +abs_testharness_helpers_re = re.compile("([\"'])/resources/testharness-helpers.js") +testharness_helpers_re = re.compile("\.\./((?:\.\./)*)resources/testharness-helpers.js") + +def testharness_helpers(line, depth): + if abs_testharness_helpers_re.findall(line): + return False, abs_testharness_helpers_re.sub(r"\1%sresources/testharness-helpers.js" % ("../" * (depth - 1)), line) + return False, testharness_helpers_re.sub(r"\1resources/testharness-helpers.js", line) + +serviceworker_path_re = re.compile("/serviceworker/") +def service_worker_path(line, depth): + return False, serviceworker_path_re.sub("/service-workers/", line) + +localhost_re = re.compile("localhost") +alt_host_re = re.compile("127\.0\.0\.1") +port_http_re = re.compile("8000") +port_https_re = re.compile("8000") + + +def server_names(line, depth): + line, count_0 = localhost_re.subn("{{host}}", line) + line, count_1 = alt_host_re.subn("{{domains[www]}}", line) + line, count_2 = port_http_re.subn("{{ports[http][0]}}", line) + line, count_3 = port_https_re.subn("{{ports[https][0]}}", line) + + count = count_0 + count_1 + count_2 + count_3 + + return bool(count), line + + +def source_paths(chromium_root): + for dirpath, dirnames, filenames in os.walk(chromium_root): + if "chromium" in dirnames: + dirnames.remove("chromium") + for filename in filenames: + if filename.endswith("-expected.txt") or filename.endswith(".php"): + continue + yield os.path.relpath(os.path.join(dirpath, filename), chromium_root) + + +def do_subs(path, line): + depth = len(os.path.split(os.path.sep)) + subs = [resources_path, python_to_php, testharness_helpers, service_worker_path, server_names] + file_is_template = False + for sub in subs: + added_template, line = sub(line, depth) + if added_template: + file_is_template = True + return file_is_template, line + +def get_head(git): + return git("rev-parse", "HEAD") + +def get_changes(git, path, old, new): + data = git("diff", "--name-status", "-z", "--no-renames", "%s..%s" % (old, new), "--", path) + items = data.split("\0") + rv = defaultdict(list) + for status, path in items: + rv[status].append(path) + + return rv + +def copy(src_path, out_dir, rel_path): + dest = os.path.normpath(os.path.join(out_dir, rel_path)) + dest_dir = os.path.split(dest)[0] + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + shutil.copy2(src_path, dest) + +def copy_local_files(local_files, out_root, tmp_dir): + for path in local_files: + rel_path = os.path.relpath(path, out_root) + copy(path, tmp_dir, rel_path) + +def copy_extra_files(chromium_root, tmp_dir): + for in_path, rel_path in get_extra_files(chromium_root): + copy(in_path, tmp_dir, rel_path) + +def sub_changed_filenames(filename_changes, f): + rv = [] + for line in f: + for in_name, out_name in filename_changes.iteritems(): + line = line.replace(in_name, out_name) + rv.append(line) + return "".join(rv) + +testharness_re = re.compile("<script[^>]*src=[\"']?/resources/testharness.js[\"' ][^>]*>") + +def is_top_level_test(path, data): + if os.path.splitext(path)[1] != ".html": + return False + for line in data: + if testharness_re.findall(line): + return True + return False + +def add_suffix(path, suffix): + root, ext = os.path.splitext(path) + return root + ".%s" % suffix + ext + +def main(): + if "--cache-tests" in sys.argv: + sw_path = os.path.join("LayoutTests", "http", "tests", "cachestorage") + out_root = os.path.abspath(os.path.join(here, "..", "cache-storage")) + elif "--sw-tests" in sys.argv: + sw_path = os.path.join("LayoutTests", "http", "tests", "serviceworkers") + out_root = os.path.abspath(os.path.join(here, "..", "service-worker")) + else: + raise ValueError("Must supply either --cache-tests or --sw-tests") + + chromium_root = os.path.abspath(sys.argv[1]) + + work_path = tempfile.mkdtemp() + + test_path = os.path.join(chromium_root, sw_path) + + local_files = glob.glob(os.path.normpath(os.path.join(here, "..", "resources", "*.py"))) + + if not os.path.exists(out_root): + os.mkdir(out_root) + + copy_local_files(local_files, out_root, work_path) + copy_extra_files(chromium_root, work_path) + + path_changes = {} + + for path in source_paths(test_path): + out_path = os.path.join(work_path, path) + out_dir = os.path.dirname(out_path) + if not os.path.exists(out_dir): + os.makedirs(out_dir) + with open(os.path.join(test_path, path), "r") as in_f: + data = [] + sub = False + for line in in_f: + sub_flag, output_line = do_subs(path, line) + data.append(output_line) + if sub_flag: + sub = True + is_test = is_top_level_test(out_path, data) + + initial_path = out_path + + if is_test: + path_1 = add_suffix(out_path, "https") + else: + path_1 = out_path + + if sub: + path_2 = add_suffix(out_path, "sub") + else: + path_2 = path_1 + + if path_2 != initial_path: + path_changes[initial_path] = path_2 + + with open(path_2, "w") as out_f: + out_f.write("".join(data)) + + filename_changes = {} + + for k, v in path_changes.iteritems(): + if os.path.basename(k) in filename_changes: + print "Got duplicate name:" + os.path.basename(k) + filename_changes[os.path.basename(k)] = os.path.basename(v) + + for path in source_paths(work_path): + full_path = os.path.join(work_path, path) + with open(full_path, "r") as f: + data = sub_changed_filenames(filename_changes, f) + with open(full_path, "w") as f: + f.write(data) + + for dirpath, dirnames, filenames in os.walk(work_path): + for filename in filenames: + in_path = os.path.join(dirpath, filename) + rel_path = os.path.relpath(in_path, work_path) + copy(in_path, out_root, rel_path) + +if __name__ == "__main__": + main() |