mirror of
https://github.com/roytam1/basilisk55.git
synced 2026-05-26 15:02:46 +00:00
ported from UXP: Issue #2240 - Align Microtasks and promises scheduling with spec (f059bb0a)
some more portions (in ServiceWorkerPrivate.cpp) from original bug1193394.
This commit is contained in:
@@ -442,7 +442,7 @@ CycleCollectedJSContext::CycleCollectedJSContext()
|
||||
, mPrevGCNurseryCollectionCallback(nullptr)
|
||||
, mJSHolders(256)
|
||||
, mDoingStableStates(false)
|
||||
, mDisableMicroTaskCheckpoint(false)
|
||||
, mTargetedMicroTaskRecursionDepth(0)
|
||||
, mMicroTaskLevel(0)
|
||||
, mMicroTaskRecursionDepth(0)
|
||||
, mOutOfMemoryState(OOMState::OK)
|
||||
@@ -463,8 +463,8 @@ CycleCollectedJSContext::~CycleCollectedJSContext()
|
||||
MOZ_ASSERT(!mDeferredFinalizerTable.Count());
|
||||
|
||||
// Last chance to process any events.
|
||||
ProcessMetastableStateQueue(mBaseRecursionDepth);
|
||||
MOZ_ASSERT(mMetastableStateEvents.IsEmpty());
|
||||
CleanupIDBTransactions(mBaseRecursionDepth);
|
||||
MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
|
||||
|
||||
ProcessStableStateQueue();
|
||||
MOZ_ASSERT(mStableStateEvents.IsEmpty());
|
||||
@@ -472,8 +472,8 @@ CycleCollectedJSContext::~CycleCollectedJSContext()
|
||||
// Clear mPendingException first, since it might be cycle collected.
|
||||
mPendingException = nullptr;
|
||||
|
||||
MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty());
|
||||
MOZ_ASSERT(mPromiseMicroTaskQueue.empty());
|
||||
MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
|
||||
MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
|
||||
|
||||
mUncaughtRejections.reset();
|
||||
mConsumedRejections.reset();
|
||||
@@ -947,7 +947,7 @@ CycleCollectedJSContext::SizeofExternalStringCallback(JSString* aStr,
|
||||
return buf->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
|
||||
}
|
||||
|
||||
class PromiseJobRunnable final : public Runnable
|
||||
class PromiseJobRunnable final : public MicroTaskRunnable
|
||||
{
|
||||
public:
|
||||
PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite,
|
||||
@@ -961,15 +961,21 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
NS_IMETHOD
|
||||
Run() override
|
||||
virtual void Run(AutoSlowOperation& aAso) override
|
||||
{
|
||||
JSObject* callback = mCallback->CallbackPreserveColor();
|
||||
nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
|
||||
if (global && !global->IsDying()) {
|
||||
mCallback->Call("promise callback");
|
||||
aAso.CheckForInterrupt();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
virtual bool Suppressed() override
|
||||
{
|
||||
nsIGlobalObject* global =
|
||||
xpc::NativeGlobal(mCallback->CallbackPreserveColor());
|
||||
return global && global->IsInSyncOperation();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -1003,7 +1009,7 @@ CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx,
|
||||
if (aIncumbentGlobal) {
|
||||
global = xpc::NativeGlobal(aIncumbentGlobal);
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
|
||||
RefPtr<MicroTaskRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
|
||||
self->DispatchToMicroTask(runnable.forget());
|
||||
return true;
|
||||
}
|
||||
@@ -1202,18 +1208,18 @@ CycleCollectedJSContext::SetPendingException(nsIException* aException)
|
||||
mPendingException = aException;
|
||||
}
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>>&
|
||||
CycleCollectedJSContext::GetPromiseMicroTaskQueue()
|
||||
std::queue<RefPtr<MicroTaskRunnable>>&
|
||||
CycleCollectedJSContext::GetMicroTaskQueue()
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
return mPromiseMicroTaskQueue;
|
||||
return mPendingMicroTaskRunnables;
|
||||
}
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>>&
|
||||
CycleCollectedJSContext::GetDebuggerPromiseMicroTaskQueue()
|
||||
std::queue<RefPtr<MicroTaskRunnable>>&
|
||||
CycleCollectedJSContext::GetDebuggerMicroTaskQueue()
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
return mDebuggerPromiseMicroTaskQueue;
|
||||
return mDebuggerMicroTaskQueue;
|
||||
}
|
||||
|
||||
nsCycleCollectionParticipant*
|
||||
@@ -1372,24 +1378,24 @@ CycleCollectedJSContext::ProcessStableStateQueue()
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
|
||||
CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth)
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
MOZ_RELEASE_ASSERT(!mDoingStableStates);
|
||||
mDoingStableStates = true;
|
||||
|
||||
nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents);
|
||||
nsTArray<PendingIDBTransactionData> localQueue = Move(mPendingIDBTransactions);
|
||||
|
||||
for (uint32_t i = 0; i < localQueue.Length(); ++i)
|
||||
{
|
||||
RunInMetastableStateData& data = localQueue[i];
|
||||
PendingIDBTransactionData& data = localQueue[i];
|
||||
if (data.mRecursionDepth != aRecursionDepth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget();
|
||||
runnable->Run();
|
||||
nsCOMPtr<nsIRunnable> transaction = data.mTransaction.forget();
|
||||
transaction->Run();
|
||||
}
|
||||
|
||||
localQueue.RemoveElementAt(i--);
|
||||
@@ -1397,11 +1403,27 @@ CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
|
||||
|
||||
// If the queue has events in it now, they were added from something we called,
|
||||
// so they belong at the end of the queue.
|
||||
localQueue.AppendElements(mMetastableStateEvents);
|
||||
localQueue.SwapElements(mMetastableStateEvents);
|
||||
localQueue.AppendElements(mPendingIDBTransactions);
|
||||
localQueue.SwapElements(mPendingIDBTransactions);
|
||||
mDoingStableStates = false;
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock)
|
||||
{
|
||||
// If ProcessNextEvent was called during a microtask callback, we
|
||||
// must process any pending microtasks before blocking in the event loop,
|
||||
// otherwise we may deadlock until an event enters the queue later.
|
||||
if (aMightBlock && PerformMicroTaskCheckPoint()) {
|
||||
// If any microtask was processed, we post a dummy event in order to
|
||||
// force the ProcessNextEvent call not to block. This is required
|
||||
// to support nested event loops implemented using a pattern like
|
||||
// "while (condition) thread.processNextEvent(true)", in case the
|
||||
// condition is triggered here by a Promise "then" callback.
|
||||
NS_DispatchToMainThread(new Runnable());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
|
||||
{
|
||||
@@ -1409,39 +1431,20 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
|
||||
|
||||
// See HTML 6.1.4.2 Processing model
|
||||
|
||||
// Execute any events that were waiting for a microtask to complete.
|
||||
// This is not (yet) in the spec.
|
||||
ProcessMetastableStateQueue(aRecursionDepth);
|
||||
|
||||
// Step 4.1: Execute microtasks.
|
||||
if (!mDisableMicroTaskCheckpoint) {
|
||||
PerformMicroTaskCheckPoint();
|
||||
if (NS_IsMainThread()) {
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
} else {
|
||||
Promise::PerformWorkerMicroTaskCheckpoint();
|
||||
}
|
||||
}
|
||||
PerformMicroTaskCheckPoint();
|
||||
|
||||
// Step 4.2 Execute any events that were waiting for a stable state.
|
||||
ProcessStableStateQueue();
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessMicrotask()
|
||||
CycleCollectedJSContext::AfterProcessMicrotasks()
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
AfterProcessMicrotask(RecursionDepth());
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth)
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
|
||||
// Between microtasks, execute any events that were waiting for a microtask
|
||||
// to complete.
|
||||
ProcessMetastableStateQueue(aRecursionDepth);
|
||||
// Cleanup Indexed Database transactions:
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
|
||||
CleanupIDBTransactions(RecursionDepth());
|
||||
}
|
||||
|
||||
uint32_t
|
||||
@@ -1458,12 +1461,12 @@ CycleCollectedJSContext::RunInStableState(already_AddRefed<nsIRunnable>&& aRunna
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable)
|
||||
CycleCollectedJSContext::AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction)
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
|
||||
RunInMetastableStateData data;
|
||||
data.mRunnable = aRunnable;
|
||||
PendingIDBTransactionData data;
|
||||
data.mTransaction = aTransaction;
|
||||
|
||||
MOZ_ASSERT(mOwningThread);
|
||||
data.mRecursionDepth = RecursionDepth();
|
||||
@@ -1480,7 +1483,7 @@ CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aR
|
||||
}
|
||||
#endif
|
||||
|
||||
mMetastableStateEvents.AppendElement(Move(data));
|
||||
mPendingIDBTransactions.AppendElement(Move(data));
|
||||
}
|
||||
|
||||
IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx,
|
||||
@@ -1689,14 +1692,14 @@ CycleCollectedJSContext::PrepareWaitingZonesForGC()
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable)
|
||||
CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable)
|
||||
{
|
||||
RefPtr<nsIRunnable> runnable(aRunnable);
|
||||
RefPtr<MicroTaskRunnable> runnable(aRunnable);
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
mPromiseMicroTaskQueue.push(runnable.forget());
|
||||
mPendingMicroTaskRunnables.push(runnable.forget());
|
||||
}
|
||||
|
||||
class AsyncMutationHandler final : public mozilla::Runnable
|
||||
@@ -1712,41 +1715,61 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
bool
|
||||
CycleCollectedJSContext::PerformMicroTaskCheckPoint()
|
||||
{
|
||||
if (mPendingMicroTaskRunnables.empty()) {
|
||||
if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
|
||||
AfterProcessMicrotasks();
|
||||
// Nothing to do, return early.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t currentDepth = RecursionDepth();
|
||||
if (mMicroTaskRecursionDepth >= currentDepth) {
|
||||
// We are already executing microtasks for the current recursion depth.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mTargetedMicroTaskRecursionDepth != 0 &&
|
||||
mTargetedMicroTaskRecursionDepth != currentDepth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
|
||||
// Special case for main thread where DOM mutations may happen when
|
||||
// it is not safe to run scripts.
|
||||
nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
|
||||
MOZ_ASSERT(currentDepth > 0);
|
||||
mMicroTaskRecursionDepth = currentDepth;
|
||||
|
||||
bool didProcess = false;
|
||||
AutoSlowOperation aso;
|
||||
|
||||
std::queue<RefPtr<MicroTaskRunnable>> suppressed;
|
||||
while (!mPendingMicroTaskRunnables.empty()) {
|
||||
RefPtr<MicroTaskRunnable> runnable =
|
||||
mPendingMicroTaskRunnables.front().forget();
|
||||
mPendingMicroTaskRunnables.pop();
|
||||
for (;;) {
|
||||
RefPtr<MicroTaskRunnable> runnable;
|
||||
if (!mDebuggerMicroTaskQueue.empty()) {
|
||||
runnable = mDebuggerMicroTaskQueue.front().forget();
|
||||
mDebuggerMicroTaskQueue.pop();
|
||||
} else if (!mPendingMicroTaskRunnables.empty()) {
|
||||
runnable = mPendingMicroTaskRunnables.front().forget();
|
||||
mPendingMicroTaskRunnables.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (runnable->Suppressed()) {
|
||||
// Microtasks in worker shall never be suppressed.
|
||||
// Otherwise, mPendingMicroTaskRunnables will be replaced later with
|
||||
// all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
suppressed.push(runnable);
|
||||
} else {
|
||||
didProcess = true;
|
||||
runnable->Run(aso);
|
||||
}
|
||||
}
|
||||
@@ -1756,13 +1779,38 @@ CycleCollectedJSContext::PerformMicroTaskCheckPoint()
|
||||
// for some time, but no longer than spinning the event loop nestedly
|
||||
// (sync XHR, alert, etc.)
|
||||
mPendingMicroTaskRunnables.swap(suppressed);
|
||||
|
||||
AfterProcessMicrotasks();
|
||||
|
||||
return didProcess;
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::DispatchMicroTaskRunnable(
|
||||
already_AddRefed<MicroTaskRunnable> aRunnable)
|
||||
{
|
||||
mPendingMicroTaskRunnables.push(aRunnable);
|
||||
CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint()
|
||||
{
|
||||
// Don't do normal microtask handling checks here, since whoever is calling
|
||||
// this method is supposed to know what they are doing.
|
||||
|
||||
AutoSlowOperation aso;
|
||||
for (;;) {
|
||||
// For a debugger microtask checkpoint, we always use the debugger microtask
|
||||
// queue.
|
||||
std::queue<RefPtr<MicroTaskRunnable>>* microtaskQueue =
|
||||
&GetDebuggerMicroTaskQueue();
|
||||
|
||||
if (microtaskQueue->empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
RefPtr<MicroTaskRunnable> runnable = microtaskQueue->front().forget();
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
// This function can re-enter, so we remove the element before calling.
|
||||
microtaskQueue->pop();
|
||||
runnable->Run(aso);
|
||||
}
|
||||
|
||||
AfterProcessMicrotasks();
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
Reference in New Issue
Block a user