Global Metrics

path: .metrics.cognitive.average
old: null
new: 1.5

path: .metrics.cognitive.sum
old: 0.0
new: 27.0

path: .metrics.halstead.effort
old: 831.2045094254761
new: 397411.0552276002

path: .metrics.halstead.level
old: 0.21768707482993196
new: 0.024039133473095737

path: .metrics.halstead.purity_ratio
old: 2.0912871113600806
new: 1.1236606717563349

path: .metrics.halstead.time
old: 46.17802830141534
new: 22078.391957088905

path: .metrics.halstead.volume
old: 180.94247824228052
new: 9553.417400300104

path: .metrics.halstead.N2
old: 21.0
new: 530.0

path: .metrics.halstead.n1
old: 7.0
new: 27.0

path: .metrics.halstead.N1
old: 19.0
new: 721.0

path: .metrics.halstead.bugs
old: 0.029468000760988843
new: 1.8017949889327456

path: .metrics.halstead.n2
old: 16.0
new: 172.0

path: .metrics.halstead.vocabulary
old: 23.0
new: 199.0

path: .metrics.halstead.estimated_program_length
old: 83.65148445440323
new: 1405.6995003671746

path: .metrics.halstead.difficulty
old: 4.59375
new: 41.59883720930232

path: .metrics.halstead.length
old: 40.0
new: 1251.0

path: .metrics.nargs.sum
old: 0.0
new: 51.0

path: .metrics.nargs.average
old: null
new: 2.8333333333333335

path: .metrics.cyclomatic.average
old: 1.0
new: 2.5789473684210527

path: .metrics.cyclomatic.sum
old: 2.0
new: 49.0

path: .metrics.mi.mi_original
old: 88.41007067592315
new: 21.25980462138125

path: .metrics.mi.mi_sei
old: 83.98704878844356
new: -20.637218203154976

path: .metrics.mi.mi_visual_studio
old: 51.70179571691412
new: 12.432634281509502

path: .metrics.nexits.average
old: null
new: 1.2222222222222223

path: .metrics.nexits.sum
old: 0.0
new: 22.0

path: .metrics.nom.closures
old: 0.0
new: 1.0

path: .metrics.nom.total
old: 0.0
new: 18.0

path: .metrics.nom.functions
old: 0.0
new: 17.0

path: .metrics.loc.cloc
old: 6.0
new: 18.0

path: .metrics.loc.lloc
old: 0.0
new: 86.0

path: .metrics.loc.ploc
old: 15.0
new: 215.0

path: .metrics.loc.sloc
old: 30.0
new: 272.0

path: .metrics.loc.blank
old: 9.0
new: 39.0

Spaces Data

Minimal test - lines (18, 272)

path: .spaces[0].metrics.halstead.n1
old: 4.0
new: 27.0

path: .spaces[0].metrics.halstead.difficulty
old: 2.25
new: 43.333333333333336

path: .spaces[0].metrics.halstead.n2
old: 8.0
new: 162.0

path: .spaces[0].metrics.halstead.time
old: 6.721804688852168
new: 22592.89944999233

path: .spaces[0].metrics.halstead.vocabulary
old: 12.0
new: 189.0

path: .spaces[0].metrics.halstead.estimated_program_length
old: 32.0
new: 1317.437663025723

path: .spaces[0].metrics.halstead.purity_ratio
old: 2.1333333333333333
new: 1.061593604372057

path: .spaces[0].metrics.halstead.volume
old: 53.77443751081734
new: 9384.74284845835

path: .spaces[0].metrics.halstead.bugs
old: 0.008154255994324386
new: 1.82967966629573

path: .spaces[0].metrics.halstead.N2
old: 9.0
new: 520.0

path: .spaces[0].metrics.halstead.level
old: 0.4444444444444444
new: 0.023076923076923075

path: .spaces[0].metrics.halstead.effort
old: 120.99248439933902
new: 406672.1900998619

path: .spaces[0].metrics.halstead.N1
old: 6.0
new: 721.0

path: .spaces[0].metrics.halstead.length
old: 15.0
new: 1241.0

path: .spaces[0].metrics.mi.mi_visual_studio
old: 70.77341864342087
new: 13.232724980635474

path: .spaces[0].metrics.mi.mi_sei
old: 98.999594644585
new: -21.03323380094112

path: .spaces[0].metrics.mi.mi_original
old: 121.02254588024968
new: 22.627959716886664

path: .spaces[0].metrics.loc.ploc
old: 5.0
new: 205.0

path: .spaces[0].metrics.loc.blank
old: 1.0
new: 37.0

path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 86.0

path: .spaces[0].metrics.loc.sloc
old: 6.0
new: 255.0

path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 13.0

path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 51.0

path: .spaces[0].metrics.nargs.average
old: null
new: 2.8333333333333335

path: .spaces[0].metrics.nexits.average
old: null
new: 1.2222222222222223

path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 22.0

path: .spaces[0].metrics.nom.functions
old: 0.0
new: 17.0

path: .spaces[0].metrics.nom.total
old: 0.0
new: 18.0

path: .spaces[0].metrics.nom.closures
old: 0.0
new: 1.0

path: .spaces[0].metrics.cognitive.average
old: null
new: 1.5

path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 27.0

path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 48.0

path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 2.6666666666666665

Code

namespace mozilla {

PreloadService::PreloadService(dom::Document* aDoc) : mDocument(aDoc) {}
PreloadService::~PreloadService() = default;

bool PreloadService::RegisterPreload(const PreloadHashKey& aKey,
                                     PreloaderBase* aPreload) {
  return mPreloads.WithEntryHandle(aKey, [&](auto&& lookup) {
    if (lookup) {
      lookup.Data() = aPreload;
      return true;
    }
    lookup.Insert(aPreload);
    return false;
  });
}

void PreloadService::DeregisterPreload(const PreloadHashKey& aKey) {
  mPreloads.Remove(aKey);
}

void PreloadService::ClearAllPreloads() { mPreloads.Clear(); }

bool PreloadService::PreloadExists(const PreloadHashKey& aKey) {
  return mPreloads.Contains(aKey);
}

already_AddRefed PreloadService::LookupPreload(
    const PreloadHashKey& aKey) const {
  return mPreloads.Get(aKey);
}

already_AddRefed PreloadService::GetPreloadURI(const nsAString& aURL) {
  nsIURI* base = BaseURIForPreload();
  auto encoding = mDocument->GetDocumentCharacterSet();

  nsCOMPtr uri;
  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, encoding, base);
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  return uri.forget();
}

already_AddRefed PreloadService::PreloadLinkElement(
    dom::HTMLLinkElement* aLinkElement, nsContentPolicyType aPolicyType) {
  if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
    MOZ_ASSERT_UNREACHABLE("Caller should check");
    return nullptr;
  }

  if (!StaticPrefs::network_preload()) {
    return nullptr;
  }

  nsAutoString as, charset, crossOrigin, integrity, referrerPolicy, srcset,
      sizes, type, url;

  nsCOMPtr uri = aLinkElement->GetURI();
  aLinkElement->GetAs(as);
  aLinkElement->GetCharset(charset);
  aLinkElement->GetImageSrcset(srcset);
  aLinkElement->GetImageSizes(sizes);
  aLinkElement->GetHref(url);
  aLinkElement->GetCrossOrigin(crossOrigin);
  aLinkElement->GetIntegrity(integrity);
  aLinkElement->GetReferrerPolicy(referrerPolicy);
  aLinkElement->GetType(type);

  auto result = PreloadOrCoalesce(uri, url, aPolicyType, as, type, charset,
                                  srcset, sizes, integrity, crossOrigin,
                                  referrerPolicy, /* aFromHeader = */ false);

  if (!result.mPreloader) {
    NotifyNodeEvent(aLinkElement, result.mAlreadyComplete);
    return nullptr;
  }

  result.mPreloader->AddLinkPreloadNode(aLinkElement);
  return result.mPreloader.forget();
}

void PreloadService::PreloadLinkHeader(
    nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
    const nsAString& aAs, const nsAString& aType, const nsAString& aIntegrity,
    const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aCORS,
    const nsAString& aReferrerPolicy) {
  if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
    MOZ_ASSERT_UNREACHABLE("Caller should check");
    return;
  }

  if (!StaticPrefs::network_preload()) {
    return;
  }

  PreloadOrCoalesce(aURI, aURL, aPolicyType, aAs, aType, u""_ns, aSrcset,
                    aSizes, aIntegrity, aCORS, aReferrerPolicy,
                    /* aFromHeader = */ true);
}

PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce(
    nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
    const nsAString& aAs, const nsAString& aType, const nsAString& aCharset,
    const nsAString& aSrcset, const nsAString& aSizes,
    const nsAString& aIntegrity, const nsAString& aCORS,
    const nsAString& aReferrerPolicy, bool aFromHeader) {
  if (!aURI) {
    MOZ_ASSERT_UNREACHABLE("Should not pass null nsIURI");
    return {nullptr, false};
  }

  bool isImgSet = false;
  PreloadHashKey preloadKey;
  nsCOMPtr uri = aURI;

  if (aAs.LowerCaseEqualsASCII("script")) {
    preloadKey = PreloadHashKey::CreateAsScript(uri, aCORS, aType);
  } else if (aAs.LowerCaseEqualsASCII("style")) {
    preloadKey = PreloadHashKey::CreateAsStyle(
        uri, mDocument->NodePrincipal(), dom::Element::StringToCORSMode(aCORS),
        css::eAuthorSheetFeatures /* see Loader::LoadSheet */);
  } else if (aAs.LowerCaseEqualsASCII("image")) {
    uri = mDocument->ResolvePreloadImage(BaseURIForPreload(), aURL, aSrcset,
                                         aSizes, &isImgSet);
    if (!uri) {
      return {nullptr, false};
    }

    preloadKey = PreloadHashKey::CreateAsImage(
        uri, mDocument->NodePrincipal(), dom::Element::StringToCORSMode(aCORS));
  } else if (aAs.LowerCaseEqualsASCII("font")) {
    preloadKey = PreloadHashKey::CreateAsFont(
        uri, dom::Element::StringToCORSMode(aCORS));
  } else if (aAs.LowerCaseEqualsASCII("fetch")) {
    preloadKey = PreloadHashKey::CreateAsFetch(
        uri, dom::Element::StringToCORSMode(aCORS));
  } else {
    return {nullptr, false};
  }

  if (RefPtr preload = LookupPreload(preloadKey)) {
    return {std::move(preload), false};
  }

  if (aAs.LowerCaseEqualsASCII("script")) {
    PreloadScript(uri, aType, aCharset, aCORS, aReferrerPolicy, aIntegrity,
                  true /* isInHead - TODO */);
  } else if (aAs.LowerCaseEqualsASCII("style")) {
    auto status = mDocument->PreloadStyle(
        aURI, Encoding::ForLabel(aCharset), aCORS,
        PreloadReferrerPolicy(aReferrerPolicy), aIntegrity,
        aFromHeader ? css::StylePreloadKind::FromLinkRelPreloadHeader
                    : css::StylePreloadKind::FromLinkRelPreloadElement);
    switch (status) {
      case dom::SheetPreloadStatus::AlreadyComplete:
        return {nullptr, /* already_complete = */ true};
      case dom::SheetPreloadStatus::Errored:
      case dom::SheetPreloadStatus::InProgress:
        break;
    }
  } else if (aAs.LowerCaseEqualsASCII("image")) {
    PreloadImage(uri, aCORS, aReferrerPolicy, isImgSet);
  } else if (aAs.LowerCaseEqualsASCII("font")) {
    PreloadFont(uri, aCORS, aReferrerPolicy);
  } else if (aAs.LowerCaseEqualsASCII("fetch")) {
    PreloadFetch(uri, aCORS, aReferrerPolicy);
  }

  return {LookupPreload(preloadKey), false};
}

void PreloadService::PreloadScript(nsIURI* aURI, const nsAString& aType,
                                   const nsAString& aCharset,
                                   const nsAString& aCrossOrigin,
                                   const nsAString& aReferrerPolicy,
                                   const nsAString& aIntegrity,
                                   bool aScriptFromHead) {
  mDocument->ScriptLoader()->PreloadURI(
      aURI, aCharset, aType, aCrossOrigin, aIntegrity, aScriptFromHead, false,
      false, false, true, PreloadReferrerPolicy(aReferrerPolicy));
}

void PreloadService::PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
                                  const nsAString& aImageReferrerPolicy,
                                  bool aIsImgSet) {
  mDocument->PreLoadImage(aURI, aCrossOrigin,
                          PreloadReferrerPolicy(aImageReferrerPolicy),
                          aIsImgSet, true);
}

void PreloadService::PreloadFont(nsIURI* aURI, const nsAString& aCrossOrigin,
                                 const nsAString& aReferrerPolicy) {
  CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
  auto key = PreloadHashKey::CreateAsFont(aURI, cors);

  // * Bug 1618549: Depending on where we decide to do the deduplication, we may
  // want to check if the font is already being preloaded here.

  RefPtr preloader = new FontPreloader();
  dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
  preloader->OpenChannel(key, aURI, cors, referrerPolicy, mDocument);
}

void PreloadService::PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
                                  const nsAString& aReferrerPolicy) {
  CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
  auto key = PreloadHashKey::CreateAsFetch(aURI, cors);

  // * Bug 1618549: Depending on where we decide to do the deduplication, we may
  // want to check if a fetch is already being preloaded here.

  RefPtr preloader = new FetchPreloader();
  dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
  preloader->OpenChannel(key, aURI, cors, referrerPolicy, mDocument);
}

// static
void PreloadService::NotifyNodeEvent(nsINode* aNode, bool aSuccess) {
  if (!aNode->IsInComposedDoc()) {
    return;
  }

  // We don't dispatch synchronously since |node| might be in a DocGroup
  // that we're not allowed to touch. (Our network request happens in the
  // DocGroup of one of the mSources nodes--not necessarily this one).

  RefPtr dispatcher = new AsyncEventDispatcher(
      aNode, aSuccess ? u"load"_ns : u"error"_ns, CanBubble::eNo);

  dispatcher->RequireNodeInDocument();
  dispatcher->PostDOMEvent();
}

dom::ReferrerPolicy PreloadService::PreloadReferrerPolicy(
    const nsAString& aReferrerPolicy) {
  dom::ReferrerPolicy referrerPolicy =
      dom::ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy);
  if (referrerPolicy == dom::ReferrerPolicy::_empty) {
    referrerPolicy = mDocument->GetPreloadReferrerInfo()->ReferrerPolicy();
  }

  return referrerPolicy;
}

nsIURI* PreloadService::BaseURIForPreload() {
  nsIURI* documentURI = mDocument->GetDocumentURI();
  nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
  return (documentURI == documentBaseURI)
             ? (mSpeculationBaseURI ? mSpeculationBaseURI.get() : documentURI)
             : documentBaseURI;
}

}  // namespace mozilla