lighthouse 13.3.0 → 13.4.0

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 (199) hide show
  1. package/cli/cli-flags.d.ts +8 -9
  2. package/cli/test/smokehouse/config/exclusions.js +2 -0
  3. package/cli/test/smokehouse/version-check.d.ts +1 -1
  4. package/core/audits/accessibility/accesskeys.js +1 -1
  5. package/core/audits/accessibility/aria-allowed-attr.js +1 -1
  6. package/core/audits/accessibility/aria-allowed-role.js +1 -1
  7. package/core/audits/accessibility/aria-command-name.js +1 -1
  8. package/core/audits/accessibility/aria-conditional-attr.js +1 -1
  9. package/core/audits/accessibility/aria-deprecated-role.js +1 -1
  10. package/core/audits/accessibility/aria-dialog-name.js +1 -1
  11. package/core/audits/accessibility/aria-hidden-body.js +1 -1
  12. package/core/audits/accessibility/aria-hidden-focus.js +1 -1
  13. package/core/audits/accessibility/aria-input-field-name.js +1 -1
  14. package/core/audits/accessibility/aria-meter-name.js +1 -1
  15. package/core/audits/accessibility/aria-progressbar-name.js +1 -1
  16. package/core/audits/accessibility/aria-prohibited-attr.js +1 -1
  17. package/core/audits/accessibility/aria-required-attr.js +1 -1
  18. package/core/audits/accessibility/aria-required-children.js +1 -1
  19. package/core/audits/accessibility/aria-required-parent.js +1 -1
  20. package/core/audits/accessibility/aria-roles.js +1 -1
  21. package/core/audits/accessibility/aria-text.js +1 -1
  22. package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
  23. package/core/audits/accessibility/aria-tooltip-name.js +1 -1
  24. package/core/audits/accessibility/aria-treeitem-name.js +1 -1
  25. package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
  26. package/core/audits/accessibility/aria-valid-attr.js +1 -1
  27. package/core/audits/accessibility/autocomplete-valid.js +1 -1
  28. package/core/audits/accessibility/button-name.js +1 -1
  29. package/core/audits/accessibility/bypass.js +1 -1
  30. package/core/audits/accessibility/color-contrast.js +1 -1
  31. package/core/audits/accessibility/definition-list.js +1 -1
  32. package/core/audits/accessibility/dlitem.js +1 -1
  33. package/core/audits/accessibility/document-title.js +1 -1
  34. package/core/audits/accessibility/duplicate-id-aria.js +1 -1
  35. package/core/audits/accessibility/empty-heading.js +1 -1
  36. package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
  37. package/core/audits/accessibility/frame-title.js +1 -1
  38. package/core/audits/accessibility/heading-order.js +1 -1
  39. package/core/audits/accessibility/html-has-lang.js +1 -1
  40. package/core/audits/accessibility/html-lang-valid.js +1 -1
  41. package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
  42. package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
  43. package/core/audits/accessibility/image-alt.js +1 -1
  44. package/core/audits/accessibility/image-redundant-alt.js +1 -1
  45. package/core/audits/accessibility/input-button-name.js +1 -1
  46. package/core/audits/accessibility/input-image-alt.js +1 -1
  47. package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
  48. package/core/audits/accessibility/label.js +1 -1
  49. package/core/audits/accessibility/landmark-one-main.js +1 -1
  50. package/core/audits/accessibility/link-in-text-block.js +1 -1
  51. package/core/audits/accessibility/link-name.js +1 -1
  52. package/core/audits/accessibility/list.js +1 -1
  53. package/core/audits/accessibility/listitem.js +1 -1
  54. package/core/audits/accessibility/meta-refresh.js +1 -1
  55. package/core/audits/accessibility/meta-viewport.js +1 -1
  56. package/core/audits/accessibility/object-alt.js +1 -1
  57. package/core/audits/accessibility/presentation-role-conflict.js +1 -1
  58. package/core/audits/accessibility/select-name.js +1 -1
  59. package/core/audits/accessibility/skip-link.js +1 -1
  60. package/core/audits/accessibility/svg-img-alt.js +1 -1
  61. package/core/audits/accessibility/tabindex.js +1 -1
  62. package/core/audits/accessibility/table-duplicate-name.js +1 -1
  63. package/core/audits/accessibility/table-fake-caption.js +1 -1
  64. package/core/audits/accessibility/target-size.js +1 -1
  65. package/core/audits/accessibility/td-has-header.js +1 -1
  66. package/core/audits/accessibility/td-headers-attr.js +1 -1
  67. package/core/audits/accessibility/th-has-data-cells.js +1 -1
  68. package/core/audits/accessibility/valid-lang.js +1 -1
  69. package/core/audits/accessibility/video-caption.js +1 -1
  70. package/core/audits/agentic/llms-txt.js +1 -1
  71. package/core/audits/baseline.js +10 -12
  72. package/core/audits/dobetterweb/geolocation-on-start.js +2 -1
  73. package/core/audits/network-requests.js +2 -0
  74. package/core/audits/seo/canonical.js +19 -7
  75. package/core/computed/js-bundles.d.ts +1 -1
  76. package/core/computed/load-simulator.d.ts +1 -1
  77. package/core/computed/metrics/first-contentful-paint-all-frames.d.ts +1 -1
  78. package/core/computed/metrics/first-contentful-paint.d.ts +1 -1
  79. package/core/computed/metrics/interactive.d.ts +1 -1
  80. package/core/computed/metrics/lantern-metric.d.ts +6 -6
  81. package/core/computed/metrics/largest-contentful-paint-all-frames.d.ts +1 -1
  82. package/core/computed/metrics/largest-contentful-paint.d.ts +1 -1
  83. package/core/computed/metrics/max-potential-fid.d.ts +1 -1
  84. package/core/computed/metrics/speed-index.d.ts +1 -1
  85. package/core/computed/metrics/time-to-first-byte.d.ts +1 -1
  86. package/core/computed/metrics/total-blocking-time.d.ts +1 -1
  87. package/core/computed/module-duplication.d.ts +1 -1
  88. package/core/computed/page-dependency-graph.d.ts +1 -1
  89. package/core/computed/unused-css.d.ts +1 -1
  90. package/core/config/agentic-browsing-config.d.ts +1 -0
  91. package/core/config/agentic-browsing-config.js +1 -0
  92. package/core/config/config-helpers.d.ts +1 -0
  93. package/core/config/config-helpers.js +1 -1
  94. package/core/config/config-plugin.d.ts +1 -0
  95. package/core/config/config-plugin.js +1 -0
  96. package/core/config/config.d.ts +1 -0
  97. package/core/config/config.js +1 -0
  98. package/core/config/constants.d.ts +1 -0
  99. package/core/config/constants.js +1 -0
  100. package/core/config/experimental-config.d.ts +1 -9
  101. package/core/config/experimental-config.js +2 -0
  102. package/core/config/filters.d.ts +1 -0
  103. package/core/config/filters.js +1 -0
  104. package/core/config/full-config.d.ts +1 -5
  105. package/core/config/full-config.js +2 -0
  106. package/core/config/lr-desktop-config.d.ts +1 -0
  107. package/core/config/lr-desktop-config.js +1 -0
  108. package/core/config/lr-mobile-config.d.ts +1 -5
  109. package/core/config/lr-mobile-config.js +2 -0
  110. package/core/config/perf-config.d.ts +1 -5
  111. package/core/config/perf-config.js +2 -0
  112. package/core/config/validation.d.ts +1 -0
  113. package/core/config/validation.js +1 -0
  114. package/core/gather/driver/execution-context.d.ts +23 -0
  115. package/core/gather/driver/execution-context.js +86 -1
  116. package/core/gather/driver/storage.js +13 -0
  117. package/core/gather/driver.d.ts +1 -1
  118. package/core/gather/gatherers/accessibility.js +1 -0
  119. package/core/gather/gatherers/trace-elements.d.ts +4 -1
  120. package/core/gather/gatherers/trace-elements.js +24 -28
  121. package/core/gather/gatherers/webmcp-schema.js +9 -16
  122. package/core/gather/gatherers/webmcp.d.ts +5 -0
  123. package/core/gather/gatherers/webmcp.js +34 -27
  124. package/core/lib/baseline/web-features-data.json +1177 -0
  125. package/core/lib/baseline/web-features-metadata.json +1 -1
  126. package/core/lib/deprecations-strings.d.ts +21 -5
  127. package/core/lib/deprecations-strings.js +16 -0
  128. package/core/lib/script-helpers.js +13 -1
  129. package/core/scoring.d.ts +58 -58
  130. package/dist/report/bundle.esm.js +4 -7
  131. package/dist/report/flow.js +6 -9
  132. package/dist/report/standalone.js +20 -12
  133. package/flow-report/types/flow-report.d.ts +2 -2
  134. package/package.json +20 -23
  135. package/report/assets/styles.css +1 -4
  136. package/report/renderer/components.js +1 -1
  137. package/report/renderer/details-renderer.d.ts +6 -1
  138. package/report/renderer/details-renderer.js +11 -3
  139. package/report/renderer/explodey-gauge.js +9 -7
  140. package/report/renderer/i18n-formatter.d.ts +1 -1
  141. package/report/renderer/logger.js +18 -4
  142. package/report/renderer/text-encoding.js +1 -1
  143. package/report/types/html-renderer.d.ts +2 -2
  144. package/shared/localization/locales/ar-XB.json +290 -65
  145. package/shared/localization/locales/ar.json +290 -65
  146. package/shared/localization/locales/bg.json +290 -65
  147. package/shared/localization/locales/ca.json +295 -70
  148. package/shared/localization/locales/cs.json +290 -65
  149. package/shared/localization/locales/da.json +294 -69
  150. package/shared/localization/locales/de.json +295 -70
  151. package/shared/localization/locales/el.json +290 -65
  152. package/shared/localization/locales/en-GB.json +290 -65
  153. package/shared/localization/locales/en-US.json +79 -67
  154. package/shared/localization/locales/en-XA.json +253 -64
  155. package/shared/localization/locales/en-XL.json +79 -67
  156. package/shared/localization/locales/es-419.json +290 -65
  157. package/shared/localization/locales/es.json +298 -73
  158. package/shared/localization/locales/fi.json +290 -65
  159. package/shared/localization/locales/fil.json +290 -65
  160. package/shared/localization/locales/fr.json +294 -69
  161. package/shared/localization/locales/he.json +293 -68
  162. package/shared/localization/locales/hi.json +291 -66
  163. package/shared/localization/locales/hr.json +290 -65
  164. package/shared/localization/locales/hu.json +290 -65
  165. package/shared/localization/locales/id.json +290 -65
  166. package/shared/localization/locales/it.json +294 -69
  167. package/shared/localization/locales/ja.json +290 -65
  168. package/shared/localization/locales/ko.json +290 -65
  169. package/shared/localization/locales/lt.json +290 -65
  170. package/shared/localization/locales/lv.json +290 -65
  171. package/shared/localization/locales/nl.json +290 -65
  172. package/shared/localization/locales/no.json +290 -65
  173. package/shared/localization/locales/pl.json +290 -65
  174. package/shared/localization/locales/pt-PT.json +291 -66
  175. package/shared/localization/locales/pt.json +290 -65
  176. package/shared/localization/locales/ro.json +290 -65
  177. package/shared/localization/locales/ru.json +301 -76
  178. package/shared/localization/locales/sk.json +291 -66
  179. package/shared/localization/locales/sl.json +290 -65
  180. package/shared/localization/locales/sr-Latn.json +290 -65
  181. package/shared/localization/locales/sr.json +290 -65
  182. package/shared/localization/locales/sv.json +297 -72
  183. package/shared/localization/locales/ta.json +291 -66
  184. package/shared/localization/locales/te.json +293 -68
  185. package/shared/localization/locales/th.json +291 -66
  186. package/shared/localization/locales/tr.json +290 -65
  187. package/shared/localization/locales/uk.json +290 -65
  188. package/shared/localization/locales/vi.json +291 -66
  189. package/shared/localization/locales/zh-HK.json +292 -67
  190. package/shared/localization/locales/zh-TW.json +291 -66
  191. package/shared/localization/locales/zh.json +291 -66
  192. package/shared/types/shared.d.ts +1 -1
  193. package/tsconfig-base.json +2 -1
  194. package/tsconfig.json +2 -0
  195. package/types/artifacts.d.ts +1 -1
  196. package/types/internal/rxjs.d.ts +1 -1
  197. package/types/internal/smokehouse.d.ts +1 -1
  198. package/types.d.ts +2 -0
  199. package/types.js +11 -0
@@ -1,13 +1,5 @@
1
1
  export default config;
2
- /**
3
- * @license
4
- * Copyright 2020 Google LLC
5
- * SPDX-License-Identifier: Apache-2.0
6
- */
7
- /**
8
- * @fileoverview Config for new audits that aren't quite ready for
9
- * being enabled by default.
10
- */
11
2
  /** @type {LH.Config} */
12
3
  declare const config: LH.Config;
4
+ import * as LH from '../../types/lh.js';
13
5
  //# sourceMappingURL=experimental-config.d.ts.map
@@ -9,6 +9,8 @@
9
9
  * being enabled by default.
10
10
  */
11
11
 
12
+ import * as LH from '../../types/lh.js';
13
+
12
14
  /** @type {LH.Config} */
13
15
  const config = {
14
16
  extends: 'lighthouse:default',
@@ -72,4 +72,5 @@ export function filterCategoriesByExplicitFilters(categories: LH.Config.Resolved
72
72
  * @return {LH.Config.ResolvedConfig['categories']}
73
73
  */
74
74
  export function filterCategoriesByGatherMode(categories: LH.Config.ResolvedConfig["categories"], mode: LH.Gatherer.GatherMode): LH.Config.ResolvedConfig["categories"];
75
+ import * as LH from '../../types/lh.js';
75
76
  //# sourceMappingURL=filters.d.ts.map
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import * as LH from '../../types/lh.js';
7
8
  import {Audit} from '../audits/audit.js';
8
9
 
9
10
  /** @type {Record<keyof LH.BaseArtifacts, string>} */
@@ -1,9 +1,5 @@
1
1
  export default fullConfig;
2
- /**
3
- * @license
4
- * Copyright 2017 Google LLC
5
- * SPDX-License-Identifier: Apache-2.0
6
- */
7
2
  /** @type {LH.Config} */
8
3
  declare const fullConfig: LH.Config;
4
+ import * as LH from '../../types/lh.js';
9
5
  //# sourceMappingURL=full-config.d.ts.map
@@ -4,6 +4,8 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import * as LH from '../../types/lh.js';
8
+
7
9
  /** @type {LH.Config} */
8
10
  const fullConfig = {
9
11
  extends: 'lighthouse:default',
@@ -1,4 +1,5 @@
1
1
  export default config;
2
2
  /** @type {LH.Config} */
3
3
  declare const config: LH.Config;
4
+ import * as LH from '../../types/lh.js';
4
5
  //# sourceMappingURL=lr-desktop-config.d.ts.map
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import * as LH from '../../types/lh.js';
7
8
  import * as constants from './constants.js';
8
9
 
9
10
  /** @type {LH.Config} */
@@ -1,9 +1,5 @@
1
1
  export default config;
2
- /**
3
- * @license
4
- * Copyright 2018 Google LLC
5
- * SPDX-License-Identifier: Apache-2.0
6
- */
7
2
  /** @type {LH.Config} */
8
3
  declare const config: LH.Config;
4
+ import * as LH from '../../types/lh.js';
9
5
  //# sourceMappingURL=lr-mobile-config.d.ts.map
@@ -4,6 +4,8 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import * as LH from '../../types/lh.js';
8
+
7
9
  /** @type {LH.Config} */
8
10
  const config = {
9
11
  extends: 'lighthouse:default',
@@ -1,9 +1,5 @@
1
1
  export default perfConfig;
2
- /**
3
- * @license
4
- * Copyright 2018 Google LLC
5
- * SPDX-License-Identifier: Apache-2.0
6
- */
7
2
  /** @type {LH.Config} */
8
3
  declare const perfConfig: LH.Config;
4
+ import * as LH from '../../types/lh.js';
9
5
  //# sourceMappingURL=perf-config.d.ts.map
@@ -4,6 +4,8 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import * as LH from '../../types/lh.js';
8
+
7
9
  /** @type {LH.Config} */
8
10
  const perfConfig = {
9
11
  extends: 'lighthouse:default',
@@ -58,4 +58,5 @@ export function throwInvalidDependencyOrder(artifactId: string, dependencyKey: s
58
58
  * @return {never}
59
59
  */
60
60
  export function throwInvalidArtifactDependency(artifactId: string, dependencyKey: string): never;
61
+ import * as LH from '../../types/lh.js';
61
62
  //# sourceMappingURL=validation.d.ts.map
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import * as LH from '../../types/lh.js';
7
8
  import {Audit} from '../audits/audit.js';
8
9
  import BaseGatherer from '../gather/base-gatherer.js';
9
10
  import * as i18n from '../lib/i18n/i18n.js';
@@ -102,6 +102,29 @@ export class ExecutionContext {
102
102
  args: T;
103
103
  deps?: Array<Function | string>;
104
104
  }): Promise<void>;
105
+ /**
106
+ * Call a function on the given object.
107
+ * Returns a promise that resolves on a value of `mainFn`'s return type.
108
+ * @template {unknown[]} T, R
109
+ * @param {((thisArg: any, ...args: T) => R)} mainFn The main function to call.
110
+ * @param {{args: T, objectId: string, deps?: Array<Function|string>}} options `args` should
111
+ * match the args of `mainFn`, and can be any serializable value. `deps` are functions that must be
112
+ * defined for `mainFn` to work.
113
+ * @return {Promise<Awaited<R>>}
114
+ */
115
+ evaluateOnObject<T extends unknown[], R>(mainFn: ((thisArg: any, ...args: T) => R), options: {
116
+ args: T;
117
+ objectId: string;
118
+ deps?: Array<Function | string>;
119
+ }): Promise<Awaited<R>>;
120
+ /**
121
+ * @param {string} functionDeclaration
122
+ * @param {{objectId: string}} options
123
+ * @return {Promise<*>}
124
+ */
125
+ _callFunctionOn(functionDeclaration: string, options: {
126
+ objectId: string;
127
+ }): Promise<any>;
105
128
  /**
106
129
  * Cache native functions/objects inside window so we are sure polyfills do not overwrite the
107
130
  * native implementations when the page loads.
@@ -223,6 +223,7 @@ class ExecutionContext {
223
223
 
224
224
  const expression = `(() => {
225
225
  ${ExecutionContext._cachedNativesPreamble};
226
+ ${pageFunctions.esbuildFunctionWrapperString}
226
227
  ${depsSerialized};
227
228
  (${mainFn})(${argsSerialized});
228
229
  })()
@@ -231,6 +232,87 @@ class ExecutionContext {
231
232
  await this._session.sendCommand('Page.addScriptToEvaluateOnNewDocument', {source: expression});
232
233
  }
233
234
 
235
+ /**
236
+ * Call a function on the given object.
237
+ * Returns a promise that resolves on a value of `mainFn`'s return type.
238
+ * @template {unknown[]} T, R
239
+ * @param {((thisArg: any, ...args: T) => R)} mainFn The main function to call.
240
+ * @param {{args: T, objectId: string, deps?: Array<Function|string>}} options `args` should
241
+ * match the args of `mainFn`, and can be any serializable value. `deps` are functions that must be
242
+ * defined for `mainFn` to work.
243
+ * @return {Promise<Awaited<R>>}
244
+ */
245
+ evaluateOnObject(mainFn, options) {
246
+ const argsSerialized = ExecutionContext.serializeArguments(options.args);
247
+ const depsSerialized = ExecutionContext.serializeDeps(options.deps);
248
+
249
+ const argsString = argsSerialized ? `this, ${argsSerialized}` : 'this';
250
+ const functionDeclaration = `function() {
251
+ ${depsSerialized}
252
+ return (${mainFn})(${argsString});
253
+ }`;
254
+ return this._callFunctionOn(functionDeclaration, options);
255
+ }
256
+
257
+ /**
258
+ * @param {string} functionDeclaration
259
+ * @param {{objectId: string}} options
260
+ * @return {Promise<*>}
261
+ */
262
+ async _callFunctionOn(functionDeclaration, options) {
263
+ const timeout = this._session.hasNextProtocolTimeout() ?
264
+ this._session.getNextProtocolTimeout() :
265
+ 60000;
266
+
267
+ const evaluationParams = {
268
+ functionDeclaration: `function wrapInNativePromise() {
269
+ ${ExecutionContext._cachedNativesPreamble};
270
+ ${pageFunctions.esbuildFunctionWrapperString}
271
+ const self = this;
272
+ const args = arguments;
273
+ return new Promise(function (resolve) {
274
+ return Promise.resolve()
275
+ .then(_ => (${functionDeclaration}).apply(self, args))
276
+ .catch(${pageFunctions.wrapRuntimeEvalErrorInBrowser})
277
+ .then(resolve);
278
+ });
279
+ }
280
+ //# sourceURL=_lighthouse-eval.js
281
+ `,
282
+ objectId: options.objectId,
283
+ returnByValue: true,
284
+ awaitPromise: true,
285
+ timeout,
286
+ };
287
+
288
+ this._session.setNextProtocolTimeout(timeout);
289
+ const response = await this._session.sendCommand('Runtime.callFunctionOn', evaluationParams);
290
+
291
+ const ex = response.exceptionDetails;
292
+ if (ex) {
293
+ const elidedExpression = functionDeclaration.replace(/\s+/g, ' ').substring(0, 100);
294
+ const messageLines = [
295
+ 'Runtime.callFunctionOn exception',
296
+ `Expression: ${elidedExpression}\n---- (elided)`,
297
+ !ex.stackTrace ? `Parse error at: ${ex.lineNumber + 1}:${ex.columnNumber + 1}` : null,
298
+ ex.exception?.description || ex.text,
299
+ ].filter(Boolean);
300
+ const evaluationError = new Error(messageLines.join('\n'));
301
+ return Promise.reject(evaluationError);
302
+ }
303
+
304
+ if (response.result === undefined) {
305
+ return Promise.reject(
306
+ new Error('Runtime.callFunctionOn response did not contain a "result" object'));
307
+ }
308
+ const value = response.result.value;
309
+ if (value?.__failedInBrowser) {
310
+ return Promise.reject(Object.assign(new Error(), value));
311
+ } else {
312
+ return value;
313
+ }
314
+ }
315
+
234
316
  /**
235
317
  * Cache native functions/objects inside window so we are sure polyfills do not overwrite the
236
318
  * native implementations when the page loads.
@@ -279,7 +361,10 @@ class ExecutionContext {
279
361
  * @return {string}
280
362
  */
281
363
  static serializeDeps(deps) {
282
- deps = [pageFunctions.esbuildFunctionWrapperString, ...deps || []];
364
+ if (!deps) {
365
+ return '';
366
+ }
367
+
283
368
  return deps.map(dep => {
284
369
  if (typeof dep === 'function') {
285
370
  // esbuild will change the actual function name (ie. function actualName() {})
@@ -7,6 +7,7 @@
7
7
  import log from 'lighthouse-logger';
8
8
 
9
9
  import * as i18n from '../../lib/i18n/i18n.js';
10
+ import {Sentry} from '../../lib/sentry.js';
10
11
 
11
12
  /* eslint-disable max-len */
12
13
  const UIStrings = {
@@ -81,6 +82,18 @@ async function getImportantStorageWarning(session, url) {
81
82
  const usageData = await session.sendCommand('Storage.getUsageAndQuota', {
82
83
  origin: url,
83
84
  });
85
+
86
+ // According to the types, this should never happen. But we've gotten an error
87
+ // report that it does.
88
+ // https://github.com/GoogleChrome/lighthouse/issues/17011
89
+ if (!usageData || !usageData.usageBreakdown) {
90
+ const err = new Error(`missing usageData: ${JSON.stringify(usageData)}`);
91
+ Sentry.captureException(err, {
92
+ level: 'error',
93
+ });
94
+ return;
95
+ }
96
+
84
97
  /** @type {Record<string, string>} */
85
98
  const storageTypeNames = {
86
99
  local_storage: 'Local Storage',
@@ -13,7 +13,7 @@ export class Driver implements LH.Gatherer.Driver {
13
13
  _executionContext: ExecutionContext | undefined;
14
14
  /** @type {Fetcher|undefined} */
15
15
  _fetcher: Fetcher | undefined;
16
- defaultSession: import("../../types/gatherer.js").default.ProtocolSession;
16
+ defaultSession: import("../../types.js").Gatherer.ProtocolSession;
17
17
  /** @return {LH.Gatherer.Driver['executionContext']} */
18
18
  get executionContext(): LH.Gatherer.Driver["executionContext"];
19
19
  get fetcher(): any;
@@ -84,6 +84,7 @@ async function runA11yChecks() {
84
84
  'table-fake-caption': {enabled: true},
85
85
  'target-size': {enabled: true},
86
86
  'td-has-header': {enabled: true},
87
+ 'aria-tab-name': {enabled: false}, // TODO: consider adding.
87
88
  },
88
89
  });
89
90
 
@@ -67,9 +67,12 @@ declare class TraceElements extends BaseGatherer {
67
67
  stopInstrumentation(context: LH.Gatherer.Context): Promise<void>;
68
68
  /**
69
69
  * @param {LH.Gatherer.ProtocolSession} session
70
+ * @param {LH.Gatherer.Driver['executionContext']} executionContext
70
71
  * @param {number} backendNodeId
71
72
  */
72
- getNodeDetails(session: LH.Gatherer.ProtocolSession, backendNodeId: number): Promise<import("devtools-protocol").Protocol.Runtime.CallFunctionOnResponse | null>;
73
+ getNodeDetails(session: LH.Gatherer.ProtocolSession, executionContext: LH.Gatherer.Driver["executionContext"], backendNodeId: number): Promise<{
74
+ node: any;
75
+ } | null | undefined>;
73
76
  /**
74
77
  * @param {LH.Gatherer.Context<'Trace'|'SourceMaps'>} context
75
78
  * @return {Promise<LH.Artifacts.TraceElement[]>}
@@ -20,7 +20,6 @@ import Trace from './trace.js';
20
20
  import {ProcessedTrace} from '../../computed/processed-trace.js';
21
21
  import {Responsiveness} from '../../computed/metrics/responsiveness.js';
22
22
  import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
23
- import {ExecutionContext} from '../driver/execution-context.js';
24
23
  import {TraceEngineResult} from '../../computed/trace-engine-result.js';
25
24
  import SourceMaps from './source-maps.js';
26
25
 
@@ -29,14 +28,14 @@ import SourceMaps from './source-maps.js';
29
28
  const MAX_LAYOUT_SHIFTS = 15;
30
29
 
31
30
  /**
32
- * @this {HTMLElement}
31
+ * @param {Element | ShadowRoot | Text} node
33
32
  */
34
33
  /* c8 ignore start */
35
- function getNodeDetailsData() {
36
- /** @type {Element|null} */
37
- let elem = this.nodeType === document.ELEMENT_NODE ? this : this.parentElement;
38
- if (!elem && this instanceof ShadowRoot) {
39
- elem = this.host;
34
+ function getNodeDetailsData(node) {
35
+ /** @type {Element | ShadowRoot | Text | null} */
36
+ let elem = node.nodeType === document.ELEMENT_NODE ? node : node.parentElement;
37
+ if (!elem && node instanceof ShadowRoot) {
38
+ elem = node.host;
40
39
  }
41
40
 
42
41
  let traceElement;
@@ -292,26 +291,22 @@ class TraceElements extends BaseGatherer {
292
291
 
293
292
  /**
294
293
  * @param {LH.Gatherer.ProtocolSession} session
294
+ * @param {LH.Gatherer.Driver['executionContext']} executionContext
295
295
  * @param {number} backendNodeId
296
296
  */
297
- async getNodeDetails(session, backendNodeId) {
297
+ async getNodeDetails(session, executionContext, backendNodeId) {
298
298
  try {
299
299
  const objectId = await resolveNodeIdToObjectId(session, backendNodeId);
300
300
  if (!objectId) return null;
301
301
 
302
- const deps = ExecutionContext.serializeDeps([
303
- pageFunctions.getNodeDetails,
302
+ return await executionContext.evaluateOnObject(
304
303
  getNodeDetailsData,
305
- ]);
306
- return await session.sendCommand('Runtime.callFunctionOn', {
307
- objectId,
308
- functionDeclaration: `function () {
309
- ${deps}
310
- return getNodeDetailsData.call(this);
311
- }`,
312
- returnByValue: true,
313
- awaitPromise: true,
314
- });
304
+ {
305
+ objectId,
306
+ args: [],
307
+ deps: [pageFunctions.getNodeDetails],
308
+ }
309
+ );
315
310
  } catch (err) {
316
311
  Sentry.captureException(err, {
317
312
  tags: {gatherer: 'TraceElements'},
@@ -346,29 +341,30 @@ class TraceElements extends BaseGatherer {
346
341
  trace, traceEngineResult, context);
347
342
  const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
348
343
 
349
- /** @type {Map<string, TraceElementData[]>} */
344
+ /** @type {Map<LH.Artifacts.TraceElement['traceEventType'], TraceElementData[]>} */
350
345
  const backendNodeDataMap = new Map([
351
346
  ['trace-engine', traceEngineData],
352
347
  ['layout-shift', shiftsData],
353
348
  ['animation', animatedElementData],
354
349
  ]);
355
350
 
356
- /** @type {Map<number, LH.Crdp.Runtime.CallFunctionOnResponse | null>} */
357
- const callFunctionOnCache = new Map();
351
+ /** @type {Map<number, {node: LH.Artifacts.NodeDetails} | null>} */
352
+ const evaluateOnObjectCache = new Map();
358
353
  /** @type {LH.Artifacts.TraceElement[]} */
359
354
  const traceElements = [];
360
355
  for (const [traceEventType, backendNodeData] of backendNodeDataMap) {
361
356
  for (let i = 0; i < backendNodeData.length; i++) {
362
357
  const backendNodeId = backendNodeData[i].nodeId;
363
- let response = callFunctionOnCache.get(backendNodeId);
358
+ let response = evaluateOnObjectCache.get(backendNodeId);
364
359
  if (response === undefined) {
365
- response = await this.getNodeDetails(session, backendNodeId);
366
- callFunctionOnCache.set(backendNodeId, response);
360
+ response = await this.getNodeDetails(
361
+ session, context.driver.executionContext, backendNodeId) || null;
362
+ evaluateOnObjectCache.set(backendNodeId, response);
367
363
  }
368
364
 
369
- if (response?.result?.value) {
365
+ if (response?.node) {
370
366
  traceElements.push({
371
- ...response.result.value,
367
+ ...response,
372
368
  traceEventType,
373
369
  animations: backendNodeData[i].animations,
374
370
  nodeId: backendNodeId,
@@ -7,7 +7,6 @@
7
7
  import BaseGatherer from '../base-gatherer.js';
8
8
  import {resolveNodeIdToObjectId} from '../driver/dom.js';
9
9
  import {pageFunctions} from '../../lib/page-functions.js';
10
- import {ExecutionContext} from '../driver/execution-context.js';
11
10
 
12
11
  class WebMcpSchemaIssues extends BaseGatherer {
13
12
  /** @type {LH.Gatherer.GathererMeta} */
@@ -68,27 +67,21 @@ class WebMcpSchemaIssues extends BaseGatherer {
68
67
  async getArtifact(context) {
69
68
  const session = context.driver.defaultSession;
70
69
 
71
- const deps = ExecutionContext.serializeDeps([
72
- pageFunctions.getNodeDetails,
73
- ]);
74
-
75
70
  const promises = this._issues.map(async (issue) => {
76
71
  const processedIssue = {...issue};
77
72
  if (issue.violatingNodeId) {
78
73
  try {
79
74
  const objectId = await resolveNodeIdToObjectId(session, issue.violatingNodeId);
80
75
  if (objectId) {
81
- const response = await session.sendCommand('Runtime.callFunctionOn', {
82
- objectId,
83
- functionDeclaration: `function () {
84
- ${deps}
85
- return getNodeDetails(this);
86
- }`,
87
- returnByValue: true,
88
- awaitPromise: true,
89
- });
90
- if (response && response.result && response.result.value) {
91
- processedIssue.nodeDetails = response.result.value;
76
+ const nodeDetails = await context.driver.executionContext.evaluateOnObject(
77
+ pageFunctions.getNodeDetails,
78
+ {
79
+ objectId,
80
+ args: [],
81
+ }
82
+ );
83
+ if (nodeDetails) {
84
+ processedIssue.nodeDetails = nodeDetails;
92
85
  }
93
86
  }
94
87
  } catch (err) {
@@ -48,6 +48,11 @@ declare class WebMCP extends BaseGatherer {
48
48
  * @param {LH.Gatherer.Context} passContext
49
49
  */
50
50
  stopInstrumentation(passContext: LH.Gatherer.Context): Promise<void>;
51
+ /**
52
+ * @param {LH.Gatherer.Context} context
53
+ * @param {WebMCPTool} tool
54
+ */
55
+ _tryResolveToolNodeDetails(context: LH.Gatherer.Context, tool: WebMCPTool): Promise<void>;
51
56
  /**
52
57
  * @param {LH.Gatherer.Context} context
53
58
  * @return {Promise<LH.Artifacts['WebMCP']>}
@@ -11,7 +11,6 @@
11
11
  import BaseGatherer from '../base-gatherer.js';
12
12
  import {resolveNodeIdToObjectId} from '../driver/dom.js';
13
13
  import {pageFunctions} from '../../lib/page-functions.js';
14
- import {ExecutionContext} from '../driver/execution-context.js';
15
14
 
16
15
  /**
17
16
  * @typedef {Object} WebMCPTool
@@ -99,6 +98,37 @@ class WebMCP extends BaseGatherer {
99
98
  }
100
99
  }
101
100
 
101
+ /**
102
+ * @param {LH.Gatherer.Context} context
103
+ * @param {WebMCPTool} tool
104
+ */
105
+ async _tryResolveToolNodeDetails(context, tool) {
106
+ if (!tool.backendNodeId) {
107
+ return;
108
+ }
109
+
110
+ const session = context.driver.defaultSession;
111
+
112
+ try {
113
+ const objectId = await resolveNodeIdToObjectId(session, tool.backendNodeId);
114
+ if (!objectId) {
115
+ return;
116
+ }
117
+
118
+ const nodeDetails = await context.driver.executionContext.evaluateOnObject(
119
+ pageFunctions.getNodeDetails, {
120
+ objectId,
121
+ args: [],
122
+ }
123
+ );
124
+ if (nodeDetails) {
125
+ tool.nodeDetails = nodeDetails;
126
+ }
127
+ } catch (err) {
128
+ // Ignore error
129
+ }
130
+ }
131
+
102
132
  /**
103
133
  * @param {LH.Gatherer.Context} context
104
134
  * @return {Promise<LH.Artifacts['WebMCP']>}
@@ -113,9 +143,8 @@ class WebMCP extends BaseGatherer {
113
143
  return {isSupported: false, tools: []};
114
144
  }
115
145
 
116
- const session = context.driver.defaultSession;
117
-
118
146
  // Remove duplicates based on name, keeping the latest occurrence.
147
+ /** @type {Map<string, WebMCPTool>} */
119
148
  const toolMap = new Map();
120
149
  for (const tool of this._tools) {
121
150
  toolMap.set(tool.name, tool);
@@ -123,32 +152,10 @@ class WebMCP extends BaseGatherer {
123
152
 
124
153
  const resolvedTools = [];
125
154
  for (const tool of toolMap.values()) {
126
- if (tool.backendNodeId) {
127
- try {
128
- const objectId = await resolveNodeIdToObjectId(session, tool.backendNodeId);
129
- if (objectId) {
130
- const deps = ExecutionContext.serializeDeps([
131
- pageFunctions.getNodeDetails,
132
- ]);
133
- const response = await session.sendCommand('Runtime.callFunctionOn', {
134
- objectId,
135
- functionDeclaration: `function () {
136
- ${deps}
137
- return getNodeDetails(this);
138
- }`,
139
- returnByValue: true,
140
- awaitPromise: true,
141
- });
142
- if (response && response.result && response.result.value) {
143
- tool.nodeDetails = response.result.value;
144
- }
145
- }
146
- } catch (err) {
147
- // Ignore error
148
- }
149
- }
155
+ await this._tryResolveToolNodeDetails(context, tool);
150
156
  resolvedTools.push(tool);
151
157
  }
158
+
152
159
  return {
153
160
  isSupported: true,
154
161
  tools: resolvedTools,