lighthouse 9.6.2 → 9.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@
8
8
  const Audit = require('./audit.js');
9
9
  const URL = require('../lib/url-shim.js');
10
10
  const NetworkRecords = require('../computed/network-records.js');
11
+ const MainResource = require('../computed/main-resource.js');
11
12
 
12
13
  class NetworkRequests extends Audit {
13
14
  /**
@@ -19,7 +20,7 @@ class NetworkRequests extends Audit {
19
20
  scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
20
21
  title: 'Network Requests',
21
22
  description: 'Lists the network requests that were made during page load.',
22
- requiredArtifacts: ['devtoolsLogs'],
23
+ requiredArtifacts: ['devtoolsLogs', 'URL', 'GatherContext'],
23
24
  };
24
25
  }
25
26
 
@@ -28,75 +29,91 @@ class NetworkRequests extends Audit {
28
29
  * @param {LH.Audit.Context} context
29
30
  * @return {Promise<LH.Audit.Product>}
30
31
  */
31
- static audit(artifacts, context) {
32
+ static async audit(artifacts, context) {
32
33
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
33
- return NetworkRecords.request(devtoolsLog, context).then(records => {
34
- const earliestStartTime = records.reduce(
35
- (min, record) => Math.min(min, record.startTime),
36
- Infinity
37
- );
34
+ const records = await NetworkRecords.request(devtoolsLog, context);
35
+ const earliestStartTime = records.reduce(
36
+ (min, record) => Math.min(min, record.startTime),
37
+ Infinity
38
+ );
38
39
 
39
- /** @param {number} time */
40
- const timeToMs = time => time < earliestStartTime || !Number.isFinite(time) ?
41
- undefined : (time - earliestStartTime) * 1000;
40
+ // Optional mainFrameId check because the main resource is only available for
41
+ // navigations. TODO: https://github.com/GoogleChrome/lighthouse/issues/14157
42
+ // for the general solution to this.
43
+ /** @type {string|undefined} */
44
+ let mainFrameId;
45
+ if (artifacts.GatherContext.gatherMode === 'navigation') {
46
+ const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
47
+ mainFrameId = mainResource.frameId;
48
+ }
42
49
 
43
- const results = records.map(record => {
44
- const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
45
- const TCPMs = record.lrStatistics?.TCPMs;
46
- const requestMs = record.lrStatistics?.requestMs;
47
- const responseMs = record.lrStatistics?.responseMs;
50
+ /** @param {number} time */
51
+ const timeToMs = time => time < earliestStartTime || !Number.isFinite(time) ?
52
+ undefined : (time - earliestStartTime) * 1000;
48
53
 
49
- return {
50
- url: URL.elideDataURI(record.url),
51
- protocol: record.protocol,
52
- startTime: timeToMs(record.startTime),
53
- endTime: timeToMs(record.endTime),
54
- finished: record.finished,
55
- transferSize: record.transferSize,
56
- resourceSize: record.resourceSize,
57
- statusCode: record.statusCode,
58
- mimeType: record.mimeType,
59
- resourceType: record.resourceType,
60
- lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
61
- lrTCPMs: TCPMs, // Only exists on Lightrider runs
62
- lrRequestMs: requestMs, // Only exists on Lightrider runs
63
- lrResponseMs: responseMs, // Only exists on Lightrider runs
64
- };
65
- });
66
-
67
- // NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
68
- /** @type {LH.Audit.Details.Table['headings']} */
69
- const headings = [
70
- {key: 'url', itemType: 'url', text: 'URL'},
71
- {key: 'protocol', itemType: 'text', text: 'Protocol'},
72
- {key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
73
- {key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
74
- {
75
- key: 'transferSize',
76
- itemType: 'bytes',
77
- displayUnit: 'kb',
78
- granularity: 1,
79
- text: 'Transfer Size',
80
- },
81
- {
82
- key: 'resourceSize',
83
- itemType: 'bytes',
84
- displayUnit: 'kb',
85
- granularity: 1,
86
- text: 'Resource Size',
87
- },
88
- {key: 'statusCode', itemType: 'text', text: 'Status Code'},
89
- {key: 'mimeType', itemType: 'text', text: 'MIME Type'},
90
- {key: 'resourceType', itemType: 'text', text: 'Resource Type'},
91
- ];
92
-
93
- const tableDetails = Audit.makeTableDetails(headings, results);
54
+ const results = records.map(record => {
55
+ const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
56
+ const TCPMs = record.lrStatistics?.TCPMs;
57
+ const requestMs = record.lrStatistics?.requestMs;
58
+ const responseMs = record.lrStatistics?.responseMs;
59
+ // Default these to undefined so omitted from JSON in the negative case.
60
+ const isLinkPreload = record.isLinkPreload || undefined;
61
+ const experimentalFromMainFrame = mainFrameId ?
62
+ ((record.frameId === mainFrameId) || undefined) :
63
+ undefined;
94
64
 
95
65
  return {
96
- score: 1,
97
- details: tableDetails,
66
+ url: URL.elideDataURI(record.url),
67
+ protocol: record.protocol,
68
+ startTime: timeToMs(record.startTime),
69
+ endTime: timeToMs(record.endTime),
70
+ finished: record.finished,
71
+ transferSize: record.transferSize,
72
+ resourceSize: record.resourceSize,
73
+ statusCode: record.statusCode,
74
+ mimeType: record.mimeType,
75
+ resourceType: record.resourceType,
76
+ isLinkPreload,
77
+ experimentalFromMainFrame,
78
+ lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
79
+ lrTCPMs: TCPMs, // Only exists on Lightrider runs
80
+ lrRequestMs: requestMs, // Only exists on Lightrider runs
81
+ lrResponseMs: responseMs, // Only exists on Lightrider runs
98
82
  };
99
83
  });
84
+
85
+ // NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
86
+ /** @type {LH.Audit.Details.Table['headings']} */
87
+ const headings = [
88
+ {key: 'url', itemType: 'url', text: 'URL'},
89
+ {key: 'protocol', itemType: 'text', text: 'Protocol'},
90
+ {key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
91
+ {key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
92
+ {
93
+ key: 'transferSize',
94
+ itemType: 'bytes',
95
+ displayUnit: 'kb',
96
+ granularity: 1,
97
+ text: 'Transfer Size',
98
+ },
99
+ {
100
+ key: 'resourceSize',
101
+ itemType: 'bytes',
102
+ displayUnit: 'kb',
103
+ granularity: 1,
104
+ text: 'Resource Size',
105
+ },
106
+ {key: 'statusCode', itemType: 'text', text: 'Status Code'},
107
+ {key: 'mimeType', itemType: 'text', text: 'MIME Type'},
108
+ {key: 'resourceType', itemType: 'text', text: 'Resource Type'},
109
+ ];
110
+
111
+ const tableDetails = Audit.makeTableDetails(headings, results);
112
+
113
+ return {
114
+ score: 1,
115
+ details: tableDetails,
116
+ };
100
117
  }
101
118
  }
102
119
 
@@ -23,6 +23,10 @@ const UIStrings = {
23
23
 
24
24
  const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
25
25
 
26
+ /**
27
+ * @typedef {Array<{url: string, initiatorType: string}>} InitiatorPath
28
+ */
29
+
26
30
  class PreloadLCPImageAudit extends Audit {
27
31
  /**
28
32
  * @return {LH.Audit.Meta}
@@ -86,22 +90,33 @@ class PreloadLCPImageAudit extends Audit {
86
90
  * @param {LH.Gatherer.Simulation.GraphNode} graph
87
91
  * @param {LH.Artifacts.TraceElement|undefined} lcpElement
88
92
  * @param {Array<LH.Artifacts.ImageElement>} imageElements
89
- * @return {LH.Gatherer.Simulation.GraphNetworkNode|undefined}
93
+ * @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
90
94
  */
91
95
  static getLCPNodeToPreload(mainResource, graph, lcpElement, imageElements) {
92
- if (!lcpElement) return undefined;
96
+ if (!lcpElement) return {};
93
97
 
94
98
  const lcpImageElement = imageElements.find(elem => {
95
99
  return elem.node.devtoolsNodePath === lcpElement.node.devtoolsNodePath;
96
100
  });
97
101
 
98
- if (!lcpImageElement) return undefined;
102
+ if (!lcpImageElement) return {};
99
103
  const lcpUrl = lcpImageElement.src;
100
104
  const {lcpNode, path} = PreloadLCPImageAudit.findLCPNode(graph, lcpUrl);
101
- if (!lcpNode || !path) return undefined;
105
+ if (!lcpNode || !path) return {};
106
+
102
107
  // eslint-disable-next-line max-len
103
108
  const shouldPreload = PreloadLCPImageAudit.shouldPreloadRequest(lcpNode.record, mainResource, path);
104
- return shouldPreload ? lcpNode : undefined;
109
+ const lcpNodeToPreload = shouldPreload ? lcpNode : undefined;
110
+
111
+ const initiatorPath = [
112
+ {url: lcpNode.record.url, initiatorType: lcpNode.initiatorType},
113
+ ...path.map(n => ({url: n.record.url, initiatorType: n.initiatorType})),
114
+ ];
115
+
116
+ return {
117
+ lcpNodeToPreload,
118
+ initiatorPath,
119
+ };
105
120
  }
106
121
 
107
122
  /**
@@ -215,10 +230,10 @@ class PreloadLCPImageAudit extends Audit {
215
230
 
216
231
  const graph = lanternLCP.pessimisticGraph;
217
232
  // eslint-disable-next-line max-len
218
- const lcpNode = PreloadLCPImageAudit.getLCPNodeToPreload(mainResource, graph, lcpElement, artifacts.ImageElements);
233
+ const {lcpNodeToPreload, initiatorPath} = PreloadLCPImageAudit.getLCPNodeToPreload(mainResource, graph, lcpElement, artifacts.ImageElements);
219
234
 
220
235
  const {results, wastedMs} =
221
- PreloadLCPImageAudit.computeWasteWithGraph(lcpElement, lcpNode, graph, simulator);
236
+ PreloadLCPImageAudit.computeWasteWithGraph(lcpElement, lcpNodeToPreload, graph, simulator);
222
237
 
223
238
  /** @type {LH.Audit.Details.Opportunity['headings']} */
224
239
  const headings = [
@@ -228,6 +243,17 @@ class PreloadLCPImageAudit extends Audit {
228
243
  ];
229
244
  const details = Audit.makeOpportunityDetails(headings, results, wastedMs);
230
245
 
246
+ // If LCP element was an image and had valid network records (regardless of
247
+ // if it should be preloaded), it will be found first in the `initiatorPath`.
248
+ // Otherwise path and length will be undefined.
249
+ if (initiatorPath) {
250
+ details.debugData = {
251
+ type: 'debugdata',
252
+ initiatorPath,
253
+ pathLength: initiatorPath.length,
254
+ };
255
+ }
256
+
231
257
  return {
232
258
  score: UnusedBytes.scoreForWastedMs(wastedMs),
233
259
  numericValue: wastedMs,
@@ -44,7 +44,7 @@ class NetworkNode extends BaseNode {
44
44
  }
45
45
 
46
46
  /**
47
- * @return {?string}
47
+ * @return {string}
48
48
  */
49
49
  get initiatorType() {
50
50
  return this._record.initiator && this._record.initiator.type;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.6.2",
3
+ "version": "9.6.3",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {