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