lighthouse 9.5.0-dev.20230130 → 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.
Files changed (33) hide show
  1. package/core/audits/byte-efficiency/offscreen-images.js +1 -1
  2. package/core/audits/critical-request-chains.d.ts +6 -6
  3. package/core/audits/critical-request-chains.js +15 -15
  4. package/core/audits/dobetterweb/doctype.js +4 -4
  5. package/core/audits/font-display.js +1 -1
  6. package/core/audits/network-requests.js +4 -4
  7. package/core/audits/redirects.js +2 -1
  8. package/core/audits/uses-rel-preconnect.js +5 -4
  9. package/core/gather/driver/network-monitor.js +2 -2
  10. package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js +4 -4
  11. package/core/lib/bf-cache-strings.js +1 -1
  12. package/core/lib/dependency-graph/network-node.js +2 -2
  13. package/core/lib/dependency-graph/simulator/network-analyzer.js +8 -6
  14. package/core/lib/lighthouse-compatibility.d.ts +8 -0
  15. package/core/lib/lighthouse-compatibility.js +139 -0
  16. package/core/lib/navigation-error.js +3 -1
  17. package/core/lib/network-recorder.js +1 -1
  18. package/core/lib/network-request.d.ts +7 -7
  19. package/core/lib/network-request.js +26 -27
  20. package/dist/report/bundle.esm.js +138 -120
  21. package/dist/report/flow.js +18 -12
  22. package/dist/report/standalone.js +9 -3
  23. package/package.json +1 -1
  24. package/report/assets/styles.css +1 -1
  25. package/report/renderer/components.js +1 -1
  26. package/report/renderer/i18n-formatter.js +1 -1
  27. package/report/renderer/report-utils.d.ts +2 -2
  28. package/report/renderer/report-utils.js +7 -122
  29. package/report/test/renderer/report-utils-test.js +0 -170
  30. package/report/tsconfig.json +1 -0
  31. package/shared/localization/locales/en-US.json +3 -3
  32. package/shared/localization/locales/en-XL.json +3 -3
  33. 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.startTime,
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.Audit.Details.SimpleCriticalRequestNode[string]}} CrcNodeInfo */
3
+ /** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
4
4
  /**
5
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
5
+ * @param {LH.Artifacts.CriticalRequestNode} tree
6
6
  * @param {function(CrcNodeInfo): void} cb
7
7
  */
8
- static _traverse(tree: LH.Audit.Details.SimpleCriticalRequestNode, cb: (arg0: {
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.Audit.Details.SimpleCriticalRequestNode[string];
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.Audit.Details.SimpleCriticalRequestNode} tree
17
+ * @param {LH.Artifacts.CriticalRequestNode} tree
18
18
  * @return {{duration: number, length: number, transferSize: number}}
19
19
  */
20
- static _getLongestChain(tree: LH.Audit.Details.SimpleCriticalRequestNode): {
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.Audit.Details.SimpleCriticalRequestNode[string]}} CrcNodeInfo */
44
+ /** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
45
45
 
46
46
  /**
47
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
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.Audit.Details.SimpleCriticalRequestNode} node
52
+ * @param {LH.Artifacts.CriticalRequestNode} node
53
53
  * @param {number} depth
54
- * @param {number=} startTime
54
+ * @param {number=} networkRequestTime
55
55
  * @param {number=} transferSize
56
56
  */
57
- function walk(node, depth, startTime, transferSize = 0) {
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 (!startTime) {
65
- startTime = child.request.startTime;
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.endTime - startTime,
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, startTime);
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.Audit.Details.SimpleCriticalRequestNode} tree
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 * 1000;
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.startTime / 1000,
127
- endTime: request.endTime / 1000,
128
- responseReceivedTime: request.responseReceivedTime / 1000,
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(flattenedChains);
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.endTime - record.startTime, 3000);
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
- startTime: normalizeTime(record.startTime),
69
- endTime: normalizeTime(record.endTime),
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: 'startTime', valueType: 'ms', granularity: 1, label: 'Start Time'},
92
- {key: 'endTime', valueType: 'ms', granularity: 1, label: 'End Time'},
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',
@@ -125,7 +125,8 @@ class Redirects extends Audit {
125
125
  }
126
126
 
127
127
  const lanternTimingDeltaMs = redirectedTiming.startTime - initialTiming.startTime;
128
- const observedTimingDeltaMs = redirectedRequest.startTime - initialRequest.startTime;
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
- return Math.max(0, record.startTime - mainResource.endTime) < PRECONNECT_SOCKET_MAX_IDLE_IN_MS;
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.startTime < firstRecord.startTime) ? record : firstRecord;
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.startTime -
190
- mainResource.endTime +
190
+ firstRecordOfOrigin.networkRequestTime -
191
+ mainResource.networkEndTime +
191
192
  firstRecordOfOrigin.timing.dnsStart;
192
193
 
193
194
  const wastedMs = Math.min(connectionTime, timeBetweenMainResourceAndDnsStart);
@@ -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.startTime * 1000, isStart: true});
221
+ timeBoundaries.push({time: request.networkRequestTime * 1000, isStart: true});
222
222
  if (request.finished) {
223
- timeBoundaries.push({time: request.endTime * 1000, isStart: false});
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.endTime),
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.endTime;
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.startTime,
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.startTime,
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
- 'IPages that JavaScript is injected into by extensions are not currently eligible for back/forward cache.',
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.startTime * 1000;
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.endTime * 1000;
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.endTime - record.startTime;
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.endTime)
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.startTime >= earliestReusePossible || record.protocol === 'h2'
288
+ record.networkRequestTime >= earliestReusePossible || record.protocol === 'h2'
289
289
  );
290
290
  }
291
291
 
292
- const firstRecord = originRecords.reduce((a, b) => (a.startTime > b.startTime ? b : a));
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.responseReceivedTime / 1000, isStart: true});
403
- boundaries.push({time: record.endTime / 1000, isStart: false});
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) => (r.startTime < min.startTime ? r : min));
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(cand => cand.responseReceivedTime <= record.startTime);
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 endTime between the observed Lighthouse endTime and Lightrider's derived endTime.
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
- startTime: number;
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
- responseReceivedTime: number;
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 responseReceivedTime to the endTime if endTime is earlier.
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
- _updateResponseReceivedTimeIfNecessary(): void;
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.