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 #include "mozilla/CycleCollectedJSContext.h"
14 #include "js/Utility.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"
49 #include "nsThreadUtils.h"
50 #include "nsWrapperCache.h"
51 #include "xpcpublic.h"
53 using namespace mozilla
;
54 using namespace mozilla::dom
;
58 CycleCollectedJSContext::CycleCollectedJSContext()
61 mDoingStableStates(false),
62 mTargetedMicroTaskRecursionDepth(0),
64 mSuppressionGeneration(0),
65 mDebuggerRecursionDepth(0),
66 mMicroTaskRecursionDepth(0),
67 mFinalizationRegistryCleanup(this) {
68 MOZ_COUNT_CTOR(CycleCollectedJSContext
);
70 nsCOMPtr
<nsIThread
> thread
= do_GetCurrentThread();
71 mOwningThread
= thread
.forget().downcast
<nsThread
>().take();
72 MOZ_RELEASE_ASSERT(mOwningThread
);
75 CycleCollectedJSContext::~CycleCollectedJSContext() {
76 MOZ_COUNT_DTOR(CycleCollectedJSContext
);
77 // If the allocation failed, here we are.
82 JS::SetHostCleanupFinalizationRegistryCallback(mJSContext
, nullptr, nullptr);
84 JS_SetContextPrivate(mJSContext
, nullptr);
86 mRuntime
->SetContext(nullptr);
87 mRuntime
->Shutdown(mJSContext
);
89 // Last chance to process any events.
90 CleanupIDBTransactions(mBaseRecursionDepth
);
91 MOZ_ASSERT(mPendingIDBTransactions
.IsEmpty());
93 ProcessStableStateQueue();
94 MOZ_ASSERT(mStableStateEvents
.IsEmpty());
96 // Clear mPendingException first, since it might be cycle collected.
97 mPendingException
= nullptr;
99 MOZ_ASSERT(mDebuggerMicroTaskQueue
.empty());
100 MOZ_ASSERT(mPendingMicroTaskRunnables
.empty());
102 mUncaughtRejections
.reset();
103 mConsumedRejections
.reset();
105 mAboutToBeNotifiedRejectedPromises
.Clear();
106 mPendingUnhandledRejections
.Clear();
108 mFinalizationRegistryCleanup
.Destroy();
110 JS_DestroyContext(mJSContext
);
111 mJSContext
= nullptr;
113 nsCycleCollector_forgetJSContext();
115 mozilla::dom::DestroyScriptSettings();
117 mOwningThread
->SetScriptObserver(nullptr);
118 NS_RELEASE(mOwningThread
);
124 nsresult
CycleCollectedJSContext::Initialize(JSRuntime
* aParentRuntime
,
125 uint32_t aMaxBytes
) {
126 MOZ_ASSERT(!mJSContext
);
128 mozilla::dom::InitScriptSettings();
129 mJSContext
= JS_NewContext(aMaxBytes
, aParentRuntime
);
131 return NS_ERROR_OUT_OF_MEMORY
;
134 mRuntime
= CreateRuntime(mJSContext
);
135 mRuntime
->SetContext(this);
137 mOwningThread
->SetScriptObserver(this);
138 // The main thread has a base recursion depth of 0, workers of 1.
139 mBaseRecursionDepth
= RecursionDepth();
141 NS_GetCurrentThread()->SetCanInvokeJS(true);
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()));
153 mFinalizationRegistryCleanup
.Init();
155 // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*).
156 JS_SetContextPrivate(mJSContext
, static_cast<PerThreadAtomCache
*>(this));
158 nsCycleCollector_registerJSContext(this);
164 CycleCollectedJSContext
* CycleCollectedJSContext::GetFor(JSContext
* aCx
) {
165 // Cast from void* matching JS_SetContextPrivate.
166 auto atomCache
= static_cast<PerThreadAtomCache
*>(JS_GetContextPrivate(aCx
));
168 return static_cast<CycleCollectedJSContext
*>(atomCache
);
171 size_t CycleCollectedJSContext::SizeOfExcludingThis(
172 MallocSizeOf aMallocSizeOf
) const {
176 class PromiseJobRunnable final
: public MicroTaskRunnable
{
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
));
188 JS::PromiseUserInputEventHandlingState state
=
189 JS::GetPromiseUserInputEventHandlingState(aPromise
);
190 mPropagateUserInputEventHandling
=
192 JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
;
196 virtual ~PromiseJobRunnable() = default;
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
;
208 doc
= win
->GetExtantDoc();
210 AutoHandlingUserInputStatePusher
userInpStatePusher(
211 mPropagateUserInputEventHandling
);
213 mCallback
->Call("promise callback");
214 aAso
.CheckForInterrupt();
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
224 virtual bool Suppressed() override
{
225 JSObject
* callback
= mCallback
->CallbackPreserveColor();
226 nsIGlobalObject
* global
= callback
? xpc::NativeGlobal(callback
) : nullptr;
227 return global
&& global
->IsInSyncOperation();
231 const RefPtr
<PromiseJobCallback
> mCallback
;
232 bool mPropagateUserInputEventHandling
;
235 JSObject
* CycleCollectedJSContext::getIncumbentGlobal(JSContext
* aCx
) {
236 nsIGlobalObject
* global
= mozilla::dom::GetIncumbentGlobal();
238 return global
->GetGlobalJSObject();
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);
249 nsIGlobalObject
* global
= nullptr;
250 if (aIncumbentGlobal
) {
251 global
= xpc::NativeGlobal(aIncumbentGlobal
);
253 JS::RootedObject
jobGlobal(aCx
, JS::CurrentGlobalOrNull(aCx
));
254 RefPtr
<PromiseJobRunnable
> runnable
= new PromiseJobRunnable(
255 aPromise
, aJob
, jobGlobal
, aAllocationSite
, global
);
256 DispatchToMicroTask(runnable
.forget());
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.
263 void CycleCollectedJSContext::runJobs(JSContext
* aCx
) {
264 MOZ_ASSERT(aCx
== Context());
265 MOZ_ASSERT(Get() == this);
266 PerformMicroTaskCheckPoint();
269 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();
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
{
281 explicit SavedMicroTaskQueue(CycleCollectedJSContext
* ccjs
) : ccjs(ccjs
) {
282 ccjs
->mDebuggerRecursionDepth
++;
283 ccjs
->mPendingMicroTaskRunnables
.swap(mQueue
);
286 ~SavedMicroTaskQueue() {
287 MOZ_RELEASE_ASSERT(ccjs
->mPendingMicroTaskRunnables
.empty());
288 MOZ_RELEASE_ASSERT(ccjs
->mDebuggerRecursionDepth
);
289 ccjs
->mDebuggerRecursionDepth
--;
290 ccjs
->mPendingMicroTaskRunnables
.swap(mQueue
);
294 CycleCollectedJSContext
* ccjs
;
295 std::deque
<RefPtr
<MicroTaskRunnable
>> mQueue
;
298 js::UniquePtr
<JS::JobQueue::SavedJobQueue
>
299 CycleCollectedJSContext::saveJobQueue(JSContext
* cx
) {
300 auto saved
= js::MakeUnique
<SavedMicroTaskQueue
>(this);
302 // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor
303 // is never called, so mPendingMicroTaskRunnables is still initialized.
304 JS_ReportOutOfMemory(cx
);
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
);
317 MOZ_ASSERT(aCx
== self
->Context());
318 MOZ_ASSERT(Get() == self
);
320 // TODO: Bug 1549351 - Promise rejection event should not be sent for
321 // cross-origin scripts
323 PromiseArray
& aboutToBeNotified
= self
->mAboutToBeNotifiedRejectedPromises
;
324 PromiseHashtable
& unhandled
= self
->mPendingUnhandledRejections
;
325 uint64_t promiseID
= JS::GetPromiseID(aPromise
);
327 if (state
== JS::PromiseRejectionHandlingState::Unhandled
) {
328 PromiseDebugging::AddUncaughtRejection(aPromise
);
330 RefPtr
<Promise
> promise
=
331 Promise::CreateFromExisting(xpc::NativeGlobal(aPromise
), aPromise
);
332 aboutToBeNotified
.AppendElement(promise
);
333 unhandled
.InsertOrUpdate(promiseID
, std::move(promise
));
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
343 aboutToBeNotified
[i
] = nullptr;
344 DebugOnly
<bool> isFound
= unhandled
.Remove(promiseID
);
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
);
358 RefPtr
<PromiseRejectionEvent
> event
=
359 PromiseRejectionEvent::Constructor(owner
, u
"rejectionhandled"_ns
,
362 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
363 new AsyncEventDispatcher(owner
, event
);
364 asyncDispatcher
->PostDOMEvent();
370 already_AddRefed
<Exception
> CycleCollectedJSContext::GetPendingException()
372 MOZ_ASSERT(mJSContext
);
374 nsCOMPtr
<Exception
> out
= mPendingException
;
378 void CycleCollectedJSContext::SetPendingException(Exception
* aException
) {
379 MOZ_ASSERT(mJSContext
);
380 mPendingException
= aException
;
383 std::deque
<RefPtr
<MicroTaskRunnable
>>&
384 CycleCollectedJSContext::GetMicroTaskQueue() {
385 MOZ_ASSERT(mJSContext
);
386 return mPendingMicroTaskRunnables
;
389 std::deque
<RefPtr
<MicroTaskRunnable
>>&
390 CycleCollectedJSContext::GetDebuggerMicroTaskQueue() {
391 MOZ_ASSERT(mJSContext
);
392 return mDebuggerMicroTaskQueue
;
395 void CycleCollectedJSContext::ProcessStableStateQueue() {
396 MOZ_ASSERT(mJSContext
);
397 MOZ_RELEASE_ASSERT(!mDoingStableStates
);
398 mDoingStableStates
= true;
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
]);
407 mStableStateEvents
.Clear();
408 mDoingStableStates
= false;
411 void CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth
) {
412 MOZ_ASSERT(mJSContext
);
413 MOZ_RELEASE_ASSERT(!mDoingStableStates
);
414 mDoingStableStates
= true;
416 nsTArray
<PendingIDBTransactionData
> localQueue
=
417 std::move(mPendingIDBTransactions
);
419 localQueue
.RemoveLastElements(
421 std::remove_if(localQueue
.begin(), localQueue
.end(),
422 [aRecursionDepth
](PendingIDBTransactionData
& data
) {
423 if (data
.mRecursionDepth
!= aRecursionDepth
) {
428 nsCOMPtr
<nsIRunnable
> transaction
=
429 std::move(data
.mTransaction
);
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;
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"));
457 void CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth
) {
458 MOZ_ASSERT(mJSContext
);
460 // See HTML 6.1.4.2 Processing model
462 // Step 4.1: Execute microtasks.
463 PerformMicroTaskCheckPoint();
465 // Step 4.2 Execute any events that were waiting for a stable state.
466 ProcessStableStateQueue();
468 // This should be a fast test so that it won't affect the next task
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 std::move(mAboutToBeNotifiedRejectedPromises
));
480 NS_DispatchToCurrentThread(runnable
);
482 // Cleanup Indexed Database transactions:
483 // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
484 CleanupIDBTransactions(RecursionDepth());
486 // Clear kept alive objects in JS WeakRef.
487 // https://whatpr.org/html/4571/webappapis.html#perform-a-microtask-checkpoint
489 // ECMAScript implementations are expected to call ClearKeptObjects when a
490 // synchronous sequence of ECMAScript execution completes.
492 // https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
493 JS::ClearKeptObjects(mJSContext
);
496 void CycleCollectedJSContext::MaybePokeGC() {
497 // Worker-compatible check to see if we want to do an idle-time minor
499 class IdleTimeGCTaskRunnable
: public mozilla::IdleRunnable
{
501 using mozilla::IdleRunnable::IdleRunnable
;
504 IdleTimeGCTaskRunnable() : IdleRunnable("IdleTimeGCTask") {}
506 NS_IMETHOD
Run() override
{
507 CycleCollectedJSRuntime
* ccrt
= CycleCollectedJSRuntime::Get();
509 ccrt
->RunIdleTimeGCTask();
515 if (Runtime()->IsIdleGCTaskNeeded()) {
516 nsCOMPtr
<nsIRunnable
> gc_task
= new IdleTimeGCTaskRunnable();
517 NS_DispatchToCurrentThreadQueue(gc_task
.forget(), EventQueuePriority::Idle
);
518 Runtime()->SetPendingIdleGCTask();
522 uint32_t CycleCollectedJSContext::RecursionDepth() const {
523 // Debugger interruptions are included in the recursion depth so that debugger
524 // microtask checkpoints do not run IDB transactions which were initiated
525 // before the interruption.
526 return mOwningThread
->RecursionDepth() + mDebuggerRecursionDepth
;
529 void CycleCollectedJSContext::RunInStableState(
530 already_AddRefed
<nsIRunnable
>&& aRunnable
) {
531 MOZ_ASSERT(mJSContext
);
532 mStableStateEvents
.AppendElement(std::move(aRunnable
));
535 void CycleCollectedJSContext::AddPendingIDBTransaction(
536 already_AddRefed
<nsIRunnable
>&& aTransaction
) {
537 MOZ_ASSERT(mJSContext
);
539 PendingIDBTransactionData data
;
540 data
.mTransaction
= aTransaction
;
542 MOZ_ASSERT(mOwningThread
);
543 data
.mRecursionDepth
= RecursionDepth();
545 // There must be an event running to get here.
546 #ifndef MOZ_WIDGET_COCOA
547 MOZ_ASSERT(data
.mRecursionDepth
> mBaseRecursionDepth
);
550 // Recursion depth should be greater than mBaseRecursionDepth,
551 // or the runnable will stay in the queue forever.
552 if (data
.mRecursionDepth
<= mBaseRecursionDepth
) {
553 data
.mRecursionDepth
= mBaseRecursionDepth
+ 1;
557 mPendingIDBTransactions
.AppendElement(std::move(data
));
560 void CycleCollectedJSContext::DispatchToMicroTask(
561 already_AddRefed
<MicroTaskRunnable
> aRunnable
) {
562 RefPtr
<MicroTaskRunnable
> runnable(aRunnable
);
564 MOZ_ASSERT(NS_IsMainThread());
565 MOZ_ASSERT(runnable
);
567 JS::JobQueueMayNotBeEmpty(Context());
569 LogMicroTaskRunnable::LogDispatch(runnable
.get());
570 mPendingMicroTaskRunnables
.push_back(std::move(runnable
));
573 class AsyncMutationHandler final
: public mozilla::Runnable
{
575 AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
577 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
579 MOZ_CAN_RUN_SCRIPT_BOUNDARY
580 NS_IMETHOD
Run() override
{
581 CycleCollectedJSContext
* ccjs
= CycleCollectedJSContext::Get();
583 ccjs
->PerformMicroTaskCheckPoint();
589 SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext
* aContext
)
590 : mContext(aContext
),
591 mSuppressionGeneration(aContext
->mSuppressionGeneration
) {}
593 bool SuppressedMicroTasks::Suppressed() {
594 if (mSuppressionGeneration
== mContext
->mSuppressionGeneration
) {
598 for (std::deque
<RefPtr
<MicroTaskRunnable
>>::reverse_iterator it
=
599 mSuppressedMicroTaskRunnables
.rbegin();
600 it
!= mSuppressedMicroTaskRunnables
.rend(); ++it
) {
601 mContext
->GetMicroTaskQueue().push_front(*it
);
603 mContext
->mSuppressedMicroTasks
= nullptr;
608 bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce
) {
609 if (mPendingMicroTaskRunnables
.empty() && mDebuggerMicroTaskQueue
.empty()) {
610 AfterProcessMicrotasks();
611 // Nothing to do, return early.
615 uint32_t currentDepth
= RecursionDepth();
616 if (mMicroTaskRecursionDepth
>= currentDepth
&& !aForce
) {
617 // We are already executing microtasks for the current recursion depth.
621 if (mTargetedMicroTaskRecursionDepth
!= 0 &&
622 mTargetedMicroTaskRecursionDepth
+ mDebuggerRecursionDepth
!=
627 if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
628 // Special case for main thread where DOM mutations may happen when
629 // it is not safe to run scripts.
630 nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
634 mozilla::AutoRestore
<uint32_t> restore(mMicroTaskRecursionDepth
);
635 MOZ_ASSERT(aForce
? currentDepth
== 0 : currentDepth
> 0);
636 mMicroTaskRecursionDepth
= currentDepth
;
638 AUTO_PROFILER_TRACING_MARKER("JS", "Perform microtasks", JS
);
640 bool didProcess
= false;
641 AutoSlowOperation aso
;
644 RefPtr
<MicroTaskRunnable
> runnable
;
645 if (!mDebuggerMicroTaskQueue
.empty()) {
646 runnable
= std::move(mDebuggerMicroTaskQueue
.front());
647 mDebuggerMicroTaskQueue
.pop_front();
648 } else if (!mPendingMicroTaskRunnables
.empty()) {
649 runnable
= std::move(mPendingMicroTaskRunnables
.front());
650 mPendingMicroTaskRunnables
.pop_front();
655 if (runnable
->Suppressed()) {
656 // Microtasks in worker shall never be suppressed.
657 // Otherwise, mPendingMicroTaskRunnables will be replaced later with
658 // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
659 MOZ_ASSERT(NS_IsMainThread());
660 JS::JobQueueMayNotBeEmpty(Context());
661 if (runnable
!= mSuppressedMicroTasks
) {
662 if (!mSuppressedMicroTasks
) {
663 mSuppressedMicroTasks
= new SuppressedMicroTasks(this);
665 mSuppressedMicroTasks
->mSuppressedMicroTaskRunnables
.push_back(
669 if (mPendingMicroTaskRunnables
.empty() &&
670 mDebuggerMicroTaskQueue
.empty() && !mSuppressedMicroTasks
) {
671 JS::JobQueueIsEmpty(Context());
675 LogMicroTaskRunnable::Run
log(runnable
.get());
681 // Put back the suppressed microtasks so that they will be run later.
682 // Note, it is possible that we end up keeping these suppressed tasks around
683 // for some time, but no longer than spinning the event loop nestedly
684 // (sync XHR, alert, etc.)
685 if (mSuppressedMicroTasks
) {
686 mPendingMicroTaskRunnables
.push_back(mSuppressedMicroTasks
);
689 AfterProcessMicrotasks();
694 void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() {
695 // Don't do normal microtask handling checks here, since whoever is calling
696 // this method is supposed to know what they are doing.
698 AutoSlowOperation aso
;
700 // For a debugger microtask checkpoint, we always use the debugger microtask
702 std::deque
<RefPtr
<MicroTaskRunnable
>>* microtaskQueue
=
703 &GetDebuggerMicroTaskQueue();
705 if (microtaskQueue
->empty()) {
709 RefPtr
<MicroTaskRunnable
> runnable
= std::move(microtaskQueue
->front());
710 MOZ_ASSERT(runnable
);
712 LogMicroTaskRunnable::Run
log(runnable
.get());
714 // This function can re-enter, so we remove the element before calling.
715 microtaskQueue
->pop_front();
717 if (mPendingMicroTaskRunnables
.empty() && mDebuggerMicroTaskQueue
.empty()) {
718 JS::JobQueueIsEmpty(Context());
724 AfterProcessMicrotasks();
727 NS_IMETHODIMP
CycleCollectedJSContext::NotifyUnhandledRejections::Run() {
728 for (size_t i
= 0; i
< mUnhandledRejections
.Length(); ++i
) {
729 CycleCollectedJSContext
* cccx
= CycleCollectedJSContext::Get();
730 NS_ENSURE_STATE(cccx
);
732 RefPtr
<Promise
>& promise
= mUnhandledRejections
[i
];
737 JS::RootingContext
* cx
= cccx
->RootingCx();
738 JS::RootedObject
promiseObj(cx
, promise
->PromiseObj());
739 MOZ_ASSERT(JS::IsPromiseObject(promiseObj
));
741 // Only fire unhandledrejection if the promise is still not handled;
742 uint64_t promiseID
= JS::GetPromiseID(promiseObj
);
743 if (!JS::GetPromiseIsHandled(promiseObj
)) {
744 if (nsCOMPtr
<EventTarget
> target
=
745 do_QueryInterface(promise
->GetParentObject())) {
746 RootedDictionary
<PromiseRejectionEventInit
> init(cx
);
747 init
.mPromise
= promise
;
748 init
.mReason
= JS::GetPromiseResult(promiseObj
);
749 init
.mCancelable
= true;
751 RefPtr
<PromiseRejectionEvent
> event
=
752 PromiseRejectionEvent::Constructor(target
, u
"unhandledrejection"_ns
,
754 // We don't use the result of dispatching event here to check whether to
755 // report the Promise to console.
756 target
->DispatchEvent(*event
);
760 cccx
= CycleCollectedJSContext::Get();
761 NS_ENSURE_STATE(cccx
);
762 if (!JS::GetPromiseIsHandled(promiseObj
)) {
763 DebugOnly
<bool> isFound
=
764 cccx
->mPendingUnhandledRejections
.Remove(promiseID
);
768 // If a rejected promise is being handled in "unhandledrejection" event
769 // handler, it should be removed from the table in
770 // PromiseRejectionTrackerCallback.
771 MOZ_ASSERT(!cccx
->mPendingUnhandledRejections
.Lookup(promiseID
));
776 nsresult
CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() {
777 CycleCollectedJSContext
* cccx
= CycleCollectedJSContext::Get();
778 NS_ENSURE_STATE(cccx
);
780 for (size_t i
= 0; i
< mUnhandledRejections
.Length(); ++i
) {
781 RefPtr
<Promise
>& promise
= mUnhandledRejections
[i
];
786 JS::RootedObject
promiseObj(cccx
->RootingCx(), promise
->PromiseObj());
787 cccx
->mPendingUnhandledRejections
.Remove(JS::GetPromiseID(promiseObj
));
792 class FinalizationRegistryCleanup::CleanupRunnable
793 : public DiscardableRunnable
{
795 explicit CleanupRunnable(FinalizationRegistryCleanup
* aCleanupWork
)
796 : DiscardableRunnable("CleanupRunnable"), mCleanupWork(aCleanupWork
) {}
798 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
800 MOZ_CAN_RUN_SCRIPT_BOUNDARY
801 NS_IMETHOD
Run() override
{
802 mCleanupWork
->DoCleanup();
807 FinalizationRegistryCleanup
* mCleanupWork
;
810 FinalizationRegistryCleanup::FinalizationRegistryCleanup(
811 CycleCollectedJSContext
* aContext
)
812 : mContext(aContext
) {}
814 void FinalizationRegistryCleanup::Destroy() {
815 // This must happen before the CycleCollectedJSContext destructor calls
816 // JS_DestroyContext().
820 void FinalizationRegistryCleanup::Init() {
821 JSContext
* cx
= mContext
->Context();
823 JS::SetHostCleanupFinalizationRegistryCallback(cx
, QueueCallback
, this);
827 void FinalizationRegistryCleanup::QueueCallback(JSFunction
* aDoCleanup
,
828 JSObject
* aIncumbentGlobal
,
830 FinalizationRegistryCleanup
* cleanup
=
831 static_cast<FinalizationRegistryCleanup
*>(aData
);
832 cleanup
->QueueCallback(aDoCleanup
, aIncumbentGlobal
);
835 void FinalizationRegistryCleanup::QueueCallback(JSFunction
* aDoCleanup
,
836 JSObject
* aIncumbentGlobal
) {
837 bool firstCallback
= mCallbacks
.empty();
839 MOZ_ALWAYS_TRUE(mCallbacks
.append(Callback
{aDoCleanup
, aIncumbentGlobal
}));
842 RefPtr
<CleanupRunnable
> cleanup
= new CleanupRunnable(this);
843 NS_DispatchToCurrentThread(cleanup
.forget());
847 void FinalizationRegistryCleanup::DoCleanup() {
848 if (mCallbacks
.empty()) {
852 JS::RootingContext
* cx
= mContext
->RootingCx();
854 JS::Rooted
<CallbackVector
> callbacks(cx
);
855 std::swap(callbacks
.get(), mCallbacks
.get());
857 for (const Callback
& callback
: callbacks
) {
858 JS::ExposeObjectToActiveJS(
859 JS_GetFunctionObject(callback
.mCallbackFunction
));
860 JS::ExposeObjectToActiveJS(callback
.mIncumbentGlobal
);
862 JS::RootedObject
functionObj(
863 cx
, JS_GetFunctionObject(callback
.mCallbackFunction
));
864 JS::RootedObject
globalObj(cx
, JS::GetNonCCWObjectGlobal(functionObj
));
866 nsIGlobalObject
* incumbentGlobal
=
867 xpc::NativeGlobal(callback
.mIncumbentGlobal
);
868 if (!incumbentGlobal
) {
872 RefPtr
<FinalizationRegistryCleanupCallback
> cleanupCallback(
873 new FinalizationRegistryCleanupCallback(functionObj
, globalObj
, nullptr,
876 nsIGlobalObject
* global
=
877 xpc::NativeGlobal(cleanupCallback
->CallbackPreserveColor());
879 cleanupCallback
->Call("FinalizationRegistryCleanup::DoCleanup");
884 void FinalizationRegistryCleanup::Callback::trace(JSTracer
* trc
) {
885 JS::TraceRoot(trc
, &mCallbackFunction
, "mCallbackFunction");
886 JS::TraceRoot(trc
, &mIncumbentGlobal
, "mIncumbentGlobal");
889 } // namespace mozilla