lighthouse 12.6.1 → 12.7.0-dev.20250628

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
@@ -13,6 +13,7 @@ const baseArtifactKeySource = {
13
13
  fetchTime: '',
14
14
  LighthouseRunWarnings: '',
15
15
  BenchmarkIndex: '',
16
+ HostDPR: '',
16
17
  settings: '',
17
18
  Timing: '',
18
19
  URL: '',
@@ -263,6 +264,12 @@ function filterConfigByGatherMode(resolvedConfig, mode) {
263
264
  */
264
265
  function filterConfigByExplicitFilters(resolvedConfig, filters) {
265
266
  const {onlyAudits, onlyCategories, skipAudits} = filters;
267
+ if (onlyAudits && !onlyAudits.length) {
268
+ throw new Error(`onlyAudits cannot be an empty array.`);
269
+ }
270
+ if (onlyCategories && !onlyCategories.length) {
271
+ throw new Error(`onlyCategories cannot be an empty array.`);
272
+ }
266
273
 
267
274
  warnOnUnknownOnlyCategories(resolvedConfig.categories, onlyCategories);
268
275
 
@@ -36,6 +36,10 @@ function isValidArtifactDependency(dependent, dependency) {
36
36
  * @param {string} pluginName
37
37
  */
38
38
  function assertValidPluginName(config, pluginName) {
39
+ const parts = pluginName.split('/');
40
+ if (parts.length === 2) {
41
+ pluginName = parts[1];
42
+ }
39
43
  if (!pluginName.startsWith('lighthouse-plugin-')) {
40
44
  throw new Error(`plugin name '${pluginName}' does not start with 'lighthouse-plugin-'`);
41
45
  }
@@ -9,6 +9,7 @@ import {isEqual} from 'lodash-es';
9
9
 
10
10
  import {
11
11
  getBrowserVersion, getBenchmarkIndex, getEnvironmentWarnings,
12
+ getDevicePixelRatio,
12
13
  } from './driver/environment.js';
13
14
 
14
15
  /**
@@ -20,6 +21,7 @@ import {
20
21
  async function getBaseArtifacts(resolvedConfig, driver, context) {
21
22
  const BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
22
23
  const {userAgent, product} = await getBrowserVersion(driver.defaultSession);
24
+ const HostDPR = await getDevicePixelRatio(driver.executionContext);
23
25
 
24
26
  return {
25
27
  // Meta artifacts.
@@ -29,6 +31,7 @@ async function getBaseArtifacts(resolvedConfig, driver, context) {
29
31
  settings: resolvedConfig.settings,
30
32
  // Environment artifacts that can always be computed.
31
33
  BenchmarkIndex,
34
+ HostDPR,
32
35
  HostUserAgent: userAgent,
33
36
  HostFormFactor: userAgent.includes('Android') || userAgent.includes('Mobile') ?
34
37
  'mobile' : 'desktop',
@@ -14,6 +14,12 @@ export function getBrowserVersion(session: LH.Gatherer.ProtocolSession): Promise
14
14
  * @return {Promise<number>}
15
15
  */
16
16
  export function getBenchmarkIndex(executionContext: LH.Gatherer.Driver["executionContext"]): Promise<number>;
17
+ /**
18
+ * Computes the observed DPR.
19
+ * @param {LH.Gatherer.Driver['executionContext']} executionContext
20
+ * @return {Promise<number>}
21
+ */
22
+ export function getDevicePixelRatio(executionContext: LH.Gatherer.Driver["executionContext"]): Promise<number>;
17
23
  /**
18
24
  * Returns a warning if the host device appeared to be underpowered according to BenchmarkIndex.
19
25
  *
@@ -58,6 +58,22 @@ async function getBenchmarkIndex(executionContext) {
58
58
  return indexVal;
59
59
  }
60
60
 
61
+ /**
62
+ * Computes the observed DPR.
63
+ * @param {LH.Gatherer.Driver['executionContext']} executionContext
64
+ * @return {Promise<number>}
65
+ */
66
+ async function getDevicePixelRatio(executionContext) {
67
+ const status = {msg: 'Host device pixel ratio', id: 'lh:gather:getDevicePixelRatio'};
68
+ log.time(status);
69
+ // eslint-disable-next-line no-undef
70
+ const indexVal = await executionContext.evaluate(() => devicePixelRatio, {
71
+ args: [],
72
+ });
73
+ log.timeEnd(status);
74
+ return indexVal;
75
+ }
76
+
61
77
  /**
62
78
  * Returns a warning if the host device appeared to be underpowered according to BenchmarkIndex.
63
79
  *
@@ -100,6 +116,7 @@ export {
100
116
  UIStrings,
101
117
  getBrowserVersion,
102
118
  getBenchmarkIndex,
119
+ getDevicePixelRatio,
103
120
  getSlowHostCpuWarning,
104
121
  getEnvironmentWarnings,
105
122
  };
@@ -58,11 +58,13 @@ export class ExecutionContext {
58
58
  */
59
59
  _evaluateInContext(expression: string, contextId: number | undefined, timeout: number): Promise<any>;
60
60
  /**
61
- * Note: Prefer `evaluate` instead.
62
61
  * Evaluate an expression in the context of the current page. If useIsolation is true, the expression
63
62
  * will be evaluated in a content script that has access to the page's DOM but whose JavaScript state
64
63
  * is completely separate.
65
64
  * Returns a promise that resolves on the expression's value.
65
+ *
66
+ * @deprecated Use `evaluate` instead! It has a better API, and unlike `evaluateAsync` doesn't sometimes
67
+ * execute invalid code.
66
68
  * @param {string} expression
67
69
  * @param {{useIsolation?: boolean}=} options
68
70
  * @return {Promise<*>}
@@ -151,11 +151,13 @@ class ExecutionContext {
151
151
  }
152
152
 
153
153
  /**
154
- * Note: Prefer `evaluate` instead.
155
154
  * Evaluate an expression in the context of the current page. If useIsolation is true, the expression
156
155
  * will be evaluated in a content script that has access to the page's DOM but whose JavaScript state
157
156
  * is completely separate.
158
157
  * Returns a promise that resolves on the expression's value.
158
+ *
159
+ * @deprecated Use `evaluate` instead! It has a better API, and unlike `evaluateAsync` doesn't sometimes
160
+ * execute invalid code.
159
161
  * @param {string} expression
160
162
  * @param {{useIsolation?: boolean}=} options
161
163
  * @return {Promise<*>}
@@ -6,7 +6,7 @@
6
6
 
7
7
  import log from 'lighthouse-logger';
8
8
 
9
- import {waitForFullyLoaded, waitForFrameNavigated, waitForUserToContinue} from './wait-for-condition.js'; // eslint-disable-line max-len
9
+ import {waitForFullyLoaded, waitForFrameNavigated, waitForUserToContinue} from './wait-for-condition.js';
10
10
  import * as constants from '../../config/constants.js';
11
11
  import * as i18n from '../../lib/i18n/i18n.js';
12
12
  import UrlUtils from '../../lib/url-utils.js';
@@ -43,7 +43,6 @@ const DEFAULT_CPU_QUIET_THRESHOLD = 0;
43
43
 
44
44
  /** @param {NavigationOptions} options */
45
45
  function resolveWaitForFullyLoadedOptions(options) {
46
- /* eslint-disable max-len */
47
46
  let {pauseAfterFcpMs, pauseAfterLoadMs, networkQuietThresholdMs, cpuQuietThresholdMs} = options;
48
47
  let maxWaitMs = options.maxWaitForLoad;
49
48
  let maxFCPMs = options.maxWaitForFcp;
@@ -56,7 +55,6 @@ function resolveWaitForFullyLoadedOptions(options) {
56
55
  if (typeof cpuQuietThresholdMs !== 'number') cpuQuietThresholdMs = DEFAULT_CPU_QUIET_THRESHOLD;
57
56
  if (typeof maxWaitMs !== 'number') maxWaitMs = constants.defaultSettings.maxWaitForLoad;
58
57
  if (typeof maxFCPMs !== 'number') maxFCPMs = constants.defaultSettings.maxWaitForFcp;
59
- /* eslint-enable max-len */
60
58
 
61
59
  if (!options.waitUntil.includes('fcp')) maxFCPMs = undefined;
62
60
 
@@ -138,7 +138,6 @@ function waitForNetworkIdle(session, networkMonitor, networkQuietOptions) {
138
138
  /** @type {Promise<void>} */
139
139
  const promise = new Promise((resolve, reject) => {
140
140
  const onIdle = () => {
141
- // eslint-disable-next-line no-use-before-define
142
141
  networkMonitor.once(busyEvent, onBusy);
143
142
  idleTimeout = setTimeout(() => {
144
143
  cancel();
@@ -10,8 +10,6 @@ import * as LH from '../../types/lh.js';
10
10
  * ignoring normal browser constraints such as CORS.
11
11
  */
12
12
 
13
- /* global fetch */
14
-
15
13
  /** @typedef {{content: string|null, status: number|null}} FetchResponse */
16
14
 
17
15
  class Fetcher {
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- /* global window, document, getNodeDetails */
7
+ /* global getNodeDetails */
8
8
 
9
9
  import BaseGatherer from '../base-gatherer.js';
10
10
  import {axeSource} from '../../lib/axe.js';
@@ -10,8 +10,6 @@ import BaseGatherer from '../base-gatherer.js';
10
10
  import {pageFunctions} from '../../lib/page-functions.js';
11
11
  import {resolveDevtoolsNodePathToObjectId} from '../driver/dom.js';
12
12
 
13
- /* eslint-env browser, node */
14
-
15
13
  /**
16
14
  * Function that is stringified and run in the page to collect anchor elements.
17
15
  * Additional complexity is introduced because anchors can be HTML or SVG elements.
@@ -38,10 +36,49 @@ function collectAnchorElements() {
38
36
  return onclick.slice(0, 1024);
39
37
  }
40
38
 
39
+ /**
40
+ * @param {HTMLElement|SVGElement} node
41
+ * @return {string|null}
42
+ */
43
+ function getLangOfInnerText(node) {
44
+ let curNodeLang = null;
45
+
46
+ // If we find multiple languages within this element, return null.
47
+ for (const child of node.querySelectorAll('*')) {
48
+ if (!child.textContent) continue;
49
+
50
+ const childLang = child.closest('[lang]')?.getAttribute('lang');
51
+ if (!childLang) continue;
52
+
53
+ if (!curNodeLang) {
54
+ curNodeLang = childLang;
55
+ continue;
56
+ }
57
+
58
+ if (curNodeLang.split('-')[0] !== childLang.split('-')[0]) {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ return curNodeLang ?? node.closest('[lang]')?.getAttribute('lang') ?? null;
64
+ }
65
+
41
66
  /** @type {Array<HTMLAnchorElement|SVGAElement>} */
42
67
  // @ts-expect-error - put into scope via stringification
43
68
  const anchorElements = getElementsInDocument('a'); // eslint-disable-line no-undef
44
69
 
70
+ // Check, if document has only one lang attribute in opening html or in body tag. If so,
71
+ // there is no need to run the `getLangOfInnerText()` function with multiple
72
+ // possible DOM traversals
73
+ /** @type {Array<HTMLElement|SVGElement>} */
74
+ // @ts-expect-error - put into scope via stringification
75
+ const langElements = getElementsInDocument('[lang]'); // eslint-disable-line no-undef
76
+ const documentHasSingleLang = langElements.length === 1 &&
77
+ (langElements[0].nodeName === 'BODY' || langElements[0].nodeName === 'HTML');
78
+ const singleLang = documentHasSingleLang ? langElements[0].getAttribute('lang') : null;
79
+
80
+ // TODO: consider Content-Language.
81
+
45
82
  return anchorElements.map(node => {
46
83
  if (node instanceof HTMLAnchorElement) {
47
84
  return {
@@ -51,9 +88,11 @@ function collectAnchorElements() {
51
88
  role: node.getAttribute('role') || '',
52
89
  name: node.name,
53
90
  text: node.innerText, // we don't want to return hidden text, so use innerText
91
+ textLang: singleLang ?? getLangOfInnerText(node) ?? undefined,
54
92
  rel: node.rel,
55
93
  target: node.target,
56
94
  id: node.getAttribute('id') || '',
95
+ attributeNames: node.getAttributeNames(),
57
96
  // @ts-expect-error - getNodeDetails put into scope via stringification
58
97
  node: getNodeDetails(node),
59
98
  };
@@ -65,9 +104,11 @@ function collectAnchorElements() {
65
104
  onclick: getTruncatedOnclick(node),
66
105
  role: node.getAttribute('role') || '',
67
106
  text: node.textContent || '',
107
+ textLang: singleLang ?? getLangOfInnerText(node) ?? undefined,
68
108
  rel: '',
69
109
  target: node.target.baseVal || '',
70
110
  id: node.getAttribute('id') || '',
111
+ attributeNames: node.getAttributeNames(),
71
112
  // @ts-expect-error - getNodeDetails put into scope via stringification
72
113
  node: getNodeDetails(node),
73
114
  };
@@ -119,9 +160,27 @@ class AnchorElements extends BaseGatherer {
119
160
  const anchorsWithEventListeners = anchors.map(async anchor => {
120
161
  const listeners = await getEventListeners(session, anchor.node.devtoolsNodePath);
121
162
 
163
+ /** @type {Set<{type: string}>} */
164
+ const ancestorListeners = new Set();
165
+ const splitPath = anchor.node.devtoolsNodePath.split(',');
166
+ const ancestorListenerPromises = [];
167
+ while (splitPath.length >= 2) {
168
+ splitPath.length -= 2;
169
+ const path = splitPath.join(',');
170
+ const promise = getEventListeners(session, path).then(listeners => {
171
+ for (const listener of listeners) {
172
+ ancestorListeners.add(listener);
173
+ }
174
+ }).catch(() => {});
175
+ ancestorListenerPromises.push(promise);
176
+ }
177
+
178
+ await Promise.all(ancestorListenerPromises);
179
+
122
180
  return {
123
181
  ...anchor,
124
182
  listeners,
183
+ ancestorListeners: Array.from(ancestorListeners),
125
184
  };
126
185
  });
127
186
 
@@ -4,8 +4,6 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- /* global caches */
8
-
9
7
  import BaseGatherer from '../base-gatherer.js';
10
8
 
11
9
  /**
@@ -30,7 +30,9 @@ class CSSUsage extends BaseGatherer {
30
30
 
31
31
  // Force style to recompute.
32
32
  // Doesn't appear to be necessary in newer versions of Chrome.
33
- await executionContext.evaluateAsync('getComputedStyle(document.body)');
33
+ await executionContext.evaluate(() => window.getComputedStyle(document.body), {
34
+ args: [],
35
+ });
34
36
 
35
37
  const {ruleUsage} = await session.sendCommand('CSS.stopRuleUsageTracking');
36
38
  await session.sendCommand('CSS.disable');
@@ -6,8 +6,6 @@
6
6
 
7
7
  import BaseGatherer from '../../base-gatherer.js';
8
8
 
9
- /* global document */
10
-
11
9
  /**
12
10
  * Get and return `name`, `publicId`, `systemId` from
13
11
  * `document.doctype`
@@ -9,7 +9,7 @@
9
9
  * and total number of elements used on the page.
10
10
  */
11
11
 
12
- /* global getNodeDetails document */
12
+ /* global getNodeDetails */
13
13
 
14
14
 
15
15
  import BaseGatherer from '../../base-gatherer.js';
@@ -3,7 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
- /* globals window getBoundingClientRect requestAnimationFrame */
6
+ /* globals getBoundingClientRect */
7
7
 
8
8
  import BaseGatherer from '../base-gatherer.js';
9
9
  import * as emulation from '../../lib/emulation.js';
@@ -15,7 +15,7 @@ import BaseGatherer from '../base-gatherer.js';
15
15
  import {pageFunctions} from '../../lib/page-functions.js';
16
16
  import * as FontSize from './seo/font-size.js';
17
17
 
18
- /* global window, getElementsInDocument, Image, getNodeDetails, ShadowRoot */
18
+ /* global getElementsInDocument, getNodeDetails */
19
19
 
20
20
  /** @param {Element} element */
21
21
  /* c8 ignore start */
@@ -81,7 +81,7 @@ class InspectorIssues extends BaseGatherer {
81
81
  propertyRuleIssue: [],
82
82
  quirksModeIssue: [],
83
83
  cookieIssue: [],
84
- selectElementAccessibilityIssue: [],
84
+ elementAccessibilityIssue: [],
85
85
  sharedArrayBufferIssue: [],
86
86
  sharedDictionaryIssue: [],
87
87
  stylesheetLoadingIssue: [],
@@ -13,7 +13,7 @@ import {MainResource} from '../../computed/main-resource.js';
13
13
  import {Util} from '../../../shared/util.js';
14
14
  import * as i18n from '../../lib/i18n/i18n.js';
15
15
 
16
- /* globals HTMLLinkElement getNodeDetails */
16
+ /* globals getNodeDetails */
17
17
 
18
18
  /**
19
19
  * @fileoverview
@@ -8,7 +8,6 @@
8
8
  * @fileoverview Gathers a list of detected JS libraries and their versions.
9
9
  */
10
10
 
11
- /* global window */
12
11
  /* global d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests */
13
12
 
14
13
  import fs from 'fs';
@@ -74,7 +74,9 @@ class Stylesheets extends BaseGatherer {
74
74
 
75
75
  // Force style to recompute.
76
76
  // Doesn't appear to be necessary in newer versions of Chrome.
77
- await executionContext.evaluateAsync('getComputedStyle(document.body)');
77
+ await executionContext.evaluate(() => window.getComputedStyle(document.body), {
78
+ args: [],
79
+ });
78
80
 
79
81
  session.off('CSS.styleSheetAdded', this._onStylesheetAdded);
80
82
 
@@ -23,21 +23,20 @@ declare class TraceElements extends BaseGatherer {
23
23
  *
24
24
  * @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
25
25
  * @param {Map<number, number>} impactByNodeId
26
- * @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
27
26
  * @return {number|undefined}
28
27
  */
29
- static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number>, event: import("../../lib/trace-engine.js").SaneSyntheticLayoutShift): number | undefined;
28
+ static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number>): number | undefined;
30
29
  /**
31
30
  * This function finds the top (up to 15) layout shifts on the page, and returns
32
31
  * the id of the largest impacted node of each shift, along with any related nodes
33
32
  * that may have caused the shift.
34
33
  *
35
34
  * @param {LH.Trace} trace
36
- * @param {LH.Artifacts.TraceEngineResult['data']} traceEngineResult
35
+ * @param {LH.Artifacts.TraceEngineResult['parsedTrace']} traceEngineResult
37
36
  * @param {LH.Gatherer.Context} context
38
37
  * @return {Promise<Array<{nodeId: number}>>}
39
38
  */
40
- static getTopLayoutShifts(trace: LH.Trace, traceEngineResult: LH.Artifacts.TraceEngineResult["data"], context: LH.Gatherer.Context): Promise<Array<{
39
+ static getTopLayoutShifts(trace: LH.Trace, traceEngineResult: LH.Artifacts.TraceEngineResult["parsedTrace"], context: LH.Gatherer.Context): Promise<Array<{
41
40
  nodeId: number;
42
41
  }>>;
43
42
  /**
@@ -35,7 +35,7 @@ const MAX_LAYOUT_SHIFTS = 15;
35
35
  */
36
36
  /* c8 ignore start */
37
37
  function getNodeDetailsData() {
38
- const elem = this.nodeType === document.ELEMENT_NODE ? this : this.parentElement; // eslint-disable-line no-undef
38
+ const elem = this.nodeType === document.ELEMENT_NODE ? this : this.parentElement;
39
39
  let traceElement;
40
40
  if (elem) {
41
41
  // @ts-expect-error - getNodeDetails put into scope via stringification
@@ -152,49 +152,19 @@ class TraceElements extends BaseGatherer {
152
152
  *
153
153
  * @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
154
154
  * @param {Map<number, number>} impactByNodeId
155
- * @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
156
155
  * @return {number|undefined}
157
156
  */
158
- static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event) {
159
- try {
160
- let biggestImpactNodeId;
161
- let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
162
- for (const node of impactedNodes) {
163
- const impactScore = impactByNodeId.get(node.node_id);
164
- if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
165
- biggestImpactNodeId = node.node_id;
166
- biggestImpactNodeScore = impactScore;
167
- }
157
+ static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId) {
158
+ let biggestImpactNodeId;
159
+ let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
160
+ for (const node of impactedNodes) {
161
+ const impactScore = impactByNodeId.get(node.node_id);
162
+ if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
163
+ biggestImpactNodeId = node.node_id;
164
+ biggestImpactNodeScore = impactScore;
168
165
  }
169
- return biggestImpactNodeId;
170
- } catch (err) {
171
- // See https://github.com/GoogleChrome/lighthouse/issues/15870
172
- // `impactedNodes` should always be an array here, but it can randomly be something else for
173
- // currently unknown reasons. This exception handling will help us identify what
174
- // `impactedNodes` really is and also prevent the error from being fatal.
175
-
176
- // It's possible `impactedNodes` is not JSON serializable, so let's add more supplemental
177
- // fields just in case.
178
- const impactedNodesType = typeof impactedNodes;
179
- const impactedNodesClassName = impactedNodes?.constructor?.name;
180
-
181
- let impactedNodesJson;
182
- let eventJson;
183
- try {
184
- impactedNodesJson = JSON.parse(JSON.stringify(impactedNodes));
185
- eventJson = JSON.parse(JSON.stringify(event));
186
- } catch {}
187
-
188
- Sentry.captureException(err, {
189
- extra: {
190
- impactedNodes: impactedNodesJson,
191
- event: eventJson,
192
- impactedNodesType,
193
- impactedNodesClassName,
194
- },
195
- });
196
- return;
197
166
  }
167
+ return biggestImpactNodeId;
198
168
  }
199
169
 
200
170
  /**
@@ -203,7 +173,7 @@ class TraceElements extends BaseGatherer {
203
173
  * that may have caused the shift.
204
174
  *
205
175
  * @param {LH.Trace} trace
206
- * @param {LH.Artifacts.TraceEngineResult['data']} traceEngineResult
176
+ * @param {LH.Artifacts.TraceEngineResult['parsedTrace']} traceEngineResult
207
177
  * @param {LH.Gatherer.Context} context
208
178
  * @return {Promise<Array<{nodeId: number}>>}
209
179
  */
@@ -222,7 +192,7 @@ class TraceElements extends BaseGatherer {
222
192
  const nodeIds = [];
223
193
  const impactedNodes = event.args.data.impacted_nodes || [];
224
194
  const biggestImpactedNodeId =
225
- this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event);
195
+ this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId);
226
196
  if (biggestImpactedNodeId !== undefined) {
227
197
  nodeIds.push(biggestImpactedNodeId);
228
198
  }
@@ -399,7 +369,7 @@ class TraceElements extends BaseGatherer {
399
369
  traceEngineResult, navigationId);
400
370
  const lcpNodeData = await TraceElements.getLcpElement(trace, context);
401
371
  const shiftsData = await TraceElements.getTopLayoutShifts(
402
- trace, traceEngineResult.data, context);
372
+ trace, traceEngineResult.parsedTrace, context);
403
373
  const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
404
374
  const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
405
375
 
@@ -6,8 +6,6 @@
6
6
 
7
7
  import BaseGatherer from '../base-gatherer.js';
8
8
 
9
- /* global window */
10
-
11
9
  /**
12
10
  * @return {LH.Artifacts.ViewportDimensions}
13
11
  */
package/core/index.cjs CHANGED
@@ -3,7 +3,6 @@
3
3
  * Copyright 2022 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- 'use strict';
7
6
 
8
7
  /**
9
8
  * @typedef ExportType
package/core/index.d.cts CHANGED
@@ -1,4 +1,9 @@
1
1
  export = lighthouse;
2
+ /**
3
+ * @license
4
+ * Copyright 2022 Google LLC
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
2
7
  /**
3
8
  * @typedef ExportType
4
9
  * @property {import('./index.js')['startFlow']} startFlow
@@ -102,5 +102,5 @@ export function normalizeTimingEntries(timings: LH.Result.MeasureEntry[]): void;
102
102
  /**
103
103
  * @param {LH.Result} lhr
104
104
  */
105
- export function elideAuditErrorStacks(lhr: LH.Result): void;
105
+ export function elideLhrErrorStacks(lhr: LH.Result): void;
106
106
  //# sourceMappingURL=asset-saver.d.ts.map
@@ -515,19 +515,31 @@ function normalizeTimingEntries(timings) {
515
515
  }
516
516
 
517
517
  /**
518
- * @param {LH.Result} lhr
518
+ * @param {string} errorStack
519
+ * @return {string}
519
520
  */
520
- function elideAuditErrorStacks(lhr) {
521
+ function elideErrorStack(errorStack) {
521
522
  const baseCallFrameUrl = url.pathToFileURL(LH_ROOT);
523
+ return errorStack
524
+ // Make paths relative to the repo root.
525
+ .replaceAll(baseCallFrameUrl.pathname, '')
526
+ // Remove line/col info.
527
+ .replaceAll(/:\d+:\d+/g, '');
528
+ }
529
+
530
+ /**
531
+ * @param {LH.Result} lhr
532
+ */
533
+ function elideLhrErrorStacks(lhr) {
522
534
  for (const auditResult of Object.values(lhr.audits)) {
523
535
  if (auditResult.errorStack) {
524
- auditResult.errorStack = auditResult.errorStack
525
- // Make paths relative to the repo root.
526
- .replaceAll(baseCallFrameUrl.pathname, '')
527
- // Remove line/col info.
528
- .replaceAll(/:\d+:\d+/g, '');
536
+ auditResult.errorStack = elideErrorStack(auditResult.errorStack);
529
537
  }
530
538
  }
539
+
540
+ if (lhr.runtimeError?.errorStack) {
541
+ lhr.runtimeError.errorStack = elideErrorStack(lhr.runtimeError.errorStack);
542
+ }
531
543
  }
532
544
 
533
545
  export {
@@ -543,5 +555,5 @@ export {
543
555
  saveLanternNetworkData,
544
556
  stringifyReplacer,
545
557
  normalizeTimingEntries,
546
- elideAuditErrorStacks,
558
+ elideLhrErrorStacks,
547
559
  };
@@ -704,7 +704,10 @@ const NotRestoredReasonDescription = {
704
704
  WebViewSafeBrowsingAllowlistChanged: {name: ('WebViewSafeBrowsingAllowlistChanged')},
705
705
  WebViewDocumentStartJavascriptChanged: {name: ('WebViewDocumentStartJavascriptChanged')},
706
706
  CacheControlNoStoreDeviceBoundSessionTerminated: {name: str_(UIStrings.cacheControlNoStore)},
707
- CacheLimitPruned: {name: ('CacheLimitPruned')},
707
+ CacheLimitPrunedOnModerateMemoryPressure:
708
+ {name: ('CacheLimitPrunedOnModerateMemoryPressure')},
709
+ CacheLimitPrunedOnCriticalMemoryPressure:
710
+ {name: ('CacheLimitPrunedOnCriticalMemoryPressure')},
708
711
  };
709
712
 
710
713
  export {