lighthouse 12.6.1 → 12.7.0-dev.20250627

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 (162) hide show
  1. package/cli/test/smokehouse/core-tests.js +2 -0
  2. package/cli/test/smokehouse/frontends/lib.js +0 -2
  3. package/cli/test/smokehouse/frontends/smokehouse-bin.js +1 -3
  4. package/cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  5. package/cli/test/smokehouse/smokehouse.js +0 -2
  6. package/core/audits/audit.d.ts +7 -0
  7. package/core/audits/audit.js +17 -2
  8. package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -8
  9. package/core/audits/byte-efficiency/legacy-javascript.js +0 -1
  10. package/core/audits/byte-efficiency/render-blocking-resources.js +1 -1
  11. package/core/audits/deprecations.js +0 -2
  12. package/core/audits/insights/cache-insight.js +2 -1
  13. package/core/audits/insights/cls-culprits-insight.js +6 -7
  14. package/core/audits/insights/{interaction-to-next-paint-insight.d.ts → inp-breakdown-insight.d.ts} +3 -3
  15. package/core/audits/insights/{interaction-to-next-paint-insight.js → inp-breakdown-insight.js} +10 -10
  16. package/core/audits/insights/insight-audit.d.ts +7 -4
  17. package/core/audits/insights/insight-audit.js +48 -7
  18. package/core/audits/insights/lcp-breakdown-insight.d.ts +16 -0
  19. package/core/audits/insights/{lcp-phases-insight.js → lcp-breakdown-insight.js} +19 -19
  20. package/core/audits/insights/modern-http-insight.js +0 -2
  21. package/core/audits/insights/network-dependency-tree-insight.js +85 -8
  22. package/core/audits/insights/render-blocking-insight.js +1 -1
  23. package/core/audits/layout-shifts.js +5 -4
  24. package/core/audits/seo/crawlable-anchors.js +27 -0
  25. package/core/audits/seo/link-text.js +130 -83
  26. package/core/audits/third-party-cookies.js +0 -2
  27. package/core/audits/valid-source-maps.js +0 -2
  28. package/core/computed/js-bundles.js +0 -1
  29. package/core/computed/metrics/cumulative-layout-shift.js +1 -1
  30. package/core/computed/metrics/lantern-metric.js +4 -3
  31. package/core/computed/metrics/timing-summary.js +4 -1
  32. package/core/computed/page-dependency-graph.js +3 -3
  33. package/core/computed/trace-engine-result.js +1 -2
  34. package/core/config/config.js +1 -1
  35. package/core/config/default-config.js +4 -4
  36. package/core/config/experimental-config.js +2 -2
  37. package/core/config/filters.js +7 -0
  38. package/core/config/validation.js +4 -0
  39. package/core/gather/base-artifacts.js +3 -0
  40. package/core/gather/driver/environment.d.ts +6 -0
  41. package/core/gather/driver/environment.js +17 -0
  42. package/core/gather/driver/execution-context.d.ts +3 -1
  43. package/core/gather/driver/execution-context.js +3 -1
  44. package/core/gather/driver/navigation.js +1 -3
  45. package/core/gather/driver/wait-for-condition.js +0 -1
  46. package/core/gather/fetcher.js +0 -2
  47. package/core/gather/gatherers/accessibility.js +1 -1
  48. package/core/gather/gatherers/anchor-elements.js +61 -2
  49. package/core/gather/gatherers/cache-contents.js +0 -2
  50. package/core/gather/gatherers/css-usage.js +3 -1
  51. package/core/gather/gatherers/dobetterweb/doctype.js +0 -2
  52. package/core/gather/gatherers/dobetterweb/domstats.js +1 -1
  53. package/core/gather/gatherers/full-page-screenshot.js +1 -1
  54. package/core/gather/gatherers/image-elements.js +1 -1
  55. package/core/gather/gatherers/inspector-issues.js +1 -1
  56. package/core/gather/gatherers/link-elements.js +1 -1
  57. package/core/gather/gatherers/stacks.js +0 -1
  58. package/core/gather/gatherers/stylesheets.js +3 -1
  59. package/core/gather/gatherers/trace-elements.d.ts +3 -4
  60. package/core/gather/gatherers/trace-elements.js +13 -43
  61. package/core/gather/gatherers/viewport-dimensions.js +0 -2
  62. package/core/index.cjs +0 -1
  63. package/core/index.d.cts +5 -0
  64. package/core/lib/asset-saver.d.ts +1 -1
  65. package/core/lib/asset-saver.js +20 -8
  66. package/core/lib/bf-cache-strings.js +4 -1
  67. package/core/lib/deprecations-strings.d.ts +51 -47
  68. package/core/lib/deprecations-strings.js +14 -8
  69. package/core/lib/i18n/i18n.js +0 -2
  70. package/core/lib/lantern-trace-saver.js +1 -1
  71. package/core/lib/lh-error.js +0 -1
  72. package/core/lib/manifest-parser.js +0 -2
  73. package/core/lib/minify-devtoolslog.js +0 -2
  74. package/core/lib/sentry.d.ts +1 -1
  75. package/core/lib/sentry.js +2 -2
  76. package/core/runner.js +11 -8
  77. package/core/scoring.d.ts +186 -15
  78. package/core/scripts/manual-chrome-launcher.js +0 -1
  79. package/dist/report/bundle.esm.js +14 -12
  80. package/dist/report/flow.js +18 -16
  81. package/dist/report/standalone.js +15 -13
  82. package/eslint.config.mjs +242 -0
  83. package/flow-report/src/common.tsx +1 -0
  84. package/flow-report/src/i18n/i18n.tsx +1 -0
  85. package/flow-report/src/util.ts +2 -0
  86. package/package.json +23 -19
  87. package/readme.md +3 -2
  88. package/report/assets/styles.css +11 -9
  89. package/report/assets/templates.html +1 -1
  90. package/report/generator/file-namer.d.ts +5 -0
  91. package/report/generator/file-namer.js +1 -1
  92. package/report/generator/flow-report-assets.js +1 -1
  93. package/report/generator/report-assets.js +1 -1
  94. package/report/generator/report-generator.js +3 -3
  95. package/report/renderer/api.js +1 -0
  96. package/report/renderer/components.js +2 -2
  97. package/report/renderer/details-renderer.d.ts +5 -0
  98. package/report/renderer/details-renderer.js +35 -3
  99. package/report/renderer/dom.d.ts +2 -0
  100. package/report/renderer/dom.js +6 -0
  101. package/report/renderer/i18n-formatter.js +2 -1
  102. package/report/renderer/performance-category-renderer.js +2 -2
  103. package/report/renderer/report-renderer.js +1 -0
  104. package/report/renderer/report-ui-features.d.ts +1 -0
  105. package/report/renderer/report-ui-features.js +16 -0
  106. package/report/renderer/report-utils.js +3 -2
  107. package/report/renderer/text-encoding.js +0 -2
  108. package/report/renderer/topbar-features.js +1 -1
  109. package/report/types/report-renderer.d.ts +5 -0
  110. package/shared/localization/locales/ar-XB.json +57 -69
  111. package/shared/localization/locales/ar.json +57 -69
  112. package/shared/localization/locales/bg.json +57 -69
  113. package/shared/localization/locales/ca.json +57 -69
  114. package/shared/localization/locales/cs.json +57 -69
  115. package/shared/localization/locales/da.json +57 -69
  116. package/shared/localization/locales/de.json +57 -69
  117. package/shared/localization/locales/el.json +57 -69
  118. package/shared/localization/locales/en-GB.json +57 -69
  119. package/shared/localization/locales/en-US.json +73 -61
  120. package/shared/localization/locales/en-XL.json +73 -61
  121. package/shared/localization/locales/es-419.json +57 -69
  122. package/shared/localization/locales/es.json +56 -68
  123. package/shared/localization/locales/fi.json +57 -69
  124. package/shared/localization/locales/fil.json +57 -69
  125. package/shared/localization/locales/fr.json +57 -69
  126. package/shared/localization/locales/he.json +57 -69
  127. package/shared/localization/locales/hi.json +57 -69
  128. package/shared/localization/locales/hr.json +57 -69
  129. package/shared/localization/locales/hu.json +56 -68
  130. package/shared/localization/locales/id.json +57 -69
  131. package/shared/localization/locales/it.json +56 -68
  132. package/shared/localization/locales/ja.json +57 -69
  133. package/shared/localization/locales/ko.json +57 -69
  134. package/shared/localization/locales/lt.json +57 -69
  135. package/shared/localization/locales/lv.json +57 -69
  136. package/shared/localization/locales/nl.json +57 -69
  137. package/shared/localization/locales/no.json +57 -69
  138. package/shared/localization/locales/pl.json +56 -68
  139. package/shared/localization/locales/pt-PT.json +57 -69
  140. package/shared/localization/locales/pt.json +57 -69
  141. package/shared/localization/locales/ro.json +57 -69
  142. package/shared/localization/locales/ru.json +58 -70
  143. package/shared/localization/locales/sk.json +57 -69
  144. package/shared/localization/locales/sl.json +56 -68
  145. package/shared/localization/locales/sr-Latn.json +57 -69
  146. package/shared/localization/locales/sr.json +57 -69
  147. package/shared/localization/locales/sv.json +57 -69
  148. package/shared/localization/locales/ta.json +57 -69
  149. package/shared/localization/locales/te.json +57 -69
  150. package/shared/localization/locales/th.json +56 -68
  151. package/shared/localization/locales/tr.json +57 -69
  152. package/shared/localization/locales/uk.json +57 -69
  153. package/shared/localization/locales/vi.json +57 -69
  154. package/shared/localization/locales/zh-HK.json +57 -69
  155. package/shared/localization/locales/zh-TW.json +56 -68
  156. package/shared/localization/locales/zh.json +57 -69
  157. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
  158. package/types/artifacts.d.ts +8 -1
  159. package/types/internal/test.d.ts +1 -1
  160. package/types/lhr/audit-details.d.ts +13 -3
  161. package/types/lhr/lhr.d.ts +8 -1
  162. package/core/audits/insights/lcp-phases-insight.d.ts +0 -16
@@ -4,11 +4,11 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js';
7
+ import {UIStrings, TOO_MANY_PRECONNECTS_THRESHOLD} from '@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js';
8
8
 
9
9
  import {Audit} from '../audit.js';
10
10
  import * as i18n from '../../lib/i18n/i18n.js';
11
- import {adaptInsightToAuditProduct} from './insight-audit.js';
11
+ import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js';
12
12
 
13
13
  // eslint-disable-next-line max-len
14
14
  const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js', UIStrings);
@@ -24,8 +24,8 @@ class NetworkDependencyTreeInsight extends Audit {
24
24
  failureTitle: str_(UIStrings.title),
25
25
  description: str_(UIStrings.description),
26
26
  guidanceLevel: 1,
27
- requiredArtifacts: ['Trace', 'SourceMaps'],
28
- replacesAudits: ['critical-request-chains'],
27
+ requiredArtifacts: ['Trace', 'SourceMaps', 'TraceElements'],
28
+ replacesAudits: ['critical-request-chains', 'uses-rel-preconnect'],
29
29
  };
30
30
  }
31
31
 
@@ -59,15 +59,92 @@ class NetworkDependencyTreeInsight extends Audit {
59
59
  */
60
60
  static async audit(artifacts, context) {
61
61
  return adaptInsightToAuditProduct(artifacts, context, 'NetworkDependencyTree', (insight) => {
62
- const chains = this.traceEngineNodesToDetailsNodes(insight.rootNodes);
62
+ const list = [];
63
+ let sectionDetails;
63
64
 
64
- return {
65
+ sectionDetails = /** @type {LH.Audit.Details.NetworkTree} */({
65
66
  type: 'network-tree',
66
- chains,
67
+ chains: this.traceEngineNodesToDetailsNodes(insight.rootNodes),
67
68
  longestChain: {
68
69
  duration: Math.round(insight.maxTime / 1000),
69
70
  },
70
- };
71
+ });
72
+ list.push(Audit.makeListDetailSectionItem(sectionDetails));
73
+
74
+ // Preconnected origins table.
75
+ if (insight.preconnectedOrigins.length) {
76
+ /** @type {LH.Audit.Details.Table['headings']} */
77
+ const headings = [
78
+ /* eslint-disable max-len */
79
+ {key: 'origin', valueType: 'text', subItemsHeading: {key: 'warning'}, label: str_(UIStrings.columnOrigin)},
80
+ {key: 'source', valueType: 'node', label: str_(UIStrings.columnSource)},
81
+ /* eslint-enable max-len */
82
+ ];
83
+
84
+ /** @type {LH.Audit.Details.Table['items']} */
85
+ const items = insight.preconnectedOrigins.map(c => {
86
+ const warnings = [];
87
+ if (c.unused) {
88
+ warnings.push(str_(UIStrings.unusedWarning));
89
+ }
90
+ if (c.crossorigin) {
91
+ warnings.push(str_(UIStrings.crossoriginWarning));
92
+ }
93
+ /** @type {LH.Audit.Details.TableSubItems} */
94
+ const subItems = {
95
+ type: 'subitems',
96
+ items: warnings.map(warning => ({warning})),
97
+ };
98
+ return {
99
+ origin: c.url,
100
+ source: c.source === 'DOM' ?
101
+ makeNodeItemForNodeId(artifacts.TraceElements, c.node_id) :
102
+ {type: 'text', value: c.headerText},
103
+ subItems,
104
+ };
105
+ });
106
+
107
+ sectionDetails = Audit.makeTableDetails(headings, items);
108
+ } else {
109
+ sectionDetails = /** @type {LH.Audit.Details.TextValue} */ (
110
+ {type: 'text', value: str_(UIStrings.noPreconnectOrigins)});
111
+ }
112
+
113
+ list.push(Audit.makeListDetailSectionItem(
114
+ sectionDetails,
115
+ str_(UIStrings.preconnectOriginsTableTitle),
116
+ str_(UIStrings.preconnectOriginsTableDescription)));
117
+
118
+ // Estimated savings table.
119
+ if (insight.preconnectCandidates.length) {
120
+ /** @type {LH.Audit.Details.Table['headings']} */
121
+ const headings = [
122
+ {key: 'origin', valueType: 'text', label: str_(UIStrings.columnOrigin)},
123
+ {key: 'wastedMs', valueType: 'ms', label: str_(UIStrings.columnWastedMs)},
124
+ ];
125
+
126
+ /** @type {LH.Audit.Details.Table['items']} */
127
+ const items = insight.preconnectCandidates.map(c => {
128
+ return {origin: c.origin, wastedMs: c.wastedMs};
129
+ });
130
+
131
+ sectionDetails = Audit.makeTableDetails(headings, items);
132
+ } else {
133
+ sectionDetails = /** @type {LH.Audit.Details.TextValue} */ (
134
+ {type: 'text', value: str_(UIStrings.noPreconnectCandidates)});
135
+ }
136
+
137
+ list.push(Audit.makeListDetailSectionItem(
138
+ sectionDetails,
139
+ str_(UIStrings.estSavingTableTitle),
140
+ str_(UIStrings.estSavingTableDescription)));
141
+
142
+ const warnings = [];
143
+ if (insight.preconnectedOrigins.length > TOO_MANY_PRECONNECTS_THRESHOLD) {
144
+ warnings.push(str_(UIStrings.tooManyPreconnectLinksWarning));
145
+ }
146
+
147
+ return {details: Audit.makeListDetails(list), warnings};
71
148
  });
72
149
  }
73
150
  }
@@ -41,7 +41,7 @@ class RenderBlockingInsight extends Audit {
41
41
  const headings = [
42
42
  {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
43
43
  {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize)},
44
- {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
44
+ {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnDuration)},
45
45
  ];
46
46
  /** @type {LH.Audit.Details.Table['items']} */
47
47
  const items = insight.renderBlockingRequests.map(request => ({
@@ -62,7 +62,7 @@ class LayoutShifts extends Audit {
62
62
  const SourceMaps = artifacts.SourceMaps;
63
63
  const traceEngineResult =
64
64
  await TraceEngineResult.request({trace, settings, SourceMaps}, context);
65
- const clusters = traceEngineResult.data.LayoutShifts.clusters ?? [];
65
+ const clusters = traceEngineResult.parsedTrace.LayoutShifts.clusters ?? [];
66
66
  const {cumulativeLayoutShift: clsSavings, impactByNodeId} =
67
67
  await CumulativeLayoutShiftComputed.request(trace, context);
68
68
  const traceElements = artifacts.TraceElements
@@ -89,7 +89,7 @@ class LayoutShifts extends Audit {
89
89
  .slice(0, MAX_LAYOUT_SHIFTS);
90
90
  for (const event of topLayoutShiftEvents) {
91
91
  const biggestImpactNodeId = TraceElements.getBiggestImpactNodeForShiftEvent(
92
- event.args.data.impacted_nodes || [], impactByNodeId, event);
92
+ event.args.data.impacted_nodes || [], impactByNodeId);
93
93
  const biggestImpactElement = traceElements.find(t => t.nodeId === biggestImpactNodeId);
94
94
 
95
95
  // Turn root causes into sub-items.
@@ -105,15 +105,16 @@ class LayoutShifts extends Audit {
105
105
  cause: str_(UIStrings.rootCauseUnsizedMedia),
106
106
  });
107
107
  }
108
- for (const request of rootCauses.fontRequests) {
108
+ for (const request of rootCauses.webFonts) {
109
109
  const url = request.args.data.url;
110
110
  subItems.push({
111
111
  extra: {type: 'url', value: url},
112
112
  cause: str_(UIStrings.rootCauseFontChanges),
113
113
  });
114
114
  }
115
- if (rootCauses.iframeIds.length) {
115
+ for (const iframe of rootCauses.iframes) {
116
116
  subItems.push({
117
+ extra: iframe.url ? {type: 'url', value: iframe.url} : undefined,
117
118
  cause: str_(UIStrings.rootCauseInjectedIframe),
118
119
  });
119
120
  }
@@ -18,6 +18,16 @@ const UIStrings = {
18
18
  columnFailingLink: 'Uncrawlable Link',
19
19
  };
20
20
 
21
+ const hrefAssociatedAttributes = [
22
+ 'target',
23
+ 'download',
24
+ 'ping',
25
+ 'rel',
26
+ 'hreflang',
27
+ 'type',
28
+ 'referrerpolicy',
29
+ ];
30
+
21
31
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
22
32
 
23
33
  class CrawlableAnchors extends Audit {
@@ -45,10 +55,14 @@ class CrawlableAnchors extends Audit {
45
55
  role = '',
46
56
  id,
47
57
  href,
58
+ attributeNames = [],
59
+ listeners = [],
60
+ ancestorListeners = [],
48
61
  }) => {
49
62
  rawHref = rawHref.replace( /\s/g, '');
50
63
  name = name.trim();
51
64
  role = role.trim();
65
+ const hasListener = Boolean(listeners.length || ancestorListeners.length);
52
66
 
53
67
  if (role.length > 0) return;
54
68
  // Ignore mailto links even if they use one of the failing patterns. See https://github.com/GoogleChrome/lighthouse/issues/11443#issuecomment-694898412
@@ -62,6 +76,19 @@ class CrawlableAnchors extends Audit {
62
76
  if (rawHref.startsWith('file:')) return true;
63
77
  if (name.length > 0) return;
64
78
 
79
+ // If the a element has no href attribute, then the element represents a
80
+ // placeholder for where a link might otherwise have been placed, if it had
81
+ // been relevant, consisting of just the element's contents. The target,
82
+ // download, ping, rel, hreflang, type, and referrerpolicy attributes must be
83
+ // omitted if the href attribute is not present.
84
+ // See https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-a-element
85
+ if (
86
+ !attributeNames.includes('href') &&
87
+ hrefAssociatedAttributes.every(attribute => !attributeNames.includes(attribute))
88
+ ) {
89
+ return hasListener;
90
+ }
91
+
65
92
  if (href === '') return true;
66
93
  if (javaScriptVoidRegExp.test(rawHref)) return true;
67
94
 
@@ -8,95 +8,123 @@ import {Audit} from '../audit.js';
8
8
  import UrlUtils from '../../lib/url-utils.js';
9
9
  import * as i18n from '../../lib/i18n/i18n.js';
10
10
 
11
- const BLOCKLIST = new Set([
11
+ /** @type {Record<string, Set<string>>} */
12
+ const nonDescriptiveLinkTexts = {
12
13
  // English
13
- 'click here',
14
- 'click this',
15
- 'go',
16
- 'here',
17
- 'information',
18
- 'learn more',
19
- 'more',
20
- 'more info',
21
- 'more information',
22
- 'right here',
23
- 'read more',
24
- 'see more',
25
- 'start',
26
- 'this',
14
+ 'en': new Set([
15
+ 'click here',
16
+ 'click this',
17
+ 'go',
18
+ 'here',
19
+ 'information',
20
+ 'learn more',
21
+ 'more',
22
+ 'more info',
23
+ 'more information',
24
+ 'right here',
25
+ 'read more',
26
+ 'see more',
27
+ 'start',
28
+ 'this',
29
+ ]),
27
30
  // Japanese
28
- 'ここをクリック',
29
- 'こちらをクリック',
30
- 'リンク',
31
- '続きを読む',
32
- '続く',
33
- '全文表示',
31
+ 'ja': new Set([
32
+ 'ここをクリック',
33
+ 'こちらをクリック',
34
+ 'リンク',
35
+ '続きを読む',
36
+ '続く',
37
+ '全文表示',
38
+ ]),
34
39
  // Spanish
35
- 'click aquí',
36
- 'click aqui',
37
- 'clicka aquí',
38
- 'clicka aqui',
39
- 'pincha aquí',
40
- 'pincha aqui',
41
- 'aquí',
42
- 'aqui',
43
- 'más',
44
- 'mas',
45
- 'más información',
46
- 'más informacion',
47
- 'mas información',
48
- 'mas informacion',
49
- 'este',
50
- 'enlace',
51
- 'este enlace',
52
- 'empezar',
40
+ 'es': new Set([
41
+ 'click aquí',
42
+ 'click aqui',
43
+ 'clicka aquí',
44
+ 'clicka aqui',
45
+ 'pincha aquí',
46
+ 'pincha aqui',
47
+ 'aquí',
48
+ 'aqui',
49
+ 'más',
50
+ 'mas',
51
+ 'más información',
52
+ 'más informacion',
53
+ 'mas información',
54
+ 'mas informacion',
55
+ 'este',
56
+ 'enlace',
57
+ 'este enlace',
58
+ 'empezar',
59
+ ]),
53
60
  // Portuguese
54
- 'clique aqui',
55
- 'ir',
56
- 'mais informação',
57
- 'mais informações',
58
- 'mais',
59
- 'veja mais',
61
+ 'pt': new Set([
62
+ 'clique aqui',
63
+ 'ir',
64
+ 'mais informação',
65
+ 'mais informações',
66
+ 'mais',
67
+ 'veja mais',
68
+ ]),
60
69
  // Korean
61
- '여기',
62
- '여기를 클릭',
63
- '클릭',
64
- '링크',
65
- '자세히',
66
- '자세히 보기',
67
- '계속',
68
- '이동',
69
- '전체 보기',
70
+ 'ko': new Set([
71
+ '여기',
72
+ '여기를 클릭',
73
+ '클릭',
74
+ '링크',
75
+ '자세히',
76
+ '자세히 보기',
77
+ '계속',
78
+ '이동',
79
+ '전체 보기',
80
+ ]),
70
81
  // Swedish
71
- 'här',
72
- 'klicka här',
73
- 'läs mer',
74
- 'mer',
75
- 'mer info',
76
- 'mer information',
82
+ 'sv': new Set([
83
+ 'här',
84
+ 'klicka här',
85
+ 'läs mer',
86
+ 'mer',
87
+ 'mer info',
88
+ 'mer information',
89
+ ]),
90
+ // German
91
+ 'de': new Set([
92
+ 'klicke hier',
93
+ 'hier klicken',
94
+ 'hier',
95
+ 'mehr',
96
+ 'siehe',
97
+ 'dies',
98
+ 'das',
99
+ 'weiterlesen',
100
+ ]),
77
101
  // Tamil
78
- 'அடுத்த பக்கம்',
79
- 'மறுபக்கம்',
80
- 'முந்தைய பக்கம்',
81
- 'முன்பக்கம்',
82
- 'மேலும் அறிக',
83
- 'மேலும் தகவலுக்கு',
84
- 'மேலும் தரவுகளுக்கு',
85
- 'தயவுசெய்து இங்கே அழுத்தவும்',
86
- 'இங்கே கிளிக் செய்யவும்',
102
+ 'ta': new Set([
103
+ 'அடுத்த பக்கம்',
104
+ 'மறுபக்கம்',
105
+ 'முந்தைய பக்கம்',
106
+ 'முன்பக்கம்',
107
+ 'மேலும் அறிக',
108
+ 'மேலும் தகவலுக்கு',
109
+ 'மேலும் தரவுகளுக்கு',
110
+ 'தயவுசெய்து இங்கே அழுத்தவும்',
111
+ 'இங்கே கிளிக் செய்யவும்',
112
+ ]),
87
113
  // Persian
88
- 'اطلاعات بیشتر',
89
- 'اطلاعات',
90
- 'این',
91
- 'اینجا بزنید',
92
- 'اینجا کلیک کنید',
93
- 'اینجا',
94
- 'برو',
95
- 'بیشتر بخوانید',
96
- 'بیشتر بدانید',
97
- 'بیشتر',
98
- 'شروع',
99
- ]);
114
+ 'fa': new Set([
115
+ 'اطلاعات بیشتر',
116
+ 'اطلاعات',
117
+ 'این',
118
+ 'اینجا بزنید',
119
+ 'اینجا کلیک کنید',
120
+ 'اینجا',
121
+ 'برو',
122
+ 'بیشتر بخوانید',
123
+ 'بیشتر بدانید',
124
+ 'بیشتر',
125
+ 'شروع',
126
+ ]),
127
+ };
100
128
 
101
129
  const UIStrings = {
102
130
  /** Title of a Lighthouse audit that tests if each link on a page contains a sufficient description of what a user will find when they click it. Generic, non-descriptive text like "click here" doesn't give an indication of what the link leads to. This descriptive title is shown when all links on the page have sufficient textual descriptions. */
@@ -135,8 +163,9 @@ class LinkText extends Audit {
135
163
  */
136
164
  static audit(artifacts) {
137
165
  const failingLinks = artifacts.AnchorElements
138
- .filter(link => link.href && !link.rel.includes('nofollow'))
139
166
  .filter(link => {
167
+ if (!link.href || link.rel.includes('nofollow')) return false;
168
+
140
169
  const href = link.href.toLowerCase();
141
170
  if (
142
171
  href.startsWith('javascript:') ||
@@ -148,12 +177,30 @@ class LinkText extends Audit {
148
177
  return false;
149
178
  }
150
179
 
151
- return BLOCKLIST.has(link.text.trim().toLowerCase());
180
+ const searchTerm = link.text.trim().toLowerCase();
181
+ if (searchTerm) {
182
+ // Use language if detected, otherwise look at everything.
183
+ if (link.textLang) {
184
+ const lang = link.textLang.split('-')[0];
185
+ if (nonDescriptiveLinkTexts[lang] && nonDescriptiveLinkTexts[lang].has(searchTerm)) {
186
+ return true;
187
+ }
188
+ } else {
189
+ for (const texts of Object.values(nonDescriptiveLinkTexts)) {
190
+ if (texts.has(searchTerm)) {
191
+ return true;
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ return false;
152
198
  })
153
199
  .map(link => {
154
200
  return {
155
201
  href: link.href,
156
202
  text: link.text.trim(),
203
+ textLang: link.textLang,
157
204
  };
158
205
  });
159
206
 
@@ -11,7 +11,6 @@
11
11
  import {Audit} from './audit.js';
12
12
  import * as i18n from '../lib/i18n/i18n.js';
13
13
 
14
- /* eslint-disable max-len */
15
14
  const UIStrings = {
16
15
  /** Title of a Lighthouse audit that provides detail on the use of third party cookies. This descriptive title is shown to users when the page does not use third party cookies. */
17
16
  title: 'Avoids third-party cookies',
@@ -25,7 +24,6 @@ const UIStrings = {
25
24
  other {# cookies found}
26
25
  }`,
27
26
  };
28
- /* eslint-enable max-len */
29
27
 
30
28
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
31
29
 
@@ -121,7 +121,6 @@ class ValidSourceMaps extends Audit {
121
121
 
122
122
  /** @type {LH.Audit.Details.TableColumnHeading[]} */
123
123
  const headings = [
124
- /* eslint-disable max-len */
125
124
  {
126
125
  key: 'scriptUrl',
127
126
  valueType: 'url',
@@ -129,7 +128,6 @@ class ValidSourceMaps extends Audit {
129
128
  label: str_(i18n.UIStrings.columnURL),
130
129
  },
131
130
  {key: 'sourceMapUrl', valueType: 'url', label: str_(UIStrings.columnMapURL)},
132
- /* eslint-enable max-len */
133
131
  ];
134
132
 
135
133
  results.sort((a, b) => {
@@ -55,7 +55,6 @@ function computeGeneratedFileSizes(map, contentLength, content) {
55
55
  let mappingLength = 0;
56
56
  if (lastColNum !== undefined) {
57
57
  if (lastColNum > line.length) {
58
- // eslint-disable-next-line max-len
59
58
  const errorMessage =
60
59
  `${map.url()} mapping for last column out of bounds: ${lineNum + 1}:${lastColNum}`;
61
60
  log.error('JSBundles', errorMessage);
@@ -159,7 +159,6 @@ class CumulativeLayoutShift {
159
159
  LayoutShifts: TraceEngine.TraceHandlers.LayoutShifts,
160
160
  Screenshots: TraceEngine.TraceHandlers.Screenshots,
161
161
  });
162
- // eslint-disable-next-line max-len
163
162
  await processor.parse(/** @type {import('@paulirish/trace_engine').Types.Events.Event[]} */ (
164
163
  events
165
164
  ), {});
@@ -212,6 +211,7 @@ class CumulativeLayoutShift {
212
211
  throw new Error(`new trace engine differed. expected: ${expected}, got: ${got}`);
213
212
  }
214
213
  } catch (err) {
214
+ // eslint-disable-next-line no-console
215
215
  console.error(err);
216
216
  newEngineResultDiffered = true;
217
217
 
@@ -38,14 +38,15 @@ async function getComputationDataParamsFromTrace(data, context) {
38
38
 
39
39
  const graph = await PageDependencyGraph.request({...data, fromTrace: true}, context);
40
40
  const traceEngineResult = await TraceEngineResult.request(data, context);
41
- const frameId = traceEngineResult.data.Meta.mainFrameId;
42
- const navigationId = traceEngineResult.data.Meta.mainFrameNavigations[0].args.data?.navigationId;
41
+ const frameId = traceEngineResult.parsedTrace.Meta.mainFrameId;
42
+ const navigationId =
43
+ traceEngineResult.parsedTrace.Meta.mainFrameNavigations[0].args.data?.navigationId;
43
44
  if (!navigationId) {
44
45
  throw new Error(`Lantern metrics could not be calculated due to missing navigation id`);
45
46
  }
46
47
 
47
48
  const processedNavigation = Lantern.TraceEngineComputationData.createProcessedNavigation(
48
- traceEngineResult.data, frameId, navigationId);
49
+ traceEngineResult.parsedTrace, frameId, navigationId);
49
50
  const simulator = data.simulator || (await LoadSimulator.request(data, context));
50
51
 
51
52
  return {simulator, graph, processedNavigation};
@@ -21,6 +21,7 @@ import {TotalBlockingTime} from './total-blocking-time.js';
21
21
  import {makeComputedArtifact} from '../computed-artifact.js';
22
22
  import {TimeToFirstByte} from './time-to-first-byte.js';
23
23
  import {LCPBreakdown} from './lcp-breakdown.js';
24
+ import {isUnderTest} from '../../lib/lh-env.js';
24
25
 
25
26
  class TimingSummary {
26
27
  /**
@@ -46,7 +47,9 @@ class TimingSummary {
46
47
  */
47
48
  const requestOrUndefined = (Artifact, artifact) => {
48
49
  return Artifact.request(artifact, context).catch(err => {
49
- log.error('lh:computed:TimingSummary', err);
50
+ if (isUnderTest) {
51
+ log.error('lh:computed:TimingSummary', err);
52
+ }
50
53
  return undefined;
51
54
  });
52
55
  };
@@ -27,11 +27,11 @@ class PageDependencyGraph {
27
27
  if (data.fromTrace) {
28
28
  const traceEngineResult =
29
29
  await TraceEngineResult.request({trace, settings, SourceMaps}, context);
30
- const traceEngineData = traceEngineResult.data;
30
+ const parsedTrace = traceEngineResult.parsedTrace;
31
31
  const requests =
32
- Lantern.TraceEngineComputationData.createNetworkRequests(trace, traceEngineData);
32
+ Lantern.TraceEngineComputationData.createNetworkRequests(trace, parsedTrace);
33
33
  const graph =
34
- Lantern.TraceEngineComputationData.createGraph(requests, trace, traceEngineData, URL);
34
+ Lantern.TraceEngineComputationData.createGraph(requests, trace, parsedTrace, URL);
35
35
  // @ts-expect-error for now, ignore that this is a SyntheticNetworkEvent instead of LH's NetworkEvent.
36
36
  return graph;
37
37
  }
@@ -34,7 +34,6 @@ class TraceEngineResult {
34
34
  lanternSettings.precomputedLanternData = settings.precomputedLanternData;
35
35
  }
36
36
 
37
- // eslint-disable-next-line max-len
38
37
  await processor.parse(/** @type {import('@paulirish/trace_engine').Types.Events.Event[]} */ (
39
38
  traceEvents
40
39
  ), {
@@ -63,7 +62,7 @@ class TraceEngineResult {
63
62
  if (!processor.parsedTrace) throw new Error('No data');
64
63
  if (!processor.insights) throw new Error('No insights');
65
64
  this.localizeInsights(processor.insights);
66
- return {data: processor.parsedTrace, insights: processor.insights};
65
+ return {parsedTrace: processor.parsedTrace, insights: processor.insights};
67
66
  }
68
67
 
69
68
  /**
@@ -10,7 +10,7 @@ import log from 'lighthouse-logger';
10
10
 
11
11
  import {Runner} from '../runner.js';
12
12
  import defaultConfig from './default-config.js';
13
- import {nonSimulatedSettingsOverrides} from './constants.js'; // eslint-disable-line max-len
13
+ import {nonSimulatedSettingsOverrides} from './constants.js';
14
14
  import {
15
15
  throwInvalidDependencyOrder,
16
16
  isValidArtifactDependency,
@@ -319,9 +319,9 @@ const defaultConfig = {
319
319
  'insights/font-display-insight',
320
320
  'insights/forced-reflow-insight',
321
321
  'insights/image-delivery-insight',
322
- 'insights/interaction-to-next-paint-insight',
322
+ 'insights/inp-breakdown-insight',
323
+ 'insights/lcp-breakdown-insight',
323
324
  'insights/lcp-discovery-insight',
324
- 'insights/lcp-phases-insight',
325
325
  'insights/legacy-javascript-insight',
326
326
  'insights/modern-http-insight',
327
327
  'insights/network-dependency-tree-insight',
@@ -421,9 +421,9 @@ const defaultConfig = {
421
421
  {id: 'font-display-insight', weight: 0, group: 'hidden'},
422
422
  {id: 'forced-reflow-insight', weight: 0, group: 'hidden'},
423
423
  {id: 'image-delivery-insight', weight: 0, group: 'hidden'},
424
- {id: 'interaction-to-next-paint-insight', weight: 0, group: 'hidden'},
424
+ {id: 'inp-breakdown-insight', weight: 0, group: 'hidden'},
425
+ {id: 'lcp-breakdown-insight', weight: 0, group: 'hidden'},
425
426
  {id: 'lcp-discovery-insight', weight: 0, group: 'hidden'},
426
- {id: 'lcp-phases-insight', weight: 0, group: 'hidden'},
427
427
  {id: 'legacy-javascript-insight', weight: 0, group: 'hidden'},
428
428
  {id: 'modern-http-insight', weight: 0, group: 'hidden'},
429
429
  {id: 'network-dependency-tree-insight', weight: 0, group: 'hidden'},
@@ -35,9 +35,9 @@ const config = {
35
35
  {id: 'font-display-insight', weight: 0, group: 'insights'},
36
36
  {id: 'forced-reflow-insight', weight: 0, group: 'insights'},
37
37
  {id: 'image-delivery-insight', weight: 0, group: 'insights'},
38
- {id: 'interaction-to-next-paint-insight', weight: 0, group: 'insights'},
38
+ {id: 'inp-breakdown-insight', weight: 0, group: 'insights'},
39
+ {id: 'lcp-breakdown-insight', weight: 0, group: 'insights'},
39
40
  {id: 'lcp-discovery-insight', weight: 0, group: 'insights'},
40
- {id: 'lcp-phases-insight', weight: 0, group: 'insights'},
41
41
  {id: 'legacy-javascript-insight', weight: 0, group: 'insights'},
42
42
  {id: 'modern-http-insight', weight: 0, group: 'insights'},
43
43
  {id: 'network-dependency-tree-insight', weight: 0, group: 'insights'},