extendable-event-async-waituntil.js (7811B)
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. 8 9 // Controlled by 'init'/'done' messages. 10 var resolveLockPromise; 11 var port; 12 13 self.addEventListener('message', function(event) { 14 var waitPromise; 15 var resolveTestPromise; 16 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; 25 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; 31 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; 37 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; 44 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; 54 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; 62 63 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; 74 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; 83 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; 90 91 case 'script-extendable-event': 92 self.dispatchEvent(new ExtendableEvent('nontrustedevent')); 93 break; 94 } 95 96 event.source.postMessage('ACK'); 97 }); 98 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; 114 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; 124 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; 134 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; 144 145 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 } 155 }); 156 157 self.addEventListener('nontrustedevent', function(event) { 158 sync_waituntil(event).then(reportResultExpecting('InvalidStateError')); 159 }); 160 161 function reportResultExpecting(expectedResult) { 162 return function (result) { 163 port.postMessage({result : result, expected: expectedResult}); 164 return result; 165 }; 166 } 167 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 }); 177 } 178 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 }); 190 } 191 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 }); 203 } 204 205 // Returns a promise that settles in a separate task. 206 function makeNewTaskPromise() { 207 return new Promise(resolve => { 208 setTimeout(resolve, 0); 209 }); 210 }