1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
<!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>
|