Source code

Revision control

Copy as Markdown

Other Tools

// This worker calls waitUntil() and respondWith() asynchronously and
// reports back to the test whether they threw.
//
// These test cases are confusing. Bear in mind that the event is active
// (calling waitUntil() is allowed) if:
// * The pending promise count is not 0, or
// * The event dispatch flag is set.
// Controlled by 'init'/'done' messages.
var resolveLockPromise;
var port;
self.addEventListener('message', function(event) {
var waitPromise;
var resolveTestPromise;
switch (event.data.step) {
case 'init':
event.waitUntil(new Promise((res) => { resolveLockPromise = res; }));
port = event.data.port;
break;
case 'done':
resolveLockPromise();
break;
// Throws because waitUntil() is called in a task after event dispatch
// finishes.
case 'no-current-extension-different-task':
async_task_waituntil(event).then(reportResultExpecting('InvalidStateError'));
break;
// OK because waitUntil() is called in a microtask that runs after the
// event handler runs, while the event dispatch flag is still set.
case 'no-current-extension-different-microtask':
async_microtask_waituntil(event).then(reportResultExpecting('OK'));
break;
// OK because the second waitUntil() is called while the first waitUntil()
// promise is still pending.
case 'current-extension-different-task':
event.waitUntil(new Promise((res) => { resolveTestPromise = res; }));
async_task_waituntil(event).then(reportResultExpecting('OK')).then(resolveTestPromise);
break;
// OK because all promises involved resolve "immediately", so the second
// waitUntil() is called during the microtask checkpoint at the end of
// event dispatching, when the event dispatch flag is still set.
case 'during-event-dispatch-current-extension-expired-same-microtask-turn':
waitPromise = Promise.resolve();
event.waitUntil(waitPromise);
waitPromise.then(() => { return sync_waituntil(event); })
.then(reportResultExpecting('OK'))
break;
// OK for the same reason as above.
case 'during-event-dispatch-current-extension-expired-same-microtask-turn-extra':
waitPromise = Promise.resolve();
event.waitUntil(waitPromise);
waitPromise.then(() => { return async_microtask_waituntil(event); })
.then(reportResultExpecting('OK'))
break;
// OK because the pending promise count is decremented in a microtask
// queued upon fulfillment of the first waitUntil() promise, so the second
// waitUntil() is called while the pending promise count is still
// positive.
case 'after-event-dispatch-current-extension-expired-same-microtask-turn':
waitPromise = makeNewTaskPromise();
event.waitUntil(waitPromise);
waitPromise.then(() => { return sync_waituntil(event); })
.then(reportResultExpecting('OK'))
break;
// Throws because the second waitUntil() is called after the pending
// promise count was decremented to 0.
case 'after-event-dispatch-current-extension-expired-same-microtask-turn-extra':
waitPromise = makeNewTaskPromise();
event.waitUntil(waitPromise);
waitPromise.then(() => { return async_microtask_waituntil(event); })
.then(reportResultExpecting('InvalidStateError'))
break;
// Throws because the second waitUntil() is called in a new task, after
// first waitUntil() promise settled and the event dispatch flag is unset.
case 'current-extension-expired-different-task':
event.waitUntil(Promise.resolve());
async_task_waituntil(event).then(reportResultExpecting('InvalidStateError'));
break;
case 'script-extendable-event':
self.dispatchEvent(new ExtendableEvent('nontrustedevent'));
break;
}
event.source.postMessage('ACK');
});
self.addEventListener('fetch', function(event) {
const path = new URL(event.request.url).pathname;
const step = path.substring(path.lastIndexOf('/') + 1);
let response;
switch (step) {
// OK because waitUntil() is called while the respondWith() promise is still
// unsettled, so the pending promise count is positive.
case 'pending-respondwith-async-waituntil':
var resolveFetch;
response = new Promise((res) => { resolveFetch = res; });
event.respondWith(response);
async_task_waituntil(event)
.then(reportResultExpecting('OK'))
.then(() => { resolveFetch(new Response('OK')); });
break;
// OK because all promises involved resolve "immediately", so waitUntil() is
// called during the microtask checkpoint at the end of event dispatching,
// when the event dispatch flag is still set.
case 'during-event-dispatch-respondwith-microtask-sync-waituntil':
response = Promise.resolve(new Response('RESP'));
event.respondWith(response);
response.then(() => { return sync_waituntil(event); })
.then(reportResultExpecting('OK'));
break;
// OK because all promises involved resolve "immediately", so waitUntil() is
// called during the microtask checkpoint at the end of event dispatching,
// when the event dispatch flag is still set.
case 'during-event-dispatch-respondwith-microtask-async-waituntil':
response = Promise.resolve(new Response('RESP'));
event.respondWith(response);
response.then(() => { return async_microtask_waituntil(event); })
.then(reportResultExpecting('OK'));
break;
// OK because the pending promise count is decremented in a microtask queued
// upon fulfillment of the respondWith() promise, so waitUntil() is called
// while the pending promise count is still positive.
case 'after-event-dispatch-respondwith-microtask-sync-waituntil':
response = makeNewTaskPromise().then(() => {return new Response('RESP');});
event.respondWith(response);
response.then(() => { return sync_waituntil(event); })
.then(reportResultExpecting('OK'));
break;
// Throws because waitUntil() is called after the pending promise count was
// decremented to 0.
case 'after-event-dispatch-respondwith-microtask-async-waituntil':
response = makeNewTaskPromise().then(() => {return new Response('RESP');});
event.respondWith(response);
response.then(() => { return async_microtask_waituntil(event); })
.then(reportResultExpecting('InvalidStateError'))
break;
}
});
self.addEventListener('nontrustedevent', function(event) {
sync_waituntil(event).then(reportResultExpecting('InvalidStateError'));
});
function reportResultExpecting(expectedResult) {
return function (result) {
port.postMessage({result : result, expected: expectedResult});
return result;
};
}
function sync_waituntil(event) {
return new Promise((res, rej) => {
try {
event.waitUntil(Promise.resolve());
res('OK');
} catch (error) {
res(error.name);
}
});
}
function async_microtask_waituntil(event) {
return new Promise((res, rej) => {
Promise.resolve().then(() => {
try {
event.waitUntil(Promise.resolve());
res('OK');
} catch (error) {
res(error.name);
}
});
});
}
function async_task_waituntil(event) {
return new Promise((res, rej) => {
setTimeout(() => {
try {
event.waitUntil(Promise.resolve());
res('OK');
} catch (error) {
res(error.name);
}
}, 0);
});
}
// Returns a promise that settles in a separate task.
function makeNewTaskPromise() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}