# Global Metrics

path: .metrics.cyclomatic.average
old: 1.0
new: 1.605263157894737
path: .metrics.cyclomatic.sum
old: 11.0
new: 61.0
path: .metrics.mi.mi_original
old: 57.150268739155294
new: 13.2847781231809
path: .metrics.mi.mi_visual_studio
old: 33.4212097889797
new: 7.768876095427427
path: .metrics.mi.mi_sei
old: 31.27719695259034
new: -21.03817613960489
path: .metrics.nexits.sum
old: 4.0
new: 30.0
path: .metrics.nexits.average
old: 0.5714285714285714
new: 0.8571428571428571
path: .metrics.nargs.sum
old: 0.0
new: 29.0
path: .metrics.nargs.average
old: 0.0
new: 0.8285714285714286
path: .metrics.cognitive.average
old: 0.0
new: 0.7142857142857143
path: .metrics.cognitive.sum
old: 0.0
new: 25.0
path: .metrics.halstead.estimated_program_length
old: 440.5686351804948
new: 1435.071397551833
path: .metrics.halstead.purity_ratio
old: 1.7764864321794145
new: 1.195892831293194
path: .metrics.halstead.N1
old: 138.0
new: 700.0
path: .metrics.halstead.level
old: 0.0715909090909091
new: 0.02707692307692308
path: .metrics.halstead.length
old: 248.0
new: 1200.0
path: .metrics.halstead.N2
old: 110.0
new: 500.0
path: .metrics.halstead.vocabulary
old: 79.0
new: 202.0
path: .metrics.halstead.difficulty
old: 13.968253968253968
new: 36.93181818181818
path: .metrics.halstead.effort
old: 21837.09699178049
new: 339398.00889468176
path: .metrics.halstead.n2
old: 63.0
new: 176.0
path: .metrics.halstead.bugs
old: 0.2604206072913231
new: 1.6218783331583877
path: .metrics.halstead.n1
old: 16.0
new: 26.0
path: .metrics.halstead.time
old: 1213.1720550989162
new: 18855.44493859343
path: .metrics.halstead.volume
old: 1563.3376255479216
new: 9189.853779302151
path: .metrics.loc.lloc
old: 6.0
new: 94.0
path: .metrics.loc.cloc
old: 9.0
new: 62.0
path: .metrics.loc.blank
old: 26.0
new: 45.0
path: .metrics.loc.ploc
old: 56.0
new: 273.0
path: .metrics.loc.sloc
old: 91.0
new: 380.0
path: .metrics.nom.functions
old: 7.0
new: 34.0
path: .metrics.nom.total
old: 7.0
new: 35.0
path: .metrics.nom.closures
old: 0.0
new: 1.0

# Spaces Data

## Minimal test - lines (23, 380)

path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 30.0
path: .spaces[0].metrics.nexits.average
old: null
new: 0.8571428571428571
path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 1.6216216216216215
path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 60.0
path: .spaces[0].metrics.halstead.length
old: 1.0
new: 1185.0
path: .spaces[0].metrics.halstead.estimated_program_length
old: null
new: 1302.4890500481215
path: .spaces[0].metrics.halstead.N2
old: 1.0
new: 485.0
path: .spaces[0].metrics.halstead.difficulty
old: 0.0
new: 39.161490683229815
path: .spaces[0].metrics.halstead.effort
old: 0.0
new: 350223.9499376769
path: .spaces[0].metrics.halstead.vocabulary
old: 1.0
new: 187.0
path: .spaces[0].metrics.halstead.volume
old: 0.0
new: 8943.069934966848
path: .spaces[0].metrics.halstead.level
old: null
new: 0.025535289452815225
path: .spaces[0].metrics.halstead.purity_ratio
old: null
new: 1.0991468776777398
path: .spaces[0].metrics.halstead.time
old: 0.0
new: 19456.886107648716
path: .spaces[0].metrics.halstead.bugs
old: 0.0
new: 1.6561867525592655
path: .spaces[0].metrics.halstead.N1
old: 0.0
new: 700.0
path: .spaces[0].metrics.halstead.n2
old: 1.0
new: 161.0
path: .spaces[0].metrics.halstead.n1
old: 0.0
new: 26.0
path: .spaces[0].metrics.nom.closures
old: 0.0
new: 1.0
path: .spaces[0].metrics.nom.total
old: 0.0
new: 35.0
path: .spaces[0].metrics.nom.functions
old: 0.0
new: 34.0
path: .spaces[0].metrics.cognitive.average
old: null
new: 0.7142857142857143
path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 25.0
path: .spaces[0].metrics.loc.blank
old: 0.0
new: 46.0
path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 94.0
path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 54.0
path: .spaces[0].metrics.loc.ploc
old: 1.0
new: 258.0
path: .spaces[0].metrics.loc.sloc
old: 1.0
new: 358.0
path: .spaces[0].metrics.nargs.average
old: null
new: 0.8285714285714286
path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 29.0
path: .spaces[0].metrics.mi.mi_original
old: null
new: 14.622467767856506
path: .spaces[0].metrics.mi.mi_sei
old: null
new: -20.19474872024005
path: .spaces[0].metrics.mi.mi_visual_studio
old: null
new: 8.551150741436553

### Code

    namespace mozilla {

    LazyLogModule gMozPromiseLog("MozPromise");
    LazyLogModule gStateWatchingLog("StateWatching");

    StaticRefPtr sMainThread;
    MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS;

    class XPCOMThreadWrapper final : public AbstractThread,
                                     public nsIThreadObserver,
                                     public nsIDirectTaskDispatcher {
     public:
      XPCOMThreadWrapper(nsIThreadInternal* aThread, bool aRequireTailDispatch,
                         bool aOnThread)
          : AbstractThread(aRequireTailDispatch),
            mThread(aThread),
            mDirectTaskDispatcher(do_QueryInterface(aThread)),
            mOnThread(aOnThread) {
        MOZ_DIAGNOSTIC_ASSERT(mThread && mDirectTaskDispatcher);
        MOZ_DIAGNOSTIC_ASSERT(!aOnThread || IsCurrentThreadIn());
        if (aOnThread) {
          MOZ_ASSERT(!sCurrentThreadTLS.get(),
                     "There can only be a single XPCOMThreadWrapper available on a "
                     "thread");
          // Set the default current thread so that GetCurrent() never returns
          // nullptr.
          sCurrentThreadTLS.set(this);
        }
      }

      NS_DECL_ISUPPORTS_INHERITED

      nsresult Dispatch(already_AddRefed aRunnable,
                        DispatchReason aReason = NormalDispatch) override {
        nsCOMPtr r = aRunnable;
        AbstractThread* currentThread;
        if (aReason != TailDispatch && (currentThread = GetCurrent()) &&
            RequiresTailDispatch(currentThread) &&
            currentThread->IsTailDispatcherAvailable()) {
          return currentThread->TailDispatcher().AddTask(this, r.forget());
        }

        // At a certain point during shutdown, we stop processing events from the
        // main thread event queue (this happens long after all _other_ XPCOM
        // threads have been shut down). However, various bits of subsequent
        // teardown logic (the media shutdown blocker and the final shutdown cycle
        // collection) can trigger state watching and state mirroring notifications
        // that result in dispatch to the main thread. This causes shutdown leaks,
        // because the |Runner| wrapper below creates a guaranteed cycle
        // (Thread->EventQueue->Runnable->Thread) until the event is processed. So
        // if we put the event into a queue that will never be processed, we'll wind
        // up with a leak.
        //
        // We opt to just release the runnable in that case. Ordinarily, this
        // approach could cause problems for runnables that are only safe to be
        // released on the target thread (and not the dispatching thread). This is
        // why XPCOM thread dispatch explicitly leaks the runnable when dispatch
        // fails, rather than releasing it. But given that this condition only
        // applies very late in shutdown when only one thread remains operational,
        // that concern is unlikely to apply.
        if (gXPCOMMainThreadEventsAreDoomed) {
          return NS_ERROR_FAILURE;
        }

        RefPtr runner = new Runner(this, r.forget());
        return mThread->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
      }

      // Prevent a GCC warning about the other overload of Dispatch being hidden.
      using AbstractThread::Dispatch;

      bool IsCurrentThreadIn() const override {
        return mThread->IsOnCurrentThread();
      }

      TaskDispatcher& TailDispatcher() override {
        MOZ_ASSERT(IsCurrentThreadIn());
        MOZ_ASSERT(IsTailDispatcherAvailable());
        if (!mTailDispatcher.isSome()) {
          mTailDispatcher.emplace(mDirectTaskDispatcher,
                                  /* aIsTailDispatcher = */ true);
          mThread->AddObserver(this);
        }

        return mTailDispatcher.ref();
      }

      bool IsTailDispatcherAvailable() override {
        // Our tail dispatching implementation relies on nsIThreadObserver
        // callbacks. If we're not doing event processing, it won't work.
        bool inEventLoop =
            static_cast(mThread.get())->RecursionDepth() > 0;
        return inEventLoop;
      }

      bool MightHaveTailTasks() override { return mTailDispatcher.isSome(); }

      nsIEventTarget* AsEventTarget() override { return mThread; }

      //-----------------------------------------------------------------------------
      // nsIThreadObserver
      //-----------------------------------------------------------------------------
      NS_IMETHOD OnDispatchedEvent() override { return NS_OK; }

      NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread,
                                       bool eventWasProcessed) override {
        // This is the primary case.
        MaybeFireTailDispatcher();
        return NS_OK;
      }

      NS_IMETHOD OnProcessNextEvent(nsIThreadInternal* thread,
                                    bool mayWait) override {
        // In general, the tail dispatcher is handled at the end of the current in
        // AfterProcessNextEvent() above. However, if start spinning a nested event
        // loop, it's generally better to fire the tail dispatcher before the first
        // nested event, rather than after it. This check handles that case.
        MaybeFireTailDispatcher();
        return NS_OK;
      }

      //-----------------------------------------------------------------------------
      // nsIDirectTaskDispatcher
      //-----------------------------------------------------------------------------
      // Forward calls to nsIDirectTaskDispatcher to the underlying nsThread object.
      // We can't use the generated NS_FORWARD_NSIDIRECTTASKDISPATCHER macro
      // as already_AddRefed type must be moved.
      NS_IMETHOD DispatchDirectTask(already_AddRefed aEvent) override {
        return mDirectTaskDispatcher->DispatchDirectTask(std::move(aEvent));
      }
      NS_IMETHOD DrainDirectTasks() override {
        return mDirectTaskDispatcher->DrainDirectTasks();
      }
      NS_IMETHOD HaveDirectTasks(bool* aResult) override {
        return mDirectTaskDispatcher->HaveDirectTasks(aResult);
      }

     private:
      const RefPtr mThread;
      const nsCOMPtr mDirectTaskDispatcher;
      Maybe mTailDispatcher;
      const bool mOnThread;

      ~XPCOMThreadWrapper() {
        if (mOnThread) {
          MOZ_DIAGNOSTIC_ASSERT(IsCurrentThreadIn(),
                                "Must be destroyed on the thread it was created");
          sCurrentThreadTLS.set(nullptr);
        }
      }

      void MaybeFireTailDispatcher() {
        if (mTailDispatcher.isSome()) {
          mTailDispatcher.ref().DrainDirectTasks();
          mThread->RemoveObserver(this);
          mTailDispatcher.reset();
        }
      }

      class Runner : public Runnable {
       public:
        explicit Runner(XPCOMThreadWrapper* aThread,
                        already_AddRefed aRunnable)
            : Runnable("XPCOMThreadWrapper::Runner"),
              mThread(aThread),
              mRunnable(aRunnable) {}

        NS_IMETHOD Run() override {
          MOZ_ASSERT(mThread == AbstractThread::GetCurrent());
          MOZ_ASSERT(mThread->IsCurrentThreadIn());
          SerialEventTargetGuard guard(mThread);
          return mRunnable->Run();
        }

    #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
        NS_IMETHOD GetName(nsACString& aName) override {
          aName.AssignLiteral("AbstractThread::Runner");
          if (nsCOMPtr named = do_QueryInterface(mRunnable)) {
            nsAutoCString name;
            named->GetName(name);
            if (!name.IsEmpty()) {
              aName.AppendLiteral(" for ");
              aName.Append(name);
            }
          }
          return NS_OK;
        }
    #endif

       private:
        const RefPtr mThread;
        const RefPtr mRunnable;
      };
    };
    NS_IMPL_ISUPPORTS_INHERITED(XPCOMThreadWrapper, AbstractThread,
                                nsIThreadObserver, nsIDirectTaskDispatcher);

    NS_IMPL_ISUPPORTS(AbstractThread, nsIEventTarget, nsISerialEventTarget)

    NS_IMETHODIMP_(bool)
    AbstractThread::IsOnCurrentThreadInfallible() { return IsCurrentThreadIn(); }

    NS_IMETHODIMP
    AbstractThread::IsOnCurrentThread(bool* aResult) {
      *aResult = IsCurrentThreadIn();
      return NS_OK;
    }

    NS_IMETHODIMP
    AbstractThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
      nsCOMPtr event(aEvent);
      return Dispatch(event.forget(), aFlags);
    }

    NS_IMETHODIMP
    AbstractThread::Dispatch(already_AddRefed aEvent,
                             uint32_t aFlags) {
      return Dispatch(std::move(aEvent), NormalDispatch);
    }

    NS_IMETHODIMP
    AbstractThread::DelayedDispatch(already_AddRefed aEvent,
                                    uint32_t aDelayMs) {
      nsCOMPtr event = aEvent;
      NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);

      RefPtr r =
          new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs);
      nsresult rv = r->Init();
      NS_ENSURE_SUCCESS(rv, rv);

      return Dispatch(r.forget(), NS_DISPATCH_NORMAL);
    }

    nsresult AbstractThread::TailDispatchTasksFor(AbstractThread* aThread) {
      if (MightHaveTailTasks()) {
        return TailDispatcher().DispatchTasksFor(aThread);
      }

      return NS_OK;
    }

    bool AbstractThread::HasTailTasksFor(AbstractThread* aThread) {
      if (!MightHaveTailTasks()) {
        return false;
      }
      return TailDispatcher().HasTasksFor(aThread);
    }

    bool AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const {
      MOZ_ASSERT(aThread);
      // We require tail dispatch if both the source and destination
      // threads support it.
      return SupportsTailDispatch() && aThread->SupportsTailDispatch();
    }

    bool AbstractThread::RequiresTailDispatchFromCurrentThread() const {
      AbstractThread* current = GetCurrent();
      return current && RequiresTailDispatch(current);
    }

    AbstractThread* AbstractThread::MainThread() {
      MOZ_ASSERT(sMainThread);
      return sMainThread;
    }

    void AbstractThread::InitTLS() {
      if (!sCurrentThreadTLS.init()) {
        MOZ_CRASH();
      }
    }

    void AbstractThread::InitMainThread() {
      MOZ_ASSERT(NS_IsMainThread());
      MOZ_ASSERT(!sMainThread);
      nsCOMPtr mainThread =
          do_QueryInterface(nsThreadManager::get().GetMainThreadWeak());
      MOZ_DIAGNOSTIC_ASSERT(mainThread);

      if (!sCurrentThreadTLS.init()) {
        MOZ_CRASH();
      }
      sMainThread = new XPCOMThreadWrapper(mainThread.get(),
                                           /* aRequireTailDispatch = */ true,
                                           true /* onThread */);
    }

    void AbstractThread::ShutdownMainThread() {
      MOZ_ASSERT(NS_IsMainThread());
      sMainThread = nullptr;
    }

    void AbstractThread::DispatchStateChange(
        already_AddRefed aRunnable) {
      AbstractThread* currentThread = GetCurrent();
      MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist");
      if (currentThread->IsTailDispatcherAvailable()) {
        currentThread->TailDispatcher().AddStateChangeTask(this,
                                                           std::move(aRunnable));
      } else {
        // If the tail dispatcher isn't available, we just avoid sending state
        // updates.
        //
        // This happens, specifically (1) During async shutdown (via the media
        // shutdown blocker), and (2) During the final shutdown cycle collection.
        // Both of these trigger changes to various watched and mirrored state.
        nsCOMPtr neverDispatched = aRunnable;
      }
    }

    /* static */
    void AbstractThread::DispatchDirectTask(
        already_AddRefed aRunnable) {
      AbstractThread* currentThread = GetCurrent();
      MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist");
      if (currentThread->IsTailDispatcherAvailable()) {
        currentThread->TailDispatcher().AddDirectTask(std::move(aRunnable));
      } else {
        // If the tail dispatcher isn't available, we post as a regular task.
        currentThread->Dispatch(std::move(aRunnable));
      }
    }

    /* static */
    already_AddRefed AbstractThread::CreateXPCOMThreadWrapper(
        nsIThread* aThread, bool aRequireTailDispatch, bool aOnThread) {
      nsCOMPtr internalThread = do_QueryInterface(aThread);
      MOZ_ASSERT(internalThread, "Need an nsThread for AbstractThread");
      RefPtr wrapper =
          new XPCOMThreadWrapper(internalThread, aRequireTailDispatch, aOnThread);

      bool onCurrentThread = false;
      Unused << aThread->IsOnCurrentThread(&onCurrentThread);

      if (onCurrentThread) {
        if (!aOnThread) {
          MOZ_ASSERT(!sCurrentThreadTLS.get(),
                     "There can only be a single XPCOMThreadWrapper available on a "
                     "thread");
          sCurrentThreadTLS.set(wrapper);
        }
        return wrapper.forget();
      }

      // Set the thread-local sCurrentThreadTLS to point to the wrapper on the
      // target thread. This ensures that sCurrentThreadTLS is as expected by
      // AbstractThread::GetCurrent() on the target thread.
      nsCOMPtr r = NS_NewRunnableFunction(
          "AbstractThread::CreateXPCOMThreadWrapper", [wrapper]() {
            MOZ_ASSERT(!sCurrentThreadTLS.get(),
                       "There can only be a single XPCOMThreadWrapper available on "
                       "a thread");
            sCurrentThreadTLS.set(wrapper);
          });
      aThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
      return wrapper.forget();
    }
    }  // namespace mozilla