blob: ff5c793f2c2208e9ac298e6b82adf745ae4c65e9 [file] [log] [blame]
<!DOCTYPE html>
<title>Service Worker: navigator.serviceWorker.ready</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<body>
<script>
test(function() {
var promise = navigator.serviceWorker.ready;
assert_equals(promise, navigator.serviceWorker.ready,
'repeated access to ready without intervening ' +
'registrations should return the same Promise object');
}, 'ready returns the same Promise object');
promise_test(function(t) {
return with_iframe('resources/blank.html?uncontrolled')
.then(t.step_func(function(frame) {
var promise = frame.contentWindow.navigator.serviceWorker.ready;
t.add_cleanup(function() {
frame.remove();
});
assert_equals(Object.getPrototypeOf(promise),
frame.contentWindow.Promise.prototype,
'the Promise should be in the context of the ' +
'related document');
}));
}, 'ready returns a Promise object in the context of the related document');
promise_test(function(t) {
var url = 'resources/empty-worker.js';
var scope = 'resources/blank.html?ready-controlled';
var expected_url = normalizeURL(url);
var frame;
return service_worker_unregister_and_register(t, url, 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) {
t.add_cleanup(function() {
f.remove();
});
frame = f;
return frame.contentWindow.navigator.serviceWorker.ready;
})
.then(function(registration) {
assert_equals(registration.installing, null,
'installing should be null');
assert_equals(registration.waiting, null,
'waiting should be null');
assert_equals(registration.active.scriptURL, expected_url,
'active after ready should not be null');
assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
registration.active,
'the controller should be the active worker');
assert_in_array(registration.active.state,
['activating', 'activated'],
'.ready should be resolved when the registration ' +
'has an active worker');
});
}, 'ready on a controlled document');
promise_test(function(t) {
var url = 'resources/empty-worker.js';
var scope = 'resources/blank.html?ready-potential-controlled';
var expected_url = normalizeURL(url);
var frame;
return with_iframe(scope)
.then(function(f) {
t.add_cleanup(function() {
f.remove();
});
frame = f;
return navigator.serviceWorker.register(url, {scope:scope});
})
.then(function(r) {
add_completion_callback(function() {
r.unregister();
});
return frame.contentWindow.navigator.serviceWorker.ready;
})
.then(function(registration) {
assert_equals(registration.installing, null,
'installing should be null');
assert_equals(registration.waiting, null,
'waiting should be null.')
assert_equals(registration.active.scriptURL, expected_url,
'active after ready should not be null');
assert_in_array(registration.active.state,
['activating', 'activated'],
'.ready should be resolved when the registration ' +
'has an active worker');
assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
null,
'uncontrolled document should not have a controller');
});
}, 'ready on a potential controlled document');
promise_test(function(t) {
var url = 'resources/empty-worker.js';
var scope = 'resources/blank.html?ready-installing';
return service_worker_unregister(t, scope)
.then(function() {
return with_iframe(scope);
})
.then(function(f) {
var promise = f.contentWindow.navigator.serviceWorker.ready;
t.add_cleanup(function() {
f.remove();
});
navigator.serviceWorker.register(url, {scope: scope});
return promise;
})
.then(function(registration) {
add_completion_callback(function() {
registration.unregister();
});
assert_equals(registration.installing, null,
'installing should be null');
assert_equals(registration.waiting, null, 'waiting should be null');
assert_not_equals(registration.active, null,
'active after ready should not be null');
assert_in_array(registration.active.state,
['activating', 'activated'],
'.ready should be resolved when the registration ' +
'has an active worker');
});
}, 'ready on an iframe whose parent registers a new service worker');
promise_test(function(t) {
var url = 'resources/empty-worker.js';
var scope = 'resources/register-iframe.html';
var expected_url = normalizeURL(url);
return with_iframe(scope)
.then(function(f) {
t.add_cleanup(function() {
f.remove();
});
return f.contentWindow.navigator.serviceWorker.ready;
})
.then(function(registration) {
add_completion_callback(function() {
registration.unregister();
});
assert_equals(registration.installing, null,
'installing should be null');
assert_equals(registration.waiting, null, 'waiting should be null');
assert_not_equals(registration.active, null,
'active after ready should not be null');
assert_in_array(registration.active.state,
['activating', 'activated'],
'.ready should be resolved with "active worker"');
});
}, 'ready on an iframe that installs a new service worker');
promise_test(function(t) {
var url = 'resources/empty-worker.js';
var matched_scope = 'resources/blank.html?ready-after-match';
var longer_matched_scope = 'resources/blank.html?ready-after-match-longer';
var frame, registration;
return Promise.all([service_worker_unregister(t, matched_scope),
service_worker_unregister(t, longer_matched_scope)])
.then(function() {
return with_iframe(longer_matched_scope);
})
.then(function(f) {
t.add_cleanup(function() {
f.remove();
});
frame = f;
return navigator.serviceWorker.register(url, {scope: matched_scope});
})
.then(function(r) {
add_completion_callback(function() {
r.unregister();
});
registration = r;
return wait_for_state(t, r.installing, 'activated');
})
.then(function() {
return navigator.serviceWorker.register(
url, {scope: longer_matched_scope});
})
.then(function(r) {
add_completion_callback(function() {
r.unregister();
});
return frame.contentWindow.navigator.serviceWorker.ready;
})
.then(function(r) {
assert_equals(r.scope, normalizeURL(longer_matched_scope),
'longer matched registration should be returned');
assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
null, 'controller should be null');
});
}, 'ready after a longer matched registration registered');
promise_test(function(t) {
var url = 'resources/empty-worker.js';
var matched_scope = 'resources/blank.html?ready-after-resolve';
var longer_matched_scope =
'resources/blank.html?ready-after-resolve-longer';
var frame, registration;
return service_worker_unregister_and_register(t, url, matched_scope)
.then(function(r) {
add_completion_callback(function() {
r.unregister();
});
registration = r;
return wait_for_state(t, r.installing, 'activated');
})
.then(function() {
return with_iframe(longer_matched_scope);
})
.then(function(f) {
t.add_cleanup(function() {
f.remove();
});
frame = f;
return f.contentWindow.navigator.serviceWorker.ready;
})
.then(function(r) {
assert_equals(r.scope, normalizeURL(matched_scope),
'matched registration should be returned');
return navigator.serviceWorker.register(
url, {scope: longer_matched_scope});
})
.then(function(r) {
add_completion_callback(function() {
r.unregister();
});
return frame.contentWindow.navigator.serviceWorker.ready;
})
.then(function(r) {
assert_equals(r.scope, normalizeURL(matched_scope),
'ready should only be resolved once');
});
}, 'access ready after it has been resolved');
promise_test(async function(t) {
var url = 'resources/empty-worker.js';
var matched_scope = 'resources/blank.html?ready-after-resurrect';
let reg1 = await service_worker_unregister_and_register(t, url, matched_scope);
add_completion_callback(function() {
reg1.unregister();
});
await wait_for_state(t, reg1.installing, 'activated');
// Hold the worker alive with a controlled worker
let frame = await with_iframe(matched_scope);
add_completion_callback(function() {
frame.remove();
});
// Doom the registration as uninstalling.
await reg1.unregister();
// Access the ready promise while the registration is doomed.
let readyPromise = frame.contentWindow.navigator.serviceWorker.ready;
// Resurrect the doomed registration;
let reg2 = await service_worker_unregister_and_register(t, url, matched_scope);
assert_equals(reg1, reg2, 'existing registration should be resurrected');
// We are trying to test if the ready promise ever resolves here. Use
// an explicit timeout check here rather than forcing a full infrastructure
// level timeout.
let timeoutId;
let timeoutPromise = new Promise(resolve => {
timeoutId = setTimeout(_ => {
timeoutId = null;
resolve();
}, 500);
});
// This should resolve immediately since there is an alive registration
// with an active promise for the matching scope.
await Promise.race([readyPromise, timeoutPromise]);
assert_not_equals(timeoutId, null,
'ready promise should resolve before timeout');
clearTimeout(timeoutId);
// We should get here and not time out.
}, 'access ready on uninstalling registration that is resurrected');
</script>