lighthouse 9.2.0-dev.20211217 → 9.2.0-dev.20211221

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 (67) hide show
  1. package/dist/report/bundle.esm.js +6 -6
  2. package/dist/report/flow.js +5 -5
  3. package/dist/report/standalone.js +3 -3
  4. package/flow-report/src/common.tsx +1 -1
  5. package/flow-report/src/sidebar/flow.tsx +1 -1
  6. package/flow-report/src/util.ts +1 -2
  7. package/lighthouse-cli/run.js +1 -1
  8. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  9. package/lighthouse-cli/test/smokehouse/report-assert.js +1 -1
  10. package/lighthouse-cli/test/smokehouse/smokehouse.js +4 -4
  11. package/lighthouse-core/audits/accessibility/axe-audit.js +4 -4
  12. package/lighthouse-core/audits/accessibility/object-alt.js +3 -3
  13. package/lighthouse-core/audits/audit.js +2 -2
  14. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
  15. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +1 -1
  16. package/lighthouse-core/audits/dobetterweb/inspector-issues.js +3 -3
  17. package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -1
  18. package/lighthouse-core/audits/font-display.js +1 -1
  19. package/lighthouse-core/audits/installable-manifest.js +1 -1
  20. package/lighthouse-core/audits/maskable-icon.js +1 -1
  21. package/lighthouse-core/audits/multi-check-audit.js +1 -1
  22. package/lighthouse-core/audits/network-requests.js +4 -4
  23. package/lighthouse-core/audits/preload-lcp-image.js +1 -1
  24. package/lighthouse-core/audits/themed-omnibox.js +1 -1
  25. package/lighthouse-core/audits/valid-source-maps.js +2 -2
  26. package/lighthouse-core/computed/image-records.js +1 -1
  27. package/lighthouse-core/computed/js-bundles.js +1 -1
  28. package/lighthouse-core/computed/metrics/lantern-metric.js +1 -1
  29. package/lighthouse-core/computed/metrics/timing-summary.js +35 -35
  30. package/lighthouse-core/computed/page-dependency-graph.js +2 -2
  31. package/lighthouse-core/computed/resource-summary.js +1 -1
  32. package/lighthouse-core/config/config-helpers.js +2 -2
  33. package/lighthouse-core/config/config.js +1 -1
  34. package/lighthouse-core/fraggle-rock/config/filters.js +23 -1
  35. package/lighthouse-core/fraggle-rock/config/validation.js +2 -2
  36. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +2 -2
  37. package/lighthouse-core/fraggle-rock/user-flow.js +4 -4
  38. package/lighthouse-core/gather/connections/connection.js +1 -1
  39. package/lighthouse-core/gather/driver/execution-context.js +1 -1
  40. package/lighthouse-core/gather/driver/network-monitor.js +2 -2
  41. package/lighthouse-core/gather/driver/target-manager.js +1 -1
  42. package/lighthouse-core/gather/driver.js +1 -1
  43. package/lighthouse-core/gather/gatherers/console-messages.js +2 -2
  44. package/lighthouse-core/gather/gatherers/inspector-issues.js +3 -3
  45. package/lighthouse-core/gather/gatherers/js-usage.js +1 -1
  46. package/lighthouse-core/gather/gatherers/seo/font-size.js +1 -1
  47. package/lighthouse-core/gather/gatherers/trace-elements.js +8 -43
  48. package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
  49. package/lighthouse-core/lib/dependency-graph/simulator/connection-pool.js +1 -1
  50. package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +1 -1
  51. package/lighthouse-core/lib/emulation.js +1 -1
  52. package/lighthouse-core/lib/icons.js +1 -2
  53. package/lighthouse-core/lib/median-run.js +1 -2
  54. package/lighthouse-core/lib/network-recorder.js +2 -2
  55. package/lighthouse-core/lib/network-request.js +1 -1
  56. package/lighthouse-core/lib/tracehouse/cpu-profile-model.js +4 -6
  57. package/lighthouse-core/lib/tracehouse/main-thread-tasks.js +1 -1
  58. package/lighthouse-core/lib/tracehouse/trace-processor.js +14 -17
  59. package/lighthouse-core/runner.js +4 -4
  60. package/package.json +10 -10
  61. package/report/renderer/category-renderer.js +1 -1
  62. package/report/renderer/performance-category-renderer.js +4 -4
  63. package/report/renderer/report-renderer.js +1 -1
  64. package/report/test-assets/faux-psi.js +2 -3
  65. package/shared/localization/locales/en-US.json +3 -3
  66. package/shared/localization/locales/en-XL.json +3 -3
  67. package/third-party/snyk/snapshot.json +3 -1
@@ -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) {
@@ -136,11 +136,11 @@ async function _collectDebugData(navigationContext, phaseState) {
136
136
  await collectPhaseArtifacts({...phaseState, phase: 'getArtifact', artifactDefinitions});
137
137
  const getArtifactState = phaseState.artifactState.getArtifact;
138
138
 
139
- const devtoolsLogArtifactId = devtoolsLogArtifactDefn && devtoolsLogArtifactDefn.id;
139
+ const devtoolsLogArtifactId = devtoolsLogArtifactDefn?.id;
140
140
  const devtoolsLog = devtoolsLogArtifactId && await getArtifactState[devtoolsLogArtifactId];
141
141
  const records = devtoolsLog && await NetworkRecords.request(devtoolsLog, navigationContext);
142
142
 
143
- const traceArtifactId = traceArtifactDefn && traceArtifactDefn.id;
143
+ const traceArtifactId = traceArtifactDefn?.id;
144
144
  const trace = traceArtifactId && await getArtifactState[traceArtifactId];
145
145
 
146
146
  return {devtoolsLog, records, trace};
@@ -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',
@@ -76,7 +76,7 @@ class InspectorIssues extends FRGatherer {
76
76
  for (const issue of this._issues) {
77
77
  if (issue.details.mixedContentIssueDetails) {
78
78
  const issueDetails = issue.details.mixedContentIssueDetails;
79
- const issueReqId = issueDetails.request && issueDetails.request.requestId;
79
+ const issueReqId = issueDetails.request?.requestId;
80
80
  // Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
81
81
  if (issueReqId &&
82
82
  networkRecords.find(req => req.requestId === issueReqId)) {
@@ -85,7 +85,7 @@ class InspectorIssues extends FRGatherer {
85
85
  }
86
86
  if (issue.details.sameSiteCookieIssueDetails) {
87
87
  const issueDetails = issue.details.sameSiteCookieIssueDetails;
88
- const issueReqId = issueDetails.request && issueDetails.request.requestId;
88
+ const issueReqId = issueDetails.request?.requestId;
89
89
  // Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
90
90
  if (issueReqId &&
91
91
  networkRecords.find(req => req.requestId === issueReqId)) {
@@ -94,7 +94,7 @@ class InspectorIssues extends FRGatherer {
94
94
  }
95
95
  if (issue.details.blockedByResponseIssueDetails) {
96
96
  const issueDetails = issue.details.blockedByResponseIssueDetails;
97
- const issueReqId = issueDetails.request && issueDetails.request.requestId;
97
+ const issueReqId = issueDetails.request?.requestId;
98
98
  // Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
99
99
  if (issueReqId &&
100
100
  networkRecords.find(req => req.requestId === issueReqId)) {
@@ -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
 
@@ -60,42 +60,6 @@ class TraceElements extends FRGatherer {
60
60
  if (name) this.animationIdToName.set(id, name);
61
61
  }
62
62
 
63
- /**
64
- * @param {LH.TraceEvent | undefined} event
65
- * @return {number | undefined}
66
- */
67
- static getNodeIDFromTraceEvent(event) {
68
- return event && event.args &&
69
- event.args.data && event.args.data.nodeId;
70
- }
71
-
72
- /**
73
- * @param {LH.TraceEvent | undefined} event
74
- * @return {string | undefined}
75
- */
76
- static getAnimationIDFromTraceEvent(event) {
77
- return event && event.args &&
78
- event.args.data && event.args.data.id;
79
- }
80
-
81
- /**
82
- * @param {LH.TraceEvent | undefined} event
83
- * @return {number | undefined}
84
- */
85
- static getFailureReasonsFromTraceEvent(event) {
86
- return event && event.args &&
87
- event.args.data && event.args.data.compositeFailed;
88
- }
89
-
90
- /**
91
- * @param {LH.TraceEvent | undefined} event
92
- * @return {string[] | undefined}
93
- */
94
- static getUnsupportedPropertiesFromTraceEvent(event) {
95
- return event && event.args &&
96
- event.args.data && event.args.data.unsupportedProperties;
97
- }
98
-
99
63
  /**
100
64
  * @param {Array<number>} rect
101
65
  * @return {LH.Artifacts.Rect}
@@ -124,7 +88,7 @@ class TraceElements extends FRGatherer {
124
88
  const clsPerNode = new Map();
125
89
  const shiftEvents = mainThreadEvents
126
90
  .filter(e => e.name === 'LayoutShift')
127
- .map(e => e.args && e.args.data);
91
+ .map(e => e.args?.data);
128
92
  const indexFirstEventWithoutInput =
129
93
  shiftEvents.findIndex(event => event && !event.had_recent_input);
130
94
 
@@ -207,11 +171,12 @@ class TraceElements extends FRGatherer {
207
171
  /** @type {Map<number, Set<{animationId: string, failureReasonsMask?: number, unsupportedProperties?: string[]}>>} */
208
172
  const elementAnimations = new Map();
209
173
  for (const {begin, status} of animationPairs.values()) {
210
- const nodeId = TraceElements.getNodeIDFromTraceEvent(begin);
211
- const animationId = TraceElements.getAnimationIDFromTraceEvent(begin);
212
- const failureReasonsMask = TraceElements.getFailureReasonsFromTraceEvent(status);
213
- const unsupportedProperties = TraceElements.getUnsupportedPropertiesFromTraceEvent(status);
174
+ const nodeId = begin?.args?.data?.nodeId;
175
+ const animationId = begin?.args?.data?.id;
176
+ const failureReasonsMask = status?.args?.data?.compositeFailed;
177
+ const unsupportedProperties = status?.args?.data?.unsupportedProperties;
214
178
  if (!nodeId || !animationId) continue;
179
+
215
180
  const animationIds = elementAnimations.get(nodeId) || new Set();
216
181
  animationIds.add({animationId, failureReasonsMask, unsupportedProperties});
217
182
  elementAnimations.set(nodeId, animationIds);
@@ -270,7 +235,7 @@ class TraceElements extends FRGatherer {
270
235
  });
271
236
  const {mainThreadEvents} = processedTrace;
272
237
 
273
- const lcpNodeId = TraceElements.getNodeIDFromTraceEvent(largestContentfulPaintEvt);
238
+ const lcpNodeId = largestContentfulPaintEvt?.args?.data?.nodeId;
274
239
  const clsNodeData = TraceElements.getTopLayoutShiftElements(mainThreadEvents);
275
240
  const animatedElementData =
276
241
  await this.getAnimatedElements(mainThreadEvents);
@@ -308,7 +273,7 @@ class TraceElements extends FRGatherer {
308
273
  continue;
309
274
  }
310
275
 
311
- if (response && response.result && response.result.value) {
276
+ if (response?.result?.value) {
312
277
  traceElements.push({
313
278
  traceEventType,
314
279
  ...response.result.value,
@@ -40,7 +40,7 @@ class ArbitraryEqualityMap {
40
40
  */
41
41
  get(key) {
42
42
  const entry = this._entries[this._findIndexOf(key)];
43
- return entry && entry.value;
43
+ return entry?.value;
44
44
  }
45
45
 
46
46
  /**
@@ -109,7 +109,7 @@ module.exports = class ConnectionPool {
109
109
  }
110
110
 
111
111
  // This connection is a match and is available! Update our max if it has a larger congestionWindow
112
- const currentMax = (maxConnection && maxConnection.congestionWindow) || -Infinity;
112
+ const currentMax = (maxConnection?.congestionWindow) || -Infinity;
113
113
  if (connection.congestionWindow > currentMax) maxConnection = connection;
114
114
  }
115
115
 
@@ -390,7 +390,7 @@ class NetworkAnalyzer {
390
390
  // downloading those bytes. We slice up all the network records into start/end boundaries, so
391
391
  // it's easier to deal with the gaps in downloading.
392
392
  const timeBoundaries = networkRecords.reduce((boundaries, record) => {
393
- const scheme = record.parsedURL && record.parsedURL.scheme;
393
+ const scheme = record.parsedURL?.scheme;
394
394
  // Requests whose bodies didn't come over the network or didn't completely finish will mess
395
395
  // with the computation, just skip over them.
396
396
  if (scheme === 'data' || record.failed || !record.finished ||
@@ -25,7 +25,7 @@ const NO_CPU_THROTTLE_METRICS = {
25
25
  */
26
26
  function parseUseragentIntoMetadata(userAgent, formFactor) {
27
27
  const match = userAgent.match(/Chrome\/([\d.]+)/); // eg 'Chrome/(71.0.3577.0)'
28
- const fullVersion = (match && match[1]) || '99.0.1234.0';
28
+ const fullVersion = match?.[1] || '99.0.1234.0';
29
29
  const [version] = fullVersion.split('.', 1);
30
30
  const brands = [
31
31
  {brand: 'Chromium', version},
@@ -73,8 +73,7 @@ function pngSizedAtLeast(sizeRequirement, manifest) {
73
73
  function containsMaskableIcon(manifest) {
74
74
  const iconValues = manifest.icons.value;
75
75
  return iconValues.some(icon => {
76
- return icon.value.purpose &&
77
- icon.value.purpose.value &&
76
+ return icon.value.purpose?.value &&
78
77
  icon.value.purpose.value.includes('maskable');
79
78
  });
80
79
  }
@@ -6,8 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  /** @param {LH.Result} lhr @param {string} auditName */
9
- const getNumericValue = (lhr, auditName) =>
10
- (lhr.audits[auditName] && lhr.audits[auditName].numericValue) || NaN;
9
+ const getNumericValue = (lhr, auditName) => lhr.audits[auditName]?.numericValue || NaN;
11
10
 
12
11
  /**
13
12
  * @param {Array<number>} numbers
@@ -251,8 +251,8 @@ class NetworkRecorder extends EventEmitter {
251
251
  if (record.redirectSource) {
252
252
  return record.redirectSource;
253
253
  }
254
- const stackFrames = (record.initiator.stack && record.initiator.stack.callFrames) || [];
255
- const initiatorURL = record.initiator.url || (stackFrames[0] && stackFrames[0].url);
254
+ const stackFrames = record.initiator.stack?.callFrames || [];
255
+ const initiatorURL = record.initiator.url || stackFrames[0]?.url;
256
256
 
257
257
  let candidates = recordsByURL.get(initiatorURL) || [];
258
258
  // The initiator must come before the initiated request.
@@ -510,7 +510,7 @@ class NetworkRequest {
510
510
 
511
511
  const reasonHeader = record.responseHeaders
512
512
  .find(header => header.name === 'Non-Authoritative-Reason');
513
- const reason = reasonHeader && reasonHeader.value;
513
+ const reason = reasonHeader?.value;
514
514
  return reason === 'HSTS' && NetworkRequest.isSecureRequest(destination);
515
515
  }
516
516
 
@@ -184,8 +184,7 @@ class CpuProfilerModel {
184
184
  if (!event) return false;
185
185
  return Boolean(
186
186
  event.name === SAMPLER_TRACE_EVENT_NAME &&
187
- event.args.data &&
188
- event.args.data._syntheticProfilerRange
187
+ event.args.data?._syntheticProfilerRange
189
188
  );
190
189
  }
191
190
 
@@ -562,9 +561,8 @@ class CpuProfilerModel {
562
561
 
563
562
  // `Profile` or `ProfileChunk` can partially define these across multiple events.
564
563
  // We'll fallback to empty values and worry about validation in the `synthesizeTraceEvents` phase.
565
- const cpuProfileArg = (event.args.data && event.args.data.cpuProfile) || {};
566
- const timeDeltas =
567
- (event.args.data && event.args.data.timeDeltas) || cpuProfileArg.timeDeltas;
564
+ const cpuProfileArg = event.args.data?.cpuProfile || {};
565
+ const timeDeltas = event.args.data?.timeDeltas || cpuProfileArg.timeDeltas;
568
566
  let profile = profiles.get(event.id);
569
567
 
570
568
  if (event.name === 'Profile') {
@@ -572,7 +570,7 @@ class CpuProfilerModel {
572
570
  id: event.id,
573
571
  pid: event.pid,
574
572
  tid: event.tid,
575
- startTime: (event.args.data && event.args.data.startTime) || event.ts,
573
+ startTime: event.args.data?.startTime || event.ts,
576
574
  nodes: cpuProfileArg.nodes || [],
577
575
  samples: cpuProfileArg.samples || [],
578
576
  timeDeltas: timeDeltas || [],
@@ -234,7 +234,7 @@ class MainThreadTasks {
234
234
  // Do bookkeeping on XHR requester data.
235
235
  if (nextTask.event.name === 'XHRReadyStateChange') {
236
236
  const data = nextTask.event.args.data;
237
- const url = data && data.url;
237
+ const url = data?.url;
238
238
  if (data && url && data.readyState === 1) priorTaskData.xhrs.set(url, nextTask);
239
239
  }
240
240
 
@@ -425,15 +425,14 @@ class TraceProcessor {
425
425
  static findMainFrameIds(events) {
426
426
  // Prefer the newer TracingStartedInBrowser event first, if it exists
427
427
  const startedInBrowserEvt = events.find(e => e.name === 'TracingStartedInBrowser');
428
- if (startedInBrowserEvt && startedInBrowserEvt.args.data &&
429
- startedInBrowserEvt.args.data.frames) {
428
+ if (startedInBrowserEvt?.args.data?.frames) {
430
429
  const mainFrame = startedInBrowserEvt.args.data.frames.find(frame => !frame.parent);
431
- const frameId = mainFrame && mainFrame.frame;
432
- const pid = mainFrame && mainFrame.processId;
430
+ const frameId = mainFrame?.frame;
431
+ const pid = mainFrame?.processId;
433
432
 
434
433
  const threadNameEvt = events.find(e => e.pid === pid && e.ph === 'M' &&
435
434
  e.cat === '__metadata' && e.name === 'thread_name' && e.args.name === 'CrRendererMain');
436
- const tid = threadNameEvt && threadNameEvt.tid;
435
+ const tid = threadNameEvt?.tid;
437
436
 
438
437
  if (pid && tid && frameId) {
439
438
  return {
@@ -448,7 +447,7 @@ class TraceProcessor {
448
447
  // The first TracingStartedInPage in the trace is definitely our renderer thread of interest
449
448
  // Beware: the tracingStartedInPage event can appear slightly after a navigationStart
450
449
  const startedInPageEvt = events.find(e => e.name === 'TracingStartedInPage');
451
- if (startedInPageEvt && startedInPageEvt.args && startedInPageEvt.args.data) {
450
+ if (startedInPageEvt?.args?.data) {
452
451
  const frameId = startedInPageEvt.args.data.page;
453
452
  if (frameId) {
454
453
  return {
@@ -463,12 +462,12 @@ class TraceProcessor {
463
462
  // If we can't find either TracingStarted event, then we'll fallback to the first navStart that
464
463
  // looks like it was loading the main frame with a real URL. Because the schema for this event
465
464
  // has changed across Chrome versions, we'll be extra defensive about finding this case.
466
- const navStartEvt = events.find(e => Boolean(e.name === 'navigationStart' && e.args &&
467
- e.args.data && e.args.data.isLoadingMainFrame && e.args.data.documentLoaderURL));
465
+ const navStartEvt = events.find(e => Boolean(e.name === 'navigationStart' &&
466
+ e.args?.data?.isLoadingMainFrame && e.args.data.documentLoaderURL));
468
467
  // Find the first resource that was requested and make sure it agrees on the id.
469
468
  const firstResourceSendEvt = events.find(e => e.name === 'ResourceSendRequest');
470
469
  // We know that these properties exist if we found the events, but TSC doesn't.
471
- if (navStartEvt && navStartEvt.args && navStartEvt.args.data &&
470
+ if (navStartEvt?.args?.data &&
472
471
  firstResourceSendEvt &&
473
472
  firstResourceSendEvt.pid === navStartEvt.pid &&
474
473
  firstResourceSendEvt.tid === navStartEvt.tid) {
@@ -503,7 +502,7 @@ class TraceProcessor {
503
502
  static isLCPEvent(evt) {
504
503
  if (evt.name !== 'largestContentfulPaint::Invalidate' &&
505
504
  evt.name !== 'largestContentfulPaint::Candidate') return false;
506
- return Boolean(evt.args && evt.args.frame);
505
+ return Boolean(evt.args?.frame);
507
506
  }
508
507
 
509
508
  /**
@@ -513,8 +512,7 @@ class TraceProcessor {
513
512
  static isLCPCandidateEvent(evt) {
514
513
  return Boolean(
515
514
  evt.name === 'largestContentfulPaint::Candidate' &&
516
- evt.args &&
517
- evt.args.frame &&
515
+ evt.args?.frame &&
518
516
  evt.args.data &&
519
517
  evt.args.data.size !== undefined
520
518
  );
@@ -613,8 +611,7 @@ class TraceProcessor {
613
611
  .filter(/** @return {evt is FrameCommittedEvent} */ evt => {
614
612
  return Boolean(
615
613
  evt.name === 'FrameCommittedInBrowser' &&
616
- evt.args.data &&
617
- evt.args.data.frame &&
614
+ evt.args.data?.frame &&
618
615
  evt.args.data.url
619
616
  );
620
617
  })
@@ -723,7 +720,7 @@ class TraceProcessor {
723
720
  firstContentfulPaintAllFrames: getTiming(fcpAllFramesEvt.ts),
724
721
  firstMeaningfulPaint: frameTimings.timings.firstMeaningfulPaint,
725
722
  largestContentfulPaint: frameTimings.timings.largestContentfulPaint,
726
- largestContentfulPaintAllFrames: maybeGetTiming(lcpAllFramesEvt && lcpAllFramesEvt.ts),
723
+ largestContentfulPaintAllFrames: maybeGetTiming(lcpAllFramesEvt?.ts),
727
724
  load: frameTimings.timings.load,
728
725
  domContentLoaded: frameTimings.timings.domContentLoaded,
729
726
  traceEnd: timings.traceEnd,
@@ -735,7 +732,7 @@ class TraceProcessor {
735
732
  firstContentfulPaintAllFrames: fcpAllFramesEvt.ts,
736
733
  firstMeaningfulPaint: frameTimings.timestamps.firstMeaningfulPaint,
737
734
  largestContentfulPaint: frameTimings.timestamps.largestContentfulPaint,
738
- largestContentfulPaintAllFrames: lcpAllFramesEvt && lcpAllFramesEvt.ts,
735
+ largestContentfulPaintAllFrames: lcpAllFramesEvt?.ts,
739
736
  load: frameTimings.timestamps.load,
740
737
  domContentLoaded: frameTimings.timestamps.domContentLoaded,
741
738
  traceEnd: timestamps.traceEnd,
@@ -886,7 +883,7 @@ class TraceProcessor {
886
883
  );
887
884
 
888
885
  /** @param {{ts: number}=} event */
889
- const getTimestamp = (event) => event && event.ts;
886
+ const getTimestamp = (event) => event?.ts;
890
887
  /** @type {TraceNavigationTimesForFrame} */
891
888
  const timestamps = {
892
889
  timeOrigin: timeOriginEvt.ts,
@@ -49,7 +49,7 @@ class Runner {
49
49
  Sentry.captureBreadcrumb({
50
50
  message: 'Run started',
51
51
  category: 'lifecycle',
52
- data: sentryContext && sentryContext.extra,
52
+ data: sentryContext?.extra,
53
53
  });
54
54
 
55
55
  // User can run -G solo, -A solo, or -GA together
@@ -116,7 +116,7 @@ class Runner {
116
116
 
117
117
  // Use version from gathering stage.
118
118
  // If accessibility gatherer didn't run or errored, it won't be in credits.
119
- const axeVersion = artifacts.Accessibility && artifacts.Accessibility.version;
119
+ const axeVersion = artifacts.Accessibility?.version;
120
120
  const credits = {
121
121
  'axe-core': axeVersion,
122
122
  };
@@ -213,7 +213,7 @@ class Runner {
213
213
  };
214
214
  }).sort((a, b) => a.startTime - b.startTime);
215
215
  const runnerEntry = timingEntries.find(e => e.name === 'lh:runner:run');
216
- return {entries: timingEntries, total: runnerEntry && runnerEntry.duration || 0};
216
+ return {entries: timingEntries, total: runnerEntry?.duration || 0};
217
217
  }
218
218
 
219
219
  /**
@@ -367,7 +367,7 @@ class Runner {
367
367
  return narrowedArtifacts;
368
368
  }, /** @type {LH.Artifacts} */ ({}));
369
369
  const product = await audit.audit(narrowedArtifacts, auditContext);
370
- runWarnings.push(...product.runWarnings || []);
370
+ runWarnings.push(...(product.runWarnings || []));
371
371
 
372
372
  auditResult = Audit.generateAuditResult(audit, product);
373
373
  } catch (err) {