mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-05 05:30:29 +00:00
Bug 1229769 - We should be able to use DOM promises in the worker debugger;r=khuey
This commit is contained in:
parent
96226d8425
commit
3e2cb55980
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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!");
|
||||
}
|
||||
|
30
dom/workers/test/WorkerDebugger_promise_debugger.js
Normal file
30
dom/workers/test/WorkerDebugger_promise_debugger.js
Normal 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");
|
||||
});
|
||||
};
|
25
dom/workers/test/WorkerDebugger_promise_worker.js
Normal file
25
dom/workers/test/WorkerDebugger_promise_worker.js
Normal 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");
|
||||
});
|
||||
};
|
@ -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]
|
||||
|
70
dom/workers/test/test_WorkerDebugger_promise.xul
Normal file
70
dom/workers/test/test_WorkerDebugger_promise.xul
Normal 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>
|
@ -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)
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user