lighthouse 12.8.2 → 13.0.0-dev.20251009

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 (270) hide show
  1. package/cli/cli-flags.js +1 -1
  2. package/cli/test/smokehouse/config/exclusions.js +0 -2
  3. package/cli/test/smokehouse/version-check.d.ts +1 -1
  4. package/core/audits/accessibility/accesskeys.js +3 -3
  5. package/core/audits/accessibility/aria-allowed-attr.js +3 -3
  6. package/core/audits/accessibility/aria-allowed-role.js +2 -1
  7. package/core/audits/accessibility/aria-command-name.js +1 -1
  8. package/core/audits/accessibility/aria-conditional-attr.js +1 -1
  9. package/core/audits/accessibility/aria-deprecated-role.js +1 -1
  10. package/core/audits/accessibility/aria-dialog-name.js +1 -1
  11. package/core/audits/accessibility/aria-hidden-body.js +3 -3
  12. package/core/audits/accessibility/aria-hidden-focus.js +3 -3
  13. package/core/audits/accessibility/aria-input-field-name.js +3 -3
  14. package/core/audits/accessibility/aria-meter-name.js +1 -1
  15. package/core/audits/accessibility/aria-progressbar-name.js +1 -1
  16. package/core/audits/accessibility/aria-prohibited-attr.js +1 -1
  17. package/core/audits/accessibility/aria-required-attr.js +3 -3
  18. package/core/audits/accessibility/aria-required-children.js +3 -3
  19. package/core/audits/accessibility/aria-required-parent.js +3 -3
  20. package/core/audits/accessibility/aria-roles.js +3 -3
  21. package/core/audits/accessibility/aria-text.js +3 -3
  22. package/core/audits/accessibility/aria-toggle-field-name.js +3 -3
  23. package/core/audits/accessibility/aria-tooltip-name.js +1 -1
  24. package/core/audits/accessibility/aria-treeitem-name.js +1 -1
  25. package/core/audits/accessibility/aria-valid-attr-value.js +3 -3
  26. package/core/audits/accessibility/aria-valid-attr.js +3 -3
  27. package/core/audits/accessibility/button-name.js +3 -3
  28. package/core/audits/accessibility/bypass.js +3 -3
  29. package/core/audits/accessibility/color-contrast.js +3 -3
  30. package/core/audits/accessibility/definition-list.js +3 -3
  31. package/core/audits/accessibility/dlitem.js +3 -3
  32. package/core/audits/accessibility/document-title.js +3 -3
  33. package/core/audits/accessibility/duplicate-id-aria.js +3 -3
  34. package/core/audits/accessibility/empty-heading.js +3 -3
  35. package/core/audits/accessibility/form-field-multiple-labels.js +3 -3
  36. package/core/audits/accessibility/frame-title.js +3 -3
  37. package/core/audits/accessibility/heading-order.js +3 -3
  38. package/core/audits/accessibility/html-has-lang.js +3 -3
  39. package/core/audits/accessibility/html-lang-valid.js +3 -3
  40. package/core/audits/accessibility/html-xml-lang-mismatch.js +3 -3
  41. package/core/audits/accessibility/identical-links-same-purpose.js +3 -3
  42. package/core/audits/accessibility/image-alt.js +3 -3
  43. package/core/audits/accessibility/image-redundant-alt.js +4 -3
  44. package/core/audits/accessibility/input-button-name.js +3 -3
  45. package/core/audits/accessibility/input-image-alt.js +3 -3
  46. package/core/audits/accessibility/label-content-name-mismatch.js +3 -3
  47. package/core/audits/accessibility/label.js +3 -3
  48. package/core/audits/accessibility/landmark-one-main.js +3 -4
  49. package/core/audits/accessibility/link-in-text-block.js +3 -3
  50. package/core/audits/accessibility/link-name.js +3 -3
  51. package/core/audits/accessibility/list.js +3 -3
  52. package/core/audits/accessibility/listitem.js +3 -3
  53. package/core/audits/accessibility/meta-refresh.js +3 -3
  54. package/core/audits/accessibility/meta-viewport.js +3 -3
  55. package/core/audits/accessibility/object-alt.js +3 -3
  56. package/core/audits/accessibility/select-name.js +3 -3
  57. package/core/audits/accessibility/skip-link.js +3 -3
  58. package/core/audits/accessibility/tabindex.js +3 -3
  59. package/core/audits/accessibility/table-duplicate-name.js +4 -3
  60. package/core/audits/accessibility/table-fake-caption.js +3 -3
  61. package/core/audits/accessibility/target-size.js +3 -3
  62. package/core/audits/accessibility/td-has-header.js +3 -3
  63. package/core/audits/accessibility/td-headers-attr.js +3 -3
  64. package/core/audits/accessibility/th-has-data-cells.js +3 -3
  65. package/core/audits/accessibility/valid-lang.js +3 -3
  66. package/core/audits/accessibility/video-caption.js +3 -3
  67. package/core/audits/audit.d.ts +0 -4
  68. package/core/audits/audit.js +2 -13
  69. package/core/audits/insights/cls-culprits-insight.js +1 -1
  70. package/core/audits/insights/dom-size-insight.js +11 -7
  71. package/core/audits/insights/font-display-insight.js +3 -1
  72. package/core/audits/insights/image-delivery-insight.js +4 -1
  73. package/core/audits/insights/insight-audit.d.ts +6 -4
  74. package/core/audits/insights/insight-audit.js +27 -8
  75. package/core/audits/insights/third-parties-insight.js +1 -1
  76. package/core/audits/layout-shifts.js +1 -1
  77. package/core/audits/predictive-perf.js +2 -2
  78. package/core/audits/seo/crawlable-anchors.js +2 -3
  79. package/core/audits/seo/manual/structured-data.js +1 -1
  80. package/core/audits/server-response-time.d.ts +0 -5
  81. package/core/audits/server-response-time.js +12 -26
  82. package/core/computed/metrics/cumulative-layout-shift.js +2 -2
  83. package/core/computed/metrics/lantern-metric.js +3 -3
  84. package/core/computed/metrics/lcp-breakdown.d.ts +10 -5
  85. package/core/computed/metrics/lcp-breakdown.js +50 -22
  86. package/core/computed/metrics/time-to-first-byte.js +33 -10
  87. package/core/computed/metrics/timing-summary.js +3 -2
  88. package/core/computed/page-dependency-graph.js +1 -1
  89. package/core/computed/trace-engine-result.js +2 -2
  90. package/core/config/default-config.js +110 -152
  91. package/core/config/experimental-config.js +1 -32
  92. package/core/config/filters.js +6 -9
  93. package/core/config/lr-desktop-config.js +0 -1
  94. package/core/config/lr-mobile-config.js +0 -1
  95. package/core/gather/driver/target-manager.d.ts +1 -1
  96. package/core/gather/driver.d.ts +1 -1
  97. package/core/gather/gatherers/anchor-elements.js +8 -24
  98. package/core/gather/gatherers/image-elements.js +32 -6
  99. package/core/gather/gatherers/inspector-issues.js +1 -28
  100. package/core/gather/gatherers/trace-elements.d.ts +2 -11
  101. package/core/gather/gatherers/trace-elements.js +9 -39
  102. package/core/gather/navigation-runner.js +0 -3
  103. package/core/gather/session.d.ts +1 -1
  104. package/core/lib/asset-saver.d.ts +2 -2
  105. package/core/lib/asset-saver.js +33 -43
  106. package/core/lib/bf-cache-strings.js +10 -9
  107. package/core/lib/deprecations-strings.js +5 -5
  108. package/core/lib/emulation.d.ts +10 -0
  109. package/core/lib/emulation.js +21 -6
  110. package/core/lib/legacy-javascript/legacy-javascript.js +4 -11
  111. package/core/lib/network-request.d.ts +0 -7
  112. package/core/lib/network-request.js +0 -16
  113. package/core/lib/proto-preprocessor.js +10 -25
  114. package/core/runner.js +1 -8
  115. package/core/scoring.js +1 -1
  116. package/dist/report/bundle.esm.js +10 -49
  117. package/dist/report/flow.js +12 -51
  118. package/dist/report/standalone.js +11 -50
  119. package/flow-report/src/i18n/i18n.d.ts +4 -6
  120. package/package.json +16 -19
  121. package/readme.md +2 -2
  122. package/report/assets/styles.css +0 -39
  123. package/report/renderer/api.js +0 -1
  124. package/report/renderer/category-renderer.js +6 -0
  125. package/report/renderer/components.js +1 -1
  126. package/report/renderer/details-renderer.d.ts +1 -2
  127. package/report/renderer/details-renderer.js +0 -1
  128. package/report/renderer/dom.d.ts +0 -13
  129. package/report/renderer/dom.js +0 -38
  130. package/report/renderer/performance-category-renderer.d.ts +0 -26
  131. package/report/renderer/performance-category-renderer.js +10 -142
  132. package/report/renderer/report-ui-features.d.ts +0 -1
  133. package/report/renderer/report-ui-features.js +2 -13
  134. package/report/renderer/report-utils.d.ts +2 -3
  135. package/report/renderer/report-utils.js +4 -6
  136. package/report/types/report-renderer.d.ts +0 -6
  137. package/shared/localization/locales/ar-XB.json +107 -455
  138. package/shared/localization/locales/ar.json +107 -455
  139. package/shared/localization/locales/bg.json +96 -444
  140. package/shared/localization/locales/ca.json +96 -444
  141. package/shared/localization/locales/cs.json +96 -444
  142. package/shared/localization/locales/da.json +96 -444
  143. package/shared/localization/locales/de.json +96 -444
  144. package/shared/localization/locales/el.json +96 -444
  145. package/shared/localization/locales/en-GB.json +96 -444
  146. package/shared/localization/locales/en-US.json +116 -467
  147. package/shared/localization/locales/en-XA.json +93 -441
  148. package/shared/localization/locales/en-XL.json +116 -467
  149. package/shared/localization/locales/es-419.json +96 -444
  150. package/shared/localization/locales/es.json +96 -444
  151. package/shared/localization/locales/fi.json +96 -444
  152. package/shared/localization/locales/fil.json +96 -444
  153. package/shared/localization/locales/fr.json +96 -444
  154. package/shared/localization/locales/he.json +118 -466
  155. package/shared/localization/locales/hi.json +96 -444
  156. package/shared/localization/locales/hr.json +100 -448
  157. package/shared/localization/locales/hu.json +96 -444
  158. package/shared/localization/locales/id.json +96 -444
  159. package/shared/localization/locales/it.json +96 -444
  160. package/shared/localization/locales/ja.json +96 -444
  161. package/shared/localization/locales/ko.json +97 -445
  162. package/shared/localization/locales/lt.json +96 -444
  163. package/shared/localization/locales/lv.json +97 -445
  164. package/shared/localization/locales/nl.json +96 -444
  165. package/shared/localization/locales/no.json +96 -444
  166. package/shared/localization/locales/pl.json +96 -444
  167. package/shared/localization/locales/pt-PT.json +96 -444
  168. package/shared/localization/locales/pt.json +97 -445
  169. package/shared/localization/locales/ro.json +97 -445
  170. package/shared/localization/locales/ru.json +96 -444
  171. package/shared/localization/locales/sk.json +96 -444
  172. package/shared/localization/locales/sl.json +96 -444
  173. package/shared/localization/locales/sr-Latn.json +96 -444
  174. package/shared/localization/locales/sr.json +96 -444
  175. package/shared/localization/locales/sv.json +96 -444
  176. package/shared/localization/locales/ta.json +96 -444
  177. package/shared/localization/locales/te.json +97 -445
  178. package/shared/localization/locales/th.json +96 -444
  179. package/shared/localization/locales/tr.json +96 -444
  180. package/shared/localization/locales/uk.json +96 -444
  181. package/shared/localization/locales/vi.json +96 -444
  182. package/shared/localization/locales/zh-HK.json +96 -444
  183. package/shared/localization/locales/zh-TW.json +97 -445
  184. package/shared/localization/locales/zh.json +96 -444
  185. package/shared/localization/locales.d.ts +2 -0
  186. package/shared/localization/locales.js +130 -139
  187. package/shared/tsconfig.json +2 -0
  188. package/tsconfig-base.json +2 -2
  189. package/tsconfig.json +1 -4
  190. package/types/artifacts.d.ts +6 -81
  191. package/types/audit.d.ts +1 -1
  192. package/types/lhr/settings.d.ts +1 -1
  193. package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -45
  194. package/core/audits/byte-efficiency/duplicated-javascript.js +0 -223
  195. package/core/audits/byte-efficiency/efficient-animated-content.d.ts +0 -22
  196. package/core/audits/byte-efficiency/efficient-animated-content.js +0 -93
  197. package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -28
  198. package/core/audits/byte-efficiency/legacy-javascript.js +0 -144
  199. package/core/audits/byte-efficiency/modern-image-formats.d.ts +0 -38
  200. package/core/audits/byte-efficiency/modern-image-formats.js +0 -187
  201. package/core/audits/byte-efficiency/offscreen-images.d.ts +0 -63
  202. package/core/audits/byte-efficiency/offscreen-images.js +0 -240
  203. package/core/audits/byte-efficiency/render-blocking-resources.d.ts +0 -53
  204. package/core/audits/byte-efficiency/render-blocking-resources.js +0 -312
  205. package/core/audits/byte-efficiency/uses-long-cache-ttl.d.ts +0 -59
  206. package/core/audits/byte-efficiency/uses-long-cache-ttl.js +0 -293
  207. package/core/audits/byte-efficiency/uses-optimized-images.d.ts +0 -33
  208. package/core/audits/byte-efficiency/uses-optimized-images.js +0 -146
  209. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.d.ts +0 -16
  210. package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +0 -106
  211. package/core/audits/byte-efficiency/uses-responsive-images.d.ts +0 -44
  212. package/core/audits/byte-efficiency/uses-responsive-images.js +0 -202
  213. package/core/audits/byte-efficiency/uses-text-compression.d.ts +0 -14
  214. package/core/audits/byte-efficiency/uses-text-compression.js +0 -108
  215. package/core/audits/critical-request-chains.d.ts +0 -44
  216. package/core/audits/critical-request-chains.js +0 -221
  217. package/core/audits/dobetterweb/dom-size.d.ts +0 -32
  218. package/core/audits/dobetterweb/dom-size.js +0 -182
  219. package/core/audits/dobetterweb/no-document-write.d.ts +0 -16
  220. package/core/audits/dobetterweb/no-document-write.js +0 -86
  221. package/core/audits/dobetterweb/uses-http2.d.ts +0 -72
  222. package/core/audits/dobetterweb/uses-http2.js +0 -276
  223. package/core/audits/dobetterweb/uses-passive-event-listeners.d.ts +0 -16
  224. package/core/audits/dobetterweb/uses-passive-event-listeners.js +0 -69
  225. package/core/audits/font-display.d.ts +0 -32
  226. package/core/audits/font-display.js +0 -195
  227. package/core/audits/largest-contentful-paint-element.d.ts +0 -34
  228. package/core/audits/largest-contentful-paint-element.js +0 -181
  229. package/core/audits/lcp-lazy-loaded.d.ts +0 -22
  230. package/core/audits/lcp-lazy-loaded.js +0 -115
  231. package/core/audits/metrics/first-meaningful-paint.d.ts +0 -12
  232. package/core/audits/metrics/first-meaningful-paint.js +0 -47
  233. package/core/audits/preload-fonts.d.ts +0 -25
  234. package/core/audits/preload-fonts.js +0 -97
  235. package/core/audits/prioritize-lcp-image.d.ts +0 -74
  236. package/core/audits/prioritize-lcp-image.js +0 -297
  237. package/core/audits/seo/font-size.d.ts +0 -24
  238. package/core/audits/seo/font-size.js +0 -344
  239. package/core/audits/third-party-facades.d.ts +0 -41
  240. package/core/audits/third-party-facades.js +0 -234
  241. package/core/audits/third-party-summary.d.ts +0 -78
  242. package/core/audits/third-party-summary.js +0 -236
  243. package/core/audits/uses-rel-preconnect.d.ts +0 -37
  244. package/core/audits/uses-rel-preconnect.js +0 -286
  245. package/core/audits/uses-rel-preload.d.ts +0 -57
  246. package/core/audits/uses-rel-preload.js +0 -263
  247. package/core/audits/viewport.d.ts +0 -17
  248. package/core/audits/viewport.js +0 -87
  249. package/core/audits/work-during-interaction.d.ts +0 -81
  250. package/core/audits/work-during-interaction.js +0 -287
  251. package/core/computed/critical-request-chains.d.ts +0 -42
  252. package/core/computed/critical-request-chains.js +0 -143
  253. package/core/computed/viewport-meta.d.ts +0 -37
  254. package/core/computed/viewport-meta.js +0 -71
  255. package/core/gather/gatherers/cache-contents.d.ts +0 -11
  256. package/core/gather/gatherers/cache-contents.js +0 -56
  257. package/core/gather/gatherers/devtools-log-compat.d.ts +0 -13
  258. package/core/gather/gatherers/devtools-log-compat.js +0 -35
  259. package/core/gather/gatherers/dobetterweb/domstats.d.ts +0 -10
  260. package/core/gather/gatherers/dobetterweb/domstats.js +0 -102
  261. package/core/gather/gatherers/dobetterweb/optimized-images.d.ts +0 -48
  262. package/core/gather/gatherers/dobetterweb/optimized-images.js +0 -169
  263. package/core/gather/gatherers/dobetterweb/response-compression.d.ts +0 -23
  264. package/core/gather/gatherers/dobetterweb/response-compression.js +0 -136
  265. package/core/gather/gatherers/seo/font-size.d.ts +0 -131
  266. package/core/gather/gatherers/seo/font-size.js +0 -347
  267. package/core/gather/gatherers/trace-compat.d.ts +0 -13
  268. package/core/gather/gatherers/trace-compat.js +0 -35
  269. package/types/internal/metaviewport-parser.d.ts +0 -13
  270. package/types/internal/parse-cache-control.d.ts +0 -20
@@ -1,312 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2018 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- /**
8
- * @fileoverview Audit a page to see if it does have resources that are blocking first paint
9
- */
10
-
11
- import {Audit} from '../audit.js';
12
- import * as i18n from '../../lib/i18n/i18n.js';
13
- import * as Lantern from '../../lib/lantern/lantern.js';
14
- import {UnusedCSS} from '../../computed/unused-css.js';
15
- import {NetworkRequest} from '../../lib/network-request.js';
16
- import {LoadSimulator} from '../../computed/load-simulator.js';
17
- import {FirstContentfulPaint} from '../../computed/metrics/first-contentful-paint.js';
18
- import {LCPImageRecord} from '../../computed/lcp-image-record.js';
19
- import {NavigationInsights} from '../../computed/navigation-insights.js';
20
-
21
- // Because of the way we detect blocking stylesheets, asynchronously loaded
22
- // CSS with link[rel=preload] and an onload handler (see https://github.com/filamentgroup/loadCSS)
23
- // can be falsely flagged as blocking. Therefore, ignore stylesheets that loaded fast enough
24
- // to possibly be non-blocking (and they have minimal impact anyway).
25
- const MINIMUM_WASTED_MS = 50;
26
-
27
- const UIStrings = {
28
- /** Imperative title of a Lighthouse audit that tells the user to reduce or remove network resources that block the initial render of the page. This is displayed in a list of audit titles that Lighthouse generates. */
29
- title: 'Eliminate render-blocking resources',
30
- /** Description of a Lighthouse audit that tells the user *why* they should reduce or remove network resources that block the initial render of the page. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
31
- description: 'Resources are blocking the first paint of your page. Consider ' +
32
- 'delivering critical JS/CSS inline and deferring all non-critical ' +
33
- 'JS/styles. [Learn how to eliminate render-blocking resources](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/).',
34
- };
35
-
36
- const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
37
-
38
- /**
39
- * Given a simulation's nodeTimings, return an object with the nodes/timing keyed by network URL
40
- * @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
41
- * @return {Map<string, {node: LH.Gatherer.Simulation.GraphNetworkNode, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>}
42
- */
43
- function getNodesAndTimingByRequestId(nodeTimings) {
44
- /** @type {Map<string, {node: LH.Gatherer.Simulation.GraphNetworkNode, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>} */
45
- const requestIdToNode = new Map();
46
-
47
- for (const [node, nodeTiming] of nodeTimings) {
48
- if (node.type !== 'network') continue;
49
-
50
- requestIdToNode.set(node.request.requestId, {node, nodeTiming});
51
- }
52
-
53
- return requestIdToNode;
54
- }
55
-
56
- /**
57
- * Adjust the timing of a node and its dependencies to account for stack specific overrides.
58
- * @param {Map<LH.Gatherer.Simulation.GraphNode, LH.Gatherer.Simulation.NodeTiming>} adjustedNodeTimings
59
- * @param {LH.Gatherer.Simulation.GraphNode} node
60
- * @param {LH.Artifacts.DetectedStack[]} Stacks
61
- */
62
- function adjustNodeTimings(adjustedNodeTimings, node, Stacks) {
63
- const nodeTiming = adjustedNodeTimings.get(node);
64
- if (!nodeTiming) return;
65
- const stackSpecificTiming = computeStackSpecificTiming(node, nodeTiming, Stacks);
66
- const difference = nodeTiming.duration - stackSpecificTiming.duration;
67
- if (!difference) return;
68
-
69
- // AMP's method of removal of stylesheets effectively removes all dependent nodes from the FCP graph
70
- node.traverse(childNode => {
71
- adjustedNodeTimings.delete(childNode);
72
- });
73
- adjustedNodeTimings.set(node, stackSpecificTiming);
74
- }
75
-
76
- /**
77
- * Any stack specific timing overrides should go in this function.
78
- * @see https://github.com/GoogleChrome/lighthouse/issues/2832#issuecomment-591066081
79
- *
80
- * @param {LH.Gatherer.Simulation.GraphNode} node
81
- * @param {LH.Gatherer.Simulation.NodeTiming} nodeTiming
82
- * @param {LH.Artifacts.DetectedStack[]} Stacks
83
- */
84
- function computeStackSpecificTiming(node, nodeTiming, Stacks) {
85
- const stackSpecificTiming = {...nodeTiming};
86
- if (Stacks.some(stack => stack.id === 'amp')) {
87
- // AMP will load a linked stylesheet asynchronously if it has not been loaded after 2.1 seconds:
88
- // https://github.com/ampproject/amphtml/blob/8e03ac2f315774070651584a7e046ff24212c9b1/src/font-stylesheet-timeout.js#L54-L59
89
- // Any potential savings must only include time spent on AMP stylesheet nodes before 2.1 seconds.
90
- if (node.type === Lantern.Graph.BaseNode.types.NETWORK &&
91
- node.request.resourceType === NetworkRequest.TYPES.Stylesheet &&
92
- nodeTiming.endTime > 2100) {
93
- stackSpecificTiming.endTime = Math.max(nodeTiming.startTime, 2100);
94
- stackSpecificTiming.duration = stackSpecificTiming.endTime - stackSpecificTiming.startTime;
95
- }
96
- }
97
- return stackSpecificTiming;
98
- }
99
-
100
- class RenderBlockingResources extends Audit {
101
- /**
102
- * @return {LH.Audit.Meta}
103
- */
104
- static get meta() {
105
- return {
106
- id: 'render-blocking-resources',
107
- title: str_(UIStrings.title),
108
- supportedModes: ['navigation'],
109
- scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
110
- description: str_(UIStrings.description),
111
- guidanceLevel: 2,
112
- // TODO: look into adding an `optionalArtifacts` property that captures the non-required nature
113
- // of CSSUsage
114
- requiredArtifacts:
115
- // eslint-disable-next-line max-len
116
- ['URL', 'Trace', 'DevtoolsLog', 'Stylesheets', 'CSSUsage', 'GatherContext', 'Stacks', 'SourceMaps'],
117
- };
118
- }
119
-
120
- /**
121
- * @param {LH.Artifacts} artifacts
122
- * @param {LH.Audit.Context} context
123
- * @return {Promise<{fcpWastedMs: number, lcpWastedMs: number, results: Array<{url: string, totalBytes: number, wastedMs: number}>}>}
124
- */
125
- static async computeResults(artifacts, context) {
126
- const settings = context.settings;
127
- const gatherContext = artifacts.GatherContext;
128
- const trace = artifacts.Trace;
129
- const devtoolsLog = artifacts.DevtoolsLog;
130
- const SourceMaps = artifacts.SourceMaps;
131
- const simulatorData = {devtoolsLog, settings: context.settings};
132
- const simulator = await LoadSimulator.request(simulatorData, context);
133
- const wastedCssBytes = await RenderBlockingResources.computeWastedCSSBytes(artifacts, context);
134
- const navInsights = await NavigationInsights.request({trace, settings, SourceMaps}, context);
135
-
136
- const renderBlocking = navInsights.model.RenderBlocking;
137
- if (renderBlocking instanceof Error) throw renderBlocking;
138
-
139
- /** @type {LH.Audit.Context['settings']} */
140
- const metricSettings = {
141
- ...context.settings,
142
- throttlingMethod: 'simulate',
143
- };
144
-
145
- const metricComputationData = {trace, devtoolsLog, gatherContext, simulator,
146
- settings: metricSettings, URL: artifacts.URL, SourceMaps: artifacts.SourceMaps};
147
-
148
- // Cast to just `LanternMetric` since we explicitly set `throttlingMethod: 'simulate'`.
149
- const fcpSimulation = /** @type {LH.Artifacts.LanternMetric} */
150
- (await FirstContentfulPaint.request(metricComputationData, context));
151
-
152
- const nodesAndTimingsByRequestId =
153
- getNodesAndTimingByRequestId(fcpSimulation.optimisticEstimate.nodeTimings);
154
-
155
- const results = [];
156
- const deferredNodeIds = new Set();
157
- for (const resource of renderBlocking.renderBlockingRequests) {
158
- const nodeAndTiming = nodesAndTimingsByRequestId.get(resource.args.data.requestId);
159
- // TODO: beacon to Sentry, https://github.com/GoogleChrome/lighthouse/issues/7041
160
- if (!nodeAndTiming) continue;
161
-
162
- const {node, nodeTiming} = nodeAndTiming;
163
-
164
- const stackSpecificTiming = computeStackSpecificTiming(node, nodeTiming, artifacts.Stacks);
165
-
166
- // Mark this node and all its dependents as deferrable
167
- node.traverse(node => deferredNodeIds.add(node.id));
168
-
169
- // "wastedMs" is the download time of the network request, responseReceived - requestSent
170
- const wastedMs = Math.round(stackSpecificTiming.duration);
171
- if (wastedMs < MINIMUM_WASTED_MS) continue;
172
-
173
- results.push({
174
- url: resource.args.data.url,
175
- totalBytes: node.request.transferSize,
176
- wastedMs,
177
- });
178
- }
179
-
180
- if (!results.length) {
181
- return {results, fcpWastedMs: 0, lcpWastedMs: 0};
182
- }
183
-
184
- const fcpWastedMs = RenderBlockingResources.estimateSavingsWithGraphs(
185
- simulator,
186
- fcpSimulation.optimisticGraph,
187
- deferredNodeIds,
188
- wastedCssBytes,
189
- artifacts.Stacks
190
- );
191
-
192
- const lcpRecord = await LCPImageRecord.request(metricComputationData, context);
193
-
194
- // In most cases if the LCP is an image, render blocking resources don't affect LCP. For these cases we should reduce its impact.
195
- return {results, fcpWastedMs, lcpWastedMs: lcpRecord ? 0 : fcpWastedMs};
196
- }
197
-
198
- /**
199
- * Estimates how much faster this page would reach FCP if we inlined all the used CSS from the
200
- * render blocking stylesheets and deferred all the scripts. This is more conservative than
201
- * removing all the assets and more aggressive than inlining everything.
202
- *
203
- * *Most* of the time, scripts in the head are there accidentally/due to lack of awareness
204
- * rather than necessity, so we're comfortable with this balance. In the worst case, we're telling
205
- * devs that they should be able to get to a reasonable first paint without JS, which is not a bad
206
- * thing.
207
- *
208
- * @param {LH.Gatherer.Simulation.Simulator} simulator
209
- * @param {LH.Gatherer.Simulation.GraphNode} fcpGraph
210
- * @param {Set<string>} deferredIds
211
- * @param {Map<string, number>} wastedCssBytesByUrl
212
- * @param {LH.Artifacts.DetectedStack[]} Stacks
213
- * @return {number}
214
- */
215
- static estimateSavingsWithGraphs(simulator, fcpGraph, deferredIds, wastedCssBytesByUrl, Stacks) {
216
- const {nodeTimings} = simulator.simulate(fcpGraph);
217
- const adjustedNodeTimings = new Map(nodeTimings);
218
-
219
- let totalChildNetworkBytes = 0;
220
- const minimalFCPGraph = fcpGraph.cloneWithRelationships(node => {
221
- adjustNodeTimings(adjustedNodeTimings, node, Stacks);
222
-
223
- // If a node can be deferred, exclude it from the new FCP graph
224
- const canDeferRequest = deferredIds.has(node.id);
225
- if (node.type !== Lantern.Graph.BaseNode.types.NETWORK) return !canDeferRequest;
226
-
227
- const isStylesheet =
228
- node.request.resourceType === NetworkRequest.TYPES.Stylesheet;
229
- if (canDeferRequest && isStylesheet) {
230
- // We'll inline the used bytes of the stylesheet and assume the rest can be deferred
231
- const wastedBytes = wastedCssBytesByUrl.get(node.request.url) || 0;
232
- totalChildNetworkBytes += (node.request.transferSize || 0) - wastedBytes;
233
- }
234
- return !canDeferRequest;
235
- });
236
-
237
- if (minimalFCPGraph.type !== 'network') {
238
- throw new Error('minimalFCPGraph not a NetworkNode');
239
- }
240
-
241
- // Recalculate the "before" time based on our adjusted node timings.
242
- const estimateBeforeInline = Math.max(...Array.from(
243
- Array.from(adjustedNodeTimings).map(timing => timing[1].endTime)
244
- ));
245
-
246
- // Add the inlined bytes to the HTML response
247
- const originalTransferSize = minimalFCPGraph.request.transferSize;
248
- const safeTransferSize = originalTransferSize || 0;
249
- minimalFCPGraph.request.transferSize = safeTransferSize + totalChildNetworkBytes;
250
- const estimateAfterInline = simulator.simulate(minimalFCPGraph).timeInMs;
251
- minimalFCPGraph.request.transferSize = originalTransferSize;
252
- return Math.round(Math.max(estimateBeforeInline - estimateAfterInline, 0));
253
- }
254
-
255
- /**
256
- * @param {LH.Artifacts} artifacts
257
- * @param {LH.Audit.Context} context
258
- * @return {Promise<Map<string, number>>}
259
- */
260
- static async computeWastedCSSBytes(artifacts, context) {
261
- const wastedBytesByUrl = new Map();
262
- try {
263
- const unusedCssItems = await UnusedCSS.request({
264
- Stylesheets: artifacts.Stylesheets,
265
- CSSUsage: artifacts.CSSUsage,
266
- devtoolsLog: artifacts.DevtoolsLog,
267
- }, context);
268
- for (const item of unusedCssItems) {
269
- wastedBytesByUrl.set(item.url, item.wastedBytes);
270
- }
271
- } catch {}
272
-
273
- return wastedBytesByUrl;
274
- }
275
-
276
- /**
277
- * @param {LH.Artifacts} artifacts
278
- * @param {LH.Audit.Context} context
279
- * @return {Promise<LH.Audit.Product>}
280
- */
281
- static async audit(artifacts, context) {
282
- const {results, fcpWastedMs, lcpWastedMs} =
283
- await RenderBlockingResources.computeResults(artifacts, context);
284
-
285
- let displayValue;
286
- if (results.length > 0) {
287
- displayValue = str_(i18n.UIStrings.displayValueMsSavings, {wastedMs: fcpWastedMs});
288
- }
289
-
290
- /** @type {LH.Audit.Details.Opportunity['headings']} */
291
- const headings = [
292
- {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
293
- {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize)},
294
- {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnDuration)},
295
- ];
296
-
297
- const details = Audit.makeOpportunityDetails(headings, results,
298
- {overallSavingsMs: fcpWastedMs});
299
-
300
- return {
301
- displayValue,
302
- score: results.length ? 0 : 1,
303
- numericValue: fcpWastedMs,
304
- numericUnit: 'millisecond',
305
- details,
306
- metricSavings: {FCP: fcpWastedMs, LCP: lcpWastedMs},
307
- };
308
- }
309
- }
310
-
311
- export default RenderBlockingResources;
312
- export {UIStrings};
@@ -1,59 +0,0 @@
1
- export default CacheHeaders;
2
- declare class CacheHeaders extends Audit {
3
- /**
4
- * @return {LH.Audit.ScoreOptions}
5
- */
6
- static get defaultOptions(): LH.Audit.ScoreOptions;
7
- /**
8
- * Computes the percent likelihood that a return visit will be within the cache lifetime, based on
9
- * Chrome UMA stats see the note below.
10
- * @param {number} maxAgeInSeconds
11
- * @return {number}
12
- */
13
- static getCacheHitProbability(maxAgeInSeconds: number): number;
14
- /**
15
- * Return max-age if defined, otherwise expires header if defined, and null if not.
16
- * @param {Map<string, string>} headers
17
- * @param {ReturnType<typeof parseCacheControl>} cacheControl
18
- * @return {?number}
19
- */
20
- static computeCacheLifetimeInSeconds(headers: Map<string, string>, cacheControl: ReturnType<typeof parseCacheControl>): number | null;
21
- /**
22
- * Given a network record, returns whether we believe the asset is cacheable, i.e. it was a network
23
- * request that satisifed the conditions:
24
- *
25
- * 1. Has a cacheable status code
26
- * 2. Has a resource type that corresponds to static assets (image, script, stylesheet, etc).
27
- *
28
- * Allowing assets with a query string is debatable, PSI considered them non-cacheable with a similar
29
- * caveat.
30
- *
31
- * TODO: Investigate impact in HTTPArchive, experiment with this policy to see what changes.
32
- *
33
- * @param {LH.Artifacts.NetworkRequest} record
34
- * @return {boolean}
35
- */
36
- static isCacheableAsset(record: LH.Artifacts.NetworkRequest): boolean;
37
- /**
38
- * Returns true if headers suggest a record should not be cached for a long time.
39
- * @param {Map<string, string>} headers
40
- * @param {ReturnType<typeof parseCacheControl>} cacheControl
41
- * @return {boolean}
42
- */
43
- static shouldSkipRecord(headers: Map<string, string>, cacheControl: ReturnType<typeof parseCacheControl>): boolean;
44
- /**
45
- * @param {LH.Artifacts} artifacts
46
- * @param {LH.Audit.Context} context
47
- * @return {Promise<LH.Audit.Product>}
48
- */
49
- static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
50
- }
51
- export namespace UIStrings {
52
- let title: string;
53
- let failureTitle: string;
54
- let description: string;
55
- let displayValue: string;
56
- }
57
- import { Audit } from '../audit.js';
58
- import parseCacheControl from 'parse-cache-control';
59
- //# sourceMappingURL=uses-long-cache-ttl.d.ts.map
@@ -1,293 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2017 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- import parseCacheControl from 'parse-cache-control';
8
-
9
- import {Audit} from '../audit.js';
10
- import {NetworkRequest} from '../../lib/network-request.js';
11
- import UrlUtils from '../../lib/url-utils.js';
12
- import {linearInterpolation} from '../../../shared/statistics.js';
13
- import * as i18n from '../../lib/i18n/i18n.js';
14
- import {NetworkRecords} from '../../computed/network-records.js';
15
-
16
- const UIStrings = {
17
- /** Title of a diagnostic audit that provides detail on the cache policy applies to the page's static assets. Cache refers to browser disk cache, which keeps old versions of network resources around for future use. This is displayed in a list of audit titles that Lighthouse generates. */
18
- title: 'Uses efficient cache policy on static assets',
19
- /** Title of a diagnostic audit that provides details on the any page resources that could have been served with more efficient cache policies. Cache refers to browser disk cache, which keeps old versions of network resources around for future use. This imperative title is shown to users when there is a significant amount of assets served with poor cache policies. */
20
- failureTitle: 'Serve static assets with an efficient cache policy',
21
- /** Description of a Lighthouse audit that tells the user *why* they need to adopt a long cache lifetime policy. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
22
- description:
23
- 'A long cache lifetime can speed up repeat visits to your page. ' +
24
- '[Learn more about efficient cache policies](https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/).',
25
- /** [ICU Syntax] Label for the audit identifying network resources with inefficient cache values. Clicking this will expand the audit to show the resources. */
26
- displayValue: `{itemCount, plural,
27
- =1 {1 resource found}
28
- other {# resources found}
29
- }`,
30
- };
31
-
32
- const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
33
-
34
- // Ignore assets that have very high likelihood of cache hit
35
- const IGNORE_THRESHOLD_IN_PERCENT = 0.925;
36
-
37
- class CacheHeaders extends Audit {
38
- /**
39
- * @return {LH.Audit.Meta}
40
- */
41
- static get meta() {
42
- return {
43
- id: 'uses-long-cache-ttl',
44
- title: str_(UIStrings.title),
45
- failureTitle: str_(UIStrings.failureTitle),
46
- description: str_(UIStrings.description),
47
- scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
48
- guidanceLevel: 3,
49
- requiredArtifacts: ['DevtoolsLog', 'SourceMaps'],
50
- };
51
- }
52
-
53
- /**
54
- * @return {LH.Audit.ScoreOptions}
55
- */
56
- static get defaultOptions() {
57
- return {
58
- // 50th and 25th percentiles HTTPArchive -> 50 and 75, with p10 derived from them.
59
- // https://bigquery.cloud.google.com/table/httparchive:lighthouse.2018_04_01_mobile?pli=1
60
- // see https://www.desmos.com/calculator/uzsyl2hbcb
61
- p10: 28 * 1024,
62
- median: 128 * 1024,
63
- };
64
- }
65
-
66
- /**
67
- * Computes the percent likelihood that a return visit will be within the cache lifetime, based on
68
- * Chrome UMA stats see the note below.
69
- * @param {number} maxAgeInSeconds
70
- * @return {number}
71
- */
72
- static getCacheHitProbability(maxAgeInSeconds) {
73
- // This array contains the hand wavy distribution of the age of a resource in hours at the time of
74
- // cache hit at 0th, 10th, 20th, 30th, etc percentiles. This is used to compute `wastedMs` since there
75
- // are clearly diminishing returns to cache duration i.e. 6 months is not 2x better than 3 months.
76
- // Based on UMA stats for HttpCache.StaleEntry.Validated.Age, see https://www.desmos.com/calculator/7v0qh1nzvh
77
- // Example: a max-age of 12 hours already covers ~50% of cases, doubling to 24 hours covers ~10% more.
78
- const RESOURCE_AGE_IN_HOURS_DECILES = [0, 0.2, 1, 3, 8, 12, 24, 48, 72, 168, 8760, Infinity];
79
- if (RESOURCE_AGE_IN_HOURS_DECILES.length !== 12) {
80
- throw new Error('deciles 0-10 and 1 for overflow');
81
- }
82
-
83
- const maxAgeInHours = maxAgeInSeconds / 3600;
84
- const upperDecileIndex = RESOURCE_AGE_IN_HOURS_DECILES.findIndex(
85
- decile => decile >= maxAgeInHours
86
- );
87
-
88
- // Clip the likelihood between 0 and 1
89
- if (upperDecileIndex === RESOURCE_AGE_IN_HOURS_DECILES.length - 1) return 1;
90
- if (upperDecileIndex === 0) return 0;
91
-
92
- // Use the two closest decile points as control points
93
- const upperDecileValue = RESOURCE_AGE_IN_HOURS_DECILES[upperDecileIndex];
94
- const lowerDecileValue = RESOURCE_AGE_IN_HOURS_DECILES[upperDecileIndex - 1];
95
- const upperDecile = upperDecileIndex / 10;
96
- const lowerDecile = (upperDecileIndex - 1) / 10;
97
-
98
- // Approximate the real likelihood with linear interpolation
99
- return linearInterpolation(
100
- lowerDecileValue,
101
- lowerDecile,
102
- upperDecileValue,
103
- upperDecile,
104
- maxAgeInHours
105
- );
106
- }
107
-
108
- /**
109
- * Return max-age if defined, otherwise expires header if defined, and null if not.
110
- * @param {Map<string, string>} headers
111
- * @param {ReturnType<typeof parseCacheControl>} cacheControl
112
- * @return {?number}
113
- */
114
- static computeCacheLifetimeInSeconds(headers, cacheControl) {
115
- if (cacheControl && cacheControl['max-age'] !== undefined) {
116
- return cacheControl['max-age'];
117
- }
118
-
119
- const expiresHeaders = headers.get('expires');
120
- if (expiresHeaders) {
121
- const expires = new Date(expiresHeaders).getTime();
122
- // Invalid expires values MUST be treated as already expired
123
- if (!expires) return 0;
124
- return Math.ceil((expires - Date.now()) / 1000);
125
- }
126
-
127
- return null;
128
- }
129
-
130
- /**
131
- * Given a network record, returns whether we believe the asset is cacheable, i.e. it was a network
132
- * request that satisifed the conditions:
133
- *
134
- * 1. Has a cacheable status code
135
- * 2. Has a resource type that corresponds to static assets (image, script, stylesheet, etc).
136
- *
137
- * Allowing assets with a query string is debatable, PSI considered them non-cacheable with a similar
138
- * caveat.
139
- *
140
- * TODO: Investigate impact in HTTPArchive, experiment with this policy to see what changes.
141
- *
142
- * @param {LH.Artifacts.NetworkRequest} record
143
- * @return {boolean}
144
- */
145
- static isCacheableAsset(record) {
146
- const CACHEABLE_STATUS_CODES = new Set([200, 203, 206]);
147
-
148
- /** @type {Set<LH.Crdp.Network.ResourceType>} */
149
- const STATIC_RESOURCE_TYPES = new Set([
150
- NetworkRequest.TYPES.Font,
151
- NetworkRequest.TYPES.Image,
152
- NetworkRequest.TYPES.Media,
153
- NetworkRequest.TYPES.Script,
154
- NetworkRequest.TYPES.Stylesheet,
155
- ]);
156
-
157
- // It's not a request loaded over the network, caching makes no sense
158
- if (NetworkRequest.isNonNetworkRequest(record)) return false;
159
-
160
-
161
- return (
162
- CACHEABLE_STATUS_CODES.has(record.statusCode) &&
163
- STATIC_RESOURCE_TYPES.has(record.resourceType || 'Other')
164
- );
165
- }
166
-
167
- /**
168
- * Returns true if headers suggest a record should not be cached for a long time.
169
- * @param {Map<string, string>} headers
170
- * @param {ReturnType<typeof parseCacheControl>} cacheControl
171
- * @return {boolean}
172
- */
173
- static shouldSkipRecord(headers, cacheControl) {
174
- // The HTTP/1.0 Pragma header can disable caching if cache-control is not set, see https://tools.ietf.org/html/rfc7234#section-5.4
175
- if (!cacheControl && (headers.get('pragma') || '').includes('no-cache')) {
176
- return true;
177
- }
178
-
179
- // Ignore assets where policy implies they should not be cached long periods
180
- if (cacheControl &&
181
- (
182
- cacheControl['must-revalidate'] ||
183
- cacheControl['no-cache'] ||
184
- cacheControl['no-store'] ||
185
- cacheControl['stale-while-revalidate'] ||
186
- cacheControl['private'])) {
187
- return true;
188
- }
189
-
190
- return false;
191
- }
192
-
193
- /**
194
- * @param {LH.Artifacts} artifacts
195
- * @param {LH.Audit.Context} context
196
- * @return {Promise<LH.Audit.Product>}
197
- */
198
- static async audit(artifacts, context) {
199
- const devtoolsLogs = artifacts.DevtoolsLog;
200
- const records = await NetworkRecords.request(devtoolsLogs, context);
201
- const results = [];
202
- let totalWastedBytes = 0;
203
-
204
- for (const record of records) {
205
- if (!CacheHeaders.isCacheableAsset(record)) continue;
206
-
207
- /** @type {Map<string, string>} */
208
- const headers = new Map();
209
- for (const header of record.responseHeaders || []) {
210
- if (headers.has(header.name.toLowerCase())) {
211
- const previousHeaderValue = headers.get(header.name.toLowerCase());
212
- headers.set(header.name.toLowerCase(),
213
- `${previousHeaderValue}, ${header.value}`);
214
- } else {
215
- headers.set(header.name.toLowerCase(), header.value);
216
- }
217
- }
218
-
219
- const cacheControl = parseCacheControl(headers.get('cache-control'));
220
- if (this.shouldSkipRecord(headers, cacheControl)) {
221
- continue;
222
- }
223
-
224
- // Ignore if cacheLifetimeInSeconds is a nonpositive number.
225
- let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
226
- headers, cacheControl);
227
- if (cacheLifetimeInSeconds !== null &&
228
- (!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
229
- continue;
230
- }
231
- cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;
232
-
233
- // Ignore assets whose cache lifetime is already high enough
234
- const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
235
- if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;
236
-
237
- const url = UrlUtils.elideDataURI(record.url);
238
- const totalBytes = record.transferSize || 0;
239
- const wastedBytes = (1 - cacheHitProbability) * totalBytes;
240
-
241
- totalWastedBytes += wastedBytes;
242
-
243
- // Include cacheControl info (if it exists) per url as a diagnostic.
244
- /** @type {LH.Audit.Details.DebugData|undefined} */
245
- let debugData;
246
- if (cacheControl) {
247
- debugData = {
248
- type: 'debugdata',
249
- ...cacheControl,
250
- };
251
- }
252
-
253
- results.push({
254
- url,
255
- debugData,
256
- cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
257
- cacheHitProbability,
258
- totalBytes,
259
- wastedBytes,
260
- });
261
- }
262
-
263
- results.sort((a, b) => {
264
- return a.cacheLifetimeMs - b.cacheLifetimeMs ||
265
- b.totalBytes - a.totalBytes ||
266
- a.url.localeCompare(b.url);
267
- });
268
-
269
- /** @type {LH.Audit.Details.Table['headings']} */
270
- const headings = [
271
- {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
272
- // TODO(i18n): pre-compute localized duration
273
- {key: 'cacheLifetimeMs', valueType: 'ms', label: str_(i18n.UIStrings.columnCacheTTL),
274
- displayUnit: 'duration'},
275
- {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize),
276
- displayUnit: 'kb', granularity: 1},
277
- ];
278
-
279
- const details = Audit.makeTableDetails(headings, results,
280
- {wastedBytes: totalWastedBytes, sortedBy: ['totalBytes'], skipSumming: ['cacheLifetimeMs']});
281
-
282
- return {
283
- score: results.length ? 0 : 1,
284
- numericValue: totalWastedBytes,
285
- numericUnit: 'byte',
286
- displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
287
- details,
288
- };
289
- }
290
- }
291
-
292
- export default CacheHeaders;
293
- export {UIStrings};