diff options
Diffstat (limited to 'xpcom')
-rw-r--r-- | xpcom/base/CycleCollectedJSContext.cpp | 71 | ||||
-rw-r--r-- | xpcom/base/CycleCollectedJSContext.h | 69 | ||||
-rw-r--r-- | xpcom/base/moz.build | 1 |
3 files changed, 140 insertions, 1 deletions
diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp index e16c15455..033ca562a 100644 --- a/xpcom/base/CycleCollectedJSContext.cpp +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -79,6 +79,7 @@ #include "nsCycleCollectionParticipant.h" #include "nsCycleCollector.h" #include "nsDOMJSUtils.h" +#include "nsDOMMutationObserver.h" #include "nsJSUtils.h" #include "nsWrapperCache.h" @@ -438,6 +439,8 @@ CycleCollectedJSContext::CycleCollectedJSContext() , mJSHolders(256) , mDoingStableStates(false) , mDisableMicroTaskCheckpoint(false) + , mMicroTaskLevel(0) + , mMicroTaskRecursionDepth(0) , mOutOfMemoryState(OOMState::OK) , mLargeAllocationFailureState(OOMState::OK) { @@ -1378,8 +1381,8 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) // Step 4.1: Execute microtasks. if (!mDisableMicroTaskCheckpoint) { + PerformMicroTaskCheckPoint(); if (NS_IsMainThread()) { - nsContentUtils::PerformMainThreadMicroTaskCheckpoint(); Promise::PerformMicroTaskCheckpoint(); } else { Promise::PerformWorkerMicroTaskCheckpoint(); @@ -1659,6 +1662,72 @@ CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunn mPromiseMicroTaskQueue.push(runnable.forget()); } +class AsyncMutationHandler final : public mozilla::Runnable +{ +public: + NS_IMETHOD Run() override + { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->PerformMicroTaskCheckPoint(); + } + return NS_OK; + } +}; + +void +CycleCollectedJSContext::PerformMicroTaskCheckPoint() +{ + if (mPendingMicroTaskRunnables.empty()) { + // Nothing to do, return early. + return; + } + + uint32_t currentDepth = RecursionDepth(); + if (mMicroTaskRecursionDepth >= currentDepth) { + // We are already executing microtasks for the current recursion depth. + return; + } + + 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; + } + + mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth); + MOZ_ASSERT(currentDepth > 0); + mMicroTaskRecursionDepth = currentDepth; + + AutoSlowOperation aso; + + std::queue<RefPtr<MicroTaskRunnable>> suppressed; + while (!mPendingMicroTaskRunnables.empty()) { + RefPtr<MicroTaskRunnable> runnable = + mPendingMicroTaskRunnables.front().forget(); + mPendingMicroTaskRunnables.pop(); + if (runnable->Suppressed()) { + suppressed.push(runnable); + } else { + runnable->Run(aso); + } + } + + // Put back the suppressed microtasks so that they will be run later. + // Note, it is possible that we end up keeping these suppressed tasks around + // for some time, but no longer than spinning the event loop nestedly + // (sync XHR, alert, etc.) + mPendingMicroTaskRunnables.swap(suppressed); +} + +void +CycleCollectedJSContext::DispatchMicroTaskRunnable( + already_AddRefed<MicroTaskRunnable> aRunnable) +{ + mPendingMicroTaskRunnables.push(aRunnable); +} + void CycleCollectedJSContext::EnvironmentPreparer::invoke(JS::HandleObject scope, js::ScriptEnvironmentPreparer::Closure& closure) diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h index ac4cf4361..4cd1479ed 100644 --- a/xpcom/base/CycleCollectedJSContext.h +++ b/xpcom/base/CycleCollectedJSContext.h @@ -33,6 +33,7 @@ struct Class; } // namespace js namespace mozilla { +class AutoSlowOperation; class JSGCThingParticipant: public nsCycleCollectionParticipant { @@ -134,6 +135,17 @@ struct CycleCollectorResults uint32_t mNumSlices; }; +class MicroTaskRunnable +{ +public: + MicroTaskRunnable() {} + NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable) + virtual void Run(AutoSlowOperation& aAso) = 0; + virtual bool Suppressed() { return false; } +protected: + virtual ~MicroTaskRunnable() {} +}; + class CycleCollectedJSContext { friend class JSGCThingParticipant; @@ -402,6 +414,39 @@ public: // Queue an async microtask to the current main or worker thread. virtual void DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable); + // Call EnterMicroTask when you're entering JS execution. + // Usually the best way to do this is to use nsAutoMicroTask. + void EnterMicroTask() + { + ++mMicroTaskLevel; + } + + void LeaveMicroTask() + { + if (--mMicroTaskLevel == 0) { + PerformMicroTaskCheckPoint(); + } + } + + bool IsInMicroTask() + { + return mMicroTaskLevel != 0; + } + + uint32_t MicroTaskLevel() + { + return mMicroTaskLevel; + } + + void SetMicroTaskLevel(uint32_t aLevel) + { + mMicroTaskLevel = aLevel; + } + + void PerformMicroTaskCheckPoint(); + + void DispatchMicroTaskRunnable(already_AddRefed<MicroTaskRunnable> aRunnable); + // Storage for watching rejected promises waiting for some client to // consume their rejection. @@ -452,6 +497,11 @@ private: bool mDisableMicroTaskCheckpoint; + uint32_t mMicroTaskLevel; + std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables; + + uint32_t mMicroTaskRecursionDepth; + OOMState mOutOfMemoryState; OOMState mLargeAllocationFailureState; @@ -470,6 +520,25 @@ private: EnvironmentPreparer mEnvironmentPreparer; }; +class MOZ_STACK_CLASS nsAutoMicroTask +{ +public: + nsAutoMicroTask() + { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->EnterMicroTask(); + } + } + ~nsAutoMicroTask() + { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->LeaveMicroTask(); + } + } +}; + void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer); // Returns true if the JS::TraceKind is one the cycle collector cares about. diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build index d6a336b40..0fdf47d7d 100644 --- a/xpcom/base/moz.build +++ b/xpcom/base/moz.build @@ -152,6 +152,7 @@ FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '../build', + '/dom/base', '/xpcom/ds', ] |