lighthouse 12.8.2-dev.20250923 → 12.8.2-dev.20250925

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 (74) hide show
  1. package/cli/test/smokehouse/version-check.d.ts +1 -1
  2. package/core/audits/accessibility/aria-allowed-role.js +1 -0
  3. package/core/audits/accessibility/image-redundant-alt.js +1 -0
  4. package/core/audits/accessibility/landmark-one-main.js +0 -1
  5. package/core/audits/accessibility/table-duplicate-name.js +1 -0
  6. package/core/config/default-config.js +90 -71
  7. package/core/gather/gatherers/image-elements.js +32 -6
  8. package/core/lib/emulation.d.ts +10 -0
  9. package/core/lib/emulation.js +21 -6
  10. package/core/lib/legacy-javascript/legacy-javascript.js +4 -11
  11. package/core/lib/proto-preprocessor.js +5 -3
  12. package/core/scoring.js +1 -1
  13. package/package.json +3 -4
  14. package/readme.md +1 -1
  15. package/report/renderer/details-renderer.d.ts +1 -2
  16. package/report/renderer/details-renderer.js +0 -1
  17. package/shared/localization/locales/ar-XB.json +0 -30
  18. package/shared/localization/locales/ar.json +0 -30
  19. package/shared/localization/locales/bg.json +0 -30
  20. package/shared/localization/locales/ca.json +0 -30
  21. package/shared/localization/locales/cs.json +0 -30
  22. package/shared/localization/locales/da.json +0 -30
  23. package/shared/localization/locales/de.json +0 -30
  24. package/shared/localization/locales/el.json +0 -30
  25. package/shared/localization/locales/en-GB.json +0 -30
  26. package/shared/localization/locales/en-US.json +0 -30
  27. package/shared/localization/locales/en-XA.json +0 -30
  28. package/shared/localization/locales/en-XL.json +0 -30
  29. package/shared/localization/locales/es-419.json +0 -30
  30. package/shared/localization/locales/es.json +0 -30
  31. package/shared/localization/locales/fi.json +0 -30
  32. package/shared/localization/locales/fil.json +0 -30
  33. package/shared/localization/locales/fr.json +0 -30
  34. package/shared/localization/locales/he.json +0 -30
  35. package/shared/localization/locales/hi.json +0 -30
  36. package/shared/localization/locales/hr.json +0 -30
  37. package/shared/localization/locales/hu.json +0 -30
  38. package/shared/localization/locales/id.json +0 -30
  39. package/shared/localization/locales/it.json +0 -30
  40. package/shared/localization/locales/ja.json +0 -30
  41. package/shared/localization/locales/ko.json +0 -30
  42. package/shared/localization/locales/lt.json +0 -30
  43. package/shared/localization/locales/lv.json +0 -30
  44. package/shared/localization/locales/nl.json +0 -30
  45. package/shared/localization/locales/no.json +0 -30
  46. package/shared/localization/locales/pl.json +0 -30
  47. package/shared/localization/locales/pt-PT.json +0 -30
  48. package/shared/localization/locales/pt.json +0 -30
  49. package/shared/localization/locales/ro.json +0 -30
  50. package/shared/localization/locales/ru.json +0 -30
  51. package/shared/localization/locales/sk.json +0 -30
  52. package/shared/localization/locales/sl.json +0 -30
  53. package/shared/localization/locales/sr-Latn.json +0 -30
  54. package/shared/localization/locales/sr.json +0 -30
  55. package/shared/localization/locales/sv.json +0 -30
  56. package/shared/localization/locales/ta.json +0 -30
  57. package/shared/localization/locales/te.json +0 -30
  58. package/shared/localization/locales/th.json +0 -30
  59. package/shared/localization/locales/tr.json +0 -30
  60. package/shared/localization/locales/uk.json +0 -30
  61. package/shared/localization/locales/vi.json +0 -30
  62. package/shared/localization/locales/zh-HK.json +0 -30
  63. package/shared/localization/locales/zh-TW.json +0 -30
  64. package/shared/localization/locales/zh.json +0 -30
  65. package/shared/localization/locales.d.ts +2 -0
  66. package/shared/localization/locales.js +130 -139
  67. package/shared/tsconfig.json +2 -0
  68. package/tsconfig-base.json +1 -1
  69. package/tsconfig.json +1 -1
  70. package/types/artifacts.d.ts +0 -33
  71. package/core/audits/seo/font-size.d.ts +0 -24
  72. package/core/audits/seo/font-size.js +0 -344
  73. package/core/gather/gatherers/seo/font-size.d.ts +0 -131
  74. package/core/gather/gatherers/seo/font-size.js +0 -347
@@ -11,5 +11,5 @@ export function chromiumVersionCheck(opts: {
11
11
  * @param {number[]} versionA
12
12
  * @param {number[]} versionB
13
13
  */
14
- export function compareVersions(versionA: number[], versionB: number[]): 0 | 1 | -1;
14
+ export function compareVersions(versionA: number[], versionB: number[]): 1 | 0 | -1;
15
15
  //# sourceMappingURL=version-check.d.ts.map
@@ -35,6 +35,7 @@ class ARIAAllowedRole 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
  }
@@ -36,6 +36,7 @@ class ImageRedundantAlt extends AxeAudit {
36
36
  title: str_(UIStrings.title),
37
37
  failureTitle: str_(UIStrings.failureTitle),
38
38
  description: str_(UIStrings.description),
39
+ scoreDisplayMode: AxeAudit.SCORING_MODES.INFORMATIVE,
39
40
  requiredArtifacts: ['Accessibility'],
40
41
  };
41
42
  }
@@ -34,7 +34,6 @@ class LandmarkOneMain extends AxeAudit {
34
34
  title: str_(UIStrings.title),
35
35
  failureTitle: str_(UIStrings.failureTitle),
36
36
  description: str_(UIStrings.description),
37
- scoreDisplayMode: AxeAudit.SCORING_MODES.INFORMATIVE,
38
37
  requiredArtifacts: ['Accessibility'],
39
38
  };
40
39
  }
@@ -35,6 +35,7 @@ class TableDuplicateName 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
  }
@@ -119,7 +119,6 @@ const defaultConfig = {
119
119
  {id: 'CSSUsage', gatherer: 'css-usage'},
120
120
  {id: 'Doctype', gatherer: 'dobetterweb/doctype'},
121
121
  {id: 'DOMStats', gatherer: 'dobetterweb/domstats'},
122
- {id: 'FontSize', gatherer: 'seo/font-size'},
123
122
  {id: 'Inputs', gatherer: 'inputs'},
124
123
  {id: 'IFrameElements', gatherer: 'iframe-elements'},
125
124
  {id: 'ImageElements', gatherer: 'image-elements'},
@@ -302,7 +301,6 @@ const defaultConfig = {
302
301
  'dobetterweb/uses-passive-event-listeners',
303
302
  'seo/meta-description',
304
303
  'seo/http-status-code',
305
- 'seo/font-size',
306
304
  'seo/link-text',
307
305
  'seo/crawlable-anchors',
308
306
  'seo/is-crawlable',
@@ -496,68 +494,86 @@ const defaultConfig = {
496
494
  description: str_(UIStrings.a11yCategoryDescription),
497
495
  manualDescription: str_(UIStrings.a11yCategoryManualDescription),
498
496
  supportedModes: ['navigation', 'snapshot'],
499
- // Audit weights are meant to match the aXe scoring system of
500
- // minor, moderate, serious, and critical.
501
- // See the audits listed at dequeuniversity.com/rules/axe/4.7.
502
- // Click on an audit and check the right hand column to see its severity.
497
+ // Audit weights weights are derived from the axe-core "Impact",
498
+ // with adjustments based on axe-core "Tags":
499
+ //
500
+ // ┌────────────┬───────────────────────────────────────────────┐
501
+ // │ Impact │ Weight Based on Tags │
502
+ // │ ├──────────────┬─────────────────┬──────────────┤
503
+ // │ │ wcag A+AA │ best-practice │ experimental │
504
+ // │ │ (ex: wcag2aa)│ (w/o wcag tag) │ │
505
+ // ├────────────┼──────────────┼─────────────────┼──────────────┤
506
+ // │ Minor │ 1 │ 0 │ 0 │
507
+ // │ Moderate │ 3 │ 3 │ 0 │
508
+ // │ Serious │ 7 │ 7 │ 0 │
509
+ // │ Critical │ 10 │ 10 │ 0 │
510
+ // └────────────┴──────────────┴─────────────────┴──────────────┘
511
+ //
512
+ // Notes:
513
+ // • Experimental rules always have weight 0
514
+ // • Best practice rules only affect scores when tagged with wcagA+AA
515
+ // and are moderate, serious, or critical.
516
+ //
517
+ // To find the latest axe-core Impact and Tag values:
518
+ // 1. Browse to https://dequeuniversity.com/rules/axe/html.
519
+ // 2. Click on the latest rule set (ex: https://dequeuniversity.com/rules/axe/html/4.10)
520
+ // 3. Review the tables
503
521
  auditRefs: [
504
- {id: 'accesskeys', weight: 7, group: 'a11y-navigation'},
505
- {id: 'aria-allowed-attr', weight: 10, group: 'a11y-aria'},
506
- {id: 'aria-allowed-role', weight: 1, group: 'a11y-aria'},
507
- {id: 'aria-command-name', weight: 7, group: 'a11y-aria'},
508
- {id: 'aria-conditional-attr', weight: 7, group: 'a11y-aria'},
509
- {id: 'aria-deprecated-role', weight: 1, group: 'a11y-aria'},
510
- {id: 'aria-dialog-name', weight: 7, group: 'a11y-aria'},
511
- {id: 'aria-hidden-body', weight: 10, group: 'a11y-aria'},
512
- {id: 'aria-hidden-focus', weight: 7, group: 'a11y-aria'},
513
- {id: 'aria-input-field-name', weight: 7, group: 'a11y-aria'},
514
- {id: 'aria-meter-name', weight: 7, group: 'a11y-aria'},
515
- {id: 'aria-progressbar-name', weight: 7, group: 'a11y-aria'},
516
- {id: 'aria-prohibited-attr', weight: 7, group: 'a11y-aria'},
517
- {id: 'aria-required-attr', weight: 10, group: 'a11y-aria'},
518
- {id: 'aria-required-children', weight: 10, group: 'a11y-aria'},
519
- {id: 'aria-required-parent', weight: 10, group: 'a11y-aria'},
520
- {id: 'aria-roles', weight: 7, group: 'a11y-aria'},
521
- {id: 'aria-text', weight: 7, group: 'a11y-aria'},
522
- {id: 'aria-toggle-field-name', weight: 7, group: 'a11y-aria'},
523
- {id: 'aria-tooltip-name', weight: 7, group: 'a11y-aria'},
524
- {id: 'aria-treeitem-name', weight: 7, group: 'a11y-aria'},
525
- {id: 'aria-valid-attr-value', weight: 10, group: 'a11y-aria'},
526
- {id: 'aria-valid-attr', weight: 10, group: 'a11y-aria'},
527
- {id: 'button-name', weight: 10, group: 'a11y-names-labels'},
528
- {id: 'bypass', weight: 7, group: 'a11y-navigation'},
529
- {id: 'color-contrast', weight: 7, group: 'a11y-color-contrast'},
530
- {id: 'definition-list', weight: 7, group: 'a11y-tables-lists'},
531
- {id: 'dlitem', weight: 7, group: 'a11y-tables-lists'},
532
- {id: 'document-title', weight: 7, group: 'a11y-names-labels'},
533
- {id: 'duplicate-id-aria', weight: 10, group: 'a11y-aria'},
534
- {id: 'form-field-multiple-labels', weight: 3, group: 'a11y-names-labels'},
535
- {id: 'frame-title', weight: 7, group: 'a11y-names-labels'},
536
- {id: 'heading-order', weight: 3, group: 'a11y-navigation'},
537
- {id: 'html-has-lang', weight: 7, group: 'a11y-language'},
538
- {id: 'html-lang-valid', weight: 7, group: 'a11y-language'},
539
- {id: 'html-xml-lang-mismatch', weight: 3, group: 'a11y-language'},
540
- {id: 'image-alt', weight: 10, group: 'a11y-names-labels'},
541
- {id: 'image-redundant-alt', weight: 1, group: 'a11y-names-labels'},
542
- {id: 'input-button-name', weight: 10, group: 'a11y-names-labels'},
543
- {id: 'input-image-alt', weight: 10, group: 'a11y-names-labels'},
544
- {id: 'label', weight: 7, group: 'a11y-names-labels'},
545
- {id: 'link-in-text-block', weight: 7, group: 'a11y-color-contrast'},
546
- {id: 'link-name', weight: 7, group: 'a11y-names-labels'},
547
- {id: 'list', weight: 7, group: 'a11y-tables-lists'},
548
- {id: 'listitem', weight: 7, group: 'a11y-tables-lists'},
549
- {id: 'meta-refresh', weight: 10, group: 'a11y-best-practices'},
550
- {id: 'meta-viewport', weight: 10, group: 'a11y-best-practices'},
551
- {id: 'object-alt', weight: 7, group: 'a11y-names-labels'},
552
- {id: 'select-name', weight: 7, group: 'a11y-names-labels'},
553
- {id: 'skip-link', weight: 3, group: 'a11y-names-labels'},
554
- {id: 'tabindex', weight: 7, group: 'a11y-navigation'},
555
- {id: 'table-duplicate-name', weight: 1, group: 'a11y-tables-lists'},
556
- {id: 'target-size', weight: 7, group: 'a11y-best-practices'},
557
- {id: 'td-headers-attr', weight: 7, group: 'a11y-tables-lists'},
558
- {id: 'th-has-data-cells', weight: 7, group: 'a11y-tables-lists'},
559
- {id: 'valid-lang', weight: 7, group: 'a11y-language'},
560
- {id: 'video-caption', weight: 10, group: 'a11y-audio-video'},
522
+ {id: 'accesskeys', weight: 7, group: 'a11y-navigation'}, // Serious, best-practice
523
+ {id: 'aria-allowed-attr', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
524
+ {id: 'aria-command-name', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
525
+ {id: 'aria-conditional-attr', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
526
+ {id: 'aria-deprecated-role', weight: 1, group: 'a11y-aria'}, // Minor, wcag2a
527
+ {id: 'aria-dialog-name', weight: 7, group: 'a11y-aria'}, // Serious, best-practice
528
+ {id: 'aria-hidden-body', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
529
+ {id: 'aria-hidden-focus', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
530
+ {id: 'aria-input-field-name', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
531
+ {id: 'aria-meter-name', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
532
+ {id: 'aria-progressbar-name', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
533
+ {id: 'aria-prohibited-attr', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
534
+ {id: 'aria-required-attr', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
535
+ {id: 'aria-required-children', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
536
+ {id: 'aria-required-parent', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
537
+ {id: 'aria-roles', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
538
+ {id: 'aria-text', weight: 7, group: 'a11y-aria'}, // Serious, best-practice
539
+ {id: 'aria-toggle-field-name', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
540
+ {id: 'aria-tooltip-name', weight: 7, group: 'a11y-aria'}, // Serious, wcag2a
541
+ {id: 'aria-treeitem-name', weight: 7, group: 'a11y-aria'}, // Serious, best-practice
542
+ {id: 'aria-valid-attr-value', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
543
+ {id: 'aria-valid-attr', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
544
+ {id: 'button-name', weight: 10, group: 'a11y-names-labels'}, // Critical, wcag2a
545
+ {id: 'bypass', weight: 7, group: 'a11y-navigation'}, // Serious, wcag2a
546
+ {id: 'color-contrast', weight: 7, group: 'a11y-color-contrast'}, // Serious, wcag2aa
547
+ {id: 'definition-list', weight: 7, group: 'a11y-tables-lists'}, // Serious, wcag2a
548
+ {id: 'dlitem', weight: 7, group: 'a11y-tables-lists'}, // Serious, wcag2a
549
+ {id: 'document-title', weight: 7, group: 'a11y-names-labels'}, // Serious, wcag2a
550
+ {id: 'duplicate-id-aria', weight: 10, group: 'a11y-aria'}, // Critical, wcag2a
551
+ {id: 'form-field-multiple-labels', weight: 3, group: 'a11y-names-labels'}, // Moderate, wcag2a
552
+ {id: 'frame-title', weight: 7, group: 'a11y-names-labels'}, // Serious, wcag2a
553
+ {id: 'heading-order', weight: 3, group: 'a11y-navigation'}, // Moderate, best-practice
554
+ {id: 'html-has-lang', weight: 7, group: 'a11y-language'}, // Serious, wcag2a
555
+ {id: 'html-lang-valid', weight: 7, group: 'a11y-language'}, // Serious, wcag2a
556
+ {id: 'html-xml-lang-mismatch', weight: 3, group: 'a11y-language'}, // Moderate, wcag2a
557
+ {id: 'image-alt', weight: 10, group: 'a11y-names-labels'}, // Critical, wcag2a
558
+ {id: 'input-button-name', weight: 10, group: 'a11y-names-labels'}, // Critical, wcag2a
559
+ {id: 'input-image-alt', weight: 10, group: 'a11y-names-labels'}, // Critical, wcag2a
560
+ {id: 'label', weight: 10, group: 'a11y-names-labels'}, // Critical, wcag2a
561
+ {id: 'link-in-text-block', weight: 7, group: 'a11y-color-contrast'}, // Serious, wcag2a
562
+ {id: 'link-name', weight: 7, group: 'a11y-names-labels'}, // Serious, wcag2a
563
+ {id: 'list', weight: 7, group: 'a11y-tables-lists'}, // Serious, wcag2a
564
+ {id: 'listitem', weight: 7, group: 'a11y-tables-lists'}, // Serious, wcag2a
565
+ {id: 'meta-refresh', weight: 10, group: 'a11y-best-practices'}, // Critical, wcag2a
566
+ {id: 'meta-viewport', weight: 10, group: 'a11y-best-practices'}, // Critical, wcag2aa
567
+ {id: 'object-alt', weight: 7, group: 'a11y-names-labels'}, // Serious, wcag2a
568
+ {id: 'select-name', weight: 10, group: 'a11y-names-labels'}, // Critical, wcag2a
569
+ {id: 'skip-link', weight: 3, group: 'a11y-names-labels'}, // Moderate, best-practice
570
+ {id: 'tabindex', weight: 7, group: 'a11y-navigation'}, // Serious, best-practice
571
+ {id: 'target-size', weight: 7, group: 'a11y-best-practices'}, // Serious, wcag22aa
572
+ {id: 'td-headers-attr', weight: 7, group: 'a11y-tables-lists'}, // Serious, wcag2a
573
+ {id: 'th-has-data-cells', weight: 7, group: 'a11y-tables-lists'}, // Serious, wcag2a
574
+ {id: 'valid-lang', weight: 7, group: 'a11y-language'}, // Serious, wcag2aa
575
+ {id: 'video-caption', weight: 10, group: 'a11y-audio-video'}, // Critical, wcag2a
576
+ {id: 'landmark-one-main', weight: 3, group: 'a11y-best-practices'}, // Moderate, best-practice
561
577
  // Manual audits
562
578
  {id: 'focusable-controls', weight: 0},
563
579
  {id: 'interactive-element-affordance', weight: 0},
@@ -569,13 +585,17 @@ const defaultConfig = {
569
585
  {id: 'offscreen-content-hidden', weight: 0},
570
586
  {id: 'custom-controls-labels', weight: 0},
571
587
  {id: 'custom-controls-roles', weight: 0},
572
- // Hidden audits
573
- {id: 'empty-heading', weight: 0, group: 'hidden'},
574
- {id: 'identical-links-same-purpose', weight: 0, group: 'hidden'},
575
- {id: 'landmark-one-main', weight: 0, group: 'hidden'},
576
- {id: 'label-content-name-mismatch', weight: 0, group: 'hidden'},
577
- {id: 'table-fake-caption', weight: 0, group: 'hidden'},
578
- {id: 'td-has-header', weight: 0, group: 'hidden'},
588
+ // Low-impact best-practices
589
+ {id: 'table-duplicate-name', weight: 0, group: 'a11y-best-practices'}, // Minor, best-practice
590
+ {id: 'empty-heading', weight: 0, group: 'a11y-best-practices'}, // Minor, best-practice
591
+ {id: 'aria-allowed-role', weight: 0, group: 'a11y-best-practices'}, // Minor, best-practice
592
+ {id: 'image-redundant-alt', weight: 0, group: 'a11y-names-labels'}, // Minor, best-practice
593
+ // WCAG AAA
594
+ {id: 'identical-links-same-purpose', weight: 0, group: 'a11y-best-practices'}, // Minor, wcag2aaa
595
+ // Hidden audits (ie. experimental)
596
+ {id: 'label-content-name-mismatch', weight: 0, group: 'hidden'}, // Serious, experimental
597
+ {id: 'table-fake-caption', weight: 0, group: 'hidden'}, // Serious, experimental
598
+ {id: 'td-has-header', weight: 0, group: 'hidden'}, // Critical, experimental
579
599
  ],
580
600
  },
581
601
  'best-practices': {
@@ -597,7 +617,6 @@ const defaultConfig = {
597
617
  {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'},
598
618
  {id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'},
599
619
  {id: 'viewport', weight: 1, group: 'best-practices-ux'},
600
- {id: 'font-size', weight: 1, group: 'best-practices-ux'},
601
620
  // Browser Compatibility
602
621
  {id: 'doctype', weight: 1, group: 'best-practices-browser-compat'},
603
622
  {id: 'charset', weight: 1, group: 'best-practices-browser-compat'},
@@ -3,17 +3,16 @@
3
3
  * Copyright 2017 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- /**
7
- * @fileoverview Gathers all images used on the page with their src, size,
8
- * and attribute information. Executes script in the context of the page.
9
- */
10
6
 
7
+ /**
8
+ * @fileoverview Gathers all images used on the page with their src, size,
9
+ * and attribute information. Executes script in the context of the page.
10
+ */
11
11
 
12
12
  import log from 'lighthouse-logger';
13
13
 
14
14
  import BaseGatherer from '../base-gatherer.js';
15
15
  import {pageFunctions} from '../../lib/page-functions.js';
16
- import * as FontSize from './seo/font-size.js';
17
16
 
18
17
  /* global getElementsInDocument, getNodeDetails */
19
18
 
@@ -186,6 +185,33 @@ function findSizeDeclaration(rule, property) {
186
185
  return definedProp.value;
187
186
  }
188
187
 
188
+ /**
189
+ * Finds the most specific directly matched CSS font-size rule from the list.
190
+ *
191
+ * @param {Array<LH.Crdp.CSS.RuleMatch>} matchedCSSRules
192
+ * @param {function(LH.Crdp.CSS.CSSStyle):boolean|string|undefined} isDeclarationOfInterest
193
+ */
194
+ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], isDeclarationOfInterest) {
195
+ let mostSpecificRule;
196
+ for (let i = matchedCSSRules.length - 1; i >= 0; i--) {
197
+ if (isDeclarationOfInterest(matchedCSSRules[i].rule.style)) {
198
+ mostSpecificRule = matchedCSSRules[i].rule;
199
+ break;
200
+ }
201
+ }
202
+
203
+ if (mostSpecificRule) {
204
+ return {
205
+ type: 'Regular',
206
+ ...mostSpecificRule.style,
207
+ parentRule: {
208
+ origin: mostSpecificRule.origin,
209
+ selectors: mostSpecificRule.selectorList.selectors,
210
+ },
211
+ };
212
+ }
213
+ }
214
+
189
215
  /**
190
216
  * Finds the most specific directly matched CSS font-size rule from the list.
191
217
  *
@@ -196,7 +222,7 @@ function findSizeDeclaration(rule, property) {
196
222
  function findMostSpecificCSSRule(matchedCSSRules, property) {
197
223
  /** @param {LH.Crdp.CSS.CSSStyle} declaration */
198
224
  const isDeclarationofInterest = (declaration) => findSizeDeclaration(declaration, property);
199
- const rule = FontSize.findMostSpecificMatchedCSSRule(matchedCSSRules, isDeclarationofInterest);
225
+ const rule = findMostSpecificMatchedCSSRule(matchedCSSRules, isDeclarationofInterest);
200
226
  if (!rule) return;
201
227
 
202
228
  return findSizeDeclaration(rule, property);
@@ -40,4 +40,14 @@ export function enableCPUThrottling(session: LH.Gatherer.ProtocolSession, thrott
40
40
  * @return {Promise<void>}
41
41
  */
42
42
  export function clearCPUThrottling(session: LH.Gatherer.ProtocolSession): Promise<void>;
43
+ /**
44
+ * Tweak a useragent to have the milestone match the host's Chrome version.
45
+ * @param {LH.Gatherer.ProtocolSession} session
46
+ * @param {string} userAgent
47
+ * @returns {Promise<{tweakedUA: string, fullVersion: string}>}
48
+ */
49
+ export function matchHostUAVersion(session: LH.Gatherer.ProtocolSession, userAgent: string): Promise<{
50
+ tweakedUA: string;
51
+ fullVersion: string;
52
+ }>;
43
53
  //# sourceMappingURL=emulation.d.ts.map
@@ -4,6 +4,8 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import {getBrowserVersion} from '../gather/driver/environment.js';
8
+
7
9
  const NO_THROTTLING_METRICS = {
8
10
  latency: 0,
9
11
  downloadThroughput: 0,
@@ -16,13 +18,11 @@ const NO_CPU_THROTTLE_METRICS = {
16
18
  };
17
19
 
18
20
  /**
19
- * @param {string} userAgent
21
+ * @param {string} fullVersion
20
22
  * @param {LH.Config.Settings['formFactor']} formFactor
21
23
  * @return {LH.Crdp.Emulation.SetUserAgentOverrideRequest['userAgentMetadata']}
22
24
  */
23
- function parseUseragentIntoMetadata(userAgent, formFactor) {
24
- const match = userAgent.match(/Chrome\/([\d.]+)/); // eg 'Chrome/(71.0.3577.0)'
25
- const fullVersion = match?.[1] || '99.0.1234.0';
25
+ function parseUseragentIntoMetadata(fullVersion, formFactor) {
26
26
  const [version] = fullVersion.split('.', 1);
27
27
  const brands = [
28
28
  {brand: 'Chromium', version},
@@ -54,6 +54,19 @@ function parseUseragentIntoMetadata(userAgent, formFactor) {
54
54
  };
55
55
  }
56
56
 
57
+ /**
58
+ * Tweak a useragent to have the milestone match the host's Chrome version.
59
+ * @param {LH.Gatherer.ProtocolSession} session
60
+ * @param {string} userAgent
61
+ * @returns {Promise<{tweakedUA: string, fullVersion: string}>}
62
+ */
63
+ async function matchHostUAVersion(session, userAgent) {
64
+ const {milestone} = await getBrowserVersion(session);
65
+ const tweakedUA = userAgent.replace(/(Chrome\/)[\d.]+/, `$1${milestone}.0.0.0`);
66
+ const fullVersion = `${milestone}.0.0.0`;
67
+ return {tweakedUA, fullVersion};
68
+ }
69
+
57
70
  /**
58
71
  * @param {LH.Gatherer.ProtocolSession} session
59
72
  * @param {LH.Config.Settings} settings
@@ -62,9 +75,10 @@ function parseUseragentIntoMetadata(userAgent, formFactor) {
62
75
  async function emulate(session, settings) {
63
76
  if (settings.emulatedUserAgent !== false) {
64
77
  const userAgent = /** @type {string} */ (settings.emulatedUserAgent);
78
+ const {tweakedUA, fullVersion} = await matchHostUAVersion(session, userAgent);
65
79
  await session.sendCommand('Network.setUserAgentOverride', {
66
- userAgent,
67
- userAgentMetadata: parseUseragentIntoMetadata(userAgent, settings.formFactor),
80
+ userAgent: tweakedUA,
81
+ userAgentMetadata: parseUseragentIntoMetadata(fullVersion, settings.formFactor),
68
82
  });
69
83
  }
70
84
  // See devtools-entry for one usecase for disabling screenEmulation
@@ -157,4 +171,5 @@ export {
157
171
  clearNetworkThrottling,
158
172
  enableCPUThrottling,
159
173
  clearCPUThrottling,
174
+ matchHostUAVersion,
160
175
  };
@@ -15,21 +15,14 @@
15
15
  /** @typedef {{name: string, line: number, column: number}} PatternMatchResult */
16
16
  /** @typedef {{matches: PatternMatchResult[], estimatedByteSavings: number}} Result */
17
17
 
18
- import fs from 'fs';
19
-
20
- import {LH_ROOT} from '../../../shared/root.js';
21
-
22
- const polyfillModuleDataJson = fs.readFileSync(
23
- `${LH_ROOT}/core/lib/legacy-javascript/polyfill-module-data.json`, 'utf-8');
18
+ import polyfillModuleData_ from './polyfill-module-data.json' with { type: 'json' };
19
+ import graph_ from './polyfill-graph-data.json' with { type: 'json' };
24
20
 
25
21
  /** @type {import('../../scripts/legacy-javascript/create-polyfill-module-data.js').PolyfillModuleData} */
26
- const polyfillModuleData = JSON.parse(polyfillModuleDataJson);
27
-
28
- const graphJson = fs.readFileSync(
29
- `${LH_ROOT}/core/lib/legacy-javascript/polyfill-graph-data.json`, 'utf-8');
22
+ const polyfillModuleData = polyfillModuleData_;
30
23
 
31
24
  /** @type {import('../../scripts/legacy-javascript/create-polyfill-size-estimation.js').PolyfillSizeEstimator} */
32
- const graph = JSON.parse(graphJson);
25
+ const graph = graph_;
33
26
 
34
27
  /**
35
28
  * Takes a list of patterns (consisting of a name identifier and a RegExp expression string)
@@ -6,8 +6,6 @@
6
6
 
7
7
  import fs from 'fs';
8
8
 
9
- import esMain from 'es-main';
10
-
11
9
  /**
12
10
  * @fileoverview Helper functions to transform an LHR into a proto-ready LHR.
13
11
  *
@@ -126,7 +124,7 @@ function processForProto(lhr) {
126
124
  }
127
125
 
128
126
  // Test if called from the CLI or as a module.
129
- if (esMain(import.meta)) {
127
+ if (import.meta.main) {
130
128
  // read in the argv for the input & output
131
129
  const args = process.argv.slice(2);
132
130
  let input;
@@ -143,6 +141,10 @@ if (esMain(import.meta)) {
143
141
  const report = processForProto(JSON.parse(fs.readFileSync(input, 'utf-8')));
144
142
  // write to output from argv
145
143
  fs.writeFileSync(output, JSON.stringify(report), 'utf-8');
144
+ // eslint-disable-next-line no-console
145
+ console.log(`file written to ${output}`);
146
+ } else {
147
+ process.exit(1);
146
148
  }
147
149
  }
148
150
 
package/core/scoring.js CHANGED
@@ -57,7 +57,7 @@ class ReportScoring {
57
57
  const member = {...configMember};
58
58
 
59
59
  // If a result was not applicable, meaning its checks did not run against anything on
60
- // the page, force it's weight to 0. It will not count during the arithmeticMean() but
60
+ // the page, force its weight to 0. It will not count during the arithmeticMean() but
61
61
  // will still be included in the final report json and displayed in the report as
62
62
  // "Not Applicable".
63
63
  const result = resultsByAuditId[member.id];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "12.8.2-dev.20250923",
4
+ "version": "12.8.2-dev.20250925",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "smokehouse": "./cli/test/smokehouse/frontends/smokehouse-bin.js"
11
11
  },
12
12
  "engines": {
13
- "node": ">=18.16"
13
+ "node": ">=22.19"
14
14
  },
15
15
  "scripts": {
16
16
  "prepack": "yarn build-report --standalone --flow --esm && yarn build-types",
@@ -147,7 +147,6 @@
147
147
  "core-js-compat": "^3.44.0",
148
148
  "cpy": "^8.1.2",
149
149
  "csv-validator": "^0.0.3",
150
- "es-main": "^1.2.0",
151
150
  "esbuild": "0.25.9",
152
151
  "eslint": "^9.28.0",
153
152
  "eslint-config-google": "^0.14.0",
@@ -185,7 +184,7 @@
185
184
  "@paulirish/trace_engine": "0.0.59",
186
185
  "@sentry/node": "^9.28.1",
187
186
  "axe-core": "^4.10.3",
188
- "chrome-launcher": "^1.2.0",
187
+ "chrome-launcher": "^1.2.1",
189
188
  "configstore": "^7.0.0",
190
189
  "csp_evaluator": "1.1.5",
191
190
  "devtools-protocol": "0.0.1507524",
package/readme.md CHANGED
@@ -53,7 +53,7 @@ The Chrome extension was available prior to Lighthouse being available in Chrome
53
53
  The Node CLI provides the most flexibility in how Lighthouse runs can be configured and reported. Users who want more advanced usage, or want to run Lighthouse in an automated fashion should use the Node CLI.
54
54
 
55
55
  > [!NOTE]
56
- > Lighthouse requires Node 18.20 or later.
56
+ > Lighthouse requires Node 22 (LTS) or later.
57
57
 
58
58
  **Installation**:
59
59
 
@@ -146,9 +146,8 @@ export class DetailsRenderer {
146
146
  /**
147
147
  * @param {LH.Audit.Details.SourceLocationValue} item
148
148
  * @return {Element|null}
149
- * @protected
150
149
  */
151
- protected renderSourceLocation(item: LH.Audit.Details.SourceLocationValue): Element | null;
150
+ renderSourceLocation(item: LH.Audit.Details.SourceLocationValue): Element | null;
152
151
  /**
153
152
  * @param {LH.Audit.Details.Filmstrip} details
154
153
  * @return {Element}
@@ -656,7 +656,6 @@ export class DetailsRenderer {
656
656
  /**
657
657
  * @param {LH.Audit.Details.SourceLocationValue} item
658
658
  * @return {Element|null}
659
- * @protected
660
659
  */
661
660
  renderSourceLocation(item) {
662
661
  if (!item.url) {
@@ -1316,36 +1316,6 @@
1316
1316
  "core/audits/seo/crawlable-anchors.js | title": {
1317
1317
  "message": "‏‮Links‬‏ ‏‮are‬‏ ‏‮crawlable‬‏"
1318
1318
  },
1319
- "core/audits/seo/font-size.js | additionalIllegibleText": {
1320
- "message": "‏‮Add‬‏'‏‮l‬‏ ‏‮illegible‬‏ ‏‮text‬‏"
1321
- },
1322
- "core/audits/seo/font-size.js | columnFontSize": {
1323
- "message": "‏‮Font‬‏ ‏‮Size‬‏"
1324
- },
1325
- "core/audits/seo/font-size.js | columnPercentPageText": {
1326
- "message": "% ‏‮of‬‏ ‏‮Page‬‏ ‏‮Text‬‏"
1327
- },
1328
- "core/audits/seo/font-size.js | columnSelector": {
1329
- "message": "‏‮Selector‬‏"
1330
- },
1331
- "core/audits/seo/font-size.js | description": {
1332
- "message": "‏‮Font‬‏ ‏‮sizes‬‏ ‏‮less‬‏ ‏‮than‬‏ 12‏‮px‬‏ ‏‮are‬‏ ‏‮too‬‏ ‏‮small‬‏ ‏‮to‬‏ ‏‮be‬‏ ‏‮legible‬‏ ‏‮and‬‏ ‏‮require‬‏ ‏‮mobile‬‏ ‏‮visitors‬‏ ‏‮to‬‏ “‏‮pinch‬‏ ‏‮to‬‏ ‏‮zoom‬‏” ‏‮in‬‏ ‏‮order‬‏ ‏‮to‬‏ ‏‮read‬‏. ‏‮Strive‬‏ ‏‮to‬‏ ‏‮have‬‏ >60% ‏‮of‬‏ ‏‮page‬‏ ‏‮text‬‏ ≥12‏‮px‬‏. [‏‮Learn‬‏ ‏‮more‬‏ ‏‮about‬‏ ‏‮legible‬‏ ‏‮font‬‏ ‏‮sizes‬‏](https://developer.chrome.com/docs/lighthouse/seo/font-size/)."
1333
- },
1334
- "core/audits/seo/font-size.js | displayValue": {
1335
- "message": "{decimalProportion, number, extendedPercent} ‏‮legible‬‏ ‏‮text‬‏"
1336
- },
1337
- "core/audits/seo/font-size.js | explanationViewport": {
1338
- "message": "‏‮Text‬‏ ‏‮is‬‏ ‏‮illegible‬‏ ‏‮because‬‏ ‏‮there‬‏'‏‮s‬‏ ‏‮no‬‏ ‏‮viewport‬‏ ‏‮meta‬‏ ‏‮tag‬‏ ‏‮optimized‬‏ ‏‮for‬‏ ‏‮mobile‬‏ ‏‮screens‬‏."
1339
- },
1340
- "core/audits/seo/font-size.js | failureTitle": {
1341
- "message": "‏‮Document‬‏ ‏‮doesn‬‏'‏‮t‬‏ ‏‮use‬‏ ‏‮legible‬‏ ‏‮font‬‏ ‏‮sizes‬‏"
1342
- },
1343
- "core/audits/seo/font-size.js | legibleText": {
1344
- "message": "‏‮Legible‬‏ ‏‮text‬‏"
1345
- },
1346
- "core/audits/seo/font-size.js | title": {
1347
- "message": "‏‮Document‬‏ ‏‮uses‬‏ ‏‮legible‬‏ ‏‮font‬‏ ‏‮sizes‬‏"
1348
- },
1349
1319
  "core/audits/seo/hreflang.js | description": {
1350
1320
  "message": "‏‮hreflang‬‏ ‏‮links‬‏ ‏‮tell‬‏ ‏‮search‬‏ ‏‮engines‬‏ ‏‮what‬‏ ‏‮version‬‏ ‏‮of‬‏ ‏‮a‬‏ ‏‮page‬‏ ‏‮they‬‏ ‏‮should‬‏ ‏‮list‬‏ ‏‮in‬‏ ‏‮search‬‏ ‏‮results‬‏ ‏‮for‬‏ ‏‮a‬‏ ‏‮given‬‏ ‏‮language‬‏ ‏‮or‬‏ ‏‮region‬‏. [‏‮Learn‬‏ ‏‮more‬‏ ‏‮about‬‏ `hreflang`](https://developer.chrome.com/docs/lighthouse/seo/hreflang/)."
1351
1321
  },
@@ -1316,36 +1316,6 @@
1316
1316
  "core/audits/seo/crawlable-anchors.js | title": {
1317
1317
  "message": "يمكن الزحف إلى الروابط"
1318
1318
  },
1319
- "core/audits/seo/font-size.js | additionalIllegibleText": {
1320
- "message": "نص إضافي غير قابل للقراءة"
1321
- },
1322
- "core/audits/seo/font-size.js | columnFontSize": {
1323
- "message": "حجم الخط"
1324
- },
1325
- "core/audits/seo/font-size.js | columnPercentPageText": {
1326
- "message": "% من نص الصفحة"
1327
- },
1328
- "core/audits/seo/font-size.js | columnSelector": {
1329
- "message": "أداة الاختيار"
1330
- },
1331
- "core/audits/seo/font-size.js | description": {
1332
- "message": "تكون أحجام الخطوط الأقل من 12 بكسل صغيرة جدًا بحيث لا يمكن قراءتها بسهولة وتتطلب من مستخدمي الأجهزة الجوّالة \"استخدام الإصبعين للتكبير\" من أجل قراءتها. يُرجى بذل قصارى جهدك لضبط الخطوط في أكثر من ‏60% من نص الصفحة على حجم أكبر من أو يساوي 12 بكسل. [مزيد من المعلومات حول أحجام الخطوط القابلة للقراءة](https://developer.chrome.com/docs/lighthouse/seo/font-size/)"
1333
- },
1334
- "core/audits/seo/font-size.js | displayValue": {
1335
- "message": "نص {decimalProportion, number, extendedPercent} قابل للقراءة"
1336
- },
1337
- "core/audits/seo/font-size.js | explanationViewport": {
1338
- "message": "النص غير مقروء لأنه لا تتوفّر علامة وصفية لإطار العرض محسنة لشاشات الجوّال."
1339
- },
1340
- "core/audits/seo/font-size.js | failureTitle": {
1341
- "message": "لا يستخدم المستند أحجام الخطوط القابلة للقراءة"
1342
- },
1343
- "core/audits/seo/font-size.js | legibleText": {
1344
- "message": "نص قابل للقراءة"
1345
- },
1346
- "core/audits/seo/font-size.js | title": {
1347
- "message": "يستخدم المستند أحجام الخط القابلة للقراءة"
1348
- },
1349
1319
  "core/audits/seo/hreflang.js | description": {
1350
1320
  "message": "توضّح روابط hreflang لمحركات البحث إصدار الصفحة الذي يجب إدراجه في نتائج البحث للغة أو منطقة معيّنة. [مزيد من المعلومات حول `hreflang`](https://developer.chrome.com/docs/lighthouse/seo/hreflang/)"
1351
1321
  },