lighthouse 11.7.0 → 11.7.1

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/core/audits/accessibility/accesskeys.js +1 -1
  2. package/core/audits/accessibility/aria-allowed-attr.js +1 -1
  3. package/core/audits/accessibility/aria-allowed-role.js +1 -1
  4. package/core/audits/accessibility/aria-command-name.js +1 -1
  5. package/core/audits/accessibility/aria-dialog-name.js +1 -1
  6. package/core/audits/accessibility/aria-hidden-body.js +1 -1
  7. package/core/audits/accessibility/aria-hidden-focus.js +1 -1
  8. package/core/audits/accessibility/aria-input-field-name.js +1 -1
  9. package/core/audits/accessibility/aria-meter-name.js +1 -1
  10. package/core/audits/accessibility/aria-progressbar-name.js +1 -1
  11. package/core/audits/accessibility/aria-required-attr.js +1 -1
  12. package/core/audits/accessibility/aria-required-children.js +1 -1
  13. package/core/audits/accessibility/aria-required-parent.js +1 -1
  14. package/core/audits/accessibility/aria-roles.js +1 -1
  15. package/core/audits/accessibility/aria-text.js +1 -1
  16. package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
  17. package/core/audits/accessibility/aria-tooltip-name.js +1 -1
  18. package/core/audits/accessibility/aria-treeitem-name.js +1 -1
  19. package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
  20. package/core/audits/accessibility/aria-valid-attr.js +1 -1
  21. package/core/audits/accessibility/button-name.js +1 -1
  22. package/core/audits/accessibility/bypass.js +1 -1
  23. package/core/audits/accessibility/color-contrast.js +1 -1
  24. package/core/audits/accessibility/definition-list.js +1 -1
  25. package/core/audits/accessibility/dlitem.js +1 -1
  26. package/core/audits/accessibility/document-title.js +1 -1
  27. package/core/audits/accessibility/duplicate-id-active.js +1 -1
  28. package/core/audits/accessibility/duplicate-id-aria.js +1 -1
  29. package/core/audits/accessibility/empty-heading.js +1 -1
  30. package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
  31. package/core/audits/accessibility/frame-title.js +1 -1
  32. package/core/audits/accessibility/heading-order.js +1 -1
  33. package/core/audits/accessibility/html-has-lang.js +1 -1
  34. package/core/audits/accessibility/html-lang-valid.js +1 -1
  35. package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
  36. package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
  37. package/core/audits/accessibility/image-alt.js +1 -1
  38. package/core/audits/accessibility/image-redundant-alt.js +1 -1
  39. package/core/audits/accessibility/input-button-name.js +1 -1
  40. package/core/audits/accessibility/input-image-alt.js +1 -1
  41. package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
  42. package/core/audits/accessibility/label.js +1 -1
  43. package/core/audits/accessibility/landmark-one-main.js +1 -1
  44. package/core/audits/accessibility/link-in-text-block.js +1 -1
  45. package/core/audits/accessibility/link-name.js +1 -1
  46. package/core/audits/accessibility/list.js +1 -1
  47. package/core/audits/accessibility/listitem.js +1 -1
  48. package/core/audits/accessibility/meta-refresh.js +1 -1
  49. package/core/audits/accessibility/meta-viewport.js +1 -1
  50. package/core/audits/accessibility/object-alt.js +1 -1
  51. package/core/audits/accessibility/select-name.js +1 -1
  52. package/core/audits/accessibility/skip-link.js +1 -1
  53. package/core/audits/accessibility/tabindex.js +1 -1
  54. package/core/audits/accessibility/table-duplicate-name.js +1 -1
  55. package/core/audits/accessibility/table-fake-caption.js +1 -1
  56. package/core/audits/accessibility/target-size.js +1 -1
  57. package/core/audits/accessibility/td-has-header.js +1 -1
  58. package/core/audits/accessibility/td-headers-attr.js +1 -1
  59. package/core/audits/accessibility/th-has-data-cells.js +1 -1
  60. package/core/audits/accessibility/valid-lang.js +1 -1
  61. package/core/audits/accessibility/video-caption.js +1 -1
  62. package/core/audits/layout-shifts.js +1 -1
  63. package/core/computed/metrics/cumulative-layout-shift.js +2 -2
  64. package/core/computed/trace-engine-result.js +1 -1
  65. package/core/gather/gatherers/trace-elements.d.ts +2 -1
  66. package/core/gather/gatherers/trace-elements.js +40 -10
  67. package/core/lib/network-request.js +1 -1
  68. package/core/lib/proto-preprocessor.js +24 -8
  69. package/core/lib/tracehouse/trace-processor.js +15 -2
  70. package/package.json +3 -3
  71. package/shared/localization/locales/en-US.json +61 -61
  72. package/shared/localization/locales/en-XL.json +61 -61
@@ -20,7 +20,7 @@ const UIStrings = {
20
20
  /** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
21
21
  description: 'The summary attribute should describe the table structure, while `<caption>` ' +
22
22
  'should have the onscreen title. Accurate table mark-up helps users of screen readers. ' +
23
- '[Learn more about summary and caption](https://dequeuniversity.com/rules/axe/4.8/table-duplicate-name).',
23
+ '[Learn more about summary and caption](https://dequeuniversity.com/rules/axe/4.9/table-duplicate-name).',
24
24
  };
25
25
 
26
26
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -23,7 +23,7 @@ const UIStrings = {
23
23
  description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
24
24
  'that tables use the actual caption element instead of cells with the `[colspan]` ' +
25
25
  'attribute may improve the experience for screen reader users. ' +
26
- '[Learn more about captions](https://dequeuniversity.com/rules/axe/4.8/table-fake-caption).',
26
+ '[Learn more about captions](https://dequeuniversity.com/rules/axe/4.9/table-fake-caption).',
27
27
  };
28
28
 
29
29
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -20,7 +20,7 @@ const UIStrings = {
20
20
  /** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
21
21
  description: 'Touch targets with sufficient size and spacing help users who may have ' +
22
22
  'difficulty targeting small controls to activate the targets. ' +
23
- '[Learn more about touch targets](https://dequeuniversity.com/rules/axe/4.8/target-size).',
23
+ '[Learn more about touch targets](https://dequeuniversity.com/rules/axe/4.9/target-size).',
24
24
  };
25
25
 
26
26
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -21,7 +21,7 @@ const UIStrings = {
21
21
  description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
22
22
  'that `<td>` elements in a large table (3 or more cells in width and height) have an ' +
23
23
  'associated table header may improve the experience for screen reader users. ' +
24
- '[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.8/td-has-header).',
24
+ '[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.9/td-has-header).',
25
25
  };
26
26
 
27
27
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -24,7 +24,7 @@ const UIStrings = {
24
24
  description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
25
25
  '`<td>` cells using the `[headers]` attribute only refer to other cells in the same ' +
26
26
  'table may improve the experience for screen reader users. ' +
27
- '[Learn more about the `headers` attribute](https://dequeuniversity.com/rules/axe/4.8/td-headers-attr).',
27
+ '[Learn more about the `headers` attribute](https://dequeuniversity.com/rules/axe/4.9/td-headers-attr).',
28
28
  };
29
29
 
30
30
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -23,7 +23,7 @@ const UIStrings = {
23
23
  description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
24
24
  'table headers always refer to some set of cells may improve the experience for screen ' +
25
25
  'reader users. ' +
26
- '[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.8/th-has-data-cells).',
26
+ '[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.9/th-has-data-cells).',
27
27
  };
28
28
 
29
29
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -20,7 +20,7 @@ const UIStrings = {
20
20
  /** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
21
21
  description: 'Specifying a valid [BCP 47 language](https://www.w3.org/International/questions/qa-choosing-language-tags#question) ' +
22
22
  'on elements helps ensure that text is pronounced correctly by a screen reader. ' +
23
- '[Learn how to use the `lang` attribute](https://dequeuniversity.com/rules/axe/4.8/valid-lang).',
23
+ '[Learn how to use the `lang` attribute](https://dequeuniversity.com/rules/axe/4.9/valid-lang).',
24
24
  };
25
25
 
26
26
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -21,7 +21,7 @@ const UIStrings = {
21
21
  /** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
22
22
  description: 'When a video provides a caption it is easier for deaf and hearing impaired ' +
23
23
  'users to access its information. ' +
24
- '[Learn more about video captions](https://dequeuniversity.com/rules/axe/4.8/video-caption).',
24
+ '[Learn more about video captions](https://dequeuniversity.com/rules/axe/4.9/video-caption).',
25
25
  };
26
26
 
27
27
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -78,7 +78,7 @@ class LayoutShifts extends Audit {
78
78
  .slice(0, MAX_LAYOUT_SHIFTS);
79
79
  for (const event of topLayoutShiftEvents) {
80
80
  const biggestImpactNodeId = TraceElements.getBiggestImpactNodeForShiftEvent(
81
- event.args.data.impacted_nodes || [], impactByNodeId);
81
+ event.args.data.impacted_nodes || [], impactByNodeId, event);
82
82
  const biggestImpactElement = traceElements.find(t => t.nodeId === biggestImpactNodeId);
83
83
 
84
84
  // Turn root causes into sub-items.
@@ -163,10 +163,10 @@ class CumulativeLayoutShift {
163
163
  await processor.parse(/** @type {import('@paulirish/trace_engine').Types.TraceEvents.TraceEventData[]} */ (
164
164
  events
165
165
  ));
166
- if (!processor.data) {
166
+ if (!processor.traceParsedData) {
167
167
  throw new Error('null trace engine result');
168
168
  }
169
- return processor.data.LayoutShifts.sessionMaxScore;
169
+ return processor.traceParsedData.LayoutShifts.sessionMaxScore;
170
170
  };
171
171
  const cumulativeLayoutShift = await run(allFrameShiftEvents.map(e => e.event));
172
172
  const cumulativeLayoutShiftMainFrame = await run(mainFrameShiftEvents.map(e => e.event));
@@ -32,7 +32,7 @@ class TraceEngineResult {
32
32
  ));
33
33
  // TODO: use TraceEngine.TraceProcessor.createWithAllHandlers above.
34
34
  return /** @type {import('@paulirish/trace_engine').Handlers.Types.TraceParseData} */(
35
- engine.data);
35
+ engine.traceParsedData);
36
36
  }
37
37
 
38
38
  /**
@@ -25,9 +25,10 @@ declare class TraceElements extends BaseGatherer {
25
25
  *
26
26
  * @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
27
27
  * @param {Map<number, number>} impactByNodeId
28
+ * @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
28
29
  * @return {number|undefined}
29
30
  */
30
- static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number>): number | undefined;
31
+ static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number>, event: import('../../lib/trace-engine.js').SaneSyntheticLayoutShift): number | undefined;
31
32
  /**
32
33
  * This function finds the top (up to 15) layout shifts on the page, and returns
33
34
  * the id of the largest impacted node of each shift, along with any related nodes
@@ -88,19 +88,49 @@ class TraceElements extends BaseGatherer {
88
88
  *
89
89
  * @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
90
90
  * @param {Map<number, number>} impactByNodeId
91
+ * @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
91
92
  * @return {number|undefined}
92
93
  */
93
- static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId) {
94
- let biggestImpactNodeId;
95
- let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
96
- for (const node of impactedNodes) {
97
- const impactScore = impactByNodeId.get(node.node_id);
98
- if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
99
- biggestImpactNodeId = node.node_id;
100
- biggestImpactNodeScore = impactScore;
94
+ static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event) {
95
+ try {
96
+ let biggestImpactNodeId;
97
+ let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
98
+ for (const node of impactedNodes) {
99
+ const impactScore = impactByNodeId.get(node.node_id);
100
+ if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
101
+ biggestImpactNodeId = node.node_id;
102
+ biggestImpactNodeScore = impactScore;
103
+ }
101
104
  }
105
+ return biggestImpactNodeId;
106
+ } catch (err) {
107
+ // See https://github.com/GoogleChrome/lighthouse/issues/15870
108
+ // `impactedNodes` should always be an array here, but it can randomly be something else for
109
+ // currently unknown reasons. This exception handling will help us identify what
110
+ // `impactedNodes` really is and also prevent the error from being fatal.
111
+
112
+ // It's possible `impactedNodes` is not JSON serializable, so let's add more supplemental
113
+ // fields just in case.
114
+ const impactedNodesType = typeof impactedNodes;
115
+ const impactedNodesClassName = impactedNodes?.constructor?.name;
116
+
117
+ let impactedNodesJson;
118
+ let eventJson;
119
+ try {
120
+ impactedNodesJson = JSON.parse(JSON.stringify(impactedNodes));
121
+ eventJson = JSON.parse(JSON.stringify(event));
122
+ } catch {}
123
+
124
+ Sentry.captureException(err, {
125
+ extra: {
126
+ impactedNodes: impactedNodesJson,
127
+ event: eventJson,
128
+ impactedNodesType,
129
+ impactedNodesClassName,
130
+ },
131
+ });
132
+ return;
102
133
  }
103
- return biggestImpactNodeId;
104
134
  }
105
135
 
106
136
  /**
@@ -129,7 +159,7 @@ class TraceElements extends BaseGatherer {
129
159
  const nodeIds = [];
130
160
  const impactedNodes = event.args.data.impacted_nodes || [];
131
161
  const biggestImpactedNodeId =
132
- this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId);
162
+ this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event);
133
163
  if (biggestImpactedNodeId !== undefined) {
134
164
  nodeIds.push(biggestImpactedNodeId);
135
165
  }
@@ -634,7 +634,7 @@ class NetworkRequest {
634
634
  /^content-encoding$/i,
635
635
  /^x-content-encoding-over-network$/i,
636
636
  ];
637
- const compressionTypes = ['gzip', 'br', 'deflate'];
637
+ const compressionTypes = ['gzip', 'br', 'deflate', 'zstd'];
638
638
  return record.responseHeaders.some(header =>
639
639
  patterns.some(p => header.name.match(p)) && compressionTypes.includes(header.value)
640
640
  );
@@ -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
  }
@@ -403,6 +403,9 @@ class TraceProcessor {
403
403
  */
404
404
  static getMainThreadTopLevelEvents(trace, startTime = 0, endTime = Infinity) {
405
405
  const topLevelEvents = [];
406
+ /** @type {ToplevelEvent|undefined} */
407
+ let prevToplevel = undefined;
408
+
406
409
  // note: mainThreadEvents is already sorted by event start
407
410
  for (const event of trace.mainThreadEvents) {
408
411
  if (!this.isScheduleableTask(event) || !event.dur) continue;
@@ -411,11 +414,21 @@ class TraceProcessor {
411
414
  const end = (event.ts + event.dur - trace.timeOriginEvt.ts) / 1000;
412
415
  if (start > endTime || end < startTime) continue;
413
416
 
414
- topLevelEvents.push({
417
+ // Temporary fix for a Chrome bug where some RunTask events can be overlapping.
418
+ // We correct that here be ensuring each RunTask ends at least 1 microsecond before the next
419
+ // https://github.com/GoogleChrome/lighthouse/issues/15896
420
+ // https://issues.chromium.org/issues/329678173
421
+ if (prevToplevel && start < prevToplevel.end) {
422
+ prevToplevel.end = start - 0.001;
423
+ }
424
+
425
+ prevToplevel = {
415
426
  start,
416
427
  end,
417
428
  duration: event.dur / 1000,
418
- });
429
+ };
430
+
431
+ topLevelEvents.push(prevToplevel);
419
432
  }
420
433
 
421
434
  return topLevelEvents;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "11.7.0",
4
+ "version": "11.7.1",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -179,9 +179,9 @@
179
179
  "webtreemap-cdt": "^3.2.1"
180
180
  },
181
181
  "dependencies": {
182
- "@paulirish/trace_engine": "^0.0.12",
182
+ "@paulirish/trace_engine": "^0.0.19",
183
183
  "@sentry/node": "^6.17.4",
184
- "axe-core": "^4.8.4",
184
+ "axe-core": "^4.9.0",
185
185
  "chrome-launcher": "^1.1.1",
186
186
  "configstore": "^5.0.1",
187
187
  "csp_evaluator": "1.1.1",