# Global Metrics

path: .metrics.nom.total
old: 30.0
new: 27.0
path: .metrics.nom.functions
old: 30.0
new: 27.0
path: .metrics.nargs.sum
old: 73.0
new: 33.0
path: .metrics.nargs.average
old: 2.433333333333333
new: 1.2222222222222223
path: .metrics.mi.mi_visual_studio
old: 0.0
new: 5.772035985856779
path: .metrics.mi.mi_sei
old: -109.88817904784824
new: -30.68651015843564
path: .metrics.mi.mi_original
old: -61.81016344691987
new: 9.870181535815092
path: .metrics.loc.blank
old: 200.0
new: 75.0
path: .metrics.loc.cloc
old: 240.0
new: 37.0
path: .metrics.loc.sloc
old: 1502.0
new: 380.0
path: .metrics.loc.lloc
old: 532.0
new: 112.0
path: .metrics.loc.ploc
old: 1062.0
new: 268.0
path: .metrics.cyclomatic.average
old: 6.7105263157894735
new: 2.466666666666667
path: .metrics.cyclomatic.sum
old: 255.0
new: 74.0
path: .metrics.nexits.average
old: 2.466666666666667
new: 0.7407407407407407
path: .metrics.nexits.sum
old: 74.0
new: 20.0
path: .metrics.halstead.difficulty
old: 133.30701754385964
new: 36.0
path: .metrics.halstead.n2
old: 342.0
new: 203.0
path: .metrics.halstead.N2
old: 2171.0
new: 522.0
path: .metrics.halstead.bugs
old: 10.93386500618351
new: 1.6836836706709506
path: .metrics.halstead.vocabulary
old: 384.0
new: 231.0
path: .metrics.halstead.purity_ratio
old: 0.5982244060581169
new: 1.331235534650554
path: .metrics.halstead.N1
old: 3020.0
new: 748.0
path: .metrics.halstead.time
old: 330042.5533946773
new: 19943.442565196787
path: .metrics.halstead.estimated_program_length
old: 3105.3828918476847
new: 1690.6691290062035
path: .metrics.halstead.n1
old: 42.0
new: 28.0
path: .metrics.halstead.volume
old: 44564.54034124352
new: 9971.721282598392
path: .metrics.halstead.length
old: 5191.0
new: 1270.0
path: .metrics.halstead.level
old: 0.007501480555372772
new: 0.027777777777777776
path: .metrics.halstead.effort
old: 5940765.961104191
new: 358981.96617354214
path: .metrics.cognitive.sum
old: 215.0
new: 51.0
path: .metrics.cognitive.average
old: 7.166666666666667
new: 1.8888888888888888

# Spaces Data

## Minimal test - lines (20, 380)

path: .spaces[0].metrics.nexits.average
old: null
new: 0.7407407407407407
path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 20.0
path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 51.0
path: .spaces[0].metrics.cognitive.average
old: null
new: 1.8888888888888888
path: .spaces[0].metrics.nom.functions
old: 0.0
new: 27.0
path: .spaces[0].metrics.nom.total
old: 0.0
new: 27.0
path: .spaces[0].metrics.nargs.average
old: null
new: 1.2222222222222223
path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 33.0
path: .spaces[0].metrics.halstead.n1
old: 9.0
new: 28.0
path: .spaces[0].metrics.halstead.vocabulary
old: 44.0
new: 221.0
path: .spaces[0].metrics.halstead.volume
old: 584.1591831941909
new: 9781.60561459564
path: .spaces[0].metrics.halstead.purity_ratio
old: 1.9444320617388284
new: 1.2738456576515544
path: .spaces[0].metrics.halstead.difficulty
old: 5.142857142857143
new: 37.067357512953365
path: .spaces[0].metrics.halstead.N2
old: 40.0
new: 511.0
path: .spaces[0].metrics.halstead.N1
old: 67.0
new: 745.0
path: .spaces[0].metrics.halstead.length
old: 107.0
new: 1256.0
path: .spaces[0].metrics.halstead.n2
old: 35.0
new: 193.0
path: .spaces[0].metrics.halstead.level
old: 0.1944444444444444
new: 0.026977914453452614
path: .spaces[0].metrics.halstead.bugs
old: 0.06940155341048619
new: 1.694909818246675
path: .spaces[0].metrics.halstead.effort
old: 3004.247227855839
new: 362578.27236692846
path: .spaces[0].metrics.halstead.estimated_program_length
old: 208.05423060605463
new: 1599.9501460103525
path: .spaces[0].metrics.halstead.time
old: 166.90262376976884
new: 20143.237353718247
path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 2.5172413793103448
path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 73.0
path: .spaces[0].metrics.mi.mi_sei
old: 70.72138813443661
new: -30.42618852483854
path: .spaces[0].metrics.mi.mi_visual_studio
old: 47.660921679805746
new: 6.4510120914123
path: .spaces[0].metrics.mi.mi_original
old: 81.50017607246782
new: 11.031230676315031
path: .spaces[0].metrics.loc.blank
old: 4.0
new: 72.0
path: .spaces[0].metrics.loc.cloc
old: 5.0
new: 31.0
path: .spaces[0].metrics.loc.ploc
old: 23.0
new: 258.0
path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 112.0
path: .spaces[0].metrics.loc.sloc
old: 32.0
new: 361.0

### Code

    namespace mozilla {

    PreloaderBase::UsageTimer::UsageTimer(PreloaderBase* aPreload,
                                          dom::Document* aDocument)
        : mDocument(aDocument), mPreload(aPreload) {}

    class PreloaderBase::RedirectSink final : public nsIInterfaceRequestor,
                                              public nsIChannelEventSink,
                                              public nsIRedirectResultListener {
      RedirectSink() = delete;
      virtual ~RedirectSink();

     public:
      NS_DECL_THREADSAFE_ISUPPORTS
      NS_DECL_NSIINTERFACEREQUESTOR
      NS_DECL_NSICHANNELEVENTSINK
      NS_DECL_NSIREDIRECTRESULTLISTENER

      RedirectSink(PreloaderBase* aPreloader, nsIInterfaceRequestor* aCallbacks);

     private:
      MainThreadWeakPtr mPreloader;
      nsCOMPtr mCallbacks;
      nsCOMPtr mRedirectChannel;
    };

    PreloaderBase::RedirectSink::RedirectSink(PreloaderBase* aPreloader,
                                              nsIInterfaceRequestor* aCallbacks)
        : mPreloader(aPreloader), mCallbacks(aCallbacks) {}

    PreloaderBase::RedirectSink::~RedirectSink() = default;

    NS_IMPL_ISUPPORTS(PreloaderBase::RedirectSink, nsIInterfaceRequestor,
                      nsIChannelEventSink, nsIRedirectResultListener)

    NS_IMETHODIMP PreloaderBase::RedirectSink::AsyncOnChannelRedirect(
        nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
        nsIAsyncVerifyRedirectCallback* aCallback) {
      MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());

      mRedirectChannel = aNewChannel;

      // Deliberately adding this before confirmation.
      nsCOMPtr uri;
      aNewChannel->GetOriginalURI(getter_AddRefs(uri));
      if (mPreloader) {
        mPreloader->mRedirectRecords.AppendElement(
            RedirectRecord(aFlags, uri.forget()));
      }

      if (mCallbacks) {
        nsCOMPtr sink(do_GetInterface(mCallbacks));
        if (sink) {
          return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
                                              aCallback);
        }
      }

      aCallback->OnRedirectVerifyCallback(NS_OK);
      return NS_OK;
    }

    NS_IMETHODIMP PreloaderBase::RedirectSink::OnRedirectResult(bool proceeding) {
      if (proceeding && mRedirectChannel) {
        mPreloader->mChannel = std::move(mRedirectChannel);
      } else {
        mRedirectChannel = nullptr;
      }

      if (mCallbacks) {
        nsCOMPtr sink(do_GetInterface(mCallbacks));
        if (sink) {
          return sink->OnRedirectResult(proceeding);
        }
      }

      return NS_OK;
    }

    NS_IMETHODIMP PreloaderBase::RedirectSink::GetInterface(const nsIID& aIID,
                                                            void** aResult) {
      NS_ENSURE_ARG_POINTER(aResult);

      if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) ||
          aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
        return QueryInterface(aIID, aResult);
      }

      if (mCallbacks) {
        return mCallbacks->GetInterface(aIID, aResult);
      }

      *aResult = nullptr;
      return NS_ERROR_NO_INTERFACE;
    }

    PreloaderBase::~PreloaderBase() { MOZ_ASSERT(NS_IsMainThread()); }

    // static
    void PreloaderBase::AddLoadBackgroundFlag(nsIChannel* aChannel) {
      nsLoadFlags loadFlags;
      aChannel->GetLoadFlags(&loadFlags);
      aChannel->SetLoadFlags(loadFlags | nsIRequest::LOAD_BACKGROUND);
    }

    void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey,
                                   dom::Document* aDocument, bool aIsPreload) {
      if (aDocument) {
        DebugOnly alreadyRegistered =
            aDocument->Preloads().RegisterPreload(aKey, this);
        // This means there is already a preload registered under this key in this
        // document.  We only allow replacement when this is a regular load.
        // Otherwise, this should never happen and is a suspected misuse of the API.
        MOZ_ASSERT_IF(alreadyRegistered, !aIsPreload);
      }

      mKey = aKey;
      mIsUsed = !aIsPreload;

      if (!mIsUsed && !mUsageTimer) {
        auto callback = MakeRefPtr(this, aDocument);
        NS_NewTimerWithCallback(getter_AddRefs(mUsageTimer), callback, 10000,
                                nsITimer::TYPE_ONE_SHOT);
      }

      ReportUsageTelemetry();
    }

    void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey, nsIChannel* aChannel,
                                   dom::Document* aDocument, bool aIsPreload) {
      NotifyOpen(aKey, aDocument, aIsPreload);
      mChannel = aChannel;

      nsCOMPtr callbacks;
      mChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
      RefPtr sink(new RedirectSink(this, callbacks));
      mChannel->SetNotificationCallbacks(sink);
    }

    void PreloaderBase::NotifyUsage(LoadBackground aLoadBackground) {
      if (!mIsUsed && mChannel && aLoadBackground == LoadBackground::Drop) {
        nsLoadFlags loadFlags;
        mChannel->GetLoadFlags(&loadFlags);

        // Preloads are initially set the LOAD_BACKGROUND flag.  When becoming
        // regular loads by hitting its consuming tag, we need to drop that flag,
        // which also means to re-add the request from/to it's loadgroup to reflect
        // that flag change.
        if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
          nsCOMPtr loadGroup;
          mChannel->GetLoadGroup(getter_AddRefs(loadGroup));

          if (loadGroup) {
            nsresult status;
            mChannel->GetStatus(&status);

            nsresult rv = loadGroup->RemoveRequest(mChannel, nullptr, status);
            mChannel->SetLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
            if (NS_SUCCEEDED(rv)) {
              loadGroup->AddRequest(mChannel, nullptr);
            }
          }
        }
      }

      mIsUsed = true;
      ReportUsageTelemetry();
      CancelUsageTimer();
    }

    void PreloaderBase::RemoveSelf(dom::Document* aDocument) {
      if (aDocument) {
        aDocument->Preloads().DeregisterPreload(mKey);
      }
    }

    void PreloaderBase::NotifyRestart(dom::Document* aDocument,
                                      PreloaderBase* aNewPreloader) {
      RemoveSelf(aDocument);
      mKey = PreloadHashKey();

      CancelUsageTimer();

      if (aNewPreloader) {
        aNewPreloader->mNodes = std::move(mNodes);
      }
    }

    void PreloaderBase::NotifyStart(nsIRequest* aRequest) {
      // If there is no channel assigned on this preloader, we are not between
      // channel switching, so we can freely update the mShouldFireLoadEvent using
      // the given channel.
      if (mChannel && !SameCOMIdentity(aRequest, mChannel)) {
        return;
      }

      nsCOMPtr httpChannel = do_QueryInterface(aRequest);
      if (!httpChannel) {
        return;
      }

      // if the load is cross origin without CORS, or the CORS access is rejected,
      // always fire load event to avoid leaking site information.
      nsresult rv;
      nsCOMPtr loadInfo = httpChannel->LoadInfo();
      mShouldFireLoadEvent =
          loadInfo->GetTainting() == LoadTainting::Opaque ||
          (loadInfo->GetTainting() == LoadTainting::CORS &&
           (NS_FAILED(httpChannel->GetStatus(&rv)) || NS_FAILED(rv)));
    }

    void PreloaderBase::NotifyStop(nsIRequest* aRequest, nsresult aStatus) {
      // Filter out notifications that may be arriving from the old channel before
      // restarting this request.
      if (!SameCOMIdentity(aRequest, mChannel)) {
        return;
      }

      NotifyStop(aStatus);
    }

    void PreloaderBase::NotifyStop(nsresult aStatus) {
      mOnStopStatus.emplace(aStatus);

      nsTArray nodes = std::move(mNodes);

      for (nsWeakPtr& weak : nodes) {
        nsCOMPtr node = do_QueryReferent(weak);
        if (node) {
          NotifyNodeEvent(node);
        }
      }

      mChannel = nullptr;
    }

    void PreloaderBase::NotifyValidating() { mOnStopStatus.reset(); }

    void PreloaderBase::NotifyValidated(nsresult aStatus) {
      NotifyStop(nullptr, aStatus);
    }

    void PreloaderBase::AddLinkPreloadNode(nsINode* aNode) {
      if (mOnStopStatus) {
        return NotifyNodeEvent(aNode);
      }

      mNodes.AppendElement(do_GetWeakReference(aNode));
    }

    void PreloaderBase::RemoveLinkPreloadNode(nsINode* aNode) {
      // Note that do_GetWeakReference returns the internal weak proxy, which is
      // always the same, so we can use it to search the array using default
      // comparator.
      nsWeakPtr node = do_GetWeakReference(aNode);
      mNodes.RemoveElement(node);

      if (kCancelAndRemovePreloadOnZeroReferences && mNodes.Length() == 0 &&
          !mIsUsed) {
        // Keep a reference, because the following call may release us.  The caller
        // may use a WeakPtr to access this.
        RefPtr self(this);
        RemoveSelf(aNode->OwnerDoc());

        if (mChannel) {
          mChannel->Cancel(NS_BINDING_ABORTED);
        }
      }
    }

    void PreloaderBase::NotifyNodeEvent(nsINode* aNode) {
      PreloadService::NotifyNodeEvent(
          aNode, mShouldFireLoadEvent || NS_SUCCEEDED(*mOnStopStatus));
    }

    void PreloaderBase::CancelUsageTimer() {
      if (mUsageTimer) {
        mUsageTimer->Cancel();
        mUsageTimer = nullptr;
      }
    }

    void PreloaderBase::ReportUsageTelemetry() {
      if (mUsageTelementryReported) {
        return;
      }
      mUsageTelementryReported = true;

      if (mKey.As() == PreloadHashKey::ResourceType::NONE) {
        return;
      }

      // The labels are structured as type1-used, type1-unused, type2-used, ...
      // The first "as" resource type is NONE with value 0.
      auto index = (static_cast(mKey.As()) - 1) * 2;
      if (!mIsUsed) {
        ++index;
      }

      auto label = static_cast(index);
      Telemetry::AccumulateCategorical(label);
    }

    nsresult PreloaderBase::AsyncConsume(nsIStreamListener* aListener) {
      // We want to return an error so that consumers can't ever use a preload to
      // consume data unless it's properly implemented.
      return NS_ERROR_NOT_IMPLEMENTED;
    }

    // PreloaderBase::RedirectRecord

    nsCString PreloaderBase::RedirectRecord::Spec() const {
      nsCOMPtr noFragment;
      NS_GetURIWithoutRef(mURI, getter_AddRefs(noFragment));
      MOZ_ASSERT(noFragment);
      return noFragment->GetSpecOrDefault();
    }

    nsCString PreloaderBase::RedirectRecord::Fragment() const {
      nsCString fragment;
      mURI->GetRef(fragment);
      return fragment;
    }

    // PreloaderBase::UsageTimer

    NS_IMPL_ISUPPORTS(PreloaderBase::UsageTimer, nsITimerCallback)

    NS_IMETHODIMP PreloaderBase::UsageTimer::Notify(nsITimer* aTimer) {
      if (!mPreload || !mDocument) {
        return NS_OK;
      }

      MOZ_ASSERT(aTimer == mPreload->mUsageTimer);
      mPreload->mUsageTimer = nullptr;

      if (mPreload->IsUsed()) {
        // Left in the hashtable, but marked as used.  This is a valid case, and we
        // don't want to emit a warning for this preload then.
        return NS_OK;
      }

      mPreload->ReportUsageTelemetry();

      // PreloadHashKey overrides GetKey, we need to use the nsURIHashKey one to get
      // the URI.
      nsIURI* uri = static_cast(&mPreload->mKey)->GetKey();
      if (!uri) {
        return NS_OK;
      }

      nsString spec;
      NS_GetSanitizedURIStringFromURI(uri, spec);
      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
                                      mDocument, nsContentUtils::eDOM_PROPERTIES,
                                      "UnusedLinkPreloadPending",
                                      nsTArray({std::move(spec)}));
      return NS_OK;
    }

    }  // namespace mozilla