static summarize(networkRecords, URLArtifact, classifiedEntities) {
    /** @type {Record<ResourceType, ResourceEntry>} */
    const resourceSummary = {
      'stylesheet': {count: 0, resourceSize: 0, transferSize: 0},
      'image': {count: 0, resourceSize: 0, transferSize: 0},
      'media': {count: 0, resourceSize: 0, transferSize: 0},
      'font': {count: 0, resourceSize: 0, transferSize: 0},
      'script': {count: 0, resourceSize: 0, transferSize: 0},
      'document': {count: 0, resourceSize: 0, transferSize: 0},
      'other': {count: 0, resourceSize: 0, transferSize: 0},
      'total': {count: 0, resourceSize: 0, transferSize: 0},
      'third-party': {count: 0, resourceSize: 0, transferSize: 0},
    };
    /** @type {ReadonlyArray<string>} */
    const firstPartyHosts = classifiedEntities.firstParty?.domains.map(domain => `*.${domain}`) ||
      [`*.${UrlUtils.getRootDomain(URLArtifact.finalDisplayedUrl)}`];

    networkRecords.filter(record => {
      // Ignore favicon.co
      // Headless Chrome does not request /favicon.ico, so don't consider this request.
      // Makes resource summary consistent across LR / other channels.
      const type = this.determineResourceType(record);
      if (type === 'other' && record.url.endsWith('/favicon.ico')) {
        return false;
      }
      // Ignore non-network protocols
      if (NetworkRequest.isNonNetworkRequest(record)) return false;
      return true;
    }).forEach((record) => {
      const type = this.determineResourceType(record);
      resourceSummary[type].count++;
      resourceSummary[type].resourceSize += record.resourceSize;
      resourceSummary[type].transferSize += record.transferSize;

      resourceSummary.total.count++;
      resourceSummary.total.resourceSize += record.resourceSize;
      resourceSummary.total.transferSize += record.transferSize;

      const isFirstParty = firstPartyHosts.some((hostExp) => {
        const url = new URL(record.url);
        if (hostExp.startsWith('*.')) {
          return url.hostname.endsWith(hostExp.slice(2));
        }
        return url.hostname === hostExp;
      });

      if (!isFirstParty) {
        resourceSummary['third-party'].count++;
        resourceSummary['third-party'].resourceSize += record.resourceSize;
        resourceSummary['third-party'].transferSize += record.transferSize;
      }
    });
    return resourceSummary;
  }
@param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@param {LH.Artifacts.URL} URLArtifact
@param {LH.Artifacts.EntityClassification} classifiedEntities
@return {Record<ResourceType, ResourceEntry>}