Global Metrics

path: .metrics.cyclomatic.average
old: 3.628205128205128
new: 2.6666666666666665

path: .metrics.cyclomatic.sum
old: 283.0
new: 48.0

path: .metrics.nargs.sum
old: 131.0
new: 33.0

path: .metrics.nargs.average
old: 2.7872340425531914
new: 1.65

path: .metrics.halstead.vocabulary
old: 429.0
new: 214.0

path: .metrics.halstead.level
old: 0.007592190889370932
new: 0.024255932870066867

path: .metrics.halstead.n2
old: 385.0
new: 185.0

path: .metrics.halstead.N2
old: 2305.0
new: 526.0

path: .metrics.halstead.estimated_program_length
old: 3546.870125919213
new: 1534.1870190542172

path: .metrics.halstead.length
old: 5741.0
new: 1215.0

path: .metrics.halstead.time
old: 367366.4441136529
new: 21543.142635710836

path: .metrics.halstead.bugs
old: 11.743387862441043
new: 1.7725552733416847

path: .metrics.halstead.n1
old: 44.0
new: 29.0

path: .metrics.halstead.purity_ratio
old: 0.6178139916250153
new: 1.262705365476722

path: .metrics.halstead.volume
old: 50204.09106108489
new: 9405.882388477394

path: .metrics.halstead.N1
old: 3436.0
new: 689.0

path: .metrics.halstead.difficulty
old: 131.71428571428572
new: 41.22702702702703

path: .metrics.halstead.effort
old: 6612595.994045753
new: 387776.567442795

path: .metrics.nexits.average
old: 2.3404255319148937
new: 1.25

path: .metrics.nexits.sum
old: 110.0
new: 25.0

path: .metrics.cognitive.average
old: 6.0638297872340425
new: 1.6

path: .metrics.cognitive.sum
old: 285.0
new: 32.0

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

path: .metrics.nom.total
old: 47.0
new: 20.0

path: .metrics.nom.functions
old: 47.0
new: 16.0

path: .metrics.mi.mi_sei
old: -131.92486650131696
new: -19.759432087242445

path: .metrics.mi.mi_original
old: -71.49288192237525
new: 18.003527356008362

path: .metrics.mi.mi_visual_studio
old: 0.0
new: 10.528378570765124

path: .metrics.loc.ploc
old: 1354.0
new: 238.0

path: .metrics.loc.sloc
old: 1766.0
new: 339.0

path: .metrics.loc.cloc
old: 101.0
new: 39.0

path: .metrics.loc.blank
old: 311.0
new: 62.0

path: .metrics.loc.lloc
old: 473.0
new: 98.0

Spaces Data

Minimal test - lines (25, 339)

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

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

path: .spaces[0].metrics.nexits.average
old: 1.0
new: 1.25

path: .spaces[0].metrics.nexits.sum
old: 1.0
new: 25.0

path: .spaces[0].metrics.nom.total
old: 1.0
new: 20.0

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

path: .spaces[0].metrics.nom.functions
old: 1.0
new: 16.0

path: .spaces[0].metrics.mi.mi_sei
old: 107.29917031068469
new: -19.000811429000716

path: .spaces[0].metrics.mi.mi_original
old: 126.77537335305064
new: 19.558554054793404

path: .spaces[0].metrics.mi.mi_visual_studio
old: 74.13764523570212
new: 11.437750909235907

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

path: .spaces[0].metrics.loc.blank
old: 0.0
new: 60.0

path: .spaces[0].metrics.loc.lloc
old: 1.0
new: 98.0

path: .spaces[0].metrics.loc.ploc
old: 4.0
new: 223.0

path: .spaces[0].metrics.loc.sloc
old: 4.0
new: 315.0

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

path: .spaces[0].metrics.cognitive.average
old: 0.0
new: 1.6

path: .spaces[0].metrics.nargs.sum
old: 2.0
new: 33.0

path: .spaces[0].metrics.nargs.average
old: 2.0
new: 1.65

path: .spaces[0].metrics.halstead.bugs
old: 0.013469665311234006
new: 1.807842223793048

path: .spaces[0].metrics.halstead.n2
old: 6.0
new: 170.0

path: .spaces[0].metrics.halstead.N1
old: 10.0
new: 689.0

path: .spaces[0].metrics.halstead.volume
old: 62.907475208398566
new: 9163.949544652378

path: .spaces[0].metrics.halstead.level
old: 0.2448979591836735
new: 0.02294351845603617

path: .spaces[0].metrics.halstead.difficulty
old: 4.083333333333333
new: 43.58529411764706

path: .spaces[0].metrics.halstead.n1
old: 7.0
new: 29.0

path: .spaces[0].metrics.halstead.length
old: 17.0
new: 1200.0

path: .spaces[0].metrics.halstead.time
old: 14.270677246349672
new: 22189.635343497317

path: .spaces[0].metrics.halstead.purity_ratio
old: 2.068309379925304
new: 1.1670649233350907

path: .spaces[0].metrics.halstead.effort
old: 256.8721904342941
new: 399413.4361829518

path: .spaces[0].metrics.halstead.N2
old: 7.0
new: 511.0

path: .spaces[0].metrics.halstead.vocabulary
old: 13.0
new: 199.0

path: .spaces[0].metrics.halstead.estimated_program_length
old: 35.161259458730164
new: 1400.4779080021087

Code

namespace mozilla {

NS_IMPL_ISUPPORTS(FetchPreloader, nsIStreamListener, nsIRequestObserver)

FetchPreloader::FetchPreloader()
    : FetchPreloader(nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD) {}

FetchPreloader::FetchPreloader(nsContentPolicyType aContentPolicyType)
    : mContentPolicyType(aContentPolicyType) {}

nsresult FetchPreloader::OpenChannel(const PreloadHashKey& aKey, nsIURI* aURI,
                                     const CORSMode aCORSMode,
                                     const dom::ReferrerPolicy& aReferrerPolicy,
                                     dom::Document* aDocument) {
  nsresult rv;
  nsCOMPtr channel;

  auto notify = MakeScopeExit([&]() {
    if (NS_FAILED(rv)) {
      // Make sure we notify any  elements when opening fails
      // because of various technical or security reasons.
      NotifyStart(channel);
      // Using the non-channel overload of this method to make it work even
      // before NotifyOpen has been called on this preload.  We are not
      // switching between channels, so it's safe to do so.
      NotifyStop(rv);
    }
  });

  nsCOMPtr loadGroup = aDocument->GetDocumentLoadGroup();
  nsCOMPtr window = aDocument->GetWindow();
  nsCOMPtr prompter;
  if (window) {
    nsIDocShell* docshell = window->GetDocShell();
    prompter = do_QueryInterface(docshell);
  }

  rv = CreateChannel(getter_AddRefs(channel), aURI, aCORSMode, aReferrerPolicy,
                     aDocument, loadGroup, prompter);
  NS_ENSURE_SUCCESS(rv, rv);

  // Doing this now so that we have the channel and tainting set on it properly
  // to notify the proper event (load or error) on the associated preload tags
  // when the CSP check fails.
  rv = CheckContentPolicy(aURI, aDocument);
  if (NS_FAILED(rv)) {
    return rv;
  }

  PrioritizeAsPreload(channel);
  AddLoadBackgroundFlag(channel);

  NotifyOpen(aKey, channel, aDocument, true);

  return mAsyncConsumeResult = rv = channel->AsyncOpen(this);
}

nsresult FetchPreloader::CreateChannel(
    nsIChannel** aChannel, nsIURI* aURI, const CORSMode aCORSMode,
    const dom::ReferrerPolicy& aReferrerPolicy, dom::Document* aDocument,
    nsILoadGroup* aLoadGroup, nsIInterfaceRequestor* aCallbacks) {
  nsresult rv;

  nsSecurityFlags securityFlags =
      aCORSMode == CORS_NONE
          ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
          : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
  if (aCORSMode == CORS_ANONYMOUS) {
    securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
  } else if (aCORSMode == CORS_USE_CREDENTIALS) {
    securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
  }

  nsCOMPtr channel;
  rv = NS_NewChannelWithTriggeringPrincipal(
      getter_AddRefs(channel), aURI, aDocument, aDocument->NodePrincipal(),
      securityFlags, nsIContentPolicy::TYPE_FETCH, nullptr, aLoadGroup,
      aCallbacks);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (nsCOMPtr httpChannel = do_QueryInterface(channel)) {
    nsCOMPtr referrerInfo = new dom::ReferrerInfo(
        aDocument->GetDocumentURIAsReferrer(), aReferrerPolicy);
    rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  if (nsCOMPtr timedChannel = do_QueryInterface(channel)) {
    timedChannel->SetInitiatorType(u"link"_ns);
  }

  channel.forget(aChannel);
  return NS_OK;
}

nsresult FetchPreloader::CheckContentPolicy(nsIURI* aURI,
                                            dom::Document* aDocument) {
  if (!aDocument) {
    return NS_OK;
  }

  nsCOMPtr secCheckLoadInfo = new net::LoadInfo(
      aDocument->NodePrincipal(), aDocument->NodePrincipal(), aDocument,
      nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, mContentPolicyType);

  int16_t shouldLoad = nsIContentPolicy::ACCEPT;
  nsresult rv =
      NS_CheckContentLoadPolicy(aURI, secCheckLoadInfo, ""_ns, &shouldLoad,
                                nsContentUtils::GetContentPolicy());
  if (NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad)) {
    return NS_OK;
  }

  return NS_ERROR_CONTENT_BLOCKED;
}

// PreloaderBase

nsresult FetchPreloader::AsyncConsume(nsIStreamListener* aListener) {
  if (NS_FAILED(mAsyncConsumeResult)) {
    // Already being consumed or failed to open.
    return mAsyncConsumeResult;
  }

  // Prevent duplicate calls.
  mAsyncConsumeResult = NS_ERROR_NOT_AVAILABLE;

  if (!mConsumeListener) {
    // Called before we are getting response from the channel.
    mConsumeListener = aListener;
  } else {
    // Channel already started, push cached calls to this listener.
    // Can't be anything else than the `Cache`, hence a safe static_cast.
    Cache* cache = static_cast(mConsumeListener.get());
    cache->AsyncConsume(aListener);
  }

  return NS_OK;
}

// static
void FetchPreloader::PrioritizeAsPreload(nsIChannel* aChannel) {
  if (nsCOMPtr cos = do_QueryInterface(aChannel)) {
    cos->AddClassFlags(nsIClassOfService::Unblocked);
  }
}

void FetchPreloader::PrioritizeAsPreload() { PrioritizeAsPreload(Channel()); }

// nsIRequestObserver + nsIStreamListener

NS_IMETHODIMP FetchPreloader::OnStartRequest(nsIRequest* request) {
  NotifyStart(request);

  if (!mConsumeListener) {
    // AsyncConsume not called yet.
    mConsumeListener = new Cache();
  }

  return mConsumeListener->OnStartRequest(request);
}

NS_IMETHODIMP FetchPreloader::OnDataAvailable(nsIRequest* request,
                                              nsIInputStream* input,
                                              uint64_t offset, uint32_t count) {
  return mConsumeListener->OnDataAvailable(request, input, offset, count);
}

NS_IMETHODIMP FetchPreloader::OnStopRequest(nsIRequest* request,
                                            nsresult status) {
  mConsumeListener->OnStopRequest(request, status);

  // We want 404 or other types of server responses to be treated as 'error'.
  if (nsCOMPtr httpChannel = do_QueryInterface(request)) {
    uint32_t responseStatus = 0;
    Unused << httpChannel->GetResponseStatus(&responseStatus);
    if (responseStatus / 100 != 2) {
      status = NS_ERROR_FAILURE;
    }
  }

  // Fetch preloader wants to keep the channel around so that consumers like XHR
  // can access it even after the preload is done.
  nsCOMPtr channel = mChannel;
  NotifyStop(request, status);
  mChannel.swap(channel);
  return NS_OK;
}

// FetchPreloader::Cache

NS_IMPL_ISUPPORTS(FetchPreloader::Cache, nsIStreamListener, nsIRequestObserver)

NS_IMETHODIMP FetchPreloader::Cache::OnStartRequest(nsIRequest* request) {
  mRequest = request;

  if (mFinalListener) {
    return mFinalListener->OnStartRequest(mRequest);
  }

  mCalls.AppendElement(Call{VariantIndex<0>{}, StartRequest{}});
  return NS_OK;
}

NS_IMETHODIMP FetchPreloader::Cache::OnDataAvailable(nsIRequest* request,
                                                     nsIInputStream* input,
                                                     uint64_t offset,
                                                     uint32_t count) {
  if (mFinalListener) {
    return mFinalListener->OnDataAvailable(mRequest, input, offset, count);
  }

  DataAvailable data;
  if (!data.mData.SetLength(count, fallible)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  uint32_t read;
  nsresult rv = input->Read(data.mData.BeginWriting(), count, &read);
  if (NS_FAILED(rv)) {
    return rv;
  }

  mCalls.AppendElement(Call{VariantIndex<1>{}, std::move(data)});
  return NS_OK;
}

NS_IMETHODIMP FetchPreloader::Cache::OnStopRequest(nsIRequest* request,
                                                   nsresult status) {
  if (mFinalListener) {
    return mFinalListener->OnStopRequest(mRequest, status);
  }

  mCalls.AppendElement(Call{VariantIndex<2>{}, StopRequest{status}});
  return NS_OK;
}

void FetchPreloader::Cache::AsyncConsume(nsIStreamListener* aListener) {
  // Must dispatch for two reasons:
  // 1. This is called directly from FetchLoader::AsyncConsume, which must
  // behave the same way as nsIChannel::AsyncOpen.
  // 2. In case there are already stream listener events scheduled on the main
  // thread we preserve the order - those will still end up in Cache.

  // * The `Cache` object is fully main thread only for now, doesn't support
  // retargeting, but it can be improved to allow it.

  nsCOMPtr listener(aListener);
  NS_DispatchToMainThread(NewRunnableMethod>(
      "FetchPreloader::Cache::Consume", this, &FetchPreloader::Cache::Consume,
      listener));
}

void FetchPreloader::Cache::Consume(nsCOMPtr aListener) {
  MOZ_ASSERT(!mFinalListener, "Duplicate call");

  mFinalListener = std::move(aListener);

  // Status of the channel read after each call.
  nsresult status = NS_OK;
  nsCOMPtr channel(do_QueryInterface(mRequest));

  RefPtr self(this);
  for (auto& call : mCalls) {
    nsresult rv = call.match(
        [&](const StartRequest& startRequest) mutable {
          return self->mFinalListener->OnStartRequest(self->mRequest);
        },
        [&](const DataAvailable& dataAvailable) mutable {
          if (NS_FAILED(status)) {
            // Channel has been cancelled during this mCalls loop.
            return NS_OK;
          }

          nsCOMPtr input;
          rv = NS_NewCStringInputStream(getter_AddRefs(input),
                                        dataAvailable.mData);
          if (NS_FAILED(rv)) {
            return rv;
          }

          return self->mFinalListener->OnDataAvailable(
              self->mRequest, input, 0, dataAvailable.mData.Length());
        },
        [&](const StopRequest& stopRequest) {
          // First cancellation overrides mStatus in nsHttpChannel.
          nsresult stopStatus =
              NS_FAILED(status) ? status : stopRequest.mStatus;
          self->mFinalListener->OnStopRequest(self->mRequest, stopStatus);
          self->mFinalListener = nullptr;
          self->mRequest = nullptr;
          return NS_OK;
        });

    if (!mRequest) {
      // We are done!
      break;
    }

    bool isCancelled = false;
    Unused << channel->GetCanceled(&isCancelled);
    if (isCancelled) {
      mRequest->GetStatus(&status);
    } else if (NS_FAILED(rv)) {
      status = rv;
      mRequest->Cancel(status);
    }
  }

  mCalls.Clear();
}

}  // namespace mozilla