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/Unused.h"
26 #include "mozilla/dom/DOMException.h"
27 #include "mozilla/dom/DOMJSClass.h"
28 #include "mozilla/dom/FinalizationRegistryBinding.h"
29 #include "mozilla/dom/PromiseBinding.h"
30 #include "mozilla/dom/PromiseDebugging.h"
31 #include "mozilla/dom/PromiseRejectionEvent.h"
32 #include "mozilla/dom/PromiseRejectionEventBinding.h"
33 #include "mozilla/dom/RootedDictionary.h"
34 #include "mozilla/dom/ScriptSettings.h"
35 #include "mozilla/dom/UserActivation.h"
36 #include "nsContentUtils.h"
37 #include "nsCycleCollectionNoteRootCallback.h"
38 #include "nsCycleCollectionParticipant.h"
39 #include "nsCycleCollector.h"
40 #include "nsDOMJSUtils.h"
41 #include "nsDOMMutationObserver.h"
42 #include "nsJSUtils.h"
43 #include "nsPIDOMWindow.h"
45 #include "nsThreadUtils.h"
46 #include "nsWrapperCache.h"
47 #include "xpcpublic.h"
49 using namespace mozilla
;
50 using namespace mozilla::dom
;
54 CycleCollectedJSContext::CycleCollectedJSContext()
57 mDoingStableStates(false),
58 mTargetedMicroTaskRecursionDepth(0),
60 mSuppressionGeneration(0),
61 mDebuggerRecursionDepth(0),
62 mMicroTaskRecursionDepth(0),
63 mFinalizationRegistryCleanup(this) {
64 MOZ_COUNT_CTOR(CycleCollectedJSContext
);
66 nsCOMPtr
<nsIThread
> thread
= do_GetCurrentThread();
67 mOwningThread
= thread
.forget().downcast
<nsThread
>().take();
68 MOZ_RELEASE_ASSERT(mOwningThread
);
71 CycleCollectedJSContext::~CycleCollectedJSContext() {
72 MOZ_COUNT_DTOR(CycleCollectedJSContext
);
73 // If the allocation failed, here we are.
78 JS::SetHostCleanupFinalizationRegistryCallback(mJSContext
, nullptr, nullptr);
80 JS_SetContextPrivate(mJSContext
, nullptr);
82 mRuntime
->SetContext(nullptr);
83 mRuntime
->Shutdown(mJSContext
);
85 // Last chance to process any events.
86 CleanupIDBTransactions(mBaseRecursionDepth
);
87 MOZ_ASSERT(mPendingIDBTransactions
.IsEmpty());
89 ProcessStableStateQueue();
90 MOZ_ASSERT(mStableStateEvents
.IsEmpty());
92 // Clear mPendingException first, since it might be cycle collected.
93 mPendingException
= nullptr;
95 MOZ_ASSERT(mDebuggerMicroTaskQueue
.empty());
96 MOZ_ASSERT(mPendingMicroTaskRunnables
.empty());
98 mUncaughtRejections
.reset();
99 mConsumedRejections
.reset();
101 mAboutToBeNotifiedRejectedPromises
.Clear();
102 mPendingUnhandledRejections
.Clear();
104 mFinalizationRegistryCleanup
.Destroy();
106 JS_DestroyContext(mJSContext
);
107 mJSContext
= nullptr;
109 nsCycleCollector_forgetJSContext();
111 mozilla::dom::DestroyScriptSettings();
113 mOwningThread
->SetScriptObserver(nullptr);
114 NS_RELEASE(mOwningThread
);
120 nsresult
CycleCollectedJSContext::Initialize(JSRuntime
* aParentRuntime
,
121 uint32_t aMaxBytes
) {
122 MOZ_ASSERT(!mJSContext
);
124 mozilla::dom::InitScriptSettings();
125 mJSContext
= JS_NewContext(aMaxBytes
, aParentRuntime
);
127 return NS_ERROR_OUT_OF_MEMORY
;
130 mRuntime
= CreateRuntime(mJSContext
);
131 mRuntime
->SetContext(this);
133 mOwningThread
->SetScriptObserver(this);
134 // The main thread has a base recursion depth of 0, workers of 1.
135 mBaseRecursionDepth
= RecursionDepth();
137 NS_GetCurrentThread()->SetCanInvokeJS(true);
139 JS::SetJobQueue(mJSContext
, this);
140 JS::SetPromiseRejectionTrackerCallback(mJSContext
,
141 PromiseRejectionTrackerCallback
, this);
142 mUncaughtRejections
.init(mJSContext
,
143 JS::GCVector
<JSObject
*, 0, js::SystemAllocPolicy
>(
144 js::SystemAllocPolicy()));
145 mConsumedRejections
.init(mJSContext
,
146 JS::GCVector
<JSObject
*, 0, js::SystemAllocPolicy
>(
147 js::SystemAllocPolicy()));
149 mFinalizationRegistryCleanup
.Init();
151 // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*).
152 JS_SetContextPrivate(mJSContext
, static_cast<PerThreadAtomCache
*>(this));
154 nsCycleCollector_registerJSContext(this);
160 CycleCollectedJSContext
* CycleCollectedJSContext::GetFor(JSContext
* aCx
) {
161 // Cast from void* matching JS_SetContextPrivate.
162 auto atomCache
= static_cast<PerThreadAtomCache
*>(JS_GetContextPrivate(aCx
));
164 return static_cast<CycleCollectedJSContext
*>(atomCache
);
167 size_t CycleCollectedJSContext::SizeOfExcludingThis(
168 MallocSizeOf aMallocSizeOf
) const {
172 class PromiseJobRunnable final
: public MicroTaskRunnable
{
174 PromiseJobRunnable(JS::HandleObject aPromise
, JS::HandleObject aCallback
,
175 JS::HandleObject aCallbackGlobal
,
176 JS::HandleObject aAllocationSite
,
177 nsIGlobalObject
* aIncumbentGlobal
)
178 : mCallback(new PromiseJobCallback(aCallback
, aCallbackGlobal
,
179 aAllocationSite
, aIncumbentGlobal
)),
180 mPropagateUserInputEventHandling(false) {
181 MOZ_ASSERT(js::IsFunctionObject(aCallback
));
184 JS::PromiseUserInputEventHandlingState state
=
185 JS::GetPromiseUserInputEventHandlingState(aPromise
);
186 mPropagateUserInputEventHandling
=
188 JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
;
192 virtual ~PromiseJobRunnable() = default;
196 virtual void Run(AutoSlowOperation
& aAso
) override
{
197 JSObject
* callback
= mCallback
->CallbackPreserveColor();
198 nsIGlobalObject
* global
= callback
? xpc::NativeGlobal(callback
) : nullptr;
199 if (global
&& !global
->IsDying()) {
200 // Propagate the user input event handling bit if needed.
201 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(global
);
202 RefPtr
<Document
> doc
;
204 doc
= win
->GetExtantDoc();
206 AutoHandlingUserInputStatePusher
userInpStatePusher(
207 mPropagateUserInputEventHandling
);
209 mCallback
->Call("promise callback");
210 aAso
.CheckForInterrupt();
212 // Now that mCallback is no longer needed, clear any pointers it contains to
213 // JS GC things. This removes any storebuffer entries associated with those
214 // pointers, which can cause problems by taking up memory and by triggering
215 // minor GCs. This otherwise would not happen until the next minor GC or
220 virtual bool Suppressed() override
{
221 JSObject
* callback
= mCallback
->CallbackPreserveColor();
222 nsIGlobalObject
* global
= callback
? xpc::NativeGlobal(callback
) : nullptr;
223 return global
&& global
->IsInSyncOperation();
227 const RefPtr
<PromiseJobCallback
> mCallback
;
228 bool mPropagateUserInputEventHandling
;
231 // Finalizer for instances of FinalizeHostDefinedData.
233 // HostDefinedData only contains incumbent global, no need to
235 // TODO(sefeng): Bug 1929356 will add [[SchedulingState]] to HostDefinedData.
236 void FinalizeHostDefinedData(JS::GCContext
* gcx
, JSObject
* objSelf
) {}
238 static const JSClassOps sHostDefinedData
= {
239 nullptr /* addProperty */, nullptr /* delProperty */,
240 nullptr /* enumerate */, nullptr /* newEnumerate */,
241 nullptr /* resolve */, nullptr /* mayResolve */,
242 FinalizeHostDefinedData
/* finalize */
245 enum { INCUMBENT_SETTING_SLOT
, HOSTDEFINED_DATA_SLOTS
};
247 // Implements `HostDefined` in https://html.spec.whatwg.org/#hostmakejobcallback
248 static const JSClass sHostDefinedDataClass
= {
250 JSCLASS_HAS_RESERVED_SLOTS(HOSTDEFINED_DATA_SLOTS
) |
251 JSCLASS_BACKGROUND_FINALIZE
,
254 bool CycleCollectedJSContext::getHostDefinedData(
255 JSContext
* aCx
, JS::MutableHandle
<JSObject
*> aData
) const {
256 nsIGlobalObject
* global
= mozilla::dom::GetIncumbentGlobal();
262 JS::Rooted
<JSObject
*> incumbentGlobal(aCx
, global
->GetGlobalJSObject());
264 if (!incumbentGlobal
) {
269 JSAutoRealm
ar(aCx
, incumbentGlobal
);
271 JS::Rooted
<JSObject
*> objResult(aCx
,
272 JS_NewObject(aCx
, &sHostDefinedDataClass
));
278 JS_SetReservedSlot(objResult
, INCUMBENT_SETTING_SLOT
,
279 JS::ObjectValue(*incumbentGlobal
));
280 aData
.set(objResult
);
285 bool CycleCollectedJSContext::enqueuePromiseJob(
286 JSContext
* aCx
, JS::HandleObject aPromise
, JS::HandleObject aJob
,
287 JS::HandleObject aAllocationSite
, JS::HandleObject hostDefinedData
) {
288 MOZ_ASSERT(aCx
== Context());
289 MOZ_ASSERT(Get() == this);
291 nsIGlobalObject
* global
= nullptr;
293 if (hostDefinedData
) {
294 MOZ_RELEASE_ASSERT(JS::GetClass(hostDefinedData
.get()) ==
295 &sHostDefinedDataClass
);
296 JS::Value incumbentGlobal
=
297 JS::GetReservedSlot(hostDefinedData
.get(), INCUMBENT_SETTING_SLOT
);
298 // hostDefinedData is only created when incumbent global exists.
299 MOZ_ASSERT(incumbentGlobal
.isObject());
300 global
= xpc::NativeGlobal(&incumbentGlobal
.toObject());
303 JS::RootedObject
jobGlobal(aCx
, JS::CurrentGlobalOrNull(aCx
));
304 RefPtr
<PromiseJobRunnable
> runnable
= new PromiseJobRunnable(
305 aPromise
, aJob
, jobGlobal
, aAllocationSite
, global
);
306 DispatchToMicroTask(runnable
.forget());
310 // Used only by the SpiderMonkey Debugger API, and even then only via
311 // JS::AutoDebuggerJobQueueInterruption, to ensure that the debuggee's queue is
312 // not affected; see comments in js/public/Promise.h.
313 void CycleCollectedJSContext::runJobs(JSContext
* aCx
) {
314 MOZ_ASSERT(aCx
== Context());
315 MOZ_ASSERT(Get() == this);
316 PerformMicroTaskCheckPoint();
319 bool CycleCollectedJSContext::empty() const {
320 // This is our override of JS::JobQueue::empty. Since that interface is only
321 // concerned with the ordinary microtask queue, not the debugger microtask
322 // queue, we only report on the former.
323 return mPendingMicroTaskRunnables
.empty();
326 // Preserve a debuggee's microtask queue while it is interrupted by the
327 // debugger. See the comments for JS::AutoDebuggerJobQueueInterruption.
328 class CycleCollectedJSContext::SavedMicroTaskQueue
329 : public JS::JobQueue::SavedJobQueue
{
331 explicit SavedMicroTaskQueue(CycleCollectedJSContext
* ccjs
) : ccjs(ccjs
) {
332 ccjs
->mDebuggerRecursionDepth
++;
333 ccjs
->mPendingMicroTaskRunnables
.swap(mQueue
);
336 ~SavedMicroTaskQueue() {
337 // The JS Debugger attempts to maintain the invariant that microtasks which
338 // occur durring debugger operation are completely flushed from the task
339 // queue before returning control to the debuggee, in order to avoid
340 // micro-tasks generated during debugging from interfering with regular
343 // While the vast majority of microtasks can be reliably flushed,
344 // synchronous operations (see nsAutoSyncOperation) such as printing and
345 // alert diaglogs suppress the execution of some microtasks.
347 // When PerformMicroTaskCheckpoint is run while microtasks are suppressed,
348 // any suppressed microtasks are gathered into a new SuppressedMicroTasks
349 // runnable, which is enqueued on exit from PerformMicroTaskCheckpoint. As a
350 // result, AutoDebuggerJobQueueInterruption::runJobs is not able to
351 // correctly guarantee that the microtask queue is totally empty in the
352 // presence of sync operations.
354 // Previous versions of this code release-asserted that the queue was empty,
355 // causing user observable crashes (Bug 1849675). To avoid this, we instead
356 // choose to move suspended microtasks from the SavedMicroTaskQueue to the
357 // main microtask queue in this destructor. This means that jobs enqueued
358 // during synchnronous events under debugger control may produce events
359 // which run outside the debugger, but this is viewed as strictly
360 // preferrable to crashing.
361 MOZ_RELEASE_ASSERT(ccjs
->mPendingMicroTaskRunnables
.size() <= 1);
362 MOZ_RELEASE_ASSERT(ccjs
->mDebuggerRecursionDepth
);
363 RefPtr
<MicroTaskRunnable
> maybeSuppressedTasks
;
365 // Handle the case where there is a SuppressedMicroTask still in the queue.
366 if (!ccjs
->mPendingMicroTaskRunnables
.empty()) {
367 maybeSuppressedTasks
= ccjs
->mPendingMicroTaskRunnables
.front();
368 ccjs
->mPendingMicroTaskRunnables
.pop_front();
371 MOZ_RELEASE_ASSERT(ccjs
->mPendingMicroTaskRunnables
.empty());
372 ccjs
->mDebuggerRecursionDepth
--;
373 ccjs
->mPendingMicroTaskRunnables
.swap(mQueue
);
375 // Re-enqueue the suppressed task now that we've put the original microtask
377 if (maybeSuppressedTasks
) {
378 ccjs
->mPendingMicroTaskRunnables
.push_back(maybeSuppressedTasks
);
383 CycleCollectedJSContext
* ccjs
;
384 std::deque
<RefPtr
<MicroTaskRunnable
>> mQueue
;
387 js::UniquePtr
<JS::JobQueue::SavedJobQueue
>
388 CycleCollectedJSContext::saveJobQueue(JSContext
* cx
) {
389 auto saved
= js::MakeUnique
<SavedMicroTaskQueue
>(this);
391 // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor
392 // is never called, so mPendingMicroTaskRunnables is still initialized.
393 JS_ReportOutOfMemory(cx
);
401 void CycleCollectedJSContext::PromiseRejectionTrackerCallback(
402 JSContext
* aCx
, bool aMutedErrors
, JS::HandleObject aPromise
,
403 JS::PromiseRejectionHandlingState state
, void* aData
) {
404 CycleCollectedJSContext
* self
= static_cast<CycleCollectedJSContext
*>(aData
);
406 MOZ_ASSERT(aCx
== self
->Context());
407 MOZ_ASSERT(Get() == self
);
409 // TODO: Bug 1549351 - Promise rejection event should not be sent for
410 // cross-origin scripts
412 PromiseArray
& aboutToBeNotified
= self
->mAboutToBeNotifiedRejectedPromises
;
413 PromiseHashtable
& unhandled
= self
->mPendingUnhandledRejections
;
414 uint64_t promiseID
= JS::GetPromiseID(aPromise
);
416 if (state
== JS::PromiseRejectionHandlingState::Unhandled
) {
417 PromiseDebugging::AddUncaughtRejection(aPromise
);
419 RefPtr
<Promise
> promise
=
420 Promise::CreateFromExisting(xpc::NativeGlobal(aPromise
), aPromise
);
421 aboutToBeNotified
.AppendElement(promise
);
422 unhandled
.InsertOrUpdate(promiseID
, std::move(promise
));
425 PromiseDebugging::AddConsumedRejection(aPromise
);
426 for (size_t i
= 0; i
< aboutToBeNotified
.Length(); i
++) {
427 if (aboutToBeNotified
[i
] &&
428 aboutToBeNotified
[i
]->PromiseObj() == aPromise
) {
429 // To avoid large amounts of memmoves, we don't shrink the vector
430 // here. Instead, we filter out nullptrs when iterating over the
432 aboutToBeNotified
[i
] = nullptr;
433 DebugOnly
<bool> isFound
= unhandled
.Remove(promiseID
);
438 RefPtr
<Promise
> promise
;
439 unhandled
.Remove(promiseID
, getter_AddRefs(promise
));
440 if (!promise
&& !aMutedErrors
) {
441 nsIGlobalObject
* global
= xpc::NativeGlobal(aPromise
);
442 if (nsCOMPtr
<EventTarget
> owner
= do_QueryInterface(global
)) {
443 RootedDictionary
<PromiseRejectionEventInit
> init(aCx
);
444 if (RefPtr
<Promise
> newPromise
=
445 Promise::CreateFromExisting(global
, aPromise
)) {
446 init
.mPromise
= newPromise
->PromiseObj();
448 init
.mReason
= JS::GetPromiseResult(aPromise
);
450 RefPtr
<PromiseRejectionEvent
> event
=
451 PromiseRejectionEvent::Constructor(owner
, u
"rejectionhandled"_ns
,
454 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
455 new AsyncEventDispatcher(owner
, event
.forget());
456 asyncDispatcher
->PostDOMEvent();
462 already_AddRefed
<Exception
> CycleCollectedJSContext::GetPendingException()
464 MOZ_ASSERT(mJSContext
);
466 nsCOMPtr
<Exception
> out
= mPendingException
;
470 void CycleCollectedJSContext::SetPendingException(Exception
* aException
) {
471 MOZ_ASSERT(mJSContext
);
472 mPendingException
= aException
;
475 std::deque
<RefPtr
<MicroTaskRunnable
>>&
476 CycleCollectedJSContext::GetMicroTaskQueue() {
477 MOZ_ASSERT(mJSContext
);
478 return mPendingMicroTaskRunnables
;
481 std::deque
<RefPtr
<MicroTaskRunnable
>>&
482 CycleCollectedJSContext::GetDebuggerMicroTaskQueue() {
483 MOZ_ASSERT(mJSContext
);
484 return mDebuggerMicroTaskQueue
;
487 void CycleCollectedJSContext::ProcessStableStateQueue() {
488 MOZ_ASSERT(mJSContext
);
489 MOZ_RELEASE_ASSERT(!mDoingStableStates
);
490 mDoingStableStates
= true;
492 // When run, one event can add another event to the mStableStateEvents, as
493 // such you can't use iterators here.
494 for (uint32_t i
= 0; i
< mStableStateEvents
.Length(); ++i
) {
495 nsCOMPtr
<nsIRunnable
> event
= std::move(mStableStateEvents
[i
]);
499 mStableStateEvents
.Clear();
500 mDoingStableStates
= false;
503 void CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth
) {
504 MOZ_ASSERT(mJSContext
);
505 MOZ_RELEASE_ASSERT(!mDoingStableStates
);
506 mDoingStableStates
= true;
508 nsTArray
<PendingIDBTransactionData
> localQueue
=
509 std::move(mPendingIDBTransactions
);
511 localQueue
.RemoveLastElements(
513 std::remove_if(localQueue
.begin(), localQueue
.end(),
514 [aRecursionDepth
](PendingIDBTransactionData
& data
) {
515 if (data
.mRecursionDepth
!= aRecursionDepth
) {
520 nsCOMPtr
<nsIRunnable
> transaction
=
521 std::move(data
.mTransaction
);
528 // If mPendingIDBTransactions has events in it now, they were added from
529 // something we called, so they belong at the end of the queue.
530 localQueue
.AppendElements(std::move(mPendingIDBTransactions
));
531 mPendingIDBTransactions
= std::move(localQueue
);
532 mDoingStableStates
= false;
535 void CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock
) {
536 // If ProcessNextEvent was called during a microtask callback, we
537 // must process any pending microtasks before blocking in the event loop,
538 // otherwise we may deadlock until an event enters the queue later.
539 if (aMightBlock
&& PerformMicroTaskCheckPoint()) {
540 // If any microtask was processed, we post a dummy event in order to
541 // force the ProcessNextEvent call not to block. This is required
542 // to support nested event loops implemented using a pattern like
543 // "while (condition) thread.processNextEvent(true)", in case the
544 // condition is triggered here by a Promise "then" callback.
545 NS_DispatchToMainThread(new Runnable("BeforeProcessTask"));
549 void CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth
) {
550 MOZ_ASSERT(mJSContext
);
552 // See HTML 6.1.4.2 Processing model
554 // Step 4.1: Execute microtasks.
555 PerformMicroTaskCheckPoint();
557 // Step 4.2 Execute any events that were waiting for a stable state.
558 ProcessStableStateQueue();
560 // This should be a fast test so that it won't affect the next task
565 void CycleCollectedJSContext::AfterProcessMicrotasks() {
566 MOZ_ASSERT(mJSContext
);
567 // Notify unhandled promise rejections:
568 // https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
569 if (mAboutToBeNotifiedRejectedPromises
.Length()) {
570 RefPtr
<NotifyUnhandledRejections
> runnable
= new NotifyUnhandledRejections(
571 std::move(mAboutToBeNotifiedRejectedPromises
));
572 NS_DispatchToCurrentThread(runnable
);
574 // Cleanup Indexed Database transactions:
575 // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
576 CleanupIDBTransactions(RecursionDepth());
578 // Clear kept alive objects in JS WeakRef.
579 // https://whatpr.org/html/4571/webappapis.html#perform-a-microtask-checkpoint
581 // ECMAScript implementations are expected to call ClearKeptObjects when a
582 // synchronous sequence of ECMAScript execution completes.
584 // https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
585 JS::ClearKeptObjects(mJSContext
);
588 void CycleCollectedJSContext::MaybePokeGC() {
589 // Worker-compatible check to see if we want to do an idle-time minor
591 class IdleTimeGCTaskRunnable
: public mozilla::IdleRunnable
{
593 using mozilla::IdleRunnable::IdleRunnable
;
596 IdleTimeGCTaskRunnable() : IdleRunnable("IdleTimeGCTask") {}
598 NS_IMETHOD
Run() override
{
599 CycleCollectedJSRuntime
* ccrt
= CycleCollectedJSRuntime::Get();
601 ccrt
->RunIdleTimeGCTask();
607 if (Runtime()->IsIdleGCTaskNeeded()) {
608 nsCOMPtr
<nsIRunnable
> gc_task
= new IdleTimeGCTaskRunnable();
609 NS_DispatchToCurrentThreadQueue(gc_task
.forget(), EventQueuePriority::Idle
);
610 Runtime()->SetPendingIdleGCTask();
614 uint32_t CycleCollectedJSContext::RecursionDepth() const {
615 // Debugger interruptions are included in the recursion depth so that debugger
616 // microtask checkpoints do not run IDB transactions which were initiated
617 // before the interruption.
618 return mOwningThread
->RecursionDepth() + mDebuggerRecursionDepth
;
621 void CycleCollectedJSContext::RunInStableState(
622 already_AddRefed
<nsIRunnable
>&& aRunnable
) {
623 MOZ_ASSERT(mJSContext
);
624 mStableStateEvents
.AppendElement(std::move(aRunnable
));
627 void CycleCollectedJSContext::AddPendingIDBTransaction(
628 already_AddRefed
<nsIRunnable
>&& aTransaction
) {
629 MOZ_ASSERT(mJSContext
);
631 PendingIDBTransactionData data
;
632 data
.mTransaction
= aTransaction
;
634 MOZ_ASSERT(mOwningThread
);
635 data
.mRecursionDepth
= RecursionDepth();
637 // There must be an event running to get here.
638 #ifndef MOZ_WIDGET_COCOA
639 MOZ_ASSERT(data
.mRecursionDepth
> mBaseRecursionDepth
);
642 // Recursion depth should be greater than mBaseRecursionDepth,
643 // or the runnable will stay in the queue forever.
644 if (data
.mRecursionDepth
<= mBaseRecursionDepth
) {
645 data
.mRecursionDepth
= mBaseRecursionDepth
+ 1;
649 mPendingIDBTransactions
.AppendElement(std::move(data
));
652 void CycleCollectedJSContext::DispatchToMicroTask(
653 already_AddRefed
<MicroTaskRunnable
> aRunnable
) {
654 RefPtr
<MicroTaskRunnable
> runnable(aRunnable
);
656 MOZ_ASSERT(NS_IsMainThread());
657 MOZ_ASSERT(runnable
);
659 JS::JobQueueMayNotBeEmpty(Context());
661 LogMicroTaskRunnable::LogDispatch(runnable
.get());
662 mPendingMicroTaskRunnables
.push_back(std::move(runnable
));
665 class AsyncMutationHandler final
: public mozilla::Runnable
{
667 AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
669 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
671 MOZ_CAN_RUN_SCRIPT_BOUNDARY
672 NS_IMETHOD
Run() override
{
673 CycleCollectedJSContext
* ccjs
= CycleCollectedJSContext::Get();
675 ccjs
->PerformMicroTaskCheckPoint();
681 SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext
* aContext
)
682 : mContext(aContext
),
683 mSuppressionGeneration(aContext
->mSuppressionGeneration
) {}
685 bool SuppressedMicroTasks::Suppressed() {
686 if (mSuppressionGeneration
== mContext
->mSuppressionGeneration
) {
690 for (std::deque
<RefPtr
<MicroTaskRunnable
>>::reverse_iterator it
=
691 mSuppressedMicroTaskRunnables
.rbegin();
692 it
!= mSuppressedMicroTaskRunnables
.rend(); ++it
) {
693 mContext
->GetMicroTaskQueue().push_front(*it
);
695 mContext
->mSuppressedMicroTasks
= nullptr;
700 bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce
) {
701 if (mPendingMicroTaskRunnables
.empty() && mDebuggerMicroTaskQueue
.empty()) {
702 AfterProcessMicrotasks();
703 // Nothing to do, return early.
707 uint32_t currentDepth
= RecursionDepth();
708 if (mMicroTaskRecursionDepth
>= currentDepth
&& !aForce
) {
709 // We are already executing microtasks for the current recursion depth.
713 if (mTargetedMicroTaskRecursionDepth
!= 0 &&
714 mTargetedMicroTaskRecursionDepth
+ mDebuggerRecursionDepth
!=
719 if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
720 // Special case for main thread where DOM mutations may happen when
721 // it is not safe to run scripts.
722 nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
726 mozilla::AutoRestore
<uint32_t> restore(mMicroTaskRecursionDepth
);
727 MOZ_ASSERT(aForce
? currentDepth
== 0 : currentDepth
> 0);
728 mMicroTaskRecursionDepth
= currentDepth
;
730 AUTO_PROFILER_TRACING_MARKER("JS", "Perform microtasks", JS
);
732 bool didProcess
= false;
733 AutoSlowOperation aso
;
736 RefPtr
<MicroTaskRunnable
> runnable
;
737 if (!mDebuggerMicroTaskQueue
.empty()) {
738 runnable
= std::move(mDebuggerMicroTaskQueue
.front());
739 mDebuggerMicroTaskQueue
.pop_front();
740 } else if (!mPendingMicroTaskRunnables
.empty()) {
741 runnable
= std::move(mPendingMicroTaskRunnables
.front());
742 mPendingMicroTaskRunnables
.pop_front();
747 if (runnable
->Suppressed()) {
748 // Microtasks in worker shall never be suppressed.
749 // Otherwise, mPendingMicroTaskRunnables will be replaced later with
750 // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
751 MOZ_ASSERT(NS_IsMainThread());
752 JS::JobQueueMayNotBeEmpty(Context());
753 if (runnable
!= mSuppressedMicroTasks
) {
754 if (!mSuppressedMicroTasks
) {
755 mSuppressedMicroTasks
= new SuppressedMicroTasks(this);
757 mSuppressedMicroTasks
->mSuppressedMicroTaskRunnables
.push_back(
761 if (mPendingMicroTaskRunnables
.empty() &&
762 mDebuggerMicroTaskQueue
.empty() && !mSuppressedMicroTasks
) {
763 JS::JobQueueIsEmpty(Context());
767 LogMicroTaskRunnable::Run
log(runnable
.get());
773 // Put back the suppressed microtasks so that they will be run later.
774 // Note, it is possible that we end up keeping these suppressed tasks around
775 // for some time, but no longer than spinning the event loop nestedly
776 // (sync XHR, alert, etc.)
777 if (mSuppressedMicroTasks
) {
778 mPendingMicroTaskRunnables
.push_back(mSuppressedMicroTasks
);
781 AfterProcessMicrotasks();
786 void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() {
787 // Don't do normal microtask handling checks here, since whoever is calling
788 // this method is supposed to know what they are doing.
790 AutoSlowOperation aso
;
792 // For a debugger microtask checkpoint, we always use the debugger microtask
794 std::deque
<RefPtr
<MicroTaskRunnable
>>* microtaskQueue
=
795 &GetDebuggerMicroTaskQueue();
797 if (microtaskQueue
->empty()) {
801 RefPtr
<MicroTaskRunnable
> runnable
= std::move(microtaskQueue
->front());
802 MOZ_ASSERT(runnable
);
804 LogMicroTaskRunnable::Run
log(runnable
.get());
806 // This function can re-enter, so we remove the element before calling.
807 microtaskQueue
->pop_front();
809 if (mPendingMicroTaskRunnables
.empty() && mDebuggerMicroTaskQueue
.empty()) {
810 JS::JobQueueIsEmpty(Context());
816 AfterProcessMicrotasks();
819 NS_IMETHODIMP
CycleCollectedJSContext::NotifyUnhandledRejections::Run() {
820 for (size_t i
= 0; i
< mUnhandledRejections
.Length(); ++i
) {
821 CycleCollectedJSContext
* cccx
= CycleCollectedJSContext::Get();
822 NS_ENSURE_STATE(cccx
);
824 RefPtr
<Promise
>& promise
= mUnhandledRejections
[i
];
829 JS::RootingContext
* cx
= cccx
->RootingCx();
830 JS::RootedObject
promiseObj(cx
, promise
->PromiseObj());
831 MOZ_ASSERT(JS::IsPromiseObject(promiseObj
));
833 // Only fire unhandledrejection if the promise is still not handled;
834 uint64_t promiseID
= JS::GetPromiseID(promiseObj
);
835 if (!JS::GetPromiseIsHandled(promiseObj
)) {
836 if (nsCOMPtr
<EventTarget
> target
=
837 do_QueryInterface(promise
->GetParentObject())) {
838 RootedDictionary
<PromiseRejectionEventInit
> init(cx
);
839 init
.mPromise
= promiseObj
;
840 init
.mReason
= JS::GetPromiseResult(promiseObj
);
841 init
.mCancelable
= true;
843 RefPtr
<PromiseRejectionEvent
> event
=
844 PromiseRejectionEvent::Constructor(target
, u
"unhandledrejection"_ns
,
846 // We don't use the result of dispatching event here to check whether to
847 // report the Promise to console.
848 target
->DispatchEvent(*event
);
852 cccx
= CycleCollectedJSContext::Get();
853 NS_ENSURE_STATE(cccx
);
854 if (!JS::GetPromiseIsHandled(promiseObj
)) {
855 DebugOnly
<bool> isFound
=
856 cccx
->mPendingUnhandledRejections
.Remove(promiseID
);
860 // If a rejected promise is being handled in "unhandledrejection" event
861 // handler, it should be removed from the table in
862 // PromiseRejectionTrackerCallback.
863 MOZ_ASSERT(!cccx
->mPendingUnhandledRejections
.Lookup(promiseID
));
868 nsresult
CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() {
869 CycleCollectedJSContext
* cccx
= CycleCollectedJSContext::Get();
870 NS_ENSURE_STATE(cccx
);
872 for (size_t i
= 0; i
< mUnhandledRejections
.Length(); ++i
) {
873 RefPtr
<Promise
>& promise
= mUnhandledRejections
[i
];
878 JS::RootedObject
promiseObj(cccx
->RootingCx(), promise
->PromiseObj());
879 cccx
->mPendingUnhandledRejections
.Remove(JS::GetPromiseID(promiseObj
));
884 #ifdef MOZ_EXECUTION_TRACING
886 void CycleCollectedJSContext::BeginExecutionTracingAsync() {
887 mOwningThread
->Dispatch(NS_NewRunnableFunction(
888 "CycleCollectedJSContext::BeginExecutionTracingAsync", [] {
889 CycleCollectedJSContext
* ccjs
= CycleCollectedJSContext::Get();
891 JS_TracerBeginTracing(ccjs
->Context());
896 void CycleCollectedJSContext::EndExecutionTracingAsync() {
897 mOwningThread
->Dispatch(NS_NewRunnableFunction(
898 "CycleCollectedJSContext::EndExecutionTracingAsync", [] {
899 CycleCollectedJSContext
* ccjs
= CycleCollectedJSContext::Get();
901 JS_TracerEndTracing(ccjs
->Context());
908 void CycleCollectedJSContext::BeginExecutionTracingAsync() {}
909 void CycleCollectedJSContext::EndExecutionTracingAsync() {}
913 class FinalizationRegistryCleanup::CleanupRunnable
914 : public DiscardableRunnable
{
916 explicit CleanupRunnable(FinalizationRegistryCleanup
* aCleanupWork
)
917 : DiscardableRunnable("CleanupRunnable"), mCleanupWork(aCleanupWork
) {}
919 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
921 MOZ_CAN_RUN_SCRIPT_BOUNDARY
922 NS_IMETHOD
Run() override
{
923 mCleanupWork
->DoCleanup();
928 FinalizationRegistryCleanup
* mCleanupWork
;
931 FinalizationRegistryCleanup::FinalizationRegistryCleanup(
932 CycleCollectedJSContext
* aContext
)
933 : mContext(aContext
) {}
935 void FinalizationRegistryCleanup::Destroy() {
936 // This must happen before the CycleCollectedJSContext destructor calls
937 // JS_DestroyContext().
941 void FinalizationRegistryCleanup::Init() {
942 JSContext
* cx
= mContext
->Context();
944 JS::SetHostCleanupFinalizationRegistryCallback(cx
, QueueCallback
, this);
948 void FinalizationRegistryCleanup::QueueCallback(JSFunction
* aDoCleanup
,
949 JSObject
* aHostDefinedData
,
951 FinalizationRegistryCleanup
* cleanup
=
952 static_cast<FinalizationRegistryCleanup
*>(aData
);
953 cleanup
->QueueCallback(aDoCleanup
, aHostDefinedData
);
956 void FinalizationRegistryCleanup::QueueCallback(JSFunction
* aDoCleanup
,
957 JSObject
* aHostDefinedData
) {
958 bool firstCallback
= mCallbacks
.empty();
960 JSObject
* incumbentGlobal
= nullptr;
962 // Extract incumbentGlobal from aHostDefinedData.
963 if (aHostDefinedData
) {
964 MOZ_RELEASE_ASSERT(JS::GetClass(aHostDefinedData
) ==
965 &sHostDefinedDataClass
);
967 JS::GetReservedSlot(aHostDefinedData
, INCUMBENT_SETTING_SLOT
);
968 incumbentGlobal
= &global
.toObject();
971 MOZ_ALWAYS_TRUE(mCallbacks
.append(Callback
{aDoCleanup
, incumbentGlobal
}));
974 RefPtr
<CleanupRunnable
> cleanup
= new CleanupRunnable(this);
975 NS_DispatchToCurrentThread(cleanup
.forget());
979 void FinalizationRegistryCleanup::DoCleanup() {
980 if (mCallbacks
.empty()) {
984 JS::RootingContext
* cx
= mContext
->RootingCx();
986 JS::Rooted
<CallbackVector
> callbacks(cx
);
987 std::swap(callbacks
.get(), mCallbacks
.get());
989 for (const Callback
& callback
: callbacks
) {
990 JS::ExposeObjectToActiveJS(
991 JS_GetFunctionObject(callback
.mCallbackFunction
));
992 JS::ExposeObjectToActiveJS(callback
.mIncumbentGlobal
);
994 JS::RootedObject
functionObj(
995 cx
, JS_GetFunctionObject(callback
.mCallbackFunction
));
996 JS::RootedObject
globalObj(cx
, JS::GetNonCCWObjectGlobal(functionObj
));
998 nsIGlobalObject
* incumbentGlobal
=
999 xpc::NativeGlobal(callback
.mIncumbentGlobal
);
1000 if (!incumbentGlobal
) {
1004 RefPtr
<FinalizationRegistryCleanupCallback
> cleanupCallback(
1005 new FinalizationRegistryCleanupCallback(functionObj
, globalObj
, nullptr,
1008 nsIGlobalObject
* global
=
1009 xpc::NativeGlobal(cleanupCallback
->CallbackPreserveColor());
1011 cleanupCallback
->Call("FinalizationRegistryCleanup::DoCleanup");
1016 void FinalizationRegistryCleanup::Callback::trace(JSTracer
* trc
) {
1017 JS::TraceRoot(trc
, &mCallbackFunction
, "mCallbackFunction");
1018 JS::TraceRoot(trc
, &mIncumbentGlobal
, "mIncumbentGlobal");
1021 } // namespace mozilla