lighthouse 12.0.0-dev.20240603 → 12.0.0-dev.20240605

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.
Files changed (34) hide show
  1. package/core/audits/accessibility/aria-allowed-role.js +7 -9
  2. package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
  3. package/core/audits/byte-efficiency/offscreen-images.js +1 -1
  4. package/core/audits/byte-efficiency/render-blocking-resources.js +5 -5
  5. package/core/audits/dobetterweb/uses-http2.js +2 -2
  6. package/core/audits/prioritize-lcp-image.js +1 -1
  7. package/core/audits/third-party-facades.js +1 -1
  8. package/core/audits/third-party-summary.js +1 -1
  9. package/core/audits/uses-rel-preconnect.js +2 -2
  10. package/core/audits/uses-rel-preload.js +9 -9
  11. package/core/computed/critical-request-chains.js +3 -3
  12. package/core/computed/metrics/lantern-metric.d.ts +2 -2
  13. package/core/computed/metrics/lantern-metric.js +2 -19
  14. package/core/computed/page-dependency-graph.d.ts +3 -1
  15. package/core/computed/page-dependency-graph.js +11 -4
  16. package/core/lib/lantern/network-node.d.ts +2 -2
  17. package/core/lib/lantern/network-node.js +3 -4
  18. package/core/lib/lantern/page-dependency-graph.d.ts +9 -9
  19. package/core/lib/lantern/page-dependency-graph.js +90 -58
  20. package/core/lib/lantern/simulator/connection-pool.d.ts +10 -10
  21. package/core/lib/lantern/simulator/connection-pool.js +22 -22
  22. package/core/lib/lantern/simulator/dns-cache.d.ts +1 -1
  23. package/core/lib/lantern/simulator/dns-cache.js +1 -1
  24. package/core/lib/lantern/simulator/network-analyzer.d.ts +12 -12
  25. package/core/lib/lantern/simulator/network-analyzer.js +53 -53
  26. package/core/lib/lantern/simulator/simulator.d.ts +4 -4
  27. package/core/lib/lantern/simulator/simulator.js +17 -17
  28. package/core/lib/lantern/types/lantern.d.ts +11 -6
  29. package/core/lib/lantern-trace-saver.js +1 -1
  30. package/core/lib/network-request.d.ts +6 -1
  31. package/core/lib/network-request.js +13 -9
  32. package/package.json +1 -1
  33. package/shared/localization/locales/en-US.json +3 -3
  34. package/shared/localization/locales/en-XL.json +3 -3
@@ -88,30 +88,30 @@ class NetworkAnalyzer {
88
88
  return summaryByKey;
89
89
  }
90
90
 
91
- /** @typedef {{record: Lantern.NetworkRequest, timing: LH.Crdp.Network.ResourceTiming, connectionReused?: boolean}} RequestInfo */
91
+ /** @typedef {{request: Lantern.NetworkRequest, timing: LH.Crdp.Network.ResourceTiming, connectionReused?: boolean}} RequestInfo */
92
92
 
93
93
  /**
94
- * @param {Lantern.NetworkRequest[]} records
94
+ * @param {Lantern.NetworkRequest[]} requests
95
95
  * @param {(e: RequestInfo) => number | number[] | undefined} iteratee
96
96
  * @return {Map<string, number[]>}
97
97
  */
98
- static _estimateValueByOrigin(records, iteratee) {
99
- const connectionWasReused = NetworkAnalyzer.estimateIfConnectionWasReused(records);
100
- const groupedByOrigin = NetworkAnalyzer.groupByOrigin(records);
98
+ static _estimateValueByOrigin(requests, iteratee) {
99
+ const connectionWasReused = NetworkAnalyzer.estimateIfConnectionWasReused(requests);
100
+ const groupedByOrigin = NetworkAnalyzer.groupByOrigin(requests);
101
101
 
102
102
  const estimates = new Map();
103
- for (const [origin, originRecords] of groupedByOrigin.entries()) {
103
+ for (const [origin, originRequests] of groupedByOrigin.entries()) {
104
104
  /** @type {number[]} */
105
105
  let originEstimates = [];
106
106
 
107
- for (const record of originRecords) {
108
- const timing = record.timing;
107
+ for (const request of originRequests) {
108
+ const timing = request.timing;
109
109
  if (!timing) continue;
110
110
 
111
111
  const value = iteratee({
112
- record,
112
+ request,
113
113
  timing,
114
- connectionReused: connectionWasReused.get(record.requestId),
114
+ connectionReused: connectionWasReused.get(request.requestId),
115
115
  });
116
116
  if (typeof value !== 'undefined') {
117
117
  originEstimates = originEstimates.concat(value);
@@ -137,11 +137,11 @@ class NetworkAnalyzer {
137
137
  * @return {number[]|number|undefined}
138
138
  */
139
139
  static _estimateRTTViaConnectionTiming(info) {
140
- const {timing, connectionReused, record} = info;
140
+ const {timing, connectionReused, request} = info;
141
141
  if (connectionReused) return;
142
142
 
143
143
  const {connectStart, sslStart, sslEnd, connectEnd} = timing;
144
- if (connectEnd >= 0 && connectStart >= 0 && record.protocol.startsWith('h3')) {
144
+ if (connectEnd >= 0 && connectStart >= 0 && request.protocol.startsWith('h3')) {
145
145
  // These values are equal to sslStart and sslEnd for h3.
146
146
  return connectEnd - connectStart;
147
147
  } else if (sslStart >= 0 && sslEnd >= 0 && sslStart !== connectStart) {
@@ -161,17 +161,17 @@ class NetworkAnalyzer {
161
161
  * @return {number|undefined}
162
162
  */
163
163
  static _estimateRTTViaDownloadTiming(info) {
164
- const {timing, connectionReused, record} = info;
164
+ const {timing, connectionReused, request} = info;
165
165
  if (connectionReused) return;
166
166
 
167
167
  // Only look at downloads that went past the initial congestion window
168
- if (record.transferSize <= INITIAL_CWD) return;
168
+ if (request.transferSize <= INITIAL_CWD) return;
169
169
  if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
170
170
 
171
171
  // Compute the amount of time downloading everything after the first congestion window took
172
- const totalTime = record.networkEndTime - record.networkRequestTime;
172
+ const totalTime = request.networkEndTime - request.networkRequestTime;
173
173
  const downloadTimeAfterFirstByte = totalTime - timing.receiveHeadersEnd;
174
- const numberOfRoundTrips = Math.log2(record.transferSize / INITIAL_CWD);
174
+ const numberOfRoundTrips = Math.log2(request.transferSize / INITIAL_CWD);
175
175
 
176
176
  // Ignore requests that required a high number of round trips since bandwidth starts to play
177
177
  // a larger role than latency
@@ -190,7 +190,7 @@ class NetworkAnalyzer {
190
190
  * @return {number|undefined}
191
191
  */
192
192
  static _estimateRTTViaSendStartTiming(info) {
193
- const {timing, connectionReused, record} = info;
193
+ const {timing, connectionReused, request} = info;
194
194
  if (connectionReused) return;
195
195
 
196
196
  if (!Number.isFinite(timing.sendStart) || timing.sendStart < 0) return;
@@ -198,8 +198,8 @@ class NetworkAnalyzer {
198
198
  // Assume everything before sendStart was just DNS + (SSL)? + TCP handshake
199
199
  // 1 RT for DNS, 1 RT (maybe) for SSL, 1 RT for TCP
200
200
  let roundTrips = 1;
201
- if (!record.protocol.startsWith('h3')) roundTrips += 1; // TCP
202
- if (record.parsedURL.scheme === 'https') roundTrips += 1;
201
+ if (!request.protocol.startsWith('h3')) roundTrips += 1; // TCP
202
+ if (request.parsedURL.scheme === 'https') roundTrips += 1;
203
203
  return timing.sendStart / roundTrips;
204
204
  }
205
205
 
@@ -213,12 +213,12 @@ class NetworkAnalyzer {
213
213
  * @return {number|undefined}
214
214
  */
215
215
  static _estimateRTTViaHeadersEndTiming(info) {
216
- const {timing, connectionReused, record} = info;
216
+ const {timing, connectionReused, request} = info;
217
217
  if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
218
- if (!record.resourceType) return;
218
+ if (!request.resourceType) return;
219
219
 
220
220
  const serverResponseTimePercentage =
221
- SERVER_RESPONSE_PERCENTAGE_OF_TTFB[record.resourceType] ||
221
+ SERVER_RESPONSE_PERCENTAGE_OF_TTFB[request.resourceType] ||
222
222
  DEFAULT_SERVER_RESPONSE_PERCENTAGE;
223
223
  const estimatedServerResponseTime = timing.receiveHeadersEnd * serverResponseTimePercentage;
224
224
 
@@ -230,8 +230,8 @@ class NetworkAnalyzer {
230
230
  // TTFB = DNS + (SSL)? + TCP handshake + 1 RT for request + server response time
231
231
  if (!connectionReused) {
232
232
  roundTrips += 1; // DNS
233
- if (!record.protocol.startsWith('h3')) roundTrips += 1; // TCP
234
- if (record.parsedURL.scheme === 'https') roundTrips += 1; // SSL
233
+ if (!request.protocol.startsWith('h3')) roundTrips += 1; // TCP
234
+ if (request.parsedURL.scheme === 'https') roundTrips += 1; // SSL
235
235
  }
236
236
 
237
237
  // subtract out our estimated server response time
@@ -246,28 +246,28 @@ class NetworkAnalyzer {
246
246
  * @return {Map<string, number[]>}
247
247
  */
248
248
  static _estimateResponseTimeByOrigin(records, rttByOrigin) {
249
- return NetworkAnalyzer._estimateValueByOrigin(records, ({record, timing}) => {
250
- if (record.serverResponseTime !== undefined) return record.serverResponseTime;
249
+ return NetworkAnalyzer._estimateValueByOrigin(records, ({request, timing}) => {
250
+ if (request.serverResponseTime !== undefined) return request.serverResponseTime;
251
251
 
252
252
  if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
253
253
  if (!Number.isFinite(timing.sendEnd) || timing.sendEnd < 0) return;
254
254
 
255
255
  const ttfb = timing.receiveHeadersEnd - timing.sendEnd;
256
- const origin = record.parsedURL.securityOrigin;
256
+ const origin = request.parsedURL.securityOrigin;
257
257
  const rtt = rttByOrigin.get(origin) || rttByOrigin.get(NetworkAnalyzer.SUMMARY) || 0;
258
258
  return Math.max(ttfb - rtt, 0);
259
259
  });
260
260
  }
261
261
 
262
262
  /**
263
- * @param {Lantern.NetworkRequest[]} records
263
+ * @param {Lantern.NetworkRequest[]} requests
264
264
  * @return {boolean}
265
265
  */
266
- static canTrustConnectionInformation(records) {
266
+ static canTrustConnectionInformation(requests) {
267
267
  const connectionIdWasStarted = new Map();
268
- for (const record of records) {
269
- const started = connectionIdWasStarted.get(record.connectionId) || !record.connectionReused;
270
- connectionIdWasStarted.set(record.connectionId, started);
268
+ for (const request of requests) {
269
+ const started = connectionIdWasStarted.get(request.connectionId) || !request.connectionReused;
270
+ connectionIdWasStarted.set(request.connectionId, started);
271
271
  }
272
272
 
273
273
  // We probably can't trust the network information if all the connection IDs were the same
@@ -289,10 +289,10 @@ class NetworkAnalyzer {
289
289
 
290
290
  // Check if we can trust the connection information coming from the protocol
291
291
  if (!forceCoarseEstimates && NetworkAnalyzer.canTrustConnectionInformation(records)) {
292
- return new Map(records.map(record => [record.requestId, !!record.connectionReused]));
292
+ return new Map(records.map(request => [request.requestId, !!request.connectionReused]));
293
293
  }
294
294
 
295
- // Otherwise we're on our own, a record may not have needed a fresh connection if...
295
+ // Otherwise we're on our own, a request may not have needed a fresh connection if...
296
296
  // - It was not the first request to the domain
297
297
  // - It was H2
298
298
  // - It was after the first request to the domain ended
@@ -300,13 +300,13 @@ class NetworkAnalyzer {
300
300
  const groupedByOrigin = NetworkAnalyzer.groupByOrigin(records);
301
301
  for (const [_, originRecords] of groupedByOrigin.entries()) {
302
302
  const earliestReusePossible = originRecords
303
- .map(record => record.networkEndTime)
303
+ .map(request => request.networkEndTime)
304
304
  .reduce((a, b) => Math.min(a, b), Infinity);
305
305
 
306
- for (const record of originRecords) {
306
+ for (const request of originRecords) {
307
307
  connectionWasReused.set(
308
- record.requestId,
309
- record.networkRequestTime >= earliestReusePossible || record.protocol === 'h2'
308
+ request.requestId,
309
+ request.networkRequestTime >= earliestReusePossible || request.protocol === 'h2'
310
310
  );
311
311
  }
312
312
 
@@ -343,7 +343,7 @@ class NetworkAnalyzer {
343
343
  const groupedByOrigin = NetworkAnalyzer.groupByOrigin(records);
344
344
 
345
345
  const estimatesByOrigin = new Map();
346
- for (const [origin, originRecords] of groupedByOrigin.entries()) {
346
+ for (const [origin, originRequests] of groupedByOrigin.entries()) {
347
347
  /** @type {number[]} */
348
348
  const originEstimates = [];
349
349
 
@@ -352,14 +352,14 @@ class NetworkAnalyzer {
352
352
  */
353
353
  // eslint-disable-next-line no-inner-declarations
354
354
  function collectEstimates(estimator, multiplier = 1) {
355
- for (const record of originRecords) {
356
- const timing = record.timing;
355
+ for (const request of originRequests) {
356
+ const timing = request.timing;
357
357
  if (!timing) continue;
358
358
 
359
359
  const estimates = estimator({
360
- record,
360
+ request,
361
361
  timing,
362
- connectionReused: connectionWasReused.get(record.requestId),
362
+ connectionReused: connectionWasReused.get(request.requestId),
363
363
  });
364
364
  if (estimates === undefined) continue;
365
365
 
@@ -427,9 +427,9 @@ class NetworkAnalyzer {
427
427
 
428
428
 
429
429
  /**
430
- * Computes the average throughput for the given records in bits/second.
430
+ * Computes the average throughput for the given requests in bits/second.
431
431
  * Excludes data URI, failed or otherwise incomplete, and cached requests.
432
- * Returns Infinity if there were no analyzable network records.
432
+ * Returns Infinity if there were no analyzable network requests.
433
433
  *
434
434
  * @param {Lantern.NetworkRequest[]} records
435
435
  * @return {number}
@@ -438,21 +438,21 @@ class NetworkAnalyzer {
438
438
  let totalBytes = 0;
439
439
 
440
440
  // We will measure throughput by summing the total bytes downloaded by the total time spent
441
- // downloading those bytes. We slice up all the network records into start/end boundaries, so
441
+ // downloading those bytes. We slice up all the network requests into start/end boundaries, so
442
442
  // it's easier to deal with the gaps in downloading.
443
- const timeBoundaries = records.reduce((boundaries, record) => {
444
- const scheme = record.parsedURL?.scheme;
443
+ const timeBoundaries = records.reduce((boundaries, request) => {
444
+ const scheme = request.parsedURL?.scheme;
445
445
  // Requests whose bodies didn't come over the network or didn't completely finish will mess
446
446
  // with the computation, just skip over them.
447
- if (scheme === 'data' || record.failed || !record.finished ||
448
- record.statusCode > 300 || !record.transferSize) {
447
+ if (scheme === 'data' || request.failed || !request.finished ||
448
+ request.statusCode > 300 || !request.transferSize) {
449
449
  return boundaries;
450
450
  }
451
451
 
452
452
  // If we've made it this far, all the times we need should be valid (i.e. not undefined/-1).
453
- totalBytes += record.transferSize;
454
- boundaries.push({time: record.responseHeadersEndTime / 1000, isStart: true});
455
- boundaries.push({time: record.networkEndTime / 1000, isStart: false});
453
+ totalBytes += request.transferSize;
454
+ boundaries.push({time: request.responseHeadersEndTime / 1000, isStart: true});
455
+ boundaries.push({time: request.networkEndTime / 1000, isStart: false});
456
456
  return boundaries;
457
457
  }, /** @type {Array<{time: number, isStart: boolean}>} */([])).sort((a, b) => a.time - b.time);
458
458
 
@@ -14,7 +14,7 @@ export class Simulator<T = any> {
14
14
  /** @return {Map<string, Map<Node, CompleteNodeTiming>>} */
15
15
  static get ALL_NODE_TIMINGS(): Map<string, Map<Node, CompleteNodeTiming>>;
16
16
  /**
17
- * We attempt to start nodes by their observed start time using the record priority as a tie breaker.
17
+ * We attempt to start nodes by their observed start time using the request priority as a tie breaker.
18
18
  * When simulating, just because a low priority image started 5ms before a high priority image doesn't mean
19
19
  * it would have happened like that when the network was slower.
20
20
  * @param {Node} node
@@ -73,10 +73,10 @@ export class Simulator<T = any> {
73
73
  */
74
74
  _markNodeAsComplete(node: Node, endTime: number, connectionTiming?: import("./simulator-timing-map.js").ConnectionTiming | undefined): void;
75
75
  /**
76
- * @param {Lantern.NetworkRequest} record
76
+ * @param {Lantern.NetworkRequest} request
77
77
  * @return {?TcpConnection}
78
78
  */
79
- _acquireConnection(record: Lantern.NetworkRequest): TcpConnection | null;
79
+ _acquireConnection(request: Lantern.NetworkRequest): TcpConnection | null;
80
80
  /**
81
81
  * @return {Node[]}
82
82
  */
@@ -136,7 +136,7 @@ export class Simulator<T = any> {
136
136
  *
137
137
  * Simulator/connection pool are allowed to deviate from what was
138
138
  * observed in the trace/devtoolsLog and start requests as soon as they are queued (i.e. do not
139
- * wait around for a warm connection to be available if the original record was fetched on a warm
139
+ * wait around for a warm connection to be available if the original request was fetched on a warm
140
140
  * connection).
141
141
  *
142
142
  * @param {Node} graph
@@ -34,7 +34,7 @@ const NodeState = {
34
34
  Complete: 3,
35
35
  };
36
36
 
37
- /** @type {Record<NetworkNode['record']['priority'], number>} */
37
+ /** @type {Record<NetworkNode['request']['priority'], number>} */
38
38
  const PriorityStartTimePenalty = {
39
39
  VeryHigh: 0,
40
40
  High: 0.25,
@@ -248,11 +248,11 @@ class Simulator {
248
248
  }
249
249
 
250
250
  /**
251
- * @param {Lantern.NetworkRequest} record
251
+ * @param {Lantern.NetworkRequest} request
252
252
  * @return {?TcpConnection}
253
253
  */
254
- _acquireConnection(record) {
255
- return this._connectionPool.acquire(record);
254
+ _acquireConnection(request) {
255
+ return this._connectionPool.acquire(request);
256
256
  }
257
257
 
258
258
  /**
@@ -342,7 +342,7 @@ class Simulator {
342
342
  * @return {number}
343
343
  */
344
344
  _estimateNetworkTimeRemaining(networkNode) {
345
- const record = networkNode.request;
345
+ const request = networkNode.request;
346
346
  const timingData = this._nodeTimings.getNetworkStarted(networkNode);
347
347
 
348
348
  let timeElapsed = 0;
@@ -350,23 +350,23 @@ class Simulator {
350
350
  // Rough access time for seeking to location on disk and reading sequentially.
351
351
  // 8ms per seek + 20ms/MB
352
352
  // @see http://norvig.com/21-days.html#answers
353
- const sizeInMb = (record.resourceSize || 0) / 1024 / 1024;
353
+ const sizeInMb = (request.resourceSize || 0) / 1024 / 1024;
354
354
  timeElapsed = 8 + 20 * sizeInMb - timingData.timeElapsed;
355
355
  } else if (networkNode.isNonNetworkProtocol) {
356
356
  // Estimates for the overhead of a data URL in Chromium and the decoding time for base64-encoded data.
357
357
  // 2ms per request + 10ms/MB
358
358
  // @see traces on https://dopiaza.org/tools/datauri/examples/index.php
359
- const sizeInMb = (record.resourceSize || 0) / 1024 / 1024;
359
+ const sizeInMb = (request.resourceSize || 0) / 1024 / 1024;
360
360
  timeElapsed = 2 + 10 * sizeInMb - timingData.timeElapsed;
361
361
  } else {
362
- const connection = this._connectionPool.acquireActiveConnectionFromRecord(record);
363
- const dnsResolutionTime = this._dns.getTimeUntilResolution(record, {
362
+ const connection = this._connectionPool.acquireActiveConnectionFromRequest(request);
363
+ const dnsResolutionTime = this._dns.getTimeUntilResolution(request, {
364
364
  requestedAt: timingData.startTime,
365
365
  shouldUpdateCache: true,
366
366
  });
367
367
  const timeAlreadyElapsed = timingData.timeElapsed;
368
368
  const calculation = connection.simulateDownloadUntil(
369
- record.transferSize - timingData.bytesDownloaded,
369
+ request.transferSize - timingData.bytesDownloaded,
370
370
  {timeAlreadyElapsed, dnsResolutionTime, maximumTimeToElapse: Infinity}
371
371
  );
372
372
 
@@ -410,14 +410,14 @@ class Simulator {
410
410
  if (node.type !== BaseNode.TYPES.NETWORK) throw new Error('Unsupported');
411
411
  if (!('bytesDownloaded' in timingData)) throw new Error('Invalid timing data');
412
412
 
413
- const record = node.request;
414
- const connection = this._connectionPool.acquireActiveConnectionFromRecord(record);
415
- const dnsResolutionTime = this._dns.getTimeUntilResolution(record, {
413
+ const request = node.request;
414
+ const connection = this._connectionPool.acquireActiveConnectionFromRequest(request);
415
+ const dnsResolutionTime = this._dns.getTimeUntilResolution(request, {
416
416
  requestedAt: timingData.startTime,
417
417
  shouldUpdateCache: true,
418
418
  });
419
419
  const calculation = connection.simulateDownloadUntil(
420
- record.transferSize - timingData.bytesDownloaded,
420
+ request.transferSize - timingData.bytesDownloaded,
421
421
  {
422
422
  dnsResolutionTime,
423
423
  timeAlreadyElapsed: timingData.timeElapsed,
@@ -430,7 +430,7 @@ class Simulator {
430
430
 
431
431
  if (isFinished) {
432
432
  connection.setWarmed(true);
433
- this._connectionPool.release(record);
433
+ this._connectionPool.release(request);
434
434
  this._markNodeAsComplete(node, totalElapsedTime, calculation.connectionTiming);
435
435
  } else {
436
436
  timingData.timeElapsed += calculation.timeElapsed;
@@ -480,7 +480,7 @@ class Simulator {
480
480
  *
481
481
  * Simulator/connection pool are allowed to deviate from what was
482
482
  * observed in the trace/devtoolsLog and start requests as soon as they are queued (i.e. do not
483
- * wait around for a warm connection to be available if the original record was fetched on a warm
483
+ * wait around for a warm connection to be available if the original request was fetched on a warm
484
484
  * connection).
485
485
  *
486
486
  * @param {Node} graph
@@ -580,7 +580,7 @@ class Simulator {
580
580
  }
581
581
 
582
582
  /**
583
- * We attempt to start nodes by their observed start time using the record priority as a tie breaker.
583
+ * We attempt to start nodes by their observed start time using the request priority as a tie breaker.
584
584
  * When simulating, just because a low priority image started 5ms before a high priority image doesn't mean
585
585
  * it would have happened like that when the network was slower.
586
586
  * @param {Node} node
@@ -38,12 +38,13 @@ type LightriderStatistics = {
38
38
 
39
39
  export class NetworkRequest<T = any> {
40
40
  /**
41
- * The canonical network record.
42
- * Users of Lantern must create NetworkRequests matching this interface,
43
- * but can store the source-of-truth for their network model in this `record`
44
- * property. This is then accessible as a read-only property on NetworkNode.
41
+ * Implementation-specifc canoncial data structure that this Lantern NetworkRequest
42
+ * was derived from.
43
+ * Users of Lantern create a NetworkRequest matching this interface,
44
+ * but can store the source-of-truth for their network model in this property.
45
+ * This is then accessible as a read-only property on NetworkNode.
45
46
  */
46
- record?: T;
47
+ rawRequest?: T;
47
48
 
48
49
  requestId: string;
49
50
  connectionId: number;
@@ -59,7 +60,11 @@ export class NetworkRequest<T = any> {
59
60
  * HTTP cache or going to the network for DNS/connection setup, in milliseconds.
60
61
  */
61
62
  networkRequestTime: number;
62
- /** When the last byte of the response headers is received, in milliseconds. */
63
+ /**
64
+ * When the last byte of the response headers is received, in milliseconds.
65
+ * Equal to networkRequestTime if no data is recieved over the
66
+ * network (ex: cached requests or data urls).
67
+ */
63
68
  responseHeadersEndTime: number;
64
69
  /** When the last byte of the response body is received, in milliseconds. */
65
70
  networkEndTime: number;
@@ -33,7 +33,7 @@ function convertNodeTimingsToTrace(nodeTimings) {
33
33
  traceEvents.push(...createFakeTaskEvents(node, timing));
34
34
  } else {
35
35
  /** @type {LH.Artifacts.NetworkRequest} */
36
- const record = node.record;
36
+ const record = node.rawRequest;
37
37
  // Ignore data URIs as they don't really add much value
38
38
  if (/^data/.test(record.url)) continue;
39
39
  traceEvents.push(...createFakeNetworkEvents(requestId, record, timing));
@@ -104,7 +104,11 @@ export class NetworkRequest {
104
104
  * HTTP cache or going to the network for DNS/connection setup, in milliseconds.
105
105
  */
106
106
  networkRequestTime: number;
107
- /** When the last byte of the response headers is received, in milliseconds. */
107
+ /**
108
+ * When the last byte of the response headers is received, in milliseconds.
109
+ * Equal to networkRequestTime if no data is recieved over the
110
+ * network (ex: cached requests or data urls).
111
+ */
108
112
  responseHeadersEndTime: number;
109
113
  /** When the last byte of the response body is received, in milliseconds. */
110
114
  networkEndTime: number;
@@ -203,6 +207,7 @@ export class NetworkRequest {
203
207
  * @param {LH.Crdp.Network.ResponseReceivedEvent['type']=} resourceType
204
208
  */
205
209
  _onResponse(response: LH.Crdp.Network.Response, timestamp: number, resourceType?: LH.Crdp.Network.ResponseReceivedEvent['type'] | undefined): void;
210
+ responseTimestamp: number | undefined;
206
211
  /**
207
212
  * Resolve differences between conflicting timing signals. Based on the property setters in DevTools.
208
213
  * @see https://github.com/ChromeDevTools/devtools-frontend/blob/56a99365197b85c24b732ac92b0ac70feed80179/front_end/sdk/NetworkRequest.js#L485-L502
@@ -129,7 +129,11 @@ class NetworkRequest {
129
129
  * HTTP cache or going to the network for DNS/connection setup, in milliseconds.
130
130
  */
131
131
  this.networkRequestTime = -1;
132
- /** When the last byte of the response headers is received, in milliseconds. */
132
+ /**
133
+ * When the last byte of the response headers is received, in milliseconds.
134
+ * Equal to networkRequestTime if no data is recieved over the
135
+ * network (ex: cached requests or data urls).
136
+ */
133
137
  this.responseHeadersEndTime = -1;
134
138
  /** When the last byte of the response body is received, in milliseconds. */
135
139
  this.networkEndTime = -1;
@@ -222,8 +226,9 @@ class NetworkRequest {
222
226
  this.isSecure = UrlUtils.isSecureScheme(this.parsedURL.scheme);
223
227
 
224
228
  this.rendererStartTime = data.timestamp * 1000;
225
- // Expected to be overridden with better value in `_recomputeTimesWithResourceTiming`.
229
+ // These are expected to be overridden with better value in `_recomputeTimesWithResourceTiming`.
226
230
  this.networkRequestTime = this.rendererStartTime;
231
+ this.responseHeadersEndTime = this.rendererStartTime;
227
232
 
228
233
  this.requestMethod = data.request.method;
229
234
 
@@ -350,8 +355,7 @@ class NetworkRequest {
350
355
 
351
356
  if (response.protocol) this.protocol = response.protocol;
352
357
 
353
- // This is updated in _recomputeTimesWithResourceTiming, if timings are present.
354
- this.responseHeadersEndTime = timestamp * 1000;
358
+ this.responseTimestamp = timestamp * 1000;
355
359
 
356
360
  this.transferSize = response.encodedDataLength;
357
361
  this.responseHeadersTransferSize = response.encodedDataLength;
@@ -381,7 +385,7 @@ class NetworkRequest {
381
385
  _recomputeTimesWithResourceTiming(timing) {
382
386
  // Don't recompute times if the data is invalid. RequestTime should always be a thread timestamp.
383
387
  // If we don't have receiveHeadersEnd, we really don't have more accurate data.
384
- if (timing.requestTime === 0 || timing.receiveHeadersEnd === -1) return;
388
+ if (timing.requestTime === -1 || timing.receiveHeadersEnd === -1) return;
385
389
 
386
390
  // Take networkRequestTime and responseHeadersEndTime from timing data for better accuracy.
387
391
  // Before this, networkRequestTime and responseHeadersEndTime were set to bogus values based on
@@ -392,15 +396,15 @@ class NetworkRequest {
392
396
  // See https://raw.githubusercontent.com/GoogleChrome/lighthouse/main/docs/Network-Timings.svg
393
397
  this.networkRequestTime = timing.requestTime * 1000;
394
398
  const headersReceivedTime = this.networkRequestTime + timing.receiveHeadersEnd;
395
- // This was set in `_onResponse` as that event's timestamp.
396
- const responseTimestamp = this.responseHeadersEndTime;
397
399
 
398
400
  // Update this.responseHeadersEndTime. All timing values from the netstack (timing) are well-ordered, and
399
401
  // so are the timestamps from CDP (which this.responseHeadersEndTime belongs to). It shouldn't be possible
400
402
  // that this timing from the netstack is greater than the onResponse timestamp, but just to ensure proper order
401
403
  // is maintained we bound the new timing by the network request time and the response timestamp.
402
404
  this.responseHeadersEndTime = headersReceivedTime;
403
- this.responseHeadersEndTime = Math.min(this.responseHeadersEndTime, responseTimestamp);
405
+ if (this.responseTimestamp !== undefined) {
406
+ this.responseHeadersEndTime = Math.min(this.responseHeadersEndTime, this.responseTimestamp);
407
+ }
404
408
  this.responseHeadersEndTime = Math.max(this.responseHeadersEndTime, this.networkRequestTime);
405
409
 
406
410
  // We're only at responseReceived (_onResponse) at this point.
@@ -604,10 +608,10 @@ class NetworkRequest {
604
608
  record.fromWorker = record.sessionTargetType === 'worker';
605
609
 
606
610
  return {
611
+ rawRequest: record,
607
612
  ...record,
608
613
  timing,
609
614
  serverResponseTime,
610
- record,
611
615
  };
612
616
  }
613
617
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "12.0.0-dev.20240603",
4
+ "version": "12.0.0-dev.20240605",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -18,13 +18,13 @@
18
18
  "message": "`[aria-*]` attributes match their roles"
19
19
  },
20
20
  "core/audits/accessibility/aria-allowed-role.js | description": {
21
- "message": "ARIA `role`s enable assistive technologies to know the role of each element on the web page. If the `role` values are misspelled, not existing ARIA `role` values, or abstract roles, then the purpose of the element will not be communicated to users of assistive technologies. [Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.9/aria-allowed-role)."
21
+ "message": "Many HTML elements can only be assigned certain ARIA roles. Using ARIA roles where they are not allowed can interfere with the accessibility of the web page. [Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.9/aria-allowed-role)."
22
22
  },
23
23
  "core/audits/accessibility/aria-allowed-role.js | failureTitle": {
24
- "message": "Values assigned to `role=\"\"` are not valid ARIA roles."
24
+ "message": "Uses ARIA roles on incompatible elements"
25
25
  },
26
26
  "core/audits/accessibility/aria-allowed-role.js | title": {
27
- "message": "Values assigned to `role=\"\"` are valid ARIA roles."
27
+ "message": "Uses ARIA roles only on compatible elements"
28
28
  },
29
29
  "core/audits/accessibility/aria-command-name.js | description": {
30
30
  "message": "When an element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to make command elements more accessible](https://dequeuniversity.com/rules/axe/4.9/aria-command-name)."
@@ -18,13 +18,13 @@
18
18
  "message": "`[aria-*]` ât́t̂ŕîb́ût́êś m̂át̂ćĥ t́ĥéîŕ r̂ól̂éŝ"
19
19
  },
20
20
  "core/audits/accessibility/aria-allowed-role.js | description": {
21
- "message": "ÂŔÎÁ `role`ŝ én̂áb̂ĺê áŝśîśt̂ív̂é t̂éh́n̂ól̂óĝíêś k̂ńô t̂h́ê ŕôĺê óf̂ âćél̂ém̂én̂t́ ôt̂h́ê ẃêb́ p̂áĝé. f́ t̂h́ê `role` v́âĺûéŝ ár̂é m̂ŝśp̂él̂ĺêd́, ó éx̂íŝt́îńĝ ÁR̂ÍÂ `role` v́âĺûéŝ, ór̂ áb̂śŕâć ŕôĺêś, t̂h́êń t̂h́ê ṕûŕp̂óŝé ôf́ t̂h́ê l̂ém̂n̂t́ íl̂n̂ót̂ b́ê ćôḿm̂úíĉt̂éd̂ t́ô úŝér̂ś ôf́ âśíŝt́îv́ê t́êćĥńôĺôǵéŝ. [Ĺêár̂ń m̂ór̂é âb́ôút̂ ÁR̂ÍÂ ŕôĺêś](https://dequeuniversity.com/rules/axe/4.9/aria-allowed-role)."
21
+ "message": "M̂án̂ý ĤT́M̂Ĺ êĺêḿêńt̂ś ĉán̂ ón̂ĺŷ b́ê áŝśîǵn̂éd̂ ćêŕt̂áîń ÂŔÎÁ r̂ól̂éŝ. Úŝín̂ǵ ÂŔÎÁ r̂ól̂éŝ ẃĥér̂é t̂h́êý âŕê ńôt́ âĺl̂óŵéd̂ ćâń îńt̂ér̂f́êŕê ẃît́ĥ t́ĥ âćĉéŝśîb́îĺît́ŷ óf̂ t́ ŵéb̂ ṕâǵê. [Ĺêár̂ń m̂ór̂é âb́ôút̂ ÁR̂ÍÂ ŕôĺêś](https://dequeuniversity.com/rules/axe/4.9/aria-allowed-role)."
22
22
  },
23
23
  "core/audits/accessibility/aria-allowed-role.js | failureTitle": {
24
- "message": "V̂ál̂úêś âśŝíĝńê `role=\"\"` âŕê ńôt́ v̂ál̂íd̂ ÁR̂ÍÂ ŕôĺêś."
24
+ "message": "Ûśêś ÂŔÎÁ r̂óéŝ ó ín̂ćôḿp̂át̂íb̂ĺê él̂ém̂én̂t́ŝ"
25
25
  },
26
26
  "core/audits/accessibility/aria-allowed-role.js | title": {
27
- "message": "V̂ál̂úêś âśŝíĝńê `role=\"\"` âŕê v́âĺîd́ ÂŔÎÁ r̂ól̂éŝ."
27
+ "message": "Ûśêś ÂŔÎÁ r̂óéŝ ón̂ĺŷ ón̂ ćôḿp̂át̂íb̂ĺê él̂ém̂én̂t́ŝ"
28
28
  },
29
29
  "core/audits/accessibility/aria-command-name.js | description": {
30
30
  "message": "Ŵh́êń âń êĺêḿêńt̂ d́ôéŝń't̂ h́âv́ê án̂ áĉćêśŝíb̂ĺê ńâḿê, śĉŕêén̂ ŕêád̂ér̂ś âńn̂óûńĉé ît́ ŵít̂h́ â ǵêńêŕîć n̂ám̂é, m̂ák̂ín̂ǵ ît́ ûńûśâb́l̂é f̂ór̂ úŝér̂ś ŵh́ô ŕêĺŷ ón̂ śĉŕêén̂ ŕêád̂ér̂ś. [L̂éâŕn̂ h́ôẃ t̂ó m̂ák̂é ĉóm̂ḿâńd̂ él̂ém̂én̂t́ŝ ḿôŕê áĉćêśŝíb̂ĺê](https://dequeuniversity.com/rules/axe/4.9/aria-command-name)."