+2017-08-03 Youenn Fablet <youenn@apple.com>
+
+ Import WPT service worker tests
+ https://bugs.webkit.org/show_bug.cgi?id=175053
+
+ Reviewed by Brady Eidson.
+
+ * TestExpectations: skipping service worker tests.
+
2017-08-03 Ms2ger <Ms2ger@igalia.com>
Test gardening.
imported/w3c/web-platform-tests/secure-contexts/shared-worker-insecure-first.https.html [ Skip ]
imported/w3c/web-platform-tests/secure-contexts/shared-worker-secure-first.https.html [ Skip ]
+# Service Workers are unsupported
+imported/w3c/web-platform-tests/service-workers [ Skip ]
+
#//////////////////////////////////////////////////////////////////////////////////////////
# End platform-specific tests.
#//////////////////////////////////////////////////////////////////////////////////////////
+2017-08-03 Youenn Fablet <youenn@apple.com>
+
+ Import WPT service worker tests
+ https://bugs.webkit.org/show_bug.cgi?id=175053
+
+ Reviewed by Brady Eidson.
+
+ Importing service-worker tests up to cfdfb48329b20e19b6492a317ac5181a99506fd2.
+
+ * resources/resource-files.json:
+ * resources/import-expectations.json:
+ * web-platform-tests/service-workers/:
+
2017-08-03 Chris Dumez <cdumez@apple.com>
Improve our support for referrer policies
"web-platform-tests/selectors": "skip",
"web-platform-tests/selectors-api": "skip",
"web-platform-tests/server-timing": "skip",
- "web-platform-tests/service-workers": "skip",
+ "web-platform-tests/service-workers": "import",
"web-platform-tests/shadow-dom": "import",
"web-platform-tests/shadow-dom/untriaged": "skip",
"web-platform-tests/staticrange": "skip",
"web-platform-tests/html/browsers/history/the-location-interface/non-automated",
"web-platform-tests/html/tools",
"web-platform-tests/images",
- "web-platform-tests/service-workers",
"web-platform-tests/tools"
],
"files": [
"web-platform-tests/test_keys_wdspec.html",
"web-platform-tests/upgrade-insecure-requests/support/post-origin-to-parent.html"
]
-}
\ No newline at end of file
+}
@ehsan
+@mkruisselbrink
+@mattto
<!DOCTYPE html>
<title>Cache Storage: Verify that Window and Workers see same storage</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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) {
function cache_test(test_function, description) {
promise_test(function(test) {
return create_temporary_cache(test)
- .then(test_function);
+ .then(function(cache) { return test_function(cache, test); });
}, description);
}
// 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.
+// {request: <Request object>, response: <Response object>}. Entries are
+// serially added to the cache in the order specified.
//
// |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) {
+ entries.forEach(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);
- });
- }))
+ p = p.then(function() {
+ return cache.put(entry.request.clone(), entry.response.clone())
+ .catch(function(e) {
+ assert_unreached(
+ 'Test setup failed for entry ' + entry.name + ': ' + e
+ );
+ });
+ });
+ });
+ return p
.then(function() {
assert_equals(Object.keys(hash).length, entries.length);
})
}
}), description);
}
+
+// Helper for testing with Request objects. Compares simple
+// attributes defined on the interfaces, as well as the headers.
+function assert_request_equals(actual, expected, description) {
+ assert_class_string(actual, "Request", description);
+ ["url"].forEach(function(attribute) {
+ assert_equals(actual[attribute], expected[attribute],
+ description + " Attributes differ: " + attribute + ".");
+ });
+ assert_header_equals(actual.headers, expected.headers, description);
+}
+
+// Asserts that two arrays |actual| and |expected| contain the same
+// set of Requests as determined by assert_request_equals(). The
+// corresponding elements must occupy corresponding indices in their
+// respective arrays.
+function assert_request_array_equals(actual, expected, description) {
+ assert_true(Array.isArray(actual), description);
+ assert_equals(actual.length, expected.length, description);
+ actual.forEach(function(value, index) {
+ assert_request_equals(value, expected[index],
+ description + " : object[" + index + "]");
+ });
+}
+
+// Deletes all caches, returning a promise indicating success.
+function delete_all_caches() {
+ return self.caches.keys()
+ .then(function(keys) {
+ return Promise.all(keys.map(self.caches.delete.bind(self.caches)));
+ });
+}
+++ /dev/null
-/*
- * 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);
- }
- });
-}
The tests in this directory were imported from the W3C repository.
Do NOT modify these tests directly in WebKit.
-Instead, create a pull request on the W3C CSS or WPT github:
- https://github.com/w3c/csswg-test
+Instead, create a pull request on the WPT github:
https://github.com/w3c/web-platform-tests
Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/resources/iframe.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/resources/simple.txt
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/resources/test-helpers.js
-/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/resources/testharness-helpers.js
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(),
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.add(),
'Cache.add should throw a TypeError when no arguments are given.');
}, 'Cache.add called with no arguments');
});
}, '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'),
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.add('javascript://this-is-not-http-mmkay'),
'Cache.add should throw a TypeError for non-HTTP/HTTPS URLs.');
}, 'Cache.add called with non-HTTP/HTTPS URL');
});
}, 'Cache.add called with Request object');
-cache_test(function(cache) {
+cache_test(function(cache, test) {
var request = new Request('../resources/simple.txt',
{method: 'POST', body: 'This is a body.'});
- return assert_promise_rejects(
- cache.add(request),
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.add(request),
'Cache.add should throw a TypeError for non-GET requests.');
}, 'Cache.add called with POST 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'),
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
+ new TypeError(),
+ cache.add('../resources/fetch-status.py?status=206'),
+ 'Cache.add should reject on partial response');
+ }, 'Cache.add with 206 response');
+
+cache_test(function(cache, test) {
+ var urls = ['../resources/fetch-status.py?status=206',
+ '../resources/fetch-status.py?status=200'];
+ var requests = urls.map(function(url) {
+ return new Request(url);
+ });
+ return promise_rejects(
+ test,
+ new TypeError(),
+ cache.addAll(requests),
+ 'Cache.addAll should reject with TypeError if any request fails');
+ }, 'Cache.addAll with 206 response');
+
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.add('this-does-not-exist-please-dont-create-it'),
'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'),
+
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.add('../resources/fetch-status.py?status=500'),
'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(),
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.addAll(),
'Cache.addAll with no arguments should throw TypeError.');
}, 'Cache.addAll with no arguments');
-cache_test(function(cache) {
+cache_test(function(cache, test) {
// 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(),
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.addAll(),
'Cache.addAll should throw TypeError for an undefined argument.');
}, 'Cache.addAll with a mix of valid and undefined arguments');
});
}, 'Cache.addAll with Request arguments');
-cache_test(function(cache) {
+cache_test(function(cache, test) {
// Assumes that ../resources/simple.txt and ../resources/blank.html exist.
// The second resource does not.
var urls = ['../resources/simple.txt',
var requests = urls.map(function(url) {
return new Request(url);
});
- return assert_promise_rejects(
- cache.addAll(requests),
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.addAll(requests),
'Cache.addAll should reject with TypeError if any request fails')
.then(function() {
- return Promise.all(urls.map(function(url) { return cache.match(url); }));
+ return Promise.all(urls.map(function(url) {
+ return cache.match(url);
+ }));
})
.then(function(matches) {
assert_array_equals(
});
}, 'Cache.addAll with a mix of succeeding and failing requests');
-cache_test(function(cache) {
+cache_test(function(cache, test) {
var request = new Request('../resources/simple.txt');
- return assert_promise_rejects(
- cache.addAll([request, request]),
+ return promise_rejects(
+ test,
'InvalidStateError',
+ cache.addAll([request, request]),
'Cache.addAll should throw InvalidStateError if the same request is added ' +
'twice.');
}, 'Cache.addAll called with the same Request object specified twice');
if (self.importScripts) {
importScripts('/resources/testharness.js');
- importScripts('../resources/testharness-helpers.js');
importScripts('../resources/test-helpers.js');
}
return new Response('Hello world!', { status: 200 });
}
-cache_test(function(cache) {
- return assert_promise_rejects(
- cache.delete(),
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.delete(),
'Cache.delete should reject with a TypeError when called with no ' +
'arguments.');
}, 'Cache.delete with no arguments');
});
}, 'Cache.delete called with a Request object');
+cache_test(function(cache) {
+ var request = new Request(test_url);
+ var response = new_test_response();
+ return cache.put(request, response)
+ .then(function() {
+ return cache.delete(new Request(test_url, {method: 'HEAD'}));
+ })
+ .then(function(result) {
+ assert_false(result,
+ 'Cache.delete should not match a non-GET request ' +
+ 'unless ignoreMethod option is set.');
+ return cache.match(test_url);
+ })
+ .then(function(result) {
+ assert_response_equals(result, response,
+ 'Cache.delete should leave non-matching response in the cache.');
+ return cache.delete(new Request(test_url, {method: 'HEAD'}),
+ {ignoreMethod: true});
+ })
+ .then(function(result) {
+ assert_true(result,
+ 'Cache.delete should match a non-GET request ' +
+ ' if ignoreMethod is true.');
+ });
+ }, 'Cache.delete called with a HEAD request');
+
+cache_test(function(cache) {
+ var vary_request = new Request('http://example.com/c',
+ {headers: {'Cookies': 'is-for-cookie'}});
+ var vary_response = new Response('', {headers: {'Vary': 'Cookies'}});
+ var mismatched_vary_request = new Request('http://example.com/c');
+
+ return cache.put(vary_request.clone(), vary_response.clone())
+ .then(function() {
+ return cache.delete(mismatched_vary_request.clone());
+ })
+ .then(function(result) {
+ assert_false(result,
+ 'Cache.delete should not delete if vary does not ' +
+ 'match unless ignoreVary is true');
+ return cache.delete(mismatched_vary_request.clone(),
+ {ignoreVary: true});
+ })
+ .then(function(result) {
+ assert_true(result,
+ 'Cache.delete should ignore vary if ignoreVary is true');
+ });
+ }, 'Cache.delete supports ignoreVary');
+
cache_test(function(cache) {
return cache.delete(test_url)
.then(function(result) {
});
}, 'Cache.delete with a non-existent entry');
-var cache_entries = {
- a: {
- request: new Request('http://example.com/abc'),
- response: new Response('')
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(entries.a_with_query.request,
+ { ignoreSearch: true })
+ .then(function(result) {
+ assert_response_array_equals(
+ result,
+ [
+ entries.a.response,
+ entries.a_with_query.response
+ ]);
+ return cache.delete(entries.a_with_query.request,
+ { ignoreSearch: true });
+ })
+ .then(function(result) {
+ return cache.matchAll(entries.a_with_query.request,
+ { ignoreSearch: true });
+ })
+ .then(function(result) {
+ assert_response_array_equals(result, []);
+ });
},
+ 'Cache.delete with ignoreSearch option (request with search parameters)');
- b: {
- request: new Request('http://example.com/b'),
- response: new Response('')
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(entries.a_with_query.request,
+ { ignoreSearch: true })
+ .then(function(result) {
+ assert_response_array_equals(
+ result,
+ [
+ entries.a.response,
+ entries.a_with_query.response
+ ]);
+ // cache.delete()'s behavior should be the same if ignoreSearch is
+ // not provided or if ignoreSearch is false.
+ return cache.delete(entries.a_with_query.request,
+ { ignoreSearch: false });
+ })
+ .then(function(result) {
+ return cache.matchAll(entries.a_with_query.request,
+ { ignoreSearch: true });
+ })
+ .then(function(result) {
+ assert_response_array_equals(result, [ entries.a.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);
-}
+ 'Cache.delete with ignoreSearch option (when it is specified as false)');
done();
--- /dev/null
+if (self.importScripts) {
+ importScripts('/resources/testharness.js');
+ importScripts('../resources/test-helpers.js');
+}
+
+cache_test(cache => {
+ return cache.keys()
+ .then(requests => {
+ assert_equals(
+ requests.length, 0,
+ 'Cache.keys should resolve to an empty array for an empty cache');
+ });
+ }, 'Cache.keys() called on an empty cache');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys('not-present-in-the-cache')
+ .then(function(result) {
+ assert_request_array_equals(
+ result, [],
+ 'Cache.keys should resolve with an empty array on failure.');
+ });
+ }, 'Cache.keys with no matching entries');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys(entries.a.request.url)
+ .then(function(result) {
+ assert_request_array_equals(result, [entries.a.request],
+ 'Cache.keys should match by URL.');
+ });
+ }, 'Cache.keys with URL');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys(entries.a.request)
+ .then(function(result) {
+ assert_request_array_equals(
+ result, [entries.a.request],
+ 'Cache.keys should match by Request.');
+ });
+ }, 'Cache.keys with Request');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys(new Request(entries.a.request.url))
+ .then(function(result) {
+ assert_request_array_equals(
+ result, [entries.a.request],
+ 'Cache.keys should match by Request.');
+ });
+ }, 'Cache.keys with new Request');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys(entries.a.request, {ignoreSearch: true})
+ .then(function(result) {
+ assert_request_array_equals(
+ result,
+ [
+ entries.a.request,
+ entries.a_with_query.request
+ ],
+ 'Cache.keys with ignoreSearch should ignore the ' +
+ 'search parameters of cached request.');
+ });
+ },
+ 'Cache.keys with ignoreSearch option (request with no search ' +
+ 'parameters)');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys(entries.a_with_query.request, {ignoreSearch: true})
+ .then(function(result) {
+ assert_request_array_equals(
+ result,
+ [
+ entries.a.request,
+ entries.a_with_query.request
+ ],
+ 'Cache.keys with ignoreSearch should ignore the ' +
+ 'search parameters of request.');
+ });
+ },
+ 'Cache.keys with ignoreSearch option (request with search parameters)');
+
+cache_test(function(cache) {
+ var request = new Request('http://example.com/');
+ var head_request = new Request('http://example.com/', {method: 'HEAD'});
+ var response = new Response('foo');
+ return cache.put(request.clone(), response.clone())
+ .then(function() {
+ return cache.keys(head_request.clone());
+ })
+ .then(function(result) {
+ assert_request_array_equals(
+ result, [],
+ 'Cache.keys should resolve with an empty array with a ' +
+ 'mismatched method.');
+ return cache.keys(head_request.clone(),
+ {ignoreMethod: true});
+ })
+ .then(function(result) {
+ assert_request_array_equals(
+ result,
+ [
+ request,
+ ],
+ 'Cache.keys with ignoreMethod should ignore the ' +
+ 'method of request.');
+ });
+ }, 'Cache.keys supports ignoreMethod');
+
+cache_test(function(cache) {
+ var vary_request = new Request('http://example.com/c',
+ {headers: {'Cookies': 'is-for-cookie'}});
+ var vary_response = new Response('', {headers: {'Vary': 'Cookies'}});
+ var mismatched_vary_request = new Request('http://example.com/c');
+
+ return cache.put(vary_request.clone(), vary_response.clone())
+ .then(function() {
+ return cache.keys(mismatched_vary_request.clone());
+ })
+ .then(function(result) {
+ assert_request_array_equals(
+ result, [],
+ 'Cache.keys should resolve with an empty array with a ' +
+ 'mismatched vary.');
+ return cache.keys(mismatched_vary_request.clone(),
+ {ignoreVary: true});
+ })
+ .then(function(result) {
+ assert_request_array_equals(
+ result,
+ [
+ vary_request,
+ ],
+ 'Cache.keys with ignoreVary should ignore the ' +
+ 'vary of request.');
+ });
+ }, 'Cache.keys supports ignoreVary');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys(entries.cat.request.url + '#mouse')
+ .then(function(result) {
+ assert_request_array_equals(
+ result,
+ [
+ entries.cat.request,
+ ],
+ 'Cache.keys should ignore URL fragment.');
+ });
+ }, 'Cache.keys with URL containing fragment');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys('http')
+ .then(function(result) {
+ assert_request_array_equals(
+ result, [],
+ 'Cache.keys should treat query as a URL and not ' +
+ 'just a string fragment.');
+ });
+ }, 'Cache.keys with string fragment "http" as query');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys()
+ .then(function(result) {
+ assert_request_array_equals(
+ result,
+ [
+ entries.a.request,
+ entries.b.request,
+ entries.a_with_query.request,
+ entries.A.request,
+ entries.a_https.request,
+ entries.a_org.request,
+ entries.cat.request,
+ entries.catmandu.request,
+ entries.cat_num_lives.request,
+ entries.cat_in_the_hat.request,
+ entries.non_2xx_response.request,
+ entries.error_response.request
+ ],
+ 'Cache.keys without parameters should match all entries.');
+ });
+ }, 'Cache.keys without parameters');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.keys(new Request(entries.cat.request.url, {method: 'HEAD'}))
+ .then(function(result) {
+ assert_request_array_equals(
+ result, [],
+ 'Cache.keys should not match HEAD request unless ignoreMethod ' +
+ 'option is set.');
+ });
+ }, 'Cache.keys with a HEAD Request');
+
+done();
if (self.importScripts) {
importScripts('/resources/testharness.js');
- importScripts('../resources/testharness-helpers.js');
importScripts('../resources/test-helpers.js');
}
});
}, 'Cache.match with Request');
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ var alt_response = new Response('', {status: 201});
+
+ return self.caches.open('second_matching_cache')
+ .then(function(cache) {
+ return cache.put(entries.a.request, alt_response.clone());
+ })
+ .then(function() {
+ return cache.match(entries.a.request);
+ })
+ .then(function(result) {
+ assert_response_equals(
+ result, entries.a.response,
+ 'Cache.match should match the first cache.');
+ });
+ }, 'Cache.match with multiple cache hits');
+
prepopulated_cache_test(simple_entries, function(cache, entries) {
return cache.match(new Request(entries.a.request.url))
.then(function(result) {
});
}, 'Cache.match with new Request');
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.match(new Request(entries.a.request.url, {method: 'HEAD'}))
+ .then(function(result) {
+ assert_equals(result, undefined,
+ 'Cache.match should not match HEAD Request.');
+ });
+ }, 'Cache.match with HEAD');
+
prepopulated_cache_test(simple_entries, function(cache, entries) {
return cache.match(entries.a.request,
{ignoreSearch: true})
},
'Cache.match with ignoreSearch option (request with search parameter)');
+cache_test(function(cache) {
+ var request = new Request('http://example.com/');
+ var head_request = new Request('http://example.com/', {method: 'HEAD'});
+ var response = new Response('foo');
+ return cache.put(request.clone(), response.clone())
+ .then(function() {
+ return cache.match(head_request.clone());
+ })
+ .then(function(result) {
+ assert_equals(
+ result, undefined,
+ 'Cache.match should resolve as undefined with a ' +
+ 'mismatched method.');
+ return cache.match(head_request.clone(),
+ {ignoreMethod: true});
+ })
+ .then(function(result) {
+ assert_response_equals(
+ result, response,
+ 'Cache.match with ignoreMethod should ignore the ' +
+ 'method of request.');
+ });
+ }, 'Cache.match supports ignoreMethod');
+
+cache_test(function(cache) {
+ var vary_request = new Request('http://example.com/c',
+ {headers: {'Cookies': 'is-for-cookie'}});
+ var vary_response = new Response('', {headers: {'Vary': 'Cookies'}});
+ var mismatched_vary_request = new Request('http://example.com/c');
+
+ return cache.put(vary_request.clone(), vary_response.clone())
+ .then(function() {
+ return cache.match(mismatched_vary_request.clone());
+ })
+ .then(function(result) {
+ assert_equals(
+ result, undefined,
+ 'Cache.match should resolve as undefined with a ' +
+ 'mismatched vary.');
+ return cache.match(mismatched_vary_request.clone(),
+ {ignoreVary: true});
+ })
+ .then(function(result) {
+ assert_response_equals(
+ result, vary_response,
+ 'Cache.match with ignoreVary should ignore the ' +
+ 'vary of request.');
+ });
+ }, 'Cache.match supports ignoreVary');
+
prepopulated_cache_test(simple_entries, function(cache, entries) {
return cache.match(entries.cat.request.url + '#mouse')
.then(function(result) {
});
}, 'Cache.match invoked multiple times for the same Request/Response');
+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.blob();
+ })
+ .then(function(blob) {
+ var sliced = blob.slice(2,8);
+
+ return new Promise(function (resolve, reject) {
+ var reader = new FileReader();
+ reader.onloadend = function(event) {
+ resolve(event.target.result);
+ };
+ reader.readAsText(sliced);
+ });
+ })
+ .then(function(text) {
+ assert_equals(text, 'simple',
+ 'A Response blob returned by Cache.match should be ' +
+ 'sliceable.' );
+ });
+ }, 'Cache.match blob should be sliceable');
+
prepopulated_cache_test(simple_entries, function(cache, entries) {
var request = new Request(entries.a.request.clone(), {method: 'POST'});
return cache.match(request)
});
}, 'Cache.match with a network error Response');
+cache_test(function(cache) {
+ // This test validates that we can get a Response from the Cache API,
+ // clone it, and read just one side of the clone. This was previously
+ // bugged in FF for Responses with large bodies.
+ var data = [];
+ data.length = 80 * 1024;
+ data.fill('F');
+ var response;
+ return cache.put('/', new Response(data.toString()))
+ .then(function(result) {
+ return cache.match('/');
+ })
+ .then(function(r) {
+ // Make sure the original response is not GC'd.
+ response = r;
+ // Return only the clone. We purposefully test that the other
+ // half of the clone does not need to be read here.
+ return response.clone().text();
+ })
+ .then(function(text) {
+ assert_equals(text, data.toString(), 'cloned body text can be read correctly');
+ });
+ }, 'Cache produces large Responses that can be cloned and read correctly.');
+
done();
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(
+ assert_response_array_equals(
result, [],
'Cache.matchAll should resolve with an empty array on failure.');
});
});
}, 'Cache.matchAll with new Request');
+prepopulated_cache_test(simple_entries, function(cache, entries) {
+ return cache.matchAll(new Request(entries.a.request.url, {method: 'HEAD'}),
+ {ignoreSearch: true})
+ .then(function(result) {
+ assert_response_array_equals(
+ result, [],
+ 'Cache.matchAll should not match HEAD Request.');
+ });
+ }, 'Cache.matchAll with HEAD');
+
prepopulated_cache_test(simple_entries, function(cache, entries) {
return cache.matchAll(entries.a.request,
{ignoreSearch: true})
.then(function(result) {
- assert_response_array_equivalent(
+ assert_response_array_equals(
result,
[
entries.a.response,
return cache.matchAll(entries.a_with_query.request,
{ignoreSearch: true})
.then(function(result) {
- assert_response_array_equivalent(
+ assert_response_array_equals(
result,
[
entries.a.response,
'search parameters of request.');
});
},
- 'Cache.matchAll with ignoreSearch option (request with search parameter)');
+ 'Cache.matchAll with ignoreSearch option (request with search parameters)');
+
+cache_test(function(cache) {
+ var request = new Request('http://example.com/');
+ var head_request = new Request('http://example.com/', {method: 'HEAD'});
+ var response = new Response('foo');
+ return cache.put(request.clone(), response.clone())
+ .then(function() {
+ return cache.matchAll(head_request.clone());
+ })
+ .then(function(result) {
+ assert_response_array_equals(
+ result, [],
+ 'Cache.matchAll should resolve with empty array for a ' +
+ 'mismatched method.');
+ return cache.matchAll(head_request.clone(),
+ {ignoreMethod: true});
+ })
+ .then(function(result) {
+ assert_response_array_equals(
+ result, [response],
+ 'Cache.matchAll with ignoreMethod should ignore the ' +
+ 'method of request.');
+ });
+ }, 'Cache.matchAll supports ignoreMethod');
+
+cache_test(function(cache) {
+ var vary_request = new Request('http://example.com/c',
+ {headers: {'Cookies': 'is-for-cookie'}});
+ var vary_response = new Response('', {headers: {'Vary': 'Cookies'}});
+ var mismatched_vary_request = new Request('http://example.com/c');
+
+ return cache.put(vary_request.clone(), vary_response.clone())
+ .then(function() {
+ return cache.matchAll(mismatched_vary_request.clone());
+ })
+ .then(function(result) {
+ assert_response_array_equals(
+ result, [],
+ 'Cache.matchAll should resolve as undefined with a ' +
+ 'mismatched vary.');
+ return cache.matchAll(mismatched_vary_request.clone(),
+ {ignoreVary: true});
+ })
+ .then(function(result) {
+ assert_response_array_equals(
+ result, [vary_response],
+ 'Cache.matchAll with ignoreVary should ignore the ' +
+ 'vary of request.');
+ });
+ }, 'Cache.matchAll supports ignoreVary');
prepopulated_cache_test(simple_entries, function(cache, entries) {
return cache.matchAll(entries.cat.request.url + '#mouse')
.then(function(result) {
- assert_response_array_equivalent(
+ assert_response_array_equals(
result,
[
entries.cat.response,
prepopulated_cache_test(simple_entries, function(cache, entries) {
return cache.matchAll('http')
.then(function(result) {
- assert_response_array_equivalent(
+ assert_response_array_equals(
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(simple_entries, function(cache, entries) {
+ return cache.matchAll()
+ .then(function(result) {
+ assert_response_array_equals(
+ result,
+ [
+ entries.a.response,
+ entries.b.response,
+ entries.a_with_query.response,
+ entries.A.response,
+ entries.a_https.response,
+ entries.a_org.response,
+ entries.cat.response,
+ entries.catmandu.response,
+ entries.cat_num_lives.response,
+ entries.cat_in_the_hat.response,
+ entries.non_2xx_response.response,
+ entries.error_response.response
+ ],
+ 'Cache.matchAll without parameters should match all entries.');
+ });
+ }, 'Cache.matchAll without parameters');
+
prepopulated_cache_test(vary_entries, function(cache, entries) {
return cache.matchAll('http://example.com/c')
.then(function(result) {
- assert_response_array_equivalent(
+ assert_response_array_equals(
result,
[
entries.vary_cookie_absent.response
{headers: {'Cookies': 'none-of-the-above'}}));
})
.then(function(result) {
- assert_response_array_equivalent(
+ assert_response_array_equals(
result,
[
],
{headers: {'Cookies': 'is-for-cookie'}}));
})
.then(function(result) {
- assert_response_array_equivalent(
+ assert_response_array_equals(
result,
[entries.vary_cookie_is_cookie.response],
'Cache.matchAll should match the entire header if a vary header ' +
return cache.matchAll('http://example.com/c',
{ignoreVary: true})
.then(function(result) {
- assert_response_array_equivalent(
+ assert_response_array_equals(
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 should support multiple vary request/response ' +
+ 'pairs.');
});
- }, 'Cache.matchAll with "ignoreVary" parameter');
+ }, 'Cache.matchAll with multiple vary pairs');
done();
if (self.importScripts) {
importScripts('/resources/testharness.js');
- importScripts('../resources/testharness-helpers.js');
importScripts('../resources/test-helpers.js');
}
});
}, 'Cache.put with an empty response body');
+cache_test(function(cache, test) {
+ var request = new Request(test_url);
+ var response = new Response('', {
+ status: 206,
+ headers: [['Content-Type', 'text/plain']]
+ });
+
+ return promise_rejects(
+ test,
+ new TypeError(),
+ cache.put(request, response),
+ 'Cache.put should reject 206 Responses with a TypeError.');
+ }, 'Cache.put with synthetic 206 response');
+
+cache_test(function(cache, test) {
+ var test_url = new URL('../resources/fetch-status.py?status=206', location.href).href;
+ var request = new Request(test_url);
+ var response;
+ return fetch(test_url)
+ .then(function(fetch_result) {
+ assert_equals(fetch_result.status, 206,
+ 'Test framework error: The status code should be 206.');
+ response = fetch_result.clone();
+ return promise_rejects(test, new TypeError, cache.put(request, fetch_result));
+ });
+ }, 'Cache.put with HTTP 206 response');
+
cache_test(function(cache) {
var test_url = new URL('../resources/fetch-status.py?status=500', location.href).href;
var request = new Request(test_url);
});
}, 'Cache.put with a string request');
-cache_test(function(cache) {
- return assert_promise_rejects(
- cache.put(new Request(test_url), 'Hello world!'),
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.put(new Request(test_url), 'Hello world!'),
'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_test(function(cache, test) {
+ return promise_rejects(
+ test,
+ new TypeError(),
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.put with a relative URL');
-cache_test(function(cache) {
+cache_test(function(cache, test) {
var request = new Request('http://example.com/foo', { method: 'HEAD' });
- return assert_promise_rejects(
- cache.put(request, new Response(test_body)),
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.put(request, new Response(test_body)),
'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),
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.put(new Request(test_url), null),
'Cache.put should throw a TypeError for a null response.');
}, 'Cache.put with a null response');
-cache_test(function(cache) {
+cache_test(function(cache, test) {
var request = new Request(test_url, {method: 'POST', body: test_body});
- return assert_promise_rejects(
- cache.put(request, new Response(test_body)),
+ return promise_rejects(
+ test,
new TypeError(),
+ cache.put(request, new Response(test_body)),
'Cache.put should throw a TypeError for a POST request.');
}, 'Cache.put with a POST request');
});
}, 'getReader() after Cache.put');
-cache_test(function(cache) {
- return assert_promise_rejects(
+cache_test(function(cache, test) {
+ return promise_rejects(
+ test,
+ new TypeError(),
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_test(function(cache, test) {
+ return promise_rejects(
+ test,
+ new TypeError(),
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();
if (self.importScripts) {
importScripts('/resources/testharness.js');
- importScripts('../resources/testharness-helpers.js');
importScripts('../resources/test-helpers.js');
}
if (self.importScripts) {
importScripts('/resources/testharness.js');
- importScripts('../resources/testharness-helpers.js');
importScripts('../resources/test-helpers.js');
}
return Promise.all(test_cache_list.map(function(key) {
return self.caches.open(key);
}))
- .then(function() { return caches.open('x'); })
+ .then(function() { return self.caches.open('x'); })
.then(function(cache) {
return cache.put(transaction.request.clone(),
transaction.response.clone());
});
}, 'CacheStorageMatch a string request');
+cache_test(function(cache) {
+ var transaction = create_unique_transaction();
+ return cache.put(transaction.request.clone(), transaction.response.clone())
+ .then(function() {
+ return self.caches.match(new Request(transaction.request.url,
+ {method: 'HEAD'}));
+ })
+ .then(function(response) {
+ assert_equals(response, undefined,
+ 'A HEAD request should not be matched');
+ });
+}, 'CacheStorageMatch a HEAD 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) {
})
.then(function(has_foo) {
assert_false(has_foo, "The cache should still not exist.");
- })
+ });
}, 'CacheStorageMatch with no caches available but name provided');
+cache_test(function(cache) {
+ var transaction = create_unique_transaction();
+
+ return self.caches.delete('')
+ .then(function() {
+ return self.caches.has('');
+ })
+ .then(function(has_cache) {
+ assert_false(has_cache, "The cache should not exist.");
+ return cache.put(transaction.request, transaction.response.clone());
+ })
+ .then(function() {
+ return self.caches.match(transaction.request, {cacheName: ''});
+ })
+ .then(function(response) {
+ assert_equals(response, undefined,
+ 'The response should not be found.');
+ return self.caches.open('');
+ })
+ .then(function(cache) {
+ return cache.put(transaction.request, transaction.response);
+ })
+ .then(function() {
+ return self.caches.match(transaction.request, {cacheName: ''});
+ })
+ .then(function(response) {
+ assert_response_equals(response, transaction.response,
+ 'The response should be matched.');
+ return self.caches.delete('');
+ });
+}, 'CacheStorageMatch with empty cache name provided');
+
+cache_test(function(cache) {
+ var request = new Request('http://example.com/?foo');
+ var no_query_request = new Request('http://example.com/');
+ var response = new Response('foo');
+ return cache.put(request.clone(), response.clone())
+ .then(function() {
+ return self.caches.match(no_query_request.clone());
+ })
+ .then(function(result) {
+ assert_equals(
+ result, undefined,
+ 'CacheStorageMatch should resolve as undefined with a ' +
+ 'mismatched query.');
+ return self.caches.match(no_query_request.clone(),
+ {ignoreSearch: true});
+ })
+ .then(function(result) {
+ assert_response_equals(
+ result, response,
+ 'CacheStorageMatch with ignoreSearch should ignore the ' +
+ 'query of the request.');
+ });
+ }, 'CacheStorageMatch supports ignoreSearch');
+
+cache_test(function(cache) {
+ var request = new Request('http://example.com/');
+ var head_request = new Request('http://example.com/', {method: 'HEAD'});
+ var response = new Response('foo');
+ return cache.put(request.clone(), response.clone())
+ .then(function() {
+ return self.caches.match(head_request.clone());
+ })
+ .then(function(result) {
+ assert_equals(
+ result, undefined,
+ 'CacheStorageMatch should resolve as undefined with a ' +
+ 'mismatched method.');
+ return self.caches.match(head_request.clone(),
+ {ignoreMethod: true});
+ })
+ .then(function(result) {
+ assert_response_equals(
+ result, response,
+ 'CacheStorageMatch with ignoreMethod should ignore the ' +
+ 'method of request.');
+ });
+ }, 'Cache.match supports ignoreMethod');
+
+cache_test(function(cache) {
+ var vary_request = new Request('http://example.com/c',
+ {headers: {'Cookies': 'is-for-cookie'}});
+ var vary_response = new Response('', {headers: {'Vary': 'Cookies'}});
+ var mismatched_vary_request = new Request('http://example.com/c');
+
+ return cache.put(vary_request.clone(), vary_response.clone())
+ .then(function() {
+ return self.caches.match(mismatched_vary_request.clone());
+ })
+ .then(function(result) {
+ assert_equals(
+ result, undefined,
+ 'CacheStorageMatch should resolve as undefined with a ' +
+ ' mismatched vary.');
+ return self.caches.match(mismatched_vary_request.clone(),
+ {ignoreVary: true});
+ })
+ .then(function(result) {
+ assert_response_equals(
+ result, vary_response,
+ 'CacheStorageMatch with ignoreVary should ignore the ' +
+ 'vary of request.');
+ });
+ }, 'CacheStorageMatch supports ignoreVary');
+
done();
if (self.importScripts) {
importScripts('/resources/testharness.js');
- importScripts('../resources/testharness-helpers.js');
importScripts('../resources/test-helpers.js');
}
});
}, 'CacheStorage.open');
+promise_test(function(t) {
+ var cache_name = 'cache-storage/bar';
+ var first_cache = null;
+ var second_cache = null;
+ return self.caches.open(cache_name)
+ .then(function(cache) {
+ first_cache = cache;
+ return self.caches.delete(cache_name);
+ })
+ .then(function() {
+ return first_cache.add('../resources/simple.txt');
+ })
+ .then(function() {
+ return self.caches.keys();
+ })
+ .then(function(cache_names) {
+ assert_equals(cache_names.indexOf(cache_name), -1);
+ return self.caches.open(cache_name);
+ })
+ .then(function(cache) {
+ second_cache = cache;
+ return second_cache.keys();
+ })
+ .then(function(keys) {
+ assert_equals(keys.length, 0);
+ return first_cache.keys();
+ })
+ .then(function(keys) {
+ assert_equals(keys.length, 1);
+ // Clean up
+ return self.caches.delete(cache_name);
+ });
+ }, 'CacheStorage.delete dooms, but does not delete immediately');
+
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.
}, 'CacheStorage.open with an empty name');
promise_test(function(t) {
- return assert_promise_rejects(
- self.caches.open(),
+ return promise_rejects(
+ t,
new TypeError(),
+ self.caches.open(),
'CacheStorage.open should throw TypeError if called with no arguments.');
}, 'CacheStorage.open with no arguments');
}, '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)
+ var unpaired_name = 'unpaired\uD800';
+ var converted_name = 'unpaired\uFFFD';
+
+ // The test assumes that a cache with converted_name does not
+ // exist, but if the implementation fails the test then such
+ // a cache will be created. Start off in a fresh state by
+ // deleting all caches.
+ return delete_all_caches()
+ .then(function() {
+ 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.open(unpaired_name); })
.then(function() { return self.caches.keys(); })
.then(function(keys) {
- assert_true(keys.indexOf(bad_name) !== -1,
+ assert_true(keys.indexOf(unpaired_name) !== -1,
'keys should include cache with bad name');
})
- .then(function() { return self.caches.has(bad_name); })
+ .then(function() { return self.caches.has(unpaired_name); })
.then(function(cache_exists) {
assert_true(cache_exists,
'CacheStorage names should be not be converted.');
The tests in this directory were imported from the W3C repository.
Do NOT modify these tests directly in WebKit.
-Instead, create a pull request on the W3C CSS or WPT github:
- https://github.com/w3c/csswg-test
+Instead, create a pull request on the WPT github:
https://github.com/w3c/web-platform-tests
Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
List of files:
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/script-tests/cache-add.js
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/script-tests/cache-delete.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/script-tests/cache-keys.js
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/script-tests/cache-match.js
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/script-tests/cache-matchAll.js
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/script-tests/cache-put.js
<!DOCTYPE html>
<title>Cache.add and Cache.addAll</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-add">
+<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>
<!DOCTYPE html>
<title>Cache.delete</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-delete">
+<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>
--- /dev/null
+<!DOCTYPE html>
+<title>Cache.keys</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-keys">
+<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-keys.js');
+</script>
<!DOCTYPE html>
<title>Cache.match</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-match">
+<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>
<!DOCTYPE html>
<title>Cache.matchAll</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-matchall">
+<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>
<!DOCTYPE html>
<title>Cache.put</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-put">
+<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>
<!DOCTYPE html>
<title>CacheStorage.keys</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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>
<!DOCTYPE html>
<title>CacheStorage.match</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage-match">
+<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>
<!DOCTYPE html>
<title>CacheStorage</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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>
<!DOCTYPE html>
<meta charset=utf-8>
<title>Cache Storage: Verify credentials are respected by Cache operations</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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>
The tests in this directory were imported from the W3C repository.
Do NOT modify these tests directly in WebKit.
-Instead, create a pull request on the W3C CSS or WPT github:
- https://github.com/w3c/csswg-test
+Instead, create a pull request on the WPT github:
https://github.com/w3c/web-platform-tests
Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
List of files:
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-add.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-delete.https.html
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-keys.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-match.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-matchAll.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-put.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-storage-match.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/cache-storage.https.html
-/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/credentials.html
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/serviceworker/credentials.https.html
The tests in this directory were imported from the W3C repository.
Do NOT modify these tests directly in WebKit.
-Instead, create a pull request on the W3C CSS or WPT github:
- https://github.com/w3c/csswg-test
+Instead, create a pull request on the WPT github:
https://github.com/w3c/web-platform-tests
Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
<!DOCTYPE html>
<title>Cache Storage: Cache.add and Cache.addAll</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-add">
+<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>
<!DOCTYPE html>
<title>Cache Storage: Cache.delete</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-delete">
+<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>
--- /dev/null
+<!DOCTYPE html>
+<title>Cache Storage: Cache.keys</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-keys">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/test-helpers.js"></script>
+<script src="../script-tests/cache-keys.js"></script>
<!DOCTYPE html>
<title>Cache Storage: Cache.match</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-match">
+<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>
<!DOCTYPE html>
<title>Cache.matchAll</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-matchall">
+<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>
<!DOCTYPE html>
<title>Cache Storage: Cache.put</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-put">
+<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>
<!DOCTYPE html>
<title>Cache Storage: CacheStorage.keys</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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>
<!DOCTYPE html>
<title>Cache Storage: CacheStorage.match</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage-match">
+<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>
<!DOCTYPE html>
<title>Cache Storage: CacheStorage</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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>
<!DOCTYPE html>
<title>Cache Storage: Verify access in sandboxed iframes</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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) {
The tests in this directory were imported from the W3C repository.
Do NOT modify these tests directly in WebKit.
-Instead, create a pull request on the W3C CSS or WPT github:
- https://github.com/w3c/csswg-test
+Instead, create a pull request on the WPT github:
https://github.com/w3c/web-platform-tests
Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
List of files:
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/window/cache-add.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/window/cache-delete.https.html
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/window/cache-keys.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/window/cache-match.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/window/cache-matchAll.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/window/cache-put.https.html
<!DOCTYPE html>
<title>Cache.add and Cache.addAll</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-add">
+<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>
<!DOCTYPE html>
<title>Cache.delete</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-delete">
+<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>
--- /dev/null
+<!DOCTYPE html>
+<title>Cache.keys</title>
+<link rel="help" href="https://w3c.github.io/ServiceWorker/#cache-keys">
+<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-keys.js'));
+</script>
<!DOCTYPE html>
<title>Cache.match</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-match">
+<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>
<!DOCTYPE html>
<title>Cache.matchAll</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-matchall">
+<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>
<!DOCTYPE html>
<title>Cache.put</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-put">
+<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>
<!DOCTYPE html>
<title>CacheStorage.keys</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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>
<!DOCTYPE html>
<title>CacheStorage.match</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage-match">
+<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>
<!DOCTYPE html>
<title>CacheStorage</title>
-<link rel="help" href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-storage">
+<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>
The tests in this directory were imported from the W3C repository.
Do NOT modify these tests directly in WebKit.
-Instead, create a pull request on the W3C CSS or WPT github:
- https://github.com/w3c/csswg-test
+Instead, create a pull request on the WPT github:
https://github.com/w3c/web-platform-tests
Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
List of files:
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-add.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-delete.https.html
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-keys.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-match.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-matchAll.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-put.https.html
--- /dev/null
+<!DOCTYPE html>
+<title>ServiceWorkerGlobalScope: ExtendableMessageEvent Constructor</title>
+<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/extendable-message-event-constructor-worker.js', document.title
+ );
+</script>
--- /dev/null
+<!DOCTYPE html>
+<title>ServiceWorkerGlobalScope: ExtendableMessageEvent</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../resources/test-helpers.sub.js'></script>
+<script src='./resources/extendable-message-event-utils.js'></script>
+<script>
+promise_test(function(t) {
+ var script = 'resources/extendable-message-event-worker.js';
+ var scope = 'resources/scope/extendable-message-event-from-toplevel';
+ var registration;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(r) {
+ registration = r;
+ add_completion_callback(function() { registration.unregister(); });
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ var saw_message = new Promise(function(resolve) {
+ navigator.serviceWorker.onmessage =
+ function(event) { resolve(event.data); }
+ });
+ var channel = new MessageChannel;
+ registration.active.postMessage('', [channel.port1]);
+ return saw_message;
+ })
+ .then(function(results) {
+ var expected = {
+ constructor: { name: 'ExtendableMessageEvent' },
+ origin: location.origin,
+ lastEventId: '',
+ source: {
+ constructor: { name: 'WindowClient' },
+ frameType: 'top-level',
+ url: location.href,
+ visibilityState: 'visible',
+ focused: true
+ },
+ ports: [ { constructor: { name: 'MessagePort' } } ]
+ };
+ ExtendableMessageEventUtils.assert_equals(results, expected);
+ });
+ }, 'Post an extendable message from a top-level client');
+
+promise_test(function(t) {
+ var script = 'resources/extendable-message-event-worker.js';
+ var scope = 'resources/scope/extendable-message-event-from-nested';
+ var frame;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(registration) {
+ add_completion_callback(function() { registration.unregister(); });
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope); })
+ .then(function(f) {
+ frame = f;
+ add_completion_callback(function() { frame.remove(); });
+ var saw_message = new Promise(function(resolve) {
+ frame.contentWindow.navigator.serviceWorker.onmessage =
+ function(event) { resolve(event.data); }
+ });
+ f.contentWindow.navigator.serviceWorker.controller.postMessage('');
+ return saw_message;
+ })
+ .then(function(results) {
+ var expected = {
+ constructor: { name: 'ExtendableMessageEvent' },
+ origin: location.origin,
+ lastEventId: '',
+ source: {
+ constructor: { name: 'WindowClient' },
+ url: frame.contentWindow.location.href,
+ frameType: 'nested',
+ visibilityState: 'visible',
+ focused: false
+ },
+ ports: []
+ };
+ ExtendableMessageEventUtils.assert_equals(results, expected);
+ });
+ }, 'Post an extendable message from a nested client');
+
+promise_test(function(t) {
+ var script = 'resources/extendable-message-event-loopback-worker.js';
+ var scope = 'resources/scope/extendable-message-event-loopback';
+ var registration;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(r) {
+ registration = r;
+ add_completion_callback(function() { registration.unregister(); });
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ var results = [];
+ var saw_message = new Promise(function(resolve) {
+ navigator.serviceWorker.onmessage = function(event) {
+ switch (event.data.type) {
+ case 'record':
+ results.push(event.data.results);
+ break;
+ case 'finish':
+ resolve(results);
+ break;
+ }
+ };
+ });
+ registration.active.postMessage({type: 'start'});
+ return saw_message;
+ })
+ .then(function(results) {
+ assert_equals(results.length, 2);
+
+ var expected_trial_1 = {
+ constructor: { name: 'ExtendableMessageEvent' },
+ origin: location.origin,
+ lastEventId: '',
+ source: {
+ constructor: { name: 'ServiceWorker' },
+ scriptURL: normalizeURL(script),
+ state: 'activated'
+ },
+ ports: []
+ };
+ assert_equals(results[0].trial, 1);
+ ExtendableMessageEventUtils.assert_equals(
+ results[0].event, expected_trial_1
+ );
+
+ var expected_trial_2 = {
+ constructor: { name: 'ExtendableMessageEvent' },
+ origin: location.origin,
+ lastEventId: '',
+ source: {
+ constructor: { name: 'ServiceWorker' },
+ scriptURL: normalizeURL(script),
+ state: 'activated'
+ },
+ ports: [],
+ };
+ assert_equals(results[1].trial, 2);
+ ExtendableMessageEventUtils.assert_equals(
+ results[1].event, expected_trial_2
+ );
+ });
+ }, 'Post loopback extendable messages');
+
+promise_test(function(t) {
+ var script1 = 'resources/extendable-message-event-ping-worker.js';
+ var script2 = 'resources/extendable-message-event-pong-worker.js';
+ var scope = 'resources/scope/extendable-message-event-pingpong';
+ var registration;
+
+ return service_worker_unregister_and_register(t, script1, scope)
+ .then(function(r) {
+ registration = r;
+ add_completion_callback(function() { registration.unregister(); });
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ // A controlled frame is necessary for keeping a waiting worker.
+ return with_iframe(scope);
+ })
+ .then(function(frame) {
+ add_completion_callback(function() { frame.remove(); });
+ return navigator.serviceWorker.register(script2, {scope: scope});
+ })
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'installed');
+ })
+ .then(function() {
+ var results = [];
+ var saw_message = new Promise(function(resolve) {
+ navigator.serviceWorker.onmessage = function(event) {
+ switch (event.data.type) {
+ case 'record':
+ results.push(event.data.results);
+ break;
+ case 'finish':
+ resolve(results);
+ break;
+ }
+ };
+ });
+ registration.active.postMessage({type: 'start'});
+ return saw_message;
+ })
+ .then(function(results) {
+ assert_equals(results.length, 2);
+
+ var expected_ping = {
+ constructor: { name: 'ExtendableMessageEvent' },
+ origin: location.origin,
+ lastEventId: '',
+ source: {
+ constructor: { name: 'ServiceWorker' },
+ scriptURL: normalizeURL(script1),
+ state: 'activated'
+ },
+ ports: []
+ };
+ assert_equals(results[0].pingOrPong, 'ping');
+ ExtendableMessageEventUtils.assert_equals(
+ results[0].event, expected_ping
+ );
+
+ var expected_pong = {
+ constructor: { name: 'ExtendableMessageEvent' },
+ origin: location.origin,
+ lastEventId: '',
+ source: {
+ constructor: { name: 'ServiceWorker' },
+ scriptURL: normalizeURL(script2),
+ state: 'installed'
+ },
+ ports: []
+ };
+ assert_equals(results[1].pingOrPong, 'pong');
+ ExtendableMessageEventUtils.assert_equals(
+ results[1].event, expected_pong
+ );
+ });
+ }, 'Post extendable messages among service workers');
+</script>
--- /dev/null
+<!DOCTYPE html>
+<title>ServiceWorkerGlobalScope: postMessage</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/postmessage-loopback-worker.js';
+ var scope = 'resources/scope/postmessage-loopback';
+ var registration;
+
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = function(event) {
+ resolve(event.data);
+ };
+ });
+ registration.active.postMessage({port: channel.port2},
+ [channel.port2]);
+ return saw_message;
+ })
+ .then(function(result) {
+ assert_equals(result, 'OK');
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Post loopback messages');
+
+promise_test(function(t) {
+ var script1 = 'resources/postmessage-ping-worker.js';
+ var script2 = 'resources/postmessage-pong-worker.js';
+ var scope = 'resources/scope/postmessage-pingpong';
+ var registration;
+ var frame;
+
+ return service_worker_unregister_and_register(t, script1, scope)
+ .then(function(r) {
+ registration = r;
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ // A controlled frame is necessary for keeping a waiting worker.
+ return with_iframe(scope);
+ })
+ .then(function(f) {
+ frame = f;
+ return navigator.serviceWorker.register(script2, {scope: scope});
+ })
+ .then(function(r) {
+ return wait_for_state(t, r.installing, 'installed');
+ })
+ .then(function() {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = function(event) {
+ resolve(event.data);
+ };
+ });
+ registration.active.postMessage({port: channel.port2},
+ [channel.port2]);
+ return saw_message;
+ })
+ .then(function(result) {
+ assert_equals(result, 'OK');
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ });
+ }, 'Post messages among service workers');
+
+</script>
importScripts('../../resources/worker-testharness.js');
test(function() {
- assert_throws({name: 'InvalidAccessError'}, function() {
- self.close();
- });
+ assert_false('close' in self);
}, 'ServiceWorkerGlobalScope close operation');
--- /dev/null
+var source;
+
+self.addEventListener('message', function(e) {
+ source = e.source;
+ throw 'testError';
+});
+
+self.addEventListener('error', function(e) {
+ source.postMessage({
+ error: e.error, filename: e.filename, message: e.message, lineno: e.lineno,
+ colno: e.colno});
+});
--- /dev/null
+importScripts('/resources/testharness.js');
+
+const TEST_OBJECT = { wanwan: 123 };
+const CHANNEL1 = new MessageChannel();
+const CHANNEL2 = new MessageChannel();
+const PORTS = [CHANNEL1.port1, CHANNEL1.port2, CHANNEL2.port1];
+function createEvent(initializer) {
+ if (initializer === undefined)
+ return new ExtendableMessageEvent('type');
+ return new ExtendableMessageEvent('type', initializer);
+}
+
+// These test cases are mostly copied from the following file in the Chromium
+// project (as of commit 848ad70823991e0f12b437d789943a4ab24d65bb):
+// third_party/WebKit/LayoutTests/fast/events/constructors/message-event-constructor.html
+
+test(function() {
+ assert_false(createEvent().bubbles);
+ assert_false(createEvent().cancelable);
+ assert_equals(createEvent().data, null);
+ assert_equals(createEvent().origin, '');
+ assert_equals(createEvent().lastEventId, '');
+ assert_equals(createEvent().source, null);
+ assert_array_equals(createEvent().ports, []);
+}, 'no initializer specified');
+
+test(function() {
+ assert_false(createEvent({ bubbles: false }).bubbles);
+ assert_true(createEvent({ bubbles: true }).bubbles);
+}, '`bubbles` is specified');
+
+test(function() {
+ assert_false(createEvent({ cancelable: false }).cancelable);
+ assert_true(createEvent({ cancelable: true }).cancelable);
+}, '`cancelable` is specified');
+
+test(function() {
+ assert_equals(createEvent({ data: TEST_OBJECT }).data, TEST_OBJECT);
+ assert_equals(createEvent({ data: undefined }).data, null);
+ assert_equals(createEvent({ data: null }).data, null);
+ assert_equals(createEvent({ data: false }).data, false);
+ assert_equals(createEvent({ data: true }).data, true);
+ assert_equals(createEvent({ data: '' }).data, '');
+ assert_equals(createEvent({ data: 'chocolate' }).data, 'chocolate');
+ assert_equals(createEvent({ data: 12345 }).data, 12345);
+ assert_equals(createEvent({ data: 18446744073709551615 }).data,
+ 18446744073709552000);
+ assert_equals(createEvent({ data: NaN }).data, NaN);
+ // Note that valueOf() is not called, when the left hand side is
+ // evaluated.
+ assert_false(
+ createEvent({ data: {
+ valueOf: function() { return TEST_OBJECT; } } }).data ==
+ TEST_OBJECT);
+ assert_equals(createEvent({ get data(){ return 123; } }).data, 123);
+ assert_throws({ name: 'Error' }, function() {
+ createEvent({ get data() { throw { name: 'Error' }; } }); });
+}, '`data` is specified');
+
+test(function() {
+ assert_equals(createEvent({ origin: 'melancholy' }).origin, 'melancholy');
+ assert_equals(createEvent({ origin: '' }).origin, '');
+ assert_equals(createEvent({ origin: null }).origin, 'null');
+ assert_equals(createEvent({ origin: false }).origin, 'false');
+ assert_equals(createEvent({ origin: true }).origin, 'true');
+ assert_equals(createEvent({ origin: 12345 }).origin, '12345');
+ assert_equals(
+ createEvent({ origin: 18446744073709551615 }).origin,
+ '18446744073709552000');
+ assert_equals(createEvent({ origin: NaN }).origin, 'NaN');
+ assert_equals(createEvent({ origin: [] }).origin, '');
+ assert_equals(createEvent({ origin: [1, 2, 3] }).origin, '1,2,3');
+ assert_equals(
+ createEvent({ origin: { melancholy: 12345 } }).origin,
+ '[object Object]');
+ // Note that valueOf() is not called, when the left hand side is
+ // evaluated.
+ assert_equals(
+ createEvent({ origin: {
+ valueOf: function() { return 'melancholy'; } } }).origin,
+ '[object Object]');
+ assert_equals(
+ createEvent({ get origin() { return 123; } }).origin, '123');
+ assert_throws({ name: 'Error' }, function() {
+ createEvent({ get origin() { throw { name: 'Error' }; } }); });
+}, '`origin` is specified');
+
+test(function() {
+ assert_equals(
+ createEvent({ lastEventId: 'melancholy' }).lastEventId, 'melancholy');
+ assert_equals(createEvent({ lastEventId: '' }).lastEventId, '');
+ assert_equals(createEvent({ lastEventId: null }).lastEventId, 'null');
+ assert_equals(createEvent({ lastEventId: false }).lastEventId, 'false');
+ assert_equals(createEvent({ lastEventId: true }).lastEventId, 'true');
+ assert_equals(createEvent({ lastEventId: 12345 }).lastEventId, '12345');
+ assert_equals(
+ createEvent({ lastEventId: 18446744073709551615 }).lastEventId,
+ '18446744073709552000');
+ assert_equals(createEvent({ lastEventId: NaN }).lastEventId, 'NaN');
+ assert_equals(createEvent({ lastEventId: [] }).lastEventId, '');
+ assert_equals(
+ createEvent({ lastEventId: [1, 2, 3] }).lastEventId, '1,2,3');
+ assert_equals(
+ createEvent({ lastEventId: { melancholy: 12345 } }).lastEventId,
+ '[object Object]');
+ // Note that valueOf() is not called, when the left hand side is
+ // evaluated.
+ assert_equals(
+ createEvent({ lastEventId: {
+ valueOf: function() { return 'melancholy'; } } }).lastEventId,
+ '[object Object]');
+ assert_equals(
+ createEvent({ get lastEventId() { return 123; } }).lastEventId,
+ '123');
+ assert_throws({ name: 'Error' }, function() {
+ createEvent({ get lastEventId() { throw { name: 'Error' }; } }); });
+}, '`lastEventId` is specified');
+
+test(function() {
+ assert_equals(createEvent({ source: CHANNEL1.port1 }).source, CHANNEL1.port1);
+ assert_equals(
+ createEvent({ source: self.registration.active }).source,
+ self.registration.active);
+ assert_equals(
+ createEvent({ source: CHANNEL1.port1 }).source, CHANNEL1.port1);
+ assert_throws(
+ { name: 'TypeError' }, function() { createEvent({ source: this }); },
+ 'source should be Client or ServiceWorker or MessagePort');
+}, '`source` is specified');
+
+test(function() {
+ // Valid message ports.
+ var passed_ports = createEvent({ ports: PORTS}).ports;
+ assert_equals(passed_ports[0], CHANNEL1.port1);
+ assert_equals(passed_ports[1], CHANNEL1.port2);
+ assert_equals(passed_ports[2], CHANNEL2.port1);
+ assert_array_equals(createEvent({ ports: [] }).ports, []);
+ assert_array_equals(createEvent({ ports: undefined }).ports, []);
+
+ // Invalid message ports.
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: [1, 2, 3] }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: TEST_OBJECT }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: null }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: this }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: false }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: true }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: '' }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: 'chocolate' }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: 12345 }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: 18446744073709551615 }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ ports: NaN }); });
+ assert_throws({ name: 'TypeError' },
+ function() { createEvent({ get ports() { return 123; } }); });
+ assert_throws({ name: 'Error' }, function() {
+ createEvent({ get ports() { throw { name: 'Error' }; } }); });
+ // Note that valueOf() is not called, when the left hand side is
+ // evaluated.
+ var valueOf = function() { return PORTS; };
+ assert_throws({ name: 'TypeError' }, function() {
+ createEvent({ ports: { valueOf: valueOf } }); });
+}, '`ports` is specified');
+
+test(function() {
+ var initializers = {
+ bubbles: true,
+ cancelable: true,
+ data: TEST_OBJECT,
+ origin: 'wonderful',
+ lastEventId: 'excellent',
+ source: CHANNEL1.port1,
+ ports: PORTS
+ };
+ assert_equals(createEvent(initializers).bubbles, true);
+ assert_equals(createEvent(initializers).cancelable, true);
+ assert_equals(createEvent(initializers).data, TEST_OBJECT);
+ assert_equals(createEvent(initializers).origin, 'wonderful');
+ assert_equals(createEvent(initializers).lastEventId, 'excellent');
+ assert_equals(createEvent(initializers).source, CHANNEL1.port1);
+ assert_equals(createEvent(initializers).ports[0], PORTS[0]);
+ assert_equals(createEvent(initializers).ports[1], PORTS[1]);
+ assert_equals(createEvent(initializers).ports[2], PORTS[2]);
+}, 'all initial values are specified');
--- /dev/null
+importScripts('./extendable-message-event-utils.js');
+
+self.addEventListener('message', function(event) {
+ switch (event.data.type) {
+ case 'start':
+ self.registration.active.postMessage(
+ {type: '1st', client_id: event.source.id});
+ break;
+ case '1st':
+ // 1st loopback message via ServiceWorkerRegistration.active.
+ var results = {
+ trial: 1,
+ event: ExtendableMessageEventUtils.serialize(event)
+ };
+ var client_id = event.data.client_id;
+ event.source.postMessage({type: '2nd', client_id: client_id});
+ event.waitUntil(clients.get(client_id)
+ .then(function(client) {
+ client.postMessage({type: 'record', results: results});
+ }));
+ break;
+ case '2nd':
+ // 2nd loopback message via ExtendableMessageEvent.source.
+ var results = {
+ trial: 2,
+ event: ExtendableMessageEventUtils.serialize(event)
+ };
+ var client_id = event.data.client_id;
+ event.waitUntil(clients.get(client_id)
+ .then(function(client) {
+ client.postMessage({type: 'record', results: results});
+ client.postMessage({type: 'finish'});
+ }));
+ break;
+ }
+ });
--- /dev/null
+importScripts('./extendable-message-event-utils.js');
+
+self.addEventListener('message', function(event) {
+ switch (event.data.type) {
+ case 'start':
+ // Send a ping message to another service worker.
+ self.registration.waiting.postMessage(
+ {type: 'ping', client_id: event.source.id});
+ break;
+ case 'pong':
+ var results = {
+ pingOrPong: 'pong',
+ event: ExtendableMessageEventUtils.serialize(event)
+ };
+ var client_id = event.data.client_id;
+ event.waitUntil(clients.get(client_id)
+ .then(function(client) {
+ client.postMessage({type: 'record', results: results});
+ client.postMessage({type: 'finish'});
+ }));
+ break;
+ }
+ });
--- /dev/null
+importScripts('./extendable-message-event-utils.js');
+
+self.addEventListener('message', function(event) {
+ switch (event.data.type) {
+ case 'ping':
+ var results = {
+ pingOrPong: 'ping',
+ event: ExtendableMessageEventUtils.serialize(event)
+ };
+ var client_id = event.data.client_id;
+ event.waitUntil(clients.get(client_id)
+ .then(function(client) {
+ client.postMessage({type: 'record', results: results});
+ event.source.postMessage({type: 'pong', client_id: client_id});
+ }));
+ break;
+ }
+ });
--- /dev/null
+var ExtendableMessageEventUtils = {};
+
+// Create a representation of a given ExtendableMessageEvent that is suitable
+// for transmission via the `postMessage` API.
+ExtendableMessageEventUtils.serialize = function(event) {
+ var ports = event.ports.map(function(port) {
+ return { constructor: { name: port.constructor.name } };
+ });
+ return {
+ constructor: {
+ name: event.constructor.name
+ },
+ origin: event.origin,
+ lastEventId: event.lastEventId,
+ source: {
+ constructor: {
+ name: event.source.constructor.name
+ },
+ url: event.source.url,
+ frameType: event.source.frameType,
+ visibilityState: event.source.visibilityState,
+ focused: event.source.focused
+ },
+ ports: ports
+ };
+};
+
+// Compare the actual and expected values of an ExtendableMessageEvent that has
+// been transformed using the `serialize` function defined in this file.
+ExtendableMessageEventUtils.assert_equals = function(actual, expected) {
+ assert_equals(
+ actual.constructor.name, expected.constructor.name, 'event constructor'
+ );
+ assert_equals(actual.origin, expected.origin, 'event `origin` property');
+ assert_equals(
+ actual.lastEventId,
+ expected.lastEventId,
+ 'event `lastEventId` property'
+ );
+
+ assert_equals(
+ actual.source.constructor.name,
+ expected.source.constructor.name,
+ 'event `source` property constructor'
+ );
+ assert_equals(
+ actual.source.url, expected.source.url, 'event `source` property `url`'
+ );
+ assert_equals(
+ actual.source.frameType,
+ expected.source.frameType,
+ 'event `source` property `frameType`'
+ );
+ assert_equals(
+ actual.source.visibilityState,
+ expected.source.visibilityState,
+ 'event `source` property `visibilityState`'
+ );
+ assert_equals(
+ actual.source.focused,
+ expected.source.focused,
+ 'event `source` property `focused`'
+ );
+
+ assert_equals(
+ actual.ports.length,
+ expected.ports.length,
+ 'event `ports` property length'
+ );
+
+ for (var idx = 0; idx < expected.ports.length; ++idx) {
+ assert_equals(
+ actual.ports[idx].constructor.name,
+ expected.ports[idx].constructor.name,
+ 'MessagePort #' + idx + ' constructor'
+ );
+ }
+};
--- /dev/null
+importScripts('./extendable-message-event-utils.js');
+
+self.addEventListener('message', function(event) {
+ event.source.postMessage(ExtendableMessageEventUtils.serialize(event));
+ });
--- /dev/null
+self.addEventListener('message', function(event) {
+ if ('port' in event.data) {
+ var port = event.data.port;
+
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(event) {
+ if ('pong' in event.data)
+ port.postMessage(event.data.pong);
+ };
+ self.registration.active.postMessage({ping: channel.port2},
+ [channel.port2]);
+ } else if ('ping' in event.data) {
+ event.data.ping.postMessage({pong: 'OK'});
+ }
+ });
--- /dev/null
+self.addEventListener('message', function(event) {
+ if ('port' in event.data) {
+ var port = event.data.port;
+
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function(event) {
+ if ('pong' in event.data)
+ port.postMessage(event.data.pong);
+ };
+
+ // Send a ping message to another service worker.
+ self.registration.waiting.postMessage({ping: channel.port2},
+ [channel.port2]);
+ }
+ });
--- /dev/null
+self.addEventListener('message', function(event) {
+ if ('ping' in event.data)
+ event.data.ping.postMessage({pong: 'OK'});
+ });
The tests in this directory were imported from the W3C repository.
Do NOT modify these tests directly in WebKit.
-Instead, create a pull request on the W3C CSS or WPT github:
- https://github.com/w3c/csswg-test
+Instead, create a pull request on the WPT github:
https://github.com/w3c/web-platform-tests
Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
------------------------------------------------------------------------
List of files:
/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/close-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/error-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-constructor-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-loopback-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-ping-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-pong-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-utils.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-loopback-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-ping-worker.js
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-pong-worker.js
/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js
/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js
--- /dev/null
+<!DOCTYPE html>
+<title>ServiceWorkerGlobalScope: Error event error message</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../resources/test-helpers.sub.js'></script>
+<script>
+promise_test(t => {
+ var script = 'resources/error-worker.js';
+ var scope = 'resources/scope/service-worker-error-event';
+ var error_name = 'testError'
+ return service_worker_unregister_and_register(t, script, scope)
+ .then(registration => {
+ var worker = registration.installing;
+ add_completion_callback(() => { registration.unregister(); });
+ return new Promise(function(resolve) {
+ navigator.serviceWorker.onmessage = resolve;
+ worker.postMessage('');
+ });
+ })
+ .then(e => {
+ assert_equals(e.data.error, error_name, 'error type');
+ assert_greater_than(
+ e.data.message.indexOf(error_name), -1, 'error message');
+ assert_greater_than(
+ e.data.filename.indexOf(script), -1, 'filename');
+ assert_equals(e.data.lineno, 5, 'error line number');
+ assert_equals(e.data.colno, 3, 'error column number');
+ });
+ }, 'Error handlers inside serviceworker should see the attributes of ' +
+ 'ErrorEvent');
+</script>
The tests in this directory were imported from the W3C repository.
Do NOT modify these tests directly in WebKit.
-Instead, create a pull request on the W3C CSS or WPT github:
- https://github.com/w3c/csswg-test
+Instead, create a pull request on the WPT github:
https://github.com/w3c/web-platform-tests
Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
------------------------------------------------------------------------
List of files:
/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/close.https.html
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/extendable-message-event-constructor.https.html
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/extendable-message-event.https.html
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/postmessage.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html
+/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/service-worker-error-event.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html
/LayoutTests/imported/w3c/web-platform-tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html
--- /dev/null
+<!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>
--- /dev/null
+<!doctype html>
+<meta charset=utf-8>
+<title>Service Worker: claim() should affect other 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 frame;
+
+ var init_scope = 'resources/blank.html?affect-other';
+ var init_worker_url = 'resources/empty.js';
+ var init_registration;
+ var init_workers = [undefined, undefined];
+
+ var claim_scope = 'resources/blank.html?affect-other-registration';
+ var claim_worker_url = 'resources/claim-worker.js';
+ var claim_registration;
+ var claim_worker;
+
+ return Promise.resolve()
+ // Register the first service worker to init_scope.
+ .then(() => navigator.serviceWorker.register(init_worker_url + '?v1',
+ {scope: init_scope}))
+ .then(r => {
+ init_registration = r;
+ init_workers[0] = r.installing;
+ return Promise.resolve()
+ .then(() => wait_for_state(t, init_workers[0], 'activated'))
+ .then(() => assert_array_equals([init_registration.active,
+ init_registration.waiting,
+ init_registration.installing],
+ [init_workers[0],
+ null,
+ null],
+ 'Wrong workers.'));
+ })
+
+ // Create an iframe as the client of the first service worker of init_scope.
+ .then(() => with_iframe(claim_scope))
+ .then(f => frame = f)
+
+ // Check the controller.
+ .then(() => frame.contentWindow.navigator.serviceWorker.getRegistration(
+ normalizeURL(init_scope)))
+ .then(r => assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ r.active,
+ '.controller should belong to init_scope.'))
+
+ // Register the second service worker to init_scope.
+ .then(() => navigator.serviceWorker.register(init_worker_url + '?v2',
+ {scope: init_scope}))
+ .then(r => {
+ assert_equals(r, init_registration, 'Should be the same registration');
+ init_workers[1] = r.installing;
+ return Promise.resolve()
+ .then(() => wait_for_state(t, init_workers[1], 'installed'))
+ .then(() => assert_array_equals([init_registration.active,
+ init_registration.waiting,
+ init_registration.installing],
+ [init_workers[0],
+ init_workers[1],
+ null],
+ 'Wrong workers.'));
+ })
+
+ // Register a service worker to claim_scope.
+ .then(() => navigator.serviceWorker.register(claim_worker_url,
+ {scope: claim_scope}))
+ .then(r => {
+ claim_registration = r;
+ claim_worker = r.installing;
+ return wait_for_state(t, claim_worker, 'activated')
+ })
+
+ // Let claim_worker claim the created iframe.
+ .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;
+ })
+
+ // Check the controller.
+ .then(() => frame.contentWindow.navigator.serviceWorker.getRegistration(
+ normalizeURL(claim_scope)))
+ .then(r => assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ r.active,
+ '.controller should belong to claim_scope.'))
+
+ // Check the status of created registrations and service workers.
+ .then(() => wait_for_state(t, init_workers[1], 'activated'))
+ .then(() => {
+ assert_array_equals([claim_registration.active,
+ claim_registration.waiting,
+ claim_registration.installing],
+ [claim_worker,
+ null,
+ null],
+ 'claim_worker should be the only worker.')
+
+ assert_array_equals([init_registration.active,
+ init_registration.waiting,
+ init_registration.installing],
+ [init_workers[1],
+ null,
+ null],
+ 'The waiting sw should become the active worker.')
+
+ assert_array_equals([init_workers[0].state,
+ init_workers[1].state,
+ claim_worker.state],
+ ['redundant',
+ 'activated',
+ 'activated'],
+ 'Wrong worker states.');
+ })
+
+ // Cleanup and finish testing.
+ .then(() => frame.remove())
+ .then(() => Promise.all([
+ init_registration.unregister(),
+ claim_registration.unregister()
+ ]))
+ .then(() => t.done());
+}, 'claim() should affect the originally controlling registration.');
+
+</script>
--- /dev/null
+<!doctype html>
+<meta charset=utf-8>
+<title></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 frame;
+ var resource = 'simple.txt';
+
+ var worker;
+ var scope = 'resources/';
+ var script = 'resources/claim-worker.js';
+
+ return Promise.resolve()
+ // Create the test iframe.
+ .then(() => with_iframe('resources/blank.html'))
+ .then(f => frame = f)
+
+ // Check the controller and test with fetch.
+ .then(() => assert_equals(frame.contentWindow.navigator.controller,
+ undefined,
+ 'Should have no controller.'))
+ .then(() => frame.contentWindow.fetch(resource))
+ .then(response => response.text())
+ .then(response_text => assert_equals(response_text,
+ 'a simple text file\n',
+ 'fetch() should not be intercepted.'))
+
+ // Register a service worker.
+ .then(() => service_worker_unregister_and_register(t, script, scope))
+ .then(r => worker = r.installing)
+ .then(() => wait_for_state(t, worker, 'activated'))
+
+ // Let the service worker claim the iframe.
+ .then(() => {
+ 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 saw_message;
+ })
+
+ // Check the controller and test with fetch.
+ .then(() => frame.contentWindow.navigator.serviceWorker.getRegistration(scope))
+ .then(r => assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ r.active,
+ 'Test iframe should be claimed.'))
+ .then(() => frame.contentWindow.fetch(resource))
+ .then(response => response.text())
+ .then(response_text => assert_equals(response_text,
+ 'Intercepted!',
+ 'fetch() should be intercepted.'))
+
+ // Cleanup this testcase.
+ .then(() => frame.remove())
+ .then(() => service_worker_unregister_and_done(t, scope));
+}, 'fetch() should be intercepted after the client is claimed.')
+
+</script>
+</body>
<!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>
<!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>
--- /dev/null
+<!DOCTYPE html>
+<title>Service Worker: Claim() when update happens after redirect</title>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.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();
+var OTHER_BASE_URL = host_info['HTTPS_REMOTE_ORIGIN'] + base_path();
+
+var WORKER_URL = OTHER_BASE_URL + 'resources/update-claim-worker.py'
+var SCOPE_URL = OTHER_BASE_URL + 'resources/redirect.py'
+var OTHER_IFRAME_URL = OTHER_BASE_URL +
+ 'resources/claim-with-redirect-iframe.html';
+
+// Different origin from the registration
+var REDIRECT_TO_URL = BASE_URL +
+ 'resources/claim-with-redirect-iframe.html?redirected';
+
+var REGISTER_IFRAME_URL = OTHER_IFRAME_URL + '?register=' +
+ encodeURIComponent(WORKER_URL) + '&' +
+ 'scope=' + encodeURIComponent(SCOPE_URL);
+var REDIRECT_IFRAME_URL = SCOPE_URL + '?Redirect=' +
+ encodeURIComponent(REDIRECT_TO_URL);
+var UPDATE_IFRAME_URL = OTHER_IFRAME_URL + '?update=' +
+ encodeURIComponent(SCOPE_URL);
+var UNREGISTER_IFRAME_URL = OTHER_IFRAME_URL + '?unregister=' +
+ encodeURIComponent(SCOPE_URL);
+
+var waiting_resolver = undefined;
+
+addEventListener('message', e => {
+ if (waiting_resolver !== undefined) {
+ waiting_resolver(e.data);
+ }
+ });
+
+function assert_with_iframe(url, expected_message) {
+ return new Promise(resolve => {
+ waiting_resolver = resolve;
+ with_iframe(url);
+ })
+ .then(data => assert_equals(data.message, expected_message));
+}
+
+// This test checks behavior when browser got a redirect header from in-scope
+// page and navigated to out-of-scope page which has a different origin from any
+// registrations.
+promise_test(t => {
+ return assert_with_iframe(REGISTER_IFRAME_URL, 'registered')
+ .then(() => assert_with_iframe(REDIRECT_IFRAME_URL, 'redirected'))
+ .then(() => assert_with_iframe(UPDATE_IFRAME_URL, 'updated'))
+ .then(() => assert_with_iframe(UNREGISTER_IFRAME_URL, 'unregistered'));
+ }, 'Claim works after redirection to another origin');
+
+</script>
+</body>
--- /dev/null
+<!DOCTYPE html>
+<title>Service Worker: Client.id</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+var test;
+var scope = 'resources/blank.html?client-id';
+var frame1, frame2;
+
+async_test(function(t) {
+ test = t;
+ service_worker_unregister_and_register(
+ t, 'resources/client-id-worker.js', scope)
+ .then(function(registration) {
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() { return with_iframe(scope + '#1'); })
+ .then(function(f) {
+ frame1 = f;
+ // To be sure Clients.matchAll() iterates in the same order.
+ f.focus();
+ return with_iframe(scope + '#2');
+ })
+ .then(function(f) {
+ frame2 = f;
+ var channel = new MessageChannel();
+ channel.port1.onmessage = t.step_func(on_message);
+ f.contentWindow.navigator.serviceWorker.controller.postMessage(
+ {port:channel.port2}, [channel.port2]);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Client.id returns the client\'s ID.');
+
+function on_message(e) {
+ // The result of two sequential clients.matchAll() calls in the SW.
+ // 1st matchAll() results in e.data[0], e.data[1].
+ // 2nd matchAll() results in e.data[2], e.data[3].
+ assert_equals(e.data.length, 4);
+ // All should be string values.
+ assert_equals(typeof e.data[0], 'string');
+ assert_equals(typeof e.data[1], 'string');
+ assert_equals(typeof e.data[2], 'string');
+ assert_equals(typeof e.data[3], 'string');
+ // Different clients should have different ids.
+ assert_not_equals(e.data[0], e.data[1]);
+ assert_not_equals(e.data[2], e.data[3]);
+ // Same clients should have an identical id.
+ assert_equals(e.data[0], e.data[2]);
+ assert_equals(e.data[1], e.data[3]);
+ frame1.remove();
+ frame2.remove();
+ service_worker_unregister_and_done(test, scope);
+}
+</script>
--- /dev/null
+<!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="/common/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());
+ frame.remove();
+ })
+ .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("SecurityError", function() { return frame.contentWindow.location.href });
+ frame.remove();
+ })
+ .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_cross_origin"))
+ .then(({result, url}) => {
+ assert_equals(result, "test_client_navigate_cross_origin");
+ assert_equals(url, "");
+ assert_throws("SecurityError", function() { return frame.contentWindow.location.href });
+ frame.remove();
+ })
+ .catch(unreached_rejection(t))
+ .then(___ => service_worker_unregister(t, scope));
+ }, "Frame location should not be accessible after cross-origin 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_about_blank"))
+ .then(({result, url}) => {
+ assert_equals(result, "test_client_navigate_about_blank");
+ assert_equals(
+ frame.contentWindow.location.href,
+ new URL("resources/client-navigate-frame.html",
+ location).toString());
+ frame.contentWindow.document.body.style = "background-color: green"
+ frame.remove();
+ })
+ .catch(unreached_rejection(t))
+ .then(___ => service_worker_unregister(t, scope));
+ }, "Frame location should not update on failed about:blank 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 => {
+ return 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_mixed_content"))
+ .then(({result, url}) => {
+ assert_equals(result, "test_client_navigate_mixed_content");
+ assert_equals(
+ frame.contentWindow.location.href,
+ new URL("resources/client-navigate-frame.html",
+ location).toString());
+ frame.contentWindow.document.body.style = "background-color: green"
+ frame.remove();
+ })
+ .catch(unreached_rejection(t))
+ .then(___ => service_worker_unregister(t, scope));
+ }, "Frame location should not update on failed mixed-content navigation");
+</script>
--- /dev/null
+<!DOCTYPE html>
+<title>Service Worker: Clients.get with window and worker clients</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-get-client-types';
+var frame_url = scope + '-frame.html';
+var shared_worker_url = scope + '-shared-worker.js';
+var worker_url = scope + '-worker.js';
+var client_ids = [];
+var frame;
+promise_test(function(t) {
+ return service_worker_unregister_and_register(
+ t, 'resources/clients-get-worker.js', scope)
+ .then(function(registration) {
+ add_completion_callback(function() { registration.unregister(); });
+ return wait_for_state(t, registration.installing, 'activated');
+ })
+ .then(function() {
+ return with_iframe(frame_url);
+ })
+ .then(function(f) {
+ frame = f;
+ add_completion_callback(function() { frame.remove(); });
+ frame.focus();
+ return wait_for_clientId();
+ })
+ .then(function(client_id) {
+ client_ids.push(client_id);
+ return new Promise(function(resolve) {
+ var w = new SharedWorker(shared_worker_url);
+ w.port.onmessage = function(e) {
+ resolve(e.data.clientId);
+ };
+ });
+ })
+ .then(function(client_id) {
+ client_ids.push(client_id);
+ var channel = new MessageChannel();
+ var w = new Worker(worker_url);
+ w.postMessage({cmd:'GetClientId', port:channel.port2},
+ [channel.port2]);
+ return new Promise(function(resolve) {
+ channel.port1.onmessage = function(e) {
+ resolve(e.data.clientId);
+ };
+ });
+ })
+ .then(function(client_id) {
+ client_ids.push(client_id);
+ var channel = new MessageChannel();
+ frame.contentWindow.postMessage('StartWorker', '*', [channel.port2]);
+ return new Promise(function(resolve) {
+ channel.port1.onmessage = function(e) {
+ resolve(e.data.clientId);
+ };
+ });
+ })
+ .then(function(client_id) {
+ client_ids.push(client_id);
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = resolve;
+ });
+ frame.contentWindow.navigator.serviceWorker.controller.postMessage(
+ {port: channel.port2, clientIds: client_ids}, [channel.port2]);
+ return saw_message;
+ })
+ .then(function(e) {
+ assert_equals(e.data.length, expected.length);
+ // We use these assert_not_equals because assert_array_equals doesn't
+ // print the error description when passed an undefined value.
+ assert_not_equals(e.data[0], undefined,
+ 'Window client should not be undefined');
+ assert_array_equals(e.data[0], expected[0], 'Window client');
+ assert_not_equals(e.data[1], undefined,
+ 'Shared worker client should not be undefined');
+ assert_array_equals(e.data[1], expected[1], 'Shared worker client');
+ assert_not_equals(e.data[2], undefined,
+ 'Worker(Started by main frame) client should not be undefined');
+ assert_array_equals(e.data[2], expected[2],
+ 'Worker(Started by main frame) client');
+ assert_not_equals(e.data[3], undefined,
+ 'Worker(Started by sub frame) client should not be undefined');
+ assert_array_equals(e.data[3], expected[3],
+ 'Worker(Started by sub frame) client');
+ });
+ }, 'Test Clients.get() with window and worker clients');
+
+function wait_for_clientId() {
+ return new Promise(function(resolve) {
+ 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, type, frameType
+ ['visible', true, normalizeURL(scope) + '-frame.html', 'window', 'nested'],
+ [undefined, undefined, normalizeURL(scope) + '-shared-worker.js', 'sharedworker', 'none'],
+ [undefined, undefined, normalizeURL(scope) + '-worker.js', 'worker', 'none'],
+ [undefined, undefined, normalizeURL(scope) + '-frame-worker.js', 'worker', 'none']
+];
+</script>
<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="/common/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 scope = 'resources/clients-get-frame.html';
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(
+ 'resources/clients-get-cross-origin-frame.html';
+// The ID of a client from the same origin as us.
+var my_origin_client_id;
+// This test asserts the behavior of the Client API in cases where the client
+// belongs to a foreign origin. It does this by creating an iframe with a
+// foreign origin which connects to a server worker in the current origin.
+promise_test(function(t) {
+ return service_worker_unregister_and_register(
t, 'resources/clients-get-worker.js', scope)
.then(function(registration) {
+ add_completion_callback(function() { registration.unregister(); });
return wait_for_state(t, registration.installing, 'activated');
})
.then(function() {
return with_iframe(scope);
})
+ // Create a same-origin client and use it to populate |my_origin_client_id|.
.then(function(frame1) {
- myOriginClientId = frame1.contentDocument.body.textContent;
+ add_completion_callback(function() { frame1.remove(); });
+ 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);
+ });
+ })
+ // Create a cross-origin client. We'll communicate with this client to
+ // test the cross-origin service worker's behavior.
+ .then(function(client_id) {
+ my_origin_client_id = client_id;
return with_iframe(other_origin_iframe);
})
+ // Post the 'getClientId' message to the cross-origin client. The client
+ // will then ask its service worker to look up |my_origin_client_id| via
+ // Clients#get. Since this client ID is for a different origin, we expect
+ // the client to not be found.
.then(function(frame2) {
- window.addEventListener('message', on_message_other_origin, false);
+ add_completion_callback(function() { frame2.remove(); });
+
frame2.contentWindow.postMessage(
- {clientId: myOriginClientId,
- message: 'get_client_id'},
- host_info['HTTPS_REMOTE_ORIGIN']);
- })
- .catch(unreached_rejection(t));
- });
+ {clientId: my_origin_client_id, type: 'getClientId'},
+ host_info['HTTPS_REMOTE_ORIGIN']
+ );
-function on_message_other_origin(e) {
- assert_equals(e.data.result, undefined);
- t.done();
-}
+ return new Promise(function(resolve) {
+ window.addEventListener('message', function(e) {
+ if (e.data && e.data.type === 'clientId') {
+ resolve(e.data.value);
+ }
+ });
+ });
+ })
+ .then(function(client_id) {
+ assert_equals(client_id, undefined, 'iframe client ID');
+ });
+ }, 'Test Clients.get() cross origin');
</script>
<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 client_ids = [];
var frame;
-t.step(function() {
- service_worker_unregister_and_register(
+promise_test(function(t) {
+ return service_worker_unregister_and_register(
t, 'resources/clients-get-worker.js', scope)
.then(function(registration) {
+ add_completion_callback(function() { registration.unregister(); });
return wait_for_state(t, registration.installing, 'activated');
})
.then(function() {
return with_iframe(scope + '#1');
})
.then(function(frame1) {
+ add_completion_callback(function() { frame1.remove(); });
frame1.focus();
return wait_for_clientId();
})
- .then(function(clientId) {
- clientIds.push(clientId);
+ .then(function(client_id) {
+ client_ids.push(client_id);
return with_iframe(scope + '#2');
})
.then(function(frame2) {
frame = frame2;
+ add_completion_callback(function() { frame2.remove(); });
return wait_for_clientId();
})
- .then(function(clientId) {
- clientIds.push(clientId);
+ .then(function(client_id) {
+ client_ids.push(client_id, 'invalid-id');
var channel = new MessageChannel();
- channel.port1.onmessage = t.step_func(on_message);
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = resolve;
+ });
frame.contentWindow.navigator.serviceWorker.controller.postMessage(
- {port:channel.port2, clientIds:clientIds,
- message: 'get_client_ids'}, [channel.port2]);
+ {port: channel.port2, clientIds: client_ids}, [channel.port2]);
+ return saw_message;
})
- .catch(unreached_rejection(t));
- });
+ .then(function(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]);
+ });
+ }, 'Test Clients.get()');
function wait_for_clientId() {
return new Promise(function(resolve, reject) {
function get_client_id(e) {
- window.removeEventListener("message", get_client_id);
+ window.removeEventListener('message', get_client_id);
resolve(e.data.clientId);
}
- window.addEventListener("message", get_client_id, false);
+ 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'],
+ // visibilityState, focused, url, type, frameType
+ ['visible', true, normalizeURL(scope) + '#1', 'window', 'nested'],
+ ['visible', false, normalizeURL(scope) + '#2', 'window', '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>
var scope = 'resources/clients-matchall-client-types';
var iframe_url = scope + '-iframe.html';
var shared_worker_url = scope + '-shared-worker.js';
+var dedicated_worker_url = scope + '-dedicated-worker.js';
-/* visibilityState, focused, url, frameType */
-var expected_without_type = [
- ['visible', true, new URL(iframe_url, location).href, 'nested']
+/* visibilityState, focused, url, type, frameType */
+var expected_only_window = [
+ ['visible', true, new URL(iframe_url, location).href, 'window', 'nested']
];
-var expected_with_window = [
- ['visible', true, new URL(iframe_url, location).href, 'nested']
+var expected_only_shared_worker = [
+ [undefined, undefined, new URL(shared_worker_url, location).href, 'sharedworker', 'none']
];
-var expected_with_shared_worker = [
- [,,new URL(shared_worker_url, location).href, 'none']
+var expected_only_dedicated_worker = [
+ [undefined, undefined, new URL(dedicated_worker_url, location).href, 'worker', 'none']
];
-var expected_with_all = [
- ['visible', true, new URL(iframe_url, location).href, 'nested'],
- [,,new URL(shared_worker_url, location).href, 'none']
+var expected_all_clients = [
+ expected_only_window[0], expected_only_shared_worker[0],
+ expected_only_dedicated_worker[0]
];
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();
- };
+ channel.port1.onmessage = function(e) { resolve(e.data); };
frame.contentWindow.navigator.serviceWorker.controller.postMessage(
{port:channel.port2, options:query_options},
[channel.port2]);
+ }).then(function(data) {
+ if (typeof data === 'string') {
+ throw new Error(data);
+ }
+
+ assert_equals(data.length, expected.length, 'result count');
+
+ for (var i = 0; i < data.length; ++i) {
+ assert_array_equals(data[i], expected[i]);
+ }
});
}
+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 test_matchall(frame, expected_only_window, {});
+ })
+ .then(function() {
+ return test_matchall(frame, expected_only_window, {type:'window'});
+ })
+ .then(function() {
+ frame.remove();
+ return service_worker_unregister_and_done(t, scope);
+ })
+ .catch(unreached_rejection(t));
+ }, 'Verify matchAll() with window client type');
+
promise_test(function(t) {
var frame;
return service_worker_unregister_and_register(
});
})
.then(function() {
- return test_matchall(frame, expected_without_type, {});
+ return new Promise(function(resolve, reject) {
+ var w = new Worker(dedicated_worker_url);
+ w.onmessage = resolve;
+ w.postMessage('Start');
+ });
+ })
+ .then(function() {
+ return test_matchall(frame, expected_only_window, {});
+ })
+ .then(function() {
+ return test_matchall(frame, expected_only_window, {type:'window'});
+ })
+ .then(function() {
+ return test_matchall(frame, expected_only_shared_worker,
+ {type:'sharedworker'});
+ })
+ .then(function() {
+ return test_matchall(frame, expected_only_dedicated_worker,
+ {type:'worker'});
})
.then(function() {
- return test_matchall(frame, expected_with_window, {type:'window'});
+ return test_matchall(frame, expected_all_clients, {type:'all'});
})
- //.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');
+}, 'Verify matchAll() with {window, sharedworker, worker} client types');
</script>
--- /dev/null
+<!DOCTYPE html>
+<title>Service Worker: Clients.matchAll with exact controller</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+const scope = 'resources/blank.html?clients-matchAll';
+const t = async_test('Test Clients.matchAll() with exact controller');
+let frames = [];
+
+function checkWorkerClients(worker, expected) {
+ return new Promise((resolve, reject) => {
+ let channel = new MessageChannel();
+ channel.port1.onmessage = evt => {
+ try {
+ assert_equals(evt.data.length, expected.length);
+ &nb