lighthouse 10.3.0 → 10.4.0-dev.20230711

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 (126) hide show
  1. package/cli/test/smokehouse/frontends/smokehouse-bin.js +38 -22
  2. package/cli/test/smokehouse/lighthouse-runners/bundle.d.ts +1 -2
  3. package/cli/test/smokehouse/lighthouse-runners/bundle.js +11 -24
  4. package/cli/test/smokehouse/lighthouse-runners/cli.d.ts +1 -2
  5. package/cli/test/smokehouse/lighthouse-runners/cli.js +3 -7
  6. package/cli/test/smokehouse/lighthouse-runners/devtools.d.ts +1 -2
  7. package/cli/test/smokehouse/lighthouse-runners/devtools.js +1 -11
  8. package/cli/test/smokehouse/readme.md +0 -2
  9. package/cli/test/smokehouse/report-assert.d.ts +1 -2
  10. package/cli/test/smokehouse/report-assert.js +2 -19
  11. package/cli/test/smokehouse/smokehouse.js +3 -32
  12. package/core/audits/accessibility/aria-dialog-name.d.ts +10 -0
  13. package/core/audits/accessibility/aria-dialog-name.js +45 -0
  14. package/core/audits/accessibility/aria-text.d.ts +10 -0
  15. package/core/audits/accessibility/aria-text.js +44 -0
  16. package/core/audits/accessibility/empty-heading.d.ts +10 -0
  17. package/core/audits/accessibility/empty-heading.js +45 -0
  18. package/core/audits/accessibility/identical-links-same-purpose.d.ts +10 -0
  19. package/core/audits/accessibility/identical-links-same-purpose.js +45 -0
  20. package/core/audits/accessibility/landmark-one-main.d.ts +10 -0
  21. package/core/audits/accessibility/landmark-one-main.js +44 -0
  22. package/core/audits/accessibility/link-in-text-block.d.ts +10 -0
  23. package/core/audits/accessibility/link-in-text-block.js +44 -0
  24. package/core/audits/accessibility/select-name.d.ts +10 -0
  25. package/core/audits/accessibility/select-name.js +44 -0
  26. package/core/audits/accessibility/target-size.d.ts +10 -0
  27. package/core/audits/accessibility/target-size.js +45 -0
  28. package/core/audits/audit.d.ts +6 -0
  29. package/core/audits/audit.js +12 -0
  30. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +22 -6
  31. package/core/audits/byte-efficiency/byte-efficiency-audit.js +97 -25
  32. package/core/audits/byte-efficiency/render-blocking-resources.js +1 -0
  33. package/core/audits/long-tasks.d.ts +48 -0
  34. package/core/audits/long-tasks.js +146 -19
  35. package/core/audits/non-composited-animations.js +7 -0
  36. package/core/audits/prioritize-lcp-image.js +2 -1
  37. package/core/audits/redirects.js +4 -0
  38. package/core/audits/seo/link-text.js +10 -0
  39. package/core/audits/unsized-images.js +3 -0
  40. package/core/audits/viewport.js +11 -0
  41. package/core/audits/work-during-interaction.js +13 -2
  42. package/core/computed/js-bundles.js +1 -1
  43. package/core/computed/metrics/tbt-utils.d.ts +26 -0
  44. package/core/computed/metrics/tbt-utils.js +48 -28
  45. package/core/computed/metrics/total-blocking-time.js +1 -1
  46. package/core/computed/tbt-impact-tasks.d.ts +54 -0
  47. package/core/computed/tbt-impact-tasks.js +221 -0
  48. package/core/config/default-config.js +17 -0
  49. package/core/config/filters.d.ts +9 -9
  50. package/core/config/filters.js +7 -7
  51. package/core/config/validation.js +12 -0
  52. package/core/gather/base-gatherer.d.ts +1 -3
  53. package/core/gather/base-gatherer.js +1 -3
  54. package/core/gather/gatherers/accessibility.js +8 -3
  55. package/core/gather/gatherers/seo/font-size.d.ts +0 -1
  56. package/core/gather/gatherers/seo/font-size.js +0 -1
  57. package/core/gather/gatherers/source-maps.js +3 -2
  58. package/core/lib/cdt/SDK.d.ts +1 -1
  59. package/core/lib/cdt/SDK.js +2 -2
  60. package/core/lib/dependency-graph/simulator/simulator.js +3 -1
  61. package/core/lib/lh-error.d.ts +14 -2
  62. package/core/lib/lh-error.js +7 -1
  63. package/core/lib/navigation-error.d.ts +2 -2
  64. package/core/lib/navigation-error.js +1 -1
  65. package/core/lib/network-recorder.js +1 -0
  66. package/core/lib/stack-packs.js +4 -0
  67. package/dist/report/bundle.esm.js +2 -1
  68. package/dist/report/flow.js +9 -9
  69. package/dist/report/standalone.js +2 -2
  70. package/package.json +5 -5
  71. package/report/assets/styles.css +5 -1
  72. package/report/renderer/category-renderer.js +1 -0
  73. package/report/renderer/components.js +1 -1
  74. package/shared/localization/locales/ar-XB.json +233 -2
  75. package/shared/localization/locales/ar.json +233 -2
  76. package/shared/localization/locales/bg.json +233 -2
  77. package/shared/localization/locales/ca.json +233 -2
  78. package/shared/localization/locales/cs.json +246 -15
  79. package/shared/localization/locales/da.json +239 -8
  80. package/shared/localization/locales/de.json +238 -7
  81. package/shared/localization/locales/el.json +234 -3
  82. package/shared/localization/locales/en-GB.json +233 -2
  83. package/shared/localization/locales/en-US.json +87 -0
  84. package/shared/localization/locales/en-XA.json +233 -2
  85. package/shared/localization/locales/en-XL.json +87 -0
  86. package/shared/localization/locales/es-419.json +233 -2
  87. package/shared/localization/locales/es.json +233 -2
  88. package/shared/localization/locales/fi.json +233 -2
  89. package/shared/localization/locales/fil.json +236 -5
  90. package/shared/localization/locales/fr.json +235 -4
  91. package/shared/localization/locales/he.json +233 -2
  92. package/shared/localization/locales/hi.json +236 -5
  93. package/shared/localization/locales/hr.json +234 -3
  94. package/shared/localization/locales/hu.json +233 -2
  95. package/shared/localization/locales/id.json +233 -2
  96. package/shared/localization/locales/it.json +234 -3
  97. package/shared/localization/locales/ja.json +233 -2
  98. package/shared/localization/locales/ko.json +233 -2
  99. package/shared/localization/locales/lt.json +233 -2
  100. package/shared/localization/locales/lv.json +250 -19
  101. package/shared/localization/locales/nl.json +233 -2
  102. package/shared/localization/locales/no.json +235 -4
  103. package/shared/localization/locales/pl.json +233 -2
  104. package/shared/localization/locales/pt-PT.json +233 -2
  105. package/shared/localization/locales/pt.json +234 -3
  106. package/shared/localization/locales/ro.json +233 -2
  107. package/shared/localization/locales/ru.json +233 -2
  108. package/shared/localization/locales/sk.json +233 -2
  109. package/shared/localization/locales/sl.json +233 -2
  110. package/shared/localization/locales/sr-Latn.json +233 -2
  111. package/shared/localization/locales/sr.json +233 -2
  112. package/shared/localization/locales/sv.json +233 -2
  113. package/shared/localization/locales/ta.json +233 -2
  114. package/shared/localization/locales/te.json +233 -2
  115. package/shared/localization/locales/th.json +233 -2
  116. package/shared/localization/locales/tr.json +233 -2
  117. package/shared/localization/locales/uk.json +233 -2
  118. package/shared/localization/locales/vi.json +233 -2
  119. package/shared/localization/locales/zh-HK.json +233 -2
  120. package/shared/localization/locales/zh-TW.json +235 -4
  121. package/shared/localization/locales/zh.json +239 -8
  122. package/tsconfig.json +1 -1
  123. package/types/artifacts.d.ts +9 -2
  124. package/types/internal/smokehouse.d.ts +1 -3
  125. package/core/audits/metrics/first-contentful-paint-3g.d.ts +0 -15
  126. package/core/audits/metrics/first-contentful-paint-3g.js +0 -69
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+
7
+ /**
8
+ * @fileoverview Ensures that identical links have the same purpose.
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 identical links have the same purpose. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: 'Identical links have the same purpose.',
18
+ /** Title of an accesibility audit that checks if identical links have the same purpose. 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: 'Identical links do not have the same purpose.',
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: 'Links with the same destination should have the same description, to help users ' +
22
+ 'understand the link\'s purpose and decide whether to follow it. ' +
23
+ '[Learn more about identical links](https://dequeuniversity.com/rules/axe/4.7/identical-links-same-purpose).',
24
+ };
25
+
26
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
27
+
28
+ class IdenticalLinksSamePurpose extends AxeAudit {
29
+ /**
30
+ * @return {LH.Audit.Meta}
31
+ */
32
+ static get meta() {
33
+ return {
34
+ id: 'identical-links-same-purpose',
35
+ title: str_(UIStrings.title),
36
+ failureTitle: str_(UIStrings.failureTitle),
37
+ description: str_(UIStrings.description),
38
+ scoreDisplayMode: AxeAudit.SCORING_MODES.INFORMATIVE,
39
+ requiredArtifacts: ['Accessibility'],
40
+ };
41
+ }
42
+ }
43
+
44
+ export default IdenticalLinksSamePurpose;
45
+ export {UIStrings};
@@ -0,0 +1,10 @@
1
+ export default LandmarkOneMain;
2
+ declare class LandmarkOneMain 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=landmark-one-main.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+
7
+ /**
8
+ * @fileoverview Ensures that the document has a main landmark.
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 the document has a main landmark. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: 'Document has a main landmark.',
18
+ /** Title of an accesibility audit that checks if the document has a main landmark. 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: 'Document does not have a main landmark.',
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: 'One main landmark helps screen reader users navigate a web page. ' +
22
+ '[Learn more about landmarks](https://dequeuniversity.com/rules/axe/4.7/landmark-one-main).',
23
+ };
24
+
25
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
26
+
27
+ class LandmarkOneMain extends AxeAudit {
28
+ /**
29
+ * @return {LH.Audit.Meta}
30
+ */
31
+ static get meta() {
32
+ return {
33
+ id: 'landmark-one-main',
34
+ title: str_(UIStrings.title),
35
+ failureTitle: str_(UIStrings.failureTitle),
36
+ description: str_(UIStrings.description),
37
+ scoreDisplayMode: AxeAudit.SCORING_MODES.INFORMATIVE,
38
+ requiredArtifacts: ['Accessibility'],
39
+ };
40
+ }
41
+ }
42
+
43
+ export default LandmarkOneMain;
44
+ export {UIStrings};
@@ -0,0 +1,10 @@
1
+ export default LinkInTextBlock;
2
+ declare class LinkInTextBlock 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=link-in-text-block.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+
7
+ /**
8
+ * @fileoverview Ensures that links can be distinguished without relying on color.
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 evaluates if all link elements can be distinguished without relying on color. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: 'Links are distinguishable without relying on color.',
18
+ /** Title of an accesibility audit that evaluates if all link elements can be distinguished without relying on color. 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: 'Links rely on color to be distinguishable.',
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: 'Low-contrast text is difficult or impossible for many users to read. Link text ' +
22
+ 'that is discernible improves the experience for users with low vision. ' +
23
+ '[Learn how to make links distinguishable](https://dequeuniversity.com/rules/axe/4.7/link-in-text-block).',
24
+ };
25
+
26
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
27
+
28
+ class LinkInTextBlock extends AxeAudit {
29
+ /**
30
+ * @return {LH.Audit.Meta}
31
+ */
32
+ static get meta() {
33
+ return {
34
+ id: 'link-in-text-block',
35
+ title: str_(UIStrings.title),
36
+ failureTitle: str_(UIStrings.failureTitle),
37
+ description: str_(UIStrings.description),
38
+ requiredArtifacts: ['Accessibility'],
39
+ };
40
+ }
41
+ }
42
+
43
+ export default LinkInTextBlock;
44
+ export {UIStrings};
@@ -0,0 +1,10 @@
1
+ export default SelectName;
2
+ declare class SelectName 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=select-name.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+
7
+ /**
8
+ * @fileoverview Ensures select elements have programmatically associated label elements.
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 evaluates if all select elements have programmatically associated label elements. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: 'Select elements have associated label elements.',
18
+ /** Title of an accesibility audit that evaluates if all select elements have programmatically associated label 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: 'Select elements do not have associated label elements.',
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: 'Form elements without effective labels can create frustrating experiences ' +
22
+ 'for screen reader users. ' +
23
+ '[Learn more about the `select` element](https://dequeuniversity.com/rules/axe/4.7/select-name).',
24
+ };
25
+
26
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
27
+
28
+ class SelectName extends AxeAudit {
29
+ /**
30
+ * @return {LH.Audit.Meta}
31
+ */
32
+ static get meta() {
33
+ return {
34
+ id: 'select-name',
35
+ title: str_(UIStrings.title),
36
+ failureTitle: str_(UIStrings.failureTitle),
37
+ description: str_(UIStrings.description),
38
+ requiredArtifacts: ['Accessibility'],
39
+ };
40
+ }
41
+ }
42
+
43
+ export default SelectName;
44
+ export {UIStrings};
@@ -0,0 +1,10 @@
1
+ export default TargetSize;
2
+ declare class TargetSize 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=target-size.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+
7
+ /**
8
+ * @fileoverview Ensures that touch targets have sufficient size and spacing.
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 all touch targets have sufficient size and spacing. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: 'Touch targets have sufficient size and spacing.',
18
+ /** Title of an accesibility audit that checks if all touch targets have sufficient size and spacing. 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: 'Touch targets do not have sufficient size or spacing.',
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: 'Touch targets with sufficient size and spacing help users who may have ' +
22
+ 'difficulty targeting small controls activate the targets. ' +
23
+ '[Learn more about touch targets](https://dequeuniversity.com/rules/axe/4.7/target-size).',
24
+ };
25
+
26
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
27
+
28
+ class TargetSize extends AxeAudit {
29
+ /**
30
+ * @return {LH.Audit.Meta}
31
+ */
32
+ static get meta() {
33
+ return {
34
+ id: 'target-size',
35
+ title: str_(UIStrings.title),
36
+ failureTitle: str_(UIStrings.failureTitle),
37
+ description: str_(UIStrings.description),
38
+ scoreDisplayMode: AxeAudit.SCORING_MODES.INFORMATIVE,
39
+ requiredArtifacts: ['Accessibility'],
40
+ };
41
+ }
42
+ }
43
+
44
+ export default TargetSize;
45
+ export {UIStrings};
@@ -159,6 +159,12 @@ export class Audit {
159
159
  * @return {LH.RawIcu<LH.Audit.Result>}
160
160
  */
161
161
  static generateAuditResult(audit: typeof Audit, product: LH.Audit.Product): LH.RawIcu<LH.Audit.Result>;
162
+ /**
163
+ * @param {LH.Artifacts} artifacts
164
+ * @param {LH.Audit.Context} context
165
+ * @returns {LH.Artifacts.MetricComputationDataInput}
166
+ */
167
+ static makeMetricComputationDataInput(artifacts: LH.Artifacts, context: LH.Audit.Context): LH.Artifacts.MetricComputationDataInput;
162
168
  }
163
169
  import * as LH from '../../types/lh.js';
164
170
  //# sourceMappingURL=audit.d.ts.map
@@ -415,6 +415,18 @@ class Audit {
415
415
  details: product.details,
416
416
  };
417
417
  }
418
+
419
+ /**
420
+ * @param {LH.Artifacts} artifacts
421
+ * @param {LH.Audit.Context} context
422
+ * @returns {LH.Artifacts.MetricComputationDataInput}
423
+ */
424
+ static makeMetricComputationDataInput(artifacts, context) {
425
+ const trace = artifacts.traces[Audit.DEFAULT_PASS];
426
+ const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
427
+ const gatherContext = artifacts.GatherContext;
428
+ return {trace, devtoolsLog, gatherContext, settings: context.settings, URL: artifacts.URL};
429
+ }
418
430
  }
419
431
 
420
432
  export {Audit};
@@ -49,6 +49,23 @@ export class ByteEfficiencyAudit extends Audit {
49
49
  * @return {Promise<LH.Audit.Product>}
50
50
  */
51
51
  static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
52
+ /**
53
+ * Computes the estimated effect of all the byte savings on the provided graph.
54
+ *
55
+ * @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
56
+ * @param {Node} graph
57
+ * @param {Simulator} simulator
58
+ * @param {{label?: string, providedWastedBytesByUrl?: Map<string, number>}=} options
59
+ * @return {{savings: number, simulationBeforeChanges: LH.Gatherer.Simulation.Result, simulationAfterChanges: LH.Gatherer.Simulation.Result}}
60
+ */
61
+ static computeWasteWithGraph(results: Array<LH.Audit.ByteEfficiencyItem>, graph: Node, simulator: Simulator, options?: {
62
+ label?: string;
63
+ providedWastedBytesByUrl?: Map<string, number>;
64
+ } | undefined): {
65
+ savings: number;
66
+ simulationBeforeChanges: LH.Gatherer.Simulation.Result;
67
+ simulationAfterChanges: LH.Gatherer.Simulation.Result;
68
+ };
52
69
  /**
53
70
  * Computes the estimated effect of all the byte savings on the maximum of the following:
54
71
  *
@@ -58,22 +75,21 @@ export class ByteEfficiencyAudit extends Audit {
58
75
  * @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
59
76
  * @param {Node} graph
60
77
  * @param {Simulator} simulator
61
- * @param {{includeLoad?: boolean, label?: string, providedWastedBytesByUrl?: Map<string, number>}=} options
78
+ * @param {{includeLoad?: boolean, providedWastedBytesByUrl?: Map<string, number>}=} options
62
79
  * @return {number}
63
80
  */
64
81
  static computeWasteWithTTIGraph(results: Array<LH.Audit.ByteEfficiencyItem>, graph: Node, simulator: Simulator, options?: {
65
82
  includeLoad?: boolean;
66
- label?: string;
67
83
  providedWastedBytesByUrl?: Map<string, number>;
68
84
  } | undefined): number;
69
85
  /**
70
86
  * @param {ByteEfficiencyProduct} result
71
- * @param {Node|null} graph
72
87
  * @param {Simulator} simulator
73
- * @param {LH.Artifacts['GatherContext']} gatherContext
74
- * @return {LH.Audit.Product}
88
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationInput
89
+ * @param {LH.Audit.Context} context
90
+ * @return {Promise<LH.Audit.Product>}
75
91
  */
76
- static createAuditProduct(result: ByteEfficiencyProduct, graph: Node | null, simulator: Simulator, gatherContext: LH.Artifacts['GatherContext']): LH.Audit.Product;
92
+ static createAuditProduct(result: ByteEfficiencyProduct, simulator: Simulator, metricComputationInput: LH.Artifacts.MetricComputationDataInput, context: LH.Audit.Context): Promise<LH.Audit.Product>;
77
93
  /**
78
94
  * @param {LH.Artifacts} artifacts
79
95
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
@@ -10,6 +10,9 @@ import * as i18n from '../../lib/i18n/i18n.js';
10
10
  import {NetworkRecords} from '../../computed/network-records.js';
11
11
  import {LoadSimulator} from '../../computed/load-simulator.js';
12
12
  import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
13
+ import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
14
+ import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
15
+ import {LCPImageRecord} from '../../computed/lcp-image-record.js';
13
16
 
14
17
  const str_ = i18n.createIcuMessageFn(import.meta.url, {});
15
18
 
@@ -104,9 +107,7 @@ class ByteEfficiencyAudit extends Audit {
104
107
  */
105
108
  static async audit(artifacts, context) {
106
109
  const gatherContext = artifacts.GatherContext;
107
- const trace = artifacts.traces[Audit.DEFAULT_PASS];
108
110
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
109
- const URL = artifacts.URL;
110
111
  const settings = context?.settings || {};
111
112
  const simulatorOptions = {
112
113
  devtoolsLog,
@@ -125,34 +126,29 @@ class ByteEfficiencyAudit extends Audit {
125
126
  };
126
127
  }
127
128
 
128
- const [result, graph, simulator] = await Promise.all([
129
+ const metricComputationInput = Audit.makeMetricComputationDataInput(artifacts, context);
130
+
131
+ const [result, simulator] = await Promise.all([
129
132
  this.audit_(artifacts, networkRecords, context),
130
- // Page dependency graph is only used in navigation mode.
131
- gatherContext.gatherMode === 'navigation' ?
132
- PageDependencyGraph.request({trace, devtoolsLog, URL}, context) :
133
- null,
134
133
  LoadSimulator.request(simulatorOptions, context),
135
134
  ]);
136
135
 
137
- return this.createAuditProduct(result, graph, simulator, gatherContext);
136
+ return this.createAuditProduct(result, simulator, metricComputationInput, context);
138
137
  }
139
138
 
140
139
  /**
141
- * Computes the estimated effect of all the byte savings on the maximum of the following:
142
- *
143
- * - end time of the last long task in the provided graph
144
- * - (if includeLoad is true or not provided) end time of the last node in the graph
140
+ * Computes the estimated effect of all the byte savings on the provided graph.
145
141
  *
146
142
  * @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
147
143
  * @param {Node} graph
148
144
  * @param {Simulator} simulator
149
- * @param {{includeLoad?: boolean, label?: string, providedWastedBytesByUrl?: Map<string, number>}=} options
150
- * @return {number}
145
+ * @param {{label?: string, providedWastedBytesByUrl?: Map<string, number>}=} options
146
+ * @return {{savings: number, simulationBeforeChanges: LH.Gatherer.Simulation.Result, simulationAfterChanges: LH.Gatherer.Simulation.Result}}
151
147
  */
152
- static computeWasteWithTTIGraph(results, graph, simulator, options) {
153
- options = Object.assign({includeLoad: true, label: this.meta.id}, options);
154
- const beforeLabel = `${options.label}-before`;
155
- const afterLabel = `${options.label}-after`;
148
+ static computeWasteWithGraph(results, graph, simulator, options) {
149
+ options = Object.assign({label: ''}, options);
150
+ const beforeLabel = `${this.meta.id}-${options.label}-before`;
151
+ const afterLabel = `${this.meta.id}-${options.label}-after`;
156
152
 
157
153
  const simulationBeforeChanges = simulator.simulate(graph, {label: beforeLabel});
158
154
 
@@ -187,7 +183,36 @@ class ByteEfficiencyAudit extends Audit {
187
183
  node.record.transferSize = originalTransferSize;
188
184
  });
189
185
 
190
- const savingsOnOverallLoad = simulationBeforeChanges.timeInMs - simulationAfterChanges.timeInMs;
186
+ const savings = simulationBeforeChanges.timeInMs - simulationAfterChanges.timeInMs;
187
+
188
+ return {
189
+ // Round waste to nearest 10ms
190
+ savings: Math.round(Math.max(savings, 0) / 10) * 10,
191
+ simulationBeforeChanges,
192
+ simulationAfterChanges,
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Computes the estimated effect of all the byte savings on the maximum of the following:
198
+ *
199
+ * - end time of the last long task in the provided graph
200
+ * - (if includeLoad is true or not provided) end time of the last node in the graph
201
+ *
202
+ * @param {Array<LH.Audit.ByteEfficiencyItem>} results The array of byte savings results per resource
203
+ * @param {Node} graph
204
+ * @param {Simulator} simulator
205
+ * @param {{includeLoad?: boolean, providedWastedBytesByUrl?: Map<string, number>}=} options
206
+ * @return {number}
207
+ */
208
+ static computeWasteWithTTIGraph(results, graph, simulator, options) {
209
+ options = Object.assign({includeLoad: true}, options);
210
+ const {savings: savingsOnOverallLoad, simulationBeforeChanges, simulationAfterChanges} =
211
+ this.computeWasteWithGraph(results, graph, simulator, {
212
+ ...options,
213
+ label: 'overallLoad',
214
+ });
215
+
191
216
  const savingsOnTTI =
192
217
  LanternInteractive.getLastLongTaskEndTime(simulationBeforeChanges.nodeTimings) -
193
218
  LanternInteractive.getLastLongTaskEndTime(simulationAfterChanges.nodeTimings);
@@ -201,24 +226,63 @@ class ByteEfficiencyAudit extends Audit {
201
226
 
202
227
  /**
203
228
  * @param {ByteEfficiencyProduct} result
204
- * @param {Node|null} graph
205
229
  * @param {Simulator} simulator
206
- * @param {LH.Artifacts['GatherContext']} gatherContext
207
- * @return {LH.Audit.Product}
230
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationInput
231
+ * @param {LH.Audit.Context} context
232
+ * @return {Promise<LH.Audit.Product>}
208
233
  */
209
- static createAuditProduct(result, graph, simulator, gatherContext) {
234
+ static async createAuditProduct(result, simulator, metricComputationInput, context) {
210
235
  const results = result.items.sort((itemA, itemB) => itemB.wastedBytes - itemA.wastedBytes);
211
236
 
212
237
  const wastedBytes = results.reduce((sum, item) => sum + item.wastedBytes, 0);
213
238
 
239
+ /** @type {LH.Audit.MetricSavings} */
240
+ const metricSavings = {
241
+ FCP: 0,
242
+ LCP: 0,
243
+ };
244
+
214
245
  // `wastedMs` may be negative, if making the opportunity change could be detrimental.
215
246
  // This is useful information in the LHR and should be preserved.
216
247
  let wastedMs;
217
- if (gatherContext.gatherMode === 'navigation') {
218
- if (!graph) throw Error('Page dependency graph should always be computed in navigation mode');
248
+ if (metricComputationInput.gatherContext.gatherMode === 'navigation') {
249
+ const graph = await PageDependencyGraph.request(metricComputationInput, context);
250
+ const {
251
+ pessimisticGraph: pessimisticFCPGraph,
252
+ } = await LanternFirstContentfulPaint.request(metricComputationInput, context);
253
+ const {
254
+ pessimisticGraph: pessimisticLCPGraph,
255
+ } = await LanternLargestContentfulPaint.request(metricComputationInput, context);
256
+
219
257
  wastedMs = this.computeWasteWithTTIGraph(results, graph, simulator, {
220
258
  providedWastedBytesByUrl: result.wastedBytesByUrl,
221
259
  });
260
+
261
+ const {savings: fcpSavings} = this.computeWasteWithGraph(
262
+ results,
263
+ pessimisticFCPGraph,
264
+ simulator,
265
+ {providedWastedBytesByUrl: result.wastedBytesByUrl, label: 'fcp'}
266
+ );
267
+ const {savings: lcpGraphSavings} = this.computeWasteWithGraph(
268
+ results,
269
+ pessimisticLCPGraph,
270
+ simulator,
271
+ {providedWastedBytesByUrl: result.wastedBytesByUrl, label: 'lcp'}
272
+ );
273
+
274
+ // The LCP graph can underestimate the LCP savings if there is potential savings on the LCP record itself.
275
+ let lcpRecordSavings = 0;
276
+ const lcpRecord = await LCPImageRecord.request(metricComputationInput, context);
277
+ if (lcpRecord) {
278
+ const lcpResult = results.find(result => result.url === lcpRecord.url);
279
+ if (lcpResult) {
280
+ lcpRecordSavings = simulator.computeWastedMsFromWastedBytes(lcpResult.wastedBytes);
281
+ }
282
+ }
283
+
284
+ metricSavings.FCP = fcpSavings;
285
+ metricSavings.LCP = Math.max(lcpGraphSavings, lcpRecordSavings);
222
286
  } else {
223
287
  wastedMs = simulator.computeWastedMsFromWastedBytes(wastedBytes);
224
288
  }
@@ -232,6 +296,13 @@ class ByteEfficiencyAudit extends Audit {
232
296
  const details = Audit.makeOpportunityDetails(result.headings, results,
233
297
  {overallSavingsMs: wastedMs, overallSavingsBytes: wastedBytes, sortedBy});
234
298
 
299
+ // TODO: Remove from debug data once `metricSavings` is added to the LHR.
300
+ // For now, add it to debug data for visibility.
301
+ details.debugData = {
302
+ type: 'debugdata',
303
+ metricSavings,
304
+ };
305
+
235
306
  return {
236
307
  explanation: result.explanation,
237
308
  warnings: result.warnings,
@@ -240,6 +311,7 @@ class ByteEfficiencyAudit extends Audit {
240
311
  numericUnit: 'millisecond',
241
312
  score: ByteEfficiencyAudit.scoreForWastedMs(wastedMs),
242
313
  details,
314
+ metricSavings,
243
315
  };
244
316
  }
245
317
 
@@ -299,6 +299,7 @@ class RenderBlockingResources extends Audit {
299
299
  numericValue: wastedMs,
300
300
  numericUnit: 'millisecond',
301
301
  details,
302
+ metricSavings: {FCP: wastedMs, LCP: wastedMs},
302
303
  };
303
304
  }
304
305
  }
@@ -1,5 +1,53 @@
1
1
  export default LongTasks;
2
+ export type TaskGroupIds = import('../lib/tracehouse/task-groups.js').TaskGroupIds;
3
+ export type Timing = {
4
+ startTime: number;
5
+ duration: number;
6
+ };
7
+ export type DebugTask = Timing & {
8
+ [p: string]: number;
9
+ urlIndex: number;
10
+ };
11
+ /** @typedef {import('../lib/tracehouse/task-groups.js').TaskGroupIds} TaskGroupIds */
12
+ /** @typedef {{startTime: number, duration: number}} Timing */
13
+ /** @typedef {Timing & {urlIndex: number, [p: string]: number}} DebugTask */
2
14
  declare class LongTasks extends Audit {
15
+ /**
16
+ * Returns the timing information for the given task, recursively walking the
17
+ * task's children and adding up time spent in each type of task activity.
18
+ * If `taskTimingsByEvent` is present, it will be used for task timing instead
19
+ * of the timings on the tasks themselves.
20
+ * If `timeByTaskGroup` is not provided, a new Map will be populated with
21
+ * timing breakdown; if one is provided, timing breakdown will be added to the
22
+ * existing breakdown.
23
+ *
24
+ * TODO: when simulated, a significant number of child tasks are dropped, so
25
+ * most time will be attributed to 'other' (the category of the top-level
26
+ * RunTask). See pruning in `PageDependencyGraph.linkCPUNodes`.
27
+ * @param {LH.Artifacts.TaskNode} task
28
+ * @param {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} taskTimingsByEvent
29
+ * @param {Map<TaskGroupIds, number>} [timeByTaskGroup]
30
+ * @return {{startTime: number, duration: number, timeByTaskGroup: Map<TaskGroupIds, number>}}
31
+ */
32
+ static getTimingBreakdown(task: LH.Artifacts.TaskNode, taskTimingsByEvent: Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming> | undefined, timeByTaskGroup?: Map<import("../lib/tracehouse/task-groups.js").TaskGroupIds, number> | undefined): {
33
+ startTime: number;
34
+ duration: number;
35
+ timeByTaskGroup: Map<TaskGroupIds, number>;
36
+ };
37
+ /**
38
+ * @param {Array<LH.Artifacts.TaskNode>} longTasks
39
+ * @param {Set<string>} jsUrls
40
+ * @param {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} taskTimingsByEvent
41
+ * @return {LH.Audit.Details.DebugData}
42
+ */
43
+ static makeDebugData(longTasks: Array<LH.Artifacts.TaskNode>, jsUrls: Set<string>, taskTimingsByEvent: Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming> | undefined): LH.Audit.Details.DebugData;
44
+ /**
45
+ * Get timing from task, overridden by taskTimingsByEvent if provided.
46
+ * @param {LH.Artifacts.TaskNode} task
47
+ * @param {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} taskTimingsByEvent
48
+ * @return {Timing}
49
+ */
50
+ static getTiming(task: LH.Artifacts.TaskNode, taskTimingsByEvent: Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming> | undefined): Timing;
3
51
  /**
4
52
  * @param {LH.Artifacts} artifacts
5
53
  * @param {LH.Audit.Context} context