Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / base / CycleCollectedJSContext.cpp
blobb65acebe2ede67597385bb6a5b5ecf1856019fb4
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 "nsStringBuffer.h"
45 #include "nsThread.h"
46 #include "nsThreadUtils.h"
47 #include "nsWrapperCache.h"
48 #include "xpcpublic.h"
50 using namespace mozilla;
51 using namespace mozilla::dom;
53 namespace mozilla {
55 CycleCollectedJSContext::CycleCollectedJSContext()
56 : mRuntime(nullptr),
57 mJSContext(nullptr),
58 mDoingStableStates(false),
59 mTargetedMicroTaskRecursionDepth(0),
60 mMicroTaskLevel(0),
61 mSuppressionGeneration(0),
62 mDebuggerRecursionDepth(0),
63 mMicroTaskRecursionDepth(0),
64 mFinalizationRegistryCleanup(this) {
65 MOZ_COUNT_CTOR(CycleCollectedJSContext);
67 nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
68 mOwningThread = thread.forget().downcast<nsThread>().take();
69 MOZ_RELEASE_ASSERT(mOwningThread);
72 CycleCollectedJSContext::~CycleCollectedJSContext() {
73 MOZ_COUNT_DTOR(CycleCollectedJSContext);
74 // If the allocation failed, here we are.
75 if (!mJSContext) {
76 return;
79 JS::SetHostCleanupFinalizationRegistryCallback(mJSContext, nullptr, nullptr);
81 JS_SetContextPrivate(mJSContext, nullptr);
83 mRuntime->SetContext(nullptr);
84 mRuntime->Shutdown(mJSContext);
86 // Last chance to process any events.
87 CleanupIDBTransactions(mBaseRecursionDepth);
88 MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
90 ProcessStableStateQueue();
91 MOZ_ASSERT(mStableStateEvents.IsEmpty());
93 // Clear mPendingException first, since it might be cycle collected.
94 mPendingException = nullptr;
96 MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
97 MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
99 mUncaughtRejections.reset();
100 mConsumedRejections.reset();
102 mAboutToBeNotifiedRejectedPromises.Clear();
103 mPendingUnhandledRejections.Clear();
105 mFinalizationRegistryCleanup.Destroy();
107 JS_DestroyContext(mJSContext);
108 mJSContext = nullptr;
110 nsCycleCollector_forgetJSContext();
112 mozilla::dom::DestroyScriptSettings();
114 mOwningThread->SetScriptObserver(nullptr);
115 NS_RELEASE(mOwningThread);
117 delete mRuntime;
118 mRuntime = nullptr;
121 nsresult CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime,
122 uint32_t aMaxBytes) {
123 MOZ_ASSERT(!mJSContext);
125 mozilla::dom::InitScriptSettings();
126 mJSContext = JS_NewContext(aMaxBytes, aParentRuntime);
127 if (!mJSContext) {
128 return NS_ERROR_OUT_OF_MEMORY;
131 mRuntime = CreateRuntime(mJSContext);
132 mRuntime->SetContext(this);
134 mOwningThread->SetScriptObserver(this);
135 // The main thread has a base recursion depth of 0, workers of 1.
136 mBaseRecursionDepth = RecursionDepth();
138 NS_GetCurrentThread()->SetCanInvokeJS(true);
140 JS::SetJobQueue(mJSContext, this);
141 JS::SetPromiseRejectionTrackerCallback(mJSContext,
142 PromiseRejectionTrackerCallback, this);
143 mUncaughtRejections.init(mJSContext,
144 JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(
145 js::SystemAllocPolicy()));
146 mConsumedRejections.init(mJSContext,
147 JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(
148 js::SystemAllocPolicy()));
150 mFinalizationRegistryCleanup.Init();
152 // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*).
153 JS_SetContextPrivate(mJSContext, static_cast<PerThreadAtomCache*>(this));
155 nsCycleCollector_registerJSContext(this);
157 return NS_OK;
160 /* static */
161 CycleCollectedJSContext* CycleCollectedJSContext::GetFor(JSContext* aCx) {
162 // Cast from void* matching JS_SetContextPrivate.
163 auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx));
164 // Down cast.
165 return static_cast<CycleCollectedJSContext*>(atomCache);
168 size_t CycleCollectedJSContext::SizeOfExcludingThis(
169 MallocSizeOf aMallocSizeOf) const {
170 return 0;
173 class PromiseJobRunnable final : public MicroTaskRunnable {
174 public:
175 PromiseJobRunnable(JS::HandleObject aPromise, JS::HandleObject aCallback,
176 JS::HandleObject aCallbackGlobal,
177 JS::HandleObject aAllocationSite,
178 nsIGlobalObject* aIncumbentGlobal)
179 : mCallback(new PromiseJobCallback(aCallback, aCallbackGlobal,
180 aAllocationSite, aIncumbentGlobal)),
181 mPropagateUserInputEventHandling(false) {
182 MOZ_ASSERT(js::IsFunctionObject(aCallback));
184 if (aPromise) {
185 JS::PromiseUserInputEventHandlingState state =
186 JS::GetPromiseUserInputEventHandlingState(aPromise);
187 mPropagateUserInputEventHandling =
188 state ==
189 JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
193 virtual ~PromiseJobRunnable() = default;
195 protected:
196 MOZ_CAN_RUN_SCRIPT
197 virtual void Run(AutoSlowOperation& aAso) override {
198 JSObject* callback = mCallback->CallbackPreserveColor();
199 nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
200 if (global && !global->IsDying()) {
201 // Propagate the user input event handling bit if needed.
202 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
203 RefPtr<Document> doc;
204 if (win) {
205 doc = win->GetExtantDoc();
207 AutoHandlingUserInputStatePusher userInpStatePusher(
208 mPropagateUserInputEventHandling);
210 mCallback->Call("promise callback");
211 aAso.CheckForInterrupt();
213 // Now that mCallback is no longer needed, clear any pointers it contains to
214 // JS GC things. This removes any storebuffer entries associated with those
215 // pointers, which can cause problems by taking up memory and by triggering
216 // minor GCs. This otherwise would not happen until the next minor GC or
217 // cycle collection.
218 mCallback->Reset();
221 virtual bool Suppressed() override {
222 JSObject* callback = mCallback->CallbackPreserveColor();
223 nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
224 return global && global->IsInSyncOperation();
227 private:
228 const RefPtr<PromiseJobCallback> mCallback;
229 bool mPropagateUserInputEventHandling;
232 JSObject* CycleCollectedJSContext::getIncumbentGlobal(JSContext* aCx) {
233 nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
234 if (global) {
235 return global->GetGlobalJSObject();
237 return nullptr;
240 bool CycleCollectedJSContext::enqueuePromiseJob(
241 JSContext* aCx, JS::HandleObject aPromise, JS::HandleObject aJob,
242 JS::HandleObject aAllocationSite, JS::HandleObject aIncumbentGlobal) {
243 MOZ_ASSERT(aCx == Context());
244 MOZ_ASSERT(Get() == this);
246 nsIGlobalObject* global = nullptr;
247 if (aIncumbentGlobal) {
248 global = xpc::NativeGlobal(aIncumbentGlobal);
250 JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
251 RefPtr<PromiseJobRunnable> runnable = new PromiseJobRunnable(
252 aPromise, aJob, jobGlobal, aAllocationSite, global);
253 DispatchToMicroTask(runnable.forget());
254 return true;
257 // Used only by the SpiderMonkey Debugger API, and even then only via
258 // JS::AutoDebuggerJobQueueInterruption, to ensure that the debuggee's queue is
259 // not affected; see comments in js/public/Promise.h.
260 void CycleCollectedJSContext::runJobs(JSContext* aCx) {
261 MOZ_ASSERT(aCx == Context());
262 MOZ_ASSERT(Get() == this);
263 PerformMicroTaskCheckPoint();
266 bool CycleCollectedJSContext::empty() const {
267 // This is our override of JS::JobQueue::empty. Since that interface is only
268 // concerned with the ordinary microtask queue, not the debugger microtask
269 // queue, we only report on the former.
270 return mPendingMicroTaskRunnables.empty();
273 // Preserve a debuggee's microtask queue while it is interrupted by the
274 // debugger. See the comments for JS::AutoDebuggerJobQueueInterruption.
275 class CycleCollectedJSContext::SavedMicroTaskQueue
276 : public JS::JobQueue::SavedJobQueue {
277 public:
278 explicit SavedMicroTaskQueue(CycleCollectedJSContext* ccjs) : ccjs(ccjs) {
279 ccjs->mDebuggerRecursionDepth++;
280 ccjs->mPendingMicroTaskRunnables.swap(mQueue);
283 ~SavedMicroTaskQueue() {
284 // The JS Debugger attempts to maintain the invariant that microtasks which
285 // occur durring debugger operation are completely flushed from the task
286 // queue before returning control to the debuggee, in order to avoid
287 // micro-tasks generated during debugging from interfering with regular
288 // operation.
290 // While the vast majority of microtasks can be reliably flushed,
291 // synchronous operations (see nsAutoSyncOperation) such as printing and
292 // alert diaglogs suppress the execution of some microtasks.
294 // When PerformMicroTaskCheckpoint is run while microtasks are suppressed,
295 // any suppressed microtasks are gathered into a new SuppressedMicroTasks
296 // runnable, which is enqueued on exit from PerformMicroTaskCheckpoint. As a
297 // result, AutoDebuggerJobQueueInterruption::runJobs is not able to
298 // correctly guarantee that the microtask queue is totally empty in the
299 // presence of sync operations.
301 // Previous versions of this code release-asserted that the queue was empty,
302 // causing user observable crashes (Bug 1849675). To avoid this, we instead
303 // choose to move suspended microtasks from the SavedMicroTaskQueue to the
304 // main microtask queue in this destructor. This means that jobs enqueued
305 // during synchnronous events under debugger control may produce events
306 // which run outside the debugger, but this is viewed as strictly
307 // preferrable to crashing.
308 MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.size() <= 1);
309 MOZ_RELEASE_ASSERT(ccjs->mDebuggerRecursionDepth);
310 RefPtr<MicroTaskRunnable> maybeSuppressedTasks;
312 // Handle the case where there is a SuppressedMicroTask still in the queue.
313 if (!ccjs->mPendingMicroTaskRunnables.empty()) {
314 maybeSuppressedTasks = ccjs->mPendingMicroTaskRunnables.front();
315 ccjs->mPendingMicroTaskRunnables.pop_front();
318 MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.empty());
319 ccjs->mDebuggerRecursionDepth--;
320 ccjs->mPendingMicroTaskRunnables.swap(mQueue);
322 // Re-enqueue the suppressed task now that we've put the original microtask
323 // queue back.
324 if (maybeSuppressedTasks) {
325 ccjs->mPendingMicroTaskRunnables.push_back(maybeSuppressedTasks);
329 private:
330 CycleCollectedJSContext* ccjs;
331 std::deque<RefPtr<MicroTaskRunnable>> mQueue;
334 js::UniquePtr<JS::JobQueue::SavedJobQueue>
335 CycleCollectedJSContext::saveJobQueue(JSContext* cx) {
336 auto saved = js::MakeUnique<SavedMicroTaskQueue>(this);
337 if (!saved) {
338 // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor
339 // is never called, so mPendingMicroTaskRunnables is still initialized.
340 JS_ReportOutOfMemory(cx);
341 return nullptr;
344 return saved;
347 /* static */
348 void CycleCollectedJSContext::PromiseRejectionTrackerCallback(
349 JSContext* aCx, bool aMutedErrors, JS::HandleObject aPromise,
350 JS::PromiseRejectionHandlingState state, void* aData) {
351 CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
353 MOZ_ASSERT(aCx == self->Context());
354 MOZ_ASSERT(Get() == self);
356 // TODO: Bug 1549351 - Promise rejection event should not be sent for
357 // cross-origin scripts
359 PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises;
360 PromiseHashtable& unhandled = self->mPendingUnhandledRejections;
361 uint64_t promiseID = JS::GetPromiseID(aPromise);
363 if (state == JS::PromiseRejectionHandlingState::Unhandled) {
364 PromiseDebugging::AddUncaughtRejection(aPromise);
365 if (!aMutedErrors) {
366 RefPtr<Promise> promise =
367 Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
368 aboutToBeNotified.AppendElement(promise);
369 unhandled.InsertOrUpdate(promiseID, std::move(promise));
371 } else {
372 PromiseDebugging::AddConsumedRejection(aPromise);
373 for (size_t i = 0; i < aboutToBeNotified.Length(); i++) {
374 if (aboutToBeNotified[i] &&
375 aboutToBeNotified[i]->PromiseObj() == aPromise) {
376 // To avoid large amounts of memmoves, we don't shrink the vector
377 // here. Instead, we filter out nullptrs when iterating over the
378 // vector later.
379 aboutToBeNotified[i] = nullptr;
380 DebugOnly<bool> isFound = unhandled.Remove(promiseID);
381 MOZ_ASSERT(isFound);
382 return;
385 RefPtr<Promise> promise;
386 unhandled.Remove(promiseID, getter_AddRefs(promise));
387 if (!promise && !aMutedErrors) {
388 nsIGlobalObject* global = xpc::NativeGlobal(aPromise);
389 if (nsCOMPtr<EventTarget> owner = do_QueryInterface(global)) {
390 RootedDictionary<PromiseRejectionEventInit> init(aCx);
391 init.mPromise = Promise::CreateFromExisting(global, aPromise);
392 init.mReason = JS::GetPromiseResult(aPromise);
394 RefPtr<PromiseRejectionEvent> event =
395 PromiseRejectionEvent::Constructor(owner, u"rejectionhandled"_ns,
396 init);
398 RefPtr<AsyncEventDispatcher> asyncDispatcher =
399 new AsyncEventDispatcher(owner, event.forget());
400 asyncDispatcher->PostDOMEvent();
406 already_AddRefed<Exception> CycleCollectedJSContext::GetPendingException()
407 const {
408 MOZ_ASSERT(mJSContext);
410 nsCOMPtr<Exception> out = mPendingException;
411 return out.forget();
414 void CycleCollectedJSContext::SetPendingException(Exception* aException) {
415 MOZ_ASSERT(mJSContext);
416 mPendingException = aException;
419 std::deque<RefPtr<MicroTaskRunnable>>&
420 CycleCollectedJSContext::GetMicroTaskQueue() {
421 MOZ_ASSERT(mJSContext);
422 return mPendingMicroTaskRunnables;
425 std::deque<RefPtr<MicroTaskRunnable>>&
426 CycleCollectedJSContext::GetDebuggerMicroTaskQueue() {
427 MOZ_ASSERT(mJSContext);
428 return mDebuggerMicroTaskQueue;
431 void CycleCollectedJSContext::ProcessStableStateQueue() {
432 MOZ_ASSERT(mJSContext);
433 MOZ_RELEASE_ASSERT(!mDoingStableStates);
434 mDoingStableStates = true;
436 // When run, one event can add another event to the mStableStateEvents, as
437 // such you can't use iterators here.
438 for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
439 nsCOMPtr<nsIRunnable> event = std::move(mStableStateEvents[i]);
440 event->Run();
443 mStableStateEvents.Clear();
444 mDoingStableStates = false;
447 void CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth) {
448 MOZ_ASSERT(mJSContext);
449 MOZ_RELEASE_ASSERT(!mDoingStableStates);
450 mDoingStableStates = true;
452 nsTArray<PendingIDBTransactionData> localQueue =
453 std::move(mPendingIDBTransactions);
455 localQueue.RemoveLastElements(
456 localQueue.end() -
457 std::remove_if(localQueue.begin(), localQueue.end(),
458 [aRecursionDepth](PendingIDBTransactionData& data) {
459 if (data.mRecursionDepth != aRecursionDepth) {
460 return false;
464 nsCOMPtr<nsIRunnable> transaction =
465 std::move(data.mTransaction);
466 transaction->Run();
469 return true;
470 }));
472 // If mPendingIDBTransactions has events in it now, they were added from
473 // something we called, so they belong at the end of the queue.
474 localQueue.AppendElements(std::move(mPendingIDBTransactions));
475 mPendingIDBTransactions = std::move(localQueue);
476 mDoingStableStates = false;
479 void CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock) {
480 // If ProcessNextEvent was called during a microtask callback, we
481 // must process any pending microtasks before blocking in the event loop,
482 // otherwise we may deadlock until an event enters the queue later.
483 if (aMightBlock && PerformMicroTaskCheckPoint()) {
484 // If any microtask was processed, we post a dummy event in order to
485 // force the ProcessNextEvent call not to block. This is required
486 // to support nested event loops implemented using a pattern like
487 // "while (condition) thread.processNextEvent(true)", in case the
488 // condition is triggered here by a Promise "then" callback.
489 NS_DispatchToMainThread(new Runnable("BeforeProcessTask"));
493 void CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) {
494 MOZ_ASSERT(mJSContext);
496 // See HTML 6.1.4.2 Processing model
498 // Step 4.1: Execute microtasks.
499 PerformMicroTaskCheckPoint();
501 // Step 4.2 Execute any events that were waiting for a stable state.
502 ProcessStableStateQueue();
504 // This should be a fast test so that it won't affect the next task
505 // processing.
506 MaybePokeGC();
509 void CycleCollectedJSContext::AfterProcessMicrotasks() {
510 MOZ_ASSERT(mJSContext);
511 // Notify unhandled promise rejections:
512 // https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
513 if (mAboutToBeNotifiedRejectedPromises.Length()) {
514 RefPtr<NotifyUnhandledRejections> runnable = new NotifyUnhandledRejections(
515 std::move(mAboutToBeNotifiedRejectedPromises));
516 NS_DispatchToCurrentThread(runnable);
518 // Cleanup Indexed Database transactions:
519 // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
520 CleanupIDBTransactions(RecursionDepth());
522 // Clear kept alive objects in JS WeakRef.
523 // https://whatpr.org/html/4571/webappapis.html#perform-a-microtask-checkpoint
525 // ECMAScript implementations are expected to call ClearKeptObjects when a
526 // synchronous sequence of ECMAScript execution completes.
528 // https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
529 JS::ClearKeptObjects(mJSContext);
532 void CycleCollectedJSContext::MaybePokeGC() {
533 // Worker-compatible check to see if we want to do an idle-time minor
534 // GC.
535 class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable {
536 public:
537 using mozilla::IdleRunnable::IdleRunnable;
539 public:
540 IdleTimeGCTaskRunnable() : IdleRunnable("IdleTimeGCTask") {}
542 NS_IMETHOD Run() override {
543 CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
544 if (ccrt) {
545 ccrt->RunIdleTimeGCTask();
547 return NS_OK;
551 if (Runtime()->IsIdleGCTaskNeeded()) {
552 nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
553 NS_DispatchToCurrentThreadQueue(gc_task.forget(), EventQueuePriority::Idle);
554 Runtime()->SetPendingIdleGCTask();
558 uint32_t CycleCollectedJSContext::RecursionDepth() const {
559 // Debugger interruptions are included in the recursion depth so that debugger
560 // microtask checkpoints do not run IDB transactions which were initiated
561 // before the interruption.
562 return mOwningThread->RecursionDepth() + mDebuggerRecursionDepth;
565 void CycleCollectedJSContext::RunInStableState(
566 already_AddRefed<nsIRunnable>&& aRunnable) {
567 MOZ_ASSERT(mJSContext);
568 mStableStateEvents.AppendElement(std::move(aRunnable));
571 void CycleCollectedJSContext::AddPendingIDBTransaction(
572 already_AddRefed<nsIRunnable>&& aTransaction) {
573 MOZ_ASSERT(mJSContext);
575 PendingIDBTransactionData data;
576 data.mTransaction = aTransaction;
578 MOZ_ASSERT(mOwningThread);
579 data.mRecursionDepth = RecursionDepth();
581 // There must be an event running to get here.
582 #ifndef MOZ_WIDGET_COCOA
583 MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
584 #else
585 // XXX bug 1261143
586 // Recursion depth should be greater than mBaseRecursionDepth,
587 // or the runnable will stay in the queue forever.
588 if (data.mRecursionDepth <= mBaseRecursionDepth) {
589 data.mRecursionDepth = mBaseRecursionDepth + 1;
591 #endif
593 mPendingIDBTransactions.AppendElement(std::move(data));
596 void CycleCollectedJSContext::DispatchToMicroTask(
597 already_AddRefed<MicroTaskRunnable> aRunnable) {
598 RefPtr<MicroTaskRunnable> runnable(aRunnable);
600 MOZ_ASSERT(NS_IsMainThread());
601 MOZ_ASSERT(runnable);
603 JS::JobQueueMayNotBeEmpty(Context());
605 LogMicroTaskRunnable::LogDispatch(runnable.get());
606 mPendingMicroTaskRunnables.push_back(std::move(runnable));
609 class AsyncMutationHandler final : public mozilla::Runnable {
610 public:
611 AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
613 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
614 // bug 1535398.
615 MOZ_CAN_RUN_SCRIPT_BOUNDARY
616 NS_IMETHOD Run() override {
617 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
618 if (ccjs) {
619 ccjs->PerformMicroTaskCheckPoint();
621 return NS_OK;
625 SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext* aContext)
626 : mContext(aContext),
627 mSuppressionGeneration(aContext->mSuppressionGeneration) {}
629 bool SuppressedMicroTasks::Suppressed() {
630 if (mSuppressionGeneration == mContext->mSuppressionGeneration) {
631 return true;
634 for (std::deque<RefPtr<MicroTaskRunnable>>::reverse_iterator it =
635 mSuppressedMicroTaskRunnables.rbegin();
636 it != mSuppressedMicroTaskRunnables.rend(); ++it) {
637 mContext->GetMicroTaskQueue().push_front(*it);
639 mContext->mSuppressedMicroTasks = nullptr;
641 return false;
644 bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) {
645 if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
646 AfterProcessMicrotasks();
647 // Nothing to do, return early.
648 return false;
651 uint32_t currentDepth = RecursionDepth();
652 if (mMicroTaskRecursionDepth >= currentDepth && !aForce) {
653 // We are already executing microtasks for the current recursion depth.
654 return false;
657 if (mTargetedMicroTaskRecursionDepth != 0 &&
658 mTargetedMicroTaskRecursionDepth + mDebuggerRecursionDepth !=
659 currentDepth) {
660 return false;
663 if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
664 // Special case for main thread where DOM mutations may happen when
665 // it is not safe to run scripts.
666 nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
667 return false;
670 mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
671 MOZ_ASSERT(aForce ? currentDepth == 0 : currentDepth > 0);
672 mMicroTaskRecursionDepth = currentDepth;
674 AUTO_PROFILER_TRACING_MARKER("JS", "Perform microtasks", JS);
676 bool didProcess = false;
677 AutoSlowOperation aso;
679 for (;;) {
680 RefPtr<MicroTaskRunnable> runnable;
681 if (!mDebuggerMicroTaskQueue.empty()) {
682 runnable = std::move(mDebuggerMicroTaskQueue.front());
683 mDebuggerMicroTaskQueue.pop_front();
684 } else if (!mPendingMicroTaskRunnables.empty()) {
685 runnable = std::move(mPendingMicroTaskRunnables.front());
686 mPendingMicroTaskRunnables.pop_front();
687 } else {
688 break;
691 if (runnable->Suppressed()) {
692 // Microtasks in worker shall never be suppressed.
693 // Otherwise, mPendingMicroTaskRunnables will be replaced later with
694 // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
695 MOZ_ASSERT(NS_IsMainThread());
696 JS::JobQueueMayNotBeEmpty(Context());
697 if (runnable != mSuppressedMicroTasks) {
698 if (!mSuppressedMicroTasks) {
699 mSuppressedMicroTasks = new SuppressedMicroTasks(this);
701 mSuppressedMicroTasks->mSuppressedMicroTaskRunnables.push_back(
702 runnable);
704 } else {
705 if (mPendingMicroTaskRunnables.empty() &&
706 mDebuggerMicroTaskQueue.empty() && !mSuppressedMicroTasks) {
707 JS::JobQueueIsEmpty(Context());
709 didProcess = true;
711 LogMicroTaskRunnable::Run log(runnable.get());
712 runnable->Run(aso);
713 runnable = nullptr;
717 // Put back the suppressed microtasks so that they will be run later.
718 // Note, it is possible that we end up keeping these suppressed tasks around
719 // for some time, but no longer than spinning the event loop nestedly
720 // (sync XHR, alert, etc.)
721 if (mSuppressedMicroTasks) {
722 mPendingMicroTaskRunnables.push_back(mSuppressedMicroTasks);
725 AfterProcessMicrotasks();
727 return didProcess;
730 void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() {
731 // Don't do normal microtask handling checks here, since whoever is calling
732 // this method is supposed to know what they are doing.
734 AutoSlowOperation aso;
735 for (;;) {
736 // For a debugger microtask checkpoint, we always use the debugger microtask
737 // queue.
738 std::deque<RefPtr<MicroTaskRunnable>>* microtaskQueue =
739 &GetDebuggerMicroTaskQueue();
741 if (microtaskQueue->empty()) {
742 break;
745 RefPtr<MicroTaskRunnable> runnable = std::move(microtaskQueue->front());
746 MOZ_ASSERT(runnable);
748 LogMicroTaskRunnable::Run log(runnable.get());
750 // This function can re-enter, so we remove the element before calling.
751 microtaskQueue->pop_front();
753 if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
754 JS::JobQueueIsEmpty(Context());
756 runnable->Run(aso);
757 runnable = nullptr;
760 AfterProcessMicrotasks();
763 NS_IMETHODIMP CycleCollectedJSContext::NotifyUnhandledRejections::Run() {
764 for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
765 CycleCollectedJSContext* cccx = CycleCollectedJSContext::Get();
766 NS_ENSURE_STATE(cccx);
768 RefPtr<Promise>& promise = mUnhandledRejections[i];
769 if (!promise) {
770 continue;
773 JS::RootingContext* cx = cccx->RootingCx();
774 JS::RootedObject promiseObj(cx, promise->PromiseObj());
775 MOZ_ASSERT(JS::IsPromiseObject(promiseObj));
777 // Only fire unhandledrejection if the promise is still not handled;
778 uint64_t promiseID = JS::GetPromiseID(promiseObj);
779 if (!JS::GetPromiseIsHandled(promiseObj)) {
780 if (nsCOMPtr<EventTarget> target =
781 do_QueryInterface(promise->GetParentObject())) {
782 RootedDictionary<PromiseRejectionEventInit> init(cx);
783 init.mPromise = promise;
784 init.mReason = JS::GetPromiseResult(promiseObj);
785 init.mCancelable = true;
787 RefPtr<PromiseRejectionEvent> event =
788 PromiseRejectionEvent::Constructor(target, u"unhandledrejection"_ns,
789 init);
790 // We don't use the result of dispatching event here to check whether to
791 // report the Promise to console.
792 target->DispatchEvent(*event);
796 cccx = CycleCollectedJSContext::Get();
797 NS_ENSURE_STATE(cccx);
798 if (!JS::GetPromiseIsHandled(promiseObj)) {
799 DebugOnly<bool> isFound =
800 cccx->mPendingUnhandledRejections.Remove(promiseID);
801 MOZ_ASSERT(isFound);
804 // If a rejected promise is being handled in "unhandledrejection" event
805 // handler, it should be removed from the table in
806 // PromiseRejectionTrackerCallback.
807 MOZ_ASSERT(!cccx->mPendingUnhandledRejections.Lookup(promiseID));
809 return NS_OK;
812 nsresult CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() {
813 CycleCollectedJSContext* cccx = CycleCollectedJSContext::Get();
814 NS_ENSURE_STATE(cccx);
816 for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
817 RefPtr<Promise>& promise = mUnhandledRejections[i];
818 if (!promise) {
819 continue;
822 JS::RootedObject promiseObj(cccx->RootingCx(), promise->PromiseObj());
823 cccx->mPendingUnhandledRejections.Remove(JS::GetPromiseID(promiseObj));
825 return NS_OK;
828 class FinalizationRegistryCleanup::CleanupRunnable
829 : public DiscardableRunnable {
830 public:
831 explicit CleanupRunnable(FinalizationRegistryCleanup* aCleanupWork)
832 : DiscardableRunnable("CleanupRunnable"), mCleanupWork(aCleanupWork) {}
834 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
835 // bug 1535398.
836 MOZ_CAN_RUN_SCRIPT_BOUNDARY
837 NS_IMETHOD Run() override {
838 mCleanupWork->DoCleanup();
839 return NS_OK;
842 private:
843 FinalizationRegistryCleanup* mCleanupWork;
846 FinalizationRegistryCleanup::FinalizationRegistryCleanup(
847 CycleCollectedJSContext* aContext)
848 : mContext(aContext) {}
850 void FinalizationRegistryCleanup::Destroy() {
851 // This must happen before the CycleCollectedJSContext destructor calls
852 // JS_DestroyContext().
853 mCallbacks.reset();
856 void FinalizationRegistryCleanup::Init() {
857 JSContext* cx = mContext->Context();
858 mCallbacks.init(cx);
859 JS::SetHostCleanupFinalizationRegistryCallback(cx, QueueCallback, this);
862 /* static */
863 void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
864 JSObject* aIncumbentGlobal,
865 void* aData) {
866 FinalizationRegistryCleanup* cleanup =
867 static_cast<FinalizationRegistryCleanup*>(aData);
868 cleanup->QueueCallback(aDoCleanup, aIncumbentGlobal);
871 void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
872 JSObject* aIncumbentGlobal) {
873 bool firstCallback = mCallbacks.empty();
875 MOZ_ALWAYS_TRUE(mCallbacks.append(Callback{aDoCleanup, aIncumbentGlobal}));
877 if (firstCallback) {
878 RefPtr<CleanupRunnable> cleanup = new CleanupRunnable(this);
879 NS_DispatchToCurrentThread(cleanup.forget());
883 void FinalizationRegistryCleanup::DoCleanup() {
884 if (mCallbacks.empty()) {
885 return;
888 JS::RootingContext* cx = mContext->RootingCx();
890 JS::Rooted<CallbackVector> callbacks(cx);
891 std::swap(callbacks.get(), mCallbacks.get());
893 for (const Callback& callback : callbacks) {
894 JS::ExposeObjectToActiveJS(
895 JS_GetFunctionObject(callback.mCallbackFunction));
896 JS::ExposeObjectToActiveJS(callback.mIncumbentGlobal);
898 JS::RootedObject functionObj(
899 cx, JS_GetFunctionObject(callback.mCallbackFunction));
900 JS::RootedObject globalObj(cx, JS::GetNonCCWObjectGlobal(functionObj));
902 nsIGlobalObject* incumbentGlobal =
903 xpc::NativeGlobal(callback.mIncumbentGlobal);
904 if (!incumbentGlobal) {
905 continue;
908 RefPtr<FinalizationRegistryCleanupCallback> cleanupCallback(
909 new FinalizationRegistryCleanupCallback(functionObj, globalObj, nullptr,
910 incumbentGlobal));
912 nsIGlobalObject* global =
913 xpc::NativeGlobal(cleanupCallback->CallbackPreserveColor());
914 if (global) {
915 cleanupCallback->Call("FinalizationRegistryCleanup::DoCleanup");
920 void FinalizationRegistryCleanup::Callback::trace(JSTracer* trc) {
921 JS::TraceRoot(trc, &mCallbackFunction, "mCallbackFunction");
922 JS::TraceRoot(trc, &mIncumbentGlobal, "mIncumbentGlobal");
925 } // namespace mozilla