Bug 1536619 [wpt PR 15852] - service worker: Improve WPT tests for async respondWith...
[gecko.git] / testing / web-platform / tests / service-workers / service-worker / resources / extendable-event-async-waituntil.js
blob8a975b0d2e9b0789ce7ab7b05757b6d93f2ec400
1 // This worker calls waitUntil() and respondWith() asynchronously and
2 // reports back to the test whether they threw.
3 //
4 // These test cases are confusing. Bear in mind that the event is active
5 // (calling waitUntil() is allowed) if:
6 // * The pending promise count is not 0, or
7 // * The event dispatch flag is set.
9 // Controlled by 'init'/'done' messages.
10 var resolveLockPromise;
11 var port;
13 self.addEventListener('message', function(event) {
14     var waitPromise;
15     var resolveTestPromise;
17     switch (event.data.step) {
18       case 'init':
19         event.waitUntil(new Promise((res) => { resolveLockPromise = res; }));
20         port = event.data.port;
21         break;
22       case 'done':
23         resolveLockPromise();
24         break;
26       // Throws because waitUntil() is called in a task after event dispatch
27       // finishes.
28       case 'no-current-extension-different-task':
29         async_task_waituntil(event).then(reportResultExpecting('InvalidStateError'));
30         break;
32       // OK because waitUntil() is called in a microtask that runs after the
33       // event handler runs, while the event dispatch flag is still set.
34       case 'no-current-extension-different-microtask':
35         async_microtask_waituntil(event).then(reportResultExpecting('OK'));
36         break;
38       // OK because the second waitUntil() is called while the first waitUntil()
39       // promise is still pending.
40       case 'current-extension-different-task':
41         event.waitUntil(new Promise((res) => { resolveTestPromise = res; }));
42         async_task_waituntil(event).then(reportResultExpecting('OK')).then(resolveTestPromise);
43         break;
45       // OK because all promises involved resolve "immediately", so the second
46       // waitUntil() is called during the microtask checkpoint at the end of
47       // event dispatching, when the event dispatch flag is still set.
48       case 'during-event-dispatch-current-extension-expired-same-microtask-turn':
49         waitPromise = Promise.resolve();
50         event.waitUntil(waitPromise);
51         waitPromise.then(() => { return sync_waituntil(event); })
52           .then(reportResultExpecting('OK'))
53         break;
55       // OK for the same reason as above.
56       case 'during-event-dispatch-current-extension-expired-same-microtask-turn-extra':
57         waitPromise = Promise.resolve();
58         event.waitUntil(waitPromise);
59         waitPromise.then(() => { return async_microtask_waituntil(event); })
60           .then(reportResultExpecting('OK'))
61         break;
64       // OK because the pending promise count is decremented in a microtask
65       // queued upon fulfillment of the first waitUntil() promise, so the second
66       // waitUntil() is called while the pending promise count is still
67       // positive.
68       case 'after-event-dispatch-current-extension-expired-same-microtask-turn':
69         waitPromise = makeNewTaskPromise();
70         event.waitUntil(waitPromise);
71         waitPromise.then(() => { return sync_waituntil(event); })
72           .then(reportResultExpecting('OK'))
73         break;
75       // Throws because the second waitUntil() is called after the pending
76       // promise count was decremented to 0.
77       case 'after-event-dispatch-current-extension-expired-same-microtask-turn-extra':
78         waitPromise = makeNewTaskPromise();
79         event.waitUntil(waitPromise);
80         waitPromise.then(() => { return async_microtask_waituntil(event); })
81           .then(reportResultExpecting('InvalidStateError'))
82         break;
84       // Throws because the second waitUntil() is called in a new task, after
85       // first waitUntil() promise settled and the event dispatch flag is unset.
86       case 'current-extension-expired-different-task':
87         event.waitUntil(Promise.resolve());
88         async_task_waituntil(event).then(reportResultExpecting('InvalidStateError'));
89         break;
91       case 'script-extendable-event':
92         self.dispatchEvent(new ExtendableEvent('nontrustedevent'));
93         break;
94     }
96     event.source.postMessage('ACK');
97   });
99 self.addEventListener('fetch', function(event) {
100   const path = new URL(event.request.url).pathname;
101   const step = path.substring(path.lastIndexOf('/') + 1);
102   let response;
103   switch (step) {
104     // OK because waitUntil() is called while the respondWith() promise is still
105     // unsettled, so the pending promise count is positive.
106     case 'pending-respondwith-async-waituntil':
107       var resolveFetch;
108       response = new Promise((res) => { resolveFetch = res; });
109       event.respondWith(response);
110       async_task_waituntil(event)
111         .then(reportResultExpecting('OK'))
112         .then(() => { resolveFetch(new Response('OK')); });
113       break;
115     // OK because all promises involved resolve "immediately", so waitUntil() is
116     // called during the microtask checkpoint at the end of event dispatching,
117     // when the event dispatch flag is still set.
118     case 'during-event-dispatch-respondwith-microtask-sync-waituntil':
119       response = Promise.resolve(new Response('RESP'));
120       event.respondWith(response);
121       response.then(() => { return sync_waituntil(event); })
122         .then(reportResultExpecting('OK'));
123       break;
125     // OK because all promises involved resolve "immediately", so waitUntil() is
126     // called during the microtask checkpoint at the end of event dispatching,
127     // when the event dispatch flag is still set.
128     case 'during-event-dispatch-respondwith-microtask-async-waituntil':
129       response = Promise.resolve(new Response('RESP'));
130       event.respondWith(response);
131       response.then(() => { return async_microtask_waituntil(event); })
132         .then(reportResultExpecting('OK'));
133       break;
135     // OK because the pending promise count is decremented in a microtask queued
136     // upon fulfillment of the respondWith() promise, so waitUntil() is called
137     // while the pending promise count is still positive.
138     case 'after-event-dispatch-respondwith-microtask-sync-waituntil':
139       response = makeNewTaskPromise().then(() => {return new Response('RESP');});
140       event.respondWith(response);
141       response.then(() => { return sync_waituntil(event); })
142         .then(reportResultExpecting('OK'));
143       break;
146     // Throws because waitUntil() is called after the pending promise count was
147     // decremented to 0.
148     case 'after-event-dispatch-respondwith-microtask-async-waituntil':
149       response = makeNewTaskPromise().then(() => {return new Response('RESP');});
150       event.respondWith(response);
151       response.then(() => { return async_microtask_waituntil(event); })
152         .then(reportResultExpecting('InvalidStateError'))
153       break;
154   }
157 self.addEventListener('nontrustedevent', function(event) {
158     sync_waituntil(event).then(reportResultExpecting('InvalidStateError'));
159   });
161 function reportResultExpecting(expectedResult) {
162   return function (result) {
163     port.postMessage({result : result, expected: expectedResult});
164     return result;
165   };
168 function sync_waituntil(event) {
169   return new Promise((res, rej) => {
170     try {
171       event.waitUntil(Promise.resolve());
172       res('OK');
173     } catch (error) {
174       res(error.name);
175     }
176   });
179 function async_microtask_waituntil(event) {
180   return new Promise((res, rej) => {
181     Promise.resolve().then(() => {
182       try {
183         event.waitUntil(Promise.resolve());
184         res('OK');
185       } catch (error) {
186         res(error.name);
187       }
188     });
189   });
192 function async_task_waituntil(event) {
193   return new Promise((res, rej) => {
194     setTimeout(() => {
195       try {
196         event.waitUntil(Promise.resolve());
197         res('OK');
198       } catch (error) {
199         res(error.name);
200       }
201     }, 0);
202   });
205 // Returns a promise that settles in a separate task.
206 function makeNewTaskPromise() {
207   return new Promise(resolve => {
208     setTimeout(resolve, 0);
209   });