Exemple #1
0
  let response = (component, err, superres) => {
    k--
    if (err != null) {
      if (mostImportantError == null) {
        mostImportantError = err
      } else {
        if (err.status >= 400 && err.status < 600 && err.status < mostImportantError.status) {
          mostImportantError = err
        }
      }
    } else {
      data[component.component] = superres.body

      if (superres.header['cache-control'] != null) {
        var cache = parseCacheControl(superres.header['cache-control'])

        if (cache != null) {
          if (cache['max-age'] != null && typeof cache['max-age'] === 'number' && (header.cache.maxAge == null || header.cache.maxAge > cache['max-age'])) {
            header.cache.maxAge = cache['max-age']
          }
          if (cache.private === true) {
            header.cache.private = true
          }
          if (cache.public === true) {
            header.cache.public = true
          }
          if (cache['no-cache'] === true) {
            header.cache.noCache = true
          }
          if (cache['no-store'] === true) {
            header.cache.noStore = true
          }
        }
      }

      if (superres.header['set-cookie'] != null) {
        var cookieArray = superres.header['set-cookie']
        header.cookie = header.cookie.concat(superres.header['set-cookie'])
      }
    }

    if (k < 1) {
      if (mostImportantError == null) {
        var cacheValue = generateCacheHeader(header.cache);
        if (cacheValue.length > 0) {
          res.set('Cache-Control', cacheValue)
        }
        if (header.cookie.length > 0) {
          res.set('Set-Cookie', header.cookie)
        }
      }

      cb(mostImportantError, data)
    }
  }
    return artifacts.requestNetworkRecords(devtoolsLogs).then(records => {
      const results = [];
      let queryStringCount = 0;
      let totalWastedBytes = 0;

      for (const record of records) {
        if (!CacheHeaders.isCacheableAsset(record)) continue;

        /** @type {Map<string, string>} */
        const headers = new Map();
        for (const header of record._responseHeaders || []) {
          headers.set(header.name.toLowerCase(), header.value);
        }

        const cacheControl = parseCacheControl(headers.get('cache-control'));
        let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
          headers,
          cacheControl
        );

        // Ignore assets with an explicit no-cache policy
        if (cacheLifetimeInSeconds === 0) continue;
        cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;

        const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
        if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;

        const url = URL.elideDataURI(record._url);
        const totalBytes = record._transferSize || 0;
        const wastedBytes = (1 - cacheHitProbability) * totalBytes;

        totalWastedBytes += wastedBytes;
        if (url.includes('?')) queryStringCount++;

        results.push({
          url,
          cacheControl,
          cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
          cacheHitProbability,
          totalBytes,
          wastedBytes,
        });
      }

      results.sort(
        (a, b) => a.cacheLifetimeMs - b.cacheLifetimeMs || b.totalBytes - a.totalBytes
      );

      const score = Audit.computeLogNormalScore(
        totalWastedBytes,
        context.options.scorePODR,
        context.options.scoreMedian
      );

      const headings = [
        {key: 'url', itemType: 'url', text: 'URL'},
        {key: 'cacheLifetimeMs', itemType: 'ms', text: 'Cache TTL', displayUnit: 'duration'},
        {key: 'totalBytes', itemType: 'bytes', text: 'Size (KB)', displayUnit: 'kb',
          granularity: 1},
      ];

      const summary = {wastedBytes: totalWastedBytes};
      const details = Audit.makeTableDetails(headings, results, summary);

      return {
        score,
        rawValue: totalWastedBytes,
        displayValue: `${results.length} asset${results.length !== 1 ? 's' : ''} found`,
        extendedInfo: {
          value: {
            results,
            queryStringCount,
          },
        },
        details,
      };
    });
    return NetworkRecords.request(devtoolsLogs, context).then(records => {
      const results = [];
      let queryStringCount = 0;
      let totalWastedBytes = 0;

      for (const record of records) {
        if (!CacheHeaders.isCacheableAsset(record)) continue;

        /** @type {Map<string, string>} */
        const headers = new Map();
        for (const header of record.responseHeaders || []) {
          if (headers.has(header.name.toLowerCase())) {
            const previousHeaderValue = headers.get(header.name.toLowerCase());
            headers.set(header.name.toLowerCase(),
              `${previousHeaderValue}, ${header.value}`);
          } else {
            headers.set(header.name.toLowerCase(), header.value);
          }
        }

        const cacheControl = parseCacheControl(headers.get('cache-control'));
        if (this.shouldSkipRecord(headers, cacheControl)) {
          continue;
        }

        // Ignore if cacheLifetimeInSeconds is a nonpositive number.
        let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
          headers, cacheControl);
        if (cacheLifetimeInSeconds !== null &&
          (!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
          continue;
        }
        cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;

        // Ignore assets whose cache lifetime is already high enough
        const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
        if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;

        const url = URL.elideDataURI(record.url);
        const totalBytes = record.transferSize || 0;
        const wastedBytes = (1 - cacheHitProbability) * totalBytes;

        totalWastedBytes += wastedBytes;
        if (url.includes('?')) queryStringCount++;

        // Include cacheControl info (if it exists) per url as a diagnostic.
        /** @type {LH.Audit.Details.Diagnostic|undefined} */
        let diagnostic;
        if (cacheControl) {
          diagnostic = {
            type: 'diagnostic',
            ...cacheControl,
          };
        }

        results.push({
          url,
          diagnostic,
          cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
          cacheHitProbability,
          totalBytes,
          wastedBytes,
        });
      }

      results.sort((a, b) => {
        return a.cacheLifetimeMs - b.cacheLifetimeMs ||
          b.totalBytes - a.totalBytes ||
          a.url.localeCompare(b.url);
      });

      const score = Audit.computeLogNormalScore(
        totalWastedBytes,
        context.options.scorePODR,
        context.options.scoreMedian
      );

      /** @type {LH.Audit.Details.Table['headings']} */
      const headings = [
        {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
        // TODO(i18n): pre-compute localized duration
        {key: 'cacheLifetimeMs', itemType: 'ms', text: str_(i18n.UIStrings.columnCacheTTL),
          displayUnit: 'duration'},
        {key: 'totalBytes', itemType: 'bytes', text: str_(i18n.UIStrings.columnSize),
          displayUnit: 'kb', granularity: 1},
      ];

      const summary = {wastedBytes: totalWastedBytes};
      const details = Audit.makeTableDetails(headings, results, summary);

      return {
        score,
        rawValue: totalWastedBytes,
        displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
        extendedInfo: {
          value: {
            results,
            queryStringCount,
          },
        },
        details,
      };
    });