lighthouse 11.7.0-dev.20240331 → 11.7.0-dev.20240402

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 (72) hide show
  1. package/cli/test/smokehouse/core-tests.js +0 -2
  2. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -16
  3. package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -38
  4. package/core/audits/byte-efficiency/offscreen-images.d.ts +0 -11
  5. package/core/audits/byte-efficiency/offscreen-images.js +0 -15
  6. package/core/audits/dobetterweb/uses-http2.d.ts +0 -13
  7. package/core/audits/dobetterweb/uses-http2.js +2 -34
  8. package/core/audits/seo/is-crawlable.d.ts +2 -1
  9. package/core/computed/page-dependency-graph.js +2 -1
  10. package/core/config/default-config.js +1 -6
  11. package/core/gather/gatherers/accessibility.js +1 -3
  12. package/core/lib/lantern/page-dependency-graph.d.ts +4 -4
  13. package/core/lib/lantern/page-dependency-graph.js +5 -5
  14. package/core/lib/proto-preprocessor.js +24 -8
  15. package/dist/report/flow.js +14 -14
  16. package/flow-report/src/summary/category.tsx +10 -12
  17. package/package.json +1 -1
  18. package/shared/localization/locales/ar-XB.json +0 -30
  19. package/shared/localization/locales/ar.json +0 -30
  20. package/shared/localization/locales/bg.json +0 -30
  21. package/shared/localization/locales/ca.json +0 -30
  22. package/shared/localization/locales/cs.json +0 -30
  23. package/shared/localization/locales/da.json +0 -30
  24. package/shared/localization/locales/de.json +0 -30
  25. package/shared/localization/locales/el.json +0 -30
  26. package/shared/localization/locales/en-GB.json +0 -30
  27. package/shared/localization/locales/en-US.json +0 -30
  28. package/shared/localization/locales/en-XA.json +0 -30
  29. package/shared/localization/locales/en-XL.json +0 -30
  30. package/shared/localization/locales/es-419.json +0 -30
  31. package/shared/localization/locales/es.json +0 -30
  32. package/shared/localization/locales/fi.json +0 -30
  33. package/shared/localization/locales/fil.json +0 -30
  34. package/shared/localization/locales/fr.json +0 -30
  35. package/shared/localization/locales/he.json +0 -30
  36. package/shared/localization/locales/hi.json +0 -30
  37. package/shared/localization/locales/hr.json +0 -30
  38. package/shared/localization/locales/hu.json +0 -30
  39. package/shared/localization/locales/id.json +0 -30
  40. package/shared/localization/locales/it.json +0 -30
  41. package/shared/localization/locales/ja.json +0 -30
  42. package/shared/localization/locales/ko.json +0 -30
  43. package/shared/localization/locales/lt.json +0 -30
  44. package/shared/localization/locales/lv.json +0 -30
  45. package/shared/localization/locales/nl.json +0 -30
  46. package/shared/localization/locales/no.json +0 -30
  47. package/shared/localization/locales/pl.json +0 -30
  48. package/shared/localization/locales/pt-PT.json +0 -30
  49. package/shared/localization/locales/pt.json +0 -30
  50. package/shared/localization/locales/ro.json +0 -30
  51. package/shared/localization/locales/ru.json +0 -30
  52. package/shared/localization/locales/sk.json +0 -30
  53. package/shared/localization/locales/sl.json +0 -30
  54. package/shared/localization/locales/sr-Latn.json +0 -30
  55. package/shared/localization/locales/sr.json +0 -30
  56. package/shared/localization/locales/sv.json +0 -30
  57. package/shared/localization/locales/ta.json +0 -30
  58. package/shared/localization/locales/te.json +0 -30
  59. package/shared/localization/locales/th.json +0 -30
  60. package/shared/localization/locales/tr.json +0 -30
  61. package/shared/localization/locales/uk.json +0 -30
  62. package/shared/localization/locales/vi.json +0 -30
  63. package/shared/localization/locales/zh-HK.json +0 -30
  64. package/shared/localization/locales/zh-TW.json +0 -30
  65. package/shared/localization/locales/zh.json +0 -30
  66. package/types/lhr/audit-details.d.ts +9 -4
  67. package/core/audits/accessibility/duplicate-id-active.d.ts +0 -10
  68. package/core/audits/accessibility/duplicate-id-active.js +0 -42
  69. package/core/audits/seo/tap-targets.d.ts +0 -50
  70. package/core/audits/seo/tap-targets.js +0 -352
  71. package/core/gather/gatherers/seo/tap-targets.d.ts +0 -21
  72. package/core/gather/gatherers/seo/tap-targets.js +0 -389
@@ -61,7 +61,6 @@ import screenshot from './test-definitions/screenshot.js';
61
61
  import seoFailing from './test-definitions/seo-failing.js';
62
62
  import seoPassing from './test-definitions/seo-passing.js';
63
63
  import seoStatus403 from './test-definitions/seo-status-403.js';
64
- import seoTapTargets from './test-definitions/seo-tap-targets.js';
65
64
  import serviceWorkerReloaded from './test-definitions/service-worker-reloaded.js';
66
65
  import shiftAttribution from './test-definitions/shift-attribution.js';
67
66
  import sourceMaps from './test-definitions/source-maps.js';
@@ -126,7 +125,6 @@ const smokeTests = [
126
125
  seoFailing,
127
126
  seoPassing,
128
127
  seoStatus403,
129
- seoTapTargets,
130
128
  serviceWorkerReloaded,
131
129
  shiftAttribution,
132
130
  sourceMaps,
@@ -56,22 +56,6 @@ export class ByteEfficiencyAudit extends Audit {
56
56
  simulationBeforeChanges: LH.Gatherer.Simulation.Result;
57
57
  simulationAfterChanges: LH.Gatherer.Simulation.Result;
58
58
  };
59
- /**
60
- * Computes the estimated effect of all the byte savings on the maximum of the following:
61
- *
62
- * - end time of the last long task in the provided graph
63
- * - (if includeLoad is true or not provided) end time of the last node in the graph
64
- *
65
- * @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
66
- * @param {Node} graph
67
- * @param {Simulator} simulator
68
- * @param {{includeLoad?: boolean, providedWastedBytesByUrl?: Map<string, number>}=} options
69
- * @return {number}
70
- */
71
- static computeWasteWithTTIGraph(results: Array<LH.Audit.ByteEfficiencyItem>, graph: Node, simulator: Simulator, options?: {
72
- includeLoad?: boolean;
73
- providedWastedBytesByUrl?: Map<string, number>;
74
- } | undefined): number;
75
59
  /**
76
60
  * @param {ByteEfficiencyProduct} result
77
61
  * @param {Simulator} simulator
@@ -5,11 +5,9 @@
5
5
  */
6
6
 
7
7
  import {Audit} from '../audit.js';
8
- import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
9
8
  import * as i18n from '../../lib/i18n/i18n.js';
10
9
  import {NetworkRecords} from '../../computed/network-records.js';
11
10
  import {LoadSimulator} from '../../computed/load-simulator.js';
12
- import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
13
11
  import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
14
12
  import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
15
13
  import {LCPImageRecord} from '../../computed/lcp-image-record.js';
@@ -151,37 +149,6 @@ class ByteEfficiencyAudit extends Audit {
151
149
  };
152
150
  }
153
151
 
154
- /**
155
- * Computes the estimated effect of all the byte savings on the maximum of the following:
156
- *
157
- * - end time of the last long task in the provided graph
158
- * - (if includeLoad is true or not provided) end time of the last node in the graph
159
- *
160
- * @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
161
- * @param {Node} graph
162
- * @param {Simulator} simulator
163
- * @param {{includeLoad?: boolean, providedWastedBytesByUrl?: Map<string, number>}=} options
164
- * @return {number}
165
- */
166
- static computeWasteWithTTIGraph(results, graph, simulator, options) {
167
- options = Object.assign({includeLoad: true}, options);
168
- const {savings: savingsOnOverallLoad, simulationBeforeChanges, simulationAfterChanges} =
169
- this.computeWasteWithGraph(results, graph, simulator, {
170
- ...options,
171
- label: 'overallLoad',
172
- });
173
-
174
- const savingsOnTTI =
175
- LanternInteractive.getLastLongTaskEndTime(simulationBeforeChanges.nodeTimings) -
176
- LanternInteractive.getLastLongTaskEndTime(simulationAfterChanges.nodeTimings);
177
-
178
- let savings = savingsOnTTI;
179
- if (options.includeLoad) savings = Math.max(savings, savingsOnOverallLoad);
180
-
181
- // Round waste to nearest 10ms
182
- return Math.round(Math.max(savings, 0) / 10) * 10;
183
- }
184
-
185
152
  /**
186
153
  * @param {ByteEfficiencyProduct} result
187
154
  * @param {Simulator} simulator
@@ -204,7 +171,6 @@ class ByteEfficiencyAudit extends Audit {
204
171
  // This is useful information in the LHR and should be preserved.
205
172
  let wastedMs;
206
173
  if (metricComputationInput.gatherContext.gatherMode === 'navigation') {
207
- const graph = await PageDependencyGraph.request(metricComputationInput, context);
208
174
  const {
209
175
  optimisticGraph: optimisticFCPGraph,
210
176
  } = await LanternFirstContentfulPaint.request(metricComputationInput, context);
@@ -212,10 +178,6 @@ class ByteEfficiencyAudit extends Audit {
212
178
  optimisticGraph: optimisticLCPGraph,
213
179
  } = await LanternLargestContentfulPaint.request(metricComputationInput, context);
214
180
 
215
- wastedMs = this.computeWasteWithTTIGraph(results, graph, simulator, {
216
- providedWastedBytesByUrl: result.wastedBytesByUrl,
217
- });
218
-
219
181
  const {savings: fcpSavings} = this.computeWasteWithGraph(
220
182
  results,
221
183
  optimisticFCPGraph,
@@ -243,6 +205,7 @@ class ByteEfficiencyAudit extends Audit {
243
205
 
244
206
  metricSavings.FCP = fcpSavings;
245
207
  metricSavings.LCP = Math.max(lcpGraphSavings, lcpRecordSavings);
208
+ wastedMs = metricSavings.LCP;
246
209
  } else {
247
210
  wastedMs = simulator.computeWastedMsFromWastedBytes(wastedBytes);
248
211
  }
@@ -47,17 +47,6 @@ declare class OffscreenImages extends ByteEfficiencyAudit {
47
47
  * @param {number} interactiveTimestamp
48
48
  */
49
49
  static filterObservedResults(images: WasteResult[], interactiveTimestamp: number): WasteResult[];
50
- /**
51
- * The default byte efficiency audit will report max(TTI, load), since lazy-loading offscreen
52
- * images won't reduce the overall time and the wasted bytes are really only "wasted" for TTI,
53
- * override the function to just look at TTI savings.
54
- *
55
- * @param {Array<LH.Audit.ByteEfficiencyItem>} results
56
- * @param {LH.Gatherer.Simulation.GraphNode} graph
57
- * @param {LH.Gatherer.Simulation.Simulator} simulator
58
- * @return {number}
59
- */
60
- static computeWasteWithTTIGraph(results: Array<LH.Audit.ByteEfficiencyItem>, graph: LH.Gatherer.Simulation.GraphNode, simulator: LH.Gatherer.Simulation.Simulator): number;
61
50
  /**
62
51
  * @param {LH.Artifacts} artifacts
63
52
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -156,21 +156,6 @@ class OffscreenImages extends ByteEfficiencyAudit {
156
156
  });
157
157
  }
158
158
 
159
- /**
160
- * The default byte efficiency audit will report max(TTI, load), since lazy-loading offscreen
161
- * images won't reduce the overall time and the wasted bytes are really only "wasted" for TTI,
162
- * override the function to just look at TTI savings.
163
- *
164
- * @param {Array<LH.Audit.ByteEfficiencyItem>} results
165
- * @param {LH.Gatherer.Simulation.GraphNode} graph
166
- * @param {LH.Gatherer.Simulation.Simulator} simulator
167
- * @return {number}
168
- */
169
- static computeWasteWithTTIGraph(results, graph, simulator) {
170
- return super.computeWasteWithTTIGraph(results, graph, simulator,
171
- {includeLoad: false});
172
- }
173
-
174
159
  /**
175
160
  * @param {LH.Artifacts} artifacts
176
161
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -20,19 +20,6 @@ declare class UsesHTTP2Audit extends Audit {
20
20
  simulationBefore: LH.Gatherer.Simulation.Result;
21
21
  simulationAfter: LH.Gatherer.Simulation.Result;
22
22
  };
23
- /**
24
- * Computes the estimated effect all results being converted to use http/2, the max of:
25
- *
26
- * - end time of the last long task in the provided graph
27
- * - end time of the last node in the graph
28
- * @param {Array<{url: string}>} results
29
- * @param {Node} graph
30
- * @param {Simulator} simulator
31
- * @return {number}
32
- */
33
- static computeWasteWithTTIGraph(results: Array<{
34
- url: string;
35
- }>, graph: Node, simulator: Simulator): number;
36
23
  /**
37
24
  * Determines whether a network request is a "static resource" that would benefit from H2 multiplexing.
38
25
  * XHRs, tracking pixels, etc generally don't benefit as much because they aren't requested en-masse
@@ -15,11 +15,9 @@
15
15
  import {Audit} from '../audit.js';
16
16
  import {EntityClassification} from '../../computed/entity-classification.js';
17
17
  import UrlUtils from '../../lib/url-utils.js';
18
- import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
19
18
  import {NetworkRequest} from '../../lib/network-request.js';
20
19
  import {NetworkRecords} from '../../computed/network-records.js';
21
20
  import {LoadSimulator} from '../../computed/load-simulator.js';
22
- import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
23
21
  import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
24
22
  import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
25
23
  import * as i18n from '../../lib/i18n/i18n.js';
@@ -117,32 +115,6 @@ class UsesHTTP2Audit extends Audit {
117
115
  };
118
116
  }
119
117
 
120
- /**
121
- * Computes the estimated effect all results being converted to use http/2, the max of:
122
- *
123
- * - end time of the last long task in the provided graph
124
- * - end time of the last node in the graph
125
- * @param {Array<{url: string}>} results
126
- * @param {Node} graph
127
- * @param {Simulator} simulator
128
- * @return {number}
129
- */
130
- static computeWasteWithTTIGraph(results, graph, simulator) {
131
- const {savings: savingsOnOverallLoad, simulationBefore, simulationAfter} =
132
- this.computeWasteWithGraph(results, graph, simulator, {
133
- label: 'tti',
134
- });
135
-
136
- const savingsOnTTI =
137
- LanternInteractive.getLastLongTaskEndTime(simulationBefore.nodeTimings) -
138
- LanternInteractive.getLastLongTaskEndTime(simulationAfter.nodeTimings);
139
-
140
- const savings = Math.max(savingsOnTTI, savingsOnOverallLoad);
141
-
142
- // Round waste to nearest 10ms
143
- return Math.round(Math.max(savings, 0) / 10) * 10;
144
- }
145
-
146
118
  /**
147
119
  * Determines whether a network request is a "static resource" that would benefit from H2 multiplexing.
148
120
  * XHRs, tracking pixels, etc generally don't benefit as much because they aren't requested en-masse
@@ -235,7 +207,6 @@ class UsesHTTP2Audit extends Audit {
235
207
  * @return {Promise<LH.Audit.Product>}
236
208
  */
237
209
  static async audit(artifacts, context) {
238
- const trace = artifacts.traces[Audit.DEFAULT_PASS];
239
210
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
240
211
  const URL = artifacts.URL;
241
212
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
@@ -269,7 +240,6 @@ class UsesHTTP2Audit extends Audit {
269
240
  devtoolsLog,
270
241
  settings,
271
242
  };
272
- const pageGraph = await PageDependencyGraph.request({trace, devtoolsLog, URL}, context);
273
243
  const simulator = await LoadSimulator.request(simulatorOptions, context);
274
244
  const metricComputationInput = Audit.makeMetricComputationDataInput(artifacts, context);
275
245
 
@@ -280,8 +250,6 @@ class UsesHTTP2Audit extends Audit {
280
250
  pessimisticGraph: lcpGraph,
281
251
  } = await LanternLargestContentfulPaint.request(metricComputationInput, context);
282
252
 
283
- const wastedMsTti = UsesHTTP2Audit.computeWasteWithTTIGraph(
284
- resources, pageGraph, simulator);
285
253
  const wasteFcp =
286
254
  UsesHTTP2Audit.computeWasteWithGraph(resources,
287
255
  fcpGraph, simulator, {label: 'fcp'});
@@ -296,11 +264,11 @@ class UsesHTTP2Audit extends Audit {
296
264
  ];
297
265
 
298
266
  const details = Audit.makeOpportunityDetails(headings, resources,
299
- {overallSavingsMs: wastedMsTti});
267
+ {overallSavingsMs: wasteLcp.savings});
300
268
 
301
269
  return {
302
270
  displayValue,
303
- numericValue: wastedMsTti,
271
+ numericValue: wasteLcp.savings,
304
272
  numericUnit: 'millisecond',
305
273
  score: resources.length ? 0 : 1,
306
274
  details,
@@ -6,11 +6,12 @@ declare class IsCrawlable extends Audit {
6
6
  static handleMetaElement(metaElement: LH.Artifacts.MetaElement): {
7
7
  source: {
8
8
  snippet: string;
9
+ /** @type {Array<string|undefined>} */
9
10
  type: "node";
10
11
  lhId?: string | undefined;
11
12
  path?: string | undefined;
12
13
  selector?: string | undefined;
13
- boundingRect?: import("../../../types/lhr/audit-details.js").default.Rect | undefined;
14
+ boundingRect?: import("../../../types/lhr/audit-details.js").default.Rect | undefined; /** @type {Array<string|undefined>} */
14
15
  nodeLabel?: string | undefined;
15
16
  };
16
17
  } | undefined;
@@ -30,8 +30,9 @@ class PageDependencyGraph {
30
30
  // Calculates the URL artifact from the processed trace and DT log.
31
31
  const URL = data.URL || await DocumentUrls.request(data, context);
32
32
 
33
+ const mainThreadEvents = processedTrace.mainThreadEvents;
33
34
  const lanternRequests = networkRecords.map(NetworkRequest.asLanternNetworkRequest);
34
- return LanternPageDependencyGraph.createGraph(processedTrace, lanternRequests, URL);
35
+ return LanternPageDependencyGraph.createGraph(mainThreadEvents, lanternRequests, URL);
35
36
  }
36
37
  }
37
38
 
@@ -153,7 +153,6 @@ const defaultConfig = {
153
153
  {id: 'SourceMaps', gatherer: 'source-maps'},
154
154
  {id: 'Stacks', gatherer: 'stacks'},
155
155
  {id: 'TagsBlockingFirstPaint', gatherer: 'dobetterweb/tags-blocking-first-paint'},
156
- {id: 'TapTargets', gatherer: 'seo/tap-targets'},
157
156
  {id: 'TraceElements', gatherer: 'trace-elements'},
158
157
  {id: 'ViewportDimensions', gatherer: 'viewport-dimensions'},
159
158
  {id: 'WebAppManifest', gatherer: 'web-app-manifest'},
@@ -251,7 +250,6 @@ const defaultConfig = {
251
250
  'accessibility/definition-list',
252
251
  'accessibility/dlitem',
253
252
  'accessibility/document-title',
254
- 'accessibility/duplicate-id-active',
255
253
  'accessibility/duplicate-id-aria',
256
254
  'accessibility/empty-heading',
257
255
  'accessibility/form-field-multiple-labels',
@@ -330,7 +328,6 @@ const defaultConfig = {
330
328
  'seo/crawlable-anchors',
331
329
  'seo/is-crawlable',
332
330
  'seo/robots-txt',
333
- 'seo/tap-targets',
334
331
  'seo/hreflang',
335
332
  'seo/plugins',
336
333
  'seo/canonical',
@@ -527,7 +524,6 @@ const defaultConfig = {
527
524
  {id: 'definition-list', weight: 7, group: 'a11y-tables-lists'},
528
525
  {id: 'dlitem', weight: 7, group: 'a11y-tables-lists'},
529
526
  {id: 'document-title', weight: 7, group: 'a11y-names-labels'},
530
- {id: 'duplicate-id-active', weight: 7, group: 'a11y-navigation'},
531
527
  {id: 'duplicate-id-aria', weight: 10, group: 'a11y-aria'},
532
528
  {id: 'form-field-multiple-labels', weight: 3, group: 'a11y-names-labels'},
533
529
  {id: 'frame-title', weight: 7, group: 'a11y-names-labels'},
@@ -551,6 +547,7 @@ const defaultConfig = {
551
547
  {id: 'skip-link', weight: 3, group: 'a11y-names-labels'},
552
548
  {id: 'tabindex', weight: 7, group: 'a11y-navigation'},
553
549
  {id: 'table-duplicate-name', weight: 1, group: 'a11y-tables-lists'},
550
+ {id: 'target-size', weight: 7, group: 'a11y-best-practices'},
554
551
  {id: 'td-headers-attr', weight: 7, group: 'a11y-tables-lists'},
555
552
  {id: 'th-has-data-cells', weight: 7, group: 'a11y-tables-lists'},
556
553
  {id: 'valid-lang', weight: 7, group: 'a11y-language'},
@@ -570,7 +567,6 @@ const defaultConfig = {
570
567
  {id: 'empty-heading', weight: 0, group: 'hidden'},
571
568
  {id: 'identical-links-same-purpose', weight: 0, group: 'hidden'},
572
569
  {id: 'landmark-one-main', weight: 0, group: 'hidden'},
573
- {id: 'target-size', weight: 0, group: 'hidden'},
574
570
  {id: 'label-content-name-mismatch', weight: 0, group: 'hidden'},
575
571
  {id: 'table-fake-caption', weight: 0, group: 'hidden'},
576
572
  {id: 'td-has-header', weight: 0, group: 'hidden'},
@@ -620,7 +616,6 @@ const defaultConfig = {
620
616
  {id: 'canonical', weight: 1, group: 'seo-content'},
621
617
  {id: 'font-size', weight: 1, group: 'seo-mobile'},
622
618
  {id: 'plugins', weight: 1, group: 'seo-content'},
623
- {id: 'tap-targets', weight: 1, group: 'seo-mobile'},
624
619
  // Manual audits
625
620
  {id: 'structured-data', weight: 0},
626
621
  ],
@@ -52,7 +52,6 @@ async function runA11yChecks() {
52
52
  'aria-text': {enabled: true},
53
53
  'audio-caption': {enabled: false},
54
54
  'blink': {enabled: false},
55
- 'duplicate-id-active': {enabled: true},
56
55
  'duplicate-id': {enabled: false},
57
56
  'empty-heading': {enabled: true},
58
57
  'frame-focusable-content': {enabled: false},
@@ -79,8 +78,7 @@ async function runA11yChecks() {
79
78
  'tabindex': {enabled: true},
80
79
  'table-duplicate-name': {enabled: true},
81
80
  'table-fake-caption': {enabled: true},
82
- // TODO: https://github.com/GoogleChrome/lighthouse/issues/15824
83
- // 'target-size': {enabled: true},
81
+ 'target-size': {enabled: true},
84
82
  'td-has-header': {enabled: true},
85
83
  },
86
84
  });
@@ -18,10 +18,10 @@ export class PageDependencyGraph {
18
18
  */
19
19
  static getNetworkNodeOutput(networkRecords: Array<Lantern.NetworkRequest>): NetworkNodeOutput;
20
20
  /**
21
- * @param {LH.Artifacts.ProcessedTrace} processedTrace
21
+ * @param {LH.TraceEvent[]} mainThreadEvents
22
22
  * @return {Array<CPUNode>}
23
23
  */
24
- static getCPUNodes({ mainThreadEvents }: LH.Artifacts.ProcessedTrace): Array<CPUNode>;
24
+ static getCPUNodes(mainThreadEvents: LH.TraceEvent[]): Array<CPUNode>;
25
25
  /**
26
26
  * @param {NetworkNode} rootNode
27
27
  * @param {NetworkNodeOutput} networkNodeOutput
@@ -40,12 +40,12 @@ export class PageDependencyGraph {
40
40
  */
41
41
  static _pruneNode(node: Node): void;
42
42
  /**
43
- * @param {LH.Artifacts.ProcessedTrace} processedTrace
43
+ * @param {LH.TraceEvent[]} mainThreadEvents
44
44
  * @param {Array<Lantern.NetworkRequest>} networkRecords
45
45
  * @param {URLArtifact} URL
46
46
  * @return {Node}
47
47
  */
48
- static createGraph(processedTrace: LH.Artifacts.ProcessedTrace, networkRecords: Array<Lantern.NetworkRequest>, URL: URLArtifact): Node;
48
+ static createGraph(mainThreadEvents: LH.TraceEvent[], networkRecords: Array<Lantern.NetworkRequest>, URL: URLArtifact): Node;
49
49
  /**
50
50
  *
51
51
  * @param {Node} rootNode
@@ -109,10 +109,10 @@ class PageDependencyGraph {
109
109
  }
110
110
 
111
111
  /**
112
- * @param {LH.Artifacts.ProcessedTrace} processedTrace
112
+ * @param {LH.TraceEvent[]} mainThreadEvents
113
113
  * @return {Array<CPUNode>}
114
114
  */
115
- static getCPUNodes({mainThreadEvents}) {
115
+ static getCPUNodes(mainThreadEvents) {
116
116
  /** @type {Array<CPUNode>} */
117
117
  const nodes = [];
118
118
  let i = 0;
@@ -393,14 +393,14 @@ class PageDependencyGraph {
393
393
  }
394
394
 
395
395
  /**
396
- * @param {LH.Artifacts.ProcessedTrace} processedTrace
396
+ * @param {LH.TraceEvent[]} mainThreadEvents
397
397
  * @param {Array<Lantern.NetworkRequest>} networkRecords
398
398
  * @param {URLArtifact} URL
399
399
  * @return {Node}
400
400
  */
401
- static createGraph(processedTrace, networkRecords, URL) {
401
+ static createGraph(mainThreadEvents, networkRecords, URL) {
402
402
  const networkNodeOutput = PageDependencyGraph.getNetworkNodeOutput(networkRecords);
403
- const cpuNodes = PageDependencyGraph.getCPUNodes(processedTrace);
403
+ const cpuNodes = PageDependencyGraph.getCPUNodes(mainThreadEvents);
404
404
  const {requestedUrl, mainDocumentUrl} = URL;
405
405
  if (!requestedUrl) throw new Error('requestedUrl is required to get the root request');
406
406
  if (!mainDocumentUrl) throw new Error('mainDocumentUrl is required to get the main resource');
@@ -83,28 +83,44 @@ function processForProto(lhr) {
83
83
  }
84
84
 
85
85
  /**
86
- * Remove any found empty strings, as they are dropped after round-tripping anyway
86
+ * Execute `cb(obj, key)` on every object property where obj[key] is a string, recursively.
87
87
  * @param {any} obj
88
+ * @param {(obj: Record<string, string>, key: string) => void} cb
88
89
  */
89
- function removeStrings(obj) {
90
+ function iterateStrings(obj, cb) {
90
91
  if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
91
92
  Object.keys(obj).forEach(key => {
92
- if (typeof obj[key] === 'string' && obj[key] === '') {
93
- delete obj[key];
94
- } else if (typeof obj[key] === 'object' || Array.isArray(obj[key])) {
95
- removeStrings(obj[key]);
93
+ if (typeof obj[key] === 'string') {
94
+ cb(obj, key);
95
+ } else {
96
+ iterateStrings(obj[key], cb);
96
97
  }
97
98
  });
98
99
  } else if (Array.isArray(obj)) {
99
100
  obj.forEach(item => {
100
101
  if (typeof item === 'object' || Array.isArray(item)) {
101
- removeStrings(item);
102
+ iterateStrings(item, cb);
102
103
  }
103
104
  });
104
105
  }
105
106
  }
106
107
 
107
- removeStrings(reportJson);
108
+ iterateStrings(reportJson, (obj, key) => {
109
+ const value = obj[key];
110
+
111
+ // Remove empty strings, as they are dropped after round-tripping anyway.
112
+ if (value === '') {
113
+ delete obj[key];
114
+ return;
115
+ }
116
+
117
+ // Sanitize lone surrogates.
118
+ // @ts-expect-error node 20
119
+ if (String.prototype.isWellFormed && !value.isWellFormed()) {
120
+ // @ts-expect-error node 20
121
+ obj[key] = value.toWellFormed();
122
+ }
123
+ });
108
124
 
109
125
  return reportJson;
110
126
  }