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