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.
- package/core/audits/accessibility/aria-allowed-role.js +7 -9
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
- package/core/audits/byte-efficiency/offscreen-images.js +1 -1
- package/core/audits/byte-efficiency/render-blocking-resources.js +5 -5
- package/core/audits/dobetterweb/uses-http2.js +2 -2
- package/core/audits/prioritize-lcp-image.js +1 -1
- package/core/audits/third-party-facades.js +1 -1
- package/core/audits/third-party-summary.js +1 -1
- package/core/audits/uses-rel-preconnect.js +2 -2
- package/core/audits/uses-rel-preload.js +9 -9
- package/core/computed/critical-request-chains.js +3 -3
- package/core/computed/metrics/lantern-metric.d.ts +2 -2
- package/core/computed/metrics/lantern-metric.js +2 -19
- package/core/computed/page-dependency-graph.d.ts +3 -1
- package/core/computed/page-dependency-graph.js +11 -4
- package/core/lib/lantern/network-node.d.ts +2 -2
- package/core/lib/lantern/network-node.js +3 -4
- package/core/lib/lantern/page-dependency-graph.d.ts +9 -9
- package/core/lib/lantern/page-dependency-graph.js +90 -58
- package/core/lib/lantern/simulator/connection-pool.d.ts +10 -10
- package/core/lib/lantern/simulator/connection-pool.js +22 -22
- package/core/lib/lantern/simulator/dns-cache.d.ts +1 -1
- package/core/lib/lantern/simulator/dns-cache.js +1 -1
- package/core/lib/lantern/simulator/network-analyzer.d.ts +12 -12
- package/core/lib/lantern/simulator/network-analyzer.js +53 -53
- package/core/lib/lantern/simulator/simulator.d.ts +4 -4
- package/core/lib/lantern/simulator/simulator.js +17 -17
- package/core/lib/lantern/types/lantern.d.ts +11 -6
- package/core/lib/lantern-trace-saver.js +1 -1
- package/core/lib/network-request.d.ts +6 -1
- package/core/lib/network-request.js +13 -9
- package/package.json +1 -1
- package/shared/localization/locales/en-US.json +3 -3
- 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 {{
|
|
91
|
+
/** @typedef {{request: Lantern.NetworkRequest, timing: LH.Crdp.Network.ResourceTiming, connectionReused?: boolean}} RequestInfo */
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* @param {Lantern.NetworkRequest[]}
|
|
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(
|
|
99
|
-
const connectionWasReused = NetworkAnalyzer.estimateIfConnectionWasReused(
|
|
100
|
-
const groupedByOrigin = NetworkAnalyzer.groupByOrigin(
|
|
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,
|
|
103
|
+
for (const [origin, originRequests] of groupedByOrigin.entries()) {
|
|
104
104
|
/** @type {number[]} */
|
|
105
105
|
let originEstimates = [];
|
|
106
106
|
|
|
107
|
-
for (const
|
|
108
|
-
const 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
|
-
|
|
112
|
+
request,
|
|
113
113
|
timing,
|
|
114
|
-
connectionReused: connectionWasReused.get(
|
|
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,
|
|
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 &&
|
|
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,
|
|
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 (
|
|
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 =
|
|
172
|
+
const totalTime = request.networkEndTime - request.networkRequestTime;
|
|
173
173
|
const downloadTimeAfterFirstByte = totalTime - timing.receiveHeadersEnd;
|
|
174
|
-
const numberOfRoundTrips = Math.log2(
|
|
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,
|
|
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 (!
|
|
202
|
-
if (
|
|
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,
|
|
216
|
+
const {timing, connectionReused, request} = info;
|
|
217
217
|
if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
|
|
218
|
-
if (!
|
|
218
|
+
if (!request.resourceType) return;
|
|
219
219
|
|
|
220
220
|
const serverResponseTimePercentage =
|
|
221
|
-
SERVER_RESPONSE_PERCENTAGE_OF_TTFB[
|
|
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 (!
|
|
234
|
-
if (
|
|
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, ({
|
|
250
|
-
if (
|
|
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 =
|
|
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[]}
|
|
263
|
+
* @param {Lantern.NetworkRequest[]} requests
|
|
264
264
|
* @return {boolean}
|
|
265
265
|
*/
|
|
266
|
-
static canTrustConnectionInformation(
|
|
266
|
+
static canTrustConnectionInformation(requests) {
|
|
267
267
|
const connectionIdWasStarted = new Map();
|
|
268
|
-
for (const
|
|
269
|
-
const started = connectionIdWasStarted.get(
|
|
270
|
-
connectionIdWasStarted.set(
|
|
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(
|
|
292
|
+
return new Map(records.map(request => [request.requestId, !!request.connectionReused]));
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
// Otherwise we're on our own, a
|
|
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(
|
|
303
|
+
.map(request => request.networkEndTime)
|
|
304
304
|
.reduce((a, b) => Math.min(a, b), Infinity);
|
|
305
305
|
|
|
306
|
-
for (const
|
|
306
|
+
for (const request of originRecords) {
|
|
307
307
|
connectionWasReused.set(
|
|
308
|
-
|
|
309
|
-
|
|
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,
|
|
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
|
|
356
|
-
const 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
|
-
|
|
360
|
+
request,
|
|
361
361
|
timing,
|
|
362
|
-
connectionReused: connectionWasReused.get(
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
444
|
-
const 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' ||
|
|
448
|
-
|
|
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 +=
|
|
454
|
-
boundaries.push({time:
|
|
455
|
-
boundaries.push({time:
|
|
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
|
|
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}
|
|
76
|
+
* @param {Lantern.NetworkRequest} request
|
|
77
77
|
* @return {?TcpConnection}
|
|
78
78
|
*/
|
|
79
|
-
_acquireConnection(
|
|
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
|
|
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['
|
|
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}
|
|
251
|
+
* @param {Lantern.NetworkRequest} request
|
|
252
252
|
* @return {?TcpConnection}
|
|
253
253
|
*/
|
|
254
|
-
_acquireConnection(
|
|
255
|
-
return this._connectionPool.acquire(
|
|
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
|
|
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 = (
|
|
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 = (
|
|
359
|
+
const sizeInMb = (request.resourceSize || 0) / 1024 / 1024;
|
|
360
360
|
timeElapsed = 2 + 10 * sizeInMb - timingData.timeElapsed;
|
|
361
361
|
} else {
|
|
362
|
-
const connection = this._connectionPool.
|
|
363
|
-
const dnsResolutionTime = this._dns.getTimeUntilResolution(
|
|
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
|
-
|
|
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
|
|
414
|
-
const connection = this._connectionPool.
|
|
415
|
-
const dnsResolutionTime = this._dns.getTimeUntilResolution(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
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.
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
//
|
|
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
|
-
|
|
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 ===
|
|
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
|
-
|
|
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
|
@@ -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": "
|
|
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": "
|
|
24
|
+
"message": "Uses ARIA roles on incompatible elements"
|
|
25
25
|
},
|
|
26
26
|
"core/audits/accessibility/aria-allowed-role.js | title": {
|
|
27
|
-
"message": "
|
|
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": "
|
|
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": "
|
|
24
|
+
"message": "Ûśêś ÂŔÎÁ r̂ól̂éŝ ón̂ ín̂ćôḿp̂át̂íb̂ĺê él̂ém̂én̂t́ŝ"
|
|
25
25
|
},
|
|
26
26
|
"core/audits/accessibility/aria-allowed-role.js | title": {
|
|
27
|
-
"message": "
|
|
27
|
+
"message": "Ûśêś ÂŔÎÁ r̂ól̂éŝ ó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)."
|