lighthouse 11.7.1 → 12.0.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 (272) hide show
  1. package/CONTRIBUTING.md +2 -2
  2. package/cli/bin.js +0 -7
  3. package/cli/cli-flags.d.ts +1 -5
  4. package/cli/cli-flags.js +4 -8
  5. package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +14 -14
  6. package/cli/test/smokehouse/config/exclusions.js +3 -0
  7. package/cli/test/smokehouse/core-tests.js +6 -20
  8. package/cli/test/smokehouse/frontends/smokehouse-bin.js +2 -2
  9. package/core/audits/accessibility/aria-conditional-attr.d.ts +10 -0
  10. package/core/audits/accessibility/aria-conditional-attr.js +43 -0
  11. package/core/audits/accessibility/{duplicate-id-active.d.ts → aria-deprecated-role.d.ts} +3 -3
  12. package/core/audits/accessibility/aria-deprecated-role.js +43 -0
  13. package/core/audits/accessibility/aria-prohibited-attr.d.ts +10 -0
  14. package/core/audits/accessibility/aria-prohibited-attr.js +44 -0
  15. package/core/audits/accessibility/target-size.js +0 -1
  16. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -16
  17. package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -38
  18. package/core/audits/byte-efficiency/offscreen-images.d.ts +0 -11
  19. package/core/audits/byte-efficiency/offscreen-images.js +0 -15
  20. package/core/audits/byte-efficiency/render-blocking-resources.js +27 -26
  21. package/core/audits/byte-efficiency/unminified-css.js +2 -2
  22. package/core/audits/byte-efficiency/unused-css-rules.js +3 -1
  23. package/core/audits/dobetterweb/uses-http2.d.ts +0 -13
  24. package/core/audits/dobetterweb/uses-http2.js +4 -38
  25. package/core/audits/font-display.js +2 -2
  26. package/core/audits/layout-shifts.js +1 -1
  27. package/core/audits/preload-fonts.d.ts +1 -5
  28. package/core/audits/preload-fonts.js +1 -10
  29. package/core/audits/prioritize-lcp-image.js +2 -2
  30. package/core/audits/redirects-http.d.ts +19 -0
  31. package/core/audits/redirects-http.js +75 -0
  32. package/core/audits/resource-summary.d.ts +2 -0
  33. package/core/audits/resource-summary.js +5 -4
  34. package/core/audits/seo/is-crawlable.d.ts +2 -1
  35. package/core/audits/seo/link-text.js +12 -0
  36. package/core/audits/uses-rel-preload.d.ts +1 -5
  37. package/core/audits/uses-rel-preload.js +5 -15
  38. package/core/audits/work-during-interaction.d.ts +2 -2
  39. package/core/audits/work-during-interaction.js +2 -2
  40. package/core/computed/js-bundles.d.ts +1 -1
  41. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +13 -82
  42. package/core/computed/metrics/lantern-first-contentful-paint.js +16 -178
  43. package/core/computed/metrics/lantern-first-meaningful-paint.d.ts +13 -12
  44. package/core/computed/metrics/lantern-first-meaningful-paint.js +11 -49
  45. package/core/computed/metrics/lantern-interactive.d.ts +13 -21
  46. package/core/computed/metrics/lantern-interactive.js +12 -84
  47. package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +13 -25
  48. package/core/computed/metrics/lantern-largest-contentful-paint.js +13 -80
  49. package/core/computed/metrics/lantern-max-potential-fid.d.ts +13 -24
  50. package/core/computed/metrics/lantern-max-potential-fid.js +12 -64
  51. package/core/computed/metrics/lantern-metric.d.ts +14 -18
  52. package/core/computed/metrics/lantern-metric.js +34 -30
  53. package/core/computed/metrics/lantern-speed-index.d.ts +13 -32
  54. package/core/computed/metrics/lantern-speed-index.js +13 -116
  55. package/core/computed/metrics/lantern-total-blocking-time.d.ts +13 -25
  56. package/core/computed/metrics/lantern-total-blocking-time.js +10 -94
  57. package/core/computed/metrics/metric.js +1 -3
  58. package/core/computed/module-duplication.d.ts +1 -1
  59. package/core/computed/navigation-insights.d.ts +36 -0
  60. package/core/computed/navigation-insights.js +35 -0
  61. package/core/computed/page-dependency-graph.js +3 -7
  62. package/core/computed/resource-summary.d.ts +10 -12
  63. package/core/computed/resource-summary.js +12 -19
  64. package/core/computed/trace-engine-result.d.ts +16 -3
  65. package/core/computed/trace-engine-result.js +19 -14
  66. package/core/computed/unused-css.d.ts +3 -1
  67. package/core/computed/unused-css.js +6 -5
  68. package/core/config/config-helpers.js +0 -4
  69. package/core/config/constants.js +0 -1
  70. package/core/config/default-config.js +63 -142
  71. package/core/config/experimental-config.js +11 -0
  72. package/core/config/filters.js +0 -1
  73. package/core/gather/driver/navigation.js +4 -1
  74. package/core/gather/driver/prepare.js +1 -1
  75. package/core/gather/driver/target-manager.js +4 -3
  76. package/core/gather/driver/wait-for-condition.js +2 -3
  77. package/core/gather/driver.d.ts +12 -0
  78. package/core/gather/driver.js +27 -0
  79. package/core/gather/gatherers/accessibility.js +4 -6
  80. package/core/gather/gatherers/css-usage.d.ts +0 -21
  81. package/core/gather/gatherers/css-usage.js +9 -119
  82. package/core/gather/gatherers/root-causes.d.ts +2 -2
  83. package/core/gather/gatherers/root-causes.js +6 -5
  84. package/core/gather/gatherers/stylesheets.d.ts +26 -0
  85. package/core/gather/gatherers/stylesheets.js +138 -0
  86. package/core/gather/gatherers/trace-elements.d.ts +2 -12
  87. package/core/gather/gatherers/trace-elements.js +2 -21
  88. package/core/gather/navigation-runner.js +3 -1
  89. package/core/gather/session.d.ts +8 -0
  90. package/core/gather/session.js +14 -1
  91. package/core/lib/emulation.js +2 -2
  92. package/core/lib/i18n/i18n.d.ts +0 -1
  93. package/core/lib/i18n/i18n.js +0 -2
  94. package/core/lib/lantern/cpu-node.d.ts +7 -1
  95. package/core/lib/lantern/cpu-node.js +12 -2
  96. package/core/lib/lantern/lantern-error.d.ts +8 -0
  97. package/core/lib/lantern/lantern-error.js +9 -0
  98. package/core/lib/lantern/metric.js +2 -6
  99. package/core/lib/lantern/metrics/first-contentful-paint.d.ts +79 -0
  100. package/core/lib/lantern/metrics/first-contentful-paint.js +200 -0
  101. package/core/lib/lantern/metrics/first-meaningful-paint.d.ts +6 -0
  102. package/core/lib/lantern/metrics/first-meaningful-paint.js +64 -0
  103. package/core/lib/lantern/metrics/interactive.d.ts +26 -0
  104. package/core/lib/lantern/metrics/interactive.js +112 -0
  105. package/core/lib/lantern/metrics/largest-contentful-paint.d.ts +19 -0
  106. package/core/lib/lantern/metrics/largest-contentful-paint.js +108 -0
  107. package/core/lib/lantern/metrics/max-potential-fid.d.ts +30 -0
  108. package/core/lib/lantern/metrics/max-potential-fid.js +92 -0
  109. package/core/lib/lantern/metrics/speed-index.d.ts +38 -0
  110. package/core/lib/lantern/metrics/speed-index.js +144 -0
  111. package/core/lib/lantern/metrics/total-blocking-time.d.ts +31 -0
  112. package/core/lib/lantern/metrics/total-blocking-time.js +128 -0
  113. package/core/lib/lantern/network-node.js +28 -4
  114. package/core/lib/lantern/page-dependency-graph.d.ts +4 -4
  115. package/core/lib/lantern/page-dependency-graph.js +19 -7
  116. package/core/lib/lantern/simulator/connection-pool.d.ts +2 -12
  117. package/core/lib/lantern/simulator/connection-pool.js +3 -22
  118. package/core/lib/lantern/simulator/network-analyzer.js +1 -12
  119. package/core/lib/lantern/simulator/simulator.d.ts +2 -4
  120. package/core/lib/lantern/simulator/simulator.js +7 -14
  121. package/core/lib/lantern/types/lantern.d.ts +5 -3
  122. package/core/lib/lantern-trace-saver.js +0 -1
  123. package/core/lib/lh-error.d.ts +9 -0
  124. package/core/lib/lh-error.js +13 -3
  125. package/core/lib/lighthouse-compatibility.js +53 -10
  126. package/core/lib/navigation-error.js +3 -0
  127. package/core/lib/network-request.d.ts +1 -0
  128. package/core/lib/network-request.js +28 -0
  129. package/core/runner.js +18 -25
  130. package/dist/report/bundle.esm.js +11 -112
  131. package/dist/report/flow.js +16 -117
  132. package/dist/report/standalone.js +12 -113
  133. package/flow-report/src/help-dialog.tsx +0 -1
  134. package/flow-report/src/i18n/i18n.d.ts +0 -4
  135. package/flow-report/src/i18n/ui-strings.d.ts +0 -1
  136. package/flow-report/src/i18n/ui-strings.js +0 -2
  137. package/flow-report/src/summary/category.tsx +10 -12
  138. package/package.json +5 -5
  139. package/readme.md +4 -4
  140. package/report/assets/styles.css +1 -60
  141. package/report/assets/templates.html +0 -95
  142. package/report/renderer/category-renderer.js +1 -1
  143. package/report/renderer/components.d.ts +2 -2
  144. package/report/renderer/components.js +2 -103
  145. package/report/renderer/dom.d.ts +1 -1
  146. package/report/renderer/dom.js +1 -1
  147. package/report/renderer/performance-category-renderer.d.ts +0 -7
  148. package/report/renderer/performance-category-renderer.js +1 -32
  149. package/report/renderer/report-renderer.js +3 -13
  150. package/report/renderer/report-ui-features.js +1 -1
  151. package/report/renderer/report-utils.d.ts +0 -1
  152. package/report/renderer/report-utils.js +13 -13
  153. package/shared/localization/locales/ar-XB.json +62 -329
  154. package/shared/localization/locales/ar.json +62 -329
  155. package/shared/localization/locales/bg.json +62 -329
  156. package/shared/localization/locales/ca.json +62 -329
  157. package/shared/localization/locales/cs.json +62 -329
  158. package/shared/localization/locales/da.json +62 -329
  159. package/shared/localization/locales/de.json +62 -329
  160. package/shared/localization/locales/el.json +62 -329
  161. package/shared/localization/locales/en-GB.json +62 -329
  162. package/shared/localization/locales/en-US.json +41 -272
  163. package/shared/localization/locales/en-XA.json +62 -329
  164. package/shared/localization/locales/en-XL.json +41 -272
  165. package/shared/localization/locales/es-419.json +62 -329
  166. package/shared/localization/locales/es.json +62 -329
  167. package/shared/localization/locales/fi.json +62 -329
  168. package/shared/localization/locales/fil.json +61 -328
  169. package/shared/localization/locales/fr.json +62 -329
  170. package/shared/localization/locales/he.json +62 -329
  171. package/shared/localization/locales/hi.json +62 -329
  172. package/shared/localization/locales/hr.json +63 -330
  173. package/shared/localization/locales/hu.json +62 -329
  174. package/shared/localization/locales/id.json +62 -329
  175. package/shared/localization/locales/it.json +62 -329
  176. package/shared/localization/locales/ja.json +62 -329
  177. package/shared/localization/locales/ko.json +62 -329
  178. package/shared/localization/locales/lt.json +62 -329
  179. package/shared/localization/locales/lv.json +62 -329
  180. package/shared/localization/locales/nl.json +62 -329
  181. package/shared/localization/locales/no.json +62 -329
  182. package/shared/localization/locales/pl.json +61 -328
  183. package/shared/localization/locales/pt-PT.json +62 -329
  184. package/shared/localization/locales/pt.json +64 -331
  185. package/shared/localization/locales/ro.json +62 -329
  186. package/shared/localization/locales/ru.json +62 -329
  187. package/shared/localization/locales/sk.json +62 -329
  188. package/shared/localization/locales/sl.json +62 -329
  189. package/shared/localization/locales/sr-Latn.json +62 -329
  190. package/shared/localization/locales/sr.json +62 -329
  191. package/shared/localization/locales/sv.json +62 -329
  192. package/shared/localization/locales/ta.json +61 -328
  193. package/shared/localization/locales/te.json +62 -329
  194. package/shared/localization/locales/th.json +62 -329
  195. package/shared/localization/locales/tr.json +62 -329
  196. package/shared/localization/locales/uk.json +62 -329
  197. package/shared/localization/locales/vi.json +61 -328
  198. package/shared/localization/locales/zh-HK.json +62 -329
  199. package/shared/localization/locales/zh-TW.json +61 -328
  200. package/shared/localization/locales/zh.json +62 -329
  201. package/tsconfig.json +1 -3
  202. package/types/artifacts.d.ts +21 -110
  203. package/types/audit.d.ts +0 -10
  204. package/types/config.d.ts +0 -1
  205. package/types/externs.d.ts +0 -2
  206. package/types/gatherer.d.ts +3 -0
  207. package/types/internal/smokehouse.d.ts +1 -1
  208. package/types/lh.d.ts +0 -2
  209. package/types/lhr/audit-details.d.ts +9 -4
  210. package/types/lhr/lhr.d.ts +0 -4
  211. package/types/lhr/settings.d.ts +0 -4
  212. package/core/audits/accessibility/duplicate-id-active.js +0 -42
  213. package/core/audits/content-width.d.ts +0 -17
  214. package/core/audits/content-width.js +0 -76
  215. package/core/audits/installable-manifest.d.ts +0 -111
  216. package/core/audits/installable-manifest.js +0 -248
  217. package/core/audits/layout-shift-elements.d.ts +0 -16
  218. package/core/audits/layout-shift-elements.js +0 -101
  219. package/core/audits/manual/pwa-cross-browser.d.ts +0 -12
  220. package/core/audits/manual/pwa-cross-browser.js +0 -40
  221. package/core/audits/manual/pwa-each-page-has-url.d.ts +0 -12
  222. package/core/audits/manual/pwa-each-page-has-url.js +0 -38
  223. package/core/audits/manual/pwa-page-transitions.d.ts +0 -12
  224. package/core/audits/manual/pwa-page-transitions.js +0 -38
  225. package/core/audits/maskable-icon.d.ts +0 -25
  226. package/core/audits/maskable-icon.js +0 -70
  227. package/core/audits/multi-check-audit.d.ts +0 -28
  228. package/core/audits/multi-check-audit.js +0 -82
  229. package/core/audits/no-unload-listeners.d.ts +0 -16
  230. package/core/audits/no-unload-listeners.js +0 -86
  231. package/core/audits/performance-budget.d.ts +0 -39
  232. package/core/audits/performance-budget.js +0 -154
  233. package/core/audits/script-elements-test-audit.d.ts +0 -14
  234. package/core/audits/script-elements-test-audit.js +0 -29
  235. package/core/audits/seo/plugins.d.ts +0 -15
  236. package/core/audits/seo/plugins.js +0 -150
  237. package/core/audits/seo/tap-targets.d.ts +0 -50
  238. package/core/audits/seo/tap-targets.js +0 -352
  239. package/core/audits/splash-screen.d.ts +0 -37
  240. package/core/audits/splash-screen.js +0 -98
  241. package/core/audits/themed-omnibox.d.ts +0 -42
  242. package/core/audits/themed-omnibox.js +0 -102
  243. package/core/audits/timing-budget.d.ts +0 -43
  244. package/core/audits/timing-budget.js +0 -175
  245. package/core/computed/manifest-values.d.ts +0 -24
  246. package/core/computed/manifest-values.js +0 -136
  247. package/core/config/budget.d.ts +0 -74
  248. package/core/config/budget.js +0 -340
  249. package/core/config/metrics-to-audits.d.ts +0 -19
  250. package/core/config/metrics-to-audits.js +0 -62
  251. package/core/gather/driver/service-workers.d.ts +0 -16
  252. package/core/gather/driver/service-workers.js +0 -52
  253. package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.d.ts +0 -47
  254. package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js +0 -233
  255. package/core/gather/gatherers/global-listeners.d.ts +0 -27
  256. package/core/gather/gatherers/global-listeners.js +0 -108
  257. package/core/gather/gatherers/installability-errors.d.ts +0 -17
  258. package/core/gather/gatherers/installability-errors.js +0 -56
  259. package/core/gather/gatherers/script-elements.d.ts +0 -21
  260. package/core/gather/gatherers/script-elements.js +0 -100
  261. package/core/gather/gatherers/seo/embedded-content.d.ts +0 -10
  262. package/core/gather/gatherers/seo/embedded-content.js +0 -63
  263. package/core/gather/gatherers/seo/tap-targets.d.ts +0 -21
  264. package/core/gather/gatherers/seo/tap-targets.js +0 -389
  265. package/core/gather/gatherers/service-worker.d.ts +0 -10
  266. package/core/gather/gatherers/service-worker.js +0 -32
  267. package/core/gather/gatherers/web-app-manifest.d.ts +0 -32
  268. package/core/gather/gatherers/web-app-manifest.js +0 -106
  269. package/report/renderer/pwa-category-renderer.d.ts +0 -55
  270. package/report/renderer/pwa-category-renderer.js +0 -184
  271. package/third-party/chromium-synchronization/installability-errors-test.js +0 -79
  272. package/types/lhr/budget.d.ts +0 -60
@@ -101,19 +101,15 @@ class Metric {
101
101
  const optimisticGraph = this.getOptimisticGraph(graph, processedNavigation);
102
102
  const pessimisticGraph = this.getPessimisticGraph(graph, processedNavigation);
103
103
 
104
- /** @type {{flexibleOrdering?: boolean, label?: string}} */
105
104
  let simulateOptions = {label: `optimistic${metricName}`};
106
105
  const optimisticSimulation = simulator.simulate(optimisticGraph, simulateOptions);
107
106
 
108
- simulateOptions = {label: `optimisticFlex${metricName}`, flexibleOrdering: true};
109
- const optimisticFlexSimulation = simulator.simulate(optimisticGraph, simulateOptions);
110
-
111
107
  simulateOptions = {label: `pessimistic${metricName}`};
112
108
  const pessimisticSimulation = simulator.simulate(pessimisticGraph, simulateOptions);
113
109
 
114
110
  const optimisticEstimate = this.getEstimateFromSimulation(
115
- optimisticSimulation.timeInMs < optimisticFlexSimulation.timeInMs ?
116
- optimisticSimulation : optimisticFlexSimulation, {...extras, optimistic: true}
111
+ optimisticSimulation,
112
+ {...extras, optimistic: true}
117
113
  );
118
114
 
119
115
  const pessimisticEstimate = this.getEstimateFromSimulation(
@@ -0,0 +1,79 @@
1
+ export type Node = import('../base-node.js').Node;
2
+ export type NetworkNode<T> = import('../network-node.js').NetworkNode<T>;
3
+ export type CpuNode = import('../cpu-node.js').CPUNode;
4
+ /** @typedef {import('../base-node.js').Node} Node */
5
+ /** @template T @typedef {import('../network-node.js').NetworkNode<T>} NetworkNode */
6
+ /** @typedef {import('../cpu-node.js').CPUNode} CpuNode */
7
+ export class FirstContentfulPaint extends Metric {
8
+ /**
9
+ * @template T
10
+ * @typedef FirstPaintBasedGraphOpts
11
+ * @property {number} cutoffTimestamp The timestamp used to filter out tasks that occured after
12
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
13
+ * @property {function(NetworkNode<T>):boolean} treatNodeAsRenderBlocking The function that determines
14
+ * which resources should be considered *possibly* render-blocking.
15
+ * @property {(function(CpuNode):boolean)=} additionalCpuNodesToTreatAsRenderBlocking The function that
16
+ * determines which CPU nodes should also be included in our blocking node IDs set,
17
+ * beyond what getRenderBlockingNodeData() already includes.
18
+ */
19
+ /**
20
+ * This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
21
+ * *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
22
+ * It also computes the set of corresponding CPU node ids that were needed for the paint at the
23
+ * given timestamp.
24
+ *
25
+ * @template [T=unknown]
26
+ * @param {Node} graph
27
+ * @param {FirstPaintBasedGraphOpts<T>} opts
28
+ * @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
29
+ */
30
+ static getRenderBlockingNodeData<T = unknown>(graph: Node, { cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking }: {
31
+ /**
32
+ * The timestamp used to filter out tasks that occured after
33
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
34
+ */
35
+ cutoffTimestamp: number;
36
+ /**
37
+ * The function that determines
38
+ * which resources should be considered *possibly* render-blocking.
39
+ */
40
+ treatNodeAsRenderBlocking: (arg0: import("../network-node.js").NetworkNode<T>) => boolean;
41
+ /**
42
+ * The function that
43
+ * determines which CPU nodes should also be included in our blocking node IDs set,
44
+ * beyond what getRenderBlockingNodeData() already includes.
45
+ */
46
+ additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CpuNode) => boolean) | undefined;
47
+ }): {
48
+ definitelyNotRenderBlockingScriptUrls: Set<string>;
49
+ renderBlockingCpuNodeIds: Set<string>;
50
+ };
51
+ /**
52
+ * This function computes the graph required for the first paint of interest.
53
+ *
54
+ * @template [T=unknown]
55
+ * @param {Node} dependencyGraph
56
+ * @param {FirstPaintBasedGraphOpts<T>} opts
57
+ * @return {Node}
58
+ */
59
+ static getFirstPaintBasedGraph<T_1 = unknown>(dependencyGraph: Node, { cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking }: {
60
+ /**
61
+ * The timestamp used to filter out tasks that occured after
62
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
63
+ */
64
+ cutoffTimestamp: number;
65
+ /**
66
+ * The function that determines
67
+ * which resources should be considered *possibly* render-blocking.
68
+ */
69
+ treatNodeAsRenderBlocking: (arg0: import("../network-node.js").NetworkNode<T_1>) => boolean;
70
+ /**
71
+ * The function that
72
+ * determines which CPU nodes should also be included in our blocking node IDs set,
73
+ * beyond what getRenderBlockingNodeData() already includes.
74
+ */
75
+ additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CpuNode) => boolean) | undefined;
76
+ }): Node;
77
+ }
78
+ import { Metric } from '../metric.js';
79
+ //# sourceMappingURL=first-contentful-paint.d.ts.map
@@ -0,0 +1,200 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Lantern from '../types/lantern.js';
8
+ import {Metric} from '../metric.js';
9
+ import {BaseNode} from '../base-node.js';
10
+
11
+ /** @typedef {import('../base-node.js').Node} Node */
12
+ /** @template T @typedef {import('../network-node.js').NetworkNode<T>} NetworkNode */
13
+ /** @typedef {import('../cpu-node.js').CPUNode} CpuNode */
14
+
15
+ class FirstContentfulPaint extends Metric {
16
+ /**
17
+ * @return {Lantern.Simulation.MetricCoefficients}
18
+ */
19
+ static get COEFFICIENTS() {
20
+ return {
21
+ intercept: 0,
22
+ optimistic: 0.5,
23
+ pessimistic: 0.5,
24
+ };
25
+ }
26
+
27
+ /**
28
+ * @template T
29
+ * @typedef FirstPaintBasedGraphOpts
30
+ * @property {number} cutoffTimestamp The timestamp used to filter out tasks that occured after
31
+ * our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
32
+ * @property {function(NetworkNode<T>):boolean} treatNodeAsRenderBlocking The function that determines
33
+ * which resources should be considered *possibly* render-blocking.
34
+ * @property {(function(CpuNode):boolean)=} additionalCpuNodesToTreatAsRenderBlocking The function that
35
+ * determines which CPU nodes should also be included in our blocking node IDs set,
36
+ * beyond what getRenderBlockingNodeData() already includes.
37
+ */
38
+
39
+ /**
40
+ * This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
41
+ * *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
42
+ * It also computes the set of corresponding CPU node ids that were needed for the paint at the
43
+ * given timestamp.
44
+ *
45
+ * @template [T=unknown]
46
+ * @param {Node} graph
47
+ * @param {FirstPaintBasedGraphOpts<T>} opts
48
+ * @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
49
+ */
50
+ static getRenderBlockingNodeData(
51
+ graph,
52
+ {cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
53
+ ) {
54
+ /** @type {Map<string, CpuNode>} A map of blocking script URLs to the earliest EvaluateScript task node that executed them. */
55
+ const scriptUrlToNodeMap = new Map();
56
+
57
+ /** @type {Array<CpuNode>} */
58
+ const cpuNodes = [];
59
+ graph.traverse(node => {
60
+ if (node.type === BaseNode.TYPES.CPU) {
61
+ // A task is *possibly* render blocking if it *started* before cutoffTimestamp.
62
+ // We use startTime here because the paint event can be *inside* the task that was render blocking.
63
+ if (node.startTime <= cutoffTimestamp) cpuNodes.push(node);
64
+
65
+ // Build our script URL map to find the earliest EvaluateScript task node.
66
+ const scriptUrls = node.getEvaluateScriptURLs();
67
+ for (const url of scriptUrls) {
68
+ // Use the earliest CPU node we find.
69
+ const existing = scriptUrlToNodeMap.get(url) || node;
70
+ scriptUrlToNodeMap.set(url, node.startTime < existing.startTime ? node : existing);
71
+ }
72
+ }
73
+ });
74
+
75
+ cpuNodes.sort((a, b) => a.startTime - b.startTime);
76
+
77
+ // A script is *possibly* render blocking if it finished loading before cutoffTimestamp.
78
+ const possiblyRenderBlockingScriptUrls = Metric.getScriptUrls(graph, node => {
79
+ // The optimistic LCP treatNodeAsRenderBlocking fn wants to exclude some images in the graph,
80
+ // but here it only receives scripts to evaluate. It's a no-op in this case, but it will
81
+ // matter below in the getFirstPaintBasedGraph clone operation.
82
+ return node.endTime <= cutoffTimestamp && treatNodeAsRenderBlocking(node);
83
+ });
84
+
85
+ // A script is *definitely not* render blocking if its EvaluateScript task started after cutoffTimestamp.
86
+ /** @type {Set<string>} */
87
+ const definitelyNotRenderBlockingScriptUrls = new Set();
88
+ /** @type {Set<string>} */
89
+ const renderBlockingCpuNodeIds = new Set();
90
+ for (const url of possiblyRenderBlockingScriptUrls) {
91
+ // Lookup the CPU node that had the earliest EvaluateScript for this URL.
92
+ const cpuNodeForUrl = scriptUrlToNodeMap.get(url);
93
+
94
+ // If we can't find it at all, we can't conclude anything, so just skip it.
95
+ if (!cpuNodeForUrl) continue;
96
+
97
+ // If we found it and it was in our `cpuNodes` set that means it finished before cutoffTimestamp, so it really is render-blocking.
98
+ if (cpuNodes.includes(cpuNodeForUrl)) {
99
+ renderBlockingCpuNodeIds.add(cpuNodeForUrl.id);
100
+ continue;
101
+ }
102
+
103
+ // We couldn't find the evaluate script in the set of CPU nodes that ran before our paint, so
104
+ // it must not have been necessary for the paint.
105
+ definitelyNotRenderBlockingScriptUrls.add(url);
106
+ }
107
+
108
+ // The first layout, first paint, and first ParseHTML are almost always necessary for first paint,
109
+ // so we always include those CPU nodes.
110
+ const firstLayout = cpuNodes.find(node => node.didPerformLayout());
111
+ if (firstLayout) renderBlockingCpuNodeIds.add(firstLayout.id);
112
+ const firstPaint = cpuNodes.find(node => node.childEvents.some(e => e.name === 'Paint'));
113
+ if (firstPaint) renderBlockingCpuNodeIds.add(firstPaint.id);
114
+ const firstParse = cpuNodes.find(node => node.childEvents.some(e => e.name === 'ParseHTML'));
115
+ if (firstParse) renderBlockingCpuNodeIds.add(firstParse.id);
116
+
117
+ // If a CPU filter was passed in, we also want to include those extra nodes.
118
+ if (additionalCpuNodesToTreatAsRenderBlocking) {
119
+ cpuNodes
120
+ .filter(additionalCpuNodesToTreatAsRenderBlocking)
121
+ .forEach(node => renderBlockingCpuNodeIds.add(node.id));
122
+ }
123
+
124
+ return {
125
+ definitelyNotRenderBlockingScriptUrls,
126
+ renderBlockingCpuNodeIds,
127
+ };
128
+ }
129
+
130
+ /**
131
+ * This function computes the graph required for the first paint of interest.
132
+ *
133
+ * @template [T=unknown]
134
+ * @param {Node} dependencyGraph
135
+ * @param {FirstPaintBasedGraphOpts<T>} opts
136
+ * @return {Node}
137
+ */
138
+ static getFirstPaintBasedGraph(
139
+ dependencyGraph,
140
+ {cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
141
+ ) {
142
+ const rbData = this.getRenderBlockingNodeData(dependencyGraph, {
143
+ cutoffTimestamp,
144
+ treatNodeAsRenderBlocking,
145
+ additionalCpuNodesToTreatAsRenderBlocking,
146
+ });
147
+ const {definitelyNotRenderBlockingScriptUrls, renderBlockingCpuNodeIds} = rbData;
148
+
149
+ return dependencyGraph.cloneWithRelationships(node => {
150
+ if (node.type === BaseNode.TYPES.NETWORK) {
151
+ // Exclude all nodes that ended after cutoffTimestamp (except for the main document which we always consider necessary)
152
+ // endTime is negative if request does not finish, make sure startTime isn't after cutoffTimestamp in this case.
153
+ const endedAfterPaint = node.endTime > cutoffTimestamp || node.startTime > cutoffTimestamp;
154
+ if (endedAfterPaint && !node.isMainDocument()) return false;
155
+
156
+ const url = node.record.url;
157
+ // If the URL definitely wasn't render-blocking then we filter it out.
158
+ if (definitelyNotRenderBlockingScriptUrls.has(url)) {
159
+ return false;
160
+ }
161
+
162
+ // Lastly, build up the FCP graph of all nodes we consider render blocking
163
+ return treatNodeAsRenderBlocking(node);
164
+ } else {
165
+ // If it's a CPU node, just check if it was blocking.
166
+ return renderBlockingCpuNodeIds.has(node.id);
167
+ }
168
+ });
169
+ }
170
+
171
+ /**
172
+ * @param {Node} dependencyGraph
173
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
174
+ * @return {Node}
175
+ */
176
+ static getOptimisticGraph(dependencyGraph, processedNavigation) {
177
+ return this.getFirstPaintBasedGraph(dependencyGraph, {
178
+ cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
179
+ // In the optimistic graph we exclude resources that appeared to be render blocking but were
180
+ // initiated by a script. While they typically have a very high importance and tend to have a
181
+ // significant impact on the page's content, these resources don't technically block rendering.
182
+ treatNodeAsRenderBlocking: node =>
183
+ node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
184
+ });
185
+ }
186
+
187
+ /**
188
+ * @param {Node} dependencyGraph
189
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
190
+ * @return {Node}
191
+ */
192
+ static getPessimisticGraph(dependencyGraph, processedNavigation) {
193
+ return this.getFirstPaintBasedGraph(dependencyGraph, {
194
+ cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
195
+ treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
196
+ });
197
+ }
198
+ }
199
+
200
+ export {FirstContentfulPaint};
@@ -0,0 +1,6 @@
1
+ export type Node = import('../base-node.js').Node;
2
+ /** @typedef {import('../base-node.js').Node} Node */
3
+ export class FirstMeaningfulPaint extends Metric {
4
+ }
5
+ import { Metric } from '../metric.js';
6
+ //# sourceMappingURL=first-meaningful-paint.d.ts.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Lantern from '../types/lantern.js';
8
+ import {Metric} from '../metric.js';
9
+ import {FirstContentfulPaint} from './first-contentful-paint.js';
10
+
11
+ /** @typedef {import('../base-node.js').Node} Node */
12
+
13
+ class FirstMeaningfulPaint extends Metric {
14
+ /**
15
+ * @return {Lantern.Simulation.MetricCoefficients}
16
+ */
17
+ static get COEFFICIENTS() {
18
+ return {
19
+ intercept: 0,
20
+ optimistic: 0.5,
21
+ pessimistic: 0.5,
22
+ };
23
+ }
24
+
25
+ /**
26
+ * @param {Node} dependencyGraph
27
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
28
+ * @return {Node}
29
+ */
30
+ static getOptimisticGraph(dependencyGraph, processedNavigation) {
31
+ const fmp = processedNavigation.timestamps.firstMeaningfulPaint;
32
+ if (!fmp) {
33
+ throw new Error('NO_FMP');
34
+ }
35
+ return FirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
36
+ cutoffTimestamp: fmp,
37
+ // See FirstContentfulPaint's getOptimisticGraph implementation for a longer description
38
+ // of why we exclude script initiated resources here.
39
+ treatNodeAsRenderBlocking: node =>
40
+ node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
41
+ });
42
+ }
43
+
44
+ /**
45
+ * @param {Node} dependencyGraph
46
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
47
+ * @return {Node}
48
+ */
49
+ static getPessimisticGraph(dependencyGraph, processedNavigation) {
50
+ const fmp = processedNavigation.timestamps.firstMeaningfulPaint;
51
+ if (!fmp) {
52
+ throw new Error('NO_FMP');
53
+ }
54
+
55
+ return FirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
56
+ cutoffTimestamp: fmp,
57
+ treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
58
+ // For pessimistic FMP we'll include *all* layout nodes
59
+ additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
60
+ });
61
+ }
62
+ }
63
+
64
+ export {FirstMeaningfulPaint};
@@ -0,0 +1,26 @@
1
+ export type Node = import('../base-node.js').Node;
2
+ export class Interactive extends Metric {
3
+ /**
4
+ * @param {Node} dependencyGraph
5
+ * @return {Node}
6
+ */
7
+ static getOptimisticGraph(dependencyGraph: Node): Node;
8
+ /**
9
+ * @param {Node} dependencyGraph
10
+ * @return {Node}
11
+ */
12
+ static getPessimisticGraph(dependencyGraph: Node): Node;
13
+ /**
14
+ * @param {LH.Gatherer.Simulation.Result} simulationResult
15
+ * @param {import('../metric.js').Extras} extras
16
+ * @return {LH.Gatherer.Simulation.Result}
17
+ */
18
+ static getEstimateFromSimulation(simulationResult: LH.Gatherer.Simulation.Result, extras: import('../metric.js').Extras): LH.Gatherer.Simulation.Result;
19
+ /**
20
+ * @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
21
+ * @return {number}
22
+ */
23
+ static getLastLongTaskEndTime(nodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], duration?: number): number;
24
+ }
25
+ import { Metric } from '../metric.js';
26
+ //# sourceMappingURL=interactive.d.ts.map
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Lantern from '../types/lantern.js';
8
+ import {Metric} from '../metric.js';
9
+ import {BaseNode} from '../base-node.js';
10
+ import {NetworkRequestTypes} from '../lantern.js';
11
+
12
+ /** @typedef {import('../base-node.js').Node} Node */
13
+
14
+ // Any CPU task of 20 ms or more will end up being a critical long task on mobile
15
+ const CRITICAL_LONG_TASK_THRESHOLD = 20;
16
+
17
+ class Interactive extends Metric {
18
+ /**
19
+ * @return {LH.Gatherer.Simulation.MetricCoefficients}
20
+ */
21
+ static get COEFFICIENTS() {
22
+ return {
23
+ intercept: 0,
24
+ optimistic: 0.45,
25
+ pessimistic: 0.55,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * @param {Node} dependencyGraph
31
+ * @return {Node}
32
+ */
33
+ static getOptimisticGraph(dependencyGraph) {
34
+ // Adjust the critical long task threshold for microseconds
35
+ const minimumCpuTaskDuration = CRITICAL_LONG_TASK_THRESHOLD * 1000;
36
+
37
+ return dependencyGraph.cloneWithRelationships(node => {
38
+ // Include everything that might be a long task
39
+ if (node.type === BaseNode.TYPES.CPU) {
40
+ return node.duration > minimumCpuTaskDuration;
41
+ }
42
+
43
+ // Include all scripts and high priority requests, exclude all images
44
+ const isImage = node.record.resourceType === NetworkRequestTypes.Image;
45
+ const isScript = node.record.resourceType === NetworkRequestTypes.Script;
46
+ return (
47
+ !isImage &&
48
+ (isScript ||
49
+ node.record.priority === 'High' ||
50
+ node.record.priority === 'VeryHigh')
51
+ );
52
+ });
53
+ }
54
+
55
+ /**
56
+ * @param {Node} dependencyGraph
57
+ * @return {Node}
58
+ */
59
+ static getPessimisticGraph(dependencyGraph) {
60
+ return dependencyGraph;
61
+ }
62
+
63
+ /**
64
+ * @param {LH.Gatherer.Simulation.Result} simulationResult
65
+ * @param {import('../metric.js').Extras} extras
66
+ * @return {LH.Gatherer.Simulation.Result}
67
+ */
68
+ static getEstimateFromSimulation(simulationResult, extras) {
69
+ if (!extras.fmpResult) throw new Error('missing fmpResult');
70
+
71
+ const lastTaskAt = Interactive.getLastLongTaskEndTime(simulationResult.nodeTimings);
72
+ const minimumTime = extras.optimistic
73
+ ? extras.fmpResult.optimisticEstimate.timeInMs
74
+ : extras.fmpResult.pessimisticEstimate.timeInMs;
75
+ return {
76
+ timeInMs: Math.max(minimumTime, lastTaskAt),
77
+ nodeTimings: simulationResult.nodeTimings,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * @param {Lantern.Simulation.MetricComputationDataInput} data
83
+ * @param {Omit<import('../metric.js').Extras, 'optimistic'>=} extras
84
+ * @return {Promise<LH.Artifacts.LanternMetric>}
85
+ */
86
+ static async compute(data, extras) {
87
+ const fmpResult = extras?.fmpResult;
88
+ if (!fmpResult) {
89
+ throw new Error('FMP is required to calculate the Interactive metric');
90
+ }
91
+
92
+ const metricResult = await super.compute(data, extras);
93
+ metricResult.timing = Math.max(metricResult.timing, fmpResult.timing);
94
+ return metricResult;
95
+ }
96
+
97
+ /**
98
+ * @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
99
+ * @return {number}
100
+ */
101
+ static getLastLongTaskEndTime(nodeTimings, duration = 50) {
102
+ return Array.from(nodeTimings.entries())
103
+ .filter(([node, timing]) => {
104
+ if (node.type !== BaseNode.TYPES.CPU) return false;
105
+ return timing.duration > duration;
106
+ })
107
+ .map(([_, timing]) => timing.endTime)
108
+ .reduce((max, x) => Math.max(max || 0, x || 0), 0);
109
+ }
110
+ }
111
+
112
+ export {Interactive};
@@ -0,0 +1,19 @@
1
+ export type Node = import('../base-node.js').Node;
2
+ /** @typedef {import('../base-node.js').Node} Node */
3
+ export class LargestContentfulPaint extends Metric {
4
+ /**
5
+ * Low priority image nodes are usually offscreen and very unlikely to be the
6
+ * resource that is required for LCP. Our LCP graphs include everything except for these images.
7
+ *
8
+ * @param {Node} node
9
+ * @return {boolean}
10
+ */
11
+ static isNotLowPriorityImageNode(node: Node): boolean;
12
+ /**
13
+ * @param {LH.Gatherer.Simulation.Result} simulationResult
14
+ * @return {LH.Gatherer.Simulation.Result}
15
+ */
16
+ static getEstimateFromSimulation(simulationResult: LH.Gatherer.Simulation.Result): LH.Gatherer.Simulation.Result;
17
+ }
18
+ import { Metric } from '../metric.js';
19
+ //# sourceMappingURL=largest-contentful-paint.d.ts.map
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Lantern from '../types/lantern.js';
8
+ import {Metric} from '../metric.js';
9
+ import {FirstContentfulPaint} from './first-contentful-paint.js';
10
+ import {LanternError} from '../lantern-error.js';
11
+
12
+ /** @typedef {import('../base-node.js').Node} Node */
13
+
14
+ class LargestContentfulPaint extends Metric {
15
+ /**
16
+ * @return {LH.Gatherer.Simulation.MetricCoefficients}
17
+ */
18
+ static get COEFFICIENTS() {
19
+ return {
20
+ intercept: 0,
21
+ optimistic: 0.5,
22
+ pessimistic: 0.5,
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Low priority image nodes are usually offscreen and very unlikely to be the
28
+ * resource that is required for LCP. Our LCP graphs include everything except for these images.
29
+ *
30
+ * @param {Node} node
31
+ * @return {boolean}
32
+ */
33
+ static isNotLowPriorityImageNode(node) {
34
+ if (node.type !== 'network') return true;
35
+ const isImage = node.record.resourceType === 'Image';
36
+ const isLowPriority = node.record.priority === 'Low' || node.record.priority === 'VeryLow';
37
+ return !isImage || !isLowPriority;
38
+ }
39
+
40
+ /**
41
+ * @param {Node} dependencyGraph
42
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
43
+ * @return {Node}
44
+ */
45
+ static getOptimisticGraph(dependencyGraph, processedNavigation) {
46
+ const lcp = processedNavigation.timestamps.largestContentfulPaint;
47
+ if (!lcp) {
48
+ throw new LanternError('NO_LCP');
49
+ }
50
+
51
+ return FirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
52
+ cutoffTimestamp: lcp,
53
+ treatNodeAsRenderBlocking: LargestContentfulPaint.isNotLowPriorityImageNode,
54
+ });
55
+ }
56
+
57
+ /**
58
+ * @param {Node} dependencyGraph
59
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
60
+ * @return {Node}
61
+ */
62
+ static getPessimisticGraph(dependencyGraph, processedNavigation) {
63
+ const lcp = processedNavigation.timestamps.largestContentfulPaint;
64
+ if (!lcp) {
65
+ throw new LanternError('NO_LCP');
66
+ }
67
+
68
+ return FirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
69
+ cutoffTimestamp: lcp,
70
+ treatNodeAsRenderBlocking: _ => true,
71
+ // For pessimistic LCP we'll include *all* layout nodes
72
+ additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
73
+ });
74
+ }
75
+
76
+ /**
77
+ * @param {LH.Gatherer.Simulation.Result} simulationResult
78
+ * @return {LH.Gatherer.Simulation.Result}
79
+ */
80
+ static getEstimateFromSimulation(simulationResult) {
81
+ const nodeTimesNotOffscreenImages = Array.from(simulationResult.nodeTimings.entries())
82
+ .filter(entry => LargestContentfulPaint.isNotLowPriorityImageNode(entry[0]))
83
+ .map(entry => entry[1].endTime);
84
+
85
+ return {
86
+ timeInMs: Math.max(...nodeTimesNotOffscreenImages),
87
+ nodeTimings: simulationResult.nodeTimings,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * @param {Lantern.Simulation.MetricComputationDataInput} data
93
+ * @param {Omit<import('../metric.js').Extras, 'optimistic'>=} extras
94
+ * @return {Promise<LH.Artifacts.LanternMetric>}
95
+ */
96
+ static async compute(data, extras) {
97
+ const fcpResult = extras?.fcpResult;
98
+ if (!fcpResult) {
99
+ throw new Error('FCP is required to calculate the LCP metric');
100
+ }
101
+
102
+ const metricResult = await super.compute(data, extras);
103
+ metricResult.timing = Math.max(metricResult.timing, fcpResult.timing);
104
+ return metricResult;
105
+ }
106
+ }
107
+
108
+ export {LargestContentfulPaint};
@@ -0,0 +1,30 @@
1
+ export type Node = import('../base-node.js').Node;
2
+ /** @typedef {import('../base-node.js').Node} Node */
3
+ export class MaxPotentialFID extends Metric {
4
+ /**
5
+ * @param {Node} dependencyGraph
6
+ * @return {Node}
7
+ */
8
+ static getOptimisticGraph(dependencyGraph: Node): Node;
9
+ /**
10
+ * @param {Node} dependencyGraph
11
+ * @return {Node}
12
+ */
13
+ static getPessimisticGraph(dependencyGraph: Node): Node;
14
+ /**
15
+ * @param {LH.Gatherer.Simulation.Result} simulation
16
+ * @param {import('../metric.js').Extras} extras
17
+ * @return {LH.Gatherer.Simulation.Result}
18
+ */
19
+ static getEstimateFromSimulation(simulation: LH.Gatherer.Simulation.Result, extras: import('../metric.js').Extras): LH.Gatherer.Simulation.Result;
20
+ /**
21
+ * @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
22
+ * @param {number} fcpTimeInMs
23
+ * @return {Array<{duration: number}>}
24
+ */
25
+ static getTimingsAfterFCP(nodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], fcpTimeInMs: number): Array<{
26
+ duration: number;
27
+ }>;
28
+ }
29
+ import { Metric } from '../metric.js';
30
+ //# sourceMappingURL=max-potential-fid.d.ts.map