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 #include "mozilla/CycleCollectedJSContext.h"
8 
9 #include <algorithm>
10 #include <utility>
11 
12 #include "js/Debug.h"
13 #include "js/GCAPI.h"
14 #include "js/Utility.h"
15 #include "jsapi.h"
16 #include "mozilla/ArrayUtils.h"
17 #include "mozilla/AsyncEventDispatcher.h"
18 #include "mozilla/AutoRestore.h"
19 #include "mozilla/CycleCollectedJSRuntime.h"
20 #include "mozilla/DebuggerOnGCRunnable.h"
21 #include "mozilla/MemoryReporting.h"
22 #include "mozilla/ProfilerMarkers.h"
23 #include "mozilla/Sprintf.h"
24 #include "mozilla/Telemetry.h"
25 #include "mozilla/TimelineConsumers.h"
26 #include "mozilla/TimelineMarker.h"
27 #include "mozilla/Unused.h"
28 #include "mozilla/dom/DOMException.h"
29 #include "mozilla/dom/DOMJSClass.h"
30 #include "mozilla/dom/FinalizationRegistryBinding.h"
31 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
32 #include "mozilla/dom/PromiseBinding.h"
33 #include "mozilla/dom/PromiseDebugging.h"
34 #include "mozilla/dom/PromiseRejectionEvent.h"
35 #include "mozilla/dom/PromiseRejectionEventBinding.h"
36 #include "mozilla/dom/RootedDictionary.h"
37 #include "mozilla/dom/ScriptSettings.h"
38 #include "mozilla/dom/UserActivation.h"
39 #include "nsContentUtils.h"
40 #include "nsCycleCollectionNoteRootCallback.h"
41 #include "nsCycleCollectionParticipant.h"
42 #include "nsCycleCollector.h"
43 #include "nsDOMJSUtils.h"
44 #include "nsDOMMutationObserver.h"
45 #include "nsJSUtils.h"
46 #include "nsPIDOMWindow.h"
47 #include "nsStringBuffer.h"
48 #include "nsThread.h"
49 #include "nsThreadUtils.h"
50 #include "nsWrapperCache.h"
51 #include "xpcpublic.h"
52 
53 using namespace mozilla;
54 using namespace mozilla::dom;
55 
56 namespace mozilla {
57 
CycleCollectedJSContext()58 CycleCollectedJSContext::CycleCollectedJSContext()
59     : mRuntime(nullptr),
60       mJSContext(nullptr),
61       mDoingStableStates(false),
62       mTargetedMicroTaskRecursionDepth(0),
63       mMicroTaskLevel(0),
64       mSuppressionGeneration(0),
65       mDebuggerRecursionDepth(0),
66       mMicroTaskRecursionDepth(0),
67       mFinalizationRegistryCleanup(this) {
68   MOZ_COUNT_CTOR(CycleCollectedJSContext);
69 
70   nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
71   mOwningThread = thread.forget().downcast<nsThread>().take();
72   MOZ_RELEASE_ASSERT(mOwningThread);
73 }
74 
~CycleCollectedJSContext()75 CycleCollectedJSContext::~CycleCollectedJSContext() {
76   MOZ_COUNT_DTOR(CycleCollectedJSContext);
77   // If the allocation failed, here we are.
78   if (!mJSContext) {
79     return;
80   }
81 
82   JS::SetHostCleanupFinalizationRegistryCallback(mJSContext, nullptr, nullptr);
83 
84   JS_SetContextPrivate(mJSContext, nullptr);
85 
86   mRuntime->SetContext(nullptr);
87   mRuntime->Shutdown(mJSContext);
88 
89   // Last chance to process any events.
90   CleanupIDBTransactions(mBaseRecursionDepth);
91   MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
92 
93   ProcessStableStateQueue();
94   MOZ_ASSERT(mStableStateEvents.IsEmpty());
95 
96   // Clear mPendingException first, since it might be cycle collected.
97   mPendingException = nullptr;
98 
99   MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
100   MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
101 
102   mUncaughtRejections.reset();
103   mConsumedRejections.reset();
104 
105   mAboutToBeNotifiedRejectedPromises.Clear();
106   mPendingUnhandledRejections.Clear();
107 
108   mFinalizationRegistryCleanup.Destroy();
109 
110   JS_DestroyContext(mJSContext);
111   mJSContext = nullptr;
112 
113   nsCycleCollector_forgetJSContext();
114 
115   mozilla::dom::DestroyScriptSettings();
116 
117   mOwningThread->SetScriptObserver(nullptr);
118   NS_RELEASE(mOwningThread);
119 
120   delete mRuntime;
121   mRuntime = nullptr;
122 }
123 
Initialize(JSRuntime * aParentRuntime,uint32_t aMaxBytes)124 nsresult CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime,
125                                              uint32_t aMaxBytes) {
126   MOZ_ASSERT(!mJSContext);
127 
128   mozilla::dom::InitScriptSettings();
129   mJSContext = JS_NewContext(aMaxBytes, aParentRuntime);
130   if (!mJSContext) {
131     return NS_ERROR_OUT_OF_MEMORY;
132   }
133 
134   mRuntime = CreateRuntime(mJSContext);
135   mRuntime->SetContext(this);
136 
137   mOwningThread->SetScriptObserver(this);
138   // The main thread has a base recursion depth of 0, workers of 1.
139   mBaseRecursionDepth = RecursionDepth();
140 
141   NS_GetCurrentThread()->SetCanInvokeJS(true);
142 
143   JS::SetJobQueue(mJSContext, this);
144   JS::SetPromiseRejectionTrackerCallback(mJSContext,
145                                          PromiseRejectionTrackerCallback, this);
146   mUncaughtRejections.init(mJSContext,
147                            JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(
148                                js::SystemAllocPolicy()));
149   mConsumedRejections.init(mJSContext,
150                            JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(
151                                js::SystemAllocPolicy()));
152 
153   mFinalizationRegistryCleanup.Init();
154 
155   // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*).
156   JS_SetContextPrivate(mJSContext, static_cast<PerThreadAtomCache*>(this));
157 
158   nsCycleCollector_registerJSContext(this);
159 
160   return NS_OK;
161 }
162 
163 /* static */
GetFor(JSContext * aCx)164 CycleCollectedJSContext* CycleCollectedJSContext::GetFor(JSContext* aCx) {
165   // Cast from void* matching JS_SetContextPrivate.
166   auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx));
167   // Down cast.
168   return static_cast<CycleCollectedJSContext*>(atomCache);
169 }
170 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const171 size_t CycleCollectedJSContext::SizeOfExcludingThis(
172     MallocSizeOf aMallocSizeOf) const {
173   return 0;
174 }
175 
176 class PromiseJobRunnable final : public MicroTaskRunnable {
177  public:
PromiseJobRunnable(JS::HandleObject aPromise,JS::HandleObject aCallback,JS::HandleObject aCallbackGlobal,JS::HandleObject aAllocationSite,nsIGlobalObject * aIncumbentGlobal)178   PromiseJobRunnable(JS::HandleObject aPromise, JS::HandleObject aCallback,
179                      JS::HandleObject aCallbackGlobal,
180                      JS::HandleObject aAllocationSite,
181                      nsIGlobalObject* aIncumbentGlobal)
182       : mCallback(new PromiseJobCallback(aCallback, aCallbackGlobal,
183                                          aAllocationSite, aIncumbentGlobal)),
184         mPropagateUserInputEventHandling(false) {
185     MOZ_ASSERT(js::IsFunctionObject(aCallback));
186 
187     if (aPromise) {
188       JS::PromiseUserInputEventHandlingState state =
189           JS::GetPromiseUserInputEventHandlingState(aPromise);
190       mPropagateUserInputEventHandling =
191           state ==
192           JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
193     }
194   }
195 
196   virtual ~PromiseJobRunnable() = default;
197 
198  protected:
199   MOZ_CAN_RUN_SCRIPT
Run(AutoSlowOperation & aAso)200   virtual void Run(AutoSlowOperation& aAso) override {
201     JSObject* callback = mCallback->CallbackPreserveColor();
202     nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
203     if (global && !global->IsDying()) {
204       // Propagate the user input event handling bit if needed.
205       nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
206       RefPtr<Document> doc;
207       if (win) {
208         doc = win->GetExtantDoc();
209       }
210       AutoHandlingUserInputStatePusher userInpStatePusher(
211           mPropagateUserInputEventHandling);
212 
213       mCallback->Call("promise callback");
214       aAso.CheckForInterrupt();
215     }
216     // Now that mCallback is no longer needed, clear any pointers it contains to
217     // JS GC things. This removes any storebuffer entries associated with those
218     // pointers, which can cause problems by taking up memory and by triggering
219     // minor GCs. This otherwise would not happen until the next minor GC or
220     // cycle collection.
221     mCallback->Reset();
222   }
223 
Suppressed()224   virtual bool Suppressed() override {
225     JSObject* callback = mCallback->CallbackPreserveColor();
226     nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
227     return global && global->IsInSyncOperation();
228   }
229 
230  private:
231   const RefPtr<PromiseJobCallback> mCallback;
232   bool mPropagateUserInputEventHandling;
233 };
234 
getIncumbentGlobal(JSContext * aCx)235 JSObject* CycleCollectedJSContext::getIncumbentGlobal(JSContext* aCx) {
236   nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
237   if (global) {
238     return global->GetGlobalJSObject();
239   }
240   return nullptr;
241 }
242 
enqueuePromiseJob(JSContext * aCx,JS::HandleObject aPromise,JS::HandleObject aJob,JS::HandleObject aAllocationSite,JS::HandleObject aIncumbentGlobal)243 bool CycleCollectedJSContext::enqueuePromiseJob(
244     JSContext* aCx, JS::HandleObject aPromise, JS::HandleObject aJob,
245     JS::HandleObject aAllocationSite, JS::HandleObject aIncumbentGlobal) {
246   MOZ_ASSERT(aCx == Context());
247   MOZ_ASSERT(Get() == this);
248 
249   nsIGlobalObject* global = nullptr;
250   if (aIncumbentGlobal) {
251     global = xpc::NativeGlobal(aIncumbentGlobal);
252   }
253   JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
254   RefPtr<PromiseJobRunnable> runnable = new PromiseJobRunnable(
255       aPromise, aJob, jobGlobal, aAllocationSite, global);
256   DispatchToMicroTask(runnable.forget());
257   return true;
258 }
259 
260 // Used only by the SpiderMonkey Debugger API, and even then only via
261 // JS::AutoDebuggerJobQueueInterruption, to ensure that the debuggee's queue is
262 // not affected; see comments in js/public/Promise.h.
runJobs(JSContext * aCx)263 void CycleCollectedJSContext::runJobs(JSContext* aCx) {
264   MOZ_ASSERT(aCx == Context());
265   MOZ_ASSERT(Get() == this);
266   PerformMicroTaskCheckPoint();
267 }
268 
empty() const269 bool CycleCollectedJSContext::empty() const {
270   // This is our override of JS::JobQueue::empty. Since that interface is only
271   // concerned with the ordinary microtask queue, not the debugger microtask
272   // queue, we only report on the former.
273   return mPendingMicroTaskRunnables.empty();
274 }
275 
276 // Preserve a debuggee's microtask queue while it is interrupted by the
277 // debugger. See the comments for JS::AutoDebuggerJobQueueInterruption.
278 class CycleCollectedJSContext::SavedMicroTaskQueue
279     : public JS::JobQueue::SavedJobQueue {
280  public:
SavedMicroTaskQueue(CycleCollectedJSContext * ccjs)281   explicit SavedMicroTaskQueue(CycleCollectedJSContext* ccjs) : ccjs(ccjs) {
282     ccjs->mDebuggerRecursionDepth++;
283     ccjs->mPendingMicroTaskRunnables.swap(mQueue);
284   }
285 
~SavedMicroTaskQueue()286   ~SavedMicroTaskQueue() {
287     MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.empty());
288     MOZ_RELEASE_ASSERT(ccjs->mDebuggerRecursionDepth);
289     ccjs->mDebuggerRecursionDepth--;
290     ccjs->mPendingMicroTaskRunnables.swap(mQueue);
291   }
292 
293  private:
294   CycleCollectedJSContext* ccjs;
295   std::deque<RefPtr<MicroTaskRunnable>> mQueue;
296 };
297 
298 js::UniquePtr<JS::JobQueue::SavedJobQueue>
saveJobQueue(JSContext * cx)299 CycleCollectedJSContext::saveJobQueue(JSContext* cx) {
300   auto saved = js::MakeUnique<SavedMicroTaskQueue>(this);
301   if (!saved) {
302     // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor
303     // is never called, so mPendingMicroTaskRunnables is still initialized.
304     JS_ReportOutOfMemory(cx);
305     return nullptr;
306   }
307 
308   return saved;
309 }
310 
311 /* static */
PromiseRejectionTrackerCallback(JSContext * aCx,bool aMutedErrors,JS::HandleObject aPromise,JS::PromiseRejectionHandlingState state,void * aData)312 void CycleCollectedJSContext::PromiseRejectionTrackerCallback(
313     JSContext* aCx, bool aMutedErrors, JS::HandleObject aPromise,
314     JS::PromiseRejectionHandlingState state, void* aData) {
315   CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
316 
317   MOZ_ASSERT(aCx == self->Context());
318   MOZ_ASSERT(Get() == self);
319 
320   // TODO: Bug 1549351 - Promise rejection event should not be sent for
321   // cross-origin scripts
322 
323   PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises;
324   PromiseHashtable& unhandled = self->mPendingUnhandledRejections;
325   uint64_t promiseID = JS::GetPromiseID(aPromise);
326 
327   if (state == JS::PromiseRejectionHandlingState::Unhandled) {
328     PromiseDebugging::AddUncaughtRejection(aPromise);
329     if (!aMutedErrors) {
330       RefPtr<Promise> promise =
331           Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
332       aboutToBeNotified.AppendElement(promise);
333       unhandled.InsertOrUpdate(promiseID, std::move(promise));
334     }
335   } else {
336     PromiseDebugging::AddConsumedRejection(aPromise);
337     for (size_t i = 0; i < aboutToBeNotified.Length(); i++) {
338       if (aboutToBeNotified[i] &&
339           aboutToBeNotified[i]->PromiseObj() == aPromise) {
340         // To avoid large amounts of memmoves, we don't shrink the vector
341         // here. Instead, we filter out nullptrs when iterating over the
342         // vector later.
343         aboutToBeNotified[i] = nullptr;
344         DebugOnly<bool> isFound = unhandled.Remove(promiseID);
345         MOZ_ASSERT(isFound);
346         return;
347       }
348     }
349     RefPtr<Promise> promise;
350     unhandled.Remove(promiseID, getter_AddRefs(promise));
351     if (!promise && !aMutedErrors) {
352       nsIGlobalObject* global = xpc::NativeGlobal(aPromise);
353       if (nsCOMPtr<EventTarget> owner = do_QueryInterface(global)) {
354         RootedDictionary<PromiseRejectionEventInit> init(aCx);
355         init.mPromise = Promise::CreateFromExisting(global, aPromise);
356         init.mReason = JS::GetPromiseResult(aPromise);
357 
358         RefPtr<PromiseRejectionEvent> event =
359             PromiseRejectionEvent::Constructor(owner, u"rejectionhandled"_ns,
360                                                init);
361 
362         RefPtr<AsyncEventDispatcher> asyncDispatcher =
363             new AsyncEventDispatcher(owner, event);
364         asyncDispatcher->PostDOMEvent();
365       }
366     }
367   }
368 }
369 
GetPendingException() const370 already_AddRefed<Exception> CycleCollectedJSContext::GetPendingException()
371     const {
372   MOZ_ASSERT(mJSContext);
373 
374   nsCOMPtr<Exception> out = mPendingException;
375   return out.forget();
376 }
377 
SetPendingException(Exception * aException)378 void CycleCollectedJSContext::SetPendingException(Exception* aException) {
379   MOZ_ASSERT(mJSContext);
380   mPendingException = aException;
381 }
382 
383 std::deque<RefPtr<MicroTaskRunnable>>&
GetMicroTaskQueue()384 CycleCollectedJSContext::GetMicroTaskQueue() {
385   MOZ_ASSERT(mJSContext);
386   return mPendingMicroTaskRunnables;
387 }
388 
389 std::deque<RefPtr<MicroTaskRunnable>>&
GetDebuggerMicroTaskQueue()390 CycleCollectedJSContext::GetDebuggerMicroTaskQueue() {
391   MOZ_ASSERT(mJSContext);
392   return mDebuggerMicroTaskQueue;
393 }
394 
ProcessStableStateQueue()395 void CycleCollectedJSContext::ProcessStableStateQueue() {
396   MOZ_ASSERT(mJSContext);
397   MOZ_RELEASE_ASSERT(!mDoingStableStates);
398   mDoingStableStates = true;
399 
400   // When run, one event can add another event to the mStableStateEvents, as
401   // such you can't use iterators here.
402   for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
403     nsCOMPtr<nsIRunnable> event = std::move(mStableStateEvents[i]);
404     event->Run();
405   }
406 
407   mStableStateEvents.Clear();
408   mDoingStableStates = false;
409 }
410 
CleanupIDBTransactions(uint32_t aRecursionDepth)411 void CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth) {
412   MOZ_ASSERT(mJSContext);
413   MOZ_RELEASE_ASSERT(!mDoingStableStates);
414   mDoingStableStates = true;
415 
416   nsTArray<PendingIDBTransactionData> localQueue =
417       std::move(mPendingIDBTransactions);
418 
419   localQueue.RemoveLastElements(
420       localQueue.end() -
421       std::remove_if(localQueue.begin(), localQueue.end(),
422                      [aRecursionDepth](PendingIDBTransactionData& data) {
423                        if (data.mRecursionDepth != aRecursionDepth) {
424                          return false;
425                        }
426 
427                        {
428                          nsCOMPtr<nsIRunnable> transaction =
429                              std::move(data.mTransaction);
430                          transaction->Run();
431                        }
432 
433                        return true;
434                      }));
435 
436   // If mPendingIDBTransactions has events in it now, they were added from
437   // something we called, so they belong at the end of the queue.
438   localQueue.AppendElements(std::move(mPendingIDBTransactions));
439   mPendingIDBTransactions = std::move(localQueue);
440   mDoingStableStates = false;
441 }
442 
BeforeProcessTask(bool aMightBlock)443 void CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock) {
444   // If ProcessNextEvent was called during a microtask callback, we
445   // must process any pending microtasks before blocking in the event loop,
446   // otherwise we may deadlock until an event enters the queue later.
447   if (aMightBlock && PerformMicroTaskCheckPoint()) {
448     // If any microtask was processed, we post a dummy event in order to
449     // force the ProcessNextEvent call not to block.  This is required
450     // to support nested event loops implemented using a pattern like
451     // "while (condition) thread.processNextEvent(true)", in case the
452     // condition is triggered here by a Promise "then" callback.
453     NS_DispatchToMainThread(new Runnable("BeforeProcessTask"));
454   }
455 }
456 
AfterProcessTask(uint32_t aRecursionDepth)457 void CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) {
458   MOZ_ASSERT(mJSContext);
459 
460   // See HTML 6.1.4.2 Processing model
461 
462   // Step 4.1: Execute microtasks.
463   PerformMicroTaskCheckPoint();
464 
465   // Step 4.2 Execute any events that were waiting for a stable state.
466   ProcessStableStateQueue();
467 
468   // This should be a fast test so that it won't affect the next task
469   // processing.
470   IsIdleGCTaskNeeded();
471 }
472 
AfterProcessMicrotasks()473 void CycleCollectedJSContext::AfterProcessMicrotasks() {
474   MOZ_ASSERT(mJSContext);
475   // Notify unhandled promise rejections:
476   // https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
477   if (mAboutToBeNotifiedRejectedPromises.Length()) {
478     RefPtr<NotifyUnhandledRejections> runnable = new NotifyUnhandledRejections(
479         this, std::move(mAboutToBeNotifiedRejectedPromises));
480     NS_DispatchToCurrentThread(runnable);
481   }
482   // Cleanup Indexed Database transactions:
483   // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
484   CleanupIDBTransactions(RecursionDepth());
485 
486   // Clear kept alive objects in JS WeakRef.
487   // https://whatpr.org/html/4571/webappapis.html#perform-a-microtask-checkpoint
488   //
489   // ECMAScript implementations are expected to call ClearKeptObjects when a
490   // synchronous sequence of ECMAScript execution completes.
491   //
492   // https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
493   JS::ClearKeptObjects(mJSContext);
494 }
495 
IsIdleGCTaskNeeded() const496 void CycleCollectedJSContext::IsIdleGCTaskNeeded() const {
497   class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable {
498    public:
499     using mozilla::IdleRunnable::IdleRunnable;
500 
501    public:
502     NS_IMETHOD Run() override {
503       CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
504       if (ccrt) {
505         ccrt->RunIdleTimeGCTask();
506       }
507       return NS_OK;
508     }
509   };
510 
511   if (Runtime()->IsIdleGCTaskNeeded()) {
512     nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
513     NS_DispatchToCurrentThreadQueue(gc_task.forget(), EventQueuePriority::Idle);
514     Runtime()->SetPendingIdleGCTask();
515   }
516 }
517 
RecursionDepth() const518 uint32_t CycleCollectedJSContext::RecursionDepth() const {
519   // Debugger interruptions are included in the recursion depth so that debugger
520   // microtask checkpoints do not run IDB transactions which were initiated
521   // before the interruption.
522   return mOwningThread->RecursionDepth() + mDebuggerRecursionDepth;
523 }
524 
RunInStableState(already_AddRefed<nsIRunnable> && aRunnable)525 void CycleCollectedJSContext::RunInStableState(
526     already_AddRefed<nsIRunnable>&& aRunnable) {
527   MOZ_ASSERT(mJSContext);
528   mStableStateEvents.AppendElement(std::move(aRunnable));
529 }
530 
AddPendingIDBTransaction(already_AddRefed<nsIRunnable> && aTransaction)531 void CycleCollectedJSContext::AddPendingIDBTransaction(
532     already_AddRefed<nsIRunnable>&& aTransaction) {
533   MOZ_ASSERT(mJSContext);
534 
535   PendingIDBTransactionData data;
536   data.mTransaction = aTransaction;
537 
538   MOZ_ASSERT(mOwningThread);
539   data.mRecursionDepth = RecursionDepth();
540 
541   // There must be an event running to get here.
542 #ifndef MOZ_WIDGET_COCOA
543   MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
544 #else
545   // XXX bug 1261143
546   // Recursion depth should be greater than mBaseRecursionDepth,
547   // or the runnable will stay in the queue forever.
548   if (data.mRecursionDepth <= mBaseRecursionDepth) {
549     data.mRecursionDepth = mBaseRecursionDepth + 1;
550   }
551 #endif
552 
553   mPendingIDBTransactions.AppendElement(std::move(data));
554 }
555 
DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable)556 void CycleCollectedJSContext::DispatchToMicroTask(
557     already_AddRefed<MicroTaskRunnable> aRunnable) {
558   RefPtr<MicroTaskRunnable> runnable(aRunnable);
559 
560   MOZ_ASSERT(NS_IsMainThread());
561   MOZ_ASSERT(runnable);
562 
563   JS::JobQueueMayNotBeEmpty(Context());
564 
565   LogMicroTaskRunnable::LogDispatch(runnable.get());
566   mPendingMicroTaskRunnables.push_back(std::move(runnable));
567 }
568 
569 class AsyncMutationHandler final : public mozilla::Runnable {
570  public:
AsyncMutationHandler()571   AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
572 
573   // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.  See
574   // bug 1535398.
575   MOZ_CAN_RUN_SCRIPT_BOUNDARY
Run()576   NS_IMETHOD Run() override {
577     CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
578     if (ccjs) {
579       ccjs->PerformMicroTaskCheckPoint();
580     }
581     return NS_OK;
582   }
583 };
584 
SuppressedMicroTasks(CycleCollectedJSContext * aContext)585 SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext* aContext)
586     : mContext(aContext),
587       mSuppressionGeneration(aContext->mSuppressionGeneration) {}
588 
Suppressed()589 bool SuppressedMicroTasks::Suppressed() {
590   if (mSuppressionGeneration == mContext->mSuppressionGeneration) {
591     return true;
592   }
593 
594   for (std::deque<RefPtr<MicroTaskRunnable>>::reverse_iterator it =
595            mSuppressedMicroTaskRunnables.rbegin();
596        it != mSuppressedMicroTaskRunnables.rend(); ++it) {
597     mContext->GetMicroTaskQueue().push_front(*it);
598   }
599   mContext->mSuppressedMicroTasks = nullptr;
600 
601   return false;
602 }
603 
PerformMicroTaskCheckPoint(bool aForce)604 bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) {
605   if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
606     AfterProcessMicrotasks();
607     // Nothing to do, return early.
608     return false;
609   }
610 
611   uint32_t currentDepth = RecursionDepth();
612   if (mMicroTaskRecursionDepth >= currentDepth && !aForce) {
613     // We are already executing microtasks for the current recursion depth.
614     return false;
615   }
616 
617   if (mTargetedMicroTaskRecursionDepth != 0 &&
618       mTargetedMicroTaskRecursionDepth + mDebuggerRecursionDepth !=
619           currentDepth) {
620     return false;
621   }
622 
623   if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
624     // Special case for main thread where DOM mutations may happen when
625     // it is not safe to run scripts.
626     nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
627     return false;
628   }
629 
630   mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
631   MOZ_ASSERT(aForce ? currentDepth == 0 : currentDepth > 0);
632   mMicroTaskRecursionDepth = currentDepth;
633 
634   AUTO_PROFILER_TRACING_MARKER("JS", "Perform microtasks", JS);
635 
636   bool didProcess = false;
637   AutoSlowOperation aso;
638 
639   for (;;) {
640     RefPtr<MicroTaskRunnable> runnable;
641     if (!mDebuggerMicroTaskQueue.empty()) {
642       runnable = std::move(mDebuggerMicroTaskQueue.front());
643       mDebuggerMicroTaskQueue.pop_front();
644     } else if (!mPendingMicroTaskRunnables.empty()) {
645       runnable = std::move(mPendingMicroTaskRunnables.front());
646       mPendingMicroTaskRunnables.pop_front();
647     } else {
648       break;
649     }
650 
651     if (runnable->Suppressed()) {
652       // Microtasks in worker shall never be suppressed.
653       // Otherwise, mPendingMicroTaskRunnables will be replaced later with
654       // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
655       MOZ_ASSERT(NS_IsMainThread());
656       JS::JobQueueMayNotBeEmpty(Context());
657       if (runnable != mSuppressedMicroTasks) {
658         if (!mSuppressedMicroTasks) {
659           mSuppressedMicroTasks = new SuppressedMicroTasks(this);
660         }
661         mSuppressedMicroTasks->mSuppressedMicroTaskRunnables.push_back(
662             runnable);
663       }
664     } else {
665       if (mPendingMicroTaskRunnables.empty() &&
666           mDebuggerMicroTaskQueue.empty() && !mSuppressedMicroTasks) {
667         JS::JobQueueIsEmpty(Context());
668       }
669       didProcess = true;
670 
671       LogMicroTaskRunnable::Run log(runnable.get());
672       runnable->Run(aso);
673       runnable = nullptr;
674     }
675   }
676 
677   // Put back the suppressed microtasks so that they will be run later.
678   // Note, it is possible that we end up keeping these suppressed tasks around
679   // for some time, but no longer than spinning the event loop nestedly
680   // (sync XHR, alert, etc.)
681   if (mSuppressedMicroTasks) {
682     mPendingMicroTaskRunnables.push_back(mSuppressedMicroTasks);
683   }
684 
685   AfterProcessMicrotasks();
686 
687   return didProcess;
688 }
689 
PerformDebuggerMicroTaskCheckpoint()690 void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() {
691   // Don't do normal microtask handling checks here, since whoever is calling
692   // this method is supposed to know what they are doing.
693 
694   AutoSlowOperation aso;
695   for (;;) {
696     // For a debugger microtask checkpoint, we always use the debugger microtask
697     // queue.
698     std::deque<RefPtr<MicroTaskRunnable>>* microtaskQueue =
699         &GetDebuggerMicroTaskQueue();
700 
701     if (microtaskQueue->empty()) {
702       break;
703     }
704 
705     RefPtr<MicroTaskRunnable> runnable = std::move(microtaskQueue->front());
706     MOZ_ASSERT(runnable);
707 
708     LogMicroTaskRunnable::Run log(runnable.get());
709 
710     // This function can re-enter, so we remove the element before calling.
711     microtaskQueue->pop_front();
712 
713     if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
714       JS::JobQueueIsEmpty(Context());
715     }
716     runnable->Run(aso);
717     runnable = nullptr;
718   }
719 
720   AfterProcessMicrotasks();
721 }
722 
Run()723 NS_IMETHODIMP CycleCollectedJSContext::NotifyUnhandledRejections::Run() {
724   for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
725     RefPtr<Promise>& promise = mUnhandledRejections[i];
726     if (!promise) {
727       continue;
728     }
729 
730     JS::RootingContext* cx = mCx->RootingCx();
731     JS::RootedObject promiseObj(cx, promise->PromiseObj());
732     MOZ_ASSERT(JS::IsPromiseObject(promiseObj));
733 
734     // Only fire unhandledrejection if the promise is still not handled;
735     uint64_t promiseID = JS::GetPromiseID(promiseObj);
736     if (!JS::GetPromiseIsHandled(promiseObj)) {
737       if (nsCOMPtr<EventTarget> target =
738               do_QueryInterface(promise->GetParentObject())) {
739         RootedDictionary<PromiseRejectionEventInit> init(cx);
740         init.mPromise = promise;
741         init.mReason = JS::GetPromiseResult(promiseObj);
742         init.mCancelable = true;
743 
744         RefPtr<PromiseRejectionEvent> event =
745             PromiseRejectionEvent::Constructor(target, u"unhandledrejection"_ns,
746                                                init);
747         // We don't use the result of dispatching event here to check whether to
748         // report the Promise to console.
749         target->DispatchEvent(*event);
750       }
751     }
752 
753     if (!JS::GetPromiseIsHandled(promiseObj)) {
754       DebugOnly<bool> isFound =
755           mCx->mPendingUnhandledRejections.Remove(promiseID);
756       MOZ_ASSERT(isFound);
757     }
758 
759     // If a rejected promise is being handled in "unhandledrejection" event
760     // handler, it should be removed from the table in
761     // PromiseRejectionTrackerCallback.
762     MOZ_ASSERT(!mCx->mPendingUnhandledRejections.Lookup(promiseID));
763   }
764   return NS_OK;
765 }
766 
Cancel()767 nsresult CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() {
768   for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
769     RefPtr<Promise>& promise = mUnhandledRejections[i];
770     if (!promise) {
771       continue;
772     }
773 
774     JS::RootedObject promiseObj(mCx->RootingCx(), promise->PromiseObj());
775     mCx->mPendingUnhandledRejections.Remove(JS::GetPromiseID(promiseObj));
776   }
777   return NS_OK;
778 }
779 
780 class FinalizationRegistryCleanup::CleanupRunnable
781     : public DiscardableRunnable {
782  public:
CleanupRunnable(FinalizationRegistryCleanup * aCleanupWork)783   explicit CleanupRunnable(FinalizationRegistryCleanup* aCleanupWork)
784       : DiscardableRunnable("CleanupRunnable"), mCleanupWork(aCleanupWork) {}
785 
786   // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.  See
787   // bug 1535398.
788   MOZ_CAN_RUN_SCRIPT_BOUNDARY
Run()789   NS_IMETHOD Run() override {
790     mCleanupWork->DoCleanup();
791     return NS_OK;
792   }
793 
794  private:
795   FinalizationRegistryCleanup* mCleanupWork;
796 };
797 
FinalizationRegistryCleanup(CycleCollectedJSContext * aContext)798 FinalizationRegistryCleanup::FinalizationRegistryCleanup(
799     CycleCollectedJSContext* aContext)
800     : mContext(aContext) {}
801 
Destroy()802 void FinalizationRegistryCleanup::Destroy() {
803   // This must happen before the CycleCollectedJSContext destructor calls
804   // JS_DestroyContext().
805   mCallbacks.reset();
806 }
807 
Init()808 void FinalizationRegistryCleanup::Init() {
809   JSContext* cx = mContext->Context();
810   mCallbacks.init(cx);
811   JS::SetHostCleanupFinalizationRegistryCallback(cx, QueueCallback, this);
812 }
813 
814 /* static */
QueueCallback(JSFunction * aDoCleanup,JSObject * aIncumbentGlobal,void * aData)815 void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
816                                                 JSObject* aIncumbentGlobal,
817                                                 void* aData) {
818   FinalizationRegistryCleanup* cleanup =
819       static_cast<FinalizationRegistryCleanup*>(aData);
820   cleanup->QueueCallback(aDoCleanup, aIncumbentGlobal);
821 }
822 
QueueCallback(JSFunction * aDoCleanup,JSObject * aIncumbentGlobal)823 void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
824                                                 JSObject* aIncumbentGlobal) {
825   bool firstCallback = mCallbacks.empty();
826 
827   MOZ_ALWAYS_TRUE(mCallbacks.append(Callback{aDoCleanup, aIncumbentGlobal}));
828 
829   if (firstCallback) {
830     RefPtr<CleanupRunnable> cleanup = new CleanupRunnable(this);
831     NS_DispatchToCurrentThread(cleanup.forget());
832   }
833 }
834 
DoCleanup()835 void FinalizationRegistryCleanup::DoCleanup() {
836   if (mCallbacks.empty()) {
837     return;
838   }
839 
840   JS::RootingContext* cx = mContext->RootingCx();
841 
842   JS::Rooted<CallbackVector> callbacks(cx);
843   std::swap(callbacks.get(), mCallbacks.get());
844 
845   for (const Callback& callback : callbacks) {
846     JS::RootedObject functionObj(
847         cx, JS_GetFunctionObject(callback.mCallbackFunction));
848     JS::RootedObject globalObj(cx, JS::GetNonCCWObjectGlobal(functionObj));
849 
850     nsIGlobalObject* incumbentGlobal =
851         xpc::NativeGlobal(callback.mIncumbentGlobal);
852     if (!incumbentGlobal) {
853       continue;
854     }
855 
856     RefPtr<FinalizationRegistryCleanupCallback> cleanupCallback(
857         new FinalizationRegistryCleanupCallback(functionObj, globalObj, nullptr,
858                                                 incumbentGlobal));
859 
860     nsIGlobalObject* global =
861         xpc::NativeGlobal(cleanupCallback->CallbackPreserveColor());
862     if (global) {
863       cleanupCallback->Call("FinalizationRegistryCleanup::DoCleanup");
864     }
865   }
866 }
867 
trace(JSTracer * trc)868 void FinalizationRegistryCleanup::Callback::trace(JSTracer* trc) {
869   JS::UnsafeTraceRoot(trc, &mCallbackFunction, "mCallbackFunction");
870   JS::UnsafeTraceRoot(trc, &mIncumbentGlobal, "mIncumbentGlobal");
871 }
872 
873 }  // namespace mozilla
874