lighthouse 9.2.0 → 9.3.0

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 (141) hide show
  1. package/changelog.md +67 -0
  2. package/dist/report/bundle.esm.js +23 -11
  3. package/dist/report/flow.js +11 -11
  4. package/dist/report/standalone.js +8 -8
  5. package/flow-report/src/common.tsx +1 -1
  6. package/flow-report/src/sidebar/flow.tsx +1 -1
  7. package/flow-report/src/util.ts +1 -2
  8. package/lighthouse-cli/bin.js +0 -4
  9. package/lighthouse-cli/cli-flags.js +22 -8
  10. package/lighthouse-cli/run.js +5 -1
  11. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +4 -2
  12. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  13. package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +103 -0
  14. package/lighthouse-cli/test/smokehouse/readme.md +10 -8
  15. package/lighthouse-cli/test/smokehouse/report-assert.js +1 -1
  16. package/lighthouse-cli/test/smokehouse/smokehouse.js +4 -5
  17. package/lighthouse-core/audits/accessibility/axe-audit.js +4 -4
  18. package/lighthouse-core/audits/accessibility/object-alt.js +3 -3
  19. package/lighthouse-core/audits/audit.js +19 -4
  20. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
  21. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +1 -1
  22. package/lighthouse-core/audits/deprecations.js +2 -2
  23. package/lighthouse-core/audits/dobetterweb/inspector-issues.js +11 -11
  24. package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -1
  25. package/lighthouse-core/audits/font-display.js +1 -1
  26. package/lighthouse-core/audits/installable-manifest.js +1 -1
  27. package/lighthouse-core/audits/is-on-https.js +1 -1
  28. package/lighthouse-core/audits/maskable-icon.js +1 -1
  29. package/lighthouse-core/audits/multi-check-audit.js +1 -1
  30. package/lighthouse-core/audits/network-requests.js +4 -4
  31. package/lighthouse-core/audits/preload-fonts.js +2 -3
  32. package/lighthouse-core/audits/preload-lcp-image.js +2 -12
  33. package/lighthouse-core/audits/seo/is-crawlable.js +2 -1
  34. package/lighthouse-core/audits/themed-omnibox.js +1 -1
  35. package/lighthouse-core/audits/uses-rel-preload.js +2 -3
  36. package/lighthouse-core/audits/valid-source-maps.js +2 -2
  37. package/lighthouse-core/computed/image-records.js +1 -1
  38. package/lighthouse-core/computed/js-bundles.js +1 -1
  39. package/lighthouse-core/computed/metrics/lantern-metric.js +1 -1
  40. package/lighthouse-core/computed/metrics/timing-summary.js +35 -35
  41. package/lighthouse-core/computed/page-dependency-graph.js +2 -2
  42. package/lighthouse-core/computed/resource-summary.js +1 -1
  43. package/lighthouse-core/config/config-helpers.js +2 -2
  44. package/lighthouse-core/config/config.js +1 -1
  45. package/lighthouse-core/fraggle-rock/config/filters.js +23 -1
  46. package/lighthouse-core/fraggle-rock/config/validation.js +2 -2
  47. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +5 -4
  48. package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +0 -1
  49. package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +0 -1
  50. package/lighthouse-core/fraggle-rock/user-flow.js +4 -4
  51. package/lighthouse-core/gather/connections/connection.js +1 -1
  52. package/lighthouse-core/gather/driver/execution-context.js +1 -1
  53. package/lighthouse-core/gather/driver/network-monitor.js +2 -2
  54. package/lighthouse-core/gather/driver/target-manager.js +1 -1
  55. package/lighthouse-core/gather/driver.js +1 -1
  56. package/lighthouse-core/gather/gatherers/console-messages.js +2 -2
  57. package/lighthouse-core/gather/gatherers/inspector-issues.js +34 -46
  58. package/lighthouse-core/gather/gatherers/js-usage.js +1 -1
  59. package/lighthouse-core/gather/gatherers/seo/font-size.js +1 -1
  60. package/lighthouse-core/gather/gatherers/trace-elements.js +8 -43
  61. package/lighthouse-core/index.js +4 -3
  62. package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
  63. package/lighthouse-core/lib/asset-saver.js +6 -4
  64. package/lighthouse-core/lib/dependency-graph/simulator/connection-pool.js +1 -1
  65. package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +1 -1
  66. package/lighthouse-core/lib/emulation.js +1 -1
  67. package/lighthouse-core/lib/icons.js +1 -2
  68. package/lighthouse-core/lib/median-run.js +1 -2
  69. package/lighthouse-core/lib/network-recorder.js +2 -2
  70. package/lighthouse-core/lib/network-request.js +1 -1
  71. package/lighthouse-core/lib/stack-packs.js +5 -0
  72. package/lighthouse-core/lib/tracehouse/cpu-profile-model.js +4 -6
  73. package/lighthouse-core/lib/tracehouse/main-thread-tasks.js +1 -1
  74. package/lighthouse-core/lib/tracehouse/trace-processor.js +14 -17
  75. package/lighthouse-core/lib/url-shim.js +15 -0
  76. package/lighthouse-core/runner.js +62 -49
  77. package/lighthouse-core/scripts/download-chrome.sh +15 -2
  78. package/package.json +21 -25
  79. package/report/assets/templates.html +2 -1
  80. package/report/clients/standalone.js +5 -1
  81. package/report/renderer/category-renderer.js +1 -1
  82. package/report/renderer/components.js +2 -2
  83. package/report/renderer/performance-category-renderer.js +4 -4
  84. package/report/renderer/report-renderer.js +1 -1
  85. package/report/renderer/report-ui-features.js +15 -3
  86. package/report/test/generator/report-generator-test.js +1 -1
  87. package/report/test/renderer/report-ui-features-test.js +22 -4
  88. package/report/test-assets/faux-psi.js +2 -3
  89. package/report/types/report-renderer.d.ts +2 -0
  90. package/shared/localization/locales/ar-XB.json +40 -4
  91. package/shared/localization/locales/ar.json +59 -23
  92. package/shared/localization/locales/bg.json +40 -4
  93. package/shared/localization/locales/ca.json +45 -9
  94. package/shared/localization/locales/cs.json +39 -3
  95. package/shared/localization/locales/da.json +43 -7
  96. package/shared/localization/locales/de.json +42 -6
  97. package/shared/localization/locales/el.json +59 -23
  98. package/shared/localization/locales/en-GB.json +43 -7
  99. package/shared/localization/locales/en-US.json +48 -6
  100. package/shared/localization/locales/en-XA.json +40 -4
  101. package/shared/localization/locales/en-XL.json +48 -6
  102. package/shared/localization/locales/es-419.json +39 -3
  103. package/shared/localization/locales/es.json +46 -10
  104. package/shared/localization/locales/fi.json +57 -21
  105. package/shared/localization/locales/fil.json +40 -4
  106. package/shared/localization/locales/fr.json +45 -9
  107. package/shared/localization/locales/he.json +41 -5
  108. package/shared/localization/locales/hi.json +59 -23
  109. package/shared/localization/locales/hr.json +57 -21
  110. package/shared/localization/locales/hu.json +39 -3
  111. package/shared/localization/locales/id.json +40 -4
  112. package/shared/localization/locales/it.json +40 -4
  113. package/shared/localization/locales/ja.json +39 -3
  114. package/shared/localization/locales/ko.json +40 -4
  115. package/shared/localization/locales/lt.json +40 -4
  116. package/shared/localization/locales/lv.json +56 -20
  117. package/shared/localization/locales/nl.json +40 -4
  118. package/shared/localization/locales/no.json +56 -20
  119. package/shared/localization/locales/pl.json +56 -20
  120. package/shared/localization/locales/pt-PT.json +57 -21
  121. package/shared/localization/locales/pt.json +41 -5
  122. package/shared/localization/locales/ro.json +39 -3
  123. package/shared/localization/locales/ru.json +40 -4
  124. package/shared/localization/locales/sk.json +39 -3
  125. package/shared/localization/locales/sl.json +39 -3
  126. package/shared/localization/locales/sr-Latn.json +40 -4
  127. package/shared/localization/locales/sr.json +40 -4
  128. package/shared/localization/locales/sv.json +39 -3
  129. package/shared/localization/locales/ta.json +47 -11
  130. package/shared/localization/locales/te.json +61 -25
  131. package/shared/localization/locales/th.json +39 -3
  132. package/shared/localization/locales/tr.json +40 -4
  133. package/shared/localization/locales/uk.json +40 -4
  134. package/shared/localization/locales/vi.json +63 -27
  135. package/shared/localization/locales/zh-HK.json +40 -4
  136. package/shared/localization/locales/zh-TW.json +45 -9
  137. package/shared/localization/locales/zh.json +41 -5
  138. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
  139. package/third-party/snyk/snapshot.json +4 -1
  140. package/types/artifacts.d.ts +16 -6
  141. package/types/robots-parser/index.d.ts +0 -20
@@ -176,7 +176,7 @@ class PreloadLCPImageAudit extends Audit {
176
176
  const node = modifiedNodesById.get(nodeId);
177
177
  if (!node) throw new Error('Impossible - node should never be undefined');
178
178
  const timings = simulationAfterChanges.nodeTimings.get(node);
179
- const endTime = timings && timings.endTime || 0;
179
+ const endTime = timings?.endTime || 0;
180
180
  maxDependencyEndTime = Math.max(maxDependencyEndTime, endTime);
181
181
  }
182
182
 
@@ -198,7 +198,7 @@ class PreloadLCPImageAudit extends Audit {
198
198
  * @param {LH.Audit.Context} context
199
199
  * @return {Promise<LH.Audit.Product>}
200
200
  */
201
- static async audit_(artifacts, context) {
201
+ static async audit(artifacts, context) {
202
202
  const gatherContext = artifacts.GatherContext;
203
203
  const trace = artifacts.traces[PreloadLCPImageAudit.DEFAULT_PASS];
204
204
  const devtoolsLog = artifacts.devtoolsLogs[PreloadLCPImageAudit.DEFAULT_PASS];
@@ -236,16 +236,6 @@ class PreloadLCPImageAudit extends Audit {
236
236
  details,
237
237
  };
238
238
  }
239
-
240
- /**
241
- * @return {Promise<LH.Audit.Product>}
242
- */
243
- static async audit() {
244
- // Preload advice is dangerous until https://bugs.chromium.org/p/chromium/issues/detail?id=788757
245
- // has been fixed and validated. All preload audits are on hold until then.
246
- // See https://github.com/GoogleChrome/lighthouse/issues/11960 for more discussion.
247
- return {score: 1, notApplicable: true, details: Audit.makeOpportunityDetails([], [], 0)};
248
- }
249
239
  }
250
240
 
251
241
  module.exports = PreloadLCPImageAudit;
@@ -6,7 +6,8 @@
6
6
  'use strict';
7
7
 
8
8
  const Audit = require('../audit.js');
9
- const robotsParser = require('robots-parser');
9
+ // TODO(esmodules): cast can be removed when this switches to import.
10
+ const robotsParser = /** @type {typeof import('robots-parser').default} */ (/** @type {unknown} */(require('robots-parser'))); // eslint-disable-line max-len
10
11
  const URL = require('../../lib/url-shim.js');
11
12
  const MainResource = require('../../computed/main-resource.js');
12
13
  const BLOCKLIST = new Set([
@@ -101,7 +101,7 @@ class ThemedOmnibox extends MultiCheckAudit {
101
101
  return {
102
102
  failures,
103
103
  manifestValues,
104
- themeColor: (themeColorMeta && themeColorMeta.content) || null,
104
+ themeColor: themeColorMeta?.content || null,
105
105
  };
106
106
  }
107
107
  }
@@ -257,9 +257,8 @@ class UsesRelPreloadAudit extends Audit {
257
257
  * @return {Promise<LH.Audit.Product>}
258
258
  */
259
259
  static async audit() {
260
- // Preload advice is dangerous until https://bugs.chromium.org/p/chromium/issues/detail?id=788757
261
- // has been fixed and validated. All preload audits are on hold until then.
262
- // See https://github.com/GoogleChrome/lighthouse/issues/11960 for more discussion.
260
+ // Preload advice is on hold until https://github.com/GoogleChrome/lighthouse/issues/11960
261
+ // is resolved.
263
262
  return {score: 1, notApplicable: true, details: Audit.makeOpportunityDetails([], [], 0)};
264
263
  }
265
264
  }
@@ -93,7 +93,7 @@ class ValidSourceMaps extends Audit {
93
93
  }
94
94
 
95
95
  // Sources content errors.
96
- if (sourceMap && sourceMap.map) {
96
+ if (sourceMap?.map) {
97
97
  const sourcesContent = sourceMap.map.sourcesContent || [];
98
98
  let missingSourcesContentCount = 0;
99
99
  for (let i = 0; i < sourceMap.map.sources.length; i++) {
@@ -108,7 +108,7 @@ class ValidSourceMaps extends Audit {
108
108
  if (sourceMap || errors.length) {
109
109
  results.push({
110
110
  scriptUrl: scriptElement.src,
111
- sourceMapUrl: sourceMap && sourceMap.sourceMapUrl,
111
+ sourceMapUrl: sourceMap?.sourceMapUrl,
112
112
  subItems: {
113
113
  type: /** @type {const} */ ('subitems'),
114
114
  items: errors,
@@ -39,7 +39,7 @@ class ImageRecords {
39
39
 
40
40
  for (const element of data.ImageElements) {
41
41
  const networkRecord = indexedNetworkRecords[element.src];
42
- const mimeType = networkRecord && networkRecord.mimeType;
42
+ const mimeType = networkRecord?.mimeType;
43
43
 
44
44
  // Don't change the guessed mime type if no mime type was found.
45
45
  imageRecords.push({
@@ -100,7 +100,7 @@ class JSBundles {
100
100
  const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map';
101
101
  const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMap);
102
102
 
103
- const content = scriptElement && scriptElement.content ? scriptElement.content : '';
103
+ const content = scriptElement?.content ? scriptElement.content : '';
104
104
  const sizes = computeGeneratedFileSizes(map, content);
105
105
 
106
106
  const bundle = {
@@ -111,7 +111,7 @@ class LanternMetricArtifact {
111
111
  const processedTrace = await ProcessedTrace.request(trace, context);
112
112
  const processedNavigation = await ProcessedNavigation.request(processedTrace, context);
113
113
  const simulator = data.simulator ||
114
- await LoadSimulator.request({devtoolsLog, settings}, context);
114
+ (await LoadSimulator.request({devtoolsLog, settings}, context));
115
115
 
116
116
  const optimisticGraph = this.getOptimisticGraph(graph, processedNavigation);
117
117
  const pessimisticGraph = this.getPessimisticGraph(graph, processedNavigation);
@@ -67,22 +67,22 @@ class TimingSummary {
67
67
  /** @type {LH.Artifacts.TimingSummary} */
68
68
  const metrics = {
69
69
  // Include the simulated/observed performance metrics
70
- firstContentfulPaint: firstContentfulPaint && firstContentfulPaint.timing,
71
- firstContentfulPaintTs: firstContentfulPaint && firstContentfulPaint.timestamp,
72
- firstContentfulPaintAllFrames: firstContentfulPaintAllFrames && firstContentfulPaintAllFrames.timing,
73
- firstContentfulPaintAllFramesTs: firstContentfulPaintAllFrames && firstContentfulPaintAllFrames.timestamp,
74
- firstMeaningfulPaint: firstMeaningfulPaint && firstMeaningfulPaint.timing,
75
- firstMeaningfulPaintTs: firstMeaningfulPaint && firstMeaningfulPaint.timestamp,
76
- largestContentfulPaint: largestContentfulPaint && largestContentfulPaint.timing,
77
- largestContentfulPaintTs: largestContentfulPaint && largestContentfulPaint.timestamp,
78
- largestContentfulPaintAllFrames: largestContentfulPaintAllFrames && largestContentfulPaintAllFrames.timing,
79
- largestContentfulPaintAllFramesTs: largestContentfulPaintAllFrames && largestContentfulPaintAllFrames.timestamp,
80
- interactive: interactive && interactive.timing,
81
- interactiveTs: interactive && interactive.timestamp,
82
- speedIndex: speedIndex && speedIndex.timing,
83
- speedIndexTs: speedIndex && speedIndex.timestamp,
84
- totalBlockingTime: totalBlockingTime && totalBlockingTime.timing,
85
- maxPotentialFID: maxPotentialFID && maxPotentialFID.timing,
70
+ firstContentfulPaint: firstContentfulPaint?.timing,
71
+ firstContentfulPaintTs: firstContentfulPaint?.timestamp,
72
+ firstContentfulPaintAllFrames: firstContentfulPaintAllFrames?.timing,
73
+ firstContentfulPaintAllFramesTs: firstContentfulPaintAllFrames?.timestamp,
74
+ firstMeaningfulPaint: firstMeaningfulPaint?.timing,
75
+ firstMeaningfulPaintTs: firstMeaningfulPaint?.timestamp,
76
+ largestContentfulPaint: largestContentfulPaint?.timing,
77
+ largestContentfulPaintTs: largestContentfulPaint?.timestamp,
78
+ largestContentfulPaintAllFrames: largestContentfulPaintAllFrames?.timing,
79
+ largestContentfulPaintAllFramesTs: largestContentfulPaintAllFrames?.timestamp,
80
+ interactive: interactive?.timing,
81
+ interactiveTs: interactive?.timestamp,
82
+ speedIndex: speedIndex?.timing,
83
+ speedIndexTs: speedIndex?.timestamp,
84
+ totalBlockingTime: totalBlockingTime?.timing,
85
+ maxPotentialFID: maxPotentialFID?.timing,
86
86
  cumulativeLayoutShift,
87
87
  cumulativeLayoutShiftMainFrame,
88
88
  totalCumulativeLayoutShift,
@@ -91,26 +91,26 @@ class TimingSummary {
91
91
  observedTimeOrigin: processedTrace.timings.timeOrigin,
92
92
  observedTimeOriginTs: processedTrace.timestamps.timeOrigin,
93
93
  // For now, navigationStart is always timeOrigin.
94
- observedNavigationStart: processedNavigation && processedNavigation.timings.timeOrigin,
95
- observedNavigationStartTs: processedNavigation && processedNavigation.timestamps.timeOrigin,
96
- observedFirstPaint: processedNavigation && processedNavigation.timings.firstPaint,
97
- observedFirstPaintTs: processedNavigation && processedNavigation.timestamps.firstPaint,
98
- observedFirstContentfulPaint: processedNavigation && processedNavigation.timings.firstContentfulPaint,
99
- observedFirstContentfulPaintTs: processedNavigation && processedNavigation.timestamps.firstContentfulPaint,
100
- observedFirstContentfulPaintAllFrames: processedNavigation && processedNavigation.timings.firstContentfulPaintAllFrames,
101
- observedFirstContentfulPaintAllFramesTs: processedNavigation && processedNavigation.timestamps.firstContentfulPaintAllFrames,
102
- observedFirstMeaningfulPaint: processedNavigation && processedNavigation.timings.firstMeaningfulPaint,
103
- observedFirstMeaningfulPaintTs: processedNavigation && processedNavigation.timestamps.firstMeaningfulPaint,
104
- observedLargestContentfulPaint: processedNavigation && processedNavigation.timings.largestContentfulPaint,
105
- observedLargestContentfulPaintTs: processedNavigation && processedNavigation.timestamps.largestContentfulPaint,
106
- observedLargestContentfulPaintAllFrames: processedNavigation && processedNavigation.timings.largestContentfulPaintAllFrames,
107
- observedLargestContentfulPaintAllFramesTs: processedNavigation && processedNavigation.timestamps.largestContentfulPaintAllFrames,
94
+ observedNavigationStart: processedNavigation?.timings.timeOrigin,
95
+ observedNavigationStartTs: processedNavigation?.timestamps.timeOrigin,
96
+ observedFirstPaint: processedNavigation?.timings.firstPaint,
97
+ observedFirstPaintTs: processedNavigation?.timestamps.firstPaint,
98
+ observedFirstContentfulPaint: processedNavigation?.timings.firstContentfulPaint,
99
+ observedFirstContentfulPaintTs: processedNavigation?.timestamps.firstContentfulPaint,
100
+ observedFirstContentfulPaintAllFrames: processedNavigation?.timings.firstContentfulPaintAllFrames,
101
+ observedFirstContentfulPaintAllFramesTs: processedNavigation?.timestamps.firstContentfulPaintAllFrames,
102
+ observedFirstMeaningfulPaint: processedNavigation?.timings.firstMeaningfulPaint,
103
+ observedFirstMeaningfulPaintTs: processedNavigation?.timestamps.firstMeaningfulPaint,
104
+ observedLargestContentfulPaint: processedNavigation?.timings.largestContentfulPaint,
105
+ observedLargestContentfulPaintTs: processedNavigation?.timestamps.largestContentfulPaint,
106
+ observedLargestContentfulPaintAllFrames: processedNavigation?.timings.largestContentfulPaintAllFrames,
107
+ observedLargestContentfulPaintAllFramesTs: processedNavigation?.timestamps.largestContentfulPaintAllFrames,
108
108
  observedTraceEnd: processedTrace.timings.traceEnd,
109
109
  observedTraceEndTs: processedTrace.timestamps.traceEnd,
110
- observedLoad: processedNavigation && processedNavigation.timings.load,
111
- observedLoadTs: processedNavigation && processedNavigation.timestamps.load,
112
- observedDomContentLoaded: processedNavigation && processedNavigation.timings.domContentLoaded,
113
- observedDomContentLoadedTs: processedNavigation && processedNavigation.timestamps.domContentLoaded,
110
+ observedLoad: processedNavigation?.timings.load,
111
+ observedLoadTs: processedNavigation?.timestamps.load,
112
+ observedDomContentLoaded: processedNavigation?.timings.domContentLoaded,
113
+ observedDomContentLoadedTs: processedNavigation?.timestamps.domContentLoaded,
114
114
  observedCumulativeLayoutShift: cumulativeLayoutShift,
115
115
  observedCumulativeLayoutShiftMainFrame: cumulativeLayoutShiftMainFrame,
116
116
  observedTotalCumulativeLayoutShift: totalCumulativeLayoutShift,
@@ -128,7 +128,7 @@ class TimingSummary {
128
128
 
129
129
  /** @type {Record<string,boolean>} */
130
130
  const debugInfo = {
131
- lcpInvalidated: !!(processedNavigation && processedNavigation.lcpInvalidated),
131
+ lcpInvalidated: !!processedNavigation?.lcpInvalidated,
132
132
  };
133
133
 
134
134
  return {metrics, debugInfo};
@@ -205,7 +205,7 @@ class PageDependencyGraph {
205
205
  networkNode.startTime <= cpuNode.startTime) return;
206
206
  const {record} = networkNode;
207
207
  const resourceType = record.resourceType ||
208
- record.redirectDestination && record.redirectDestination.resourceType;
208
+ record.redirectDestination?.resourceType;
209
209
  if (!linkableResourceTypes.has(resourceType)) {
210
210
  // We only link some resources to CPU nodes because we observe LCP simulation
211
211
  // regressions when including images, etc.
@@ -405,7 +405,7 @@ class PageDependencyGraph {
405
405
 
406
406
  // The root request is the earliest request in the main document redirect chain.
407
407
  // Will be undefined if there were no redirects.
408
- const rootRequest = mainDocumentNode.record.redirects && mainDocumentNode.record.redirects[0];
408
+ const rootRequest = mainDocumentNode.record.redirects?.[0];
409
409
 
410
410
  let rootNode;
411
411
  if (rootRequest) {
@@ -55,7 +55,7 @@ class ResourceSummary {
55
55
  const budget = Budget.getMatchingBudget(budgets, mainResourceURL);
56
56
  /** @type {ReadonlyArray<string>} */
57
57
  let firstPartyHosts = [];
58
- if (budget && budget.options && budget.options.firstPartyHostnames) {
58
+ if (budget?.options?.firstPartyHostnames) {
59
59
  firstPartyHosts = budget.options.firstPartyHostnames;
60
60
  } else {
61
61
  const rootDomain = Util.getRootDomain(mainResourceURL);
@@ -295,7 +295,7 @@ function resolveSettings(settingsJson = {}, overrides = undefined) {
295
295
  // however `lookupLocale` will always determine which of our supported locales to use (falling
296
296
  // back if necessary).
297
297
  // TODO: could do more work to sniff out the user's locale
298
- const locale = i18n.lookupLocale((overrides && overrides.locale) || settingsJson.locale);
298
+ const locale = i18n.lookupLocale(overrides?.locale || settingsJson.locale);
299
299
 
300
300
  // Fill in missing settings with defaults
301
301
  const {defaultSettings} = constants;
@@ -332,7 +332,7 @@ function resolveSettings(settingsJson = {}, overrides = undefined) {
332
332
  */
333
333
  function mergePlugins(configJSON, configDir, flags) {
334
334
  const configPlugins = configJSON.plugins || [];
335
- const flagPlugins = (flags && flags.plugins) || [];
335
+ const flagPlugins = flags?.plugins || [];
336
336
  const pluginNames = new Set([...configPlugins, ...flagPlugins]);
337
337
 
338
338
  for (const pluginName of pluginNames) {
@@ -161,7 +161,7 @@ class Config {
161
161
  constructor(configJSON, flags) {
162
162
  const status = {msg: 'Create config', id: 'lh:init:config'};
163
163
  log.time(status, 'verbose');
164
- let configPath = flags && flags.configPath;
164
+ let configPath = flags?.configPath;
165
165
 
166
166
  if (!configJSON) {
167
167
  configJSON = defaultConfig;
@@ -5,6 +5,8 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
+ const log = require('lighthouse-logger');
9
+
8
10
  const Audit = require('../../audits/audit.js');
9
11
 
10
12
  /** @type {Record<keyof LH.FRBaseArtifacts, string>} */
@@ -45,7 +47,7 @@ function getAuditIdsInCategories(allCategories, onlyCategories) {
45
47
 
46
48
  onlyCategories = onlyCategories || Object.keys(allCategories);
47
49
  const categories = onlyCategories.map(categoryId => allCategories[categoryId]);
48
- const auditRefs = categories.flatMap(category => category.auditRefs);
50
+ const auditRefs = categories.flatMap(category => category?.auditRefs || []);
49
51
  return new Set(auditRefs.map(auditRef => auditRef.id));
50
52
  }
51
53
 
@@ -194,6 +196,24 @@ function filterCategoriesByExplicitFilters(categories, onlyCategories) {
194
196
  return Object.fromEntries(categoriesToKeep);
195
197
  }
196
198
 
199
+ /**
200
+ * Logs a warning if any specified onlyCategory is not a known category that can
201
+ * be included.
202
+ *
203
+ * @param {LH.Config.Config['categories']} allCategories
204
+ * @param {string[] | null} onlyCategories
205
+ * @return {void}
206
+ */
207
+ function warnOnUnknownOnlyCategories(allCategories, onlyCategories) {
208
+ if (!onlyCategories) return;
209
+
210
+ for (const onlyCategoryId of onlyCategories) {
211
+ if (!allCategories?.[onlyCategoryId]) {
212
+ log.warn('config', `unrecognized category in 'onlyCategories': ${onlyCategoryId}`);
213
+ }
214
+ }
215
+ }
216
+
197
217
  /**
198
218
  * Filters a categories object and their auditRefs down to the set that can be computed using
199
219
  * only the specified audits.
@@ -266,6 +286,8 @@ function filterConfigByGatherMode(config, mode) {
266
286
  function filterConfigByExplicitFilters(config, filters) {
267
287
  const {onlyAudits, onlyCategories, skipAudits} = filters;
268
288
 
289
+ warnOnUnknownOnlyCategories(config.categories, onlyCategories);
290
+
269
291
  let baseAuditIds = getAuditIdsInCategories(config.categories, undefined);
270
292
  if (onlyCategories) {
271
293
  baseAuditIds = getAuditIdsInCategories(config.categories, onlyCategories);
@@ -49,7 +49,7 @@ function assertValidPluginName(configJSON, pluginName) {
49
49
  throw new Error(`plugin name '${pluginName}' does not start with 'lighthouse-plugin-'`);
50
50
  }
51
51
 
52
- if (configJSON.categories && configJSON.categories[pluginName]) {
52
+ if (configJSON.categories?.[pluginName]) {
53
53
  throw new Error(`plugin name '${pluginName}' not allowed because it is the id of a category already found in config`); // eslint-disable-line max-len
54
54
  }
55
55
  }
@@ -123,7 +123,7 @@ function assertValidFRNavigations(navigationsDefn) {
123
123
  function assertValidAudit(auditDefinition) {
124
124
  const {implementation, path: auditPath} = auditDefinition;
125
125
  const auditName = auditPath ||
126
- (implementation && implementation.meta && implementation.meta.id) ||
126
+ implementation?.meta?.id ||
127
127
  'Unknown audit';
128
128
 
129
129
  if (typeof implementation.audit !== 'function' || implementation.audit === Audit.audit) {
@@ -22,6 +22,7 @@ const {initializeConfig} = require('../config/config.js');
22
22
  const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
23
23
  const format = require('../../../shared/localization/format.js');
24
24
  const LighthouseError = require('../../lib/lh-error.js');
25
+ const URL = require('../../lib/url-shim.js');
25
26
  const {getPageLoadError} = require('../../lib/navigation-error.js');
26
27
  const Trace = require('../../gather/gatherers/trace.js');
27
28
  const DevtoolsLog = require('../../gather/gatherers/devtools-log.js');
@@ -136,11 +137,11 @@ async function _collectDebugData(navigationContext, phaseState) {
136
137
  await collectPhaseArtifacts({...phaseState, phase: 'getArtifact', artifactDefinitions});
137
138
  const getArtifactState = phaseState.artifactState.getArtifact;
138
139
 
139
- const devtoolsLogArtifactId = devtoolsLogArtifactDefn && devtoolsLogArtifactDefn.id;
140
+ const devtoolsLogArtifactId = devtoolsLogArtifactDefn?.id;
140
141
  const devtoolsLog = devtoolsLogArtifactId && await getArtifactState[devtoolsLogArtifactId];
141
142
  const records = devtoolsLog && await NetworkRecords.request(devtoolsLog, navigationContext);
142
143
 
143
- const traceArtifactId = traceArtifactDefn && traceArtifactDefn.id;
144
+ const traceArtifactId = traceArtifactDefn?.id;
144
145
  const trace = traceArtifactId && await getArtifactState[traceArtifactId];
145
146
 
146
147
  return {devtoolsLog, records, trace};
@@ -279,7 +280,7 @@ async function _cleanup({requestedUrl, driver, config}) {
279
280
  * @return {Promise<LH.RunnerResult|undefined>}
280
281
  */
281
282
  async function navigation(options) {
282
- const {url: requestedUrl, page, configContext = {}} = options;
283
+ const {url, page, configContext = {}} = options;
283
284
  const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'navigation'});
284
285
  const computedCache = new Map();
285
286
  const internalOptions = {
@@ -289,6 +290,7 @@ async function navigation(options) {
289
290
  return Runner.run(
290
291
  async () => {
291
292
  const driver = new Driver(page);
293
+ const requestedUrl = URL.normalizeUrl(url);
292
294
  const context = {driver, config, requestedUrl, options: internalOptions};
293
295
  const {baseArtifacts} = await _setup(context);
294
296
  const {artifacts} = await _navigations({...context, baseArtifacts, computedCache});
@@ -297,7 +299,6 @@ async function navigation(options) {
297
299
  return finalizeArtifacts(baseArtifacts, artifacts);
298
300
  },
299
301
  {
300
- url: requestedUrl,
301
302
  config,
302
303
  computedCache: new Map(),
303
304
  }
@@ -51,7 +51,6 @@ async function snapshot(options) {
51
51
  return finalizeArtifacts(baseArtifacts, artifacts);
52
52
  },
53
53
  {
54
- url,
55
54
  config,
56
55
  computedCache,
57
56
  }
@@ -64,7 +64,6 @@ async function startTimespan(options) {
64
64
  return finalizeArtifacts(baseArtifacts, artifacts);
65
65
  },
66
66
  {
67
- url: finalUrl,
68
67
  config,
69
68
  computedCache,
70
69
  }
@@ -23,7 +23,7 @@ class UserFlow {
23
23
  /** @type {FrOptions} */
24
24
  this.options = {page, ...options};
25
25
  /** @type {string|undefined} */
26
- this.name = options && options.name;
26
+ this.name = options?.name;
27
27
  /** @type {LH.FlowResult.Step[]} */
28
28
  this.steps = [];
29
29
  }
@@ -90,7 +90,7 @@ class UserFlow {
90
90
  const result = await navigation(this._getNextNavigationOptions(url, stepOptions));
91
91
  if (!result) throw Error('Navigation returned undefined');
92
92
 
93
- const providedName = stepOptions && stepOptions.stepName;
93
+ const providedName = stepOptions?.stepName;
94
94
  this.steps.push({
95
95
  lhr: result.lhr,
96
96
  name: providedName || this._getDefaultStepName(result.lhr),
@@ -118,7 +118,7 @@ class UserFlow {
118
118
  this.currentTimespan = undefined;
119
119
  if (!result) throw Error('Timespan returned undefined');
120
120
 
121
- const providedName = options && options.stepName;
121
+ const providedName = options?.stepName;
122
122
  this.steps.push({
123
123
  lhr: result.lhr,
124
124
  name: providedName || this._getDefaultStepName(result.lhr),
@@ -137,7 +137,7 @@ class UserFlow {
137
137
  const result = await snapshot(options);
138
138
  if (!result) throw Error('Snapshot returned undefined');
139
139
 
140
- const providedName = stepOptions && stepOptions.stepName;
140
+ const providedName = stepOptions?.stepName;
141
141
  this.steps.push({
142
142
  lhr: result.lhr,
143
143
  name: providedName || this._getDefaultStepName(result.lhr),
@@ -148,7 +148,7 @@ class Connection {
148
148
  } else {
149
149
  // In DevTools we receive responses to commands we did not send which we cannot act on, so we
150
150
  // just log these occurrences.
151
- const error = object.error && object.error.message;
151
+ const error = object.error?.message;
152
152
  log.formatProtocol(`disowned method <= browser ${error ? 'ERR' : 'OK'}`,
153
153
  {method: 'UNKNOWN', params: error || object.result}, 'verbose');
154
154
  }
@@ -121,7 +121,7 @@ class ExecutionContext {
121
121
  new Error('Runtime.evaluate response did not contain a "result" object'));
122
122
  }
123
123
  const value = response.result.value;
124
- if (value && value.__failedInBrowser) {
124
+ if (value?.__failedInBrowser) {
125
125
  return Promise.reject(Object.assign(new Error(), value));
126
126
  } else {
127
127
  return value;
@@ -144,7 +144,7 @@ class NetworkMonitor {
144
144
  const finalNavigation = mainFrameNavigations[mainFrameNavigations.length - 1];
145
145
  if (!finalNavigation) log.warn('NetworkMonitor', 'No detected navigations');
146
146
 
147
- return finalNavigation && finalNavigation.url;
147
+ return finalNavigation?.url;
148
148
  }
149
149
 
150
150
  /**
@@ -172,7 +172,7 @@ class NetworkMonitor {
172
172
  if (!this._networkRecorder) return false;
173
173
  const requests = this._networkRecorder.getRawRecords();
174
174
  const rootFrameRequest = requests.find(r => r.resourceType === 'Document');
175
- const rootFrameId = rootFrameRequest && rootFrameRequest.frameId;
175
+ const rootFrameId = rootFrameRequest?.frameId;
176
176
 
177
177
  return this._isActiveIdlePeriod(
178
178
  0,
@@ -49,7 +49,7 @@ class TargetManager {
49
49
  async _onSessionAttached(session) {
50
50
  try {
51
51
  const target = await session.sendCommand('Target.getTargetInfo').catch(() => null);
52
- const targetType = target && target.targetInfo && target.targetInfo.type;
52
+ const targetType = target?.targetInfo?.type;
53
53
  const hasValidTargetType = targetType === 'page' || targetType === 'iframe';
54
54
  if (!target || !hasValidTargetType) return;
55
55
 
@@ -425,7 +425,7 @@ class Driver {
425
425
  * @return {Promise<void>}
426
426
  */
427
427
  async beginTrace(settings) {
428
- const additionalCategories = (settings && settings.additionalTraceCategories &&
428
+ const additionalCategories = (settings?.additionalTraceCategories &&
429
429
  settings.additionalTraceCategories.split(',')) || [];
430
430
  const traceCategories = TraceGatherer.getDefaultTraceCategories().concat(additionalCategories);
431
431
 
@@ -66,7 +66,7 @@ class ConsoleMessages extends FRGatherer {
66
66
  }
67
67
 
68
68
  const {url, lineNumber, columnNumber} =
69
- event.stackTrace && event.stackTrace.callFrames[0] || {};
69
+ event.stackTrace?.callFrames[0] || {};
70
70
  /** @type {LH.Artifacts.ConsoleMessage} */
71
71
  const consoleMessage = {
72
72
  eventType: 'consoleAPI',
@@ -117,7 +117,7 @@ class ConsoleMessages extends FRGatherer {
117
117
 
118
118
  // JS events have a stack trace, which we use to get the column.
119
119
  // CSS/HTML events only expose a line number.
120
- const {columnNumber} = event.entry.stackTrace && event.entry.stackTrace.callFrames[0] || {};
120
+ const {columnNumber} = event.entry.stackTrace?.callFrames[0] || {};
121
121
 
122
122
  this._logEntries.push({
123
123
  eventType: 'protocolLog',
@@ -58,58 +58,46 @@ class InspectorIssues extends FRGatherer {
58
58
  * @return {Promise<LH.Artifacts['InspectorIssues']>}
59
59
  */
60
60
  async _getArtifact(networkRecords) {
61
+ /** @type {LH.Artifacts.InspectorIssues} */
61
62
  const artifact = {
62
- /** @type {Array<LH.Crdp.Audits.MixedContentIssueDetails>} */
63
- mixedContent: [],
64
- /** @type {Array<LH.Crdp.Audits.SameSiteCookieIssueDetails>} */
65
- sameSiteCookies: [],
66
- /** @type {Array<LH.Crdp.Audits.BlockedByResponseIssueDetails>} */
67
- blockedByResponse: [],
68
- /** @type {Array<LH.Crdp.Audits.HeavyAdIssueDetails>} */
69
- heavyAds: [],
70
- /** @type {Array<LH.Crdp.Audits.ContentSecurityPolicyIssueDetails>} */
71
- contentSecurityPolicy: [],
72
- /** @type {Array<LH.Crdp.Audits.DeprecationIssueDetails>} */
73
- deprecations: [],
63
+ attributionReportingIssue: [],
64
+ blockedByResponseIssue: [],
65
+ clientHintIssue: [],
66
+ contentSecurityPolicyIssue: [],
67
+ corsIssue: [],
68
+ deprecationIssue: [],
69
+ federatedAuthRequestIssue: [],
70
+ genericIssue: [],
71
+ heavyAdIssue: [],
72
+ lowTextContrastIssue: [],
73
+ mixedContentIssue: [],
74
+ navigatorUserAgentIssue: [],
75
+ quirksModeIssue: [],
76
+ sameSiteCookieIssue: [],
77
+ sharedArrayBufferIssue: [],
78
+ twaQualityEnforcement: [],
74
79
  };
75
-
76
- for (const issue of this._issues) {
77
- if (issue.details.mixedContentIssueDetails) {
78
- const issueDetails = issue.details.mixedContentIssueDetails;
79
- const issueReqId = issueDetails.request && issueDetails.request.requestId;
80
- // Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
81
- if (issueReqId &&
82
- networkRecords.find(req => req.requestId === issueReqId)) {
83
- artifact.mixedContent.push(issueDetails);
80
+ const keys = /** @type {Array<keyof LH.Artifacts['InspectorIssues']>} */(Object.keys(artifact));
81
+ for (const key of keys) {
82
+ /** @type {`${key}Details`} */
83
+ const detailsKey = `${key}Details`;
84
+ const allDetails = this._issues.map(issue => issue.details[detailsKey]);
85
+ for (const detail of allDetails) {
86
+ if (!detail) {
87
+ continue;
84
88
  }
85
- }
86
- if (issue.details.sameSiteCookieIssueDetails) {
87
- const issueDetails = issue.details.sameSiteCookieIssueDetails;
88
- const issueReqId = issueDetails.request && issueDetails.request.requestId;
89
89
  // Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
90
- if (issueReqId &&
91
- networkRecords.find(req => req.requestId === issueReqId)) {
92
- artifact.sameSiteCookies.push(issueDetails);
90
+ const requestId = 'request' in detail && detail.request && detail.request.requestId;
91
+ if (requestId) {
92
+ if (networkRecords.find(req => req.requestId === requestId)) {
93
+ // @ts-expect-error - detail types are not all compatible
94
+ artifact[key].push(detail);
95
+ }
96
+ } else {
97
+ // @ts-expect-error - detail types are not all compatible
98
+ artifact[key].push(detail);
93
99
  }
94
100
  }
95
- if (issue.details.blockedByResponseIssueDetails) {
96
- const issueDetails = issue.details.blockedByResponseIssueDetails;
97
- const issueReqId = issueDetails.request && issueDetails.request.requestId;
98
- // Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
99
- if (issueReqId &&
100
- networkRecords.find(req => req.requestId === issueReqId)) {
101
- artifact.blockedByResponse.push(issueDetails);
102
- }
103
- }
104
- if (issue.details.heavyAdIssueDetails) {
105
- artifact.heavyAds.push(issue.details.heavyAdIssueDetails);
106
- }
107
- if (issue.details.contentSecurityPolicyIssueDetails) {
108
- artifact.contentSecurityPolicy.push(issue.details.contentSecurityPolicyIssueDetails);
109
- }
110
- if (issue.details.deprecationIssueDetails) {
111
- artifact.deprecations.push(issue.details.deprecationIssueDetails);
112
- }
113
101
  }
114
102
 
115
103
  return artifact;
@@ -116,7 +116,7 @@ class JsUsage extends FRGatherer {
116
116
  let url = scriptUsage.url;
117
117
  const scriptParsedEvent =
118
118
  this._scriptParsedEvents.find(e => e.scriptId === scriptUsage.scriptId);
119
- if (scriptParsedEvent && scriptParsedEvent.embedderName) {
119
+ if (scriptParsedEvent?.embedderName) {
120
120
  url = scriptParsedEvent.embedderName;
121
121
  }
122
122
 
@@ -355,7 +355,7 @@ class FontSize extends FRGatherer {
355
355
  // For the nodes whose computed style we could attribute to a stylesheet, assign
356
356
  // the stylsheet to the data.
357
357
  analyzedFailingNodesData
358
- .filter(data => data.cssRule && data.cssRule.styleSheetId)
358
+ .filter(data => data.cssRule?.styleSheetId)
359
359
  // @ts-expect-error - guaranteed to exist from the filter immediately above
360
360
  .forEach(data => (data.cssRule.stylesheet = stylesheets.get(data.cssRule.styleSheetId)));
361
361