Bug 1910362 - Create new Nimbus helper r=aaronmt,ohorvath
[gecko.git] / xpcom / base / CycleCollectedJSContext.cpp
blob46dba16745babed530bcfa020e8bd90d702cab2c
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"
9 #include <algorithm>
10 #include <utility>
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/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"
44 #include "nsThread.h"
45 #include "nsThreadUtils.h"
46 #include "nsWrapperCache.h"
47 #include "xpcpublic.h"
49 using namespace mozilla;
50 using namespace mozilla::dom;
52 namespace mozilla {
54 CycleCollectedJSContext::CycleCollectedJSContext()
55 : mRuntime(nullptr),
56 mJSContext(nullptr),
57 mDoingStableStates(false),
58 mTargetedMicroTaskRecursionDepth(0),
59 mMicroTaskLevel(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.
74 if (!mJSContext) {
75 return;
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);
116 delete mRuntime;
117 mRuntime = nullptr;
120 nsresult CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime,
121 uint32_t aMaxBytes) {
122 MOZ_ASSERT(!mJSContext);
124 mozilla::dom::InitScriptSettings();
125 mJSContext = JS_NewContext(aMaxBytes, aParentRuntime);
126 if (!mJSContext) {
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);
156 return NS_OK;
159 /* static */
160 CycleCollectedJSContext* CycleCollectedJSContext::GetFor(JSContext* aCx) {
161 // Cast from void* matching JS_SetContextPrivate.
162 auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx));
163 // Down cast.
164 return static_cast<CycleCollectedJSContext*>(atomCache);
167 size_t CycleCollectedJSContext::SizeOfExcludingThis(
168 MallocSizeOf aMallocSizeOf) const {
169 return 0;
172 class PromiseJobRunnable final : public MicroTaskRunnable {
173 public:
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));
183 if (aPromise) {
184 JS::PromiseUserInputEventHandlingState state =
185 JS::GetPromiseUserInputEventHandlingState(aPromise);
186 mPropagateUserInputEventHandling =
187 state ==
188 JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
192 virtual ~PromiseJobRunnable() = default;
194 protected:
195 MOZ_CAN_RUN_SCRIPT
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;
203 if (win) {
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
216 // cycle collection.
217 mCallback->Reset();
220 virtual bool Suppressed() override {
221 JSObject* callback = mCallback->CallbackPreserveColor();
222 nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
223 return global && global->IsInSyncOperation();
226 private:
227 const RefPtr<PromiseJobCallback> mCallback;
228 bool mPropagateUserInputEventHandling;
231 // Finalizer for instances of FinalizeHostDefinedData.
233 // HostDefinedData only contains incumbent global, no need to
234 // clean that up.
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 = {
249 "HostDefinedData",
250 JSCLASS_HAS_RESERVED_SLOTS(HOSTDEFINED_DATA_SLOTS) |
251 JSCLASS_BACKGROUND_FINALIZE,
252 &sHostDefinedData};
254 bool CycleCollectedJSContext::getHostDefinedData(
255 JSContext* aCx, JS::MutableHandle<JSObject*> aData) const {
256 nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
257 if (!global) {
258 aData.set(nullptr);
259 return true;
262 JS::Rooted<JSObject*> incumbentGlobal(aCx, global->GetGlobalJSObject());
264 if (!incumbentGlobal) {
265 aData.set(nullptr);
266 return true;
269 JSAutoRealm ar(aCx, incumbentGlobal);
271 JS::Rooted<JSObject*> objResult(aCx,
272 JS_NewObject(aCx, &sHostDefinedDataClass));
273 if (!objResult) {
274 aData.set(nullptr);
275 return false;
278 JS_SetReservedSlot(objResult, INCUMBENT_SETTING_SLOT,
279 JS::ObjectValue(*incumbentGlobal));
280 aData.set(objResult);
282 return true;
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());
307 return true;
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 {
330 public:
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
341 // operation.
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
376 // queue back.
377 if (maybeSuppressedTasks) {
378 ccjs->mPendingMicroTaskRunnables.push_back(maybeSuppressedTasks);
382 private:
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);
390 if (!saved) {
391 // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor
392 // is never called, so mPendingMicroTaskRunnables is still initialized.
393 JS_ReportOutOfMemory(cx);
394 return nullptr;
397 return saved;
400 /* static */
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);
418 if (!aMutedErrors) {
419 RefPtr<Promise> promise =
420 Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
421 aboutToBeNotified.AppendElement(promise);
422 unhandled.InsertOrUpdate(promiseID, std::move(promise));
424 } else {
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
431 // vector later.
432 aboutToBeNotified[i] = nullptr;
433 DebugOnly<bool> isFound = unhandled.Remove(promiseID);
434 MOZ_ASSERT(isFound);
435 return;
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,
452 init);
454 RefPtr<AsyncEventDispatcher> asyncDispatcher =
455 new AsyncEventDispatcher(owner, event.forget());
456 asyncDispatcher->PostDOMEvent();
462 already_AddRefed<Exception> CycleCollectedJSContext::GetPendingException()
463 const {
464 MOZ_ASSERT(mJSContext);
466 nsCOMPtr<Exception> out = mPendingException;
467 return out.forget();
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]);
496 event->Run();
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(
512 localQueue.end() -
513 std::remove_if(localQueue.begin(), localQueue.end(),
514 [aRecursionDepth](PendingIDBTransactionData& data) {
515 if (data.mRecursionDepth != aRecursionDepth) {
516 return false;
520 nsCOMPtr<nsIRunnable> transaction =
521 std::move(data.mTransaction);
522 transaction->Run();
525 return true;
526 }));
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
561 // processing.
562 MaybePokeGC();
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
590 // GC.
591 class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable {
592 public:
593 using mozilla::IdleRunnable::IdleRunnable;
595 public:
596 IdleTimeGCTaskRunnable() : IdleRunnable("IdleTimeGCTask") {}
598 NS_IMETHOD Run() override {
599 CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
600 if (ccrt) {
601 ccrt->RunIdleTimeGCTask();
603 return NS_OK;
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);
640 #else
641 // XXX bug 1261143
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;
647 #endif
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 {
666 public:
667 AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
669 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
670 // bug 1535398.
671 MOZ_CAN_RUN_SCRIPT_BOUNDARY
672 NS_IMETHOD Run() override {
673 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
674 if (ccjs) {
675 ccjs->PerformMicroTaskCheckPoint();
677 return NS_OK;
681 SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext* aContext)
682 : mContext(aContext),
683 mSuppressionGeneration(aContext->mSuppressionGeneration) {}
685 bool SuppressedMicroTasks::Suppressed() {
686 if (mSuppressionGeneration == mContext->mSuppressionGeneration) {
687 return true;
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;
697 return false;
700 bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) {
701 if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
702 AfterProcessMicrotasks();
703 // Nothing to do, return early.
704 return false;
707 uint32_t currentDepth = RecursionDepth();
708 if (mMicroTaskRecursionDepth >= currentDepth && !aForce) {
709 // We are already executing microtasks for the current recursion depth.
710 return false;
713 if (mTargetedMicroTaskRecursionDepth != 0 &&
714 mTargetedMicroTaskRecursionDepth + mDebuggerRecursionDepth !=
715 currentDepth) {
716 return false;
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());
723 return false;
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;
735 for (;;) {
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();
743 } else {
744 break;
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(
758 runnable);
760 } else {
761 if (mPendingMicroTaskRunnables.empty() &&
762 mDebuggerMicroTaskQueue.empty() && !mSuppressedMicroTasks) {
763 JS::JobQueueIsEmpty(Context());
765 didProcess = true;
767 LogMicroTaskRunnable::Run log(runnable.get());
768 runnable->Run(aso);
769 runnable = nullptr;
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();
783 return didProcess;
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;
791 for (;;) {
792 // For a debugger microtask checkpoint, we always use the debugger microtask
793 // queue.
794 std::deque<RefPtr<MicroTaskRunnable>>* microtaskQueue =
795 &GetDebuggerMicroTaskQueue();
797 if (microtaskQueue->empty()) {
798 break;
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());
812 runnable->Run(aso);
813 runnable = nullptr;
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];
825 if (!promise) {
826 continue;
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,
845 init);
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);
857 MOZ_ASSERT(isFound);
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));
865 return NS_OK;
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];
874 if (!promise) {
875 continue;
878 JS::RootedObject promiseObj(cccx->RootingCx(), promise->PromiseObj());
879 cccx->mPendingUnhandledRejections.Remove(JS::GetPromiseID(promiseObj));
881 return NS_OK;
884 #ifdef MOZ_EXECUTION_TRACING
886 void CycleCollectedJSContext::BeginExecutionTracingAsync() {
887 mOwningThread->Dispatch(NS_NewRunnableFunction(
888 "CycleCollectedJSContext::BeginExecutionTracingAsync", [] {
889 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
890 if (ccjs) {
891 JS_TracerBeginTracing(ccjs->Context());
893 }));
896 void CycleCollectedJSContext::EndExecutionTracingAsync() {
897 mOwningThread->Dispatch(NS_NewRunnableFunction(
898 "CycleCollectedJSContext::EndExecutionTracingAsync", [] {
899 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
900 if (ccjs) {
901 JS_TracerEndTracing(ccjs->Context());
903 }));
906 #else
908 void CycleCollectedJSContext::BeginExecutionTracingAsync() {}
909 void CycleCollectedJSContext::EndExecutionTracingAsync() {}
911 #endif
913 class FinalizationRegistryCleanup::CleanupRunnable
914 : public DiscardableRunnable {
915 public:
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
920 // bug 1535398.
921 MOZ_CAN_RUN_SCRIPT_BOUNDARY
922 NS_IMETHOD Run() override {
923 mCleanupWork->DoCleanup();
924 return NS_OK;
927 private:
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().
938 mCallbacks.reset();
941 void FinalizationRegistryCleanup::Init() {
942 JSContext* cx = mContext->Context();
943 mCallbacks.init(cx);
944 JS::SetHostCleanupFinalizationRegistryCallback(cx, QueueCallback, this);
947 /* static */
948 void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
949 JSObject* aHostDefinedData,
950 void* aData) {
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);
966 JS::Value global =
967 JS::GetReservedSlot(aHostDefinedData, INCUMBENT_SETTING_SLOT);
968 incumbentGlobal = &global.toObject();
971 MOZ_ALWAYS_TRUE(mCallbacks.append(Callback{aDoCleanup, incumbentGlobal}));
973 if (firstCallback) {
974 RefPtr<CleanupRunnable> cleanup = new CleanupRunnable(this);
975 NS_DispatchToCurrentThread(cleanup.forget());
979 void FinalizationRegistryCleanup::DoCleanup() {
980 if (mCallbacks.empty()) {
981 return;
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) {
1001 continue;
1004 RefPtr<FinalizationRegistryCleanupCallback> cleanupCallback(
1005 new FinalizationRegistryCleanupCallback(functionObj, globalObj, nullptr,
1006 incumbentGlobal));
1008 nsIGlobalObject* global =
1009 xpc::NativeGlobal(cleanupCallback->CallbackPreserveColor());
1010 if (global) {
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