Bug 1229769 - We should be able to use DOM promises in the worker debugger;r=khuey

This commit is contained in:
Eddy Bruel 2016-03-24 16:12:00 +01:00
parent 96226d8425
commit 3e2cb55980
12 changed files with 274 additions and 33 deletions

View File

@ -1230,12 +1230,14 @@ Animation::GetPresContext() const
void
Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
{
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
DoFinishNotificationImmediately();
} else if (!mFinishNotificationTask.IsPending()) {
RefPtr<nsRunnableMethod<Animation>> runnable =
NS_NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately);
Promise::DispatchToMicroTask(runnable);
runtime->DispatchToMicroTask(runnable);
mFinishNotificationTask = runnable;
}
}

View File

@ -922,7 +922,11 @@ Promise::MaybeRejectWithNull()
bool
Promise::PerformMicroTaskCheckpoint()
{
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
// On the main thread, we always use the main promise micro task queue.
std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
runtime->GetPromiseMicroTaskQueue();
@ -930,10 +934,7 @@ Promise::PerformMicroTaskCheckpoint()
return false;
}
Maybe<AutoSafeJSContext> cx;
if (NS_IsMainThread()) {
cx.emplace();
}
AutoSafeJSContext cx;
do {
nsCOMPtr<nsIRunnable> runnable = microtaskQueue.front();
@ -945,15 +946,77 @@ Promise::PerformMicroTaskCheckpoint()
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
if (cx.isSome()) {
JS_CheckForInterrupt(cx.ref());
}
JS_CheckForInterrupt(cx);
runtime->AfterProcessMicrotask();
} while (!microtaskQueue.empty());
return true;
}
void
Promise::PerformWorkerMicroTaskCheckpoint()
{
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
for (;;) {
// For a normal microtask checkpoint, we try to use the debugger microtask
// queue first. If the debugger queue is empty, we use the normal microtask
// queue instead.
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
&runtime->GetDebuggerPromiseMicroTaskQueue();
if (microtaskQueue->empty()) {
microtaskQueue = &runtime->GetPromiseMicroTaskQueue();
if (microtaskQueue->empty()) {
break;
}
}
nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front();
MOZ_ASSERT(runnable);
// This function can re-enter, so we remove the element before calling.
microtaskQueue->pop();
nsresult rv = runnable->Run();
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
runtime->AfterProcessMicrotask();
}
}
void
Promise::PerformWorkerDebuggerMicroTaskCheckpoint()
{
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
for (;;) {
// For a debugger microtask checkpoint, we always use the debugger microtask
// queue.
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
&runtime->GetDebuggerPromiseMicroTaskQueue();
if (microtaskQueue->empty()) {
break;
}
nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front();
MOZ_ASSERT(runnable);
// This function can re-enter, so we remove the element before calling.
microtaskQueue->pop();
nsresult rv = runnable->Run();
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
runtime->AfterProcessMicrotask();
}
}
#ifndef SPIDERMONKEY_PROMISE
/* static */ bool
@ -2415,18 +2478,6 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
}
#endif // SPIDERMONKEY_PROMISE
/* static */ void
Promise::DispatchToMicroTask(nsIRunnable* aRunnable)
{
MOZ_ASSERT(aRunnable);
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
runtime->GetPromiseMicroTaskQueue();
microtaskQueue.push(aRunnable);
}
#ifndef SPIDERMONKEY_PROMISE
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
void
@ -2517,6 +2568,8 @@ void
Promise::ResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
mResolvePending = true;
if (aValue.isObject()) {
@ -2558,7 +2611,7 @@ Promise::ResolveInternal(JSContext* aCx,
new PromiseInit(nullptr, thenObj, mozilla::dom::GetIncumbentGlobal());
RefPtr<PromiseResolveThenableJob> task =
new PromiseResolveThenableJob(this, valueObj, thenCallback);
DispatchToMicroTask(task);
runtime->DispatchToMicroTask(task);
return;
}
}
@ -2648,6 +2701,8 @@ Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
void
Promise::TriggerPromiseReactions()
{
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
nsTArray<RefPtr<PromiseCallback>> callbacks;
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
: mRejectCallbacks);
@ -2657,7 +2712,7 @@ Promise::TriggerPromiseReactions()
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
RefPtr<PromiseReactionJob> task =
new PromiseReactionJob(this, callbacks[i], mResult);
DispatchToMicroTask(task);
runtime->DispatchToMicroTask(task);
}
}

View File

@ -166,6 +166,10 @@ public:
// Returns true if at least one microtask was processed.
static bool PerformMicroTaskCheckpoint();
static void PerformWorkerMicroTaskCheckpoint();
static void PerformWorkerDebuggerMicroTaskCheckpoint();
// WebIDL
nsIGlobalObject* GetParentObject() const
@ -288,10 +292,6 @@ public:
uint64_t GetID();
#endif // SPIDERMONKEY_PROMISE
// Queue an async microtask to current main or worker thread.
static void
DispatchToMicroTask(nsIRunnable* aRunnable);
#ifndef SPIDERMONKEY_PROMISE
enum JSCallbackSlots {
SLOT_PROMISE = 0,

View File

@ -22,7 +22,7 @@ callback AnyCallback = any (any value);
// values work.
#ifndef SPIDERMONKEY_PROMISE
[Constructor(PromiseInit init),
Exposed=(Window,Worker,System)]
Exposed=(Window,Worker,WorkerDebugger,System)]
// Need to escape "Promise" so it's treated as an identifier.
interface _Promise {
// Have to use "any" (or "object", but "any" is simpler) as the type to

View File

@ -960,6 +960,34 @@ public:
}
}
virtual void DispatchToMicroTask(nsIRunnable* aRunnable) override
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aRunnable);
std::queue<nsCOMPtr<nsIRunnable>>* microTaskQueue = nullptr;
JSContext* cx = GetCurrentThreadJSContext();
NS_ASSERTION(cx, "This should never be null!");
JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
NS_ASSERTION(global, "This should never be null!");
// On worker threads, if the current global is the worker global, we use the
// main promise micro task queue. Otherwise, the current global must be
// either the debugger global or a debugger sandbox, and we use the debugger
// promise micro task queue instead.
if (IsWorkerGlobal(global)) {
microTaskQueue = &mPromiseMicroTaskQueue;
} else {
MOZ_ASSERT(IsDebuggerGlobal(global) || IsDebuggerSandbox(global));
microTaskQueue = &mDebuggerPromiseMicroTaskQueue;
}
microTaskQueue->push(aRunnable);
}
private:
WorkerPrivate* mWorkerPrivate;
};

View File

@ -4532,6 +4532,9 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
static_cast<nsIRunnable*>(runnable)->Run();
runnable->Release();
// Flush the promise queue.
Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
if (debuggerRunnablesPending) {
WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
MOZ_ASSERT(globalScope);
@ -5583,6 +5586,9 @@ WorkerPrivate::EnterDebuggerEventLoop()
static_cast<nsIRunnable*>(runnable)->Run();
runnable->Release();
// Flush the promise queue.
Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
// Now *might* be a good time to GC. Let the JS engine make the decision.
if (JS::CurrentGlobalOrNull(cx)) {
JS_MaybeGC(cx);
@ -6077,7 +6083,7 @@ 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();
Promise::PerformWorkerMicroTaskCheckpoint();
NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
}

View File

@ -0,0 +1,30 @@
"use strict";
var self = this;
self.onmessage = function (event) {
if (event.data !== "resolve") {
return;
}
// This then-handler should be executed inside the top-level event loop,
// within the context of the debugger's global.
Promise.resolve().then(function () {
var dbg = new Debugger(global);
dbg.onDebuggerStatement = function () {
self.onmessage = function (event) {
if (event.data !== "resume") {
return;
}
// This then-handler should be executed inside the nested event loop,
// within the context of the debugger's global.
Promise.resolve().then(function () {
postMessage("resumed");
leaveEventLoop();
});
};
postMessage("paused");
enterEventLoop();
};
postMessage("resolved");
});
};

View File

@ -0,0 +1,25 @@
"use strict";
self.onmessage = function (event) {
if (event.data !== "resolve") {
return;
}
// This then-handler should be executed inside the top-level event loop,
// within the context of the worker's global.
Promise.resolve().then(function () {
self.onmessage = function (event) {
if (event.data !== "pause") {
return;
}
// This then-handler should be executed inside the top-level event loop,
// within the context of the worker's global. Because the debugger
// statement here below enters a nested event loop, the then-handler
// should not be executed until the debugger statement returns.
Promise.resolve().then(function () {
postMessage("resumed");
});
debugger;
}
postMessage("resolved");
});
};

View File

@ -28,6 +28,8 @@ support-files =
WorkerDebuggerManager_childWorker.js
WorkerDebuggerManager_worker.js
WorkerDebugger_childWorker.js
WorkerDebugger_promise_debugger.js
WorkerDebugger_promise_worker.js
WorkerDebugger_worker.js
WorkerDebugger_sharedWorker.js
WorkerDebugger_suspended_debugger.js
@ -67,6 +69,7 @@ support-files =
[test_WorkerDebuggerManager.xul]
[test_WorkerDebugger_console.xul]
[test_WorkerDebugger_frozen.xul]
[test_WorkerDebugger_promise.xul]
[test_WorkerDebugger_suspended.xul]
[test_bug883784.xul]
[test_chromeWorker.xul]

View File

@ -0,0 +1,70 @@
<?xml version="1.0"?>
<!--
Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
-->
<window title="Test for WorkerDebugger with DOM Promises"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="test();">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript" src="dom_worker_helper.js"/>
<script type="application/javascript">
<![CDATA[
const WORKER_URL = "WorkerDebugger_promise_worker.js";
const DEBUGGER_URL = BASE_URL + "WorkerDebugger_promise_debugger.js";
function test() {
Task.spawn(function* () {
SimpleTest.waitForExplicitFinish();
let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
let worker = new Worker(WORKER_URL);
let dbg = yield promise;
info("Send a request to the worker. This should cause the worker " +
"to send a response.");
promise = waitForWorkerMessage(worker, "resolved");
worker.postMessage("resolve");
yield promise;
info("Send a request to the debugger. This should cause the debugger " +
"to send a response.");
promise = waitForDebuggerMessage(dbg, "resolved");
dbg.postMessage("resolve");
yield promise;
info("Send a request to the worker. This should cause the debugger " +
"to enter a nested event loop.");
promise = waitForDebuggerMessage(dbg, "paused");
worker.postMessage("pause");
yield promise;
info("Send a request to the debugger. This should cause the debugger " +
"to leave the nested event loop.");
promise = waitForMultiple([
waitForDebuggerMessage(dbg, "resumed"),
waitForWorkerMessage(worker, "resumed")
]);
dbg.postMessage("resume");
yield promise;
SimpleTest.finish();
});
}
]]>
</script>
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display"></p>
<div id="content" style="display:none;"></div>
<pre id="test"></pre>
</body>
<label id="test-result"/>
</window>

View File

@ -953,7 +953,7 @@ CycleCollectedJSRuntime::EnqueuePromiseJobCallback(JSContext* aCx,
MOZ_ASSERT(Get() == self);
nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aCx, aJob);
self->GetPromiseMicroTaskQueue().push(runnable);
self->DispatchToMicroTask(runnable);
return true;
}
@ -1138,6 +1138,13 @@ CycleCollectedJSRuntime::GetPromiseMicroTaskQueue()
return mPromiseMicroTaskQueue;
}
std::queue<nsCOMPtr<nsIRunnable>>&
CycleCollectedJSRuntime::GetDebuggerPromiseMicroTaskQueue()
{
MOZ_ASSERT(mJSRuntime);
return mDebuggerPromiseMicroTaskQueue;
}
nsCycleCollectionParticipant*
CycleCollectedJSRuntime::GCThingParticipant()
{
@ -1369,10 +1376,11 @@ CycleCollectedJSRuntime::AfterProcessTask(uint32_t aRecursionDepth)
// Step 4.1: Execute microtasks.
if (NS_IsMainThread()) {
nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
Promise::PerformMicroTaskCheckpoint();
} else {
Promise::PerformWorkerMicroTaskCheckpoint();
}
Promise::PerformMicroTaskCheckpoint();
// Step 4.2 Execute any events that were waiting for a stable state.
ProcessStableStateQueue();
}
@ -1650,6 +1658,15 @@ CycleCollectedJSRuntime::PrepareWaitingZonesForGC()
}
}
void
CycleCollectedJSRuntime::DispatchToMicroTask(nsIRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aRunnable);
mPromiseMicroTaskQueue.push(aRunnable);
}
void
CycleCollectedJSRuntime::EnvironmentPreparer::invoke(JS::HandleObject scope,
js::ScriptEnvironmentPreparer::Closure& closure)

View File

@ -158,6 +158,9 @@ protected:
return true; // Don't block context creation.
}
std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue;
private:
void
DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
@ -285,6 +288,7 @@ public:
void SetPendingException(nsIException* aException);
std::queue<nsCOMPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
std::queue<nsCOMPtr<nsIRunnable>>& GetDebuggerPromiseMicroTaskQueue();
nsCycleCollectionParticipant* GCThingParticipant();
nsCycleCollectionParticipant* ZoneParticipant();
@ -348,6 +352,9 @@ public:
// full GC.
void PrepareWaitingZonesForGC();
// Queue an async microtask to the current main or worker thread.
virtual void DispatchToMicroTask(nsIRunnable* aRunnable);
// Storage for watching rejected promises waiting for some client to
// consume their rejection.
// We store values as `nsISupports` to avoid adding compile-time dependencies
@ -378,8 +385,6 @@ private:
nsCOMPtr<nsIException> mPendingException;
nsThread* mOwningThread; // Manual refcounting to avoid include hell.
std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
struct RunInMetastableStateData
{
nsCOMPtr<nsIRunnable> mRunnable;