1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_CycleCollectedJSContext_h
8 #define mozilla_CycleCollectedJSContext_h
12 #include "mozilla/Attributes.h"
13 #include "mozilla/MemoryReporting.h"
14 #include "mozilla/dom/AtomList.h"
15 #include "mozilla/dom/Promise.h"
16 #include "js/GCVector.h"
17 #include "js/Promise.h"
20 #include "nsRefPtrHashtable.h"
23 class nsCycleCollectionNoteRootCallback
;
28 class AutoSlowOperation
;
30 class CycleCollectedJSContext
;
31 class CycleCollectedJSRuntime
;
35 class WorkerJSContext
;
36 class WorkletJSContext
;
39 // Contains various stats about the cycle collection.
40 struct CycleCollectorResults
{
41 CycleCollectorResults() {
42 // Initialize here so when we increment mNumSlices the first time we're
43 // not using uninitialized memory.
49 mSuspectedAtCCStart
= 0;
52 mVisitedRefCounted
= 0;
58 // mNumSlices is initialized to one, because we call Init() after the
59 // per-slice increment of mNumSlices has already occurred.
64 // mAnyManual is true if any slice was manually triggered, and at shutdown.
66 uint32_t mSuspectedAtCCStart
;
67 uint32_t mVisitedRefCounted
;
68 uint32_t mVisitedGCed
;
69 uint32_t mFreedRefCounted
;
71 uint32_t mFreedJSZones
;
75 class MicroTaskRunnable
{
77 MicroTaskRunnable() = default;
78 NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable
)
79 MOZ_CAN_RUN_SCRIPT
virtual void Run(AutoSlowOperation
& aAso
) = 0;
80 virtual bool Suppressed() { return false; }
83 virtual ~MicroTaskRunnable() = default;
86 // Store the suppressed mictotasks in another microtask so that operations
87 // for the microtask queue as a whole keep working.
88 class SuppressedMicroTasks
: public MicroTaskRunnable
{
90 explicit SuppressedMicroTasks(CycleCollectedJSContext
* aContext
);
92 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void Run(AutoSlowOperation
& aAso
) final
{}
93 virtual bool Suppressed();
95 CycleCollectedJSContext
* mContext
;
96 uint64_t mSuppressionGeneration
;
97 std::deque
<RefPtr
<MicroTaskRunnable
>> mSuppressedMicroTaskRunnables
;
100 // Support for JS FinalizationRegistry objects, which allow a JS callback to be
101 // registered that is called when objects die.
103 // We keep a vector of functions that call back into the JS engine along
104 // with their associated incumbent globals, one per FinalizationRegistry object
105 // that has pending cleanup work. These are run in their own task.
106 class FinalizationRegistryCleanup
{
108 explicit FinalizationRegistryCleanup(CycleCollectedJSContext
* aContext
);
111 void QueueCallback(JSFunction
* aDoCleanup
, JSObject
* aHostDefinedData
);
112 MOZ_CAN_RUN_SCRIPT
void DoCleanup();
115 static void QueueCallback(JSFunction
* aDoCleanup
, JSObject
* aHostDefinedData
,
118 class CleanupRunnable
;
121 JSFunction
* mCallbackFunction
;
122 JSObject
* mIncumbentGlobal
;
123 void trace(JSTracer
* trc
);
126 // This object is part of CycleCollectedJSContext, so it's safe to have a raw
127 // pointer to its containing context here.
128 CycleCollectedJSContext
* mContext
;
130 using CallbackVector
= JS::GCVector
<Callback
, 0, InfallibleAllocPolicy
>;
131 JS::PersistentRooted
<CallbackVector
> mCallbacks
;
134 class CycleCollectedJSContext
: dom::PerThreadAtomCache
, private JS::JobQueue
{
135 friend class CycleCollectedJSRuntime
;
136 friend class SuppressedMicroTasks
;
139 CycleCollectedJSContext();
140 virtual ~CycleCollectedJSContext();
143 nsresult
Initialize(JSRuntime
* aParentRuntime
, uint32_t aMaxBytes
);
145 virtual CycleCollectedJSRuntime
* CreateRuntime(JSContext
* aCx
) = 0;
147 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const;
150 static void PromiseRejectionTrackerCallback(
151 JSContext
* aCx
, bool aMutedErrors
, JS::Handle
<JSObject
*> aPromise
,
152 JS::PromiseRejectionHandlingState state
, void* aData
);
154 void AfterProcessMicrotasks();
157 void ProcessStableStateQueue();
160 void CleanupIDBTransactions(uint32_t aRecursionDepth
);
163 virtual dom::WorkerJSContext
* GetAsWorkerJSContext() { return nullptr; }
164 virtual dom::WorkletJSContext
* GetAsWorkletJSContext() { return nullptr; }
166 CycleCollectedJSRuntime
* Runtime() const {
167 MOZ_ASSERT(mRuntime
);
171 already_AddRefed
<dom::Exception
> GetPendingException() const;
172 void SetPendingException(dom::Exception
* aException
);
174 std::deque
<RefPtr
<MicroTaskRunnable
>>& GetMicroTaskQueue();
175 std::deque
<RefPtr
<MicroTaskRunnable
>>& GetDebuggerMicroTaskQueue();
177 JSContext
* Context() const {
178 MOZ_ASSERT(mJSContext
);
182 JS::RootingContext
* RootingCx() const {
183 MOZ_ASSERT(mJSContext
);
184 return JS::RootingContext::get(mJSContext
);
187 void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth
) {
188 mTargetedMicroTaskRecursionDepth
= aDepth
;
191 void UpdateMicroTaskSuppressionGeneration() { ++mSuppressionGeneration
; }
194 JSContext
* MaybeContext() const { return mJSContext
; }
197 // nsThread entrypoints
199 // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate
200 // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now.
201 // But we really should!
202 MOZ_CAN_RUN_SCRIPT_BOUNDARY
203 virtual void BeforeProcessTask(bool aMightBlock
);
204 // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate
205 // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now.
206 // But we really should!
207 MOZ_CAN_RUN_SCRIPT_BOUNDARY
208 virtual void AfterProcessTask(uint32_t aRecursionDepth
);
210 // Check whether any eager thresholds have been reached, which would mean
211 // an idle GC task (minor or major) would be useful.
212 virtual void MaybePokeGC();
214 uint32_t RecursionDepth() const;
216 // Run in stable state (call through nsContentUtils)
217 void RunInStableState(already_AddRefed
<nsIRunnable
>&& aRunnable
);
219 void AddPendingIDBTransaction(already_AddRefed
<nsIRunnable
>&& aTransaction
);
221 // Get the CycleCollectedJSContext for a JSContext.
222 // Returns null only if Initialize() has not completed on or during
223 // destruction of the CycleCollectedJSContext.
224 static CycleCollectedJSContext
* GetFor(JSContext
* aCx
);
226 // Get the current thread's CycleCollectedJSContext. Returns null if there
228 static CycleCollectedJSContext
* Get();
230 // Queue an async microtask to the current main or worker thread.
231 virtual void DispatchToMicroTask(
232 already_AddRefed
<MicroTaskRunnable
> aRunnable
);
234 // Call EnterMicroTask when you're entering JS execution.
235 // Usually the best way to do this is to use nsAutoMicroTask.
236 void EnterMicroTask() { ++mMicroTaskLevel
; }
239 void LeaveMicroTask() {
240 if (--mMicroTaskLevel
== 0) {
241 PerformMicroTaskCheckPoint();
245 uint32_t MicroTaskLevel() const { return mMicroTaskLevel
; }
247 void SetMicroTaskLevel(uint32_t aLevel
) { mMicroTaskLevel
= aLevel
; }
250 bool PerformMicroTaskCheckPoint(bool aForce
= false);
253 void PerformDebuggerMicroTaskCheckpoint();
255 bool IsInStableOrMetaStableState() const { return mDoingStableStates
; }
257 // Storage for watching rejected promises waiting for some client to
258 // consume their rejection.
259 // Promises in this list have been rejected in the last turn of the
260 // event loop without the rejection being handled.
261 // Note that this can contain nullptrs in place of promises removed because
262 // they're consumed before it'd be reported.
263 JS::PersistentRooted
<JS::GCVector
<JSObject
*, 0, js::SystemAllocPolicy
>>
266 // Promises in this list have previously been reported as rejected
267 // (because they were in the above list), but the rejection was handled
268 // in the last turn of the event loop.
269 JS::PersistentRooted
<JS::GCVector
<JSObject
*, 0, js::SystemAllocPolicy
>>
271 nsTArray
<nsCOMPtr
<nsISupports
/* UncaughtRejectionObserver */>>
272 mUncaughtRejectionObservers
;
274 virtual bool IsSystemCaller() const = 0;
276 // Unused on main thread. Used by AutoJSAPI on Worker and Worklet threads.
277 virtual void ReportError(JSErrorReport
* aReport
,
278 JS::ConstUTF8CharsZ aToStringResult
) {
279 MOZ_ASSERT_UNREACHABLE("Not supported");
282 // These two functions control a special flag variable which lets us turn
283 // tracing on and off from a thread other than this JSContext's main thread.
284 // This is useful because we want to be able to start tracing many threads
285 // all at once from the Gecko Profiler in Firefox.
287 // NOTE: the caller must ensure that this CycleCollectedJSContext is not
288 // being destroyed when this is called. At the time of this API being added,
289 // the only consumer is the Gecko Profiler, which guarantees this via a mutex
290 // around unregistering the context, which always occurs before the context
292 void BeginExecutionTracingAsync();
293 void EndExecutionTracingAsync();
296 // JS::JobQueue implementation: see js/public/Promise.h.
297 // SpiderMonkey uses some of these methods to enqueue promise resolution jobs.
298 // Others protect the debuggee microtask queue from the debugger's
299 // interruptions; see the comments on JS::AutoDebuggerJobQueueInterruption for
301 bool getHostDefinedData(JSContext
* cx
,
302 JS::MutableHandle
<JSObject
*> aData
) const override
;
304 bool enqueuePromiseJob(JSContext
* cx
, JS::Handle
<JSObject
*> promise
,
305 JS::Handle
<JSObject
*> job
,
306 JS::Handle
<JSObject
*> allocationSite
,
307 JS::Handle
<JSObject
*> hostDefinedData
) override
;
308 // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now so we don't have to change SpiderMonkey
309 // headers. The caller presumably knows this can run script (like everything
310 // in SpiderMonkey!) and will deal.
311 MOZ_CAN_RUN_SCRIPT_BOUNDARY
312 void runJobs(JSContext
* cx
) override
;
313 bool empty() const override
;
314 bool isDrainingStopped() const override
{ return false; }
315 class SavedMicroTaskQueue
;
316 js::UniquePtr
<SavedJobQueue
> saveJobQueue(JSContext
*) override
;
319 CycleCollectedJSRuntime
* mRuntime
;
321 JSContext
* mJSContext
;
323 nsCOMPtr
<dom::Exception
> mPendingException
;
324 nsThread
* mOwningThread
; // Manual refcounting to avoid include hell.
326 struct PendingIDBTransactionData
{
327 nsCOMPtr
<nsIRunnable
> mTransaction
;
328 uint32_t mRecursionDepth
;
331 nsTArray
<nsCOMPtr
<nsIRunnable
>> mStableStateEvents
;
332 nsTArray
<PendingIDBTransactionData
> mPendingIDBTransactions
;
333 uint32_t mBaseRecursionDepth
;
334 bool mDoingStableStates
;
336 // If set to none 0, microtasks will be processed only when recursion depth
338 uint32_t mTargetedMicroTaskRecursionDepth
;
340 uint32_t mMicroTaskLevel
;
342 std::deque
<RefPtr
<MicroTaskRunnable
>> mPendingMicroTaskRunnables
;
343 std::deque
<RefPtr
<MicroTaskRunnable
>> mDebuggerMicroTaskQueue
;
344 RefPtr
<SuppressedMicroTasks
> mSuppressedMicroTasks
;
345 uint64_t mSuppressionGeneration
;
347 // How many times the debugger has interrupted execution, possibly creating
348 // microtask checkpoints in places that they would not normally occur.
349 uint32_t mDebuggerRecursionDepth
;
351 uint32_t mMicroTaskRecursionDepth
;
353 // This implements about-to-be-notified rejected promises list in the spec.
354 // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
355 typedef nsTArray
<RefPtr
<dom::Promise
>> PromiseArray
;
356 PromiseArray mAboutToBeNotifiedRejectedPromises
;
358 // This is for the "outstanding rejected promises weak set" in the spec,
359 // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set
360 // We use different data structure and opposite logic here to achieve the same
361 // effect. Basically this is used for tracking the rejected promise that does
362 // NOT need firing a rejectionhandled event. We will check the table to see if
363 // firing rejectionhandled event is required when a rejected promise is being
366 // The rejected promise will be stored in the table if
367 // - it is unhandled, and
368 // - The unhandledrejection is not yet fired.
370 // And be removed when
371 // - it is handled, or
372 // - A unhandledrejection is fired and it isn't being handled in event
374 typedef nsRefPtrHashtable
<nsUint64HashKey
, dom::Promise
> PromiseHashtable
;
375 PromiseHashtable mPendingUnhandledRejections
;
377 class NotifyUnhandledRejections final
: public CancelableRunnable
{
379 explicit NotifyUnhandledRejections(PromiseArray
&& aPromises
)
380 : CancelableRunnable("NotifyUnhandledRejections"),
381 mUnhandledRejections(std::move(aPromises
)) {}
383 NS_IMETHOD
Run() final
;
385 nsresult
Cancel() final
;
388 PromiseArray mUnhandledRejections
;
391 FinalizationRegistryCleanup mFinalizationRegistryCleanup
;
394 class MOZ_STACK_CLASS nsAutoMicroTask
{
397 CycleCollectedJSContext
* ccjs
= CycleCollectedJSContext::Get();
399 ccjs
->EnterMicroTask();
402 MOZ_CAN_RUN_SCRIPT
~nsAutoMicroTask() {
403 CycleCollectedJSContext
* ccjs
= CycleCollectedJSContext::Get();
405 ccjs
->LeaveMicroTask();
410 } // namespace mozilla
412 #endif // mozilla_CycleCollectedJSContext_h