1 // Adapter for testharness.js-style tests with Service Workers
3 function service_worker_unregister_and_register(test, url, scope) {
4 if (!scope || scope.length == 0)
5 return Promise.reject(new Error('tests must define a scope'));
7 var options = { scope: scope };
8 return service_worker_unregister(test, scope)
10 return navigator.serviceWorker.register(url, options);
12 .catch(unreached_rejection(test,
13 'unregister and register should not fail'));
16 function service_worker_unregister(test, documentUrl) {
17 return navigator.serviceWorker.getRegistration(documentUrl)
18 .then(function(registration) {
20 return registration.unregister();
22 .catch(unreached_rejection(test, 'unregister should not fail'));
25 function service_worker_unregister_and_done(test, scope) {
26 return service_worker_unregister(test, scope)
27 .then(test.done.bind(test));
30 function unreached_fulfillment(test, prefix) {
31 return test.step_func(function(result) {
32 var error_prefix = prefix || 'unexpected fulfillment';
33 assert_unreached(error_prefix + ': ' + result);
37 // Rejection-specific helper that provides more details
38 function unreached_rejection(test, prefix) {
39 return test.step_func(function(error) {
40 var reason = error.message || error.name || error;
41 var error_prefix = prefix || 'unexpected rejection';
42 assert_unreached(error_prefix + ': ' + reason);
46 // Adds an iframe to the document and returns a promise that resolves to the
47 // iframe when it finishes loading. The caller is responsible for removing the
48 // iframe later if needed.
49 function with_iframe(url) {
50 return new Promise(function(resolve) {
51 var frame = document.createElement('iframe');
53 frame.onload = function() { resolve(frame); };
54 document.body.appendChild(frame);
58 function with_sandboxed_iframe(url, sandbox) {
59 return new Promise(function(resolve) {
60 var frame = document.createElement('iframe');
61 frame.sandbox = sandbox;
63 frame.onload = function() { resolve(frame); };
64 document.body.appendChild(frame);
68 function normalizeURL(url) {
69 return new URL(url, self.location).toString().replace(/#.*$/, '');
72 function wait_for_update(test, registration) {
73 if (!registration || registration.unregister == undefined) {
74 return Promise.reject(new Error(
75 'wait_for_update must be passed a ServiceWorkerRegistration'));
78 return new Promise(test.step_func(function(resolve) {
79 registration.addEventListener('updatefound', test.step_func(function() {
80 resolve(registration.installing);
85 function wait_for_state(test, worker, state) {
86 if (!worker || worker.state == undefined) {
87 return Promise.reject(new Error(
88 'wait_for_state must be passed a ServiceWorker'));
90 if (worker.state === state)
91 return Promise.resolve(state);
93 if (state === 'installing') {
94 switch (worker.state) {
99 return Promise.reject(new Error(
100 'worker is ' + worker.state + ' but waiting for ' + state));
104 if (state === 'installed') {
105 switch (worker.state) {
109 return Promise.reject(new Error(
110 'worker is ' + worker.state + ' but waiting for ' + state));
114 if (state === 'activating') {
115 switch (worker.state) {
118 return Promise.reject(new Error(
119 'worker is ' + worker.state + ' but waiting for ' + state));
123 if (state === 'activated') {
124 switch (worker.state) {
126 return Promise.reject(new Error(
127 'worker is ' + worker.state + ' but waiting for ' + state));
131 return new Promise(test.step_func(function(resolve) {
132 worker.addEventListener('statechange', test.step_func(function() {
133 if (worker.state === state)
139 // Declare a test that runs entirely in the ServiceWorkerGlobalScope. The |url|
140 // is the service worker script URL. This function:
141 // - Instantiates a new test with the description specified in |description|.
142 // The test will succeed if the specified service worker can be successfully
143 // registered and installed.
144 // - Creates a new ServiceWorker registration with a scope unique to the current
145 // document URL. Note that this doesn't allow more than one
146 // service_worker_test() to be run from the same document.
147 // - Waits for the new worker to begin installing.
148 // - Imports tests results from tests running inside the ServiceWorker.
149 function service_worker_test(url, description) {
150 // If the document URL is https://example.com/document and the script URL is
151 // https://example.com/script/worker.js, then the scope would be
152 // https://example.com/script/scope/document.
153 var scope = new URL('scope' + window.location.pathname,
154 new URL(url, window.location)).toString();
155 promise_test(function(test) {
156 return service_worker_unregister_and_register(test, url, scope)
157 .then(function(registration) {
158 add_completion_callback(function() {
159 registration.unregister();
161 return wait_for_update(test, registration)
162 .then(function(worker) {
163 return fetch_tests_from_worker(worker);
169 function base_path() {
170 return location.pathname.replace(/\/[^\/]*$/, '/');
173 function test_login(test, origin, username, password, cookie) {
174 return new Promise(function(resolve, reject) {
177 '/serviceworker/resources/fetch-access-control-login.html')
178 .then(test.step_func(function(frame) {
179 var channel = new MessageChannel();
180 channel.port1.onmessage = test.step_func(function() {
184 frame.contentWindow.postMessage(
185 {username: username, password: password, cookie: cookie},
186 origin, [channel.port2]);
191 function login(test, local, remote) {
192 var suffix = (local.indexOf("https") != -1) ? "s": "";
193 return test_login(test, local, 'username1' + suffix, 'password1' + suffix,
196 return test_login(test, remote, 'username2' + suffix,
197 'password2' + suffix, 'cookie2');
201 // Helper for testing with ServiceWorkerRegistration objects. Compares simple
202 // attributes defined on the interfaces.
203 function assert_registration_equals(actual, expected, description) {
204 assert_class_string(actual, 'ServiceWorkerRegistration', description);
205 ['scope', 'installing', 'waiting', 'active'].forEach(function(attribute) {
206 assert_equals(actual[attribute], expected[attribute],
207 description + ' Attributes differ: ' + attribute + '.');
211 // Asserts that two arrays |actual| and |expected| contain the same set of
212 // ServiceWorkerRegistration as determined by assert_registration_equals(). The
213 // corresponding elements must occupy corresponding indices in their respective
215 function assert_registration_array_equals(actual, expected, description) {
216 assert_true(Array.isArray(actual), description);
217 assert_equals(actual.length, expected.length, description);
218 actual.forEach(function(value, index) {
219 assert_registration_equals(value, expected[index],
220 description + ' : object[' + index + ']');