Bug 1910362 - Create new Nimbus helper r=aaronmt,ohorvath
[gecko.git] / xpcom / base / CycleCollectedJSContext.h
blob038f3c0d4e98e906193e603a43693e63c77e0a9a
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
10 #include <deque>
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"
19 #include "nsCOMPtr.h"
20 #include "nsRefPtrHashtable.h"
21 #include "nsTArray.h"
23 class nsCycleCollectionNoteRootCallback;
24 class nsIRunnable;
25 class nsThread;
27 namespace mozilla {
28 class AutoSlowOperation;
30 class CycleCollectedJSContext;
31 class CycleCollectedJSRuntime;
33 namespace dom {
34 class Exception;
35 class WorkerJSContext;
36 class WorkletJSContext;
37 } // namespace dom
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.
44 Init();
47 void Init() {
48 mForcedGC = false;
49 mSuspectedAtCCStart = 0;
50 mMergedZones = false;
51 mAnyManual = false;
52 mVisitedRefCounted = 0;
53 mVisitedGCed = 0;
54 mFreedRefCounted = 0;
55 mFreedGCed = 0;
56 mFreedJSZones = 0;
57 mNumSlices = 1;
58 // mNumSlices is initialized to one, because we call Init() after the
59 // per-slice increment of mNumSlices has already occurred.
62 bool mForcedGC;
63 bool mMergedZones;
64 // mAnyManual is true if any slice was manually triggered, and at shutdown.
65 bool mAnyManual;
66 uint32_t mSuspectedAtCCStart;
67 uint32_t mVisitedRefCounted;
68 uint32_t mVisitedGCed;
69 uint32_t mFreedRefCounted;
70 uint32_t mFreedGCed;
71 uint32_t mFreedJSZones;
72 uint32_t mNumSlices;
75 class MicroTaskRunnable {
76 public:
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; }
82 protected:
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 {
89 public:
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 {
107 public:
108 explicit FinalizationRegistryCleanup(CycleCollectedJSContext* aContext);
109 void Init();
110 void Destroy();
111 void QueueCallback(JSFunction* aDoCleanup, JSObject* aHostDefinedData);
112 MOZ_CAN_RUN_SCRIPT void DoCleanup();
114 private:
115 static void QueueCallback(JSFunction* aDoCleanup, JSObject* aHostDefinedData,
116 void* aData);
118 class CleanupRunnable;
120 struct Callback {
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;
138 protected:
139 CycleCollectedJSContext();
140 virtual ~CycleCollectedJSContext();
142 MOZ_IS_CLASS_INIT
143 nsresult Initialize(JSRuntime* aParentRuntime, uint32_t aMaxBytes);
145 virtual CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) = 0;
147 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
149 private:
150 static void PromiseRejectionTrackerCallback(
151 JSContext* aCx, bool aMutedErrors, JS::Handle<JSObject*> aPromise,
152 JS::PromiseRejectionHandlingState state, void* aData);
154 void AfterProcessMicrotasks();
156 public:
157 void ProcessStableStateQueue();
159 private:
160 void CleanupIDBTransactions(uint32_t aRecursionDepth);
162 public:
163 virtual dom::WorkerJSContext* GetAsWorkerJSContext() { return nullptr; }
164 virtual dom::WorkletJSContext* GetAsWorkletJSContext() { return nullptr; }
166 CycleCollectedJSRuntime* Runtime() const {
167 MOZ_ASSERT(mRuntime);
168 return 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);
179 return 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; }
193 protected:
194 JSContext* MaybeContext() const { return mJSContext; }
196 public:
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
227 // isn't one.
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; }
238 MOZ_CAN_RUN_SCRIPT
239 void LeaveMicroTask() {
240 if (--mMicroTaskLevel == 0) {
241 PerformMicroTaskCheckPoint();
245 uint32_t MicroTaskLevel() const { return mMicroTaskLevel; }
247 void SetMicroTaskLevel(uint32_t aLevel) { mMicroTaskLevel = aLevel; }
249 MOZ_CAN_RUN_SCRIPT
250 bool PerformMicroTaskCheckPoint(bool aForce = false);
252 MOZ_CAN_RUN_SCRIPT
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>>
264 mUncaughtRejections;
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>>
270 mConsumedRejections;
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
291 // is destroyed.
292 void BeginExecutionTracingAsync();
293 void EndExecutionTracingAsync();
295 private:
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
300 // details.
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;
318 private:
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
337 // is the set value.
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
364 // handled.
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
373 // handler.
374 typedef nsRefPtrHashtable<nsUint64HashKey, dom::Promise> PromiseHashtable;
375 PromiseHashtable mPendingUnhandledRejections;
377 class NotifyUnhandledRejections final : public CancelableRunnable {
378 public:
379 explicit NotifyUnhandledRejections(PromiseArray&& aPromises)
380 : CancelableRunnable("NotifyUnhandledRejections"),
381 mUnhandledRejections(std::move(aPromises)) {}
383 NS_IMETHOD Run() final;
385 nsresult Cancel() final;
387 private:
388 PromiseArray mUnhandledRejections;
391 FinalizationRegistryCleanup mFinalizationRegistryCleanup;
394 class MOZ_STACK_CLASS nsAutoMicroTask {
395 public:
396 nsAutoMicroTask() {
397 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
398 if (ccjs) {
399 ccjs->EnterMicroTask();
402 MOZ_CAN_RUN_SCRIPT ~nsAutoMicroTask() {
403 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
404 if (ccjs) {
405 ccjs->LeaveMicroTask();
410 } // namespace mozilla
412 #endif // mozilla_CycleCollectedJSContext_h