lighthouse 11.7.0-dev.20240408 → 11.7.1

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 (141) hide show
  1. package/CONTRIBUTING.md +2 -2
  2. package/cli/test/smokehouse/core-tests.js +2 -0
  3. package/core/audits/accessibility/duplicate-id-active.d.ts +10 -0
  4. package/core/audits/accessibility/duplicate-id-active.js +42 -0
  5. package/core/audits/accessibility/target-size.js +1 -0
  6. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +16 -0
  7. package/core/audits/byte-efficiency/byte-efficiency-audit.js +38 -1
  8. package/core/audits/byte-efficiency/offscreen-images.d.ts +11 -0
  9. package/core/audits/byte-efficiency/offscreen-images.js +15 -0
  10. package/core/audits/dobetterweb/uses-http2.d.ts +13 -0
  11. package/core/audits/dobetterweb/uses-http2.js +34 -2
  12. package/core/audits/layout-shift-elements.d.ts +16 -0
  13. package/core/audits/layout-shift-elements.js +101 -0
  14. package/core/audits/no-unload-listeners.d.ts +16 -0
  15. package/core/audits/no-unload-listeners.js +86 -0
  16. package/core/audits/preload-fonts.d.ts +5 -1
  17. package/core/audits/preload-fonts.js +10 -1
  18. package/core/audits/script-elements-test-audit.d.ts +14 -0
  19. package/core/audits/script-elements-test-audit.js +29 -0
  20. package/core/audits/seo/is-crawlable.d.ts +1 -2
  21. package/core/audits/seo/plugins.d.ts +15 -0
  22. package/core/audits/seo/plugins.js +150 -0
  23. package/core/audits/seo/tap-targets.d.ts +50 -0
  24. package/core/audits/seo/tap-targets.js +352 -0
  25. package/core/audits/uses-rel-preload.d.ts +5 -1
  26. package/core/audits/uses-rel-preload.js +11 -1
  27. package/core/audits/work-during-interaction.d.ts +2 -2
  28. package/core/audits/work-during-interaction.js +2 -2
  29. package/core/computed/js-bundles.d.ts +1 -1
  30. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +82 -13
  31. package/core/computed/metrics/lantern-first-contentful-paint.js +178 -15
  32. package/core/computed/metrics/lantern-first-meaningful-paint.d.ts +12 -13
  33. package/core/computed/metrics/lantern-first-meaningful-paint.js +49 -10
  34. package/core/computed/metrics/lantern-interactive.d.ts +21 -13
  35. package/core/computed/metrics/lantern-interactive.js +84 -11
  36. package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +25 -13
  37. package/core/computed/metrics/lantern-largest-contentful-paint.js +80 -12
  38. package/core/computed/metrics/lantern-max-potential-fid.d.ts +24 -13
  39. package/core/computed/metrics/lantern-max-potential-fid.js +64 -11
  40. package/core/computed/metrics/lantern-metric.d.ts +18 -9
  41. package/core/computed/metrics/lantern-metric.js +31 -13
  42. package/core/computed/metrics/lantern-speed-index.d.ts +32 -13
  43. package/core/computed/metrics/lantern-speed-index.js +116 -12
  44. package/core/computed/metrics/lantern-total-blocking-time.d.ts +25 -13
  45. package/core/computed/metrics/lantern-total-blocking-time.js +94 -10
  46. package/core/computed/metrics/metric.js +3 -1
  47. package/core/computed/module-duplication.d.ts +1 -1
  48. package/core/computed/page-dependency-graph.js +1 -2
  49. package/core/config/default-config.js +74 -46
  50. package/core/config/experimental-config.js +0 -11
  51. package/core/config/metrics-to-audits.d.ts +19 -0
  52. package/core/config/metrics-to-audits.js +62 -0
  53. package/core/gather/gatherers/accessibility.js +3 -1
  54. package/core/gather/gatherers/global-listeners.d.ts +27 -0
  55. package/core/gather/gatherers/global-listeners.js +108 -0
  56. package/core/gather/gatherers/script-elements.d.ts +21 -0
  57. package/core/gather/gatherers/script-elements.js +100 -0
  58. package/core/gather/gatherers/seo/embedded-content.d.ts +10 -0
  59. package/core/gather/gatherers/seo/embedded-content.js +63 -0
  60. package/core/gather/gatherers/seo/tap-targets.d.ts +21 -0
  61. package/core/gather/gatherers/seo/tap-targets.js +389 -0
  62. package/core/gather/gatherers/trace-elements.d.ts +10 -0
  63. package/core/gather/gatherers/trace-elements.js +19 -0
  64. package/core/lib/lantern/page-dependency-graph.d.ts +4 -4
  65. package/core/lib/lantern/page-dependency-graph.js +5 -5
  66. package/core/lib/lighthouse-compatibility.js +10 -53
  67. package/dist/report/bundle.esm.js +1 -1
  68. package/dist/report/flow.js +14 -14
  69. package/dist/report/standalone.js +1 -1
  70. package/flow-report/src/summary/category.tsx +12 -10
  71. package/package.json +1 -1
  72. package/report/renderer/performance-category-renderer.d.ts +7 -0
  73. package/report/renderer/performance-category-renderer.js +11 -1
  74. package/report/renderer/report-utils.js +10 -13
  75. package/shared/localization/locales/ar-XB.json +64 -1
  76. package/shared/localization/locales/ar.json +64 -1
  77. package/shared/localization/locales/bg.json +64 -1
  78. package/shared/localization/locales/ca.json +64 -1
  79. package/shared/localization/locales/cs.json +64 -1
  80. package/shared/localization/locales/da.json +64 -1
  81. package/shared/localization/locales/de.json +64 -1
  82. package/shared/localization/locales/el.json +64 -1
  83. package/shared/localization/locales/en-GB.json +64 -1
  84. package/shared/localization/locales/en-US.json +65 -2
  85. package/shared/localization/locales/en-XA.json +64 -1
  86. package/shared/localization/locales/en-XL.json +65 -2
  87. package/shared/localization/locales/es-419.json +64 -1
  88. package/shared/localization/locales/es.json +64 -1
  89. package/shared/localization/locales/fi.json +64 -1
  90. package/shared/localization/locales/fil.json +64 -1
  91. package/shared/localization/locales/fr.json +64 -1
  92. package/shared/localization/locales/he.json +64 -1
  93. package/shared/localization/locales/hi.json +64 -1
  94. package/shared/localization/locales/hr.json +64 -1
  95. package/shared/localization/locales/hu.json +64 -1
  96. package/shared/localization/locales/id.json +64 -1
  97. package/shared/localization/locales/it.json +64 -1
  98. package/shared/localization/locales/ja.json +64 -1
  99. package/shared/localization/locales/ko.json +64 -1
  100. package/shared/localization/locales/lt.json +64 -1
  101. package/shared/localization/locales/lv.json +64 -1
  102. package/shared/localization/locales/nl.json +64 -1
  103. package/shared/localization/locales/no.json +64 -1
  104. package/shared/localization/locales/pl.json +64 -1
  105. package/shared/localization/locales/pt-PT.json +64 -1
  106. package/shared/localization/locales/pt.json +64 -1
  107. package/shared/localization/locales/ro.json +64 -1
  108. package/shared/localization/locales/ru.json +64 -1
  109. package/shared/localization/locales/sk.json +64 -1
  110. package/shared/localization/locales/sl.json +64 -1
  111. package/shared/localization/locales/sr-Latn.json +64 -1
  112. package/shared/localization/locales/sr.json +64 -1
  113. package/shared/localization/locales/sv.json +64 -1
  114. package/shared/localization/locales/ta.json +64 -1
  115. package/shared/localization/locales/te.json +64 -1
  116. package/shared/localization/locales/th.json +64 -1
  117. package/shared/localization/locales/tr.json +64 -1
  118. package/shared/localization/locales/uk.json +64 -1
  119. package/shared/localization/locales/vi.json +64 -1
  120. package/shared/localization/locales/zh-HK.json +64 -1
  121. package/shared/localization/locales/zh-TW.json +64 -1
  122. package/shared/localization/locales/zh.json +64 -1
  123. package/tsconfig.json +0 -1
  124. package/types/artifacts.d.ts +17 -3
  125. package/types/config.d.ts +1 -0
  126. package/types/lhr/audit-details.d.ts +4 -9
  127. package/types/lhr/lhr.d.ts +2 -0
  128. package/core/lib/lantern/metrics/first-contentful-paint.d.ts +0 -79
  129. package/core/lib/lantern/metrics/first-contentful-paint.js +0 -200
  130. package/core/lib/lantern/metrics/first-meaningful-paint.d.ts +0 -6
  131. package/core/lib/lantern/metrics/first-meaningful-paint.js +0 -66
  132. package/core/lib/lantern/metrics/interactive.d.ts +0 -26
  133. package/core/lib/lantern/metrics/interactive.js +0 -112
  134. package/core/lib/lantern/metrics/largest-contentful-paint.d.ts +0 -19
  135. package/core/lib/lantern/metrics/largest-contentful-paint.js +0 -108
  136. package/core/lib/lantern/metrics/max-potential-fid.d.ts +0 -30
  137. package/core/lib/lantern/metrics/max-potential-fid.js +0 -92
  138. package/core/lib/lantern/metrics/speed-index.d.ts +0 -38
  139. package/core/lib/lantern/metrics/speed-index.js +0 -145
  140. package/core/lib/lantern/metrics/total-blocking-time.d.ts +0 -31
  141. package/core/lib/lantern/metrics/total-blocking-time.js +0 -128
package/CONTRIBUTING.md CHANGED
@@ -79,8 +79,8 @@ A PR adding or changing a gatherer almost always needs to include the following:
79
79
  1. **Golden artifacts**: `sample_v2.json` is generated from a set of artifacts that come from running LH against `dbw_tester.html`. Those artifacts likely need to be updated after gatherer changes with `yarn update:sample-artifacts`, but limit to just the artifact being altered if possible. For example:
80
80
 
81
81
  ```sh
82
- # update just the Scripts artifact
83
- yarn update:sample-artifacts Scripts
82
+ # update just the ScriptElements artifact
83
+ yarn update:sample-artifacts ScriptElements
84
84
  ```
85
85
 
86
86
  This command works for updating `yarn update:sample-artifacts DevtoolsLog` or `Trace` as well, but the resulting `sample_v2.json` churn may be extensive and you might be better off editing manually.
@@ -61,6 +61,7 @@ import screenshot from './test-definitions/screenshot.js';
61
61
  import seoFailing from './test-definitions/seo-failing.js';
62
62
  import seoPassing from './test-definitions/seo-passing.js';
63
63
  import seoStatus403 from './test-definitions/seo-status-403.js';
64
+ import seoTapTargets from './test-definitions/seo-tap-targets.js';
64
65
  import serviceWorkerReloaded from './test-definitions/service-worker-reloaded.js';
65
66
  import shiftAttribution from './test-definitions/shift-attribution.js';
66
67
  import sourceMaps from './test-definitions/source-maps.js';
@@ -125,6 +126,7 @@ const smokeTests = [
125
126
  seoFailing,
126
127
  seoPassing,
127
128
  seoStatus403,
129
+ seoTapTargets,
128
130
  serviceWorkerReloaded,
129
131
  shiftAttribution,
130
132
  sourceMaps,
@@ -0,0 +1,10 @@
1
+ export default DuplicateIdActive;
2
+ declare class DuplicateIdActive extends AxeAudit {
3
+ }
4
+ export namespace UIStrings {
5
+ const title: string;
6
+ const failureTitle: string;
7
+ const description: string;
8
+ }
9
+ import AxeAudit from './axe-audit.js';
10
+ //# sourceMappingURL=duplicate-id-active.d.ts.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2019 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * @fileoverview Ensures every `id` attribute value of active, focusable elements is unique.
9
+ * See base class in axe-audit.js for audit() implementation.
10
+ */
11
+
12
+ import AxeAudit from './axe-audit.js';
13
+ import * as i18n from '../../lib/i18n/i18n.js';
14
+
15
+ const UIStrings = {
16
+ /** Title of an accesibility audit that checks if there are any duplicate id HTML attributes on active, focusable elements. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: '`[id]` attributes on active, focusable elements are unique',
18
+ /** Title of an accesibility audit that checks if there are any duplicate id HTML attributes on active, focusable elements. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */
19
+ failureTitle: '`[id]` attributes on active, focusable elements are not unique',
20
+ /** Description of a Lighthouse audit that tells the user *why* they should try to pass. 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. */
21
+ description: 'All focusable elements must have a unique `id` to ensure that they\'re visible to assistive technologies. [Learn how to fix duplicate `id`s](https://dequeuniversity.com/rules/axe/4.9/duplicate-id-active).',
22
+ };
23
+
24
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
25
+
26
+ class DuplicateIdActive extends AxeAudit {
27
+ /**
28
+ * @return {LH.Audit.Meta}
29
+ */
30
+ static get meta() {
31
+ return {
32
+ id: 'duplicate-id-active',
33
+ title: str_(UIStrings.title),
34
+ failureTitle: str_(UIStrings.failureTitle),
35
+ description: str_(UIStrings.description),
36
+ requiredArtifacts: ['Accessibility'],
37
+ };
38
+ }
39
+ }
40
+
41
+ export default DuplicateIdActive;
42
+ export {UIStrings};
@@ -35,6 +35,7 @@ class TargetSize extends AxeAudit {
35
35
  title: str_(UIStrings.title),
36
36
  failureTitle: str_(UIStrings.failureTitle),
37
37
  description: str_(UIStrings.description),
38
+ scoreDisplayMode: AxeAudit.SCORING_MODES.INFORMATIVE,
38
39
  requiredArtifacts: ['Accessibility'],
39
40
  };
40
41
  }
@@ -56,6 +56,22 @@ export class ByteEfficiencyAudit extends Audit {
56
56
  simulationBeforeChanges: LH.Gatherer.Simulation.Result;
57
57
  simulationAfterChanges: LH.Gatherer.Simulation.Result;
58
58
  };
59
+ /**
60
+ * Computes the estimated effect of all the byte savings on the maximum of the following:
61
+ *
62
+ * - end time of the last long task in the provided graph
63
+ * - (if includeLoad is true or not provided) end time of the last node in the graph
64
+ *
65
+ * @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
66
+ * @param {Node} graph
67
+ * @param {Simulator} simulator
68
+ * @param {{includeLoad?: boolean, providedWastedBytesByUrl?: Map<string, number>}=} options
69
+ * @return {number}
70
+ */
71
+ static computeWasteWithTTIGraph(results: Array<LH.Audit.ByteEfficiencyItem>, graph: Node, simulator: Simulator, options?: {
72
+ includeLoad?: boolean;
73
+ providedWastedBytesByUrl?: Map<string, number>;
74
+ } | undefined): number;
59
75
  /**
60
76
  * @param {ByteEfficiencyProduct} result
61
77
  * @param {Simulator} simulator
@@ -5,9 +5,11 @@
5
5
  */
6
6
 
7
7
  import {Audit} from '../audit.js';
8
+ import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
8
9
  import * as i18n from '../../lib/i18n/i18n.js';
9
10
  import {NetworkRecords} from '../../computed/network-records.js';
10
11
  import {LoadSimulator} from '../../computed/load-simulator.js';
12
+ import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
11
13
  import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
12
14
  import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
13
15
  import {LCPImageRecord} from '../../computed/lcp-image-record.js';
@@ -149,6 +151,37 @@ class ByteEfficiencyAudit extends Audit {
149
151
  };
150
152
  }
151
153
 
154
+ /**
155
+ * Computes the estimated effect of all the byte savings on the maximum of the following:
156
+ *
157
+ * - end time of the last long task in the provided graph
158
+ * - (if includeLoad is true or not provided) end time of the last node in the graph
159
+ *
160
+ * @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
161
+ * @param {Node} graph
162
+ * @param {Simulator} simulator
163
+ * @param {{includeLoad?: boolean, providedWastedBytesByUrl?: Map<string, number>}=} options
164
+ * @return {number}
165
+ */
166
+ static computeWasteWithTTIGraph(results, graph, simulator, options) {
167
+ options = Object.assign({includeLoad: true}, options);
168
+ const {savings: savingsOnOverallLoad, simulationBeforeChanges, simulationAfterChanges} =
169
+ this.computeWasteWithGraph(results, graph, simulator, {
170
+ ...options,
171
+ label: 'overallLoad',
172
+ });
173
+
174
+ const savingsOnTTI =
175
+ LanternInteractive.getLastLongTaskEndTime(simulationBeforeChanges.nodeTimings) -
176
+ LanternInteractive.getLastLongTaskEndTime(simulationAfterChanges.nodeTimings);
177
+
178
+ let savings = savingsOnTTI;
179
+ if (options.includeLoad) savings = Math.max(savings, savingsOnOverallLoad);
180
+
181
+ // Round waste to nearest 10ms
182
+ return Math.round(Math.max(savings, 0) / 10) * 10;
183
+ }
184
+
152
185
  /**
153
186
  * @param {ByteEfficiencyProduct} result
154
187
  * @param {Simulator} simulator
@@ -171,6 +204,7 @@ class ByteEfficiencyAudit extends Audit {
171
204
  // This is useful information in the LHR and should be preserved.
172
205
  let wastedMs;
173
206
  if (metricComputationInput.gatherContext.gatherMode === 'navigation') {
207
+ const graph = await PageDependencyGraph.request(metricComputationInput, context);
174
208
  const {
175
209
  optimisticGraph: optimisticFCPGraph,
176
210
  } = await LanternFirstContentfulPaint.request(metricComputationInput, context);
@@ -178,6 +212,10 @@ class ByteEfficiencyAudit extends Audit {
178
212
  optimisticGraph: optimisticLCPGraph,
179
213
  } = await LanternLargestContentfulPaint.request(metricComputationInput, context);
180
214
 
215
+ wastedMs = this.computeWasteWithTTIGraph(results, graph, simulator, {
216
+ providedWastedBytesByUrl: result.wastedBytesByUrl,
217
+ });
218
+
181
219
  const {savings: fcpSavings} = this.computeWasteWithGraph(
182
220
  results,
183
221
  optimisticFCPGraph,
@@ -205,7 +243,6 @@ class ByteEfficiencyAudit extends Audit {
205
243
 
206
244
  metricSavings.FCP = fcpSavings;
207
245
  metricSavings.LCP = Math.max(lcpGraphSavings, lcpRecordSavings);
208
- wastedMs = metricSavings.LCP;
209
246
  } else {
210
247
  wastedMs = simulator.computeWastedMsFromWastedBytes(wastedBytes);
211
248
  }
@@ -47,6 +47,17 @@ declare class OffscreenImages extends ByteEfficiencyAudit {
47
47
  * @param {number} interactiveTimestamp
48
48
  */
49
49
  static filterObservedResults(images: WasteResult[], interactiveTimestamp: number): WasteResult[];
50
+ /**
51
+ * The default byte efficiency audit will report max(TTI, load), since lazy-loading offscreen
52
+ * images won't reduce the overall time and the wasted bytes are really only "wasted" for TTI,
53
+ * override the function to just look at TTI savings.
54
+ *
55
+ * @param {Array<LH.Audit.ByteEfficiencyItem>} results
56
+ * @param {LH.Gatherer.Simulation.GraphNode} graph
57
+ * @param {LH.Gatherer.Simulation.Simulator} simulator
58
+ * @return {number}
59
+ */
60
+ static computeWasteWithTTIGraph(results: Array<LH.Audit.ByteEfficiencyItem>, graph: LH.Gatherer.Simulation.GraphNode, simulator: LH.Gatherer.Simulation.Simulator): number;
50
61
  /**
51
62
  * @param {LH.Artifacts} artifacts
52
63
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -156,6 +156,21 @@ class OffscreenImages extends ByteEfficiencyAudit {
156
156
  });
157
157
  }
158
158
 
159
+ /**
160
+ * The default byte efficiency audit will report max(TTI, load), since lazy-loading offscreen
161
+ * images won't reduce the overall time and the wasted bytes are really only "wasted" for TTI,
162
+ * override the function to just look at TTI savings.
163
+ *
164
+ * @param {Array<LH.Audit.ByteEfficiencyItem>} results
165
+ * @param {LH.Gatherer.Simulation.GraphNode} graph
166
+ * @param {LH.Gatherer.Simulation.Simulator} simulator
167
+ * @return {number}
168
+ */
169
+ static computeWasteWithTTIGraph(results, graph, simulator) {
170
+ return super.computeWasteWithTTIGraph(results, graph, simulator,
171
+ {includeLoad: false});
172
+ }
173
+
159
174
  /**
160
175
  * @param {LH.Artifacts} artifacts
161
176
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -20,6 +20,19 @@ declare class UsesHTTP2Audit extends Audit {
20
20
  simulationBefore: LH.Gatherer.Simulation.Result;
21
21
  simulationAfter: LH.Gatherer.Simulation.Result;
22
22
  };
23
+ /**
24
+ * Computes the estimated effect all results being converted to use http/2, the max of:
25
+ *
26
+ * - end time of the last long task in the provided graph
27
+ * - end time of the last node in the graph
28
+ * @param {Array<{url: string}>} results
29
+ * @param {Node} graph
30
+ * @param {Simulator} simulator
31
+ * @return {number}
32
+ */
33
+ static computeWasteWithTTIGraph(results: Array<{
34
+ url: string;
35
+ }>, graph: Node, simulator: Simulator): number;
23
36
  /**
24
37
  * Determines whether a network request is a "static resource" that would benefit from H2 multiplexing.
25
38
  * XHRs, tracking pixels, etc generally don't benefit as much because they aren't requested en-masse
@@ -15,9 +15,11 @@
15
15
  import {Audit} from '../audit.js';
16
16
  import {EntityClassification} from '../../computed/entity-classification.js';
17
17
  import UrlUtils from '../../lib/url-utils.js';
18
+ import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
18
19
  import {NetworkRequest} from '../../lib/network-request.js';
19
20
  import {NetworkRecords} from '../../computed/network-records.js';
20
21
  import {LoadSimulator} from '../../computed/load-simulator.js';
22
+ import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
21
23
  import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
22
24
  import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
23
25
  import * as i18n from '../../lib/i18n/i18n.js';
@@ -115,6 +117,32 @@ class UsesHTTP2Audit extends Audit {
115
117
  };
116
118
  }
117
119
 
120
+ /**
121
+ * Computes the estimated effect all results being converted to use http/2, the max of:
122
+ *
123
+ * - end time of the last long task in the provided graph
124
+ * - end time of the last node in the graph
125
+ * @param {Array<{url: string}>} results
126
+ * @param {Node} graph
127
+ * @param {Simulator} simulator
128
+ * @return {number}
129
+ */
130
+ static computeWasteWithTTIGraph(results, graph, simulator) {
131
+ const {savings: savingsOnOverallLoad, simulationBefore, simulationAfter} =
132
+ this.computeWasteWithGraph(results, graph, simulator, {
133
+ label: 'tti',
134
+ });
135
+
136
+ const savingsOnTTI =
137
+ LanternInteractive.getLastLongTaskEndTime(simulationBefore.nodeTimings) -
138
+ LanternInteractive.getLastLongTaskEndTime(simulationAfter.nodeTimings);
139
+
140
+ const savings = Math.max(savingsOnTTI, savingsOnOverallLoad);
141
+
142
+ // Round waste to nearest 10ms
143
+ return Math.round(Math.max(savings, 0) / 10) * 10;
144
+ }
145
+
118
146
  /**
119
147
  * Determines whether a network request is a "static resource" that would benefit from H2 multiplexing.
120
148
  * XHRs, tracking pixels, etc generally don't benefit as much because they aren't requested en-masse
@@ -207,6 +235,7 @@ class UsesHTTP2Audit extends Audit {
207
235
  * @return {Promise<LH.Audit.Product>}
208
236
  */
209
237
  static async audit(artifacts, context) {
238
+ const trace = artifacts.traces[Audit.DEFAULT_PASS];
210
239
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
211
240
  const URL = artifacts.URL;
212
241
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
@@ -240,6 +269,7 @@ class UsesHTTP2Audit extends Audit {
240
269
  devtoolsLog,
241
270
  settings,
242
271
  };
272
+ const pageGraph = await PageDependencyGraph.request({trace, devtoolsLog, URL}, context);
243
273
  const simulator = await LoadSimulator.request(simulatorOptions, context);
244
274
  const metricComputationInput = Audit.makeMetricComputationDataInput(artifacts, context);
245
275
 
@@ -250,6 +280,8 @@ class UsesHTTP2Audit extends Audit {
250
280
  pessimisticGraph: lcpGraph,
251
281
  } = await LanternLargestContentfulPaint.request(metricComputationInput, context);
252
282
 
283
+ const wastedMsTti = UsesHTTP2Audit.computeWasteWithTTIGraph(
284
+ resources, pageGraph, simulator);
253
285
  const wasteFcp =
254
286
  UsesHTTP2Audit.computeWasteWithGraph(resources,
255
287
  fcpGraph, simulator, {label: 'fcp'});
@@ -264,11 +296,11 @@ class UsesHTTP2Audit extends Audit {
264
296
  ];
265
297
 
266
298
  const details = Audit.makeOpportunityDetails(headings, resources,
267
- {overallSavingsMs: wasteLcp.savings});
299
+ {overallSavingsMs: wastedMsTti});
268
300
 
269
301
  return {
270
302
  displayValue,
271
- numericValue: wasteLcp.savings,
303
+ numericValue: wastedMsTti,
272
304
  numericUnit: 'millisecond',
273
305
  score: resources.length ? 0 : 1,
274
306
  details,
@@ -0,0 +1,16 @@
1
+ export default LayoutShiftElements;
2
+ declare class LayoutShiftElements extends Audit {
3
+ /**
4
+ * @param {LH.Artifacts} artifacts
5
+ * @param {LH.Audit.Context} context
6
+ * @return {Promise<LH.Audit.Product>}
7
+ */
8
+ static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
9
+ }
10
+ export namespace UIStrings {
11
+ const title: string;
12
+ const description: string;
13
+ const columnContribution: string;
14
+ }
15
+ import { Audit } from './audit.js';
16
+ //# sourceMappingURL=layout-shift-elements.d.ts.map
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @license Copyright 2020 Google LLC
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import {Audit} from './audit.js';
7
+ import * as i18n from '../lib/i18n/i18n.js';
8
+ import {CumulativeLayoutShift as CumulativeLayoutShiftComputed} from '../computed/metrics/cumulative-layout-shift.js';
9
+ import CumulativeLayoutShift from './metrics/cumulative-layout-shift.js';
10
+
11
+ const UIStrings = {
12
+ /** Descriptive title of a diagnostic audit that provides the top elements affected by layout shifts. */
13
+ title: 'Avoid large layout shifts',
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
+ };
19
+
20
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
21
+
22
+ class LayoutShiftElements extends Audit {
23
+ /**
24
+ * @return {LH.Audit.Meta}
25
+ */
26
+ static get meta() {
27
+ return {
28
+ id: 'layout-shift-elements',
29
+ title: str_(UIStrings.title),
30
+ description: str_(UIStrings.description),
31
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
32
+ guidanceLevel: 2,
33
+ requiredArtifacts: ['traces', 'TraceElements'],
34
+ };
35
+ }
36
+
37
+ /**
38
+ * @param {LH.Artifacts} artifacts
39
+ * @param {LH.Audit.Context} context
40
+ * @return {Promise<LH.Audit.Product>}
41
+ */
42
+ static async audit(artifacts, context) {
43
+ const {cumulativeLayoutShift: clsSavings, impactByNodeId} =
44
+ await CumulativeLayoutShiftComputed.request(artifacts.traces[Audit.DEFAULT_PASS], context);
45
+
46
+ /** @type {Array<{node: LH.Audit.Details.ItemValue, score: number}>} */
47
+ const clsElementData = artifacts.TraceElements
48
+ .filter(element => element.traceEventType === 'layout-shift-element')
49
+ .map(element => ({
50
+ node: Audit.makeNodeItem(element.node),
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
+ }
69
+
70
+ /** @type {LH.Audit.Details.Table['headings']} */
71
+ const headings = [
72
+ {key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
73
+ {key: 'score', valueType: 'numeric',
74
+ granularity: 0.001, label: str_(UIStrings.columnContribution)},
75
+ ];
76
+
77
+ const details = Audit.makeTableDetails(headings, clsElementData);
78
+
79
+ let displayValue;
80
+ if (impactByNodeId.size > 0) {
81
+ displayValue = str_(i18n.UIStrings.displayValueElementsFound,
82
+ {nodeCount: impactByNodeId.size});
83
+ }
84
+
85
+ const passed = clsSavings <= CumulativeLayoutShift.defaultOptions.p10;
86
+
87
+ return {
88
+ score: passed ? 1 : 0,
89
+ scoreDisplayMode: passed ? Audit.SCORING_MODES.INFORMATIVE : undefined,
90
+ metricSavings: {
91
+ CLS: clsSavings,
92
+ },
93
+ notApplicable: details.items.length === 0,
94
+ displayValue,
95
+ details,
96
+ };
97
+ }
98
+ }
99
+
100
+ export default LayoutShiftElements;
101
+ export {UIStrings};
@@ -0,0 +1,16 @@
1
+ export default NoUnloadListeners;
2
+ declare class NoUnloadListeners extends Audit {
3
+ /**
4
+ * @param {LH.Artifacts} artifacts
5
+ * @param {LH.Audit.Context} context
6
+ * @return {Promise<LH.Audit.Product>}
7
+ */
8
+ static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
9
+ }
10
+ export namespace UIStrings {
11
+ const title: string;
12
+ const failureTitle: string;
13
+ const description: string;
14
+ }
15
+ import { Audit } from './audit.js';
16
+ //# sourceMappingURL=no-unload-listeners.d.ts.map
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2020 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import {Audit} from './audit.js';
8
+ import {JSBundles} from '../computed/js-bundles.js';
9
+ import * as i18n from './../lib/i18n/i18n.js';
10
+
11
+ const UIStrings = {
12
+ /** Descriptive title of a Lighthouse audit that checks if a web page has 'unload' event listeners and finds none. */
13
+ title: 'Avoids `unload` event listeners',
14
+ /** Descriptive title of a Lighthouse audit that checks if a web page has 'unload' event listeners and finds that it is using them. */
15
+ failureTitle: 'Registers an `unload` listener',
16
+ /** Description of a Lighthouse audit that tells the user why pages should not use the 'unload' event. This is displayed after a user expands the section to see more. The last sentence starting with 'Learn' becomes link text to additional documentation. */
17
+ description: 'The `unload` event does not fire reliably and listening for it can prevent browser optimizations like the Back-Forward Cache. Use `pagehide` or `visibilitychange` events instead. [Learn more about unload event listeners](https://web.dev/articles/bfcache#never_use_the_unload_event)',
18
+ };
19
+
20
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
21
+
22
+ class NoUnloadListeners extends Audit {
23
+ /**
24
+ * @return {LH.Audit.Meta}
25
+ */
26
+ static get meta() {
27
+ return {
28
+ id: 'no-unload-listeners',
29
+ title: str_(UIStrings.title),
30
+ failureTitle: str_(UIStrings.failureTitle),
31
+ description: str_(UIStrings.description),
32
+ requiredArtifacts: ['GlobalListeners', 'SourceMaps', 'Scripts'],
33
+ };
34
+ }
35
+
36
+ /**
37
+ * @param {LH.Artifacts} artifacts
38
+ * @param {LH.Audit.Context} context
39
+ * @return {Promise<LH.Audit.Product>}
40
+ */
41
+ static async audit(artifacts, context) {
42
+ const unloadListeners = artifacts.GlobalListeners.filter(l => l.type === 'unload');
43
+ if (!unloadListeners.length) {
44
+ return {
45
+ score: 1,
46
+ };
47
+ }
48
+
49
+ const bundles = await JSBundles.request(artifacts, context);
50
+
51
+ /** @type {LH.Audit.Details.Table['headings']} */
52
+ const headings = [
53
+ {key: 'source', valueType: 'source-location', label: str_(i18n.UIStrings.columnSource)},
54
+ ];
55
+
56
+ /** @type {Array<{source: LH.Audit.Details.ItemValue}>} */
57
+ const tableItems = unloadListeners.map(listener => {
58
+ const {lineNumber, columnNumber} = listener;
59
+ const script = artifacts.Scripts.find(s => s.scriptId === listener.scriptId);
60
+
61
+ // If we can't find a url, still show something so the user can manually
62
+ // look for where an `unload` handler is being created.
63
+ if (!script) {
64
+ return {
65
+ source: {
66
+ type: 'url',
67
+ value: `(unknown):${lineNumber}:${columnNumber}`,
68
+ },
69
+ };
70
+ }
71
+
72
+ const bundle = bundles.find(bundle => bundle.script.scriptId === script.scriptId);
73
+ return {
74
+ source: Audit.makeSourceLocation(script.url, lineNumber, columnNumber, bundle),
75
+ };
76
+ });
77
+
78
+ return {
79
+ score: 0,
80
+ details: Audit.makeTableDetails(headings, tableItems),
81
+ };
82
+ }
83
+ }
84
+
85
+ export default NoUnloadListeners;
86
+ export {UIStrings};
@@ -14,7 +14,11 @@ declare class PreloadFontsAudit extends Audit {
14
14
  * @param {LH.Audit.Context} context
15
15
  * @return {Promise<LH.Audit.Product>}
16
16
  */
17
- static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
17
+ static audit_(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
18
+ /**
19
+ * @return {Promise<LH.Audit.Product>}
20
+ */
21
+ static audit(): Promise<LH.Audit.Product>;
18
22
  }
19
23
  export namespace UIStrings {
20
24
  const title: string;
@@ -62,7 +62,7 @@ class PreloadFontsAudit extends Audit {
62
62
  * @param {LH.Audit.Context} context
63
63
  * @return {Promise<LH.Audit.Product>}
64
64
  */
65
- static async audit(artifacts, context) {
65
+ static async audit_(artifacts, context) {
66
66
  const devtoolsLog = artifacts.devtoolsLogs[this.DEFAULT_PASS];
67
67
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
68
68
 
@@ -91,6 +91,15 @@ class PreloadFontsAudit extends Audit {
91
91
  notApplicable: optionalFontURLs.size === 0,
92
92
  };
93
93
  }
94
+
95
+ /**
96
+ * @return {Promise<LH.Audit.Product>}
97
+ */
98
+ static async audit() {
99
+ // Preload advice is on hold until https://github.com/GoogleChrome/lighthouse/issues/11960
100
+ // is resolved.
101
+ return {score: 1, notApplicable: true};
102
+ }
94
103
  }
95
104
 
96
105
  export default PreloadFontsAudit;
@@ -0,0 +1,14 @@
1
+ declare namespace _default {
2
+ namespace meta {
3
+ const id: string;
4
+ const title: string;
5
+ const failureTitle: string;
6
+ const description: string;
7
+ const requiredArtifacts: string[];
8
+ }
9
+ function audit(): {
10
+ score: number;
11
+ };
12
+ }
13
+ export default _default;
14
+ //# sourceMappingURL=script-elements-test-audit.d.ts.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * @fileoverview This is a fake audit used exclusively in smoke tests to force inclusion of ScriptElements artifact.
9
+ * It is included here for complex reasons in the way the bundled smoketests work.
10
+ *
11
+ * The smokehouse configs are evaluated first in the node CLI side (which requires an absolute path using LH_ROOT).
12
+ * The smokehouse configs are then *re-evaluated* in the bundled context for execution by Lighthouse (which *cannot* use an absolute path using LH_ROOT).
13
+ *
14
+ * This mismatch in environment demands that the audit path in the config must be context-aware,
15
+ * yet the require-graph for the config is included before even the CLI knows that it will be using
16
+ * a bundled runner. Rather than force a massive smoketest architecture change, we include a harmless,
17
+ * test-only audit in our core list instead.
18
+ */
19
+
20
+ export default {
21
+ meta: {
22
+ id: 'script-elements-test-audit',
23
+ title: 'ScriptElements',
24
+ failureTitle: 'ScriptElements',
25
+ description: 'Audit to force the inclusion of ScriptElements artifact',
26
+ requiredArtifacts: ['ScriptElements'],
27
+ },
28
+ audit: () => ({score: 1}),
29
+ };
@@ -6,12 +6,11 @@ declare class IsCrawlable extends Audit {
6
6
  static handleMetaElement(metaElement: LH.Artifacts.MetaElement): {
7
7
  source: {
8
8
  snippet: string;
9
- /** @type {Array<string|undefined>} */
10
9
  type: "node";
11
10
  lhId?: string | undefined;
12
11
  path?: string | undefined;
13
12
  selector?: string | undefined;
14
- boundingRect?: import("../../../types/lhr/audit-details.js").default.Rect | undefined; /** @type {Array<string|undefined>} */
13
+ boundingRect?: import("../../../types/lhr/audit-details.js").default.Rect | undefined;
15
14
  nodeLabel?: string | undefined;
16
15
  };
17
16
  } | undefined;