Bug 1162013. Process the Promise queue between adjacent setTimeout callback invocations when we're going through the callback list without returning to the event loop. r=smaug

This commit is contained in:
Boris Zbarsky 2015-05-07 14:49:31 -04:00
parent d383fa3c40
commit 59ea7eac31
6 changed files with 71 additions and 7 deletions

View File

@ -12438,6 +12438,12 @@ nsGlobalWindow::RunTimeoutHandler(nsTimeout* aTimeout,
// point anyway, and the script context should have already reported
// the script error in the usual way - so we just drop it.
// Since we might be processing more timeouts, go ahead and flush the promise
// queue now before we do that. We need to do that while we're still in our
// "running JS is safe" state (e.g. mRunningTimeout is set, timeout->mRunning
// is false).
Promise::PerformMicroTaskCheckpoint();
if (trackNestingLevel) {
sNestingLevel = nestingLevel;
}
@ -12447,6 +12453,7 @@ nsGlobalWindow::RunTimeoutHandler(nsTimeout* aTimeout,
mRunningTimeout = last_running_timeout;
timeout->mRunning = false;
return timeout->mCleared;
}
@ -12566,14 +12573,17 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout)
deadline = now;
}
// The timeout list is kept in deadline order. Discover the latest
// timeout whose deadline has expired. On some platforms, native
// timeout events fire "early", so we need to test the timer as well
// as the deadline.
// The timeout list is kept in deadline order. Discover the latest timeout
// whose deadline has expired. On some platforms, native timeout events fire
// "early", but we handled that above by setting deadline to aTimeout->mWhen
// if the timer fired early. So we can stop walking if we get to timeouts
// whose mWhen is greater than deadline, since once that happens we know
// nothing past that point is expired.
last_expired_timeout = nullptr;
for (nsTimeout *timeout = mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) {
if (((timeout == aTimeout) || (timeout->mWhen <= deadline)) &&
(timeout->mFiringDepth == 0)) {
for (nsTimeout *timeout = mTimeouts.getFirst();
timeout && timeout->mWhen <= deadline;
timeout = timeout->getNext()) {
if (timeout->mFiringDepth == 0) {
// Mark any timeouts that are on the list to be fired with the
// firing depth so that we can reentrantly run timeouts
timeout->mFiringDepth = firingDepth;

View File

@ -0,0 +1,18 @@
var log = [];
var resolvedPromise = Promise.resolve(null);
function schedulePromiseTask(f) {
resolvedPromise.then(f);
}
setTimeout(function() {
log.push('t1start');
schedulePromiseTask(function() {
log.push('promise');
});
log.push('t1end');
}, 10);
setTimeout(function() {
log.push('t2');
postMessage(log.join(', '));
}, 10);

View File

@ -7,3 +7,7 @@
[test_resolve.html]
[test_resolver_return_value.html]
[test_thenable_vs_promise_ordering.html]
[test_promise_and_timeout_ordering.html]
support-files = file_promise_and_timeout_ordering.js
[test_promise_and_timeout_ordering_workers.html]
support-files = file_promise_and_timeout_ordering.js

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Test for promise and timeout ordering</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
var t = async_test("Promise callbacks should run immediately after the setTimeout handler that enqueues them");
var origPostMessage = window.postMessage;
window.postMessage = function(msg) { origPostMessage.call(window, msg, "*"); }
window.onmessage = t.step_func_done(function(e) {
assert_equals(e.data, "t1start, t1end, promise, t2");
});
</script>
<script src="file_promise_and_timeout_ordering.js"></script>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Test for promise and timeout ordering in workers</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
var t = async_test("Promise callbacks in workers should run immediately after the setTimeout handler that enqueues them");
var w = new Worker("file_promise_and_timeout_ordering.js");
w.onmessage = t.step_func_done(function(e) {
assert_equals(e.data, "t1start, t1end, promise, t2");
});
</script>

View File

@ -6745,6 +6745,10 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
}
}
// Since we might be processing more timeouts, go ahead and flush
// the promise queue now before we do that.
Promise::PerformMicroTaskCheckpoint();
NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
}