Source code

Revision control

Copy as Markdown

Other Tools

'use strict';
if (self.importScripts) {
importScripts('/resources/testharness.js');
}
setup({
allow_uncaught_exception: true
});
//
// Straightforward unhandledrejection tests
//
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = Promise.reject(e);
}, 'unhandledrejection: from Promise.reject');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = new Promise(function(_, reject) {
reject(e);
});
}, 'unhandledrejection: from a synchronous rejection in new Promise');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = new Promise(function(_, reject) {
queueTask(function() {
reject(e);
});
});
}, 'unhandledrejection: from a task-delayed rejection');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = new Promise(function(_, reject) {
setTimeout(function() {
reject(e);
}, 1);
});
}, 'unhandledrejection: from a setTimeout-delayed rejection');
async_test(function(t) {
var e = new Error();
var e2 = new Error();
var promise2;
onUnhandledSucceed(t, e2, function() { return promise2; });
var unreached = t.unreached_func('promise should not be fulfilled');
promise2 = Promise.reject(e).then(unreached, function(reason) {
t.step(function() {
assert_equals(reason, e);
});
throw e2;
});
}, 'unhandledrejection: from a throw in a rejection handler chained off of Promise.reject');
async_test(function(t) {
var e = new Error();
var e2 = new Error();
var promise2;
onUnhandledSucceed(t, e2, function() { return promise2; });
var unreached = t.unreached_func('promise should not be fulfilled');
promise2 = new Promise(function(_, reject) {
setTimeout(function() {
reject(e);
}, 1);
}).then(unreached, function(reason) {
t.step(function() {
assert_equals(reason, e);
});
throw e2;
});
}, 'unhandledrejection: from a throw in a rejection handler chained off of a setTimeout-delayed rejection');
async_test(function(t) {
var e = new Error();
var e2 = new Error();
var promise2;
onUnhandledSucceed(t, e2, function() { return promise2; });
var promise = new Promise(function(_, reject) {
setTimeout(function() {
reject(e);
mutationObserverMicrotask(function() {
var unreached = t.unreached_func('promise should not be fulfilled');
promise2 = promise.then(unreached, function(reason) {
t.step(function() {
assert_equals(reason, e);
});
throw e2;
});
});
}, 1);
});
}, 'unhandledrejection: from a throw in a rejection handler attached one microtask after a setTimeout-delayed rejection');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = Promise.resolve().then(function() {
return Promise.reject(e);
});
}, 'unhandledrejection: from returning a Promise.reject-created rejection in a fulfillment handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = Promise.resolve().then(function() {
throw e;
});
}, 'unhandledrejection: from a throw in a fulfillment handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = Promise.resolve().then(function() {
return new Promise(function(_, reject) {
setTimeout(function() {
reject(e);
}, 1);
});
});
}, 'unhandledrejection: from returning a setTimeout-delayed rejection in a fulfillment handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = Promise.all([Promise.reject(e)]);
}, 'unhandledrejection: from Promise.reject, indirected through Promise.all');
async_test(function(t) {
var p;
var unhandled = function(ev) {
if (ev.promise === p) {
t.step(function() {
assert_equals(ev.reason.name, 'InvalidStateError');
assert_equals(ev.promise, p);
});
t.done();
}
};
addEventListener('unhandledrejection', unhandled);
ensureCleanup(t, unhandled);
p = createImageBitmap(new Blob());
}, 'unhandledrejection: from createImageBitmap which is UA triggered');
//
// Negative unhandledrejection/rejectionhandled tests with immediate attachment
//
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
var unreached = t.unreached_func('promise should not be fulfilled');
p = Promise.reject(e).then(unreached, function() {});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise from Promise.reject');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
var unreached = t.unreached_func('promise should not be fulfilled');
p = Promise.all([Promise.reject(e)]).then(unreached, function() {});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise from ' +
'Promise.reject, indirecting through Promise.all');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
var unreached = t.unreached_func('promise should not be fulfilled');
p = new Promise(function(_, reject) {
reject(e);
}).then(unreached, function() {});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a synchronously-rejected ' +
'promise created with new Promise');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
var unreached = t.unreached_func('promise should not be fulfilled');
p = Promise.resolve().then(function() {
throw e;
}).then(unreached, function(reason) {
t.step(function() {
assert_equals(reason, e);
});
});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
'throwing in a fulfillment handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
var unreached = t.unreached_func('promise should not be fulfilled');
p = Promise.resolve().then(function() {
return Promise.reject(e);
}).then(unreached, function(reason) {
t.step(function() {
assert_equals(reason, e);
});
});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
'returning a Promise.reject-created promise in a fulfillment handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
var unreached = t.unreached_func('promise should not be fulfilled');
p = Promise.resolve().then(function() {
return new Promise(function(_, reject) {
setTimeout(function() {
reject(e);
}, 1);
});
}).then(unreached, function(reason) {
t.step(function() {
assert_equals(reason, e);
});
});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
'returning a setTimeout-delayed rejection in a fulfillment handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
queueTask(function() {
p = Promise.resolve().then(function() {
return Promise.reject(e);
})
.catch(function() {});
});
}, 'no unhandledrejection/rejectionhandled: all inside a queued task, a rejection handler attached synchronously to ' +
'a promise created from returning a Promise.reject-created promise in a fulfillment handler');
async_test(function(t) {
var p;
onUnhandledFail(t, function() { return p; });
var unreached = t.unreached_func('promise should not be fulfilled');
p = createImageBitmap(new Blob()).then(unreached, function() {});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
'createImageBitmap');
//
// Negative unhandledrejection/rejectionhandled tests with microtask-delayed attachment
//
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
p = Promise.reject(e);
mutationObserverMicrotask(function() {
var unreached = t.unreached_func('promise should not be fulfilled');
p.then(unreached, function() {});
});
}, 'delayed handling: a microtask delay before attaching a handler prevents both events (Promise.reject-created ' +
'promise)');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
p = new Promise(function(_, reject) {
reject(e);
});
mutationObserverMicrotask(function() {
var unreached = t.unreached_func('promise should not be fulfilled');
p.then(unreached, function() {});
});
}, 'delayed handling: a microtask delay before attaching a handler prevents both events (immediately-rejected new ' +
'Promise-created promise)');
async_test(function(t) {
var e = new Error();
var p1;
var p2;
onUnhandledFail(t, function() { return p1; });
onUnhandledFail(t, function() { return p2; });
p1 = new Promise(function(_, reject) {
mutationObserverMicrotask(function() {
reject(e);
});
});
p2 = Promise.all([p1]);
mutationObserverMicrotask(function() {
var unreached = t.unreached_func('promise should not be fulfilled');
p2.then(unreached, function() {});
});
}, 'delayed handling: a microtask delay before attaching the handler, and before rejecting the promise, indirected ' +
'through Promise.all');
//
// Negative unhandledrejection/rejectionhandled tests with nested-microtask-delayed attachment
//
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
p = Promise.reject(e);
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
});
});
}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
queueTask(function() {
p = Promise.reject(e);
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
});
});
});
}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks, ' +
'all inside a queueTask');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
setTimeout(function() {
p = Promise.reject(e);
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
});
});
}, 0);
}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks, ' +
'all inside a setTimeout');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
p = Promise.reject(e);
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
p.catch(function() {});
});
});
});
});
}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
queueTask(function() {
p = Promise.reject(e);
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
p.catch(function() {});
});
});
});
});
});
}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask, ' +
'all inside a queueTask');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
setTimeout(function() {
p = Promise.reject(e);
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
Promise.resolve().then(function() {
mutationObserverMicrotask(function() {
p.catch(function() {});
});
});
});
});
}, 0);
}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask, ' +
'all inside a setTimeout');
// For workers, queueTask() involves posting tasks to other threads, so
// the following tests don't work there.
if ('document' in self) {
//
// Negative unhandledrejection/rejectionhandled tests with task-delayed attachment
//
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
var _reject;
p = new Promise(function(_, reject) {
_reject = reject;
});
_reject(e);
queueTask(function() {
var unreached = t.unreached_func('promise should not be fulfilled');
p.then(unreached, function() {});
});
}, 'delayed handling: a task delay before attaching a handler prevents unhandledrejection');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
p = Promise.reject(e);
queueTask(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
}, 'delayed handling: queueTask after promise creation/rejection, plus promise microtasks, is not too late to ' +
'attach a rejection handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
queueTask(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
});
});
});
p = Promise.reject(e);
}, 'delayed handling: queueTask before promise creation/rejection, plus many promise microtasks, is not too ' +
'late to attach a rejection handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledFail(t, function() { return p; });
p = Promise.reject(e);
queueTask(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
});
});
});
}, 'delayed handling: queueTask after promise creation/rejection, plus many promise microtasks, is not too ' +
'late to attach a rejection handler');
}
//
// Positive unhandledrejection/rejectionhandled tests with delayed attachment
//
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
var _reject;
p = new Promise(function(_, reject) {
_reject = reject;
});
_reject(e);
queueTask(function() {
queueTask(function() {
var unreached = t.unreached_func('promise should not be fulfilled');
p.then(unreached, function() {});
});
});
}, 'delayed handling: a nested-task delay before attaching a handler causes unhandledrejection');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = Promise.reject(e);
queueTask(function() {
queueTask(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
});
}, 'delayed handling: a nested-queueTask after promise creation/rejection, plus promise microtasks, is too ' +
'late to attach a rejection handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
queueTask(function() {
queueTask(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
});
});
});
});
p = Promise.reject(e);
}, 'delayed handling: a nested-queueTask before promise creation/rejection, plus many promise microtasks, is ' +
'too late to attach a rejection handler');
async_test(function(t) {
var e = new Error();
var p;
onUnhandledSucceed(t, e, function() { return p; });
p = Promise.reject(e);
queueTask(function() {
queueTask(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
Promise.resolve().then(function() {
p.catch(function() {});
});
});
});
});
});
});
}, 'delayed handling: a nested-queueTask after promise creation/rejection, plus many promise microtasks, is ' +
'too late to attach a rejection handler');
async_test(function(t) {
var unhandledPromises = [];
var unhandledReasons = [];
var e = new Error();
var p;
var unhandled = function(ev) {
if (ev.promise === p) {
t.step(function() {
unhandledPromises.push(ev.promise);
unhandledReasons.push(ev.reason);
});
}
};
var handled = function(ev) {
if (ev.promise === p) {
t.step(function() {
assert_array_equals(unhandledPromises, [p]);
assert_array_equals(unhandledReasons, [e]);
assert_equals(ev.promise, p);
assert_equals(ev.reason, e);
});
}
};
addEventListener('unhandledrejection', unhandled);
addEventListener('rejectionhandled', handled);
ensureCleanup(t, unhandled, handled);
p = new Promise(function() {
throw e;
});
setTimeout(function() {
var unreached = t.unreached_func('promise should not be fulfilled');
p.then(unreached, function(reason) {
assert_equals(reason, e);
setTimeout(function() { t.done(); }, 10);
});
}, 10);
}, 'delayed handling: delaying handling by setTimeout(,10) will cause both events to fire');
async_test(function(t) {
var unhandledPromises = [];
var unhandledReasons = [];
var p;
var unhandled = function(ev) {
if (ev.promise === p) {
t.step(function() {
unhandledPromises.push(ev.promise);
unhandledReasons.push(ev.reason.name);
});
}
};
var handled = function(ev) {
if (ev.promise === p) {
t.step(function() {
assert_array_equals(unhandledPromises, [p]);
assert_array_equals(unhandledReasons, ['InvalidStateError']);
assert_equals(ev.promise, p);
assert_equals(ev.reason.name, 'InvalidStateError');
});
}
};
addEventListener('unhandledrejection', unhandled);
addEventListener('rejectionhandled', handled);
ensureCleanup(t, unhandled, handled);
p = createImageBitmap(new Blob());
setTimeout(function() {
var unreached = t.unreached_func('promise should not be fulfilled');
p.then(unreached, function(reason) {
assert_equals(reason.name, 'InvalidStateError');
setTimeout(function() { t.done(); }, 10);
});
}, 10);
}, 'delayed handling: delaying handling rejected promise created from createImageBitmap will cause both events to fire');
//
// Miscellaneous tests about integration with the rest of the platform
//
async_test(function(t) {
var e = new Error();
var l = function(ev) {
var order = [];
mutationObserverMicrotask(function() {
order.push(1);
});
setTimeout(function() {
order.push(2);
t.step(function() {
assert_array_equals(order, [1, 2]);
});
t.done();
}, 1);
};
addEventListener('unhandledrejection', l);
ensureCleanup(t, l);
Promise.reject(e);
}, 'mutationObserverMicrotask vs. queueTask ordering is not disturbed inside unhandledrejection events');
// For workers, queueTask() involves posting tasks to other threads, so
// the following tests don't work there.
if ('document' in self) {
// and the following comments.
async_test(function(t) {
var sequenceOfEvents = [];
addEventListener('unhandledrejection', l);
ensureCleanup(t, l);
var p1 = Promise.reject();
var p2;
queueTask(function() {
p2 = Promise.reject();
queueTask(function() {
sequenceOfEvents.push('queueTask');
checkSequence();
});
});
function l(ev) {
if (ev.promise === p1 || ev.promise === p2) {
sequenceOfEvents.push(ev.promise);
checkSequence();
}
}
function checkSequence() {
if (sequenceOfEvents.length === 3) {
t.step(function() {
assert_array_equals(sequenceOfEvents, [p1, 'queueTask', p2]);
});
t.done();
}
}
}, 'queueTask ordering vs. the task queued for unhandled rejection notification (1)');
async_test(function(t) {
var sequenceOfEvents = [];
addEventListener('unhandledrejection', l);
ensureCleanup(t, l);
var p2;
queueTask(function() {
p2 = Promise.reject();
queueTask(function() {
sequenceOfEvents.push('queueTask');
checkSequence();
});
});
function l(ev) {
if (ev.promise == p2) {
sequenceOfEvents.push(ev.promise);
checkSequence();
}
}
function checkSequence() {
if (sequenceOfEvents.length === 2) {
t.step(function() {
assert_array_equals(sequenceOfEvents, ['queueTask', p2]);
});
t.done();
}
}
}, 'queueTask ordering vs. the task queued for unhandled rejection notification (2)');
async_test(function(t) {
var sequenceOfEvents = [];
addEventListener('unhandledrejection', unhandled);
addEventListener('rejectionhandled', handled);
ensureCleanup(t, unhandled, handled);
var p = Promise.reject();
function unhandled(ev) {
if (ev.promise === p) {
sequenceOfEvents.push('unhandled');
checkSequence();
setTimeout(function() {
queueTask(function() {
sequenceOfEvents.push('task before catch');
checkSequence();
});
p.catch(function() {
sequenceOfEvents.push('catch');
checkSequence();
});
queueTask(function() {
sequenceOfEvents.push('task after catch');
checkSequence();
});
sequenceOfEvents.push('after catch');
checkSequence();
}, 10);
}
}
function handled(ev) {
if (ev.promise === p) {
sequenceOfEvents.push('handled');
checkSequence();
}
}
function checkSequence() {
if (sequenceOfEvents.length === 6) {
t.step(function() {
assert_array_equals(sequenceOfEvents,
['unhandled', 'after catch', 'catch', 'task before catch', 'handled', 'task after catch']);
});
t.done();
}
}
}, 'rejectionhandled is dispatched from a queued task, and not immediately');
}
//
// HELPERS
//
// This function queues a task in "DOM manipulation task source" in window
// context, but not in workers.
function queueTask(f) {
if ('document' in self) {
var d = document.createElement("details");
d.ontoggle = function() {
f();
};
d.setAttribute("open", "");
} else {
// We need to fix this to use something that can queue tasks in
// "DOM manipulation task source" to ensure the order is correct
var channel = new MessageChannel();
channel.port1.onmessage = function() { channel.port1.close(); f(); };
channel.port2.postMessage('abusingpostmessageforfunandprofit');
channel.port2.close();
}
}
function mutationObserverMicrotask(f) {
if ('document' in self) {
var observer = new MutationObserver(function() { f(); });
var node = document.createTextNode('');
observer.observe(node, { characterData: true });
node.data = 'foo';
} else {
// We don't have mutation observers on workers, so just post a promise-based
// microtask.
Promise.resolve().then(function() { f(); });
}
}
function onUnhandledSucceed(t, expectedReason, expectedPromiseGetter) {
var l = function(ev) {
if (ev.promise === expectedPromiseGetter()) {
t.step(function() {
assert_equals(ev.reason, expectedReason);
assert_equals(ev.promise, expectedPromiseGetter());
});
t.done();
}
};
addEventListener('unhandledrejection', l);
ensureCleanup(t, l);
}
function onUnhandledFail(t, expectedPromiseGetter) {
var unhandled = function(evt) {
if (evt.promise === expectedPromiseGetter()) {
t.step(function() {
assert_unreached('unhandledrejection event is not supposed to be triggered');
});
}
};
var handled = function(evt) {
if (evt.promise === expectedPromiseGetter()) {
t.step(function() {
assert_unreached('rejectionhandled event is not supposed to be triggered');
});
}
};
addEventListener('unhandledrejection', unhandled);
addEventListener('rejectionhandled', handled);
ensureCleanup(t, unhandled, handled);
setTimeout(function() {
t.done();
}, 10);
}
function ensureCleanup(t, unhandled, handled) {
t.add_cleanup(function() {
if (unhandled)
removeEventListener('unhandledrejection', unhandled);
if (handled)
removeEventListener('rejectionhandled', handled);
});
}
done();