lighthouse 12.5.1 → 12.6.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 (269) hide show
  1. package/cli/run.js +1 -1
  2. package/cli/test/smokehouse/config/exclusions.js +2 -0
  3. package/cli/test/smokehouse/lib/concurrent-mapper.d.ts +3 -3
  4. package/core/audits/audit.d.ts +1 -1
  5. package/core/audits/audit.js +3 -2
  6. package/core/audits/bootup-time.d.ts +1 -1
  7. package/core/audits/bootup-time.js +3 -3
  8. package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
  9. package/core/audits/byte-efficiency/duplicated-javascript.js +1 -1
  10. package/core/audits/byte-efficiency/efficient-animated-content.js +1 -1
  11. package/core/audits/byte-efficiency/legacy-javascript.js +2 -3
  12. package/core/audits/byte-efficiency/modern-image-formats.js +1 -1
  13. package/core/audits/byte-efficiency/offscreen-images.js +4 -4
  14. package/core/audits/byte-efficiency/render-blocking-resources.js +4 -4
  15. package/core/audits/byte-efficiency/total-byte-weight.d.ts +1 -1
  16. package/core/audits/byte-efficiency/total-byte-weight.js +2 -2
  17. package/core/audits/byte-efficiency/unminified-css.js +1 -1
  18. package/core/audits/byte-efficiency/unminified-javascript.js +1 -1
  19. package/core/audits/byte-efficiency/unused-css-rules.js +2 -2
  20. package/core/audits/byte-efficiency/unused-javascript.js +1 -1
  21. package/core/audits/byte-efficiency/uses-long-cache-ttl.d.ts +1 -1
  22. package/core/audits/byte-efficiency/uses-long-cache-ttl.js +2 -2
  23. package/core/audits/byte-efficiency/uses-optimized-images.js +2 -2
  24. package/core/audits/byte-efficiency/uses-responsive-images.d.ts +1 -1
  25. package/core/audits/byte-efficiency/uses-responsive-images.js +1 -1
  26. package/core/audits/byte-efficiency/uses-text-compression.js +1 -1
  27. package/core/audits/clickjacking-mitigation.js +2 -2
  28. package/core/audits/critical-request-chains.js +3 -3
  29. package/core/audits/csp-xss.js +2 -2
  30. package/core/audits/diagnostics.js +3 -3
  31. package/core/audits/dobetterweb/charset.js +2 -2
  32. package/core/audits/dobetterweb/doctype.js +2 -2
  33. package/core/audits/dobetterweb/dom-size.d.ts +1 -1
  34. package/core/audits/dobetterweb/dom-size.js +3 -3
  35. package/core/audits/dobetterweb/uses-http2.js +2 -2
  36. package/core/audits/final-screenshot.js +2 -2
  37. package/core/audits/font-display.js +2 -2
  38. package/core/audits/has-hsts.js +2 -2
  39. package/core/audits/image-size-responsive.d.ts +3 -2
  40. package/core/audits/image-size-responsive.js +30 -4
  41. package/core/audits/insights/{use-cache-insight.d.ts → cache-insight.d.ts} +3 -3
  42. package/core/audits/insights/{use-cache-insight.js → cache-insight.js} +15 -13
  43. package/core/audits/insights/cls-culprits-insight.js +3 -3
  44. package/core/audits/insights/document-latency-insight.js +1 -1
  45. package/core/audits/insights/dom-size-insight.js +1 -1
  46. package/core/audits/insights/duplicated-javascript-insight.d.ts +13 -0
  47. package/core/audits/insights/duplicated-javascript-insight.js +36 -9
  48. package/core/audits/insights/font-display-insight.js +1 -1
  49. package/core/audits/insights/forced-reflow-insight.js +1 -1
  50. package/core/audits/insights/image-delivery-insight.js +1 -1
  51. package/core/audits/insights/insight-audit.d.ts +11 -9
  52. package/core/audits/insights/insight-audit.js +37 -35
  53. package/core/audits/insights/interaction-to-next-paint-insight.js +3 -6
  54. package/core/audits/insights/lcp-discovery-insight.js +6 -3
  55. package/core/audits/insights/lcp-phases-insight.js +3 -6
  56. package/core/audits/insights/legacy-javascript-insight.d.ts +23 -0
  57. package/core/audits/insights/legacy-javascript-insight.js +101 -0
  58. package/core/audits/insights/modern-http-insight.d.ts +11 -0
  59. package/core/audits/insights/modern-http-insight.js +53 -0
  60. package/core/audits/insights/network-dependency-tree-insight.d.ts +5 -0
  61. package/core/audits/insights/network-dependency-tree-insight.js +35 -13
  62. package/core/audits/insights/render-blocking-insight.js +1 -1
  63. package/core/audits/insights/slow-css-selector-insight.js +3 -1
  64. package/core/audits/insights/third-parties-insight.d.ts +3 -3
  65. package/core/audits/insights/third-parties-insight.js +28 -23
  66. package/core/audits/insights/viewport-insight.js +1 -1
  67. package/core/audits/is-on-https.js +2 -2
  68. package/core/audits/largest-contentful-paint-element.js +3 -3
  69. package/core/audits/layout-shifts.js +4 -4
  70. package/core/audits/lcp-lazy-loaded.js +1 -1
  71. package/core/audits/long-tasks.d.ts +1 -1
  72. package/core/audits/long-tasks.js +3 -3
  73. package/core/audits/main-thread-tasks.js +2 -2
  74. package/core/audits/mainthread-work-breakdown.d.ts +1 -1
  75. package/core/audits/mainthread-work-breakdown.js +2 -2
  76. package/core/audits/manual/manual-audit.d.ts +1 -1
  77. package/core/audits/metrics/cumulative-layout-shift.d.ts +1 -1
  78. package/core/audits/metrics/cumulative-layout-shift.js +2 -2
  79. package/core/audits/metrics/first-contentful-paint.js +3 -3
  80. package/core/audits/metrics/first-meaningful-paint.js +1 -1
  81. package/core/audits/metrics/interaction-to-next-paint.d.ts +1 -1
  82. package/core/audits/metrics/interaction-to-next-paint.js +2 -2
  83. package/core/audits/metrics/interactive.js +3 -3
  84. package/core/audits/metrics/largest-contentful-paint.js +3 -3
  85. package/core/audits/metrics/max-potential-fid.d.ts +1 -1
  86. package/core/audits/metrics/max-potential-fid.js +3 -3
  87. package/core/audits/metrics/speed-index.js +3 -3
  88. package/core/audits/metrics/total-blocking-time.js +3 -3
  89. package/core/audits/metrics.js +3 -3
  90. package/core/audits/network-requests.js +2 -2
  91. package/core/audits/network-rtt.js +2 -2
  92. package/core/audits/network-server-latency.js +2 -2
  93. package/core/audits/origin-isolation.js +2 -2
  94. package/core/audits/predictive-perf.js +3 -3
  95. package/core/audits/preload-fonts.js +2 -2
  96. package/core/audits/prioritize-lcp-image.js +3 -3
  97. package/core/audits/redirects.js +3 -3
  98. package/core/audits/resource-summary.js +2 -2
  99. package/core/audits/screenshot-thumbnails.js +2 -2
  100. package/core/audits/script-treemap-data.js +32 -2
  101. package/core/audits/seo/canonical.js +2 -2
  102. package/core/audits/seo/http-status-code.js +2 -2
  103. package/core/audits/seo/is-crawlable.js +2 -2
  104. package/core/audits/server-response-time.js +2 -2
  105. package/core/audits/third-party-cookies.js +1 -1
  106. package/core/audits/third-party-facades.js +2 -2
  107. package/core/audits/third-party-summary.js +2 -2
  108. package/core/audits/user-timings.d.ts +1 -1
  109. package/core/audits/user-timings.js +2 -2
  110. package/core/audits/uses-rel-preconnect.js +3 -3
  111. package/core/audits/uses-rel-preload.js +3 -3
  112. package/core/audits/valid-source-maps.js +2 -2
  113. package/core/audits/work-during-interaction.js +3 -3
  114. package/core/computed/critical-request-chains.d.ts +1 -1
  115. package/core/computed/document-urls.d.ts +4 -1
  116. package/core/computed/entity-classification.d.ts +1 -1
  117. package/core/computed/image-records.d.ts +1 -1
  118. package/core/computed/js-bundles.d.ts +1 -1
  119. package/core/computed/lcp-image-record.d.ts +1 -1
  120. package/core/computed/load-simulator.d.ts +1 -1
  121. package/core/computed/main-resource.d.ts +1 -1
  122. package/core/computed/main-thread-tasks.d.ts +1 -1
  123. package/core/computed/metrics/cumulative-layout-shift.d.ts +10 -1
  124. package/core/computed/metrics/first-contentful-paint-all-frames.d.ts +1 -1
  125. package/core/computed/metrics/first-contentful-paint.d.ts +1 -1
  126. package/core/computed/metrics/interactive.d.ts +1 -1
  127. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +1 -1
  128. package/core/computed/metrics/lantern-interactive.d.ts +1 -1
  129. package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +1 -1
  130. package/core/computed/metrics/lantern-max-potential-fid.d.ts +1 -1
  131. package/core/computed/metrics/lantern-speed-index.d.ts +1 -1
  132. package/core/computed/metrics/lantern-total-blocking-time.d.ts +1 -1
  133. package/core/computed/metrics/largest-contentful-paint-all-frames.d.ts +1 -1
  134. package/core/computed/metrics/largest-contentful-paint.d.ts +1 -1
  135. package/core/computed/metrics/lcp-breakdown.d.ts +5 -1
  136. package/core/computed/metrics/max-potential-fid.d.ts +1 -1
  137. package/core/computed/metrics/responsiveness.d.ts +1 -1
  138. package/core/computed/metrics/speed-index.d.ts +1 -1
  139. package/core/computed/metrics/time-to-first-byte.d.ts +1 -1
  140. package/core/computed/metrics/timing-summary.d.ts +4 -1
  141. package/core/computed/metrics/total-blocking-time.d.ts +1 -1
  142. package/core/computed/module-duplication.d.ts +5 -1
  143. package/core/computed/navigation-insights.d.ts +1 -1
  144. package/core/computed/network-analysis.d.ts +1 -1
  145. package/core/computed/network-records.d.ts +1 -1
  146. package/core/computed/page-dependency-graph.d.ts +1 -1
  147. package/core/computed/processed-navigation.d.ts +1 -1
  148. package/core/computed/processed-trace.d.ts +1 -1
  149. package/core/computed/resource-summary.d.ts +1 -1
  150. package/core/computed/screenshots.d.ts +4 -1
  151. package/core/computed/speedline.d.ts +1 -1
  152. package/core/computed/tbt-impact-tasks.d.ts +1 -1
  153. package/core/computed/trace-engine-result.d.ts +1 -1
  154. package/core/computed/unused-css.d.ts +1 -1
  155. package/core/computed/unused-javascript-summary.d.ts +1 -1
  156. package/core/computed/user-timings.d.ts +1 -1
  157. package/core/computed/viewport-meta.d.ts +1 -1
  158. package/core/config/default-config.js +11 -6
  159. package/core/config/experimental-config.js +3 -2
  160. package/core/gather/driver/network-monitor.d.ts +1 -1
  161. package/core/gather/driver/network.d.ts +1 -1
  162. package/core/gather/driver.d.ts +1 -1
  163. package/core/gather/gatherers/devtools-log.d.ts +1 -1
  164. package/core/gather/gatherers/dobetterweb/optimized-images.js +3 -10
  165. package/core/gather/gatherers/trace-elements.d.ts +3 -4
  166. package/core/gather/gatherers/trace-elements.js +6 -8
  167. package/core/gather/gatherers/trace.js +0 -3
  168. package/core/gather/navigation-runner.d.ts +1 -1
  169. package/core/gather/snapshot-runner.d.ts +1 -1
  170. package/core/gather/timespan-runner.d.ts +1 -1
  171. package/core/index.d.ts +6 -6
  172. package/core/lib/asset-saver.d.ts +1 -1
  173. package/core/lib/asset-saver.js +1 -1
  174. package/core/lib/bf-cache-strings.js +2 -0
  175. package/core/lib/deprecations-strings.d.ts +71 -76
  176. package/core/lib/deprecations-strings.js +22 -25
  177. package/core/lib/i18n/README.md +1 -1
  178. package/core/lib/i18n/i18n.d.ts +1 -1
  179. package/core/lib/i18n/i18n.js +4 -4
  180. package/core/lib/legacy-javascript/legacy-javascript.js +4 -11
  181. package/core/lib/legacy-javascript/package.json +14 -0
  182. package/core/lib/page-functions.d.ts +1 -1
  183. package/core/lib/stack-packs.js +1 -1
  184. package/core/lib/tracehouse/cpu-profile-model.d.ts +1 -1
  185. package/core/lib/tracehouse/main-thread-tasks.d.ts +3 -3
  186. package/core/lib/tracehouse/main-thread-tasks.js +8 -0
  187. package/core/lib/tracehouse/trace-processor.d.ts +1 -1
  188. package/core/lib/traces/metric-trace-events.d.ts +2 -2
  189. package/core/runner.js +2 -1
  190. package/core/scoring.d.ts +539 -3
  191. package/core/user-flow.d.ts +6 -6
  192. package/dist/report/bundle.esm.js +89 -19
  193. package/dist/report/flow.js +92 -22
  194. package/dist/report/standalone.js +89 -19
  195. package/flow-report/src/i18n/i18n.d.ts +6 -2
  196. package/package.json +17 -16
  197. package/readme.md +1 -1
  198. package/report/README.md +1 -1
  199. package/report/assets/styles.css +76 -9
  200. package/report/assets/templates.html +3 -1
  201. package/report/clients/standalone.js +6 -4
  202. package/report/renderer/category-renderer.d.ts +2 -2
  203. package/report/renderer/components.js +3 -9
  204. package/report/renderer/crc-details-renderer.d.ts +13 -31
  205. package/report/renderer/crc-details-renderer.js +49 -47
  206. package/report/renderer/details-renderer.d.ts +1 -1
  207. package/report/renderer/details-renderer.js +6 -0
  208. package/report/renderer/dom.js +7 -0
  209. package/report/renderer/features-util.d.ts +1 -1
  210. package/report/renderer/performance-category-renderer.d.ts +28 -2
  211. package/report/renderer/performance-category-renderer.js +121 -3
  212. package/report/renderer/report-utils.d.ts +3 -1
  213. package/report/renderer/report-utils.js +11 -4
  214. package/report/renderer/topbar-features.js +1 -9
  215. package/shared/localization/format.d.ts +1 -1
  216. package/shared/localization/locales/ar-XB.json +66 -6
  217. package/shared/localization/locales/ar.json +66 -6
  218. package/shared/localization/locales/bg.json +66 -6
  219. package/shared/localization/locales/ca.json +66 -6
  220. package/shared/localization/locales/cs.json +66 -6
  221. package/shared/localization/locales/da.json +66 -6
  222. package/shared/localization/locales/de.json +66 -6
  223. package/shared/localization/locales/el.json +66 -6
  224. package/shared/localization/locales/en-GB.json +66 -6
  225. package/shared/localization/locales/en-US.json +87 -36
  226. package/shared/localization/locales/en-XA.json +0 -6
  227. package/shared/localization/locales/en-XL.json +87 -36
  228. package/shared/localization/locales/es-419.json +66 -6
  229. package/shared/localization/locales/es.json +67 -7
  230. package/shared/localization/locales/fi.json +66 -6
  231. package/shared/localization/locales/fil.json +66 -6
  232. package/shared/localization/locales/fr.json +66 -6
  233. package/shared/localization/locales/he.json +66 -6
  234. package/shared/localization/locales/hi.json +66 -6
  235. package/shared/localization/locales/hr.json +66 -6
  236. package/shared/localization/locales/hu.json +66 -6
  237. package/shared/localization/locales/id.json +66 -6
  238. package/shared/localization/locales/it.json +67 -7
  239. package/shared/localization/locales/ja.json +66 -6
  240. package/shared/localization/locales/ko.json +66 -6
  241. package/shared/localization/locales/lt.json +66 -6
  242. package/shared/localization/locales/lv.json +66 -6
  243. package/shared/localization/locales/nl.json +66 -6
  244. package/shared/localization/locales/no.json +66 -6
  245. package/shared/localization/locales/pl.json +66 -6
  246. package/shared/localization/locales/pt-PT.json +66 -6
  247. package/shared/localization/locales/pt.json +67 -7
  248. package/shared/localization/locales/ro.json +66 -6
  249. package/shared/localization/locales/ru.json +66 -6
  250. package/shared/localization/locales/sk.json +66 -6
  251. package/shared/localization/locales/sl.json +66 -6
  252. package/shared/localization/locales/sr-Latn.json +66 -6
  253. package/shared/localization/locales/sr.json +66 -6
  254. package/shared/localization/locales/sv.json +66 -6
  255. package/shared/localization/locales/ta.json +69 -9
  256. package/shared/localization/locales/te.json +66 -6
  257. package/shared/localization/locales/th.json +66 -6
  258. package/shared/localization/locales/tr.json +66 -6
  259. package/shared/localization/locales/uk.json +66 -6
  260. package/shared/localization/locales/vi.json +66 -6
  261. package/shared/localization/locales/zh-HK.json +67 -7
  262. package/shared/localization/locales/zh-TW.json +67 -7
  263. package/shared/localization/locales/zh.json +66 -6
  264. package/tsconfig-base.json +1 -1
  265. package/tsconfig.json +1 -0
  266. package/types/artifacts.d.ts +0 -2
  267. package/types/internal/node.d.ts +0 -16
  268. package/types/lhr/audit-details.d.ts +33 -1
  269. package/types/lhr/treemap.d.ts +5 -1
@@ -14,46 +14,26 @@ import {Globals} from './report-globals.js';
14
14
  /** @typedef {import('./dom.js').DOM} DOM */
15
15
  /** @typedef {import('./details-renderer.js').DetailsRenderer} DetailsRenderer */
16
16
  /**
17
- * @typedef CRCSegment
18
- * @property {LH.Audit.Details.SimpleCriticalRequestNode[string]} node
17
+ * @typedef NetworkSegment
18
+ * @property {LH.Audit.Details.SimpleCriticalRequestNode[string]|LH.Audit.Details.NetworkNode[string]} node
19
19
  * @property {boolean} isLastChild
20
20
  * @property {boolean} hasChildren
21
- * @property {number} startTime
22
- * @property {number} transferSize
23
21
  * @property {boolean[]} treeMarkers
24
22
  */
25
23
 
26
24
  class CriticalRequestChainRenderer {
27
- /**
28
- * Create render context for critical-request-chain tree display.
29
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
30
- * @return {{tree: LH.Audit.Details.SimpleCriticalRequestNode, startTime: number, transferSize: number}}
31
- */
32
- static initTree(tree) {
33
- let startTime = 0;
34
- const rootNodes = Object.keys(tree);
35
- if (rootNodes.length > 0) {
36
- const node = tree[rootNodes[0]];
37
- startTime = node.request.startTime;
38
- }
39
-
40
- return {tree, startTime, transferSize: 0};
41
- }
42
-
43
25
  /**
44
26
  * Helper to create context for each critical-request-chain node based on its
45
27
  * parent. Calculates if this node is the last child, whether it has any
46
28
  * children itself and what the tree looks like all the way back up to the root,
47
29
  * so the tree markers can be drawn correctly.
48
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} parent
30
+ * @param {LH.Audit.Details.SimpleCriticalRequestNode|LH.Audit.Details.NetworkNode} parent
49
31
  * @param {string} id
50
- * @param {number} startTime
51
- * @param {number} transferSize
52
32
  * @param {Array<boolean>=} treeMarkers
53
33
  * @param {boolean=} parentIsLastChild
54
- * @return {CRCSegment}
34
+ * @return {NetworkSegment}
55
35
  */
56
- static createSegment(parent, id, startTime, transferSize, treeMarkers, parentIsLastChild) {
36
+ static createSegment(parent, id, treeMarkers, parentIsLastChild) {
57
37
  const node = parent[id];
58
38
  const siblings = Object.keys(parent);
59
39
  const isLastChild = siblings.indexOf(id) === (siblings.length - 1);
@@ -71,8 +51,6 @@ class CriticalRequestChainRenderer {
71
51
  node,
72
52
  isLastChild,
73
53
  hasChildren,
74
- startTime,
75
- transferSize: transferSize + node.request.transferSize,
76
54
  treeMarkers: newTreeMarkers,
77
55
  };
78
56
  }
@@ -80,15 +58,42 @@ class CriticalRequestChainRenderer {
80
58
  /**
81
59
  * Creates the DOM for a tree segment.
82
60
  * @param {DOM} dom
83
- * @param {CRCSegment} segment
61
+ * @param {NetworkSegment} segment
84
62
  * @param {DetailsRenderer} detailsRenderer
85
63
  * @return {Node}
86
64
  */
87
65
  static createChainNode(dom, segment, detailsRenderer) {
88
66
  const chainEl = dom.createComponent('crcChain');
89
67
 
68
+ // This can be either the duration or the chain end time depending on the detail type.
69
+ let nodeTiming;
70
+ let nodeTransferSize;
71
+ let nodeUrl;
72
+ let alwaysShowTiming;
73
+ let highlightLongest;
74
+
75
+ // `segment.node.request` indicates that this is a legacy critical request chain details node.
76
+ // For historical reasons, the legacy CRC will only show details for leaf nodes and will show
77
+ // the leaf node request duration rather than the duration of the entire tree.
78
+ if ('request' in segment.node) {
79
+ nodeTransferSize = segment.node.request.transferSize;
80
+ nodeUrl = segment.node.request.url;
81
+ nodeTiming = (segment.node.request.endTime - segment.node.request.startTime) * 1000;
82
+ alwaysShowTiming = false;
83
+ } else {
84
+ nodeTransferSize = segment.node.transferSize;
85
+ nodeUrl = segment.node.url;
86
+ nodeTiming = segment.node.navStartToEndTime;
87
+ alwaysShowTiming = true;
88
+ highlightLongest = segment.node.isLongest;
89
+ }
90
+
90
91
  // Hovering over request shows full URL.
91
- dom.find('.lh-crc-node', chainEl).setAttribute('title', segment.node.request.url);
92
+ const nodeEl = dom.find('.lh-crc-node', chainEl);
93
+ nodeEl.setAttribute('title', nodeUrl);
94
+ if (highlightLongest) {
95
+ nodeEl.classList.add('lh-crc-node__longest');
96
+ }
92
97
 
93
98
  const treeMarkeEl = dom.find('.lh-crc-node__tree-marker', chainEl);
94
99
 
@@ -117,18 +122,17 @@ class CriticalRequestChainRenderer {
117
122
  );
118
123
 
119
124
  // Fill in url, host, and request size information.
120
- const url = segment.node.request.url;
121
- const linkEl = detailsRenderer.renderTextURL(url);
125
+ const linkEl = detailsRenderer.renderTextURL(nodeUrl);
122
126
  const treevalEl = dom.find('.lh-crc-node__tree-value', chainEl);
123
127
  treevalEl.append(linkEl);
124
128
 
125
- if (!segment.hasChildren) {
126
- const {startTime, endTime, transferSize} = segment.node.request;
129
+
130
+ if (!segment.hasChildren || alwaysShowTiming) {
127
131
  const span = dom.createElement('span', 'lh-crc-node__chain-duration');
128
132
  span.textContent =
129
- ' - ' + Globals.i18n.formatMilliseconds((endTime - startTime) * 1000) + ', ';
130
- const span2 = dom.createElement('span', 'lh-crc-node__chain-duration');
131
- span2.textContent = Globals.i18n.formatBytesToKiB(transferSize, 0.01);
133
+ ' - ' + Globals.i18n.formatMilliseconds(nodeTiming) + ', ';
134
+ const span2 = dom.createElement('span', 'lh-crc-node__chain-size');
135
+ span2.textContent = Globals.i18n.formatBytesToKiB(nodeTransferSize, 0.01);
132
136
 
133
137
  treevalEl.append(span, span2);
134
138
  }
@@ -139,26 +143,24 @@ class CriticalRequestChainRenderer {
139
143
  /**
140
144
  * Recursively builds a tree from segments.
141
145
  * @param {DOM} dom
142
- * @param {DocumentFragment} tmpl
143
- * @param {CRCSegment} segment
146
+ * @param {NetworkSegment} segment
144
147
  * @param {Element} elem Parent element.
145
- * @param {LH.Audit.Details.CriticalRequestChain} details
146
148
  * @param {DetailsRenderer} detailsRenderer
147
149
  */
148
- static buildTree(dom, tmpl, segment, elem, details, detailsRenderer) {
150
+ static buildTree(dom, segment, elem, detailsRenderer) {
149
151
  elem.append(CRCRenderer.createChainNode(dom, segment, detailsRenderer));
150
152
  if (segment.node.children) {
151
153
  for (const key of Object.keys(segment.node.children)) {
152
154
  const childSegment = CRCRenderer.createSegment(segment.node.children, key,
153
- segment.startTime, segment.transferSize, segment.treeMarkers, segment.isLastChild);
154
- CRCRenderer.buildTree(dom, tmpl, childSegment, elem, details, detailsRenderer);
155
+ segment.treeMarkers, segment.isLastChild);
156
+ CRCRenderer.buildTree(dom, childSegment, elem, detailsRenderer);
155
157
  }
156
158
  }
157
159
  }
158
160
 
159
161
  /**
160
162
  * @param {DOM} dom
161
- * @param {LH.Audit.Details.CriticalRequestChain} details
163
+ * @param {LH.Audit.Details.CriticalRequestChain|LH.Audit.Details.NetworkTree} details
162
164
  * @param {DetailsRenderer} detailsRenderer
163
165
  * @return {Element}
164
166
  */
@@ -174,10 +176,10 @@ class CriticalRequestChainRenderer {
174
176
  Globals.i18n.formatMilliseconds(details.longestChain.duration);
175
177
 
176
178
  // Construct visual tree.
177
- const root = CRCRenderer.initTree(details.chains);
178
- for (const key of Object.keys(root.tree)) {
179
- const segment = CRCRenderer.createSegment(root.tree, key, root.startTime, root.transferSize);
180
- CRCRenderer.buildTree(dom, tmpl, segment, containerEl, details, detailsRenderer);
179
+ const tree = details.chains;
180
+ for (const key of Object.keys(tree)) {
181
+ const segment = CRCRenderer.createSegment(tree, key);
182
+ CRCRenderer.buildTree(dom, segment, containerEl, detailsRenderer);
181
183
  }
182
184
 
183
185
  return dom.find('.lh-crc-container', tmpl);
@@ -6,7 +6,7 @@ export class DetailsRenderer {
6
6
  constructor(dom: DOM, options?: {
7
7
  fullPageScreenshot?: LH.Result.FullPageScreenshot;
8
8
  entities?: LH.Result.Entities;
9
- } | undefined);
9
+ });
10
10
  _dom: import("./dom.js").DOM;
11
11
  _fullPageScreenshot: import("../../types/lhr/lhr.js").default.FullPageScreenshot | undefined;
12
12
  _entities: import("../../types/lhr/lhr.js").default.Entities | undefined;
@@ -50,6 +50,7 @@ export class DetailsRenderer {
50
50
  case 'table':
51
51
  case 'opportunity':
52
52
  return this._renderTable(details);
53
+ case 'network-tree':
53
54
  case 'criticalrequestchain':
54
55
  return CriticalRequestChainRenderer.render(this._dom, details, this);
55
56
 
@@ -547,6 +548,11 @@ export class DetailsRenderer {
547
548
  const listContainer = this._dom.createElement('div', 'lh-list');
548
549
 
549
550
  details.items.forEach(item => {
551
+ if (item.type === 'node') {
552
+ listContainer.append(this.renderNode(item));
553
+ return;
554
+ }
555
+
550
556
  const listItem = this.render(item);
551
557
  if (!listItem) return;
552
558
  listContainer.append(listItem);
@@ -345,6 +345,13 @@ export class DOM {
345
345
  const parent = section.parentNode;
346
346
  if (!parent) return;
347
347
 
348
+ // LH Report enforces that only the first instance of a component will include the styles.
349
+ // If these single-instance happen to be in `section` then they could be lost once `section` is
350
+ // removed from the DOM.
351
+ // Therefore we need to transfer any styles that only exist in `section` into `newSection`.
352
+ const stylesToTransfer = section.querySelectorAll('style');
353
+ newSection.append(...stylesToTransfer);
354
+
348
355
  parent.insertBefore(newSection, section);
349
356
  section.remove();
350
357
  }
@@ -8,6 +8,6 @@
8
8
  * @param {DOM} dom
9
9
  * @param {boolean} [force]
10
10
  */
11
- export function toggleDarkTheme(dom: DOM, force?: boolean | undefined): void;
11
+ export function toggleDarkTheme(dom: DOM, force?: boolean): void;
12
12
  export type DOM = import("./dom.js").DOM;
13
13
  //# sourceMappingURL=features-util.d.ts.map
@@ -1,4 +1,9 @@
1
+ /**
2
+ * @typedef {('DEFAULT'|'AUDITS'|'INSIGHTS')} InsightsExperimentState
3
+ */
1
4
  export class PerformanceCategoryRenderer extends CategoryRenderer {
5
+ /** @type InsightsExperimentState*/
6
+ _memoryInsightToggleState: InsightsExperimentState;
2
7
  /**
3
8
  * @param {LH.ReportResult.AuditRef} audit
4
9
  * @return {!Element}
@@ -23,6 +28,26 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
23
28
  overallImpact: number;
24
29
  overallLinearImpact: number;
25
30
  };
31
+ /**
32
+ * @param {InsightsExperimentState} newState
33
+ **/
34
+ _persistInsightToggleToStorage(newState: InsightsExperimentState): void;
35
+ /**
36
+ * @returns {InsightsExperimentState}
37
+ **/
38
+ _getInsightToggleState(): InsightsExperimentState;
39
+ /**
40
+ * @returns {InsightsExperimentState}
41
+ **/
42
+ _getRawInsightToggleState(): InsightsExperimentState;
43
+ /**
44
+ * @param {HTMLButtonElement} button
45
+ **/
46
+ _setInsightToggleButtonText(button: HTMLButtonElement): void;
47
+ /**
48
+ * @param {HTMLElement} element
49
+ */
50
+ _renderInsightsToggle(element: HTMLElement): void;
26
51
  /**
27
52
  * @param {LH.ReportResult.Category} category
28
53
  * @param {Object<string, LH.Result.ReportGroup>} groups
@@ -31,7 +56,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
31
56
  * @override
32
57
  */
33
58
  override render(category: LH.ReportResult.Category, groups: {
34
- [x: string]: LH.Result.ReportGroup;
59
+ [x: string]: import("../../types/lhr/lhr.js").default.ReportGroup;
35
60
  }, options?: {
36
61
  gatherMode: LH.Result.GatherMode;
37
62
  } | undefined): Element;
@@ -43,7 +68,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
43
68
  * @return {Element|null}
44
69
  */
45
70
  renderFilterableSection(category: LH.ReportResult.Category, groups: {
46
- [x: string]: LH.Result.ReportGroup;
71
+ [x: string]: import("../../types/lhr/lhr.js").default.ReportGroup;
47
72
  }, groupNames: string[], metricAudits: LH.ReportResult.AuditRef[]): Element | null;
48
73
  /**
49
74
  * Render the control to filter the audits by metric. The filtering is done at runtime by CSS only
@@ -54,5 +79,6 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
54
79
  renderMetricAuditFilter(filterableMetrics: LH.ReportResult.AuditRef[], categoryEl: HTMLDivElement, onFilterChange: (acronym: string) => void): void;
55
80
  }
56
81
  export type DOM = import("./dom.js").DOM;
82
+ export type InsightsExperimentState = ("DEFAULT" | "AUDITS" | "INSIGHTS");
57
83
  import { CategoryRenderer } from './category-renderer.js';
58
84
  //# sourceMappingURL=performance-category-renderer.d.ts.map
@@ -12,7 +12,16 @@ import {Globals} from './report-globals.js';
12
12
  import {Util} from '../../shared/util.js';
13
13
  import {createGauge, updateGauge} from './explodey-gauge.js';
14
14
 
15
+ const LOCAL_STORAGE_INSIGHTS_KEY = '__lh__insights_audits_toggle_state';
16
+
17
+ /**
18
+ * @typedef {('DEFAULT'|'AUDITS'|'INSIGHTS')} InsightsExperimentState
19
+ */
20
+
15
21
  export class PerformanceCategoryRenderer extends CategoryRenderer {
22
+ /** @type InsightsExperimentState*/
23
+ _memoryInsightToggleState = 'DEFAULT';
24
+
16
25
  /**
17
26
  * @param {LH.ReportResult.AuditRef} audit
18
27
  * @return {!Element}
@@ -134,6 +143,85 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
134
143
  return {overallImpact, overallLinearImpact};
135
144
  }
136
145
 
146
+ /**
147
+ * @param {InsightsExperimentState} newState
148
+ **/
149
+ _persistInsightToggleToStorage(newState) {
150
+ try {
151
+ window.localStorage.setItem(LOCAL_STORAGE_INSIGHTS_KEY, newState);
152
+ } finally {
153
+ this._memoryInsightToggleState = newState;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @returns {InsightsExperimentState}
159
+ **/
160
+ _getInsightToggleState() {
161
+ let state = this._getRawInsightToggleState();
162
+ if (state === 'DEFAULT') state = 'AUDITS';
163
+ return state;
164
+ }
165
+
166
+ /**
167
+ * @returns {InsightsExperimentState}
168
+ **/
169
+ _getRawInsightToggleState() {
170
+ try {
171
+ const fromStorage = window.localStorage.getItem(LOCAL_STORAGE_INSIGHTS_KEY);
172
+ if (fromStorage === 'AUDITS' || fromStorage === 'INSIGHTS') {
173
+ return fromStorage;
174
+ }
175
+ } catch {
176
+ return this._memoryInsightToggleState;
177
+ }
178
+ return 'DEFAULT';
179
+ }
180
+
181
+ /**
182
+ * @param {HTMLButtonElement} button
183
+ **/
184
+ _setInsightToggleButtonText(button) {
185
+ const state = this._getInsightToggleState();
186
+ button.innerText =
187
+ state === 'AUDITS' ? Globals.strings.tryInsights : Globals.strings.goBackToAudits;
188
+ }
189
+
190
+ /**
191
+ * @param {HTMLElement} element
192
+ */
193
+ _renderInsightsToggle(element) {
194
+ // Insights / Audits toggle.
195
+ const container = this.dom.createChildOf(element, 'div', 'lh-perf-insights-toggle');
196
+ const textSpan = this.dom.createChildOf(container, 'span', 'lh-perf-toggle-text');
197
+ const icon = this.dom.createElement('span', 'lh-perf-insights-icon');
198
+ icon.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M18 13V11H22V13H18ZM19.2 20L16 17.6L17.2 16L20.4 18.4L19.2 20ZM17.2 8L16 6.4L19.2 4L20.4 5.6L17.2 8ZM5 19V15H4C3.45 15 2.975 14.8083 2.575 14.425C2.19167 14.025 2 13.55 2 13V11C2 10.45 2.19167 9.98333 2.575 9.6C2.975 9.2 3.45 9 4 9H8L13 6V18L8 15H7V19H5ZM11 14.45V9.55L8.55 11H4V13H8.55L11 14.45ZM14 15.35V8.65C14.45 9.05 14.8083 9.54167 15.075 10.125C15.3583 10.6917 15.5 11.3167 15.5 12C15.5 12.6833 15.3583 13.3167 15.075 13.9C14.8083 14.4667 14.45 14.95 14 15.35Z"/></svg>';
199
+ textSpan.appendChild(icon);
200
+ textSpan.appendChild(this.dom.convertMarkdownLinkSnippets(Globals.strings.insightsNotice));
201
+
202
+ const buttonClasses = 'lh-button lh-button-insight-toggle';
203
+ const button = this.dom.createChildOf(container, 'button', buttonClasses);
204
+ this._setInsightToggleButtonText(button);
205
+
206
+ button.addEventListener('click', event => {
207
+ event.preventDefault();
208
+ const swappableSection = this.dom.maybeFind('.lh-perf-audits--swappable');
209
+ if (swappableSection) {
210
+ this.dom.swapSectionIfPossible(swappableSection);
211
+ }
212
+ const currentState = this._getInsightToggleState();
213
+ const newState = currentState === 'AUDITS' ? 'INSIGHTS' : 'AUDITS';
214
+ this.dom.fireEventOn('lh-analytics', this.dom.document(), {
215
+ name: 'toggle_insights',
216
+ data: {newState},
217
+ });
218
+ this._persistInsightToggleToStorage(newState);
219
+ this._setInsightToggleButtonText(button);
220
+ });
221
+
222
+ container.appendChild(button);
223
+ }
224
+
137
225
  /**
138
226
  * @param {LH.ReportResult.Category} category
139
227
  * @param {Object<string, LH.Result.ReportGroup>} groups
@@ -201,6 +289,8 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
201
289
  filmstripEl && timelineEl.append(filmstripEl);
202
290
  }
203
291
 
292
+ this._renderInsightsToggle(element);
293
+
204
294
  const legacyAuditsSection =
205
295
  this.renderFilterableSection(category, groups, ['diagnostics'], metricAudits);
206
296
  legacyAuditsSection?.classList.add('lh-perf-audits--swappable', 'lh-perf-audits--legacy');
@@ -220,6 +310,23 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
220
310
  this.dom.registerSwappableSections(legacyAuditsSection, experimentalInsightsSection);
221
311
  }
222
312
  }
313
+ // Deal with the user loading the report and having toggled to Insights
314
+ // which is now stored in local storage. Put in a rAF otherwise this code
315
+ // runs before the DOM is created.
316
+ if (this._getInsightToggleState() === 'INSIGHTS') {
317
+ requestAnimationFrame(() => {
318
+ const swappableSection = this.dom.maybeFind('.lh-perf-audits--swappable');
319
+ if (swappableSection) {
320
+ this.dom.swapSectionIfPossible(swappableSection);
321
+ }
322
+ });
323
+ }
324
+
325
+ // Log the initial state.
326
+ this.dom.fireEventOn('lh-analytics', this.dom.document(), {
327
+ name: 'initial_insights_state',
328
+ data: {state: this._getRawInsightToggleState()},
329
+ });
223
330
 
224
331
  const isNavigationMode = !options || options?.gatherMode === 'navigation';
225
332
  if (isNavigationMode && category.score !== null) {
@@ -246,8 +353,19 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
246
353
  /** @type {Set<string>} */
247
354
  const replacedAuditIds = new Set();
248
355
 
356
+ /**
357
+ * This exists to temporarily allow showing insights - which are in the hidden
358
+ * group by default - when using the insights toggle.
359
+ * See https://github.com/GoogleChrome/lighthouse/pull/16418 for motivation.
360
+ *
361
+ * @param {LH.ReportResult.AuditRef} auditRef
362
+ */
363
+ const getGroup = (auditRef) => {
364
+ return auditRef.id.endsWith('-insight') ? 'insights' : auditRef.group ?? '';
365
+ };
366
+
249
367
  const allGroupAudits =
250
- category.auditRefs.filter(audit => audit.group && groupNames.includes(audit.group));
368
+ category.auditRefs.filter(audit => groupNames.includes(getGroup(audit)));
251
369
  for (const auditRef of allGroupAudits) {
252
370
  auditRef.result.replacesAudits?.forEach(replacedAuditId => {
253
371
  replacedAuditIds.add(replacedAuditId);
@@ -327,7 +445,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
327
445
  for (const audit of filterableAudits) {
328
446
  if (!audit.auditRef.group) continue;
329
447
 
330
- const groupEls = groupElsMap[audit.auditRef.group];
448
+ const groupEls = groupElsMap[getGroup(audit.auditRef)];
331
449
  if (!groupEls) continue;
332
450
 
333
451
  const [groupEl, footerEl] = groupEls;
@@ -355,7 +473,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
355
473
  refreshFilteredAudits('All');
356
474
 
357
475
  for (const groupName of groupNames) {
358
- if (filterableAudits.some(auditRef => auditRef.auditRef.group === groupName)) {
476
+ if (filterableAudits.some(audit => getGroup(audit.auditRef) === groupName)) {
359
477
  const groupEls = groupElsMap[groupName];
360
478
  if (!groupEls) continue;
361
479
  element.append(groupEls[0]);
@@ -108,7 +108,6 @@ export namespace UIStrings {
108
108
  let dropdownViewer: string;
109
109
  let dropdownSaveGist: string;
110
110
  let dropdownDarkTheme: string;
111
- let dropdownInsightsToggle: string;
112
111
  let dropdownViewUnthrottledTrace: string;
113
112
  let runtimeSettingsDevice: string;
114
113
  let runtimeSettingsNetworkThrottling: string;
@@ -137,5 +136,8 @@ export namespace UIStrings {
137
136
  let firstPartyChipLabel: string;
138
137
  let openInANewTabTooltip: string;
139
138
  let unattributable: string;
139
+ let insightsNotice: string;
140
+ let tryInsights: string;
141
+ let goBackToAudits: string;
140
142
  }
141
143
  //# sourceMappingURL=report-utils.d.ts.map
@@ -67,13 +67,15 @@ class ReportUtils {
67
67
 
68
68
  // attach the stackpacks to the auditRef object
69
69
  if (clone.stackPacks) {
70
+ const ids = [auditRef.id, ...auditRef.result.replacesAudits ?? []];
70
71
  clone.stackPacks.forEach(pack => {
71
- if (pack.descriptions[auditRef.id]) {
72
+ const id = ids.find(id => pack.descriptions[id]);
73
+ if (id && pack.descriptions[id]) {
72
74
  auditRef.stackPacks = auditRef.stackPacks || [];
73
75
  auditRef.stackPacks.push({
74
76
  title: pack.title,
75
77
  iconDataURL: pack.iconDataURL,
76
- description: pack.descriptions[auditRef.id],
78
+ description: pack.descriptions[id],
77
79
  });
78
80
  }
79
81
  });
@@ -418,8 +420,6 @@ const UIStrings = {
418
420
  dropdownSaveGist: 'Save as Gist',
419
421
  /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */
420
422
  dropdownDarkTheme: 'Toggle Dark Theme',
421
- /** Option in a dropdown menu that toggles the type of performance insights displayed. */
422
- dropdownInsightsToggle: 'Toggle experimental insights',
423
423
  /** Option in a dropdown menu that opens the trace of the page without throttling. "Unthrottled" can be replaced with "Original". */
424
424
  dropdownViewUnthrottledTrace: 'View Unthrottled Trace',
425
425
 
@@ -481,6 +481,13 @@ const UIStrings = {
481
481
  openInANewTabTooltip: 'Open in a new tab',
482
482
  /** Generic category name for all resources that could not be attributed to a 1st or 3rd party entity. */
483
483
  unattributable: 'Unattributable',
484
+
485
+ /** Notice about upcoming planned changes to Lighthouse, to replace most performance audits with a new set of "insight" audits. */
486
+ insightsNotice: 'Later this year, insights will replace performance audits. [Learn more and provide feedback here](https://github.com/GoogleChrome/lighthouse/discussions/16462).',
487
+ /** Text for a button to try out "Performance insight audits", a new set of performance advice that will replace performance audits. */
488
+ tryInsights: 'Try insights',
489
+ /** Text for a button for going back to normal "Performance audits", instead of using the new set of performance insight audits that will replace performance audits. */
490
+ goBackToAudits: 'Go back to audits',
484
491
  };
485
492
 
486
493
  export {
@@ -114,13 +114,6 @@ export class TopbarFeatures {
114
114
  toggleDarkTheme(this._dom);
115
115
  break;
116
116
  }
117
- case 'toggle-insights': {
118
- const swappableSection = this._dom.maybeFind('.lh-perf-audits--swappable');
119
- if (swappableSection) {
120
- this._dom.swapSectionIfPossible(swappableSection);
121
- }
122
- break;
123
- }
124
117
  case 'view-unthrottled-trace': {
125
118
  this._reportUIFeatures._opts.onViewTrace?.();
126
119
  }
@@ -153,8 +146,7 @@ export class TopbarFeatures {
153
146
  */
154
147
  onCopyButtonClick() {
155
148
  this._dom.fireEventOn('lh-analytics', this._dom.document(), {
156
- cmd: 'send',
157
- fields: {hitType: 'event', eventCategory: 'report', eventAction: 'copy'},
149
+ name: 'copy',
158
150
  });
159
151
 
160
152
  try {
@@ -19,7 +19,7 @@ export function _formatPathAsString(pathInLHR: string[]): string;
19
19
  * @param {Map<string, MessageFormatElement>} [customElements]
20
20
  * @return {Map<string, MessageFormatElement>}
21
21
  */
22
- export function collectAllCustomElementsFromICU(icuElements: Array<MessageFormatElement>, customElements?: Map<string, import("@formatjs/icu-messageformat-parser").MessageFormatElement> | undefined): Map<string, MessageFormatElement>;
22
+ export function collectAllCustomElementsFromICU(icuElements: Array<MessageFormatElement>, customElements?: Map<string, MessageFormatElement>): Map<string, MessageFormatElement>;
23
23
  /**
24
24
  * Returns whether `icuMessageOrNot`` is an `LH.IcuMessage` instance.
25
25
  * @param {unknown} icuMessageOrNot