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
+ ']');