lighthouse 10.0.1-dev.20230215 → 10.0.1-dev.20230216

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.
@@ -17,32 +17,33 @@ declare class PrioritizeLcpImage extends Audit {
17
17
  static shouldPreloadRequest(request: LH.Artifacts.NetworkRequest, mainResource: LH.Artifacts.NetworkRequest, initiatorPath: Array<LH.Gatherer.Simulation.GraphNode>): boolean;
18
18
  /**
19
19
  * @param {LH.Gatherer.Simulation.GraphNode} graph
20
- * @param {string} imageUrl
20
+ * @param {NetworkRequest} lcpRecord
21
21
  * @return {{lcpNode: LH.Gatherer.Simulation.GraphNetworkNode|undefined, path: Array<LH.Gatherer.Simulation.GraphNetworkNode>|undefined}}
22
22
  */
23
- static findLCPNode(graph: LH.Gatherer.Simulation.GraphNode, imageUrl: string): {
23
+ static findLCPNode(graph: LH.Gatherer.Simulation.GraphNode, lcpRecord: NetworkRequest): {
24
24
  lcpNode: LH.Gatherer.Simulation.GraphNetworkNode | undefined;
25
25
  path: Array<LH.Gatherer.Simulation.GraphNetworkNode> | undefined;
26
26
  };
27
27
  /**
28
28
  * @param {LH.Artifacts.NetworkRequest} mainResource
29
29
  * @param {LH.Gatherer.Simulation.GraphNode} graph
30
- * @param {string | undefined} lcpUrl
30
+ * @param {NetworkRequest|undefined} lcpRecord
31
31
  * @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
32
32
  */
33
- static getLCPNodeToPreload(mainResource: LH.Artifacts.NetworkRequest, graph: LH.Gatherer.Simulation.GraphNode, lcpUrl: string | undefined): {
33
+ static getLCPNodeToPreload(mainResource: LH.Artifacts.NetworkRequest, graph: LH.Gatherer.Simulation.GraphNode, lcpRecord: NetworkRequest | undefined): {
34
34
  lcpNodeToPreload?: import("../lib/dependency-graph/network-node.js").NetworkNode | undefined;
35
35
  initiatorPath?: InitiatorPath | undefined;
36
36
  };
37
37
  /**
38
- * Match the LCP event with the paint event to get the URL of the image actually painted.
38
+ * Match the LCP event with the paint event to get the request of the image actually painted.
39
39
  * This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
40
40
  * was a pseudo-element associated with a node containing a smaller background-image.
41
41
  * @param {LH.Trace} trace
42
42
  * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
43
- * @return {string | undefined}
43
+ * @param {Array<NetworkRequest>} networkRecords
44
+ * @return {NetworkRequest|undefined}
44
45
  */
45
- static getLcpUrl(trace: LH.Trace, processedNavigation: LH.Artifacts.ProcessedNavigation): string | undefined;
46
+ static getLcpRecord(trace: LH.Trace, processedNavigation: LH.Artifacts.ProcessedNavigation, networkRecords: Array<NetworkRequest>): NetworkRequest | undefined;
46
47
  /**
47
48
  * Computes the estimated effect of preloading the LCP image.
48
49
  * @param {LH.Artifacts.TraceElement} lcpElement
@@ -12,6 +12,7 @@ import {LanternLargestContentfulPaint} from '../computed/metrics/lantern-largest
12
12
  import {LoadSimulator} from '../computed/load-simulator.js';
13
13
  import {ByteEfficiencyAudit} from './byte-efficiency/byte-efficiency-audit.js';
14
14
  import {ProcessedNavigation} from '../computed/processed-navigation.js';
15
+ import {NetworkRecords} from '../computed/network-records.js';
15
16
 
16
17
  const UIStrings = {
17
18
  /** Title of a lighthouse audit that tells a user to preload an image in order to improve their LCP time. */
@@ -64,15 +65,15 @@ class PrioritizeLcpImage extends Audit {
64
65
 
65
66
  /**
66
67
  * @param {LH.Gatherer.Simulation.GraphNode} graph
67
- * @param {string} imageUrl
68
+ * @param {NetworkRequest} lcpRecord
68
69
  * @return {{lcpNode: LH.Gatherer.Simulation.GraphNetworkNode|undefined, path: Array<LH.Gatherer.Simulation.GraphNetworkNode>|undefined}}
69
70
  */
70
- static findLCPNode(graph, imageUrl) {
71
+ static findLCPNode(graph, lcpRecord) {
71
72
  let lcpNode;
72
73
  let path;
73
74
  graph.traverse((node, traversalPath) => {
74
75
  if (node.type !== 'network') return;
75
- if (node.record.url === imageUrl) {
76
+ if (node.record.requestId === lcpRecord.requestId) {
76
77
  lcpNode = node;
77
78
  path =
78
79
  traversalPath.slice(1).filter(initiator => initiator.type === 'network');
@@ -87,12 +88,12 @@ class PrioritizeLcpImage extends Audit {
87
88
  /**
88
89
  * @param {LH.Artifacts.NetworkRequest} mainResource
89
90
  * @param {LH.Gatherer.Simulation.GraphNode} graph
90
- * @param {string | undefined} lcpUrl
91
+ * @param {NetworkRequest|undefined} lcpRecord
91
92
  * @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
92
93
  */
93
- static getLCPNodeToPreload(mainResource, graph, lcpUrl) {
94
- if (!lcpUrl) return {};
95
- const {lcpNode, path} = PrioritizeLcpImage.findLCPNode(graph, lcpUrl);
94
+ static getLCPNodeToPreload(mainResource, graph, lcpRecord) {
95
+ if (!lcpRecord) return {};
96
+ const {lcpNode, path} = PrioritizeLcpImage.findLCPNode(graph, lcpRecord);
96
97
  if (!lcpNode || !path) return {};
97
98
 
98
99
  // eslint-disable-next-line max-len
@@ -111,14 +112,15 @@ class PrioritizeLcpImage extends Audit {
111
112
  }
112
113
 
113
114
  /**
114
- * Match the LCP event with the paint event to get the URL of the image actually painted.
115
+ * Match the LCP event with the paint event to get the request of the image actually painted.
115
116
  * This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
116
117
  * was a pseudo-element associated with a node containing a smaller background-image.
117
118
  * @param {LH.Trace} trace
118
119
  * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
119
- * @return {string | undefined}
120
+ * @param {Array<NetworkRequest>} networkRecords
121
+ * @return {NetworkRequest|undefined}
120
122
  */
121
- static getLcpUrl(trace, processedNavigation) {
123
+ static getLcpRecord(trace, processedNavigation, networkRecords) {
122
124
  // Use main-frame-only LCP to match the metric value.
123
125
  const lcpEvent = processedNavigation.largestContentfulPaintEvt;
124
126
  if (!lcpEvent) return;
@@ -131,7 +133,31 @@ class PrioritizeLcpImage extends Audit {
131
133
  // Get last candidate, in case there was more than one.
132
134
  }).sort((a, b) => b.ts - a.ts)[0];
133
135
 
134
- return lcpImagePaintEvent?.args.data?.imageUrl;
136
+ const lcpUrl = lcpImagePaintEvent?.args.data?.imageUrl;
137
+ if (!lcpUrl) return;
138
+
139
+ const candidates = networkRecords.filter(record => {
140
+ return record.url === lcpUrl &&
141
+ record.finished &&
142
+ // Same frame as LCP trace event.
143
+ record.frameId === lcpImagePaintEvent.args.frame &&
144
+ record.networkRequestTime < (processedNavigation.timestamps.largestContentfulPaint || 0);
145
+ }).map(record => {
146
+ // Follow any redirects to find the real image request.
147
+ while (record.redirectDestination) {
148
+ record = record.redirectDestination;
149
+ }
150
+ return record;
151
+ }).filter(record => {
152
+ // Don't select if also loaded by some other means (xhr, etc). `resourceType`
153
+ // isn't set on redirect _sources_, so have to check after following redirects.
154
+ return record.resourceType === 'Image';
155
+ });
156
+
157
+ // If there are still multiple candidates, at this point it appears the page
158
+ // simply made multiple requests for the image. The first loaded is the best
159
+ // guess of the request that made the image available for use.
160
+ return candidates.sort((a, b) => a.networkEndTime - b.networkEndTime)[0];
135
161
  }
136
162
 
137
163
  /**
@@ -233,7 +259,8 @@ class PrioritizeLcpImage extends Audit {
233
259
  const trace = artifacts.traces[PrioritizeLcpImage.DEFAULT_PASS];
234
260
  const devtoolsLog = artifacts.devtoolsLogs[PrioritizeLcpImage.DEFAULT_PASS];
235
261
  const URL = artifacts.URL;
236
- const metricData = {trace, devtoolsLog, gatherContext, settings: context.settings, URL};
262
+ const settings = context.settings;
263
+ const metricData = {trace, devtoolsLog, gatherContext, settings, URL};
237
264
  const lcpElement = artifacts.TraceElements
238
265
  .find(element => element.traceEventType === 'largest-contentful-paint');
239
266
 
@@ -241,17 +268,16 @@ class PrioritizeLcpImage extends Audit {
241
268
  return {score: null, notApplicable: true};
242
269
  }
243
270
 
244
- const [processedNavigation, mainResource, lanternLCP, simulator] = await Promise.all([
245
- ProcessedNavigation.request(trace, context),
246
- MainResource.request({devtoolsLog, URL}, context),
247
- LanternLargestContentfulPaint.request(metricData, context),
248
- LoadSimulator.request({devtoolsLog, settings: context.settings}, context),
249
- ]);
271
+ const networkRecords = await NetworkRecords.request(devtoolsLog, context);
272
+ const processedNavigation = await ProcessedNavigation.request(trace, context);
273
+ const mainResource = await MainResource.request({devtoolsLog, URL}, context);
274
+ const lanternLCP = await LanternLargestContentfulPaint.request(metricData, context);
275
+ const simulator = await LoadSimulator.request({devtoolsLog, settings}, context);
250
276
 
251
- const lcpUrl = PrioritizeLcpImage.getLcpUrl(trace, processedNavigation);
277
+ const lcpRecord = PrioritizeLcpImage.getLcpRecord(trace, processedNavigation, networkRecords);
252
278
  const graph = lanternLCP.pessimisticGraph;
253
- // eslint-disable-next-line max-len
254
- const {lcpNodeToPreload, initiatorPath} = PrioritizeLcpImage.getLCPNodeToPreload(mainResource, graph, lcpUrl);
279
+ const {lcpNodeToPreload, initiatorPath} = PrioritizeLcpImage.getLCPNodeToPreload(mainResource,
280
+ graph, lcpRecord);
255
281
 
256
282
  const {results, wastedMs} =
257
283
  PrioritizeLcpImage.computeWasteWithGraph(lcpElement, lcpNodeToPreload, graph, simulator);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "10.0.1-dev.20230215",
4
+ "version": "10.0.1-dev.20230216",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {