# Global Metrics

path: .metrics.nexits.average
old: 0.4878048780487805
new: 0.625
path: .metrics.nexits.sum
old: 20.0
new: 30.0
path: .metrics.halstead.n1
old: 25.0
new: 36.0
path: .metrics.halstead.effort
old: 378162.4170361553
new: 1379247.0120300471
path: .metrics.halstead.difficulty
old: 53.45744680851064
new: 56.72727272727273
path: .metrics.halstead.volume
old: 7074.08302515395
new: 24313.649250529677
path: .metrics.halstead.length
old: 1026.0
new: 2814.0
path: .metrics.halstead.level
old: 0.018706467661691543
new: 0.017628205128205128
path: .metrics.halstead.time
old: 21009.023168675296
new: 76624.83400166928
path: .metrics.halstead.n2
old: 94.0
new: 363.0
path: .metrics.halstead.N2
old: 402.0
new: 1144.0
path: .metrics.halstead.purity_ratio
old: 0.7136722775848596
new: 1.1631151538537243
path: .metrics.halstead.estimated_program_length
old: 732.227756802066
new: 3273.006042944381
path: .metrics.halstead.N1
old: 624.0
new: 1670.0
path: .metrics.halstead.vocabulary
old: 119.0
new: 399.0
path: .metrics.halstead.bugs
old: 1.7431348619807203
new: 4.130222483102637
path: .metrics.cognitive.average
old: 0.2682926829268293
new: 1.6666666666666667
path: .metrics.cognitive.sum
old: 11.0
new: 80.0
path: .metrics.cyclomatic.sum
old: 62.0
new: 117.0
path: .metrics.cyclomatic.average
old: 1.1923076923076923
new: 2.1666666666666665
path: .metrics.mi.mi_original
old: 16.169699714937437
new: -17.62013810539986
path: .metrics.mi.mi_sei
old: -9.435135225245354
new: -63.81864955874518
path: .metrics.mi.mi_visual_studio
old: 9.455964745577448
new: 0.0
path: .metrics.nargs.average
old: 0.7073170731707317
new: 0.7916666666666666
path: .metrics.nargs.sum
old: 29.0
new: 38.0
path: .metrics.loc.blank
old: 46.0
new: 144.0
path: .metrics.loc.cloc
old: 96.0
new: 100.0
path: .metrics.loc.sloc
old: 341.0
new: 846.0
path: .metrics.loc.lloc
old: 68.0
new: 244.0
path: .metrics.loc.ploc
old: 199.0
new: 602.0
path: .metrics.nom.closures
old: 0.0
new: 1.0
path: .metrics.nom.functions
old: 41.0
new: 47.0
path: .metrics.nom.total
old: 41.0
new: 48.0

# Spaces Data

## Minimal test - lines (56, 846)

path: .spaces[0].metrics.mi.mi_original
old: 17.6021940662407
new: -16.117964318349593
path: .spaces[0].metrics.mi.mi_visual_studio
old: 10.293680740491638
new: 0.0
path: .spaces[0].metrics.mi.mi_sei
old: -7.596460608291643
new: -61.69191024611536
path: .spaces[0].metrics.cyclomatic.average
old: 1.196078431372549
new: 2.188679245283019
path: .spaces[0].metrics.cyclomatic.sum
old: 61.0
new: 116.0
path: .spaces[0].metrics.cognitive.sum
old: 11.0
new: 80.0
path: .spaces[0].metrics.cognitive.average
old: 0.2682926829268293
new: 1.6666666666666667
path: .spaces[0].metrics.loc.sloc
old: 319.0
new: 791.0
path: .spaces[0].metrics.loc.blank
old: 41.0
new: 140.0
path: .spaces[0].metrics.loc.lloc
old: 68.0
new: 244.0
path: .spaces[0].metrics.loc.ploc
old: 189.0
new: 557.0
path: .spaces[0].metrics.loc.cloc
old: 89.0
new: 94.0
path: .spaces[0].metrics.nargs.sum
old: 29.0
new: 38.0
path: .spaces[0].metrics.nargs.average
old: 0.7073170731707317
new: 0.7916666666666666
path: .spaces[0].metrics.nom.total
old: 41.0
new: 48.0
path: .spaces[0].metrics.nom.closures
old: 0.0
new: 1.0
path: .spaces[0].metrics.nom.functions
old: 41.0
new: 47.0
path: .spaces[0].metrics.nexits.average
old: 0.4878048780487805
new: 0.625
path: .spaces[0].metrics.nexits.sum
old: 20.0
new: 30.0
path: .spaces[0].metrics.halstead.bugs
old: 1.7936190399834078
new: 4.268569883759849
path: .spaces[0].metrics.halstead.estimated_program_length
old: 668.7551736487485
new: 2858.9011654181827
path: .spaces[0].metrics.halstead.n1
old: 25.0
new: 36.0
path: .spaces[0].metrics.halstead.difficulty
old: 57.122093023255815
new: 61.73831775700935
path: .spaces[0].metrics.halstead.purity_ratio
old: 0.6575763752691726
new: 1.032840016408303
path: .spaces[0].metrics.halstead.N2
old: 393.0
new: 1101.0
path: .spaces[0].metrics.halstead.vocabulary
old: 111.0
new: 357.0
path: .spaces[0].metrics.halstead.time
old: 21928.285916332985
new: 80506.87532612658
path: .spaces[0].metrics.halstead.length
old: 1017.0
new: 2768.0
path: .spaces[0].metrics.halstead.n2
old: 86.0
new: 321.0
path: .spaces[0].metrics.halstead.level
old: 0.017506361323155216
new: 0.016197396306388133
path: .spaces[0].metrics.halstead.N1
old: 624.0
new: 1667.0
path: .spaces[0].metrics.halstead.effort
old: 394709.1464939937
new: 1449123.7558702785
path: .spaces[0].metrics.halstead.volume
old: 6909.920936078058
new: 23472.031770832546

### Code

    namespace mozilla {

    CycleCollectedJSContext::CycleCollectedJSContext()
        : mRuntime(nullptr),
          mJSContext(nullptr),
          mDoingStableStates(false),
          mTargetedMicroTaskRecursionDepth(0),
          mMicroTaskLevel(0),
          mDebuggerRecursionDepth(0),
          mMicroTaskRecursionDepth(0),
          mFinalizationRegistryCleanup(this) {
      MOZ_COUNT_CTOR(CycleCollectedJSContext);

      nsCOMPtr thread = do_GetCurrentThread();
      mOwningThread = thread.forget().downcast().take();
      MOZ_RELEASE_ASSERT(mOwningThread);
    }

    CycleCollectedJSContext::~CycleCollectedJSContext() {
      MOZ_COUNT_DTOR(CycleCollectedJSContext);
      // If the allocation failed, here we are.
      if (!mJSContext) {
        return;
      }

      JS::SetHostCleanupFinalizationRegistryCallback(mJSContext, nullptr, nullptr);

      JS_SetContextPrivate(mJSContext, nullptr);

      mRuntime->SetContext(nullptr);
      mRuntime->Shutdown(mJSContext);

      // Last chance to process any events.
      CleanupIDBTransactions(mBaseRecursionDepth);
      MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());

      ProcessStableStateQueue();
      MOZ_ASSERT(mStableStateEvents.IsEmpty());

      // Clear mPendingException first, since it might be cycle collected.
      mPendingException = nullptr;

      MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
      MOZ_ASSERT(mPendingMicroTaskRunnables.empty());

      mUncaughtRejections.reset();
      mConsumedRejections.reset();

      mAboutToBeNotifiedRejectedPromises.Clear();
      mPendingUnhandledRejections.Clear();

      mFinalizationRegistryCleanup.Destroy();

      JS_DestroyContext(mJSContext);
      mJSContext = nullptr;

      nsCycleCollector_forgetJSContext();

      mozilla::dom::DestroyScriptSettings();

      mOwningThread->SetScriptObserver(nullptr);
      NS_RELEASE(mOwningThread);

      delete mRuntime;
      mRuntime = nullptr;
    }

    nsresult CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime,
                                                 uint32_t aMaxBytes) {
      MOZ_ASSERT(!mJSContext);

      mozilla::dom::InitScriptSettings();
      mJSContext = JS_NewContext(aMaxBytes, aParentRuntime);
      if (!mJSContext) {
        return NS_ERROR_OUT_OF_MEMORY;
      }

      mRuntime = CreateRuntime(mJSContext);
      mRuntime->SetContext(this);

      mOwningThread->SetScriptObserver(this);
      // The main thread has a base recursion depth of 0, workers of 1.
      mBaseRecursionDepth = RecursionDepth();

      NS_GetCurrentThread()->SetCanInvokeJS(true);

      JS::SetJobQueue(mJSContext, this);
      JS::SetPromiseRejectionTrackerCallback(mJSContext,
                                             PromiseRejectionTrackerCallback, this);
      mUncaughtRejections.init(mJSContext,
                               JS::GCVector(
                                   js::SystemAllocPolicy()));
      mConsumedRejections.init(mJSContext,
                               JS::GCVector(
                                   js::SystemAllocPolicy()));

      mFinalizationRegistryCleanup.Init();

      // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*).
      JS_SetContextPrivate(mJSContext, static_cast(this));

      nsCycleCollector_registerJSContext(this);

      return NS_OK;
    }

    /* static */
    CycleCollectedJSContext* CycleCollectedJSContext::GetFor(JSContext* aCx) {
      // Cast from void* matching JS_SetContextPrivate.
      auto atomCache = static_cast(JS_GetContextPrivate(aCx));
      // Down cast.
      return static_cast(atomCache);
    }

    size_t CycleCollectedJSContext::SizeOfExcludingThis(
        MallocSizeOf aMallocSizeOf) const {
      return 0;
    }

    class PromiseJobRunnable final : public MicroTaskRunnable {
     public:
      PromiseJobRunnable(JS::HandleObject aPromise, JS::HandleObject aCallback,
                         JS::HandleObject aCallbackGlobal,
                         JS::HandleObject aAllocationSite,
                         nsIGlobalObject* aIncumbentGlobal)
          : mCallback(new PromiseJobCallback(aCallback, aCallbackGlobal,
                                             aAllocationSite, aIncumbentGlobal)),
            mPropagateUserInputEventHandling(false) {
        MOZ_ASSERT(js::IsFunctionObject(aCallback));

        if (aPromise) {
          JS::PromiseUserInputEventHandlingState state =
              JS::GetPromiseUserInputEventHandlingState(aPromise);
          mPropagateUserInputEventHandling =
              state ==
              JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
        }
      }

      virtual ~PromiseJobRunnable() = default;

     protected:
      MOZ_CAN_RUN_SCRIPT
      virtual void Run(AutoSlowOperation& aAso) override {
        JSObject* callback = mCallback->CallbackPreserveColor();
        nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
        if (global && !global->IsDying()) {
          // Propagate the user input event handling bit if needed.
          nsCOMPtr win = do_QueryInterface(global);
          RefPtr doc;
          if (win) {
            doc = win->GetExtantDoc();
          }
          AutoHandlingUserInputStatePusher userInpStatePusher(
              mPropagateUserInputEventHandling);

          mCallback->Call("promise callback");
          aAso.CheckForInterrupt();
        }
        // Now that mCallback is no longer needed, clear any pointers it contains to
        // JS GC things. This removes any storebuffer entries associated with those
        // pointers, which can cause problems by taking up memory and by triggering
        // minor GCs. This otherwise would not happen until the next minor GC or
        // cycle collection.
        mCallback->Reset();
      }

      virtual bool Suppressed() override {
        JSObject* callback = mCallback->CallbackPreserveColor();
        nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
        return global && global->IsInSyncOperation();
      }

     private:
      const RefPtr mCallback;
      bool mPropagateUserInputEventHandling;
    };

    JSObject* CycleCollectedJSContext::getIncumbentGlobal(JSContext* aCx) {
      nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
      if (global) {
        return global->GetGlobalJSObject();
      }
      return nullptr;
    }

    bool CycleCollectedJSContext::enqueuePromiseJob(
        JSContext* aCx, JS::HandleObject aPromise, JS::HandleObject aJob,
        JS::HandleObject aAllocationSite, JS::HandleObject aIncumbentGlobal) {
      MOZ_ASSERT(aCx == Context());
      MOZ_ASSERT(Get() == this);

      nsIGlobalObject* global = nullptr;
      if (aIncumbentGlobal) {
        global = xpc::NativeGlobal(aIncumbentGlobal);
      }
      JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
      RefPtr runnable = new PromiseJobRunnable(
          aPromise, aJob, jobGlobal, aAllocationSite, global);
      DispatchToMicroTask(runnable.forget());
      return true;
    }

    // Used only by the SpiderMonkey Debugger API, and even then only via
    // JS::AutoDebuggerJobQueueInterruption, to ensure that the debuggee's queue is
    // not affected; see comments in js/public/Promise.h.
    void CycleCollectedJSContext::runJobs(JSContext* aCx) {
      MOZ_ASSERT(aCx == Context());
      MOZ_ASSERT(Get() == this);
      PerformMicroTaskCheckPoint();
    }

    bool CycleCollectedJSContext::empty() const {
      // This is our override of JS::JobQueue::empty. Since that interface is only
      // concerned with the ordinary microtask queue, not the debugger microtask
      // queue, we only report on the former.
      return mPendingMicroTaskRunnables.empty();
    }

    // Preserve a debuggee's microtask queue while it is interrupted by the
    // debugger. See the comments for JS::AutoDebuggerJobQueueInterruption.
    class CycleCollectedJSContext::SavedMicroTaskQueue
        : public JS::JobQueue::SavedJobQueue {
     public:
      explicit SavedMicroTaskQueue(CycleCollectedJSContext* ccjs) : ccjs(ccjs) {
        ccjs->mDebuggerRecursionDepth++;
        ccjs->mPendingMicroTaskRunnables.swap(mQueue);
      }

      ~SavedMicroTaskQueue() {
        MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.empty());
        MOZ_RELEASE_ASSERT(ccjs->mDebuggerRecursionDepth);
        ccjs->mDebuggerRecursionDepth--;
        ccjs->mPendingMicroTaskRunnables.swap(mQueue);
      }

     private:
      CycleCollectedJSContext* ccjs;
      std::queue> mQueue;
    };

    js::UniquePtr
    CycleCollectedJSContext::saveJobQueue(JSContext* cx) {
      auto saved = js::MakeUnique(this);
      if (!saved) {
        // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor
        // is never called, so mPendingMicroTaskRunnables is still initialized.
        JS_ReportOutOfMemory(cx);
        return nullptr;
      }

      return saved;
    }

    /* static */
    void CycleCollectedJSContext::PromiseRejectionTrackerCallback(
        JSContext* aCx, bool aMutedErrors, JS::HandleObject aPromise,
        JS::PromiseRejectionHandlingState state, void* aData) {
      CycleCollectedJSContext* self = static_cast(aData);

      MOZ_ASSERT(aCx == self->Context());
      MOZ_ASSERT(Get() == self);

      // TODO: Bug 1549351 - Promise rejection event should not be sent for
      // cross-origin scripts

      PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises;
      PromiseHashtable& unhandled = self->mPendingUnhandledRejections;
      uint64_t promiseID = JS::GetPromiseID(aPromise);

      if (state == JS::PromiseRejectionHandlingState::Unhandled) {
        PromiseDebugging::AddUncaughtRejection(aPromise);
        if (!aMutedErrors) {
          RefPtr promise =
              Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
          aboutToBeNotified.AppendElement(promise);
          unhandled.InsertOrUpdate(promiseID, std::move(promise));
        }
      } else {
        PromiseDebugging::AddConsumedRejection(aPromise);
        for (size_t i = 0; i < aboutToBeNotified.Length(); i++) {
          if (aboutToBeNotified[i] &&
              aboutToBeNotified[i]->PromiseObj() == aPromise) {
            // To avoid large amounts of memmoves, we don't shrink the vector
            // here. Instead, we filter out nullptrs when iterating over the
            // vector later.
            aboutToBeNotified[i] = nullptr;
            DebugOnly isFound = unhandled.Remove(promiseID);
            MOZ_ASSERT(isFound);
            return;
          }
        }
        RefPtr promise;
        unhandled.Remove(promiseID, getter_AddRefs(promise));
        if (!promise && !aMutedErrors) {
          nsIGlobalObject* global = xpc::NativeGlobal(aPromise);
          if (nsCOMPtr owner = do_QueryInterface(global)) {
            RootedDictionary init(aCx);
            init.mPromise = Promise::CreateFromExisting(global, aPromise);
            init.mReason = JS::GetPromiseResult(aPromise);

            RefPtr event =
                PromiseRejectionEvent::Constructor(owner, u"rejectionhandled"_ns,
                                                   init);

            RefPtr asyncDispatcher =
                new AsyncEventDispatcher(owner, event);
            asyncDispatcher->PostDOMEvent();
          }
        }
      }
    }

    already_AddRefed CycleCollectedJSContext::GetPendingException()
        const {
      MOZ_ASSERT(mJSContext);

      nsCOMPtr out = mPendingException;
      return out.forget();
    }

    void CycleCollectedJSContext::SetPendingException(Exception* aException) {
      MOZ_ASSERT(mJSContext);
      mPendingException = aException;
    }

    std::queue>&
    CycleCollectedJSContext::GetMicroTaskQueue() {
      MOZ_ASSERT(mJSContext);
      return mPendingMicroTaskRunnables;
    }

    std::queue>&
    CycleCollectedJSContext::GetDebuggerMicroTaskQueue() {
      MOZ_ASSERT(mJSContext);
      return mDebuggerMicroTaskQueue;
    }

    void CycleCollectedJSContext::ProcessStableStateQueue() {
      MOZ_ASSERT(mJSContext);
      MOZ_RELEASE_ASSERT(!mDoingStableStates);
      mDoingStableStates = true;

      // When run, one event can add another event to the mStableStateEvents, as
      // such you can't use iterators here.
      for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
        nsCOMPtr event = std::move(mStableStateEvents[i]);
        event->Run();
      }

      mStableStateEvents.Clear();
      mDoingStableStates = false;
    }

    void CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth) {
      MOZ_ASSERT(mJSContext);
      MOZ_RELEASE_ASSERT(!mDoingStableStates);
      mDoingStableStates = true;

      nsTArray localQueue =
          std::move(mPendingIDBTransactions);

      localQueue.RemoveLastElements(
          localQueue.end() -
          std::remove_if(localQueue.begin(), localQueue.end(),
                         [aRecursionDepth](PendingIDBTransactionData& data) {
                           if (data.mRecursionDepth != aRecursionDepth) {
                             return false;
                           }

                           {
                             nsCOMPtr transaction =
                                 std::move(data.mTransaction);
                             transaction->Run();
                           }

                           return true;
                         }));

      // If mPendingIDBTransactions has events in it now, they were added from
      // something we called, so they belong at the end of the queue.
      localQueue.AppendElements(std::move(mPendingIDBTransactions));
      mPendingIDBTransactions = std::move(localQueue);
      mDoingStableStates = false;
    }

    void CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock) {
      // If ProcessNextEvent was called during a microtask callback, we
      // must process any pending microtasks before blocking in the event loop,
      // otherwise we may deadlock until an event enters the queue later.
      if (aMightBlock && PerformMicroTaskCheckPoint()) {
        // If any microtask was processed, we post a dummy event in order to
        // force the ProcessNextEvent call not to block.  This is required
        // to support nested event loops implemented using a pattern like
        // "while (condition) thread.processNextEvent(true)", in case the
        // condition is triggered here by a Promise "then" callback.
        NS_DispatchToMainThread(new Runnable("BeforeProcessTask"));
      }
    }

    void CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) {
      MOZ_ASSERT(mJSContext);

      // See HTML 6.1.4.2 Processing model

      // Step 4.1: Execute microtasks.
      PerformMicroTaskCheckPoint();

      // Step 4.2 Execute any events that were waiting for a stable state.
      ProcessStableStateQueue();

      // This should be a fast test so that it won't affect the next task
      // processing.
      IsIdleGCTaskNeeded();
    }

    void CycleCollectedJSContext::AfterProcessMicrotasks() {
      MOZ_ASSERT(mJSContext);
      // Notify unhandled promise rejections:
      // https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
      if (mAboutToBeNotifiedRejectedPromises.Length()) {
        RefPtr runnable = new NotifyUnhandledRejections(
            this, std::move(mAboutToBeNotifiedRejectedPromises));
        NS_DispatchToCurrentThread(runnable);
      }
      // Cleanup Indexed Database transactions:
      // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
      CleanupIDBTransactions(RecursionDepth());

      // Clear kept alive objects in JS WeakRef.
      // https://whatpr.org/html/4571/webappapis.html#perform-a-microtask-checkpoint
      //
      // ECMAScript implementations are expected to call ClearKeptObjects when a
      // synchronous sequence of ECMAScript execution completes.
      //
      // https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
      JS::ClearKeptObjects(mJSContext);
    }

    void CycleCollectedJSContext::IsIdleGCTaskNeeded() const {
      class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable {
       public:
        using mozilla::IdleRunnable::IdleRunnable;

       public:
        NS_IMETHOD Run() override {
          CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
          if (ccrt) {
            ccrt->RunIdleTimeGCTask();
          }
          return NS_OK;
        }
      };

      if (Runtime()->IsIdleGCTaskNeeded()) {
        nsCOMPtr gc_task = new IdleTimeGCTaskRunnable();
        NS_DispatchToCurrentThreadQueue(gc_task.forget(), EventQueuePriority::Idle);
        Runtime()->SetPendingIdleGCTask();
      }
    }

    uint32_t CycleCollectedJSContext::RecursionDepth() const {
      // Debugger interruptions are included in the recursion depth so that debugger
      // microtask checkpoints do not run IDB transactions which were initiated
      // before the interruption.
      return mOwningThread->RecursionDepth() + mDebuggerRecursionDepth;
    }

    void CycleCollectedJSContext::RunInStableState(
        already_AddRefed&& aRunnable) {
      MOZ_ASSERT(mJSContext);
      mStableStateEvents.AppendElement(std::move(aRunnable));
    }

    void CycleCollectedJSContext::AddPendingIDBTransaction(
        already_AddRefed&& aTransaction) {
      MOZ_ASSERT(mJSContext);

      PendingIDBTransactionData data;
      data.mTransaction = aTransaction;

      MOZ_ASSERT(mOwningThread);
      data.mRecursionDepth = RecursionDepth();

      // There must be an event running to get here.
    #ifndef MOZ_WIDGET_COCOA
      MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
    #else
      // XXX bug 1261143
      // Recursion depth should be greater than mBaseRecursionDepth,
      // or the runnable will stay in the queue forever.
      if (data.mRecursionDepth <= mBaseRecursionDepth) {
        data.mRecursionDepth = mBaseRecursionDepth + 1;
      }
    #endif

      mPendingIDBTransactions.AppendElement(std::move(data));
    }

    void CycleCollectedJSContext::DispatchToMicroTask(
        already_AddRefed aRunnable) {
      RefPtr runnable(aRunnable);

      MOZ_ASSERT(NS_IsMainThread());
      MOZ_ASSERT(runnable);

      JS::JobQueueMayNotBeEmpty(Context());

      LogMicroTaskRunnable::LogDispatch(runnable.get());
      mPendingMicroTaskRunnables.push(std::move(runnable));
    }

    class AsyncMutationHandler final : public mozilla::Runnable {
     public:
      AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}

      // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.  See
      // bug 1535398.
      MOZ_CAN_RUN_SCRIPT_BOUNDARY
      NS_IMETHOD Run() override {
        CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
        if (ccjs) {
          ccjs->PerformMicroTaskCheckPoint();
        }
        return NS_OK;
      }
    };

    bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) {
      if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
        AfterProcessMicrotasks();
        // Nothing to do, return early.
        return false;
      }

      uint32_t currentDepth = RecursionDepth();
      if (mMicroTaskRecursionDepth >= currentDepth && !aForce) {
        // We are already executing microtasks for the current recursion depth.
        return false;
      }

      if (mTargetedMicroTaskRecursionDepth != 0 &&
          mTargetedMicroTaskRecursionDepth + mDebuggerRecursionDepth !=
              currentDepth) {
        return false;
      }

      if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
        // Special case for main thread where DOM mutations may happen when
        // it is not safe to run scripts.
        nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
        return false;
      }

      mozilla::AutoRestore restore(mMicroTaskRecursionDepth);
      MOZ_ASSERT(aForce ? currentDepth == 0 : currentDepth > 0);
      mMicroTaskRecursionDepth = currentDepth;

      AUTO_PROFILER_TRACING_MARKER("JS", "Perform microtasks", JS);

      bool didProcess = false;
      AutoSlowOperation aso;

      std::queue> suppressed;
      for (;;) {
        RefPtr runnable;
        if (!mDebuggerMicroTaskQueue.empty()) {
          runnable = std::move(mDebuggerMicroTaskQueue.front());
          mDebuggerMicroTaskQueue.pop();
        } else if (!mPendingMicroTaskRunnables.empty()) {
          runnable = std::move(mPendingMicroTaskRunnables.front());
          mPendingMicroTaskRunnables.pop();
        } else {
          break;
        }

        if (runnable->Suppressed()) {
          // Microtasks in worker shall never be suppressed.
          // Otherwise, mPendingMicroTaskRunnables will be replaced later with
          // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
          MOZ_ASSERT(NS_IsMainThread());
          JS::JobQueueMayNotBeEmpty(Context());
          suppressed.push(runnable);
        } else {
          if (mPendingMicroTaskRunnables.empty() &&
              mDebuggerMicroTaskQueue.empty() && suppressed.empty()) {
            JS::JobQueueIsEmpty(Context());
          }
          didProcess = true;

          LogMicroTaskRunnable::Run log(runnable.get());
          runnable->Run(aso);
          runnable = nullptr;
        }
      }

      // Put back the suppressed microtasks so that they will be run later.
      // Note, it is possible that we end up keeping these suppressed tasks around
      // for some time, but no longer than spinning the event loop nestedly
      // (sync XHR, alert, etc.)
      mPendingMicroTaskRunnables.swap(suppressed);

      AfterProcessMicrotasks();

      return didProcess;
    }

    void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() {
      // Don't do normal microtask handling checks here, since whoever is calling
      // this method is supposed to know what they are doing.

      AutoSlowOperation aso;
      for (;;) {
        // For a debugger microtask checkpoint, we always use the debugger microtask
        // queue.
        std::queue>* microtaskQueue =
            &GetDebuggerMicroTaskQueue();

        if (microtaskQueue->empty()) {
          break;
        }

        RefPtr runnable = std::move(microtaskQueue->front());
        MOZ_ASSERT(runnable);

        LogMicroTaskRunnable::Run log(runnable.get());

        // This function can re-enter, so we remove the element before calling.
        microtaskQueue->pop();

        if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
          JS::JobQueueIsEmpty(Context());
        }
        runnable->Run(aso);
        runnable = nullptr;
      }

      AfterProcessMicrotasks();
    }

    NS_IMETHODIMP CycleCollectedJSContext::NotifyUnhandledRejections::Run() {
      for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
        RefPtr& promise = mUnhandledRejections[i];
        if (!promise) {
          continue;
        }

        JS::RootingContext* cx = mCx->RootingCx();
        JS::RootedObject promiseObj(cx, promise->PromiseObj());
        MOZ_ASSERT(JS::IsPromiseObject(promiseObj));

        // Only fire unhandledrejection if the promise is still not handled;
        uint64_t promiseID = JS::GetPromiseID(promiseObj);
        if (!JS::GetPromiseIsHandled(promiseObj)) {
          if (nsCOMPtr target =
                  do_QueryInterface(promise->GetParentObject())) {
            RootedDictionary init(cx);
            init.mPromise = promise;
            init.mReason = JS::GetPromiseResult(promiseObj);
            init.mCancelable = true;

            RefPtr event =
                PromiseRejectionEvent::Constructor(target, u"unhandledrejection"_ns,
                                                   init);
            // We don't use the result of dispatching event here to check whether to
            // report the Promise to console.
            target->DispatchEvent(*event);
          }
        }

        if (!JS::GetPromiseIsHandled(promiseObj)) {
          DebugOnly isFound =
              mCx->mPendingUnhandledRejections.Remove(promiseID);
          MOZ_ASSERT(isFound);
        }

        // If a rejected promise is being handled in "unhandledrejection" event
        // handler, it should be removed from the table in
        // PromiseRejectionTrackerCallback.
        MOZ_ASSERT(!mCx->mPendingUnhandledRejections.Lookup(promiseID));
      }
      return NS_OK;
    }

    nsresult CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() {
      for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
        RefPtr& promise = mUnhandledRejections[i];
        if (!promise) {
          continue;
        }

        JS::RootedObject promiseObj(mCx->RootingCx(), promise->PromiseObj());
        mCx->mPendingUnhandledRejections.Remove(JS::GetPromiseID(promiseObj));
      }
      return NS_OK;
    }

    class FinalizationRegistryCleanup::CleanupRunnable
        : public DiscardableRunnable {
     public:
      explicit CleanupRunnable(FinalizationRegistryCleanup* aCleanupWork)
          : DiscardableRunnable("CleanupRunnable"), mCleanupWork(aCleanupWork) {}

      // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.  See
      // bug 1535398.
      MOZ_CAN_RUN_SCRIPT_BOUNDARY
      NS_IMETHOD Run() override {
        mCleanupWork->DoCleanup();
        return NS_OK;
      }

     private:
      FinalizationRegistryCleanup* mCleanupWork;
    };

    FinalizationRegistryCleanup::FinalizationRegistryCleanup(
        CycleCollectedJSContext* aContext)
        : mContext(aContext) {}

    void FinalizationRegistryCleanup::Destroy() {
      // This must happen before the CycleCollectedJSContext destructor calls
      // JS_DestroyContext().
      mCallbacks.reset();
    }

    void FinalizationRegistryCleanup::Init() {
      JSContext* cx = mContext->Context();
      mCallbacks.init(cx);
      JS::SetHostCleanupFinalizationRegistryCallback(cx, QueueCallback, this);
    }

    /* static */
    void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
                                                    JSObject* aIncumbentGlobal,
                                                    void* aData) {
      FinalizationRegistryCleanup* cleanup =
          static_cast(aData);
      cleanup->QueueCallback(aDoCleanup, aIncumbentGlobal);
    }

    void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
                                                    JSObject* aIncumbentGlobal) {
      bool firstCallback = mCallbacks.empty();

      MOZ_ALWAYS_TRUE(mCallbacks.append(Callback{aDoCleanup, aIncumbentGlobal}));

      if (firstCallback) {
        RefPtr cleanup = new CleanupRunnable(this);
        NS_DispatchToCurrentThread(cleanup.forget());
      }
    }

    void FinalizationRegistryCleanup::DoCleanup() {
      if (mCallbacks.empty()) {
        return;
      }

      JS::RootingContext* cx = mContext->RootingCx();

      JS::Rooted callbacks(cx);
      std::swap(callbacks.get(), mCallbacks.get());

      for (const Callback& callback : callbacks) {
        JS::RootedObject functionObj(
            cx, JS_GetFunctionObject(callback.mCallbackFunction));
        JS::RootedObject globalObj(cx, JS::GetNonCCWObjectGlobal(functionObj));

        nsIGlobalObject* incumbentGlobal =
            xpc::NativeGlobal(callback.mIncumbentGlobal);
        if (!incumbentGlobal) {
          continue;
        }

        RefPtr cleanupCallback(
            new FinalizationRegistryCleanupCallback(functionObj, globalObj, nullptr,
                                                    incumbentGlobal));

        nsIGlobalObject* global =
            xpc::NativeGlobal(cleanupCallback->CallbackPreserveColor());
        if (global) {
          cleanupCallback->Call("FinalizationRegistryCleanup::DoCleanup");
        }
      }
    }

    void FinalizationRegistryCleanup::Callback::trace(JSTracer* trc) {
      JS::UnsafeTraceRoot(trc, &mCallbackFunction, "mCallbackFunction");
      JS::UnsafeTraceRoot(trc, &mIncumbentGlobal, "mIncumbentGlobal");
    }

    }  // namespace mozilla

## Minimal test - lines (58, 72)

path: .spaces[0].spaces[0].metrics.mi.mi_original
old: 17.954319353786886
new: 96.54310340835048
path: .spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 10.49960196127888
new: 56.45795520956168
path: .spaces[0].spaces[0].metrics.mi.mi_sei
old: -7.26031241019988
new: 63.683224386649314
path: .spaces[0].spaces[0].metrics.cyclomatic.average
old: 1.2
new: 1.0
path: .spaces[0].spaces[0].metrics.cyclomatic.sum
old: 60.0
new: 1.0
path: .spaces[0].spaces[0].metrics.nom.total
old: 41.0
new: 1.0
path: .spaces[0].spaces[0].metrics.nom.functions
old: 41.0
new: 1.0
path: .spaces[0].spaces[0].metrics.nargs.average
old: 0.7073170731707317
new: 0.0
path: .spaces[0].spaces[0].metrics.nargs.sum
old: 29.0
new: 0.0
path: .spaces[0].spaces[0].metrics.loc.ploc
old: 187.0
new: 14.0
path: .spaces[0].spaces[0].metrics.loc.lloc
old: 68.0
new: 3.0
path: .spaces[0].spaces[0].metrics.loc.sloc
old: 317.0
new: 15.0
path: .spaces[0].spaces[0].metrics.loc.cloc
old: 88.0
new: 0.0
path: .spaces[0].spaces[0].metrics.loc.blank
old: 42.0
new: 1.0
path: .spaces[0].spaces[0].metrics.nexits.sum
old: 20.0
new: 0.0
path: .spaces[0].spaces[0].metrics.nexits.average
old: 0.4878048780487805
new: 0.0
path: .spaces[0].spaces[0].metrics.halstead.length
old: 1015.0
new: 68.0
path: .spaces[0].spaces[0].metrics.halstead.N2
old: 392.0
new: 30.0
path: .spaces[0].spaces[0].metrics.halstead.N1
old: 623.0
new: 38.0
path: .spaces[0].spaces[0].metrics.halstead.bugs
old: 1.7999152817759714
new: 0.05701638034984962
path: .spaces[0].spaces[0].metrics.halstead.level
old: 0.017346938775510204
new: 0.15333333333333335
path: .spaces[0].spaces[0].metrics.halstead.n1
old: 25.0
new: 10.0
path: .spaces[0].spaces[0].metrics.halstead.volume
old: 6883.08010922753
new: 343.01880011637485
path: .spaces[0].spaces[0].metrics.halstead.effort
old: 396789.32394370466
new: 2237.0791311937487
path: .spaces[0].spaces[0].metrics.halstead.n2
old: 85.0
new: 23.0
path: .spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 0.6511277185380027
new: 2.0185471461497784
path: .spaces[0].spaces[0].metrics.halstead.time
old: 22043.851330205813
new: 124.28217395520826
path: .spaces[0].spaces[0].metrics.halstead.vocabulary
old: 110.0
new: 33.0
path: .spaces[0].spaces[0].metrics.halstead.difficulty
old: 57.64705882352941
new: 6.521739130434782
path: .spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 660.8946343160727
new: 137.26120593818493
path: .spaces[0].spaces[0].metrics.cognitive.average
old: 0.2682926829268293
new: 0.0
path: .spaces[0].spaces[0].metrics.cognitive.sum
old: 11.0
new: 0.0

### Code

    CycleCollectedJSContext::CycleCollectedJSContext()
        : mRuntime(nullptr),
          mJSContext(nullptr),
          mDoingStableStates(false),
          mTargetedMicroTaskRecursionDepth(0),
          mMicroTaskLevel(0),
          mDebuggerRecursionDepth(0),
          mMicroTaskRecursionDepth(0),
          mFinalizationRegistryCleanup(this) {
      MOZ_COUNT_CTOR(CycleCollectedJSContext);

      nsCOMPtr thread = do_GetCurrentThread();
      mOwningThread = thread.forget().downcast().take();
      MOZ_RELEASE_ASSERT(mOwningThread);
    }