lighthouse 12.3.0 → 12.4.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 (142) hide show
  1. package/cli/test/smokehouse/core-tests.js +4 -0
  2. package/core/audits/audit.d.ts +5 -0
  3. package/core/audits/audit.js +12 -0
  4. package/core/audits/bootup-time.js +0 -2
  5. package/core/audits/byte-efficiency/duplicated-javascript.d.ts +4 -5
  6. package/core/audits/byte-efficiency/duplicated-javascript.js +9 -5
  7. package/core/audits/byte-efficiency/legacy-javascript.d.ts +2 -2
  8. package/core/audits/byte-efficiency/legacy-javascript.js +17 -5
  9. package/core/audits/byte-efficiency/polyfill-graph-data.json +48 -49
  10. package/core/audits/byte-efficiency/total-byte-weight.js +0 -2
  11. package/core/audits/clickjacking-mitigation.d.ts +42 -0
  12. package/core/audits/clickjacking-mitigation.js +139 -0
  13. package/core/audits/dobetterweb/dom-size.js +0 -2
  14. package/core/audits/insights/README.md +3 -0
  15. package/core/audits/insights/cls-culprits-insight.d.ts +25 -0
  16. package/core/audits/insights/cls-culprits-insight.js +137 -0
  17. package/core/audits/insights/document-latency-insight.d.ts +11 -0
  18. package/core/audits/insights/document-latency-insight.js +48 -0
  19. package/core/audits/insights/dom-size-insight.d.ts +11 -0
  20. package/core/audits/insights/dom-size-insight.js +85 -0
  21. package/core/audits/insights/font-display-insight.d.ts +11 -0
  22. package/core/audits/insights/font-display-insight.js +53 -0
  23. package/core/audits/insights/forced-reflow-insight.d.ts +11 -0
  24. package/core/audits/insights/forced-reflow-insight.js +52 -0
  25. package/core/audits/insights/image-delivery-insight.d.ts +11 -0
  26. package/core/audits/insights/image-delivery-insight.js +83 -0
  27. package/core/audits/insights/insight-audit.d.ts +23 -0
  28. package/core/audits/insights/insight-audit.js +133 -0
  29. package/core/audits/insights/interaction-to-next-paint-insight.d.ts +11 -0
  30. package/core/audits/insights/interaction-to-next-paint-insight.js +71 -0
  31. package/core/audits/insights/lcp-discovery-insight.d.ts +11 -0
  32. package/core/audits/insights/lcp-discovery-insight.js +48 -0
  33. package/core/audits/insights/lcp-phases-insight.d.ts +16 -0
  34. package/core/audits/insights/lcp-phases-insight.js +87 -0
  35. package/core/audits/insights/long-critical-network-tree-insight.d.ts +11 -0
  36. package/core/audits/insights/long-critical-network-tree-insight.js +53 -0
  37. package/core/audits/insights/render-blocking-insight.d.ts +11 -0
  38. package/core/audits/insights/render-blocking-insight.js +57 -0
  39. package/core/audits/insights/slow-css-selector-insight.d.ts +11 -0
  40. package/core/audits/insights/slow-css-selector-insight.js +52 -0
  41. package/core/audits/insights/third-parties-insight.d.ts +28 -0
  42. package/core/audits/insights/third-parties-insight.js +90 -0
  43. package/core/audits/insights/viewport-insight.d.ts +11 -0
  44. package/core/audits/insights/viewport-insight.js +54 -0
  45. package/core/audits/layout-shifts.d.ts +0 -1
  46. package/core/audits/layout-shifts.js +18 -21
  47. package/core/audits/mainthread-work-breakdown.js +0 -2
  48. package/core/audits/seo/is-crawlable.d.ts +1 -0
  49. package/core/audits/server-response-time.js +0 -1
  50. package/core/computed/metrics/lantern-metric.js +5 -1
  51. package/core/computed/trace-engine-result.js +71 -17
  52. package/core/config/default-config.js +37 -1
  53. package/core/gather/gatherers/inspector-issues.js +3 -0
  54. package/core/gather/gatherers/trace-elements.d.ts +10 -2
  55. package/core/gather/gatherers/trace-elements.js +89 -12
  56. package/core/lib/bf-cache-strings.d.ts +7 -4
  57. package/core/lib/bf-cache-strings.js +174 -140
  58. package/core/lib/cdt/generated/ParsedURL.d.ts +1 -0
  59. package/core/lib/cdt/generated/ParsedURL.js +16 -4
  60. package/core/lib/cdt/generated/SourceMap.d.ts +32 -5
  61. package/core/lib/cdt/generated/SourceMap.js +192 -100
  62. package/core/lib/deprecations-strings.d.ts +78 -98
  63. package/core/lib/deprecations-strings.js +23 -41
  64. package/core/lib/i18n/i18n.d.ts +1 -0
  65. package/core/lib/i18n/i18n.js +2 -0
  66. package/core/lib/trace-engine.d.ts +1 -0
  67. package/core/lib/trace-engine.js +2 -0
  68. package/core/runner.js +2 -0
  69. package/dist/report/bundle.esm.js +196 -9
  70. package/dist/report/flow.js +197 -10
  71. package/dist/report/standalone.js +197 -10
  72. package/flow-report/src/i18n/i18n.d.ts +2 -0
  73. package/package.json +15 -13
  74. package/readme.md +3 -0
  75. package/report/assets/styles.css +179 -5
  76. package/report/assets/templates.html +14 -0
  77. package/report/renderer/components.js +9 -3
  78. package/report/renderer/details-renderer.d.ts +5 -0
  79. package/report/renderer/details-renderer.js +24 -0
  80. package/report/renderer/dom.d.ts +12 -1
  81. package/report/renderer/dom.js +26 -1
  82. package/report/renderer/i18n-formatter.d.ts +1 -1
  83. package/report/renderer/performance-category-renderer.d.ts +10 -0
  84. package/report/renderer/performance-category-renderer.js +81 -20
  85. package/report/renderer/report-utils.d.ts +1 -0
  86. package/report/renderer/report-utils.js +2 -0
  87. package/report/renderer/topbar-features.js +7 -0
  88. package/shared/localization/locales/ar-XB.json +74 -26
  89. package/shared/localization/locales/ar.json +76 -28
  90. package/shared/localization/locales/bg.json +74 -26
  91. package/shared/localization/locales/ca.json +74 -26
  92. package/shared/localization/locales/cs.json +74 -26
  93. package/shared/localization/locales/da.json +74 -26
  94. package/shared/localization/locales/de.json +75 -27
  95. package/shared/localization/locales/el.json +74 -26
  96. package/shared/localization/locales/en-GB.json +74 -26
  97. package/shared/localization/locales/en-US.json +288 -30
  98. package/shared/localization/locales/en-XA.json +48 -24
  99. package/shared/localization/locales/en-XL.json +288 -30
  100. package/shared/localization/locales/es-419.json +74 -26
  101. package/shared/localization/locales/es.json +74 -26
  102. package/shared/localization/locales/fi.json +74 -26
  103. package/shared/localization/locales/fil.json +75 -27
  104. package/shared/localization/locales/fr.json +74 -26
  105. package/shared/localization/locales/he.json +82 -34
  106. package/shared/localization/locales/hi.json +74 -26
  107. package/shared/localization/locales/hr.json +75 -27
  108. package/shared/localization/locales/hu.json +74 -26
  109. package/shared/localization/locales/id.json +74 -26
  110. package/shared/localization/locales/it.json +85 -37
  111. package/shared/localization/locales/ja.json +75 -27
  112. package/shared/localization/locales/ko.json +75 -27
  113. package/shared/localization/locales/lt.json +75 -27
  114. package/shared/localization/locales/lv.json +74 -26
  115. package/shared/localization/locales/nl.json +74 -26
  116. package/shared/localization/locales/no.json +75 -27
  117. package/shared/localization/locales/pl.json +74 -26
  118. package/shared/localization/locales/pt-PT.json +74 -26
  119. package/shared/localization/locales/pt.json +74 -26
  120. package/shared/localization/locales/ro.json +74 -26
  121. package/shared/localization/locales/ru.json +74 -26
  122. package/shared/localization/locales/sk.json +74 -26
  123. package/shared/localization/locales/sl.json +74 -26
  124. package/shared/localization/locales/sr-Latn.json +74 -26
  125. package/shared/localization/locales/sr.json +74 -26
  126. package/shared/localization/locales/sv.json +74 -26
  127. package/shared/localization/locales/ta.json +74 -26
  128. package/shared/localization/locales/te.json +74 -26
  129. package/shared/localization/locales/th.json +76 -28
  130. package/shared/localization/locales/tr.json +74 -26
  131. package/shared/localization/locales/uk.json +74 -26
  132. package/shared/localization/locales/vi.json +74 -26
  133. package/shared/localization/locales/zh-HK.json +75 -27
  134. package/shared/localization/locales/zh-TW.json +74 -26
  135. package/shared/localization/locales/zh.json +74 -26
  136. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +3 -0
  137. package/types/artifacts.d.ts +5 -3
  138. package/types/audit.d.ts +2 -0
  139. package/types/lhr/audit-details.d.ts +13 -1
  140. package/types/lhr/audit-result.d.ts +2 -0
  141. package/core/gather/gatherers/root-causes.d.ts +0 -19
  142. package/core/gather/gatherers/root-causes.js +0 -144
@@ -29,8 +29,6 @@ const UIStrings = {
29
29
  rootCauseFontChanges: 'Web font loaded',
30
30
  /** A possible reason why that the layout shift occured. */
31
31
  rootCauseInjectedIframe: 'Injected iframe',
32
- /** A possible reason why that the layout shift occured. */
33
- rootCauseRenderBlockingRequest: 'A late network request adjusted the page layout',
34
32
  /** Label shown per-audit to show how many layout shifts are present. The `{# shifts found}` placeholder will be replaced with the number of layout shifts. */
35
33
  displayValueShiftsFound: `{shiftCount, plural, =1 {1 layout shift found} other {# layout shifts found}}`,
36
34
  };
@@ -49,7 +47,7 @@ class LayoutShifts extends Audit {
49
47
  description: str_(UIStrings.description),
50
48
  scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
51
49
  guidanceLevel: 2,
52
- requiredArtifacts: ['traces', 'RootCauses', 'TraceElements'],
50
+ requiredArtifacts: ['traces', 'TraceElements'],
53
51
  };
54
52
  }
55
53
 
@@ -67,11 +65,21 @@ class LayoutShifts extends Audit {
67
65
  const traceElements = artifacts.TraceElements
68
66
  .filter(element => element.traceEventType === 'layout-shift');
69
67
 
68
+ /** @type {LH.Artifacts.TraceEngineRootCauses} */
69
+ const allRootCauses = {
70
+ layoutShifts: new Map(),
71
+ };
72
+ for (const insightSet of traceEngineResult.insights.values()) {
73
+ for (const [shift, reasons] of insightSet.model.CLSCulprits.shifts) {
74
+ allRootCauses.layoutShifts.set(shift, reasons);
75
+ }
76
+ }
77
+
70
78
  /** @type {Item[]} */
71
79
  const items = [];
72
80
  const layoutShiftEvents =
73
81
  /** @type {import('../lib/trace-engine.js').SaneSyntheticLayoutShift[]} */(
74
- clusters.flatMap(c => c.events)
82
+ clusters.flatMap(c => c.events).filter(e => !!e.args.data)
75
83
  );
76
84
  const topLayoutShiftEvents = layoutShiftEvents
77
85
  .sort((a, b) => b.args.data.weighted_score_delta - a.args.data.weighted_score_delta)
@@ -82,41 +90,30 @@ class LayoutShifts extends Audit {
82
90
  const biggestImpactElement = traceElements.find(t => t.nodeId === biggestImpactNodeId);
83
91
 
84
92
  // Turn root causes into sub-items.
85
- const index = layoutShiftEvents.indexOf(event);
86
- const rootCauses = artifacts.RootCauses.layoutShifts[index];
93
+ const rootCauses = allRootCauses.layoutShifts.get(event);
87
94
  /** @type {SubItem[]} */
88
95
  const subItems = [];
89
96
  if (rootCauses) {
90
- for (const cause of rootCauses.unsizedMedia) {
97
+ for (const backendNodeId of rootCauses.unsizedImages) {
91
98
  const element = artifacts.TraceElements.find(
92
- t => t.traceEventType === 'layout-shift' && t.nodeId === cause.node.backendNodeId);
99
+ t => t.traceEventType === 'trace-engine' && t.nodeId === backendNodeId);
93
100
  subItems.push({
94
101
  extra: element ? Audit.makeNodeItem(element.node) : undefined,
95
102
  cause: str_(UIStrings.rootCauseUnsizedMedia),
96
103
  });
97
104
  }
98
- for (const cause of rootCauses.fontChanges) {
99
- const url = cause.request.args.data.url;
105
+ for (const request of rootCauses.fontRequests) {
106
+ const url = request.args.data.url;
100
107
  subItems.push({
101
108
  extra: {type: 'url', value: url},
102
109
  cause: str_(UIStrings.rootCauseFontChanges),
103
110
  });
104
111
  }
105
- for (const cause of rootCauses.iframes) {
106
- const element = artifacts.TraceElements.find(
107
- t => t.traceEventType === 'layout-shift' && t.nodeId === cause.iframe.backendNodeId);
112
+ if (rootCauses.iframeIds.length) {
108
113
  subItems.push({
109
- extra: element ? Audit.makeNodeItem(element.node) : undefined,
110
114
  cause: str_(UIStrings.rootCauseInjectedIframe),
111
115
  });
112
116
  }
113
- for (const cause of rootCauses.renderBlockingRequests) {
114
- const url = cause.request.args.data.url;
115
- subItems.push({
116
- extra: {type: 'url', value: url},
117
- cause: str_(UIStrings.rootCauseRenderBlockingRequest),
118
- });
119
- }
120
117
  }
121
118
 
122
119
  items.push({
@@ -16,7 +16,6 @@ import * as i18n from '../lib/i18n/i18n.js';
16
16
  import {MainThreadTasks} from '../computed/main-thread-tasks.js';
17
17
  import {TotalBlockingTime} from '../computed/metrics/total-blocking-time.js';
18
18
  import {Sentry} from '../lib/sentry.js';
19
- import {Util} from '../../shared/util.js';
20
19
 
21
20
  const UIStrings = {
22
21
  /** Title of a diagnostic audit that provides detail on the main thread work the browser did to load the page. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
@@ -142,7 +141,6 @@ class MainThreadWorkBreakdown extends Audit {
142
141
 
143
142
  return {
144
143
  score,
145
- scoreDisplayMode: score >= Util.PASS_THRESHOLD ? Audit.SCORING_MODES.INFORMATIVE : undefined,
146
144
  numericValue: totalExecutionTime,
147
145
  numericUnit: 'millisecond',
148
146
  displayValue: str_(i18n.UIStrings.seconds, {timeInMs: totalExecutionTime}),
@@ -12,6 +12,7 @@ declare class IsCrawlable extends Audit {
12
12
  selector?: string;
13
13
  boundingRect?: import("../../../types/lhr/audit-details.js").default.Rect;
14
14
  nodeLabel?: string;
15
+ explanation?: string;
15
16
  };
16
17
  } | undefined;
17
18
  /**
@@ -92,7 +92,6 @@ class ServerResponseTime extends Audit {
92
92
  numericValue: responseTime,
93
93
  numericUnit: 'millisecond',
94
94
  score: Number(passed),
95
- scoreDisplayMode: passed ? Audit.SCORING_MODES.INFORMATIVE : undefined,
96
95
  displayValue,
97
96
  details,
98
97
  metricSavings: {
@@ -39,7 +39,11 @@ async function getComputationDataParamsFromTrace(data, context) {
39
39
  const graph = await PageDependencyGraph.request({...data, fromTrace: true}, context);
40
40
  const traceEngineResult = await TraceEngineResult.request(data, context);
41
41
  const frameId = traceEngineResult.data.Meta.mainFrameId;
42
- const navigationId = traceEngineResult.data.Meta.mainFrameNavigations[0].args.data.navigationId;
42
+ const navigationId = traceEngineResult.data.Meta.mainFrameNavigations[0].args.data?.navigationId;
43
+ if (!navigationId) {
44
+ throw new Error(`Lantern metrics could not be calculated due to missing navigation id`);
45
+ }
46
+
43
47
  const processedNavigation = Lantern.TraceEngineComputationData.createProcessedNavigation(
44
48
  traceEngineResult.data, frameId, navigationId);
45
49
  const simulator = data.simulator || (await LoadSimulator.request(data, context));
@@ -20,15 +20,7 @@ class TraceEngineResult {
20
20
  * @return {Promise<LH.Artifacts.TraceEngineResult>}
21
21
  */
22
22
  static async runTraceEngine(traceEvents) {
23
- const traceHandlers = {...TraceEngine.TraceHandlers};
24
-
25
- // @ts-expect-error Temporarily disable this handler
26
- // It's not currently used anywhere in trace engine insights or Lighthouse.
27
- // TODO: Re-enable this when its memory usage is improved in the trace engine
28
- // https://github.com/GoogleChrome/lighthouse/issues/16111
29
- delete traceHandlers.Invalidations;
30
-
31
- const processor = new TraceEngine.TraceProcessor(traceHandlers);
23
+ const processor = new TraceEngine.TraceProcessor(TraceEngine.TraceHandlers);
32
24
 
33
25
  // eslint-disable-next-line max-len
34
26
  await processor.parse(/** @type {import('@paulirish/trace_engine').Types.Events.Event[]} */ (
@@ -44,22 +36,84 @@ class TraceEngineResult {
44
36
  * @param {import('@paulirish/trace_engine/models/trace/insights/types.js').TraceInsightSets} insightSets
45
37
  */
46
38
  static localizeInsights(insightSets) {
39
+ /**
40
+ * Execute `cb(traceEngineI18nObject)` on every i18n object, recursively. The cb return
41
+ * value replaces traceEngineI18nObject.
42
+ * @param {any} obj
43
+ * @param {(traceEngineI18nObject: {i18nId: string, values?: {}}) => LH.IcuMessage} cb
44
+ * @param {Set<object>} seen
45
+ */
46
+ function recursiveReplaceLocalizableStrings(obj, cb, seen) {
47
+ if (seen.has(seen)) {
48
+ return;
49
+ }
50
+
51
+ seen.add(obj);
52
+
53
+ if (obj instanceof Map) {
54
+ for (const [key, value] of obj) {
55
+ if (value && typeof value === 'object' && 'i18nId' in value) {
56
+ obj.set(key, cb(value));
57
+ } else {
58
+ recursiveReplaceLocalizableStrings(value, cb, seen);
59
+ }
60
+ }
61
+ } else if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
62
+ Object.keys(obj).forEach(key => {
63
+ const value = obj[key];
64
+ if (value && typeof value === 'object' && 'i18nId' in value) {
65
+ obj[key] = cb(value);
66
+ } else {
67
+ recursiveReplaceLocalizableStrings(value, cb, seen);
68
+ }
69
+ });
70
+ } else if (Array.isArray(obj)) {
71
+ for (let i = 0; i < obj.length; i++) {
72
+ const value = obj[i];
73
+ if (value && typeof value === 'object' && 'i18nId' in value) {
74
+ obj[i] = cb(value);
75
+ } else {
76
+ recursiveReplaceLocalizableStrings(value, cb, seen);
77
+ }
78
+ }
79
+ }
80
+ }
81
+
47
82
  for (const insightSet of insightSets.values()) {
48
83
  for (const [name, model] of Object.entries(insightSet.model)) {
49
84
  if (model instanceof Error) {
50
85
  continue;
51
86
  }
52
87
 
88
+ /** @type {Record<string, string>} */
89
+ let traceEngineUIStrings;
90
+ if (name in TraceEngine.Insights.Models) {
91
+ const nameAsKey = /** @type {keyof typeof TraceEngine.Insights.Models} */ (name);
92
+ traceEngineUIStrings = TraceEngine.Insights.Models[nameAsKey].UIStrings;
93
+ } else {
94
+ throw new Error(`insight missing UIStrings: ${name}`);
95
+ }
96
+
53
97
  const key = `node_modules/@paulirish/trace_engine/models/trace/insights/${name}.js`;
54
- const str_ = i18n.createIcuMessageFn(key, {
55
- title: model.title,
56
- description: model.description,
57
- });
98
+ const str_ = i18n.createIcuMessageFn(key, traceEngineUIStrings);
99
+
100
+ // Pass `{i18nId: string, values?: {}}` through Lighthouse's i18n pipeline.
101
+ // This is equivalent to if we directly did `str_(UIStrings.whatever, ...)`
102
+ recursiveReplaceLocalizableStrings(model, (traceEngineI18nObject) => {
103
+ let values = traceEngineI18nObject.values;
104
+ if (values) {
105
+ values = structuredClone(values);
106
+ for (const [key, value] of Object.entries(values)) {
107
+ if (value && typeof value === 'object' && '__i18nBytes' in value) {
108
+ // @ts-expect-error
109
+ values[key] = value.__i18nBytes;
110
+ // TODO: use an actual byte formatter. Right now, this shows the exact number of bytes.
111
+ }
112
+ }
113
+ }
58
114
 
59
- // @ts-expect-error coerce to string, should be fine
60
- model.title = str_(model.title);
61
- // @ts-expect-error coerce to string, should be fine
62
- model.description = str_(model.description);
115
+ return str_(traceEngineI18nObject.i18nId, values);
116
+ }, new Set());
63
117
  }
64
118
  }
65
119
  }
@@ -15,6 +15,8 @@ const UIStrings = {
15
15
  performanceCategoryTitle: 'Performance',
16
16
  /** Title of the speed metrics section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. */
17
17
  metricGroupTitle: 'Metrics',
18
+ /** Title of the insights section of the Performance category. Within this section are various insights to give developers tips on how to improve the performance of their page. */
19
+ insightGroupTitle: 'Insights',
18
20
  /** Title of an opportunity sub-section of the Performance category. Within this section are audits with imperative titles that suggest actions the user can take to improve the time of the first initial render of the webpage. */
19
21
  firstPaintImprovementsGroupTitle: 'First Paint Improvements',
20
22
  /** Description of an opportunity sub-section of the Performance category. Within this section are audits with imperative titles that suggest actions the user can take to improve the time of the first initial render of the webpage. */
@@ -107,7 +109,6 @@ const defaultConfig = {
107
109
  // Artifacts which can be depended on come first.
108
110
  {id: 'DevtoolsLog', gatherer: 'devtools-log'},
109
111
  {id: 'Trace', gatherer: 'trace'},
110
- {id: 'RootCauses', gatherer: 'root-causes'},
111
112
 
112
113
  {id: 'Accessibility', gatherer: 'accessibility'},
113
114
  {id: 'AnchorElements', gatherer: 'anchor-elements'},
@@ -194,6 +195,7 @@ const defaultConfig = {
194
195
  'csp-xss',
195
196
  'has-hsts',
196
197
  'origin-isolation',
198
+ 'clickjacking-mitigation',
197
199
  'script-treemap-data',
198
200
  'accessibility/accesskeys',
199
201
  'accessibility/aria-allowed-attr',
@@ -307,11 +309,28 @@ const defaultConfig = {
307
309
  'seo/manual/structured-data',
308
310
  'work-during-interaction',
309
311
  'bf-cache',
312
+ 'insights/cls-culprits-insight',
313
+ 'insights/document-latency-insight',
314
+ 'insights/dom-size-insight',
315
+ 'insights/font-display-insight',
316
+ 'insights/forced-reflow-insight',
317
+ 'insights/image-delivery-insight',
318
+ 'insights/interaction-to-next-paint-insight',
319
+ 'insights/lcp-discovery-insight',
320
+ 'insights/lcp-phases-insight',
321
+ 'insights/long-critical-network-tree-insight',
322
+ 'insights/render-blocking-insight',
323
+ 'insights/slow-css-selector-insight',
324
+ 'insights/third-parties-insight',
325
+ 'insights/viewport-insight',
310
326
  ],
311
327
  groups: {
312
328
  'metrics': {
313
329
  title: str_(UIStrings.metricGroupTitle),
314
330
  },
331
+ 'insights': {
332
+ title: str_(UIStrings.insightGroupTitle),
333
+ },
315
334
  'diagnostics': {
316
335
  title: str_(UIStrings.diagnosticsGroupTitle),
317
336
  description: str_(UIStrings.diagnosticsGroupDescription),
@@ -387,6 +406,22 @@ const defaultConfig = {
387
406
  {id: 'speed-index', weight: 10, group: 'metrics', acronym: 'SI'},
388
407
  {id: 'interaction-to-next-paint', weight: 0, group: 'metrics', acronym: 'INP'},
389
408
 
409
+ // Insight audits.
410
+ {id: 'cls-culprits-insight', weight: 0, group: 'insights'},
411
+ {id: 'document-latency-insight', weight: 0, group: 'insights'},
412
+ {id: 'dom-size-insight', weight: 0, group: 'insights'},
413
+ {id: 'font-display-insight', weight: 0, group: 'insights'},
414
+ {id: 'forced-reflow-insight', weight: 0, group: 'insights'},
415
+ {id: 'image-delivery-insight', weight: 0, group: 'insights'},
416
+ {id: 'interaction-to-next-paint-insight', weight: 0, group: 'insights'},
417
+ {id: 'lcp-discovery-insight', weight: 0, group: 'insights'},
418
+ {id: 'lcp-phases-insight', weight: 0, group: 'insights'},
419
+ {id: 'long-critical-network-tree-insight', weight: 0, group: 'insights'},
420
+ {id: 'render-blocking-insight', weight: 0, group: 'insights'},
421
+ {id: 'slow-css-selector-insight', weight: 0, group: 'insights'},
422
+ {id: 'third-parties-insight', weight: 0, group: 'insights'},
423
+ {id: 'viewport-insight', weight: 0, group: 'insights'},
424
+
390
425
  // These are our "invisible" metrics. Not displayed, but still in the LHR.
391
426
  {id: 'interactive', weight: 0, group: 'hidden', acronym: 'TTI'},
392
427
  {id: 'max-potential-fid', weight: 0, group: 'hidden'},
@@ -545,6 +580,7 @@ const defaultConfig = {
545
580
  {id: 'csp-xss', weight: 0, group: 'best-practices-trust-safety'},
546
581
  {id: 'has-hsts', weight: 0, group: 'best-practices-trust-safety'},
547
582
  {id: 'origin-isolation', weight: 0, group: 'best-practices-trust-safety'},
583
+ {id: 'clickjacking-mitigation', weight: 0, group: 'best-practices-trust-safety'},
548
584
  // User Experience
549
585
  {id: 'paste-preventing-inputs', weight: 3, group: 'best-practices-ux'},
550
586
  {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'},
@@ -76,12 +76,15 @@ class InspectorIssues extends BaseGatherer {
76
76
  lowTextContrastIssue: [],
77
77
  mixedContentIssue: [],
78
78
  navigatorUserAgentIssue: [],
79
+ partitioningBlobURLIssue: [],
79
80
  propertyRuleIssue: [],
80
81
  quirksModeIssue: [],
81
82
  cookieIssue: [],
83
+ selectElementAccessibilityIssue: [],
82
84
  sharedArrayBufferIssue: [],
83
85
  sharedDictionaryIssue: [],
84
86
  stylesheetLoadingIssue: [],
87
+ sriMessageSignatureIssue: [],
85
88
  federatedAuthUserInfoRequestIssue: [],
86
89
  };
87
90
  const keys = /** @type {Array<keyof LH.Artifacts['InspectorIssues']>} */(Object.keys(artifact));
@@ -9,6 +9,14 @@ export type TraceElementData = {
9
9
  type?: string;
10
10
  };
11
11
  declare class TraceElements extends BaseGatherer {
12
+ /**
13
+ * @param {LH.Artifacts.TraceEngineResult} traceEngineResult
14
+ * @param {string|undefined} navigationId
15
+ * @return {Promise<Array<{nodeId: number}>>}
16
+ */
17
+ static getTraceEngineElements(traceEngineResult: LH.Artifacts.TraceEngineResult, navigationId: string | undefined): Promise<Array<{
18
+ nodeId: number;
19
+ }>>;
12
20
  /**
13
21
  * We want to a single representative node to represent the shift, so let's pick
14
22
  * the one with the largest impact (size x distance moved).
@@ -48,8 +56,8 @@ declare class TraceElements extends BaseGatherer {
48
56
  nodeId: number;
49
57
  type: string;
50
58
  } | undefined>;
51
- /** @type {LH.Gatherer.GathererMeta<'Trace'|'RootCauses'>} */
52
- meta: LH.Gatherer.GathererMeta<"Trace" | "RootCauses">;
59
+ /** @type {LH.Gatherer.GathererMeta<'Trace'>} */
60
+ meta: LH.Gatherer.GathererMeta<"Trace">;
53
61
  /** @type {Map<string, string>} */
54
62
  animationIdToName: Map<string, string>;
55
63
  /** @param {LH.Crdp.Animation.AnimationStartedEvent} args */
@@ -23,7 +23,6 @@ import {LighthouseError} from '../../lib/lh-error.js';
23
23
  import {Responsiveness} from '../../computed/metrics/responsiveness.js';
24
24
  import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
25
25
  import {ExecutionContext} from '../driver/execution-context.js';
26
- import RootCauses from './root-causes.js';
27
26
  import {TraceEngineResult} from '../../computed/trace-engine-result.js';
28
27
 
29
28
  /** @typedef {{nodeId: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
@@ -46,10 +45,10 @@ function getNodeDetailsData() {
46
45
  /* c8 ignore stop */
47
46
 
48
47
  class TraceElements extends BaseGatherer {
49
- /** @type {LH.Gatherer.GathererMeta<'Trace'|'RootCauses'>} */
48
+ /** @type {LH.Gatherer.GathererMeta<'Trace'>} */
50
49
  meta = {
51
50
  supportedModes: ['timespan', 'navigation'],
52
- dependencies: {Trace: Trace.symbol, RootCauses: RootCauses.symbol},
51
+ dependencies: {Trace: Trace.symbol},
53
52
  };
54
53
 
55
54
  /** @type {Map<string, string>} */
@@ -65,6 +64,87 @@ class TraceElements extends BaseGatherer {
65
64
  if (name) this.animationIdToName.set(id, name);
66
65
  }
67
66
 
67
+ /**
68
+ * @param {LH.Artifacts.TraceEngineResult} traceEngineResult
69
+ * @param {string|undefined} navigationId
70
+ * @return {Promise<Array<{nodeId: number}>>}
71
+ */
72
+ static async getTraceEngineElements(traceEngineResult, navigationId) {
73
+ // Can only resolve elements for the latest insight set, which should correspond
74
+ // to the current navigation id (if present). Can't resolve elements for pages
75
+ // that are gone.
76
+ const insightSet = [...traceEngineResult.insights.values()].at(-1);
77
+ if (!insightSet) {
78
+ return [];
79
+ }
80
+
81
+ if (navigationId) {
82
+ if (insightSet.navigation?.args.data?.navigationId !== navigationId) {
83
+ return [];
84
+ }
85
+ } else {
86
+ if (insightSet.navigation) {
87
+ return [];
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Execute `cb(obj, key)` on every object property (non-objects only), recursively.
93
+ * @param {any} obj
94
+ * @param {(obj: Record<string, unknown>, key: string) => void} cb
95
+ * @param {Set<object>} seen
96
+ */
97
+ function recursiveObjectEnumerate(obj, cb, seen) {
98
+ if (seen.has(seen)) {
99
+ return;
100
+ }
101
+
102
+ seen.add(obj);
103
+
104
+ if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
105
+ if (obj instanceof Map) {
106
+ for (const [key, val] of obj) {
107
+ if (typeof val === 'object') {
108
+ recursiveObjectEnumerate(val, cb, seen);
109
+ } else {
110
+ cb(val, key);
111
+ }
112
+ }
113
+ } else {
114
+ Object.keys(obj).forEach(key => {
115
+ if (typeof obj[key] === 'object') {
116
+ recursiveObjectEnumerate(obj[key], cb, seen);
117
+ } else {
118
+ cb(obj[key], key);
119
+ }
120
+ });
121
+ }
122
+ } else if (Array.isArray(obj)) {
123
+ obj.forEach(item => {
124
+ if (typeof item === 'object' || Array.isArray(item)) {
125
+ recursiveObjectEnumerate(item, cb, seen);
126
+ }
127
+ });
128
+ }
129
+ }
130
+
131
+ /** @type {number[]} */
132
+ const nodeIds = [];
133
+ recursiveObjectEnumerate(insightSet.model, (val, key) => {
134
+ const keys = ['nodeId', 'node_id'];
135
+ if (typeof val === 'number' && keys.includes(key)) {
136
+ nodeIds.push(val);
137
+ }
138
+ }, new Set());
139
+
140
+ // TODO: would be better if unsizedImages was `Array<{nodeId}>`.
141
+ for (const shift of insightSet.model.CLSCulprits.shifts.values()) {
142
+ nodeIds.push(...shift.unsizedImages);
143
+ }
144
+
145
+ return [...new Set(nodeIds)].map(id => ({nodeId: id}));
146
+ }
147
+
68
148
  /**
69
149
  * We want to a single representative node to represent the shift, so let's pick
70
150
  * the one with the largest impact (size x distance moved).
@@ -147,14 +227,6 @@ class TraceElements extends BaseGatherer {
147
227
  nodeIds.push(biggestImpactedNodeId);
148
228
  }
149
229
 
150
- const index = layoutShiftEvents.indexOf(event);
151
- const shiftRootCauses = rootCauses.layoutShifts[index];
152
- if (shiftRootCauses) {
153
- for (const cause of shiftRootCauses.unsizedMedia) {
154
- nodeIds.push(cause.node.backendNodeId);
155
- }
156
- }
157
-
158
230
  return nodeIds.map(nodeId => ({nodeId}));
159
231
  });
160
232
  }
@@ -319,7 +391,10 @@ class TraceElements extends BaseGatherer {
319
391
 
320
392
  const processedTrace = await ProcessedTrace.request(trace, context);
321
393
  const {mainThreadEvents} = processedTrace;
394
+ const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
322
395
 
396
+ const traceEngineData = await TraceElements.getTraceEngineElements(
397
+ traceEngineResult, navigationId);
323
398
  const lcpNodeData = await TraceElements.getLcpElement(trace, context);
324
399
  const shiftsData = await TraceElements.getTopLayoutShifts(
325
400
  trace, traceEngineResult.data, rootCauses, context);
@@ -328,6 +403,7 @@ class TraceElements extends BaseGatherer {
328
403
 
329
404
  /** @type {Map<string, TraceElementData[]>} */
330
405
  const backendNodeDataMap = new Map([
406
+ ['trace-engine', traceEngineData],
331
407
  ['largest-contentful-paint', lcpNodeData ? [lcpNodeData] : []],
332
408
  ['layout-shift', shiftsData],
333
409
  ['animation', animatedElementData],
@@ -336,6 +412,7 @@ class TraceElements extends BaseGatherer {
336
412
 
337
413
  /** @type {Map<number, LH.Crdp.Runtime.CallFunctionOnResponse | null>} */
338
414
  const callFunctionOnCache = new Map();
415
+ /** @type {LH.Artifacts.TraceElement[]} */
339
416
  const traceElements = [];
340
417
  for (const [traceEventType, backendNodeData] of backendNodeDataMap) {
341
418
  for (let i = 0; i < backendNodeData.length; i++) {
@@ -348,8 +425,8 @@ class TraceElements extends BaseGatherer {
348
425
 
349
426
  if (response?.result?.value) {
350
427
  traceElements.push({
351
- traceEventType,
352
428
  ...response.result.value,
429
+ traceEventType,
353
430
  animations: backendNodeData[i].animations,
354
431
  nodeId: backendNodeId,
355
432
  type: backendNodeData[i].type,
@@ -1,6 +1,6 @@
1
- /** @type {Record<string, {name: LH.IcuMessage} | undefined>} */
1
+ /** @type {Record<string, {name: LH.IcuMessage|string} | undefined>} */
2
2
  export const NotRestoredReasonDescription: Record<string, {
3
- name: LH.IcuMessage;
3
+ name: LH.IcuMessage | string;
4
4
  } | undefined>;
5
5
  export namespace UIStrings {
6
6
  let notMainFrame: string;
@@ -77,7 +77,6 @@ export namespace UIStrings {
77
77
  let printing: string;
78
78
  let webDatabase: string;
79
79
  let pictureInPicture: string;
80
- let portal: string;
81
80
  let speechRecognizer: string;
82
81
  let idleManager: string;
83
82
  let paymentManager: string;
@@ -87,6 +86,7 @@ export namespace UIStrings {
87
86
  let outstandingNetworkRequestDirectSocket: string;
88
87
  let injectedJavascript: string;
89
88
  let injectedStyleSheet: string;
89
+ let contentDiscarded: string;
90
90
  let contentSecurityHandler: string;
91
91
  let contentWebAuthenticationAPI: string;
92
92
  let contentFileChooser: string;
@@ -117,8 +117,11 @@ export namespace UIStrings {
117
117
  let errorDocument: string;
118
118
  let fencedFramesEmbedder: string;
119
119
  let keepaliveRequest: string;
120
- let authorizationHeader: string;
120
+ let jsNetworkRequestReceivedCacheControlNoStoreResource: string;
121
121
  let indexedDBEvent: string;
122
122
  let cookieDisabled: string;
123
+ let webRTCSticky: string;
124
+ let webTransportSticky: string;
125
+ let webSocketSticky: string;
123
126
  }
124
127
  //# sourceMappingURL=bf-cache-strings.d.ts.map