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/. */
6 
7 #ifndef mozilla_CycleCollectedJSContext_h
8 #define mozilla_CycleCollectedJSContext_h
9 
10 #include <queue>
11 
12 #include "mozilla/Attributes.h"
13 #include "mozilla/DeferredFinalize.h"
14 #include "mozilla/LinkedList.h"
15 #include "mozilla/mozalloc.h"
16 #include "mozilla/MemoryReporting.h"
17 #include "mozilla/dom/AtomList.h"
18 #include "mozilla/dom/Promise.h"
19 #include "jsapi.h"
20 #include "js/GCVector.h"
21 #include "js/Promise.h"
22 
23 #include "nsCOMPtr.h"
24 #include "nsCycleCollectionParticipant.h"
25 #include "nsTArray.h"
26 
27 class nsCycleCollectionNoteRootCallback;
28 class nsIRunnable;
29 class nsThread;
30 class nsWrapperCache;
31 
32 namespace mozilla {
33 class AutoSlowOperation;
34 
35 class CycleCollectedJSRuntime;
36 
37 namespace dom {
38 class Exception;
39 class WorkerJSContext;
40 class WorkletJSContext;
41 }  // namespace dom
42 
43 // Contains various stats about the cycle collection.
44 struct CycleCollectorResults {
CycleCollectorResultsCycleCollectorResults45   CycleCollectorResults() {
46     // Initialize here so when we increment mNumSlices the first time we're
47     // not using uninitialized memory.
48     Init();
49   }
50 
InitCycleCollectorResults51   void Init() {
52     mForcedGC = false;
53     mMergedZones = false;
54     mAnyManual = false;
55     mVisitedRefCounted = 0;
56     mVisitedGCed = 0;
57     mFreedRefCounted = 0;
58     mFreedGCed = 0;
59     mFreedJSZones = 0;
60     mNumSlices = 1;
61     // mNumSlices is initialized to one, because we call Init() after the
62     // per-slice increment of mNumSlices has already occurred.
63   }
64 
65   bool mForcedGC;
66   bool mMergedZones;
67   // mAnyManual is true if any slice was manually triggered, and at shutdown.
68   bool mAnyManual;
69   uint32_t mVisitedRefCounted;
70   uint32_t mVisitedGCed;
71   uint32_t mFreedRefCounted;
72   uint32_t mFreedGCed;
73   uint32_t mFreedJSZones;
74   uint32_t mNumSlices;
75 };
76 
77 class MicroTaskRunnable {
78  public:
79   MicroTaskRunnable() = default;
80   NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable)
81   MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) = 0;
Suppressed()82   virtual bool Suppressed() { return false; }
83 
84  protected:
85   virtual ~MicroTaskRunnable() = default;
86 };
87 
88 class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue {
89   friend class CycleCollectedJSRuntime;
90 
91  protected:
92   CycleCollectedJSContext();
93   virtual ~CycleCollectedJSContext();
94 
95   MOZ_IS_CLASS_INIT
96   nsresult Initialize(JSRuntime* aParentRuntime, uint32_t aMaxBytes);
97 
98   virtual CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) = 0;
99 
100   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
101 
102  private:
103   static JSObject* GetIncumbentGlobalCallback(JSContext* aCx);
104   static bool EnqueuePromiseJobCallback(JSContext* aCx,
105                                         JS::HandleObject aPromise,
106                                         JS::HandleObject aJob,
107                                         JS::HandleObject aAllocationSite,
108                                         JS::HandleObject aIncumbentGlobal,
109                                         void* aData);
110   static void PromiseRejectionTrackerCallback(
111       JSContext* aCx, bool aMutedErrors, JS::HandleObject aPromise,
112       JS::PromiseRejectionHandlingState state, void* aData);
113 
114   void AfterProcessMicrotasks();
115 
116  public:
117   void ProcessStableStateQueue();
118 
119  private:
120   void CleanupIDBTransactions(uint32_t aRecursionDepth);
121 
122  public:
123   enum DeferredFinalizeType {
124     FinalizeIncrementally,
125     FinalizeNow,
126   };
127 
GetAsWorkerJSContext()128   virtual dom::WorkerJSContext* GetAsWorkerJSContext() { return nullptr; }
GetAsWorkletJSContext()129   virtual dom::WorkletJSContext* GetAsWorkletJSContext() { return nullptr; }
130 
Runtime()131   CycleCollectedJSRuntime* Runtime() const {
132     MOZ_ASSERT(mRuntime);
133     return mRuntime;
134   }
135 
136   already_AddRefed<dom::Exception> GetPendingException() const;
137   void SetPendingException(dom::Exception* aException);
138 
139   std::queue<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue();
140   std::queue<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue();
141 
Context()142   JSContext* Context() const {
143     MOZ_ASSERT(mJSContext);
144     return mJSContext;
145   }
146 
RootingCx()147   JS::RootingContext* RootingCx() const {
148     MOZ_ASSERT(mJSContext);
149     return JS::RootingContext::get(mJSContext);
150   }
151 
SetTargetedMicroTaskRecursionDepth(uint32_t aDepth)152   void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth) {
153     mTargetedMicroTaskRecursionDepth = aDepth;
154   }
155 
156  protected:
MaybeContext()157   JSContext* MaybeContext() const { return mJSContext; }
158 
159  public:
160   // nsThread entrypoints
161   //
162   // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate
163   // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now.
164   // But we really should!
165   MOZ_CAN_RUN_SCRIPT_BOUNDARY
166   virtual void BeforeProcessTask(bool aMightBlock);
167   // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate
168   // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now.
169   // But we really should!
170   MOZ_CAN_RUN_SCRIPT_BOUNDARY
171   virtual void AfterProcessTask(uint32_t aRecursionDepth);
172 
173   // Check whether we need an idle GC task.
174   void IsIdleGCTaskNeeded() const;
175 
176   uint32_t RecursionDepth() const;
177 
178   // Run in stable state (call through nsContentUtils)
179   void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
180 
181   void AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction);
182 
183   // Get the CycleCollectedJSContext for a JSContext.
184   // Returns null only if Initialize() has not completed on or during
185   // destruction of the CycleCollectedJSContext.
186   static CycleCollectedJSContext* GetFor(JSContext* aCx);
187 
188   // Get the current thread's CycleCollectedJSContext.  Returns null if there
189   // isn't one.
190   static CycleCollectedJSContext* Get();
191 
192   // Queue an async microtask to the current main or worker thread.
193   virtual void DispatchToMicroTask(
194       already_AddRefed<MicroTaskRunnable> aRunnable);
195 
196   // Call EnterMicroTask when you're entering JS execution.
197   // Usually the best way to do this is to use nsAutoMicroTask.
EnterMicroTask()198   void EnterMicroTask() { ++mMicroTaskLevel; }
199 
200   MOZ_CAN_RUN_SCRIPT
LeaveMicroTask()201   void LeaveMicroTask() {
202     if (--mMicroTaskLevel == 0) {
203       PerformMicroTaskCheckPoint();
204     }
205   }
206 
IsInMicroTask()207   bool IsInMicroTask() const { return mMicroTaskLevel != 0; }
208 
MicroTaskLevel()209   uint32_t MicroTaskLevel() const { return mMicroTaskLevel; }
210 
SetMicroTaskLevel(uint32_t aLevel)211   void SetMicroTaskLevel(uint32_t aLevel) { mMicroTaskLevel = aLevel; }
212 
213   MOZ_CAN_RUN_SCRIPT
214   bool PerformMicroTaskCheckPoint(bool aForce = false);
215 
216   MOZ_CAN_RUN_SCRIPT
217   void PerformDebuggerMicroTaskCheckpoint();
218 
IsInStableOrMetaStableState()219   bool IsInStableOrMetaStableState() const { return mDoingStableStates; }
220 
221   // Storage for watching rejected promises waiting for some client to
222   // consume their rejection.
223   // Promises in this list have been rejected in the last turn of the
224   // event loop without the rejection being handled.
225   // Note that this can contain nullptrs in place of promises removed because
226   // they're consumed before it'd be reported.
227   JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>>
228       mUncaughtRejections;
229 
230   // Promises in this list have previously been reported as rejected
231   // (because they were in the above list), but the rejection was handled
232   // in the last turn of the event loop.
233   JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>>
234       mConsumedRejections;
235   nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */>>
236       mUncaughtRejectionObservers;
237 
238   virtual bool IsSystemCaller() const = 0;
239 
240   // Unused on main thread.  Used by AutoJSAPI on Worker and Worklet threads.
ReportError(JSErrorReport * aReport,JS::ConstUTF8CharsZ aToStringResult)241   virtual void ReportError(JSErrorReport* aReport,
242                            JS::ConstUTF8CharsZ aToStringResult) {
243     MOZ_ASSERT_UNREACHABLE("Not supported");
244   }
245 
246  private:
247   // JS::JobQueue implementation: see js/public/Promise.h.
248   // SpiderMonkey uses some of these methods to enqueue promise resolution jobs.
249   // Others protect the debuggee microtask queue from the debugger's
250   // interruptions; see the comments on JS::AutoDebuggerJobQueueInterruption for
251   // details.
252   JSObject* getIncumbentGlobal(JSContext* cx) override;
253   bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise,
254                          JS::HandleObject job, JS::HandleObject allocationSite,
255                          JS::HandleObject incumbentGlobal) override;
256   // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now so we don't have to change SpiderMonkey
257   // headers.  The caller presumably knows this can run script (like everything
258   // in SpiderMonkey!) and will deal.
259   MOZ_CAN_RUN_SCRIPT_BOUNDARY
260   void runJobs(JSContext* cx) override;
261   bool empty() const override;
262   class SavedMicroTaskQueue;
263   js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) override;
264 
265   static void CleanupFinalizationRegistryCallback(JSObject* aRegistry,
266                                                   void* aData);
267   void QueueFinalizationRegistryForCleanup(JSObject* aRegistry);
268 
269  private:
270   CycleCollectedJSRuntime* mRuntime;
271 
272   JSContext* mJSContext;
273 
274   nsCOMPtr<dom::Exception> mPendingException;
275   nsThread* mOwningThread;  // Manual refcounting to avoid include hell.
276 
277   struct PendingIDBTransactionData {
278     nsCOMPtr<nsIRunnable> mTransaction;
279     uint32_t mRecursionDepth;
280   };
281 
282   nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
283   nsTArray<PendingIDBTransactionData> mPendingIDBTransactions;
284   uint32_t mBaseRecursionDepth;
285   bool mDoingStableStates;
286 
287   // If set to none 0, microtasks will be processed only when recursion depth
288   // is the set value.
289   uint32_t mTargetedMicroTaskRecursionDepth;
290 
291   uint32_t mMicroTaskLevel;
292 
293   std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables;
294   std::queue<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue;
295 
296   // How many times the debugger has interrupted execution, possibly creating
297   // microtask checkpoints in places that they would not normally occur.
298   uint32_t mDebuggerRecursionDepth;
299 
300   uint32_t mMicroTaskRecursionDepth;
301 
302   // This implements about-to-be-notified rejected promises list in the spec.
303   // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
304   typedef nsTArray<RefPtr<dom::Promise>> PromiseArray;
305   PromiseArray mAboutToBeNotifiedRejectedPromises;
306 
307   // This is for the "outstanding rejected promises weak set" in the spec,
308   // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set
309   // We use different data structure and opposite logic here to achieve the same
310   // effect. Basically this is used for tracking the rejected promise that does
311   // NOT need firing a rejectionhandled event. We will check the table to see if
312   // firing rejectionhandled event is required when a rejected promise is being
313   // handled.
314   //
315   // The rejected promise will be stored in the table if
316   // - it is unhandled, and
317   // - The unhandledrejection is not yet fired.
318   //
319   // And be removed when
320   // - it is handled, or
321   // - A unhandledrejection is fired and it isn't being handled in event
322   // handler.
323   typedef nsRefPtrHashtable<nsUint64HashKey, dom::Promise> PromiseHashtable;
324   PromiseHashtable mPendingUnhandledRejections;
325 
326   class NotifyUnhandledRejections final : public CancelableRunnable {
327    public:
NotifyUnhandledRejections(CycleCollectedJSContext * aCx,PromiseArray && aPromises)328     NotifyUnhandledRejections(CycleCollectedJSContext* aCx,
329                               PromiseArray&& aPromises)
330         : CancelableRunnable("NotifyUnhandledRejections"),
331           mCx(aCx),
332           mUnhandledRejections(std::move(aPromises)) {}
333 
334     NS_IMETHOD Run() final;
335 
336     nsresult Cancel() final;
337 
338    private:
339     CycleCollectedJSContext* mCx;
340     PromiseArray mUnhandledRejections;
341   };
342 
343   // Support for JS FinalizationRegistry objects.
344   //
345   // These allow a JS callback to be registered that is called when an object
346   // dies. The browser part of the implementation keeps a vector of
347   // FinalizationRegistries with pending callbacks here.
348   friend class CleanupFinalizationRegistriesRunnable;
349   using ObjectVector = JS::GCVector<JSObject*, 0, InfallibleAllocPolicy>;
350   JS::PersistentRooted<ObjectVector> mFinalizationRegistriesToCleanUp;
351 };
352 
353 class MOZ_STACK_CLASS nsAutoMicroTask {
354  public:
nsAutoMicroTask()355   nsAutoMicroTask() {
356     CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
357     if (ccjs) {
358       ccjs->EnterMicroTask();
359     }
360   }
~nsAutoMicroTask()361   MOZ_CAN_RUN_SCRIPT ~nsAutoMicroTask() {
362     CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
363     if (ccjs) {
364       ccjs->LeaveMicroTask();
365     }
366   }
367 };
368 
369 }  // namespace mozilla
370 
371 #endif  // mozilla_CycleCollectedJSContext_h
372