Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<script>
'use strict';
function send_message_to_worker_and_wait_for_response(worker, message) {
return new Promise(resolve => {
// Use a dedicated channel for every request to avoid race conditions on
// concurrent requests.
const channel = new MessageChannel();
worker.postMessage(channel.port1, [channel.port1]);
let messageReceived = false;
channel.port2.onmessage = event => {
assert_false(messageReceived, 'Already received response for ' + message);
messageReceived = true;
resolve(event.data);
};
channel.port2.postMessage(message);
});
}
async function ensure_install_event_fired(worker) {
const response = await send_message_to_worker_and_wait_for_response(worker, 'awaitInstallEvent');
assert_equals('installEventFired', response);
assert_equals('installing', worker.state, 'Expected worker to be installing.');
}
async function finish_install(worker) {
await ensure_install_event_fired(worker);
const response = await send_message_to_worker_and_wait_for_response(worker, 'finishInstall');
assert_equals('installFinished', response);
}
async function activate_service_worker(t, worker) {
await finish_install(worker);
// By waiting for both states at the same time, the test fails
// quickly if the installation fails, avoiding a timeout.
await Promise.race([wait_for_state(t, worker, 'activated'),
wait_for_state(t, worker, 'redundant')]);
assert_equals('activated', worker.state, 'Service worker should be activated.');
}
async function update_within_service_worker(worker) {
// This function returns a Promise that resolves when update()
// has been called but is not necessarily finished yet.
// Call finish() on the returned object to wait for update() settle.
const port = await send_message_to_worker_and_wait_for_response(worker, 'callUpdate');
let messageReceived = false;
return {
finish: () => {
return new Promise(resolve => {
port.onmessage = event => {
assert_false(messageReceived, 'Update already finished.');
messageReceived = true;
resolve(event.data);
};
});
},
};
}
async function update_from_client_and_await_installing_version(test, registration) {
const updatefound = wait_for_update(test, registration);
registration.update();
await updatefound;
return registration.installing;
}
async function spin_up_service_worker(test) {
const script = 'resources/update-during-installation-worker.py';
const scope = 'resources/blank.html';
const registration = await service_worker_unregister_and_register(test, script, scope);
test.add_cleanup(async () => {
if (registration.installing) {
// If there is an installing worker, we need to finish installing it.
// Otherwise, the tests fails with an timeout because unregister() blocks
// until the install-event-handler finishes.
const worker = registration.installing;
await send_message_to_worker_and_wait_for_response(worker, 'awaitInstallEvent');
await send_message_to_worker_and_wait_for_response(worker, 'finishInstall');
}
return registration.unregister();
});
return registration;
}
promise_test(async t => {
const registration = await spin_up_service_worker(t);
const worker = registration.installing;
await ensure_install_event_fired(worker);
const result = registration.update();
await activate_service_worker(t, worker);
return result;
}, 'ServiceWorkerRegistration.update() from client succeeds while installing service worker.');
promise_test(async t => {
const registration = await spin_up_service_worker(t);
const worker = registration.installing;
await ensure_install_event_fired(worker);
// Add event listener to fail the test if update() succeeds.
const updatefound = t.step_func(async () => {
registration.removeEventListener('updatefound', updatefound);
// Activate new worker so non-compliant browsers don't fail with timeout.
await activate_service_worker(t, registration.installing);
assert_unreached("update() should have failed");
});
registration.addEventListener('updatefound', updatefound);
const update = await update_within_service_worker(worker);
// Activate worker to ensure update() finishes and the test doesn't timeout
// in non-compliant browsers.
await activate_service_worker(t, worker);
const response = await update.finish();
assert_false(response.success, 'update() should have failed.');
assert_equals('InvalidStateError', response.exception, 'update() should have thrown InvalidStateError.');
}, 'ServiceWorkerRegistration.update() from installing service worker throws.');
promise_test(async t => {
const registration = await spin_up_service_worker(t);
const worker1 = registration.installing;
await activate_service_worker(t, worker1);
const worker2 = await update_from_client_and_await_installing_version(t, registration);
await ensure_install_event_fired(worker2);
const update = await update_within_service_worker(worker1);
// Activate the new version so that update() finishes and the test doesn't timeout.
await activate_service_worker(t, worker2);
const response = await update.finish();
assert_true(response.success, 'update() from active service worker should have succeeded.');
}, 'ServiceWorkerRegistration.update() from active service worker succeeds while installing service worker.');
</script>