lighthouse 10.2.0 → 10.3.0-dev.20230614

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 (133) hide show
  1. package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +4 -6
  2. package/core/audits/accessibility/html-xml-lang-mismatch.d.ts +10 -0
  3. package/core/audits/accessibility/html-xml-lang-mismatch.js +47 -0
  4. package/core/audits/accessibility/input-button-name.d.ts +10 -0
  5. package/core/audits/accessibility/input-button-name.js +44 -0
  6. package/core/audits/accessibility/table-fake-caption.d.ts +10 -0
  7. package/core/audits/accessibility/table-fake-caption.js +47 -0
  8. package/core/audits/accessibility/td-has-header.d.ts +10 -0
  9. package/core/audits/accessibility/td-has-header.js +45 -0
  10. package/core/audits/audit.d.ts +2 -1
  11. package/core/audits/audit.js +5 -2
  12. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +3 -3
  13. package/core/audits/byte-efficiency/byte-efficiency-audit.js +14 -19
  14. package/core/audits/deprecations.js +1 -1
  15. package/core/audits/largest-contentful-paint-element.d.ts +14 -2
  16. package/core/audits/largest-contentful-paint-element.js +49 -31
  17. package/core/audits/layout-shift-elements.d.ts +3 -2
  18. package/core/audits/layout-shift-elements.js +10 -2
  19. package/core/audits/metrics.js +0 -2
  20. package/core/audits/network-requests.js +6 -0
  21. package/core/audits/seo/crawlable-anchors.js +4 -0
  22. package/core/audits/server-response-time.d.ts +2 -1
  23. package/core/audits/server-response-time.js +17 -3
  24. package/core/audits/uses-rel-preconnect.js +20 -4
  25. package/core/computed/entity-classification.d.ts +14 -1
  26. package/core/computed/entity-classification.js +61 -6
  27. package/core/computed/metrics/cumulative-layout-shift.d.ts +2 -9
  28. package/core/computed/metrics/cumulative-layout-shift.js +3 -16
  29. package/core/computed/metrics/time-to-first-byte.js +10 -7
  30. package/core/computed/metrics/timing-summary.js +0 -3
  31. package/core/config/default-config.js +8 -0
  32. package/core/gather/driver/execution-context.js +13 -5
  33. package/core/gather/driver/target-manager.d.ts +13 -0
  34. package/core/gather/driver/target-manager.js +49 -0
  35. package/core/gather/gatherers/accessibility.js +19 -20
  36. package/core/gather/gatherers/anchor-elements.js +2 -0
  37. package/core/gather/gatherers/global-listeners.js +38 -21
  38. package/core/gather/gatherers/inspector-issues.js +2 -1
  39. package/core/gather/gatherers/trace-elements.d.ts +3 -2
  40. package/core/gather/gatherers/trace-elements.js +10 -19
  41. package/core/legacy/config/config.d.ts +1 -1
  42. package/core/legacy/config/config.js +1 -1
  43. package/core/legacy/gather/driver.d.ts +1 -0
  44. package/core/legacy/gather/driver.js +11 -0
  45. package/core/lib/asset-saver.d.ts +4 -0
  46. package/core/lib/asset-saver.js +19 -0
  47. package/core/lib/bf-cache-strings.d.ts +3 -0
  48. package/core/lib/bf-cache-strings.js +248 -232
  49. package/core/lib/cdt/generated/SourceMap.d.ts +18 -31
  50. package/core/lib/cdt/generated/SourceMap.js +51 -101
  51. package/core/lib/dependency-graph/simulator/network-analyzer.d.ts +6 -2
  52. package/core/lib/dependency-graph/simulator/network-analyzer.js +45 -13
  53. package/core/lib/deprecation-description.d.ts +17 -0
  54. package/core/lib/deprecation-description.js +71 -0
  55. package/core/lib/deprecations-strings.d.ts +282 -63
  56. package/core/lib/deprecations-strings.js +282 -551
  57. package/core/lib/lh-error.d.ts +9 -4
  58. package/core/lib/lh-error.js +15 -11
  59. package/core/lib/network-request.d.ts +13 -6
  60. package/core/lib/network-request.js +39 -14
  61. package/core/lib/stack-packs.js +4 -0
  62. package/core/lib/tracehouse/trace-processor.js +1 -1
  63. package/core/lib/url-utils.js +4 -0
  64. package/core/runner.js +4 -2
  65. package/dist/report/bundle.esm.js +24 -7
  66. package/dist/report/flow.js +3 -3
  67. package/dist/report/standalone.js +8 -8
  68. package/package.json +13 -13
  69. package/readme.md +7 -0
  70. package/report/assets/styles.css +18 -2
  71. package/report/assets/templates.html +4 -0
  72. package/report/renderer/category-renderer.js +5 -2
  73. package/report/renderer/components.js +2 -2
  74. package/report/renderer/dom.d.ts +4 -1
  75. package/report/renderer/dom.js +3 -2
  76. package/shared/localization/locales/ar-XB.json +20 -170
  77. package/shared/localization/locales/ar.json +20 -170
  78. package/shared/localization/locales/bg.json +19 -169
  79. package/shared/localization/locales/ca.json +19 -169
  80. package/shared/localization/locales/cs.json +19 -169
  81. package/shared/localization/locales/da.json +19 -169
  82. package/shared/localization/locales/de.json +19 -169
  83. package/shared/localization/locales/el.json +19 -169
  84. package/shared/localization/locales/en-GB.json +9 -159
  85. package/shared/localization/locales/en-US.json +166 -70
  86. package/shared/localization/locales/en-XA.json +20 -170
  87. package/shared/localization/locales/en-XL.json +166 -70
  88. package/shared/localization/locales/es-419.json +19 -169
  89. package/shared/localization/locales/es.json +19 -169
  90. package/shared/localization/locales/fi.json +19 -169
  91. package/shared/localization/locales/fil.json +17 -167
  92. package/shared/localization/locales/fr.json +18 -168
  93. package/shared/localization/locales/he.json +19 -169
  94. package/shared/localization/locales/hi.json +20 -170
  95. package/shared/localization/locales/hr.json +19 -169
  96. package/shared/localization/locales/hu.json +19 -169
  97. package/shared/localization/locales/id.json +19 -169
  98. package/shared/localization/locales/it.json +19 -169
  99. package/shared/localization/locales/ja.json +19 -169
  100. package/shared/localization/locales/ko.json +19 -169
  101. package/shared/localization/locales/lt.json +20 -170
  102. package/shared/localization/locales/lv.json +20 -170
  103. package/shared/localization/locales/nl.json +18 -168
  104. package/shared/localization/locales/no.json +19 -169
  105. package/shared/localization/locales/pl.json +19 -169
  106. package/shared/localization/locales/pt-PT.json +19 -169
  107. package/shared/localization/locales/pt.json +19 -169
  108. package/shared/localization/locales/ro.json +19 -169
  109. package/shared/localization/locales/ru.json +19 -169
  110. package/shared/localization/locales/sk.json +20 -170
  111. package/shared/localization/locales/sl.json +19 -169
  112. package/shared/localization/locales/sr-Latn.json +19 -169
  113. package/shared/localization/locales/sr.json +19 -169
  114. package/shared/localization/locales/sv.json +19 -169
  115. package/shared/localization/locales/ta.json +19 -169
  116. package/shared/localization/locales/te.json +19 -169
  117. package/shared/localization/locales/th.json +19 -169
  118. package/shared/localization/locales/tr.json +19 -169
  119. package/shared/localization/locales/uk.json +19 -169
  120. package/shared/localization/locales/vi.json +20 -170
  121. package/shared/localization/locales/zh-HK.json +19 -169
  122. package/shared/localization/locales/zh-TW.json +19 -169
  123. package/shared/localization/locales/zh.json +19 -169
  124. package/shared/util.d.ts +6 -0
  125. package/shared/util.js +14 -1
  126. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +2 -1
  127. package/tsconfig-base.json +1 -1
  128. package/types/artifacts.d.ts +10 -8
  129. package/types/audit.d.ts +5 -0
  130. package/types/gatherer.d.ts +1 -0
  131. package/types/lhr/audit-details.d.ts +1 -0
  132. package/types/lhr/audit-result.d.ts +10 -0
  133. package/types/internal/es-main.d.ts +0 -18
@@ -3,7 +3,7 @@
3
3
  exports[`getAssertionReport works (multiple failing) 1`] = `
4
4
  "X difference at cumulative-layout-shift audit.details.items.length
5
5
  expected: []
6
- found: [{\\"cumulativeLayoutShiftMainFrame\\":0.13570762803819444,\\"totalCumulativeLayoutShift\\":0.13570762803819444}]
6
+ found: [{\\"cumulativeLayoutShiftMainFrame\\":0.13570762803819444}]
7
7
 
8
8
 
9
9
  X difference at cumulative-layout-shift audit.details.blah
@@ -24,8 +24,7 @@ exports[`getAssertionReport works (multiple failing) 1`] = `
24
24
  \\"type\\": \\"debugdata\\",
25
25
  \\"items\\": [
26
26
  {
27
- \\"cumulativeLayoutShiftMainFrame\\": 0.13570762803819444,
28
- \\"totalCumulativeLayoutShift\\": 0.13570762803819444
27
+ \\"cumulativeLayoutShiftMainFrame\\": 0.13570762803819444
29
28
  }
30
29
  ]
31
30
  }
@@ -35,7 +34,7 @@ exports[`getAssertionReport works (multiple failing) 1`] = `
35
34
  exports[`getAssertionReport works (trivial failing) 1`] = `
36
35
  "X difference at cumulative-layout-shift audit.details.items.length
37
36
  expected: []
38
- found: [{\\"cumulativeLayoutShiftMainFrame\\":0.13570762803819444,\\"totalCumulativeLayoutShift\\":0.13570762803819444}]
37
+ found: [{\\"cumulativeLayoutShiftMainFrame\\":0.13570762803819444}]
39
38
 
40
39
  found result:
41
40
  {
@@ -51,8 +50,7 @@ exports[`getAssertionReport works (trivial failing) 1`] = `
51
50
  \\"type\\": \\"debugdata\\",
52
51
  \\"items\\": [
53
52
  {
54
- \\"cumulativeLayoutShiftMainFrame\\": 0.13570762803819444,
55
- \\"totalCumulativeLayoutShift\\": 0.13570762803819444
53
+ \\"cumulativeLayoutShiftMainFrame\\": 0.13570762803819444
56
54
  }
57
55
  ]
58
56
  }
@@ -0,0 +1,10 @@
1
+ export default HTMLXMLLangMismatch;
2
+ declare class HTMLXMLLangMismatch 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=html-xml-lang-mismatch.d.ts.map
@@ -0,0 +1,47 @@
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, if present, the `[xml:lang]` attribute value in an HTML document has
9
+ * the same base language as the `[lang]` attribute.
10
+ * See base class in axe-audit.js for audit() implementation.
11
+ */
12
+
13
+ import AxeAudit from './axe-audit.js';
14
+ import * as i18n from '../../lib/i18n/i18n.js';
15
+
16
+ const UIStrings = {
17
+ /** Title of an accesibility audit that evaluates if the xml:lang attribute, if present, has the same base language as the `lang` attribute. This title is descriptive of the successful state and is shown to users when no user action is required. */
18
+ title: '`<html>` element has an `[xml:lang]` attribute with the same base language as the ' +
19
+ '`[lang]` attribute.',
20
+ /** Title of an accesibility audit that evaluates if the xml:lang attribute, if present, has the same base language as the `lang` attribute. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */
21
+ failureTitle: '`<html>` element does not have an `[xml:lang]` attribute with the same base ' +
22
+ 'language as the `[lang]` attribute.',
23
+ /** 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. */
24
+ description: 'If the webpage does not specify a consistent language, then the screen ' +
25
+ 'reader might not announce the page\'s text correctly. ' +
26
+ '[Learn more about the `lang` attribute](https://dequeuniversity.com/rules/axe/4.7/html-xml-lang-mismatch).',
27
+ };
28
+
29
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
30
+
31
+ class HTMLXMLLangMismatch extends AxeAudit {
32
+ /**
33
+ * @return {LH.Audit.Meta}
34
+ */
35
+ static get meta() {
36
+ return {
37
+ id: 'html-xml-lang-mismatch',
38
+ title: str_(UIStrings.title),
39
+ failureTitle: str_(UIStrings.failureTitle),
40
+ description: str_(UIStrings.description),
41
+ requiredArtifacts: ['Accessibility'],
42
+ };
43
+ }
44
+ }
45
+
46
+ export default HTMLXMLLangMismatch;
47
+ export {UIStrings};
@@ -0,0 +1,10 @@
1
+ export default InputButtonName;
2
+ declare class InputButtonName 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=input-button-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 input buttons have discernible text.
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 input buttons have discernible text. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: 'Input buttons have discernible text.',
18
+ /** Title of an accesibility audit that evaluates if all input buttons have discernible text. 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: 'Input buttons do not have discernible text.',
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: 'Adding discernable and accessible text to input buttons may help screen reader ' +
22
+ 'users understand the purpose of the input button. ' +
23
+ '[Learn more about input buttons](https://dequeuniversity.com/rules/axe/4.7/input-button-name).',
24
+ };
25
+
26
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
27
+
28
+ class InputButtonName extends AxeAudit {
29
+ /**
30
+ * @return {LH.Audit.Meta}
31
+ */
32
+ static get meta() {
33
+ return {
34
+ id: 'input-button-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 InputButtonName;
44
+ export {UIStrings};
@@ -0,0 +1,10 @@
1
+ export default TableFakeCaption;
2
+ declare class TableFakeCaption 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=table-fake-caption.d.ts.map
@@ -0,0 +1,47 @@
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 Ensure that tables use `<caption>` instead of colspan for a caption.
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 tables use caption instead of colspan to indicate a caption. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: 'Tables use `<caption>` instead of cells with the `[colspan]` attribute to indicate a ' +
18
+ 'caption.',
19
+ /** Title of an accesibility audit that evaluates if all tables use caption instead of colspan to indicate a caption. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */
20
+ failureTitle: 'Tables do not use `<caption>` instead of cells with the `[colspan]` attribute ' +
21
+ 'to indicate a caption.',
22
+ /** 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. */
23
+ description: 'Screen readers have features to make navigating tables easier. Ensuring ' +
24
+ 'that tables use the actual caption element instead of cells with the `[colspan]` ' +
25
+ 'attribute may improve the experience for screen reader users. ' +
26
+ '[Learn more about captions](https://dequeuniversity.com/rules/axe/4.7/table-fake-caption).',
27
+ };
28
+
29
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
30
+
31
+ class TableFakeCaption extends AxeAudit {
32
+ /**
33
+ * @return {LH.Audit.Meta}
34
+ */
35
+ static get meta() {
36
+ return {
37
+ id: 'table-fake-caption',
38
+ title: str_(UIStrings.title),
39
+ failureTitle: str_(UIStrings.failureTitle),
40
+ description: str_(UIStrings.description),
41
+ requiredArtifacts: ['Accessibility'],
42
+ };
43
+ }
44
+ }
45
+
46
+ export default TableFakeCaption;
47
+ export {UIStrings};
@@ -0,0 +1,10 @@
1
+ export default TDHasHeader;
2
+ declare class TDHasHeader 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=td-has-header.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 Ensure that large tables have `[header]` attributes.
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 large table elements use the headers HTML attribute. This title is descriptive of the successful state and is shown to users when no user action is required. */
17
+ title: '`<td>` elements in a large `<table>` have one or more table headers.',
18
+ /** Title of an accesibility audit that evaluates if all large table elements use the headers HTML attribute. 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: '`<td>` elements in a large `<table>` do not have table headers.',
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: 'Screen readers have features to make navigating tables easier. Ensuring ' +
22
+ 'that `<td>` elements in a large table (3 or more cells in width and height) have an ' +
23
+ 'associated table header may improve the experience for screen reader users. ' +
24
+ '[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header).',
25
+ };
26
+
27
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
28
+
29
+ class TDHasHeader extends AxeAudit {
30
+ /**
31
+ * @return {LH.Audit.Meta}
32
+ */
33
+ static get meta() {
34
+ return {
35
+ id: 'td-has-header',
36
+ title: str_(UIStrings.title),
37
+ failureTitle: str_(UIStrings.failureTitle),
38
+ description: str_(UIStrings.description),
39
+ requiredArtifacts: ['Accessibility'],
40
+ };
41
+ }
42
+ }
43
+
44
+ export default TDHasHeader;
45
+ export {UIStrings};
@@ -149,9 +149,10 @@ export class Audit {
149
149
  /**
150
150
  * @param {typeof Audit} audit
151
151
  * @param {string | LH.IcuMessage} errorMessage
152
+ * @param {string=} errorStack
152
153
  * @return {LH.RawIcu<LH.Audit.Result>}
153
154
  */
154
- static generateErrorAuditResult(audit: typeof Audit, errorMessage: string | LH.IcuMessage): LH.RawIcu<LH.Audit.Result>;
155
+ static generateErrorAuditResult(audit: typeof Audit, errorMessage: string | LH.IcuMessage, errorStack?: string | undefined): LH.RawIcu<LH.Audit.Result>;
155
156
  /**
156
157
  * @param {typeof Audit} audit
157
158
  * @param {LH.Audit.Product} product
@@ -348,12 +348,14 @@ class Audit {
348
348
  /**
349
349
  * @param {typeof Audit} audit
350
350
  * @param {string | LH.IcuMessage} errorMessage
351
+ * @param {string=} errorStack
351
352
  * @return {LH.RawIcu<LH.Audit.Result>}
352
353
  */
353
- static generateErrorAuditResult(audit, errorMessage) {
354
+ static generateErrorAuditResult(audit, errorMessage, errorStack) {
354
355
  return Audit.generateAuditResult(audit, {
355
356
  score: null,
356
357
  errorMessage,
358
+ errorStack,
357
359
  });
358
360
  }
359
361
 
@@ -371,7 +373,7 @@ class Audit {
371
373
  let scoreDisplayMode = audit.meta.scoreDisplayMode || Audit.SCORING_MODES.BINARY;
372
374
 
373
375
  // But override if product contents require it.
374
- if (product.errorMessage) {
376
+ if (product.errorMessage !== undefined) {
375
377
  // Error result.
376
378
  scoreDisplayMode = Audit.SCORING_MODES.ERROR;
377
379
  } else if (product.notApplicable) {
@@ -407,6 +409,7 @@ class Audit {
407
409
  displayValue: product.displayValue,
408
410
  explanation: product.explanation,
409
411
  errorMessage: product.errorMessage,
412
+ errorStack: product.errorStack,
410
413
  warnings: product.warnings,
411
414
 
412
415
  details: product.details,
@@ -25,9 +25,9 @@ export type ByteEfficiencyProduct = {
25
25
  */
26
26
  export class ByteEfficiencyAudit extends Audit {
27
27
  /**
28
- * Creates a score based on the wastedMs value using linear interpolation between control points.
29
- * A negative wastedMs is scored as 1, assuming time is not being wasted with respect to the
30
- * opportunity being measured.
28
+ * Creates a score based on the wastedMs value using log-normal distribution scoring. A negative
29
+ * wastedMs will be scored as 1, assuming time is not being wasted with respect to the opportunity
30
+ * being measured.
31
31
  *
32
32
  * @param {number} wastedMs
33
33
  * @return {number}
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  import {Audit} from '../audit.js';
8
- import {linearInterpolation} from '../../lib/statistics.js';
9
8
  import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
10
9
  import * as i18n from '../../lib/i18n/i18n.js';
11
10
  import {NetworkRecords} from '../../computed/network-records.js';
@@ -17,9 +16,13 @@ const str_ = i18n.createIcuMessageFn(import.meta.url, {});
17
16
  /** @typedef {import('../../lib/dependency-graph/simulator/simulator').Simulator} Simulator */
18
17
  /** @typedef {import('../../lib/dependency-graph/base-node.js').Node} Node */
19
18
 
20
- const WASTED_MS_FOR_AVERAGE = 300;
21
- const WASTED_MS_FOR_POOR = 750;
22
- const WASTED_MS_FOR_SCORE_OF_ZERO = 5000;
19
+ // Parameters for log-normal distribution scoring. These values were determined by fitting the
20
+ // log-normal cumulative distribution function curve to the former method of linear interpolation
21
+ // scoring between the control points {average = 300 ms, poor = 750 ms, zero = 5000 ms} using the
22
+ // curve-fit tool at https://mycurvefit.com/ rounded to the nearest integer. See
23
+ // https://www.desmos.com/calculator/gcexiyesdi for an interactive visualization of the curve fit.
24
+ const WASTED_MS_P10 = 150;
25
+ const WASTED_MS_MEDIAN = 935;
23
26
 
24
27
  /**
25
28
  * @typedef {object} ByteEfficiencyProduct
@@ -38,26 +41,18 @@ const WASTED_MS_FOR_SCORE_OF_ZERO = 5000;
38
41
  */
39
42
  class ByteEfficiencyAudit extends Audit {
40
43
  /**
41
- * Creates a score based on the wastedMs value using linear interpolation between control points.
42
- * A negative wastedMs is scored as 1, assuming time is not being wasted with respect to the
43
- * opportunity being measured.
44
+ * Creates a score based on the wastedMs value using log-normal distribution scoring. A negative
45
+ * wastedMs will be scored as 1, assuming time is not being wasted with respect to the opportunity
46
+ * being measured.
44
47
  *
45
48
  * @param {number} wastedMs
46
49
  * @return {number}
47
50
  */
48
51
  static scoreForWastedMs(wastedMs) {
49
- if (wastedMs <= 0) {
50
- return 1;
51
- } else if (wastedMs < WASTED_MS_FOR_AVERAGE) {
52
- return linearInterpolation(0, 1, WASTED_MS_FOR_AVERAGE, 0.75, wastedMs);
53
- } else if (wastedMs < WASTED_MS_FOR_POOR) {
54
- return linearInterpolation(WASTED_MS_FOR_AVERAGE, 0.75, WASTED_MS_FOR_POOR, 0.5, wastedMs);
55
- } else {
56
- return Math.max(
57
- 0,
58
- linearInterpolation(WASTED_MS_FOR_POOR, 0.5, WASTED_MS_FOR_SCORE_OF_ZERO, 0, wastedMs)
59
- );
60
- }
52
+ return Audit.computeLogNormalScore(
53
+ {p10: WASTED_MS_P10, median: WASTED_MS_MEDIAN},
54
+ wastedMs
55
+ );
61
56
  }
62
57
 
63
58
  /**
@@ -11,7 +11,7 @@
11
11
  import {Audit} from './audit.js';
12
12
  import {JSBundles} from '../computed/js-bundles.js';
13
13
  import * as i18n from '../lib/i18n/i18n.js';
14
- import {getIssueDetailDescription} from '../lib/deprecations-strings.js';
14
+ import {getIssueDetailDescription} from '../lib/deprecation-description.js';
15
15
 
16
16
  /* eslint-disable max-len */
17
17
  const UIStrings = {
@@ -1,11 +1,23 @@
1
1
  export default LargestContentfulPaintElement;
2
2
  declare class LargestContentfulPaintElement extends Audit {
3
+ /**
4
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
5
+ * @param {LH.Audit.Context} context
6
+ * @return {Promise<number|undefined>}
7
+ */
8
+ static getOptionalLCPMetric(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Audit.Context): Promise<number | undefined>;
3
9
  /**
4
10
  * @param {LH.Artifacts} artifacts
11
+ * @return {LH.Audit.Details.Table|undefined}
12
+ */
13
+ static makeElementTable(artifacts: LH.Artifacts): LH.Audit.Details.Table | undefined;
14
+ /**
15
+ * @param {number} metricLcp
16
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
5
17
  * @param {LH.Audit.Context} context
6
- * @return {Promise<LH.Audit.Details.Table|undefined>}
18
+ * @return {Promise<LH.Audit.Details.Table>}
7
19
  */
8
- static makePhaseTable(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Details.Table | undefined>;
20
+ static makePhaseTable(metricLcp: number, metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Audit.Context): Promise<LH.Audit.Details.Table>;
9
21
  /**
10
22
  * @param {LH.Artifacts} artifacts
11
23
  * @param {LH.Audit.Context} context
@@ -50,19 +50,44 @@ class LargestContentfulPaintElement extends Audit {
50
50
  }
51
51
 
52
52
  /**
53
- * @param {LH.Artifacts} artifacts
53
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
54
54
  * @param {LH.Audit.Context} context
55
- * @return {Promise<LH.Audit.Details.Table|undefined>}
55
+ * @return {Promise<number|undefined>}
56
56
  */
57
- static async makePhaseTable(artifacts, context) {
58
- const trace = artifacts.traces[Audit.DEFAULT_PASS];
59
- const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
60
- const gatherContext = artifacts.GatherContext;
61
- const metricComputationData = {trace, devtoolsLog, gatherContext,
62
- settings: context.settings, URL: artifacts.URL};
57
+ static async getOptionalLCPMetric(metricComputationData, context) {
58
+ try {
59
+ const {timing: metricLcp} =
60
+ await LargestContentfulPaint.request(metricComputationData, context);
61
+ return metricLcp;
62
+ } catch {}
63
+ }
64
+
65
+ /**
66
+ * @param {LH.Artifacts} artifacts
67
+ * @return {LH.Audit.Details.Table|undefined}
68
+ */
69
+ static makeElementTable(artifacts) {
70
+ const lcpElement = artifacts.TraceElements
71
+ .find(element => element.traceEventType === 'largest-contentful-paint');
72
+ if (!lcpElement) return;
73
+
74
+ /** @type {LH.Audit.Details.Table['headings']} */
75
+ const headings = [
76
+ {key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
77
+ ];
63
78
 
64
- const {timing: metricLcp} =
65
- await LargestContentfulPaint.request(metricComputationData, context);
79
+ const lcpElementDetails = [{node: Audit.makeNodeItem(lcpElement.node)}];
80
+
81
+ return Audit.makeTableDetails(headings, lcpElementDetails);
82
+ }
83
+
84
+ /**
85
+ * @param {number} metricLcp
86
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
87
+ * @param {LH.Audit.Context} context
88
+ * @return {Promise<LH.Audit.Details.Table>}
89
+ */
90
+ static async makePhaseTable(metricLcp, metricComputationData, context) {
66
91
  const {ttfb, loadStart, loadEnd} = await LCPBreakdown.request(metricComputationData, context);
67
92
 
68
93
  let loadDelay = 0;
@@ -102,36 +127,29 @@ class LargestContentfulPaintElement extends Audit {
102
127
  * @return {Promise<LH.Audit.Product>}
103
128
  */
104
129
  static async audit(artifacts, context) {
105
- const lcpElement = artifacts.TraceElements
106
- .find(element => element.traceEventType === 'largest-contentful-paint');
107
- const lcpElementDetails = [];
108
- if (lcpElement) {
109
- lcpElementDetails.push({
110
- node: Audit.makeNodeItem(lcpElement.node),
111
- });
112
- }
113
-
114
- /** @type {LH.Audit.Details.Table['headings']} */
115
- const headings = [
116
- {key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
117
- ];
130
+ const trace = artifacts.traces[Audit.DEFAULT_PASS];
131
+ const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
132
+ const gatherContext = artifacts.GatherContext;
133
+ const metricComputationData = {trace, devtoolsLog, gatherContext,
134
+ settings: context.settings, URL: artifacts.URL};
118
135
 
119
- const elementTable = Audit.makeTableDetails(headings, lcpElementDetails);
136
+ const elementTable = this.makeElementTable(artifacts);
137
+ if (!elementTable) return {score: null, notApplicable: true};
120
138
 
121
139
  const items = [elementTable];
122
- if (elementTable.items.length) {
123
- const phaseTable = await this.makePhaseTable(artifacts, context);
124
- if (phaseTable) items.push(phaseTable);
140
+ let displayValue;
141
+
142
+ const metricLcp = await this.getOptionalLCPMetric(metricComputationData, context);
143
+ if (metricLcp) {
144
+ displayValue = str_(i18n.UIStrings.ms, {timeInMs: metricLcp});
145
+ const phaseTable = await this.makePhaseTable(metricLcp, metricComputationData, context);
146
+ items.push(phaseTable);
125
147
  }
126
148
 
127
149
  const details = Audit.makeListDetails(items);
128
150
 
129
- const displayValue = str_(i18n.UIStrings.displayValueElementsFound,
130
- {nodeCount: lcpElementDetails.length});
131
-
132
151
  return {
133
152
  score: 1,
134
- notApplicable: lcpElementDetails.length === 0,
135
153
  displayValue,
136
154
  details,
137
155
  };
@@ -2,9 +2,10 @@ export default LayoutShiftElements;
2
2
  declare class LayoutShiftElements extends Audit {
3
3
  /**
4
4
  * @param {LH.Artifacts} artifacts
5
- * @return {LH.Audit.Product}
5
+ * @param {LH.Audit.Context} context
6
+ * @return {Promise<LH.Audit.Product>}
6
7
  */
7
- static audit(artifacts: LH.Artifacts): LH.Audit.Product;
8
+ static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
8
9
  }
9
10
  export namespace UIStrings {
10
11
  const title: string;
@@ -6,6 +6,7 @@
6
6
 
7
7
  import {Audit} from './audit.js';
8
8
  import * as i18n from '../lib/i18n/i18n.js';
9
+ import {CumulativeLayoutShift} from '../computed/metrics/cumulative-layout-shift.js';
9
10
 
10
11
  const UIStrings = {
11
12
  /** Descriptive title of a diagnostic audit that provides up to the top five elements contributing to Cumulative Layout Shift. */
@@ -34,9 +35,10 @@ class LayoutShiftElements extends Audit {
34
35
 
35
36
  /**
36
37
  * @param {LH.Artifacts} artifacts
37
- * @return {LH.Audit.Product}
38
+ * @param {LH.Audit.Context} context
39
+ * @return {Promise<LH.Audit.Product>}
38
40
  */
39
- static audit(artifacts) {
41
+ static async audit(artifacts, context) {
40
42
  const clsElements = artifacts.TraceElements
41
43
  .filter(element => element.traceEventType === 'layout-shift');
42
44
 
@@ -61,8 +63,14 @@ class LayoutShiftElements extends Audit {
61
63
  {nodeCount: clsElementData.length});
62
64
  }
63
65
 
66
+ const {cumulativeLayoutShift: clsSavings} =
67
+ await CumulativeLayoutShift.request(artifacts.traces[Audit.DEFAULT_PASS], context);
68
+
64
69
  return {
65
70
  score: 1,
71
+ metricSavings: {
72
+ CLS: clsSavings,
73
+ },
66
74
  notApplicable: details.items.length === 0,
67
75
  displayValue,
68
76
  details,
@@ -11,10 +11,8 @@ import {TimingSummary} from '../computed/metrics/timing-summary.js';
11
11
  const DECIMAL_METRIC_KEYS = new Set([
12
12
  'cumulativeLayoutShift',
13
13
  'cumulativeLayoutShiftMainFrame',
14
- 'totalCumulativeLayoutShift',
15
14
  'observedCumulativeLayoutShift',
16
15
  'observedCumulativeLayoutShiftMainFrame',
17
- 'observedTotalCumulativeLayoutShift',
18
16
  ]);
19
17
 
20
18
  class Metrics extends Audit {
@@ -8,6 +8,7 @@ import {Audit} from './audit.js';
8
8
  import UrlUtils from '../lib/url-utils.js';
9
9
  import {NetworkRecords} from '../computed/network-records.js';
10
10
  import {MainResource} from '../computed/main-resource.js';
11
+ import {EntityClassification} from '../computed/entity-classification.js';
11
12
 
12
13
  class NetworkRequests extends Audit {
13
14
  /**
@@ -31,6 +32,8 @@ class NetworkRequests extends Audit {
31
32
  static async audit(artifacts, context) {
32
33
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
33
34
  const records = await NetworkRecords.request(devtoolsLog, context);
35
+ const classifiedEntities = await EntityClassification.request(
36
+ {URL: artifacts.URL, devtoolsLog}, context);
34
37
  const earliestRendererStartTime = records.reduce(
35
38
  (min, record) => Math.min(min, record.rendererStartTime),
36
39
  Infinity
@@ -61,6 +64,8 @@ class NetworkRequests extends Audit {
61
64
  ((record.frameId === mainFrameId) || undefined) :
62
65
  undefined;
63
66
 
67
+ const entity = classifiedEntities.entityByUrl.get(record.url);
68
+
64
69
  return {
65
70
  url: UrlUtils.elideDataURI(record.url),
66
71
  sessionTargetType: record.sessionTargetType,
@@ -77,6 +82,7 @@ class NetworkRequests extends Audit {
77
82
  priority: record.priority,
78
83
  isLinkPreload,
79
84
  experimentalFromMainFrame,
85
+ entity: entity?.name,
80
86
  lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
81
87
  lrTCPMs: TCPMs, // Only exists on Lightrider runs
82
88
  lrRequestMs: requestMs, // Only exists on Lightrider runs
@@ -43,6 +43,7 @@ class CrawlableAnchors extends Audit {
43
43
  rawHref,
44
44
  name = '',
45
45
  role = '',
46
+ id,
46
47
  }) => {
47
48
  rawHref = rawHref.replace( /\s/g, '');
48
49
  name = name.trim();
@@ -52,6 +53,9 @@ class CrawlableAnchors extends Audit {
52
53
  // Ignore mailto links even if they use one of the failing patterns. See https://github.com/GoogleChrome/lighthouse/issues/11443#issuecomment-694898412
53
54
  if (rawHref.startsWith('mailto:')) return;
54
55
 
56
+ // Ignore `<a id="something">` elements acting as an anchor.
57
+ if (rawHref === '' && id) return;
58
+
55
59
  const javaScriptVoidRegExp = /javascript:void(\(|)0(\)|)/;
56
60
 
57
61
  if (rawHref.startsWith('file:')) return true;