lighthouse 9.5.0-dev.20230129 → 9.5.0-dev.20230131
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/byte-efficiency/offscreen-images.js +1 -1
- package/core/audits/critical-request-chains.d.ts +6 -6
- package/core/audits/critical-request-chains.js +15 -15
- package/core/audits/dobetterweb/doctype.js +4 -4
- package/core/audits/font-display.js +1 -1
- package/core/audits/network-requests.js +4 -4
- package/core/audits/redirects.js +2 -1
- package/core/audits/uses-rel-preconnect.js +5 -4
- package/core/computed/entity-classification.js +2 -1
- package/core/computed/resource-summary.js +2 -1
- package/core/gather/driver/network-monitor.js +2 -2
- package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js +4 -4
- package/core/lib/bf-cache-strings.js +1 -1
- package/core/lib/dependency-graph/network-node.js +2 -2
- package/core/lib/dependency-graph/simulator/network-analyzer.js +8 -6
- package/core/lib/lighthouse-compatibility.d.ts +8 -0
- package/core/lib/lighthouse-compatibility.js +139 -0
- package/core/lib/navigation-error.js +3 -1
- package/core/lib/network-recorder.js +1 -1
- package/core/lib/network-request.d.ts +7 -7
- package/core/lib/network-request.js +26 -27
- package/dist/report/bundle.esm.js +138 -120
- package/dist/report/flow.js +18 -12
- package/dist/report/standalone.js +9 -3
- package/package.json +1 -1
- package/report/assets/styles.css +1 -1
- package/report/renderer/components.js +1 -1
- package/report/renderer/i18n-formatter.js +1 -1
- package/report/renderer/report-utils.d.ts +2 -2
- package/report/renderer/report-utils.js +7 -122
- package/report/test/renderer/report-utils-test.js +0 -170
- package/report/tsconfig.json +1 -0
- package/shared/localization/locales/en-US.json +3 -3
- package/shared/localization/locales/en-XL.json +3 -3
- package/tsconfig.json +1 -0
|
@@ -102,7 +102,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
102
102
|
return {
|
|
103
103
|
node: ByteEfficiencyAudit.makeNodeItem(image.node),
|
|
104
104
|
url,
|
|
105
|
-
requestStartTime: networkRecord.
|
|
105
|
+
requestStartTime: networkRecord.networkRequestTime,
|
|
106
106
|
totalBytes,
|
|
107
107
|
wastedBytes,
|
|
108
108
|
wastedPercent: 100 * wastedRatio,
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
export default CriticalRequestChains;
|
|
2
2
|
declare class CriticalRequestChains extends Audit {
|
|
3
|
-
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.
|
|
3
|
+
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
|
|
4
4
|
/**
|
|
5
|
-
* @param {LH.
|
|
5
|
+
* @param {LH.Artifacts.CriticalRequestNode} tree
|
|
6
6
|
* @param {function(CrcNodeInfo): void} cb
|
|
7
7
|
*/
|
|
8
|
-
static _traverse(tree: LH.
|
|
8
|
+
static _traverse(tree: LH.Artifacts.CriticalRequestNode, cb: (arg0: {
|
|
9
9
|
depth: number;
|
|
10
10
|
id: string;
|
|
11
11
|
chainDuration: number;
|
|
12
12
|
chainTransferSize: number;
|
|
13
|
-
node: LH.
|
|
13
|
+
node: LH.Artifacts.CriticalRequestNode[string];
|
|
14
14
|
}) => void): void;
|
|
15
15
|
/**
|
|
16
16
|
* Get stats about the longest initiator chain (as determined by time duration)
|
|
17
|
-
* @param {LH.
|
|
17
|
+
* @param {LH.Artifacts.CriticalRequestNode} tree
|
|
18
18
|
* @return {{duration: number, length: number, transferSize: number}}
|
|
19
19
|
*/
|
|
20
|
-
static _getLongestChain(tree: LH.
|
|
20
|
+
static _getLongestChain(tree: LH.Artifacts.CriticalRequestNode): {
|
|
21
21
|
duration: number;
|
|
22
22
|
length: number;
|
|
23
23
|
transferSize: number;
|
|
@@ -41,28 +41,28 @@ class CriticalRequestChains extends Audit {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.
|
|
44
|
+
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* @param {LH.
|
|
47
|
+
* @param {LH.Artifacts.CriticalRequestNode} tree
|
|
48
48
|
* @param {function(CrcNodeInfo): void} cb
|
|
49
49
|
*/
|
|
50
50
|
static _traverse(tree, cb) {
|
|
51
51
|
/**
|
|
52
|
-
* @param {LH.
|
|
52
|
+
* @param {LH.Artifacts.CriticalRequestNode} node
|
|
53
53
|
* @param {number} depth
|
|
54
|
-
* @param {number=}
|
|
54
|
+
* @param {number=} networkRequestTime
|
|
55
55
|
* @param {number=} transferSize
|
|
56
56
|
*/
|
|
57
|
-
function walk(node, depth,
|
|
57
|
+
function walk(node, depth, networkRequestTime, transferSize = 0) {
|
|
58
58
|
const children = Object.keys(node);
|
|
59
59
|
if (children.length === 0) {
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
children.forEach(id => {
|
|
63
63
|
const child = node[id];
|
|
64
|
-
if (!
|
|
65
|
-
|
|
64
|
+
if (!networkRequestTime) {
|
|
65
|
+
networkRequestTime = child.request.networkRequestTime;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Call the callback with the info for this child.
|
|
@@ -70,13 +70,13 @@ class CriticalRequestChains extends Audit {
|
|
|
70
70
|
depth,
|
|
71
71
|
id,
|
|
72
72
|
node: child,
|
|
73
|
-
chainDuration: child.request.
|
|
73
|
+
chainDuration: child.request.networkEndTime - networkRequestTime,
|
|
74
74
|
chainTransferSize: transferSize + child.request.transferSize,
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
// Carry on walking.
|
|
78
78
|
if (child.children) {
|
|
79
|
-
walk(child.children, depth + 1,
|
|
79
|
+
walk(child.children, depth + 1, networkRequestTime);
|
|
80
80
|
}
|
|
81
81
|
}, '');
|
|
82
82
|
}
|
|
@@ -86,7 +86,7 @@ class CriticalRequestChains extends Audit {
|
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Get stats about the longest initiator chain (as determined by time duration)
|
|
89
|
-
* @param {LH.
|
|
89
|
+
* @param {LH.Artifacts.CriticalRequestNode} tree
|
|
90
90
|
* @return {{duration: number, length: number, transferSize: number}}
|
|
91
91
|
*/
|
|
92
92
|
static _getLongestChain(tree) {
|
|
@@ -96,7 +96,7 @@ class CriticalRequestChains extends Audit {
|
|
|
96
96
|
transferSize: 0,
|
|
97
97
|
};
|
|
98
98
|
CriticalRequestChains._traverse(tree, opts => {
|
|
99
|
-
const duration = opts.chainDuration
|
|
99
|
+
const duration = opts.chainDuration;
|
|
100
100
|
if (duration > longest.duration) {
|
|
101
101
|
longest.duration = duration;
|
|
102
102
|
longest.transferSize = opts.chainTransferSize;
|
|
@@ -123,9 +123,9 @@ class CriticalRequestChains extends Audit {
|
|
|
123
123
|
const request = opts.node.request;
|
|
124
124
|
const simpleRequest = {
|
|
125
125
|
url: request.url,
|
|
126
|
-
startTime: request.
|
|
127
|
-
endTime: request.
|
|
128
|
-
responseReceivedTime: request.
|
|
126
|
+
startTime: request.networkRequestTime / 1000,
|
|
127
|
+
endTime: request.networkEndTime / 1000,
|
|
128
|
+
responseReceivedTime: request.responseHeadersEndTime / 1000,
|
|
129
129
|
transferSize: request.transferSize,
|
|
130
130
|
};
|
|
131
131
|
|
|
@@ -199,7 +199,7 @@ class CriticalRequestChains extends Audit {
|
|
|
199
199
|
walk(initialNavChildren, 0);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
const longestChain = CriticalRequestChains._getLongestChain(
|
|
202
|
+
const longestChain = CriticalRequestChains._getLongestChain(chains);
|
|
203
203
|
|
|
204
204
|
return {
|
|
205
205
|
score: Number(chainCount === 0),
|
|
@@ -18,10 +18,10 @@ const UIStrings = {
|
|
|
18
18
|
'[Learn more about the doctype declaration](https://developer.chrome.com/docs/lighthouse/best-practices/doctype/).',
|
|
19
19
|
/** Explanatory message stating that the document has no doctype. */
|
|
20
20
|
explanationNoDoctype: 'Document must contain a doctype',
|
|
21
|
-
/** Explanatory message stating that the document has wrong doctype */
|
|
22
|
-
explanationWrongDoctype: 'Document contains a doctype that triggers quirks-mode',
|
|
23
|
-
/** Explanatory message stating that the document has wrong doctype */
|
|
24
|
-
explanationLimitedQuirks: 'Document contains a doctype that triggers limited-quirks-mode',
|
|
21
|
+
/** Explanatory message stating that the document has wrong doctype that triggers `quirks-mode` */
|
|
22
|
+
explanationWrongDoctype: 'Document contains a `doctype` that triggers `quirks-mode`',
|
|
23
|
+
/** Explanatory message stating that the document has wrong doctype that triggers `limited-quirks-mode` */
|
|
24
|
+
explanationLimitedQuirks: 'Document contains a `doctype` that triggers `limited-quirks-mode`',
|
|
25
25
|
/** Explanatory message stating that the publicId field is not empty. */
|
|
26
26
|
explanationPublicId: 'Expected publicId to be an empty string',
|
|
27
27
|
/** Explanatory message stating that the systemId field is not empty. */
|
|
@@ -165,7 +165,7 @@ class FontDisplay extends Audit {
|
|
|
165
165
|
.map(record => {
|
|
166
166
|
// In reality the end time should be calculated with paint time included
|
|
167
167
|
// all browsers wait 3000ms to block text so we make sure 3000 is our max wasted time
|
|
168
|
-
const wastedMs = Math.min(record.
|
|
168
|
+
const wastedMs = Math.min(record.networkEndTime - record.networkRequestTime, 3000);
|
|
169
169
|
|
|
170
170
|
return {
|
|
171
171
|
url: record.url,
|
|
@@ -65,8 +65,8 @@ class NetworkRequests extends Audit {
|
|
|
65
65
|
url: UrlUtils.elideDataURI(record.url),
|
|
66
66
|
protocol: record.protocol,
|
|
67
67
|
rendererStartTime: normalizeTime(record.rendererStartTime),
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
networkRequestTime: normalizeTime(record.networkRequestTime),
|
|
69
|
+
networkEndTime: normalizeTime(record.networkEndTime),
|
|
70
70
|
finished: record.finished,
|
|
71
71
|
transferSize: record.transferSize,
|
|
72
72
|
resourceSize: record.resourceSize,
|
|
@@ -88,8 +88,8 @@ class NetworkRequests extends Audit {
|
|
|
88
88
|
const headings = [
|
|
89
89
|
{key: 'url', valueType: 'url', label: 'URL'},
|
|
90
90
|
{key: 'protocol', valueType: 'text', label: 'Protocol'},
|
|
91
|
-
{key: '
|
|
92
|
-
{key: '
|
|
91
|
+
{key: 'networkRequestTime', valueType: 'ms', granularity: 1, label: 'Network Request Time'},
|
|
92
|
+
{key: 'networkEndTime', valueType: 'ms', granularity: 1, label: 'Network End Time'},
|
|
93
93
|
{
|
|
94
94
|
key: 'transferSize',
|
|
95
95
|
valueType: 'bytes',
|
package/core/audits/redirects.js
CHANGED
|
@@ -125,7 +125,8 @@ class Redirects extends Audit {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const lanternTimingDeltaMs = redirectedTiming.startTime - initialTiming.startTime;
|
|
128
|
-
const observedTimingDeltaMs = redirectedRequest.
|
|
128
|
+
const observedTimingDeltaMs = redirectedRequest.networkRequestTime -
|
|
129
|
+
initialRequest.networkRequestTime;
|
|
129
130
|
const wastedMs = settings.throttlingMethod === 'simulate' ?
|
|
130
131
|
lanternTimingDeltaMs : observedTimingDeltaMs;
|
|
131
132
|
totalWastedMs += wastedMs;
|
|
@@ -95,7 +95,8 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
95
95
|
* @return {boolean}
|
|
96
96
|
*/
|
|
97
97
|
static socketStartTimeIsBelowThreshold(record, mainResource) {
|
|
98
|
-
|
|
98
|
+
const timeSinceMainEnd = Math.max(0, record.networkRequestTime - mainResource.networkEndTime);
|
|
99
|
+
return timeSinceMainEnd < PRECONNECT_SOCKET_MAX_IDLE_IN_MS;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
/**
|
|
@@ -169,7 +170,7 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
169
170
|
// Sometimes requests are done simultaneous and the connection has not been made
|
|
170
171
|
// chrome will try to connect for each network record, we get the first record
|
|
171
172
|
const firstRecordOfOrigin = records.reduce((firstRecord, record) => {
|
|
172
|
-
return (record.
|
|
173
|
+
return (record.networkRequestTime < firstRecord.networkRequestTime) ? record : firstRecord;
|
|
173
174
|
});
|
|
174
175
|
|
|
175
176
|
// Skip the origin if we don't have timing information
|
|
@@ -186,8 +187,8 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
186
187
|
if (firstRecordOfOrigin.parsedURL.scheme === 'https') connectionTime = connectionTime * 2;
|
|
187
188
|
|
|
188
189
|
const timeBetweenMainResourceAndDnsStart =
|
|
189
|
-
firstRecordOfOrigin.
|
|
190
|
-
mainResource.
|
|
190
|
+
firstRecordOfOrigin.networkRequestTime -
|
|
191
|
+
mainResource.networkEndTime +
|
|
191
192
|
firstRecordOfOrigin.timing.dnsStart;
|
|
192
193
|
|
|
193
194
|
const wastedMs = Math.min(connectionTime, timeBetweenMainResourceAndDnsStart);
|
|
@@ -98,5 +98,6 @@ class EntityClassification {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const EntityClassificationComputed = makeComputedArtifact(EntityClassification,
|
|
101
|
+
const EntityClassificationComputed = makeComputedArtifact(EntityClassification,
|
|
102
|
+
['URL', 'devtoolsLog']);
|
|
102
103
|
export {EntityClassificationComputed as EntityClassification};
|
|
@@ -107,7 +107,8 @@ class ResourceSummary {
|
|
|
107
107
|
*/
|
|
108
108
|
static async compute_(data, context) {
|
|
109
109
|
const networkRecords = await NetworkRecords.request(data.devtoolsLog, context);
|
|
110
|
-
const classifiedEntities = await EntityClassification.request(
|
|
110
|
+
const classifiedEntities = await EntityClassification.request(
|
|
111
|
+
{URL: data.URL, devtoolsLog: data.devtoolsLog}, context);
|
|
111
112
|
return ResourceSummary.summarize(networkRecords, data.URL, data.budgets, classifiedEntities);
|
|
112
113
|
}
|
|
113
114
|
}
|
|
@@ -218,9 +218,9 @@ class NetworkMonitor extends NetworkMonitorEventEmitter {
|
|
|
218
218
|
if (request.protocol === 'ws' || request.protocol === 'wss') return;
|
|
219
219
|
|
|
220
220
|
// convert the network timestamp to ms
|
|
221
|
-
timeBoundaries.push({time: request.
|
|
221
|
+
timeBoundaries.push({time: request.networkRequestTime * 1000, isStart: true});
|
|
222
222
|
if (request.finished) {
|
|
223
|
-
timeBoundaries.push({time: request.
|
|
223
|
+
timeBoundaries.push({time: request.networkEndTime * 1000, isStart: false});
|
|
224
224
|
}
|
|
225
225
|
});
|
|
226
226
|
|
|
@@ -155,7 +155,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
|
|
|
155
155
|
*/
|
|
156
156
|
static async findBlockingTags(driver, networkRecords) {
|
|
157
157
|
const firstRequestEndTime = networkRecords.reduce(
|
|
158
|
-
(min, record) => Math.min(min, record.
|
|
158
|
+
(min, record) => Math.min(min, record.networkEndTime),
|
|
159
159
|
Infinity
|
|
160
160
|
);
|
|
161
161
|
const tags = await driver.executionContext.evaluate(collectTagsThatBlockFirstPaint, {args: []});
|
|
@@ -167,7 +167,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
|
|
|
167
167
|
const request = requests.get(tag.url);
|
|
168
168
|
if (!request || request.isLinkPreload) continue;
|
|
169
169
|
|
|
170
|
-
let endTime = request.
|
|
170
|
+
let endTime = request.networkEndTime;
|
|
171
171
|
let mediaChanges;
|
|
172
172
|
|
|
173
173
|
if (tag.tagName === 'LINK') {
|
|
@@ -180,7 +180,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
|
|
|
180
180
|
if (timesResourceBecameNonBlocking.length > 0) {
|
|
181
181
|
const earliestNonBlockingTime = Math.min(...timesResourceBecameNonBlocking);
|
|
182
182
|
const lastTimeResourceWasBlocking = Math.max(
|
|
183
|
-
request.
|
|
183
|
+
request.networkRequestTime,
|
|
184
184
|
firstRequestEndTime + earliestNonBlockingTime / 1000
|
|
185
185
|
);
|
|
186
186
|
endTime = Math.min(endTime, lastTimeResourceWasBlocking);
|
|
@@ -194,7 +194,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
|
|
|
194
194
|
result.push({
|
|
195
195
|
tag: {tagName, url, mediaChanges},
|
|
196
196
|
transferSize: request.transferSize,
|
|
197
|
-
startTime: request.
|
|
197
|
+
startTime: request.networkRequestTime,
|
|
198
198
|
endTime,
|
|
199
199
|
});
|
|
200
200
|
|
|
@@ -367,7 +367,7 @@ const UIStrings = {
|
|
|
367
367
|
* @description Description text for not restored reason InjectedJavascript.
|
|
368
368
|
*/
|
|
369
369
|
injectedJavascript:
|
|
370
|
-
'
|
|
370
|
+
'Pages that JavaScript is injected into by extensions are not currently eligible for back/forward cache.',
|
|
371
371
|
/**
|
|
372
372
|
* @description Description text for not restored reason InjectedStyleSheet.
|
|
373
373
|
*/
|
|
@@ -26,14 +26,14 @@ class NetworkNode extends BaseNode {
|
|
|
26
26
|
* @return {number}
|
|
27
27
|
*/
|
|
28
28
|
get startTime() {
|
|
29
|
-
return this._record.
|
|
29
|
+
return this._record.networkRequestTime * 1000;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* @return {number}
|
|
34
34
|
*/
|
|
35
35
|
get endTime() {
|
|
36
|
-
return this._record.
|
|
36
|
+
return this._record.networkEndTime * 1000;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -150,7 +150,7 @@ class NetworkAnalyzer {
|
|
|
150
150
|
if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
|
|
151
151
|
|
|
152
152
|
// Compute the amount of time downloading everything after the first congestion window took
|
|
153
|
-
const totalTime = record.
|
|
153
|
+
const totalTime = record.networkEndTime - record.networkRequestTime;
|
|
154
154
|
const downloadTimeAfterFirstByte = totalTime - timing.receiveHeadersEnd;
|
|
155
155
|
const numberOfRoundTrips = Math.log2(record.transferSize / INITIAL_CWD);
|
|
156
156
|
|
|
@@ -279,17 +279,19 @@ class NetworkAnalyzer {
|
|
|
279
279
|
const groupedByOrigin = NetworkAnalyzer.groupByOrigin(records);
|
|
280
280
|
for (const [_, originRecords] of groupedByOrigin.entries()) {
|
|
281
281
|
const earliestReusePossible = originRecords
|
|
282
|
-
.map(record => record.
|
|
282
|
+
.map(record => record.networkEndTime)
|
|
283
283
|
.reduce((a, b) => Math.min(a, b), Infinity);
|
|
284
284
|
|
|
285
285
|
for (const record of originRecords) {
|
|
286
286
|
connectionWasReused.set(
|
|
287
287
|
record.requestId,
|
|
288
|
-
record.
|
|
288
|
+
record.networkRequestTime >= earliestReusePossible || record.protocol === 'h2'
|
|
289
289
|
);
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
const firstRecord = originRecords.reduce((a, b) =>
|
|
292
|
+
const firstRecord = originRecords.reduce((a, b) => {
|
|
293
|
+
return a.networkRequestTime > b.networkRequestTime ? b : a;
|
|
294
|
+
});
|
|
293
295
|
connectionWasReused.set(firstRecord.requestId, false);
|
|
294
296
|
}
|
|
295
297
|
|
|
@@ -399,8 +401,8 @@ class NetworkAnalyzer {
|
|
|
399
401
|
|
|
400
402
|
// If we've made it this far, all the times we need should be valid (i.e. not undefined/-1).
|
|
401
403
|
totalBytes += record.transferSize;
|
|
402
|
-
boundaries.push({time: record.
|
|
403
|
-
boundaries.push({time: record.
|
|
404
|
+
boundaries.push({time: record.responseHeadersEndTime / 1000, isStart: true});
|
|
405
|
+
boundaries.push({time: record.networkEndTime / 1000, isStart: false});
|
|
404
406
|
return boundaries;
|
|
405
407
|
}, /** @type {Array<{time: number, isStart: boolean}>} */([])).sort((a, b) => a.time - b.time);
|
|
406
408
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ItemValueType = import('../../types/lhr/audit-details').default.ItemValueType;
|
|
2
|
+
/** @typedef {import('../../types/lhr/audit-details').default.ItemValueType} ItemValueType */
|
|
3
|
+
/**
|
|
4
|
+
* Upgrades an lhr object in-place to account for changes in the data structure over major versions.
|
|
5
|
+
* @param {LH.Result} lhr
|
|
6
|
+
*/
|
|
7
|
+
export function upgradeLhrForCompatibility(lhr: LH.Result): void;
|
|
8
|
+
//# sourceMappingURL=lighthouse-compatibility.d.ts.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Util} from '../../shared/util.js';
|
|
8
|
+
|
|
9
|
+
const SCREENSHOT_PREFIX = 'data:image/jpeg;base64,';
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('../../types/lhr/audit-details').default.ItemValueType} ItemValueType */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Upgrades an lhr object in-place to account for changes in the data structure over major versions.
|
|
15
|
+
* @param {LH.Result} lhr
|
|
16
|
+
*/
|
|
17
|
+
function upgradeLhrForCompatibility(lhr) {
|
|
18
|
+
// If LHR is older (≤3.0.3), it has no locale setting. Set default.
|
|
19
|
+
if (!lhr.configSettings.locale) {
|
|
20
|
+
lhr.configSettings.locale = 'en';
|
|
21
|
+
}
|
|
22
|
+
if (!lhr.configSettings.formFactor) {
|
|
23
|
+
// @ts-expect-error fallback handling for emulatedFormFactor
|
|
24
|
+
lhr.configSettings.formFactor = lhr.configSettings.emulatedFormFactor;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
lhr.finalDisplayedUrl = Util.getFinalDisplayedUrl(lhr);
|
|
28
|
+
lhr.mainDocumentUrl = Util.getMainDocumentUrl(lhr);
|
|
29
|
+
|
|
30
|
+
for (const audit of Object.values(lhr.audits)) {
|
|
31
|
+
// Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
|
|
32
|
+
// into 'notApplicable' (LHR ≥4.0).
|
|
33
|
+
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
|
|
34
|
+
// eslint-disable-next-line max-len
|
|
35
|
+
if (audit.scoreDisplayMode === 'not_applicable' || audit.scoreDisplayMode === 'not-applicable') {
|
|
36
|
+
audit.scoreDisplayMode = 'notApplicable';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (audit.details) {
|
|
40
|
+
// Turn `auditDetails.type` of undefined (LHR <4.2) and 'diagnostic' (LHR <5.0)
|
|
41
|
+
// into 'debugdata' (LHR ≥5.0).
|
|
42
|
+
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
|
|
43
|
+
if (audit.details.type === undefined || audit.details.type === 'diagnostic') {
|
|
44
|
+
// @ts-expect-error details is of type never.
|
|
45
|
+
audit.details.type = 'debugdata';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add the jpg data URL prefix to filmstrip screenshots without them (LHR <5.0).
|
|
49
|
+
if (audit.details.type === 'filmstrip') {
|
|
50
|
+
for (const screenshot of audit.details.items) {
|
|
51
|
+
if (!screenshot.data.startsWith(SCREENSHOT_PREFIX)) {
|
|
52
|
+
screenshot.data = SCREENSHOT_PREFIX + screenshot.data;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Circa 10.0, table items were refactored.
|
|
58
|
+
if (audit.details.type === 'table') {
|
|
59
|
+
for (const heading of audit.details.headings) {
|
|
60
|
+
/** @type {{itemType: ItemValueType|undefined, text: string|undefined}} */
|
|
61
|
+
// @ts-expect-error
|
|
62
|
+
const {itemType, text} = heading;
|
|
63
|
+
if (itemType !== undefined) {
|
|
64
|
+
heading.valueType = itemType;
|
|
65
|
+
// @ts-expect-error
|
|
66
|
+
delete heading.itemType;
|
|
67
|
+
}
|
|
68
|
+
if (text !== undefined) {
|
|
69
|
+
heading.label = text;
|
|
70
|
+
// @ts-expect-error
|
|
71
|
+
delete heading.text;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// @ts-expect-error
|
|
75
|
+
const subItemsItemType = heading.subItemsHeading?.itemType;
|
|
76
|
+
if (heading.subItemsHeading && subItemsItemType !== undefined) {
|
|
77
|
+
heading.subItemsHeading.valueType = subItemsItemType;
|
|
78
|
+
// @ts-expect-error
|
|
79
|
+
delete heading.subItemsHeading.itemType;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// TODO: convert printf-style displayValue.
|
|
85
|
+
// Added: #5099, v3
|
|
86
|
+
// Removed: #6767, v4
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// This backcompat converts old LHRs (<9.0.0) to use the new "hidden" group.
|
|
91
|
+
// Old LHRs used "no group" to identify audits that should be hidden in performance instead of the "hidden" group.
|
|
92
|
+
// Newer LHRs use "no group" to identify opportunities and diagnostics whose groups are assigned by details type.
|
|
93
|
+
const [majorVersion] = lhr.lighthouseVersion.split('.').map(Number);
|
|
94
|
+
const perfCategory = lhr.categories['performance'];
|
|
95
|
+
if (majorVersion < 9 && perfCategory) {
|
|
96
|
+
if (!lhr.categoryGroups) lhr.categoryGroups = {};
|
|
97
|
+
lhr.categoryGroups['hidden'] = {title: ''};
|
|
98
|
+
for (const auditRef of perfCategory.auditRefs) {
|
|
99
|
+
if (!auditRef.group) {
|
|
100
|
+
auditRef.group = 'hidden';
|
|
101
|
+
} else if (['load-opportunities', 'diagnostics'].includes(auditRef.group)) {
|
|
102
|
+
delete auditRef.group;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add some minimal stuff so older reports still work.
|
|
108
|
+
if (!lhr.environment) {
|
|
109
|
+
// @ts-expect-error
|
|
110
|
+
lhr.environment = {benchmarkIndex: 0};
|
|
111
|
+
}
|
|
112
|
+
if (!lhr.configSettings.screenEmulation) {
|
|
113
|
+
// @ts-expect-error
|
|
114
|
+
lhr.configSettings.screenEmulation = {};
|
|
115
|
+
}
|
|
116
|
+
if (!lhr.i18n) {
|
|
117
|
+
// @ts-expect-error
|
|
118
|
+
lhr.i18n = {};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// In 10.0, full-page-screenshot became a top-level property on the LHR.
|
|
122
|
+
if (lhr.audits['full-page-screenshot']) {
|
|
123
|
+
const details = /** @type {LH.Result.FullPageScreenshot=} */ (
|
|
124
|
+
lhr.audits['full-page-screenshot'].details);
|
|
125
|
+
if (details) {
|
|
126
|
+
lhr.fullPageScreenshot = {
|
|
127
|
+
screenshot: details.screenshot,
|
|
128
|
+
nodes: details.nodes,
|
|
129
|
+
};
|
|
130
|
+
} else {
|
|
131
|
+
lhr.fullPageScreenshot = null;
|
|
132
|
+
}
|
|
133
|
+
delete lhr.audits['full-page-screenshot'];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
upgradeLhrForCompatibility,
|
|
139
|
+
};
|
|
@@ -125,7 +125,9 @@ function getPageLoadError(navigationError, context) {
|
|
|
125
125
|
record.resourceType === NetworkRequest.TYPES.Document
|
|
126
126
|
);
|
|
127
127
|
if (documentRequests.length) {
|
|
128
|
-
mainRecord = documentRequests.reduce((min, r) =>
|
|
128
|
+
mainRecord = documentRequests.reduce((min, r) => {
|
|
129
|
+
return r.networkRequestTime < min.networkRequestTime ? r : min;
|
|
130
|
+
});
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
133
|
|
|
@@ -248,7 +248,7 @@ class NetworkRecorder extends RequestEventEmitter {
|
|
|
248
248
|
|
|
249
249
|
let candidates = recordsByURL.get(initiatorURL) || [];
|
|
250
250
|
// The initiator must come before the initiated request.
|
|
251
|
-
candidates = candidates.filter(
|
|
251
|
+
candidates = candidates.filter(c => c.responseHeadersEndTime <= record.networkRequestTime);
|
|
252
252
|
if (candidates.length > 1) {
|
|
253
253
|
// Disambiguate based on prefetch. Prefetch requests have type 'Other' and cannot
|
|
254
254
|
// initiate requests, so we drop them here.
|
|
@@ -14,7 +14,7 @@ export type ParsedURL = {
|
|
|
14
14
|
securityOrigin: string;
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
|
-
* The difference in
|
|
17
|
+
* The difference in networkEndTime between the observed Lighthouse networkEndTime and Lightrider's derived networkEndTime.
|
|
18
18
|
*/
|
|
19
19
|
export type LightriderStatistics = {
|
|
20
20
|
/**
|
|
@@ -92,11 +92,11 @@ export class NetworkRequest {
|
|
|
92
92
|
* When the network service is about to handle a request, ie. just before going to the
|
|
93
93
|
* HTTP cache or going to the network for DNS/connection setup, in milliseconds.
|
|
94
94
|
*/
|
|
95
|
-
|
|
96
|
-
/** When the last byte of the response body is received, in milliseconds. */
|
|
97
|
-
endTime: number;
|
|
95
|
+
networkRequestTime: number;
|
|
98
96
|
/** When the last byte of the response headers is received, in milliseconds. */
|
|
99
|
-
|
|
97
|
+
responseHeadersEndTime: number;
|
|
98
|
+
/** When the last byte of the response body is received, in milliseconds. */
|
|
99
|
+
networkEndTime: number;
|
|
100
100
|
transferSize: number;
|
|
101
101
|
resourceSize: number;
|
|
102
102
|
fromDiskCache: boolean;
|
|
@@ -194,10 +194,10 @@ export class NetworkRequest {
|
|
|
194
194
|
*/
|
|
195
195
|
_recomputeTimesWithResourceTiming(timing: LH.Crdp.Network.ResourceTiming): void;
|
|
196
196
|
/**
|
|
197
|
-
* Update
|
|
197
|
+
* Update responseHeadersEndTime to the networkEndTime if networkEndTime is earlier.
|
|
198
198
|
* A response can't be received after the entire request finished.
|
|
199
199
|
*/
|
|
200
|
-
|
|
200
|
+
_updateResponseHeadersEndTimeIfNecessary(): void;
|
|
201
201
|
/**
|
|
202
202
|
* LR loses transfer size information, but passes it in the 'X-TotalFetchedSize' header.
|
|
203
203
|
* 'X-TotalFetchedSize' is the canonical transfer size in LR. Nothing should supersede it.
|