lighthouse 11.3.0 → 11.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/core/audits/accessibility/aria-allowed-role.js +1 -1
  2. package/core/audits/bf-cache.d.ts +1 -0
  3. package/core/audits/bf-cache.js +12 -2
  4. package/core/audits/bootup-time.js +3 -0
  5. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -10
  6. package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -46
  7. package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -6
  8. package/core/audits/byte-efficiency/duplicated-javascript.js +9 -38
  9. package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -11
  10. package/core/audits/byte-efficiency/legacy-javascript.js +5 -36
  11. package/core/audits/byte-efficiency/unminified-css.js +2 -2
  12. package/core/audits/byte-efficiency/unminified-javascript.js +2 -3
  13. package/core/audits/byte-efficiency/unused-javascript.js +10 -11
  14. package/core/audits/dobetterweb/uses-http2.d.ts +1 -1
  15. package/core/audits/dobetterweb/uses-http2.js +8 -5
  16. package/core/audits/layout-shift-elements.js +32 -17
  17. package/core/audits/metrics/cumulative-layout-shift.js +5 -1
  18. package/core/audits/metrics/max-potential-fid.d.ts +20 -0
  19. package/core/audits/metrics/max-potential-fid.js +69 -0
  20. package/core/audits/resource-summary.d.ts +11 -0
  21. package/core/audits/resource-summary.js +86 -0
  22. package/core/audits/third-party-cookies.d.ts +22 -0
  23. package/core/audits/third-party-cookies.js +112 -0
  24. package/core/audits/third-party-summary.d.ts +20 -2
  25. package/core/audits/third-party-summary.js +3 -48
  26. package/core/computed/entity-classification.js +1 -1
  27. package/core/computed/metrics/cumulative-layout-shift.d.ts +13 -1
  28. package/core/computed/metrics/cumulative-layout-shift.js +47 -1
  29. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +50 -16
  30. package/core/computed/metrics/lantern-first-contentful-paint.js +56 -59
  31. package/core/computed/metrics/lantern-first-meaningful-paint.js +10 -12
  32. package/core/computed/metrics/lantern-largest-contentful-paint.js +9 -12
  33. package/core/computed/metrics/lantern-metric.d.ts +2 -2
  34. package/core/computed/metrics/lantern-metric.js +6 -5
  35. package/core/computed/resource-summary.d.ts +0 -1
  36. package/core/computed/resource-summary.js +2 -2
  37. package/core/computed/unused-css.js +2 -2
  38. package/core/config/constants.d.ts +2 -2
  39. package/core/config/constants.js +2 -2
  40. package/core/config/default-config.js +4 -0
  41. package/core/config/filters.js +1 -0
  42. package/core/gather/base-artifacts.js +2 -1
  43. package/core/gather/gatherers/css-usage.js +5 -0
  44. package/core/gather/gatherers/trace-elements.d.ts +6 -15
  45. package/core/gather/gatherers/trace-elements.js +15 -71
  46. package/core/gather/navigation-runner.js +1 -1
  47. package/core/lib/i18n/i18n.d.ts +2 -2
  48. package/core/lib/i18n/i18n.js +2 -2
  49. package/core/lib/minification-estimator.js +1 -1
  50. package/core/lib/network-recorder.d.ts +8 -0
  51. package/core/lib/network-recorder.js +13 -0
  52. package/core/lib/network-request.d.ts +11 -0
  53. package/core/lib/network-request.js +21 -1
  54. package/core/lib/rect-helpers.d.ts +5 -0
  55. package/core/lib/rect-helpers.js +15 -0
  56. package/core/lib/script-helpers.d.ts +34 -5
  57. package/core/lib/script-helpers.js +136 -0
  58. package/core/lib/url-utils.d.ts +6 -0
  59. package/core/lib/url-utils.js +14 -2
  60. package/core/runner.js +5 -1
  61. package/core/scripts/download-chrome.sh +56 -13
  62. package/dist/report/bundle.esm.js +30 -13
  63. package/dist/report/flow.js +32 -15
  64. package/dist/report/standalone.js +32 -15
  65. package/flow-report/src/i18n/i18n.d.ts +4 -0
  66. package/package.json +5 -4
  67. package/report/assets/styles.css +25 -8
  68. package/report/assets/templates.html +1 -1
  69. package/report/clients/standalone.js +1 -0
  70. package/report/renderer/components.js +2 -2
  71. package/report/renderer/explodey-gauge.js +0 -1
  72. package/report/renderer/performance-category-renderer.js +4 -2
  73. package/report/renderer/report-renderer.js +12 -1
  74. package/report/renderer/report-ui-features.js +3 -2
  75. package/report/renderer/report-utils.d.ts +2 -0
  76. package/report/renderer/report-utils.js +8 -4
  77. package/report/types/report-renderer.d.ts +2 -0
  78. package/shared/localization/locales/ar-XB.json +34 -25
  79. package/shared/localization/locales/ar.json +35 -26
  80. package/shared/localization/locales/bg.json +34 -25
  81. package/shared/localization/locales/ca.json +35 -26
  82. package/shared/localization/locales/cs.json +34 -25
  83. package/shared/localization/locales/da.json +51 -42
  84. package/shared/localization/locales/de.json +38 -29
  85. package/shared/localization/locales/el.json +34 -25
  86. package/shared/localization/locales/en-GB.json +35 -26
  87. package/shared/localization/locales/en-US.json +26 -5
  88. package/shared/localization/locales/en-XA.json +34 -25
  89. package/shared/localization/locales/en-XL.json +26 -5
  90. package/shared/localization/locales/es-419.json +34 -25
  91. package/shared/localization/locales/es.json +34 -25
  92. package/shared/localization/locales/fi.json +34 -25
  93. package/shared/localization/locales/fil.json +35 -26
  94. package/shared/localization/locales/fr.json +34 -25
  95. package/shared/localization/locales/he.json +35 -26
  96. package/shared/localization/locales/hi.json +35 -26
  97. package/shared/localization/locales/hr.json +35 -26
  98. package/shared/localization/locales/hu.json +34 -25
  99. package/shared/localization/locales/id.json +34 -25
  100. package/shared/localization/locales/it.json +34 -25
  101. package/shared/localization/locales/ja.json +34 -25
  102. package/shared/localization/locales/ko.json +35 -26
  103. package/shared/localization/locales/lt.json +34 -25
  104. package/shared/localization/locales/lv.json +35 -26
  105. package/shared/localization/locales/nl.json +35 -26
  106. package/shared/localization/locales/no.json +34 -25
  107. package/shared/localization/locales/pl.json +34 -25
  108. package/shared/localization/locales/pt-PT.json +35 -26
  109. package/shared/localization/locales/pt.json +35 -26
  110. package/shared/localization/locales/ro.json +34 -25
  111. package/shared/localization/locales/ru.json +35 -26
  112. package/shared/localization/locales/sk.json +35 -26
  113. package/shared/localization/locales/sl.json +35 -26
  114. package/shared/localization/locales/sr-Latn.json +34 -25
  115. package/shared/localization/locales/sr.json +34 -25
  116. package/shared/localization/locales/sv.json +35 -26
  117. package/shared/localization/locales/ta.json +35 -26
  118. package/shared/localization/locales/te.json +35 -26
  119. package/shared/localization/locales/th.json +34 -25
  120. package/shared/localization/locales/tr.json +35 -26
  121. package/shared/localization/locales/uk.json +34 -25
  122. package/shared/localization/locales/vi.json +34 -25
  123. package/shared/localization/locales/zh-HK.json +35 -26
  124. package/shared/localization/locales/zh-TW.json +38 -29
  125. package/shared/localization/locales/zh.json +34 -25
  126. package/shared/util.d.ts +14 -2
  127. package/shared/util.js +25 -3
  128. package/tsconfig.json +2 -0
  129. package/types/artifacts.d.ts +5 -2
@@ -22,7 +22,7 @@ const UIStrings = {
22
22
  'the web page. If the `role` values are misspelled, not existing ARIA `role` values, or ' +
23
23
  'abstract roles, then the purpose of the element will not be communicated to users of ' +
24
24
  'assistive technologies. ' +
25
- '[Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.8/aria-allowed-roles).',
25
+ '[Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.8/aria-allowed-role).',
26
26
  };
27
27
 
28
28
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -15,6 +15,7 @@ export namespace UIStrings {
15
15
  const supportPendingFailureType: string;
16
16
  const failureReasonColumn: string;
17
17
  const failureTypeColumn: string;
18
+ const warningHeadless: string;
18
19
  const displayValue: string;
19
20
  }
20
21
  import { Audit } from './audit.js';
@@ -26,6 +26,8 @@ const UIStrings = {
26
26
  failureReasonColumn: 'Failure reason',
27
27
  /** Label for a column in a data table; entries in the column will be a string representing the type of failure preventing the back/forward cache from being used. */
28
28
  failureTypeColumn: 'Failure type',
29
+ /** Warning explaining that the back/forward cache results cannot be shown in the old Headless Chrome. "back/forward" refers to the back and forward buttons found in modern browsers. "Headless Chrome" is a product name and should not be translated. */
30
+ warningHeadless: 'Back/forward cache cannot be tested in old Headless Chrome (`--chrome-flags="--headless=old"`). To see audit results, use the new Headless Chrome (`--chrome-flags="--headless=new"`) or standard Chrome.',
29
31
  /**
30
32
  * @description [ICU Syntax] Label for an audit identifying the number of back/forward cache failure reasons found in the page.
31
33
  */
@@ -60,8 +62,8 @@ class BFCache extends Audit {
60
62
  description: str_(UIStrings.description),
61
63
  supportedModes: ['navigation', 'timespan'],
62
64
  guidanceLevel: 2,
63
- requiredArtifacts: ['BFCacheFailures'],
64
- scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
65
+ requiredArtifacts: ['BFCacheFailures', 'HostProduct'],
66
+ scoreDisplayMode: Audit.SCORING_MODES.BINARY,
65
67
  };
66
68
  }
67
69
 
@@ -70,6 +72,14 @@ class BFCache extends Audit {
70
72
  * @return {Promise<LH.Audit.Product>}
71
73
  */
72
74
  static async audit(artifacts) {
75
+ if (/HeadlessChrome/.test(artifacts.HostProduct)) {
76
+ return {
77
+ score: null,
78
+ notApplicable: true,
79
+ warnings: [str_(UIStrings.warningHeadless)],
80
+ };
81
+ }
82
+
73
83
  const failures = artifacts.BFCacheFailures;
74
84
  if (!failures.length) return {score: 1};
75
85
 
@@ -109,6 +109,9 @@ class BootupTime extends Audit {
109
109
  settings.throttling.cpuSlowdownMultiplier : 1;
110
110
 
111
111
  const executionTimings = getExecutionTimingsByURL(tasks, networkRecords);
112
+ // Exclude our own tasks.
113
+ executionTimings.delete('_lighthouse-eval.js');
114
+
112
115
  const tbtImpact = await this.getTbtImpact(artifacts, context);
113
116
 
114
117
  let hadExcessiveChromeExtension = false;
@@ -33,16 +33,6 @@ export class ByteEfficiencyAudit extends Audit {
33
33
  * @return {number}
34
34
  */
35
35
  static scoreForWastedMs(wastedMs: number): number;
36
- /**
37
- * Estimates the number of bytes this network record would have consumed on the network based on the
38
- * uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable.
39
- *
40
- * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
41
- * @param {number} totalBytes Uncompressed size of the resource
42
- * @param {LH.Crdp.Network.ResourceType=} resourceType
43
- * @return {number}
44
- */
45
- static estimateTransferSize(networkRecord: LH.Artifacts.NetworkRequest | undefined, totalBytes: number, resourceType?: LH.Crdp.Network.ResourceType | undefined): number;
46
36
  /**
47
37
  * @param {LH.Artifacts} artifacts
48
38
  * @param {LH.Audit.Context} context
@@ -58,48 +58,6 @@ class ByteEfficiencyAudit extends Audit {
58
58
  );
59
59
  }
60
60
 
61
- /**
62
- * Estimates the number of bytes this network record would have consumed on the network based on the
63
- * uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable.
64
- *
65
- * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
66
- * @param {number} totalBytes Uncompressed size of the resource
67
- * @param {LH.Crdp.Network.ResourceType=} resourceType
68
- * @return {number}
69
- */
70
- static estimateTransferSize(networkRecord, totalBytes, resourceType) {
71
- if (!networkRecord) {
72
- // We don't know how many bytes this asset used on the network, but we can guess it was
73
- // roughly the size of the content gzipped.
74
- // See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples
75
- // See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers
76
- switch (resourceType) {
77
- case 'Stylesheet':
78
- // Stylesheets tend to compress extremely well.
79
- return Math.round(totalBytes * 0.2);
80
- case 'Script':
81
- case 'Document':
82
- // Scripts and HTML compress fairly well too.
83
- return Math.round(totalBytes * 0.33);
84
- default:
85
- // Otherwise we'll just fallback to the average savings in HTTPArchive
86
- return Math.round(totalBytes * 0.5);
87
- }
88
- } else if (networkRecord.resourceType === resourceType) {
89
- // This was a regular standalone asset, just use the transfer size.
90
- return networkRecord.transferSize || 0;
91
- } else {
92
- // This was an asset that was inlined in a different resource type (e.g. HTML document).
93
- // Use the compression ratio of the resource to estimate the total transferred bytes.
94
- const transferSize = networkRecord.transferSize || 0;
95
- const resourceSize = networkRecord.resourceSize || 0;
96
- // Get the compression ratio, if it's an invalid number, assume no compression.
97
- const compressionRatio = Number.isFinite(resourceSize) && resourceSize > 0 ?
98
- (transferSize / resourceSize) : 1;
99
- return Math.round(totalBytes * compressionRatio);
100
- }
101
- }
102
-
103
61
  /**
104
62
  * @param {LH.Artifacts} artifacts
105
63
  * @param {LH.Audit.Context} context
@@ -248,10 +206,10 @@ class ByteEfficiencyAudit extends Audit {
248
206
  if (metricComputationInput.gatherContext.gatherMode === 'navigation') {
249
207
  const graph = await PageDependencyGraph.request(metricComputationInput, context);
250
208
  const {
251
- pessimisticGraph: pessimisticFCPGraph,
209
+ optimisticGraph: optimisticFCPGraph,
252
210
  } = await LanternFirstContentfulPaint.request(metricComputationInput, context);
253
211
  const {
254
- pessimisticGraph: pessimisticLCPGraph,
212
+ optimisticGraph: optimisticLCPGraph,
255
213
  } = await LanternLargestContentfulPaint.request(metricComputationInput, context);
256
214
 
257
215
  wastedMs = this.computeWasteWithTTIGraph(results, graph, simulator, {
@@ -260,17 +218,19 @@ class ByteEfficiencyAudit extends Audit {
260
218
 
261
219
  const {savings: fcpSavings} = this.computeWasteWithGraph(
262
220
  results,
263
- pessimisticFCPGraph,
221
+ optimisticFCPGraph,
264
222
  simulator,
265
223
  {providedWastedBytesByUrl: result.wastedBytesByUrl, label: 'fcp'}
266
224
  );
225
+ // Note: LCP's optimistic graph sometimes unexpectedly yields higher savings than the pessimistic graph.
267
226
  const {savings: lcpGraphSavings} = this.computeWasteWithGraph(
268
227
  results,
269
- pessimisticLCPGraph,
228
+ optimisticLCPGraph,
270
229
  simulator,
271
230
  {providedWastedBytesByUrl: result.wastedBytesByUrl, label: 'lcp'}
272
231
  );
273
232
 
233
+
274
234
  // The LCP graph can underestimate the LCP savings if there is potential savings on the LCP record itself.
275
235
  let lcpRecordSavings = 0;
276
236
  const lcpRecord = await LCPImageRecord.request(metricComputationInput, context);
@@ -25,12 +25,6 @@ declare class DuplicatedJavascript extends ByteEfficiencyAudit {
25
25
  scriptUrl: string;
26
26
  resourceSize: number;
27
27
  }[]>>;
28
- /**
29
- *
30
- * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
31
- * @param {number} contentLength
32
- */
33
- static _estimateTransferRatio(networkRecord: LH.Artifacts.NetworkRequest | undefined, contentLength: number): number;
34
28
  /**
35
29
  * This audit highlights JavaScript modules that appear to be duplicated across all resources,
36
30
  * either within the same bundle or between different bundles. Each details item returned is
@@ -11,7 +11,7 @@
11
11
  import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
12
12
  import {ModuleDuplication} from '../../computed/module-duplication.js';
13
13
  import * as i18n from '../../lib/i18n/i18n.js';
14
- import {getRequestForScript} from '../../lib/script-helpers.js';
14
+ import {estimateCompressionRatioForContent} from '../../lib/script-helpers.js';
15
15
 
16
16
  const UIStrings = {
17
17
  /** Imperative title of a Lighthouse audit that tells the user to remove duplicate JavaScript from their code. This is displayed in a list of audit titles that Lighthouse generates. */
@@ -103,17 +103,6 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
103
103
  return groupedDuplication;
104
104
  }
105
105
 
106
- /**
107
- *
108
- * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
109
- * @param {number} contentLength
110
- */
111
- static _estimateTransferRatio(networkRecord, contentLength) {
112
- const transferSize =
113
- ByteEfficiencyAudit.estimateTransferSize(networkRecord, contentLength, 'Script');
114
- return transferSize / contentLength;
115
- }
116
-
117
106
  /**
118
107
  * This audit highlights JavaScript modules that appear to be duplicated across all resources,
119
108
  * either within the same bundle or between different bundles. Each details item returned is
@@ -132,7 +121,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
132
121
  await DuplicatedJavascript._getDuplicationGroupedByNodeModules(artifacts, context);
133
122
 
134
123
  /** @type {Map<string, number>} */
135
- const transferRatioByUrl = new Map();
124
+ const compressionRatioByUrl = new Map();
136
125
 
137
126
  /** @type {Item[]} */
138
127
  const items = [];
@@ -145,10 +134,10 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
145
134
  for (const [source, sourceDatas] of duplication.entries()) {
146
135
  // One copy of this module is treated as the canonical version - the rest will have
147
136
  // non-zero `wastedBytes`. In the case of all copies being the same version, all sizes are
148
- // equal and the selection doesn't matter. When the copies are different versions, it does
149
- // matter. Ideally the newest version would be the canonical copy, but version information
150
- // is not present. Instead, size is used as a heuristic for latest version. This makes the
151
- // audit conserative in its estimation.
137
+ // equal and the selection doesn't matter (ignoring compression ratios). When the copies are
138
+ // different versions, it does matter. Ideally the newest version would be the canonical
139
+ // copy, but version information is not present. Instead, size is used as a heuristic for
140
+ // latest version. This makes the audit conserative in its estimation.
152
141
 
153
142
  /** @type {SubItem[]} */
154
143
  const subItems = [];
@@ -159,27 +148,9 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
159
148
  const scriptId = sourceData.scriptId;
160
149
  const script = artifacts.Scripts.find(script => script.scriptId === scriptId);
161
150
  const url = script?.url || '';
162
-
163
- /** @type {number|undefined} */
164
- let transferRatio = transferRatioByUrl.get(url);
165
- if (transferRatio === undefined) {
166
- if (!script || script.length === undefined) {
167
- // This should never happen because we found the wasted bytes from bundles, which required contents in a Script.
168
- continue;
169
- }
170
-
171
- const contentLength = script.length;
172
- const networkRecord = getRequestForScript(networkRecords, script);
173
- transferRatio = DuplicatedJavascript._estimateTransferRatio(networkRecord, contentLength);
174
- transferRatioByUrl.set(url, transferRatio);
175
- }
176
-
177
- if (transferRatio === undefined) {
178
- // Shouldn't happen for above reasons.
179
- continue;
180
- }
181
-
182
- const transferSize = Math.round(sourceData.resourceSize * transferRatio);
151
+ const compressionRatio = estimateCompressionRatioForContent(
152
+ compressionRatioByUrl, url, artifacts, networkRecords);
153
+ const transferSize = Math.round(sourceData.resourceSize * compressionRatio);
183
154
 
184
155
  subItems.push({
185
156
  url,
@@ -60,17 +60,6 @@ declare class LegacyJavascript extends ByteEfficiencyAudit {
60
60
  * @return {number}
61
61
  */
62
62
  static estimateWastedBytes(matches: PatternMatchResult[]): number;
63
- /**
64
- * Utility function to estimate transfer size and cache calculation.
65
- *
66
- * Note: duplicated-javascript does this exact thing. In the future, consider
67
- * making a generic estimator on ByteEfficienyAudit.
68
- * @param {Map<string, number>} transferRatioByUrl
69
- * @param {string} url
70
- * @param {LH.Artifacts} artifacts
71
- * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
72
- */
73
- static estimateTransferRatioForScript(transferRatioByUrl: Map<string, number>, url: string, artifacts: LH.Artifacts, networkRecords: Array<LH.Artifacts.NetworkRequest>): Promise<number>;
74
63
  /**
75
64
  * @param {LH.Artifacts} artifacts
76
65
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -24,7 +24,7 @@ import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
24
24
  import {EntityClassification} from '../../computed/entity-classification.js';
25
25
  import {JSBundles} from '../../computed/js-bundles.js';
26
26
  import * as i18n from '../../lib/i18n/i18n.js';
27
- import {getRequestForScript} from '../../lib/script-helpers.js';
27
+ import {estimateCompressionRatioForContent} from '../../lib/script-helpers.js';
28
28
  import {LH_ROOT} from '../../../shared/root.js';
29
29
 
30
30
  const graphJson = fs.readFileSync(
@@ -390,37 +390,6 @@ class LegacyJavascript extends ByteEfficiencyAudit {
390
390
  return estimatedWastedBytes;
391
391
  }
392
392
 
393
- /**
394
- * Utility function to estimate transfer size and cache calculation.
395
- *
396
- * Note: duplicated-javascript does this exact thing. In the future, consider
397
- * making a generic estimator on ByteEfficienyAudit.
398
- * @param {Map<string, number>} transferRatioByUrl
399
- * @param {string} url
400
- * @param {LH.Artifacts} artifacts
401
- * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
402
- */
403
- static async estimateTransferRatioForScript(transferRatioByUrl, url, artifacts, networkRecords) {
404
- let transferRatio = transferRatioByUrl.get(url);
405
- if (transferRatio !== undefined) return transferRatio;
406
-
407
- const script = artifacts.Scripts.find(script => script.url === url);
408
-
409
- if (!script || script.content === null) {
410
- // Can't find content, so just use 1.
411
- transferRatio = 1;
412
- } else {
413
- const networkRecord = getRequestForScript(networkRecords, script);
414
- const contentLength = script.length || 0;
415
- const transferSize =
416
- ByteEfficiencyAudit.estimateTransferSize(networkRecord, contentLength, 'Script');
417
- transferRatio = transferSize / contentLength;
418
- }
419
-
420
- transferRatioByUrl.set(url, transferRatio);
421
- return transferRatio;
422
- }
423
-
424
393
  /**
425
394
  * @param {LH.Artifacts} artifacts
426
395
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -443,14 +412,14 @@ class LegacyJavascript extends ByteEfficiencyAudit {
443
412
  ]);
444
413
 
445
414
  /** @type {Map<string, number>} */
446
- const transferRatioByUrl = new Map();
415
+ const compressionRatioByUrl = new Map();
447
416
 
448
417
  const scriptToMatchResults =
449
418
  this.detectAcrossScripts(matcher, artifacts.Scripts, networkRecords, bundles);
450
419
  for (const [script, matches] of scriptToMatchResults.entries()) {
451
- const transferRatio = await this.estimateTransferRatioForScript(
452
- transferRatioByUrl, script.url, artifacts, networkRecords);
453
- const wastedBytes = Math.round(this.estimateWastedBytes(matches) * transferRatio);
420
+ const compressionRatio = estimateCompressionRatioForContent(
421
+ compressionRatioByUrl, script.url, artifacts, networkRecords);
422
+ const wastedBytes = Math.round(this.estimateWastedBytes(matches) * compressionRatio);
454
423
  /** @type {typeof items[number]} */
455
424
  const item = {
456
425
  url: script.url,
@@ -8,6 +8,7 @@ import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
8
8
  import {UnusedCSS} from '../../computed/unused-css.js';
9
9
  import * as i18n from '../../lib/i18n/i18n.js';
10
10
  import {computeCSSTokenLength as computeTokenLength} from '../../lib/minification-estimator.js';
11
+ import {estimateTransferSize} from '../../lib/script-helpers.js';
11
12
 
12
13
  const UIStrings = {
13
14
  /** Imperative title of a Lighthouse audit that tells the user to minify (remove whitespace) the page's CSS code. This is displayed in a list of audit titles that Lighthouse generates. */
@@ -65,8 +66,7 @@ class UnminifiedCSS extends ByteEfficiencyAudit {
65
66
  url = contentPreview;
66
67
  }
67
68
 
68
- const totalBytes = ByteEfficiencyAudit.estimateTransferSize(networkRecord, content.length,
69
- 'Stylesheet');
69
+ const totalBytes = estimateTransferSize(networkRecord, content.length, 'Stylesheet');
70
70
  const wastedRatio = 1 - totalTokenLength / content.length;
71
71
  const wastedBytes = Math.round(totalBytes * wastedRatio);
72
72
 
@@ -7,7 +7,7 @@
7
7
  import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
8
8
  import * as i18n from '../../lib/i18n/i18n.js';
9
9
  import {computeJSTokenLength as computeTokenLength} from '../../lib/minification-estimator.js';
10
- import {getRequestForScript, isInline} from '../../lib/script-helpers.js';
10
+ import {estimateCompressedContentSize, getRequestForScript, isInline} from '../../lib/script-helpers.js';
11
11
  import {Util} from '../../../shared/util.js';
12
12
 
13
13
  const UIStrings = {
@@ -58,8 +58,7 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit {
58
58
  const contentLength = scriptContent.length;
59
59
  const totalTokenLength = computeTokenLength(scriptContent);
60
60
 
61
- const totalBytes = ByteEfficiencyAudit.estimateTransferSize(networkRecord, contentLength,
62
- 'Script');
61
+ const totalBytes = estimateCompressedContentSize(networkRecord, contentLength, 'Script');
63
62
  const wastedRatio = 1 - totalTokenLength / contentLength;
64
63
  const wastedBytes = Math.round(totalBytes * wastedRatio);
65
64
 
@@ -8,7 +8,7 @@ import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
8
8
  import {UnusedJavascriptSummary} from '../../computed/unused-javascript-summary.js';
9
9
  import {JSBundles} from '../../computed/js-bundles.js';
10
10
  import * as i18n from '../../lib/i18n/i18n.js';
11
- import {getRequestForScript} from '../../lib/script-helpers.js';
11
+ import {estimateCompressionRatioForContent} from '../../lib/script-helpers.js';
12
12
 
13
13
  const UIStrings = {
14
14
  /** Imperative title of a Lighthouse audit that tells the user to reduce JavaScript that is never evaluated during page load. This is displayed in a list of audit titles that Lighthouse generates. */
@@ -86,27 +86,26 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
86
86
  bundleSourceUnusedThreshold = UNUSED_BYTES_IGNORE_BUNDLE_SOURCE_THRESHOLD,
87
87
  } = context.options || {};
88
88
 
89
+ /** @type {Map<string, number>} */
90
+ const compressionRatioByUrl = new Map();
91
+
89
92
  const items = [];
90
93
  for (const [scriptId, scriptCoverage] of Object.entries(artifacts.JsUsage)) {
91
94
  const script = artifacts.Scripts.find(s => s.scriptId === scriptId);
92
95
  if (!script) continue; // This should never happen.
93
96
 
94
- const networkRecord = getRequestForScript(networkRecords, script);
95
- if (!networkRecord) continue;
96
-
97
97
  const bundle = bundles.find(b => b.script.scriptId === scriptId);
98
98
  const unusedJsSummary =
99
99
  await UnusedJavascriptSummary.request({scriptId, scriptCoverage, bundle}, context);
100
100
  if (unusedJsSummary.wastedBytes === 0 || unusedJsSummary.totalBytes === 0) continue;
101
101
 
102
- const transfer = ByteEfficiencyAudit
103
- .estimateTransferSize(networkRecord, unusedJsSummary.totalBytes, 'Script');
104
- const transferRatio = transfer / unusedJsSummary.totalBytes;
102
+ const compressionRatio = estimateCompressionRatioForContent(
103
+ compressionRatioByUrl, script.url, artifacts, networkRecords);
105
104
  /** @type {LH.Audit.ByteEfficiencyItem} */
106
105
  const item = {
107
106
  url: script.url,
108
- totalBytes: Math.round(transferRatio * unusedJsSummary.totalBytes),
109
- wastedBytes: Math.round(transferRatio * unusedJsSummary.wastedBytes),
107
+ totalBytes: Math.round(compressionRatio * unusedJsSummary.totalBytes),
108
+ wastedBytes: Math.round(compressionRatio * unusedJsSummary.wastedBytes),
110
109
  wastedPercent: unusedJsSummary.wastedPercent,
111
110
  };
112
111
 
@@ -127,8 +126,8 @@ class UnusedJavaScript extends ByteEfficiencyAudit {
127
126
  const total = source === '(unmapped)' ? sizes.unmappedBytes : sizes.files[source];
128
127
  return {
129
128
  source,
130
- unused: Math.round(unused * transferRatio),
131
- total: Math.round(total * transferRatio),
129
+ unused: Math.round(unused * compressionRatio),
130
+ total: Math.round(total * compressionRatio),
132
131
  };
133
132
  })
134
133
  .filter(d => d.unused >= bundleSourceUnusedThreshold);
@@ -42,7 +42,7 @@ declare class UsesHTTP2Audit extends Audit {
42
42
  * @param {LH.Artifacts.EntityClassification} classifiedEntities
43
43
  * @return {boolean}
44
44
  */
45
- static isStaticAsset(networkRequest: LH.Artifacts.NetworkRequest, classifiedEntities: LH.Artifacts.EntityClassification): boolean;
45
+ static isMultiplexableStaticAsset(networkRequest: LH.Artifacts.NetworkRequest, classifiedEntities: LH.Artifacts.EntityClassification): boolean;
46
46
  /**
47
47
  * Determine the set of resources that aren't HTTP/2 but should be.
48
48
  * We're a little conservative about what we surface for a few reasons:
@@ -152,16 +152,19 @@ class UsesHTTP2Audit extends Audit {
152
152
  * @param {LH.Artifacts.EntityClassification} classifiedEntities
153
153
  * @return {boolean}
154
154
  */
155
- static isStaticAsset(networkRequest, classifiedEntities) {
155
+ static isMultiplexableStaticAsset(networkRequest, classifiedEntities) {
156
156
  if (!STATIC_RESOURCE_TYPES.has(networkRequest.resourceType)) return false;
157
157
 
158
158
  // Resources from third-parties that are less than 100 bytes are usually tracking pixels, not actual resources.
159
159
  // They can masquerade as static types though (gifs, documents, etc)
160
160
  if (networkRequest.resourceSize < 100) {
161
- // This logic needs to be revisited.
162
- // See https://github.com/GoogleChrome/lighthouse/issues/14661
163
161
  const entity = classifiedEntities.entityByUrl.get(networkRequest.url);
164
- if (entity && !entity.isUnrecognized) return false;
162
+ if (entity) {
163
+ // Third-party assets are multiplexable in their first-party context.
164
+ if (classifiedEntities.firstParty?.name === entity.name) return true;
165
+ // Skip recognizable third-parties' requests.
166
+ if (!entity.isUnrecognized) return false;
167
+ }
165
168
  }
166
169
 
167
170
  return true;
@@ -199,7 +202,7 @@ class UsesHTTP2Audit extends Audit {
199
202
  /** @type {Map<string, Array<LH.Artifacts.NetworkRequest>>} */
200
203
  const groupedByOrigin = new Map();
201
204
  for (const record of networkRecords) {
202
- if (!UsesHTTP2Audit.isStaticAsset(record, classifiedEntities)) continue;
205
+ if (!UsesHTTP2Audit.isMultiplexableStaticAsset(record, classifiedEntities)) continue;
203
206
  if (UrlUtils.isLikeLocalhost(record.parsedURL.host)) continue;
204
207
  const existing = groupedByOrigin.get(record.parsedURL.securityOrigin) || [];
205
208
  existing.push(record);
@@ -9,12 +9,12 @@ import {CumulativeLayoutShift as CumulativeLayoutShiftComputed} from '../compute
9
9
  import CumulativeLayoutShift from './metrics/cumulative-layout-shift.js';
10
10
 
11
11
  const UIStrings = {
12
- /** Descriptive title of a diagnostic audit that provides up to the top five elements contributing to Cumulative Layout Shift. */
12
+ /** Descriptive title of a diagnostic audit that provides the top elements affected by layout shifts. */
13
13
  title: 'Avoid large layout shifts',
14
- /** Description of a diagnostic audit that provides up to the top five elements contributing to Cumulative Layout Shift. The last sentence starting with 'Learn' becomes link text to additional documentation. */
15
- description: 'These DOM elements contribute most to the CLS of the page. [Learn how to improve CLS](https://web.dev/articles/optimize-cls)',
16
- /** Label for a column in a data table; entries in this column will be the amount that the corresponding element contributes to the total CLS metric score. */
17
- columnContribution: 'CLS Contribution',
14
+ /** Description of a diagnostic audit that provides the top elements affected by layout shifts. "windowing" means the metric value is calculated using the subset of events in a small window of time during the run. "normalization" is a good substitute for "windowing". The last sentence starting with 'Learn' becomes link text to additional documentation. */
15
+ description: 'These DOM elements were most affected by layout shifts. Some layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)',
16
+ /** Label for a column in a data table; entries in this column will be the amount that the corresponding element affected by layout shifts. */
17
+ columnContribution: 'Layout shift impact',
18
18
  };
19
19
 
20
20
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
@@ -40,15 +40,32 @@ class LayoutShiftElements extends Audit {
40
40
  * @return {Promise<LH.Audit.Product>}
41
41
  */
42
42
  static async audit(artifacts, context) {
43
- const clsElements = artifacts.TraceElements
44
- .filter(element => element.traceEventType === 'layout-shift');
43
+ const {cumulativeLayoutShift: clsSavings, impactByNodeId} =
44
+ await CumulativeLayoutShiftComputed.request(artifacts.traces[Audit.DEFAULT_PASS], context);
45
45
 
46
- const clsElementData = clsElements.map(element => {
47
- return {
46
+ /** @type {Array<{node: LH.Audit.Details.ItemValue, score: number}>} */
47
+ const clsElementData = artifacts.TraceElements
48
+ .filter(element => element.traceEventType === 'layout-shift')
49
+ .map(element => ({
48
50
  node: Audit.makeNodeItem(element.node),
49
- score: element.score,
50
- };
51
- });
51
+ score: impactByNodeId.get(element.nodeId) || 0,
52
+ }));
53
+
54
+ if (clsElementData.length < impactByNodeId.size) {
55
+ const displayedNodeImpact = clsElementData.reduce((sum, {score}) => sum += score, 0);
56
+
57
+ // This is not necessarily the same as CLS due to normalization.
58
+ const totalNodeImpact = Array.from(impactByNodeId.values())
59
+ .reduce((sum, score) => sum + score);
60
+
61
+ clsElementData.push({
62
+ node: {
63
+ type: 'code',
64
+ value: str_(i18n.UIStrings.otherResourceType),
65
+ },
66
+ score: totalNodeImpact - displayedNodeImpact,
67
+ });
68
+ }
52
69
 
53
70
  /** @type {LH.Audit.Details.Table['headings']} */
54
71
  const headings = [
@@ -58,15 +75,13 @@ class LayoutShiftElements extends Audit {
58
75
  ];
59
76
 
60
77
  const details = Audit.makeTableDetails(headings, clsElementData);
78
+
61
79
  let displayValue;
62
- if (clsElementData.length > 0) {
80
+ if (impactByNodeId.size > 0) {
63
81
  displayValue = str_(i18n.UIStrings.displayValueElementsFound,
64
- {nodeCount: clsElementData.length});
82
+ {nodeCount: impactByNodeId.size});
65
83
  }
66
84
 
67
- const {cumulativeLayoutShift: clsSavings} =
68
- await CumulativeLayoutShiftComputed.request(artifacts.traces[Audit.DEFAULT_PASS], context);
69
-
70
85
  const passed = clsSavings <= CumulativeLayoutShift.defaultOptions.p10;
71
86
 
72
87
  return {
@@ -54,7 +54,11 @@ class CumulativeLayoutShift extends Audit {
54
54
  */
55
55
  static async audit(artifacts, context) {
56
56
  const trace = artifacts.traces[Audit.DEFAULT_PASS];
57
- const {cumulativeLayoutShift, ...rest} = await ComputedCLS.request(trace, context);
57
+
58
+ // impactByNodeId is unused but we don't want it on debug data
59
+ // eslint-disable-next-line no-unused-vars
60
+ const {cumulativeLayoutShift, impactByNodeId, ...rest} =
61
+ await ComputedCLS.request(trace, context);
58
62
 
59
63
  /** @type {LH.Audit.Details.DebugData} */
60
64
  const details = {
@@ -1,4 +1,14 @@
1
1
  export default MaxPotentialFID;
2
+ export type LoafDebugDetails = {
3
+ type: 'debugdata';
4
+ observedMaxDurationLoaf?: LH.TraceEvent | undefined;
5
+ observedMaxBlockingLoaf?: LH.TraceEvent | undefined;
6
+ observedLoafs: Array<{
7
+ startTime: number;
8
+ duration: number;
9
+ blockingDuration: number;
10
+ }>;
11
+ };
2
12
  /**
3
13
  * @fileoverview This metric is the duration of the longest task after FCP. It is meant to capture
4
14
  * the worst case First Input Delay that a user might experience.
@@ -9,6 +19,14 @@ declare class MaxPotentialFID extends Audit {
9
19
  * @return {LH.Audit.ScoreOptions}
10
20
  */
11
21
  static get defaultOptions(): import("../../../types/audit.js").default.ScoreOptions;
22
+ /**
23
+ * Extract potential LoAF replacements for MPFID from the trace to log in
24
+ * debugdata details.
25
+ * @param {LH.Artifacts.ProcessedTrace} processedTrace
26
+ * @param {LH.Artifacts.ProcessedNavigation} processedNavigation
27
+ * @return {LoafDebugDetails|undefined}
28
+ */
29
+ static getLongAnimationFrameDetails(processedTrace: LH.Artifacts.ProcessedTrace, processedNavigation: LH.Artifacts.ProcessedNavigation): LoafDebugDetails | undefined;
12
30
  /**
13
31
  * @param {LH.Artifacts} artifacts
14
32
  * @param {LH.Audit.Context} context
@@ -20,4 +38,6 @@ export namespace UIStrings {
20
38
  const description: string;
21
39
  }
22
40
  import { Audit } from '../audit.js';
41
+ import { ProcessedTrace } from '../../computed/processed-trace.js';
42
+ import { ProcessedNavigation } from '../../computed/processed-navigation.js';
23
43
  //# sourceMappingURL=max-potential-fid.d.ts.map