lighthouse 11.7.0 → 12.0.0-dev.20240422

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 (334) 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/accesskeys.js +1 -1
  10. package/core/audits/accessibility/aria-allowed-attr.js +1 -1
  11. package/core/audits/accessibility/aria-allowed-role.js +1 -1
  12. package/core/audits/accessibility/aria-command-name.js +1 -1
  13. package/core/audits/accessibility/aria-conditional-attr.d.ts +10 -0
  14. package/core/audits/accessibility/aria-conditional-attr.js +43 -0
  15. package/core/audits/accessibility/{duplicate-id-active.d.ts → aria-deprecated-role.d.ts} +3 -3
  16. package/core/audits/accessibility/aria-deprecated-role.js +43 -0
  17. package/core/audits/accessibility/aria-dialog-name.js +1 -1
  18. package/core/audits/accessibility/aria-hidden-body.js +1 -1
  19. package/core/audits/accessibility/aria-hidden-focus.js +1 -1
  20. package/core/audits/accessibility/aria-input-field-name.js +1 -1
  21. package/core/audits/accessibility/aria-meter-name.js +1 -1
  22. package/core/audits/accessibility/aria-progressbar-name.js +1 -1
  23. package/core/audits/accessibility/aria-prohibited-attr.d.ts +10 -0
  24. package/core/audits/accessibility/aria-prohibited-attr.js +44 -0
  25. package/core/audits/accessibility/aria-required-attr.js +1 -1
  26. package/core/audits/accessibility/aria-required-children.js +1 -1
  27. package/core/audits/accessibility/aria-required-parent.js +1 -1
  28. package/core/audits/accessibility/aria-roles.js +1 -1
  29. package/core/audits/accessibility/aria-text.js +1 -1
  30. package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
  31. package/core/audits/accessibility/aria-tooltip-name.js +1 -1
  32. package/core/audits/accessibility/aria-treeitem-name.js +1 -1
  33. package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
  34. package/core/audits/accessibility/aria-valid-attr.js +1 -1
  35. package/core/audits/accessibility/button-name.js +1 -1
  36. package/core/audits/accessibility/bypass.js +1 -1
  37. package/core/audits/accessibility/color-contrast.js +1 -1
  38. package/core/audits/accessibility/definition-list.js +1 -1
  39. package/core/audits/accessibility/dlitem.js +1 -1
  40. package/core/audits/accessibility/document-title.js +1 -1
  41. package/core/audits/accessibility/duplicate-id-aria.js +1 -1
  42. package/core/audits/accessibility/empty-heading.js +1 -1
  43. package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
  44. package/core/audits/accessibility/frame-title.js +1 -1
  45. package/core/audits/accessibility/heading-order.js +1 -1
  46. package/core/audits/accessibility/html-has-lang.js +1 -1
  47. package/core/audits/accessibility/html-lang-valid.js +1 -1
  48. package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
  49. package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
  50. package/core/audits/accessibility/image-alt.js +1 -1
  51. package/core/audits/accessibility/image-redundant-alt.js +1 -1
  52. package/core/audits/accessibility/input-button-name.js +1 -1
  53. package/core/audits/accessibility/input-image-alt.js +1 -1
  54. package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
  55. package/core/audits/accessibility/label.js +1 -1
  56. package/core/audits/accessibility/landmark-one-main.js +1 -1
  57. package/core/audits/accessibility/link-in-text-block.js +1 -1
  58. package/core/audits/accessibility/link-name.js +1 -1
  59. package/core/audits/accessibility/list.js +1 -1
  60. package/core/audits/accessibility/listitem.js +1 -1
  61. package/core/audits/accessibility/meta-refresh.js +1 -1
  62. package/core/audits/accessibility/meta-viewport.js +1 -1
  63. package/core/audits/accessibility/object-alt.js +1 -1
  64. package/core/audits/accessibility/select-name.js +1 -1
  65. package/core/audits/accessibility/skip-link.js +1 -1
  66. package/core/audits/accessibility/tabindex.js +1 -1
  67. package/core/audits/accessibility/table-duplicate-name.js +1 -1
  68. package/core/audits/accessibility/table-fake-caption.js +1 -1
  69. package/core/audits/accessibility/target-size.js +1 -2
  70. package/core/audits/accessibility/td-has-header.js +1 -1
  71. package/core/audits/accessibility/td-headers-attr.js +1 -1
  72. package/core/audits/accessibility/th-has-data-cells.js +1 -1
  73. package/core/audits/accessibility/valid-lang.js +1 -1
  74. package/core/audits/accessibility/video-caption.js +1 -1
  75. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -16
  76. package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -38
  77. package/core/audits/byte-efficiency/offscreen-images.d.ts +0 -11
  78. package/core/audits/byte-efficiency/offscreen-images.js +0 -15
  79. package/core/audits/byte-efficiency/render-blocking-resources.js +27 -26
  80. package/core/audits/byte-efficiency/unminified-css.js +2 -2
  81. package/core/audits/byte-efficiency/unused-css-rules.js +3 -1
  82. package/core/audits/dobetterweb/uses-http2.d.ts +0 -13
  83. package/core/audits/dobetterweb/uses-http2.js +4 -38
  84. package/core/audits/font-display.js +2 -2
  85. package/core/audits/layout-shifts.js +2 -2
  86. package/core/audits/preload-fonts.d.ts +1 -5
  87. package/core/audits/preload-fonts.js +1 -10
  88. package/core/audits/prioritize-lcp-image.js +2 -2
  89. package/core/audits/redirects-http.d.ts +19 -0
  90. package/core/audits/redirects-http.js +75 -0
  91. package/core/audits/resource-summary.d.ts +2 -0
  92. package/core/audits/resource-summary.js +5 -4
  93. package/core/audits/seo/is-crawlable.d.ts +2 -1
  94. package/core/audits/seo/link-text.js +12 -0
  95. package/core/audits/uses-rel-preload.d.ts +1 -5
  96. package/core/audits/uses-rel-preload.js +5 -15
  97. package/core/audits/work-during-interaction.d.ts +2 -2
  98. package/core/audits/work-during-interaction.js +2 -2
  99. package/core/computed/js-bundles.d.ts +1 -1
  100. package/core/computed/metrics/cumulative-layout-shift.js +2 -2
  101. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +13 -82
  102. package/core/computed/metrics/lantern-first-contentful-paint.js +16 -178
  103. package/core/computed/metrics/lantern-first-meaningful-paint.d.ts +13 -12
  104. package/core/computed/metrics/lantern-first-meaningful-paint.js +11 -49
  105. package/core/computed/metrics/lantern-interactive.d.ts +13 -21
  106. package/core/computed/metrics/lantern-interactive.js +12 -84
  107. package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +13 -25
  108. package/core/computed/metrics/lantern-largest-contentful-paint.js +13 -80
  109. package/core/computed/metrics/lantern-max-potential-fid.d.ts +13 -24
  110. package/core/computed/metrics/lantern-max-potential-fid.js +12 -64
  111. package/core/computed/metrics/lantern-metric.d.ts +14 -18
  112. package/core/computed/metrics/lantern-metric.js +34 -30
  113. package/core/computed/metrics/lantern-speed-index.d.ts +13 -32
  114. package/core/computed/metrics/lantern-speed-index.js +13 -116
  115. package/core/computed/metrics/lantern-total-blocking-time.d.ts +13 -25
  116. package/core/computed/metrics/lantern-total-blocking-time.js +10 -94
  117. package/core/computed/metrics/metric.js +1 -3
  118. package/core/computed/module-duplication.d.ts +1 -1
  119. package/core/computed/navigation-insights.d.ts +36 -0
  120. package/core/computed/navigation-insights.js +35 -0
  121. package/core/computed/page-dependency-graph.js +3 -7
  122. package/core/computed/resource-summary.d.ts +10 -12
  123. package/core/computed/resource-summary.js +12 -19
  124. package/core/computed/trace-engine-result.d.ts +16 -3
  125. package/core/computed/trace-engine-result.js +19 -14
  126. package/core/computed/unused-css.d.ts +3 -1
  127. package/core/computed/unused-css.js +6 -5
  128. package/core/config/config-helpers.js +0 -4
  129. package/core/config/constants.js +0 -1
  130. package/core/config/default-config.js +63 -142
  131. package/core/config/experimental-config.js +11 -0
  132. package/core/config/filters.js +0 -1
  133. package/core/gather/driver/navigation.js +4 -1
  134. package/core/gather/driver/prepare.js +1 -1
  135. package/core/gather/driver/target-manager.js +4 -3
  136. package/core/gather/driver/wait-for-condition.js +2 -3
  137. package/core/gather/driver.d.ts +12 -0
  138. package/core/gather/driver.js +27 -0
  139. package/core/gather/gatherers/accessibility.js +4 -6
  140. package/core/gather/gatherers/css-usage.d.ts +0 -21
  141. package/core/gather/gatherers/css-usage.js +9 -119
  142. package/core/gather/gatherers/root-causes.d.ts +2 -2
  143. package/core/gather/gatherers/root-causes.js +6 -5
  144. package/core/gather/gatherers/stylesheets.d.ts +26 -0
  145. package/core/gather/gatherers/stylesheets.js +138 -0
  146. package/core/gather/gatherers/trace-elements.d.ts +4 -13
  147. package/core/gather/gatherers/trace-elements.js +42 -31
  148. package/core/gather/navigation-runner.js +3 -1
  149. package/core/gather/session.d.ts +8 -0
  150. package/core/gather/session.js +14 -1
  151. package/core/lib/emulation.js +2 -2
  152. package/core/lib/i18n/i18n.d.ts +0 -1
  153. package/core/lib/i18n/i18n.js +0 -2
  154. package/core/lib/lantern/cpu-node.d.ts +7 -1
  155. package/core/lib/lantern/cpu-node.js +12 -2
  156. package/core/lib/lantern/lantern-error.d.ts +8 -0
  157. package/core/lib/lantern/lantern-error.js +9 -0
  158. package/core/lib/lantern/metric.js +2 -6
  159. package/core/lib/lantern/metrics/first-contentful-paint.d.ts +79 -0
  160. package/core/lib/lantern/metrics/first-contentful-paint.js +200 -0
  161. package/core/lib/lantern/metrics/first-meaningful-paint.d.ts +6 -0
  162. package/core/lib/lantern/metrics/first-meaningful-paint.js +64 -0
  163. package/core/lib/lantern/metrics/interactive.d.ts +26 -0
  164. package/core/lib/lantern/metrics/interactive.js +112 -0
  165. package/core/lib/lantern/metrics/largest-contentful-paint.d.ts +19 -0
  166. package/core/lib/lantern/metrics/largest-contentful-paint.js +108 -0
  167. package/core/lib/lantern/metrics/max-potential-fid.d.ts +30 -0
  168. package/core/lib/lantern/metrics/max-potential-fid.js +92 -0
  169. package/core/lib/lantern/metrics/speed-index.d.ts +38 -0
  170. package/core/lib/lantern/metrics/speed-index.js +144 -0
  171. package/core/lib/lantern/metrics/total-blocking-time.d.ts +31 -0
  172. package/core/lib/lantern/metrics/total-blocking-time.js +128 -0
  173. package/core/lib/lantern/network-node.js +28 -4
  174. package/core/lib/lantern/page-dependency-graph.d.ts +4 -4
  175. package/core/lib/lantern/page-dependency-graph.js +19 -7
  176. package/core/lib/lantern/simulator/connection-pool.d.ts +2 -12
  177. package/core/lib/lantern/simulator/connection-pool.js +3 -22
  178. package/core/lib/lantern/simulator/network-analyzer.js +1 -12
  179. package/core/lib/lantern/simulator/simulator.d.ts +2 -4
  180. package/core/lib/lantern/simulator/simulator.js +7 -14
  181. package/core/lib/lantern/types/lantern.d.ts +5 -3
  182. package/core/lib/lantern-trace-saver.js +0 -1
  183. package/core/lib/lh-error.d.ts +9 -0
  184. package/core/lib/lh-error.js +13 -3
  185. package/core/lib/lighthouse-compatibility.js +53 -10
  186. package/core/lib/navigation-error.js +3 -0
  187. package/core/lib/network-request.d.ts +1 -0
  188. package/core/lib/network-request.js +29 -1
  189. package/core/lib/proto-preprocessor.js +24 -8
  190. package/core/lib/tracehouse/trace-processor.js +15 -2
  191. package/core/runner.js +18 -25
  192. package/dist/report/bundle.esm.js +11 -112
  193. package/dist/report/flow.js +16 -117
  194. package/dist/report/standalone.js +12 -113
  195. package/flow-report/src/help-dialog.tsx +0 -1
  196. package/flow-report/src/i18n/i18n.d.ts +0 -4
  197. package/flow-report/src/i18n/ui-strings.d.ts +0 -1
  198. package/flow-report/src/i18n/ui-strings.js +0 -2
  199. package/flow-report/src/summary/category.tsx +10 -12
  200. package/package.json +7 -7
  201. package/readme.md +4 -4
  202. package/report/assets/styles.css +1 -60
  203. package/report/assets/templates.html +0 -95
  204. package/report/renderer/category-renderer.js +1 -1
  205. package/report/renderer/components.d.ts +2 -2
  206. package/report/renderer/components.js +2 -103
  207. package/report/renderer/dom.d.ts +1 -1
  208. package/report/renderer/dom.js +1 -1
  209. package/report/renderer/performance-category-renderer.d.ts +0 -7
  210. package/report/renderer/performance-category-renderer.js +1 -32
  211. package/report/renderer/report-renderer.js +3 -13
  212. package/report/renderer/report-ui-features.js +1 -1
  213. package/report/renderer/report-utils.d.ts +0 -1
  214. package/report/renderer/report-utils.js +13 -13
  215. package/shared/localization/locales/ar-XB.json +62 -329
  216. package/shared/localization/locales/ar.json +62 -329
  217. package/shared/localization/locales/bg.json +62 -329
  218. package/shared/localization/locales/ca.json +62 -329
  219. package/shared/localization/locales/cs.json +62 -329
  220. package/shared/localization/locales/da.json +62 -329
  221. package/shared/localization/locales/de.json +62 -329
  222. package/shared/localization/locales/el.json +62 -329
  223. package/shared/localization/locales/en-GB.json +62 -329
  224. package/shared/localization/locales/en-US.json +101 -332
  225. package/shared/localization/locales/en-XA.json +62 -329
  226. package/shared/localization/locales/en-XL.json +101 -332
  227. package/shared/localization/locales/es-419.json +62 -329
  228. package/shared/localization/locales/es.json +62 -329
  229. package/shared/localization/locales/fi.json +62 -329
  230. package/shared/localization/locales/fil.json +61 -328
  231. package/shared/localization/locales/fr.json +62 -329
  232. package/shared/localization/locales/he.json +62 -329
  233. package/shared/localization/locales/hi.json +62 -329
  234. package/shared/localization/locales/hr.json +63 -330
  235. package/shared/localization/locales/hu.json +62 -329
  236. package/shared/localization/locales/id.json +62 -329
  237. package/shared/localization/locales/it.json +62 -329
  238. package/shared/localization/locales/ja.json +62 -329
  239. package/shared/localization/locales/ko.json +62 -329
  240. package/shared/localization/locales/lt.json +62 -329
  241. package/shared/localization/locales/lv.json +62 -329
  242. package/shared/localization/locales/nl.json +62 -329
  243. package/shared/localization/locales/no.json +62 -329
  244. package/shared/localization/locales/pl.json +61 -328
  245. package/shared/localization/locales/pt-PT.json +62 -329
  246. package/shared/localization/locales/pt.json +64 -331
  247. package/shared/localization/locales/ro.json +62 -329
  248. package/shared/localization/locales/ru.json +62 -329
  249. package/shared/localization/locales/sk.json +62 -329
  250. package/shared/localization/locales/sl.json +62 -329
  251. package/shared/localization/locales/sr-Latn.json +62 -329
  252. package/shared/localization/locales/sr.json +62 -329
  253. package/shared/localization/locales/sv.json +62 -329
  254. package/shared/localization/locales/ta.json +61 -328
  255. package/shared/localization/locales/te.json +62 -329
  256. package/shared/localization/locales/th.json +62 -329
  257. package/shared/localization/locales/tr.json +62 -329
  258. package/shared/localization/locales/uk.json +62 -329
  259. package/shared/localization/locales/vi.json +61 -328
  260. package/shared/localization/locales/zh-HK.json +62 -329
  261. package/shared/localization/locales/zh-TW.json +61 -328
  262. package/shared/localization/locales/zh.json +62 -329
  263. package/tsconfig.json +1 -3
  264. package/types/artifacts.d.ts +21 -110
  265. package/types/audit.d.ts +0 -10
  266. package/types/config.d.ts +0 -1
  267. package/types/externs.d.ts +0 -2
  268. package/types/gatherer.d.ts +3 -0
  269. package/types/internal/smokehouse.d.ts +1 -1
  270. package/types/lh.d.ts +0 -2
  271. package/types/lhr/audit-details.d.ts +9 -4
  272. package/types/lhr/lhr.d.ts +0 -4
  273. package/types/lhr/settings.d.ts +0 -4
  274. package/core/audits/accessibility/duplicate-id-active.js +0 -42
  275. package/core/audits/content-width.d.ts +0 -17
  276. package/core/audits/content-width.js +0 -76
  277. package/core/audits/installable-manifest.d.ts +0 -111
  278. package/core/audits/installable-manifest.js +0 -248
  279. package/core/audits/layout-shift-elements.d.ts +0 -16
  280. package/core/audits/layout-shift-elements.js +0 -101
  281. package/core/audits/manual/pwa-cross-browser.d.ts +0 -12
  282. package/core/audits/manual/pwa-cross-browser.js +0 -40
  283. package/core/audits/manual/pwa-each-page-has-url.d.ts +0 -12
  284. package/core/audits/manual/pwa-each-page-has-url.js +0 -38
  285. package/core/audits/manual/pwa-page-transitions.d.ts +0 -12
  286. package/core/audits/manual/pwa-page-transitions.js +0 -38
  287. package/core/audits/maskable-icon.d.ts +0 -25
  288. package/core/audits/maskable-icon.js +0 -70
  289. package/core/audits/multi-check-audit.d.ts +0 -28
  290. package/core/audits/multi-check-audit.js +0 -82
  291. package/core/audits/no-unload-listeners.d.ts +0 -16
  292. package/core/audits/no-unload-listeners.js +0 -86
  293. package/core/audits/performance-budget.d.ts +0 -39
  294. package/core/audits/performance-budget.js +0 -154
  295. package/core/audits/script-elements-test-audit.d.ts +0 -14
  296. package/core/audits/script-elements-test-audit.js +0 -29
  297. package/core/audits/seo/plugins.d.ts +0 -15
  298. package/core/audits/seo/plugins.js +0 -150
  299. package/core/audits/seo/tap-targets.d.ts +0 -50
  300. package/core/audits/seo/tap-targets.js +0 -352
  301. package/core/audits/splash-screen.d.ts +0 -37
  302. package/core/audits/splash-screen.js +0 -98
  303. package/core/audits/themed-omnibox.d.ts +0 -42
  304. package/core/audits/themed-omnibox.js +0 -102
  305. package/core/audits/timing-budget.d.ts +0 -43
  306. package/core/audits/timing-budget.js +0 -175
  307. package/core/computed/manifest-values.d.ts +0 -24
  308. package/core/computed/manifest-values.js +0 -136
  309. package/core/config/budget.d.ts +0 -74
  310. package/core/config/budget.js +0 -340
  311. package/core/config/metrics-to-audits.d.ts +0 -19
  312. package/core/config/metrics-to-audits.js +0 -62
  313. package/core/gather/driver/service-workers.d.ts +0 -16
  314. package/core/gather/driver/service-workers.js +0 -52
  315. package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.d.ts +0 -47
  316. package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js +0 -233
  317. package/core/gather/gatherers/global-listeners.d.ts +0 -27
  318. package/core/gather/gatherers/global-listeners.js +0 -108
  319. package/core/gather/gatherers/installability-errors.d.ts +0 -17
  320. package/core/gather/gatherers/installability-errors.js +0 -56
  321. package/core/gather/gatherers/script-elements.d.ts +0 -21
  322. package/core/gather/gatherers/script-elements.js +0 -100
  323. package/core/gather/gatherers/seo/embedded-content.d.ts +0 -10
  324. package/core/gather/gatherers/seo/embedded-content.js +0 -63
  325. package/core/gather/gatherers/seo/tap-targets.d.ts +0 -21
  326. package/core/gather/gatherers/seo/tap-targets.js +0 -389
  327. package/core/gather/gatherers/service-worker.d.ts +0 -10
  328. package/core/gather/gatherers/service-worker.js +0 -32
  329. package/core/gather/gatherers/web-app-manifest.d.ts +0 -32
  330. package/core/gather/gatherers/web-app-manifest.js +0 -106
  331. package/report/renderer/pwa-category-renderer.d.ts +0 -55
  332. package/report/renderer/pwa-category-renderer.js +0 -184
  333. package/third-party/chromium-synchronization/installability-errors-test.js +0 -79
  334. package/types/lhr/budget.d.ts +0 -60
@@ -28,7 +28,6 @@ import {TraceEngineResult} from '../../computed/trace-engine-result.js';
28
28
 
29
29
  /** @typedef {{nodeId: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
30
30
 
31
- const MAX_LAYOUT_SHIFT_ELEMENTS = 15;
32
31
  const MAX_LAYOUT_SHIFTS = 15;
33
32
 
34
33
  /**
@@ -66,41 +65,55 @@ class TraceElements extends BaseGatherer {
66
65
  if (name) this.animationIdToName.set(id, name);
67
66
  }
68
67
 
69
- /**
70
- * This function finds the top (up to 15) elements that shift on the page.
71
- *
72
- * @param {LH.Trace} trace
73
- * @param {LH.Gatherer.Context} context
74
- * @return {Promise<Array<{nodeId: number}>>}
75
- */
76
- static async getTopLayoutShiftElements(trace, context) {
77
- const {impactByNodeId} = await CumulativeLayoutShift.request(trace, context);
78
-
79
- return [...impactByNodeId.entries()]
80
- .sort((a, b) => b[1] - a[1])
81
- .slice(0, MAX_LAYOUT_SHIFT_ELEMENTS)
82
- .map(([nodeId]) => ({nodeId}));
83
- }
84
-
85
68
  /**
86
69
  * We want to a single representative node to represent the shift, so let's pick
87
70
  * the one with the largest impact (size x distance moved).
88
71
  *
89
72
  * @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
90
73
  * @param {Map<number, number>} impactByNodeId
74
+ * @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
91
75
  * @return {number|undefined}
92
76
  */
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;
77
+ static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event) {
78
+ try {
79
+ let biggestImpactNodeId;
80
+ let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
81
+ for (const node of impactedNodes) {
82
+ const impactScore = impactByNodeId.get(node.node_id);
83
+ if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
84
+ biggestImpactNodeId = node.node_id;
85
+ biggestImpactNodeScore = impactScore;
86
+ }
101
87
  }
88
+ return biggestImpactNodeId;
89
+ } catch (err) {
90
+ // See https://github.com/GoogleChrome/lighthouse/issues/15870
91
+ // `impactedNodes` should always be an array here, but it can randomly be something else for
92
+ // currently unknown reasons. This exception handling will help us identify what
93
+ // `impactedNodes` really is and also prevent the error from being fatal.
94
+
95
+ // It's possible `impactedNodes` is not JSON serializable, so let's add more supplemental
96
+ // fields just in case.
97
+ const impactedNodesType = typeof impactedNodes;
98
+ const impactedNodesClassName = impactedNodes?.constructor?.name;
99
+
100
+ let impactedNodesJson;
101
+ let eventJson;
102
+ try {
103
+ impactedNodesJson = JSON.parse(JSON.stringify(impactedNodes));
104
+ eventJson = JSON.parse(JSON.stringify(event));
105
+ } catch {}
106
+
107
+ Sentry.captureException(err, {
108
+ extra: {
109
+ impactedNodes: impactedNodesJson,
110
+ event: eventJson,
111
+ impactedNodesType,
112
+ impactedNodesClassName,
113
+ },
114
+ });
115
+ return;
102
116
  }
103
- return biggestImpactNodeId;
104
117
  }
105
118
 
106
119
  /**
@@ -109,7 +122,7 @@ class TraceElements extends BaseGatherer {
109
122
  * that may have caused the shift.
110
123
  *
111
124
  * @param {LH.Trace} trace
112
- * @param {LH.Artifacts.TraceEngineResult} traceEngineResult
125
+ * @param {LH.Artifacts.TraceEngineResult['data']} traceEngineResult
113
126
  * @param {LH.Artifacts.TraceEngineRootCauses} rootCauses
114
127
  * @param {LH.Gatherer.Context} context
115
128
  * @return {Promise<Array<{nodeId: number}>>}
@@ -129,7 +142,7 @@ class TraceElements extends BaseGatherer {
129
142
  const nodeIds = [];
130
143
  const impactedNodes = event.args.data.impacted_nodes || [];
131
144
  const biggestImpactedNodeId =
132
- this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId);
145
+ this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event);
133
146
  if (biggestImpactedNodeId !== undefined) {
134
147
  nodeIds.push(biggestImpactedNodeId);
135
148
  }
@@ -308,16 +321,14 @@ class TraceElements extends BaseGatherer {
308
321
  const {mainThreadEvents} = processedTrace;
309
322
 
310
323
  const lcpNodeData = await TraceElements.getLcpElement(trace, context);
311
- const shiftElementsNodeData = await TraceElements.getTopLayoutShiftElements(trace, context);
312
324
  const shiftsData = await TraceElements.getTopLayoutShifts(
313
- trace, traceEngineResult, rootCauses, context);
325
+ trace, traceEngineResult.data, rootCauses, context);
314
326
  const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
315
327
  const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
316
328
 
317
329
  /** @type {Map<string, TraceElementData[]>} */
318
330
  const backendNodeDataMap = new Map([
319
331
  ['largest-contentful-paint', lcpNodeData ? [lcpNodeData] : []],
320
- ['layout-shift-element', shiftElementsNodeData],
321
332
  ['layout-shift', shiftsData],
322
333
  ['animation', animatedElementData],
323
334
  ['responsiveness', responsivenessElementData ? [responsivenessElementData] : []],
@@ -92,7 +92,9 @@ async function _navigate(navigationContext) {
92
92
  return {requestedUrl, mainDocumentUrl, navigationError: undefined};
93
93
  } catch (err) {
94
94
  if (!(err instanceof LighthouseError)) throw err;
95
- if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG') throw err;
95
+ if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG' && err.code !== 'TARGET_CRASHED') {
96
+ throw err;
97
+ }
96
98
  if (typeof requestor !== 'string') throw err;
97
99
 
98
100
  // TODO: Make the urls optional here so we don't need to throw an error with a callback requestor.
@@ -40,6 +40,14 @@ export class ProtocolSession extends ProtocolSession_base implements LH.Gatherer
40
40
  * @return {Promise<LH.CrdpCommands[C]['returnType']>}
41
41
  */
42
42
  sendCommand<C extends keyof import("puppeteer-core").ProtocolMapping.Commands>(method: C, ...params: import("puppeteer-core").ProtocolMapping.Commands[C]["paramsType"]): Promise<import("puppeteer-core").ProtocolMapping.Commands[C]["returnType"]>;
43
+ /**
44
+ * Send and if there's an error response, do not reject.
45
+ * @template {keyof LH.CrdpCommands} C
46
+ * @param {C} method
47
+ * @param {LH.CrdpCommands[C]['paramsType']} params
48
+ * @return {Promise<void>}
49
+ */
50
+ sendCommandAndIgnore<C_1 extends keyof import("puppeteer-core").ProtocolMapping.Commands>(method: C_1, ...params: import("puppeteer-core").ProtocolMapping.Commands[C_1]["paramsType"]): Promise<void>;
43
51
  /**
44
52
  * Disposes of a session so that it can no longer talk to Chrome.
45
53
  * @return {Promise<void>}
@@ -100,6 +100,7 @@ class ProtocolSession extends CrdpEventEmitter {
100
100
  /** @type {NodeJS.Timer|undefined} */
101
101
  let timeout;
102
102
  const timeoutPromise = new Promise((resolve, reject) => {
103
+ // Unexpected setTimeout invocation to preserve the error stack. https://github.com/GoogleChrome/lighthouse/issues/13332
103
104
  // eslint-disable-next-line max-len
104
105
  timeout = setTimeout(reject, timeoutMs, new LighthouseError(LighthouseError.errors.PROTOCOL_TIMEOUT, {
105
106
  protocolMethod: method,
@@ -120,6 +121,18 @@ class ProtocolSession extends CrdpEventEmitter {
120
121
  });
121
122
  }
122
123
 
124
+ /**
125
+ * Send and if there's an error response, do not reject.
126
+ * @template {keyof LH.CrdpCommands} C
127
+ * @param {C} method
128
+ * @param {LH.CrdpCommands[C]['paramsType']} params
129
+ * @return {Promise<void>}
130
+ */
131
+ sendCommandAndIgnore(method, ...params) {
132
+ return this.sendCommand(method, ...params)
133
+ .catch(e => log.verbose('session', method, e.message)).then(_ => void 0);
134
+ }
135
+
123
136
  /**
124
137
  * Disposes of a session so that it can no longer talk to Chrome.
125
138
  * @return {Promise<void>}
@@ -127,7 +140,7 @@ class ProtocolSession extends CrdpEventEmitter {
127
140
  async dispose() {
128
141
  // @ts-expect-error Puppeteer expects the handler params to be type `unknown`
129
142
  this._cdpSession.off('*', this._handleProtocolEvent);
130
- await this._cdpSession.detach();
143
+ await this._cdpSession.detach().catch(e => log.verbose('session', 'detach failed', e.message));
131
144
  }
132
145
  }
133
146
 
@@ -128,7 +128,7 @@ function enableNetworkThrottling(session, throttlingSettings) {
128
128
  * @return {Promise<void>}
129
129
  */
130
130
  function clearNetworkThrottling(session) {
131
- return session.sendCommand('Network.emulateNetworkConditions', NO_THROTTLING_METRICS);
131
+ return session.sendCommandAndIgnore('Network.emulateNetworkConditions', NO_THROTTLING_METRICS);
132
132
  }
133
133
 
134
134
  /**
@@ -146,7 +146,7 @@ function enableCPUThrottling(session, throttlingSettings) {
146
146
  * @return {Promise<void>}
147
147
  */
148
148
  function clearCPUThrottling(session) {
149
- return session.sendCommand('Emulation.setCPUThrottlingRate', NO_CPU_THROTTLE_METRICS);
149
+ return session.sendCommandAndIgnore('Emulation.setCPUThrottlingRate', NO_CPU_THROTTLE_METRICS);
150
150
  }
151
151
 
152
152
  export {
@@ -19,7 +19,6 @@ export namespace UIStrings {
19
19
  const columnRequests: string;
20
20
  const columnName: string;
21
21
  const columnSource: string;
22
- const columnOverBudget: string;
23
22
  const columnElement: string;
24
23
  const columnStartTime: string;
25
24
  const columnDuration: string;
@@ -57,8 +57,6 @@ const UIStrings = {
57
57
  columnName: 'Name',
58
58
  /** Label for a column in a data table; entries will be the locations of JavaScript or CSS code, e.g. the name of a Javascript package or module. */
59
59
  columnSource: 'Source',
60
- /** Label for a column in a data table; entries will be how much a predetermined budget has been exeeded by. Depending on the context, this number could represent an excess in quantity or size of network requests, or, an excess in the duration of time that it takes for the page to load.*/
61
- columnOverBudget: 'Over Budget',
62
60
  /** Label for a column in a data table; entries will be a representation of a DOM element. */
63
61
  columnElement: 'Element',
64
62
  /** Label for a column in a data table; entries will be the number of milliseconds since the page started loading. */
@@ -6,11 +6,17 @@ export class CPUNode<T = any> extends BaseNode<T> {
6
6
  /**
7
7
  * @param {LH.TraceEvent} parentEvent
8
8
  * @param {LH.TraceEvent[]=} childEvents
9
+ * @param {number=} correctedEndTs
9
10
  */
10
- constructor(parentEvent: LH.TraceEvent, childEvents?: LH.TraceEvent[] | undefined);
11
+ constructor(parentEvent: LH.TraceEvent, childEvents?: LH.TraceEvent[] | undefined, correctedEndTs?: number | undefined);
11
12
  _event: LH.TraceEvent;
12
13
  _childEvents: LH.TraceEvent[];
14
+ _correctedEndTs: number | undefined;
13
15
  get type(): "cpu";
16
+ /**
17
+ * @return {number}
18
+ */
19
+ get duration(): number;
14
20
  /**
15
21
  * @return {LH.TraceEvent}
16
22
  */
@@ -15,13 +15,15 @@ class CPUNode extends BaseNode {
15
15
  /**
16
16
  * @param {LH.TraceEvent} parentEvent
17
17
  * @param {LH.TraceEvent[]=} childEvents
18
+ * @param {number=} correctedEndTs
18
19
  */
19
- constructor(parentEvent, childEvents = []) {
20
+ constructor(parentEvent, childEvents = [], correctedEndTs) {
20
21
  const nodeId = `${parentEvent.tid}.${parentEvent.ts}`;
21
22
  super(nodeId);
22
23
 
23
24
  this._event = parentEvent;
24
25
  this._childEvents = childEvents;
26
+ this._correctedEndTs = correctedEndTs;
25
27
  }
26
28
 
27
29
  get type() {
@@ -39,9 +41,17 @@ class CPUNode extends BaseNode {
39
41
  * @return {number}
40
42
  */
41
43
  get endTime() {
44
+ if (this._correctedEndTs) return this._correctedEndTs;
42
45
  return this._event.ts + this._event.dur;
43
46
  }
44
47
 
48
+ /**
49
+ * @return {number}
50
+ */
51
+ get duration() {
52
+ return this.endTime - this.startTime;
53
+ }
54
+
45
55
  /**
46
56
  * @return {LH.TraceEvent}
47
57
  */
@@ -83,7 +93,7 @@ class CPUNode extends BaseNode {
83
93
  * @return {CPUNode}
84
94
  */
85
95
  cloneWithoutRelationships() {
86
- return new CPUNode(this._event, this._childEvents);
96
+ return new CPUNode(this._event, this._childEvents, this._correctedEndTs);
87
97
  }
88
98
  }
89
99
 
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export class LanternError extends Error {
7
+ }
8
+ //# sourceMappingURL=lantern-error.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ class LanternError extends Error {}
8
+
9
+ export {LanternError};
@@ -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