lighthouse 9.5.0-dev.20220509 → 9.6.1

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 (259) hide show
  1. package/changelog.md +70 -0
  2. package/dist/report/bundle.esm.js +400 -808
  3. package/dist/report/flow.js +22 -27
  4. package/dist/report/standalone.js +18 -23
  5. package/flow-report/src/summary/category.tsx +1 -1
  6. package/jest.config.js +1 -1
  7. package/lighthouse-cli/.eslintrc.cjs +4 -3
  8. package/lighthouse-cli/bin.js +1 -0
  9. package/lighthouse-cli/cli-flags.js +4 -3
  10. package/lighthouse-cli/commands/commands.js +1 -0
  11. package/lighthouse-cli/commands/list-audits.js +1 -0
  12. package/lighthouse-cli/commands/list-locales.js +1 -0
  13. package/lighthouse-cli/commands/list-trace-categories.js +1 -0
  14. package/lighthouse-cli/index.js +1 -0
  15. package/lighthouse-cli/printer.js +1 -0
  16. package/lighthouse-cli/run.js +24 -9
  17. package/lighthouse-cli/sentry-prompt.js +1 -0
  18. package/lighthouse-cli/test/smokehouse/core-tests.js +1 -0
  19. package/lighthouse-cli/test/smokehouse/frontends/back-compat-util.js +1 -0
  20. package/lighthouse-cli/test/smokehouse/frontends/lib.js +1 -0
  21. package/lighthouse-cli/test/smokehouse/frontends/node.js +1 -0
  22. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +1 -0
  23. package/lighthouse-cli/test/smokehouse/lib/child-process-error.js +1 -0
  24. package/lighthouse-cli/test/smokehouse/lib/concurrent-mapper.js +1 -0
  25. package/lighthouse-cli/test/smokehouse/lib/local-console.js +1 -0
  26. package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +23 -109
  27. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +3 -2
  28. package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +19 -28
  29. package/lighthouse-cli/test/smokehouse/report-assert.js +4 -3
  30. package/lighthouse-cli/test/smokehouse/smokehouse.js +1 -1
  31. package/lighthouse-cli/test/smokehouse/version-check-test.js +3 -0
  32. package/lighthouse-cli/test/smokehouse/version-check.js +1 -0
  33. package/lighthouse-core/audits/accessibility/accesskeys.js +1 -1
  34. package/lighthouse-core/audits/accessibility/aria-allowed-attr.js +1 -1
  35. package/lighthouse-core/audits/accessibility/aria-command-name.js +1 -1
  36. package/lighthouse-core/audits/accessibility/aria-hidden-body.js +1 -1
  37. package/lighthouse-core/audits/accessibility/aria-hidden-focus.js +1 -1
  38. package/lighthouse-core/audits/accessibility/aria-input-field-name.js +1 -1
  39. package/lighthouse-core/audits/accessibility/aria-meter-name.js +1 -1
  40. package/lighthouse-core/audits/accessibility/aria-progressbar-name.js +1 -1
  41. package/lighthouse-core/audits/accessibility/aria-required-attr.js +1 -1
  42. package/lighthouse-core/audits/accessibility/aria-required-children.js +1 -1
  43. package/lighthouse-core/audits/accessibility/aria-required-parent.js +1 -1
  44. package/lighthouse-core/audits/accessibility/aria-roles.js +1 -1
  45. package/lighthouse-core/audits/accessibility/aria-toggle-field-name.js +1 -1
  46. package/lighthouse-core/audits/accessibility/aria-tooltip-name.js +1 -1
  47. package/lighthouse-core/audits/accessibility/aria-treeitem-name.js +1 -1
  48. package/lighthouse-core/audits/accessibility/aria-valid-attr-value.js +1 -1
  49. package/lighthouse-core/audits/accessibility/aria-valid-attr.js +1 -1
  50. package/lighthouse-core/audits/accessibility/button-name.js +1 -1
  51. package/lighthouse-core/audits/accessibility/bypass.js +1 -1
  52. package/lighthouse-core/audits/accessibility/color-contrast.js +1 -1
  53. package/lighthouse-core/audits/accessibility/definition-list.js +1 -1
  54. package/lighthouse-core/audits/accessibility/dlitem.js +1 -1
  55. package/lighthouse-core/audits/accessibility/document-title.js +1 -1
  56. package/lighthouse-core/audits/accessibility/duplicate-id-active.js +1 -1
  57. package/lighthouse-core/audits/accessibility/duplicate-id-aria.js +1 -1
  58. package/lighthouse-core/audits/accessibility/form-field-multiple-labels.js +1 -1
  59. package/lighthouse-core/audits/accessibility/frame-title.js +1 -1
  60. package/lighthouse-core/audits/accessibility/heading-order.js +1 -1
  61. package/lighthouse-core/audits/accessibility/html-has-lang.js +1 -1
  62. package/lighthouse-core/audits/accessibility/html-lang-valid.js +1 -1
  63. package/lighthouse-core/audits/accessibility/image-alt.js +1 -1
  64. package/lighthouse-core/audits/accessibility/input-image-alt.js +1 -1
  65. package/lighthouse-core/audits/accessibility/label.js +1 -1
  66. package/lighthouse-core/audits/accessibility/link-name.js +1 -1
  67. package/lighthouse-core/audits/accessibility/list.js +1 -1
  68. package/lighthouse-core/audits/accessibility/listitem.js +5 -5
  69. package/lighthouse-core/audits/accessibility/meta-refresh.js +1 -1
  70. package/lighthouse-core/audits/accessibility/meta-viewport.js +1 -1
  71. package/lighthouse-core/audits/accessibility/object-alt.js +1 -1
  72. package/lighthouse-core/audits/accessibility/tabindex.js +1 -1
  73. package/lighthouse-core/audits/accessibility/td-headers-attr.js +1 -1
  74. package/lighthouse-core/audits/accessibility/th-has-data-cells.js +1 -1
  75. package/lighthouse-core/audits/accessibility/valid-lang.js +1 -1
  76. package/lighthouse-core/audits/accessibility/video-caption.js +1 -1
  77. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -2
  78. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +12 -11
  79. package/lighthouse-core/audits/byte-efficiency/efficient-animated-content.js +1 -1
  80. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +24 -22
  81. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +2 -3
  82. package/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js +3 -2
  83. package/lighthouse-core/audits/byte-efficiency/unminified-css.js +5 -3
  84. package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +11 -10
  85. package/lighthouse-core/audits/byte-efficiency/unused-css-rules.js +1 -0
  86. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +8 -11
  87. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +1 -1
  88. package/lighthouse-core/audits/byte-efficiency/uses-text-compression.js +1 -1
  89. package/lighthouse-core/audits/deprecations.js +598 -8
  90. package/lighthouse-core/audits/diagnostics.js +3 -4
  91. package/lighthouse-core/audits/dobetterweb/doctype.js +9 -20
  92. package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +1 -1
  93. package/lighthouse-core/audits/dobetterweb/no-document-write.js +1 -1
  94. package/lighthouse-core/audits/dobetterweb/notification-on-start.js +1 -1
  95. package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -2
  96. package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +1 -1
  97. package/lighthouse-core/audits/errors-in-console.js +2 -2
  98. package/lighthouse-core/audits/long-tasks.js +2 -3
  99. package/lighthouse-core/audits/metrics/experimental-interaction-to-next-paint.js +3 -1
  100. package/lighthouse-core/audits/metrics/first-contentful-paint-3g.js +2 -2
  101. package/lighthouse-core/audits/metrics/first-contentful-paint.js +2 -3
  102. package/lighthouse-core/audits/metrics/first-meaningful-paint.js +2 -3
  103. package/lighthouse-core/audits/metrics/interactive.js +2 -3
  104. package/lighthouse-core/audits/metrics/largest-contentful-paint.js +2 -3
  105. package/lighthouse-core/audits/metrics/max-potential-fid.js +2 -3
  106. package/lighthouse-core/audits/metrics/speed-index.js +2 -3
  107. package/lighthouse-core/audits/metrics/total-blocking-time.js +2 -3
  108. package/lighthouse-core/audits/metrics.js +2 -3
  109. package/lighthouse-core/audits/no-unload-listeners.js +15 -7
  110. package/lighthouse-core/audits/predictive-perf.js +1 -2
  111. package/lighthouse-core/audits/preload-lcp-image.js +3 -3
  112. package/lighthouse-core/audits/redirects.js +1 -1
  113. package/lighthouse-core/audits/script-treemap-data.js +45 -45
  114. package/lighthouse-core/audits/seo/http-status-code.js +4 -2
  115. package/lighthouse-core/audits/seo/link-text.js +0 -2
  116. package/lighthouse-core/audits/server-response-time.js +17 -2
  117. package/lighthouse-core/audits/service-worker.js +2 -7
  118. package/lighthouse-core/audits/timing-budget.js +2 -3
  119. package/lighthouse-core/audits/uses-rel-preconnect.js +1 -1
  120. package/lighthouse-core/audits/uses-rel-preload.js +1 -1
  121. package/lighthouse-core/audits/valid-source-maps.js +14 -12
  122. package/lighthouse-core/audits/violation-audit.js +1 -1
  123. package/lighthouse-core/audits/work-during-interaction.js +280 -0
  124. package/lighthouse-core/computed/critical-request-chains.js +9 -5
  125. package/lighthouse-core/computed/js-bundles.js +11 -11
  126. package/lighthouse-core/computed/main-resource.js +2 -3
  127. package/lighthouse-core/computed/metrics/first-contentful-paint-all-frames.js +1 -1
  128. package/lighthouse-core/computed/metrics/first-contentful-paint.js +1 -1
  129. package/lighthouse-core/computed/metrics/first-meaningful-paint.js +1 -1
  130. package/lighthouse-core/computed/metrics/interactive.js +1 -1
  131. package/lighthouse-core/computed/metrics/lantern-first-contentful-paint.js +1 -1
  132. package/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js +1 -1
  133. package/lighthouse-core/computed/metrics/lantern-interactive.js +1 -1
  134. package/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js +1 -1
  135. package/lighthouse-core/computed/metrics/lantern-max-potential-fid.js +1 -1
  136. package/lighthouse-core/computed/metrics/lantern-metric.js +5 -3
  137. package/lighthouse-core/computed/metrics/lantern-speed-index.js +1 -1
  138. package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +1 -1
  139. package/lighthouse-core/computed/metrics/largest-contentful-paint-all-frames.js +1 -1
  140. package/lighthouse-core/computed/metrics/largest-contentful-paint.js +1 -1
  141. package/lighthouse-core/computed/metrics/max-potential-fid.js +1 -1
  142. package/lighthouse-core/computed/metrics/metric.js +0 -1
  143. package/lighthouse-core/computed/metrics/responsiveness.js +12 -9
  144. package/lighthouse-core/computed/metrics/speed-index.js +1 -1
  145. package/lighthouse-core/computed/metrics/timing-summary.js +6 -13
  146. package/lighthouse-core/computed/metrics/total-blocking-time.js +1 -1
  147. package/lighthouse-core/computed/module-duplication.js +5 -6
  148. package/lighthouse-core/computed/page-dependency-graph.js +28 -55
  149. package/lighthouse-core/computed/resource-summary.js +5 -5
  150. package/lighthouse-core/computed/unused-css.js +7 -6
  151. package/lighthouse-core/computed/unused-javascript-summary.js +36 -16
  152. package/lighthouse-core/config/budget.js +2 -2
  153. package/lighthouse-core/config/default-config.js +1 -2
  154. package/lighthouse-core/config/metrics-to-audits.js +5 -0
  155. package/lighthouse-core/fraggle-rock/api.js +1 -1
  156. package/lighthouse-core/fraggle-rock/config/default-config.js +8 -5
  157. package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +3 -6
  158. package/lighthouse-core/fraggle-rock/gather/driver.js +2 -2
  159. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +26 -51
  160. package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +3 -0
  161. package/lighthouse-core/fraggle-rock/gather/session.js +2 -2
  162. package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +4 -5
  163. package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +6 -6
  164. package/lighthouse-core/gather/connections/cri.js +0 -1
  165. package/lighthouse-core/gather/driver/execution-context.js +2 -4
  166. package/lighthouse-core/gather/driver/navigation.js +9 -9
  167. package/lighthouse-core/gather/driver/network-monitor.js +3 -16
  168. package/lighthouse-core/gather/driver.js +1 -6
  169. package/lighthouse-core/gather/fetcher.js +210 -34
  170. package/lighthouse-core/gather/gather-runner.js +17 -13
  171. package/lighthouse-core/gather/gatherers/accessibility.js +4 -16
  172. package/lighthouse-core/gather/gatherers/console-messages.js +2 -4
  173. package/lighthouse-core/gather/gatherers/dobetterweb/doctype.js +2 -4
  174. package/lighthouse-core/gather/gatherers/full-page-screenshot.js +2 -3
  175. package/lighthouse-core/gather/gatherers/js-usage.js +87 -12
  176. package/lighthouse-core/gather/gatherers/link-elements.js +14 -12
  177. package/lighthouse-core/gather/gatherers/main-document-content.js +8 -8
  178. package/lighthouse-core/gather/gatherers/script-elements.js +56 -7
  179. package/lighthouse-core/gather/gatherers/seo/robots-txt.js +30 -2
  180. package/lighthouse-core/gather/gatherers/source-maps.js +1 -3
  181. package/lighthouse-core/gather/gatherers/trace-elements.js +21 -2
  182. package/lighthouse-core/gather/gatherers/web-app-manifest.js +2 -2
  183. package/lighthouse-core/index.js +2 -26
  184. package/lighthouse-core/lib/cdt/generated/SourceMap.js +1 -1
  185. package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +29 -8
  186. package/lighthouse-core/lib/navigation-error.js +4 -13
  187. package/lighthouse-core/lib/script-helpers.js +6 -11
  188. package/lighthouse-core/lib/url-shim.js +2 -2
  189. package/lighthouse-core/runner.js +5 -0
  190. package/lighthouse-core/scripts/manual-chrome-launcher.js +1 -0
  191. package/lighthouse-core/util-commonjs.js +24 -28
  192. package/package.json +12 -12
  193. package/readme.md +1 -1
  194. package/report/.eslintrc.cjs +1 -3
  195. package/report/assets/styles.css +10 -12
  196. package/report/assets/templates.html +9 -10
  197. package/report/clients/bundle.js +1 -0
  198. package/report/clients/standalone.js +1 -0
  199. package/report/generator/report-generator.js +21 -46
  200. package/report/renderer/api.js +1 -0
  201. package/report/renderer/category-renderer.js +1 -0
  202. package/report/renderer/components.js +304 -303
  203. package/report/renderer/crc-details-renderer.js +1 -0
  204. package/report/renderer/details-renderer.js +10 -10
  205. package/report/renderer/dom.js +1 -0
  206. package/report/renderer/drop-down-menu.js +1 -0
  207. package/report/renderer/element-screenshot-renderer.js +4 -0
  208. package/report/renderer/features-util.js +1 -0
  209. package/report/renderer/i18n.js +62 -118
  210. package/report/renderer/logger.js +1 -0
  211. package/report/renderer/open-tab.js +3 -1
  212. package/report/renderer/performance-category-renderer.js +1 -0
  213. package/report/renderer/pwa-category-renderer.js +1 -0
  214. package/report/renderer/report-renderer.js +1 -0
  215. package/report/renderer/report-ui-features.js +1 -0
  216. package/report/renderer/snippet-renderer.js +1 -0
  217. package/report/renderer/swap-locale-feature.js +1 -0
  218. package/report/renderer/text-encoding.js +1 -0
  219. package/report/renderer/topbar-features.js +1 -0
  220. package/report/renderer/util.js +24 -27
  221. package/report/test/clients/bundle-test.js +4 -0
  222. package/report/test/generator/file-namer-test.js +1 -0
  223. package/report/test/generator/report-generator-test.js +20 -25
  224. package/report/test/renderer/category-renderer-test.js +3 -0
  225. package/report/test/renderer/components-test.js +3 -0
  226. package/report/test/renderer/crc-details-renderer-test.js +3 -0
  227. package/report/test/renderer/details-renderer-test.js +12 -58
  228. package/report/test/renderer/dom-test.js +3 -0
  229. package/report/test/renderer/element-screenshot-renderer-test.js +6 -3
  230. package/report/test/renderer/i18n-test.js +25 -70
  231. package/report/test/renderer/performance-category-renderer-test.js +4 -11
  232. package/report/test/renderer/pwa-category-renderer-test.js +3 -0
  233. package/report/test/renderer/report-renderer-axe-test.js +3 -0
  234. package/report/test/renderer/report-renderer-test.js +3 -0
  235. package/report/test/renderer/report-ui-features-test.js +3 -0
  236. package/report/test/renderer/snippet-renderer-test.js +3 -0
  237. package/report/test/renderer/text-encoding-test.js +3 -0
  238. package/report/test/renderer/util-test.js +5 -2
  239. package/report/test-assets/faux-psi.js +1 -2
  240. package/shared/localization/locales/en-US.json +260 -53
  241. package/shared/localization/locales/en-XL.json +260 -53
  242. package/shared/test/localization/format-test.js +2 -0
  243. package/shared/test/localization/locales-test.js +2 -0
  244. package/shared/test/localization/swap-locale-test.js +2 -0
  245. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +2 -0
  246. package/third-party/chromium-synchronization/installability-errors-test.js +2 -0
  247. package/tsconfig.json +0 -1
  248. package/types/artifacts.d.ts +9 -48
  249. package/types/config.d.ts +0 -2
  250. package/types/externs.d.ts +2 -2
  251. package/types/gatherer.d.ts +2 -1
  252. package/types/global-lh.d.ts +0 -3
  253. package/types/lhr/audit-details.d.ts +3 -1
  254. package/types/lhr/lhr.d.ts +2 -2
  255. package/types/lhr/treemap.d.ts +1 -0
  256. package/lighthouse-core/audits/script-elements-test-audit.js +0 -30
  257. package/lighthouse-core/gather/gatherers/scripts.js +0 -154
  258. package/report/test/.eslintrc.cjs +0 -11
  259. package/shared/test/localization/.eslintrc.cjs +0 -11
@@ -6,55 +6,124 @@
6
6
  'use strict';
7
7
 
8
8
  /**
9
- * @fileoverview Fetcher is a utility for making requests to any arbitrary resource,
10
- * ignoring normal browser constraints such as CORS.
9
+ * @fileoverview Fetcher is a utility for making requests within the context of the page.
10
+ * Requests can circumvent CORS, and so are good for fetching source maps that may be hosted
11
+ * on a different origin.
11
12
  */
12
13
 
13
- /* global fetch */
14
+ /* global document */
14
15
 
15
16
  /** @typedef {{content: string|null, status: number|null}} FetchResponse */
16
17
 
18
+ const log = require('lighthouse-logger');
19
+ const {getBrowserVersion} = require('./driver/environment.js');
20
+
17
21
  class Fetcher {
18
22
  /**
19
23
  * @param {LH.Gatherer.FRProtocolSession} session
24
+ * @param {import('./driver/execution-context.js')} executionContext
20
25
  */
21
- constructor(session) {
26
+ constructor(session, executionContext) {
22
27
  this.session = session;
28
+ this.executionContext = executionContext;
29
+ /** @type {Map<string, (event: LH.Crdp.Fetch.RequestPausedEvent) => void>} */
30
+ this._onRequestPausedHandlers = new Map();
31
+ this._onRequestPaused = this._onRequestPaused.bind(this);
32
+ this._enabled = false;
23
33
  }
24
34
 
25
35
  /**
26
- * Fetches any resource using the network directly.
36
+ * Chrome M92 and above:
37
+ * We use `Network.loadNetworkResource` to fetch each resource.
38
+ *
39
+ * Chrome <M92:
40
+ * The Fetch domain accepts patterns for controlling what requests are intercepted, but we
41
+ * enable the domain for all patterns and filter events at a lower level to support multiple
42
+ * concurrent usages. Reasons for this:
43
+ *
44
+ * 1) only one set of patterns may be applied for the entire domain.
45
+ * 2) every request that matches the patterns are paused and only resumes when certain Fetch
46
+ * commands are sent. So a listener of the `Fetch.requestPaused` event must either handle
47
+ * the requests it cares about, or explicitly allow them to continue.
48
+ * 3) if multiple commands to continue the same request are sent, protocol errors occur.
27
49
  *
50
+ * So instead we have one global `Fetch.enable` / `Fetch.requestPaused` pair, and allow specific
51
+ * urls to be intercepted via `fetcher._setOnRequestPausedHandler`.
52
+ */
53
+ async enable() {
54
+ if (this._enabled) return;
55
+
56
+ this._enabled = true;
57
+ await this.session.sendCommand('Fetch.enable', {
58
+ patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}],
59
+ });
60
+ await this.session.on('Fetch.requestPaused', this._onRequestPaused);
61
+ }
62
+
63
+ async disable() {
64
+ if (!this._enabled) return;
65
+
66
+ this._enabled = false;
67
+ await this.session.off('Fetch.requestPaused', this._onRequestPaused);
68
+ await this.session.sendCommand('Fetch.disable');
69
+ this._onRequestPausedHandlers.clear();
70
+ }
71
+
72
+ /**
28
73
  * @param {string} url
29
- * @param {{timeout: number}=} options timeout is in ms
30
- * @return {Promise<FetchResponse>}
74
+ * @param {(event: LH.Crdp.Fetch.RequestPausedEvent) => void} handler
31
75
  */
32
- async fetchResource(url, options = {timeout: 2_000}) {
33
- // In Lightrider, `Network.loadNetworkResource` is not implemented, but fetch
34
- // is configured to work for any resource.
35
- if (global.isLightrider) {
36
- return this._wrapWithTimeout(this._fetchWithFetchApi(url), options.timeout);
76
+ async _setOnRequestPausedHandler(url, handler) {
77
+ this._onRequestPausedHandlers.set(url, handler);
78
+ }
79
+
80
+ /**
81
+ * @param {LH.Crdp.Fetch.RequestPausedEvent} event
82
+ */
83
+ _onRequestPaused(event) {
84
+ const handler = this._onRequestPausedHandlers.get(event.request.url);
85
+ if (handler) {
86
+ handler(event);
87
+ } else {
88
+ // Nothing cares about this URL, so continue.
89
+ this.session.sendCommand('Fetch.continueRequest', {requestId: event.requestId}).catch(err => {
90
+ log.error('Fetcher', `Failed to continueRequest: ${err.message}`);
91
+ });
37
92
  }
93
+ }
38
94
 
39
- return this._fetchResourceOverProtocol(url, options);
95
+ /**
96
+ * `Network.loadNetworkResource` was introduced in M88.
97
+ * The long timeout bug with `IO.read` was fixed in M92:
98
+ * https://bugs.chromium.org/p/chromium/issues/detail?id=1191757
99
+ * Lightrider has a bug forcing us to use the old version for now:
100
+ * https://docs.google.com/document/d/1V-DxgsOFMPxUuFrdGPQpyiCqSljvgNlOqXCtqDtd0b8/edit?usp=sharing&resourcekey=0-aIaIqcHFKG-0dX4MAudBEw
101
+ * @return {Promise<boolean>}
102
+ */
103
+ async shouldUseLegacyFetcher() {
104
+ const {milestone} = await getBrowserVersion(this.session);
105
+ return milestone < 92 || Boolean(global.isLightrider);
40
106
  }
41
107
 
42
108
  /**
109
+ * Requires that `fetcher.enable` has been called.
110
+ *
111
+ * Fetches any resource in a way that circumvents CORS.
112
+ *
43
113
  * @param {string} url
114
+ * @param {{timeout: number}=} options timeout is in ms
44
115
  * @return {Promise<FetchResponse>}
45
116
  */
46
- async _fetchWithFetchApi(url) {
47
- const response = await fetch(url);
117
+ async fetchResource(url, options = {timeout: 2_000}) {
118
+ if (!this._enabled) {
119
+ throw new Error('Must call `enable` before using fetchResource');
120
+ }
48
121
 
49
- let content = null;
50
- try {
51
- content = await response.text();
52
- } catch {}
122
+ if (await this.shouldUseLegacyFetcher()) {
123
+ return this._fetchResourceIframe(url, options);
124
+ }
53
125
 
54
- return {
55
- content,
56
- status: response.status,
57
- };
126
+ return this._fetchResourceOverProtocol(url, options);
58
127
  }
59
128
 
60
129
  /**
@@ -103,6 +172,18 @@ class Fetcher {
103
172
  };
104
173
  }
105
174
 
175
+ /**
176
+ * @param {string} requestId
177
+ * @return {Promise<string>}
178
+ */
179
+ async _resolveResponseBody(requestId) {
180
+ const responseBody = await this.session.sendCommand('Fetch.getResponseBody', {requestId});
181
+ if (responseBody.base64Encoded) {
182
+ return Buffer.from(responseBody.body, 'base64').toString();
183
+ }
184
+ return responseBody.body;
185
+ }
186
+
106
187
  /**
107
188
  * @param {string} url
108
189
  * @param {{timeout: number}} options timeout is in ms
@@ -110,7 +191,18 @@ class Fetcher {
110
191
  */
111
192
  async _fetchResourceOverProtocol(url, options) {
112
193
  const startTime = Date.now();
113
- const response = await this._wrapWithTimeout(this._loadNetworkResource(url), options.timeout);
194
+
195
+ /** @type {NodeJS.Timeout} */
196
+ let timeoutHandle;
197
+ const timeoutPromise = new Promise((_, reject) => {
198
+ timeoutHandle = setTimeout(reject, options.timeout, new Error('Timed out fetching resource'));
199
+ });
200
+
201
+ const responsePromise = this._loadNetworkResource(url);
202
+
203
+ /** @type {{stream: LH.Crdp.IO.StreamHandle|null, status: number|null}} */
204
+ const response = await Promise.race([responsePromise, timeoutPromise])
205
+ .finally(() => clearTimeout(timeoutHandle));
114
206
 
115
207
  const isOk = response.status && response.status >= 200 && response.status <= 299;
116
208
  if (!response.stream || !isOk) return {status: response.status, content: null};
@@ -121,21 +213,105 @@ class Fetcher {
121
213
  }
122
214
 
123
215
  /**
124
- * @template T
125
- * @param {Promise<T>} promise
126
- * @param {number} ms
216
+ * Fetches resource by injecting an iframe into the page.
217
+ * @param {string} url
218
+ * @param {{timeout: number}} options timeout is in ms
219
+ * @return {Promise<FetchResponse>}
127
220
  */
128
- async _wrapWithTimeout(promise, ms) {
221
+ async _fetchResourceIframe(url, options) {
222
+ /** @type {Promise<FetchResponse>} */
223
+ const requestInterceptionPromise = new Promise((resolve, reject) => {
224
+ /** @param {LH.Crdp.Fetch.RequestPausedEvent} event */
225
+ const handlerAsync = async event => {
226
+ const {requestId, responseStatusCode} = event;
227
+
228
+ // The first requestPaused event is for the request stage. Continue it.
229
+ if (!responseStatusCode) {
230
+ // Remove cookies so we aren't buying stuff on Amazon.
231
+ const headers = Object.entries(event.request.headers)
232
+ .filter(([name]) => name !== 'Cookie')
233
+ .map(([name, value]) => {
234
+ return {name, value};
235
+ });
236
+
237
+ await this.session.sendCommand('Fetch.continueRequest', {
238
+ requestId,
239
+ headers,
240
+ });
241
+ return;
242
+ }
243
+
244
+ if (responseStatusCode >= 200 && responseStatusCode <= 299) {
245
+ resolve({
246
+ status: responseStatusCode,
247
+ content: await this._resolveResponseBody(requestId),
248
+ });
249
+ } else {
250
+ resolve({status: responseStatusCode, content: null});
251
+ }
252
+
253
+ // Fail the request (from the page's perspective) so that the iframe never loads.
254
+ await this.session.sendCommand('Fetch.failRequest', {requestId, errorReason: 'Aborted'});
255
+ };
256
+ this._setOnRequestPausedHandler(url, event => handlerAsync(event).catch(reject));
257
+ });
258
+
259
+ /**
260
+ * @param {string} src
261
+ */
262
+ /* c8 ignore start */
263
+ function injectIframe(src) {
264
+ const iframe = document.createElement('iframe');
265
+ // Try really hard not to affect the page.
266
+ iframe.style.display = 'none';
267
+ iframe.style.visibility = 'hidden';
268
+ iframe.style.position = 'absolute';
269
+ iframe.style.top = '-1000px';
270
+ iframe.style.left = '-1000px';
271
+ iframe.style.width = '1px';
272
+ iframe.style.height = '1px';
273
+ iframe.src = src;
274
+ iframe.onload = iframe.onerror = () => {
275
+ iframe.remove();
276
+ iframe.onload = null;
277
+ iframe.onerror = null;
278
+ };
279
+ document.body.appendChild(iframe);
280
+ }
281
+ /* c8 ignore stop */
282
+
129
283
  /** @type {NodeJS.Timeout} */
130
- let timeoutHandle;
284
+ let asyncTimeout;
285
+ /** @type {Promise<never>} */
131
286
  const timeoutPromise = new Promise((_, reject) => {
132
- timeoutHandle = setTimeout(reject, ms, new Error('Timed out fetching resource'));
287
+ asyncTimeout = setTimeout(reject, options.timeout, new Error('Timed out fetching resource.'));
133
288
  });
134
289
 
135
- /** @type {Promise<T>} */
136
- const wrappedPromise = await Promise.race([promise, timeoutPromise])
137
- .finally(() => clearTimeout(timeoutHandle));
138
- return wrappedPromise;
290
+ const racePromise = Promise.race([
291
+ timeoutPromise,
292
+ requestInterceptionPromise,
293
+ ]).finally(() => clearTimeout(asyncTimeout));
294
+
295
+ // Temporarily disable auto-attaching for this iframe.
296
+ await this.session.sendCommand('Target.setAutoAttach', {
297
+ autoAttach: false,
298
+ waitForDebuggerOnStart: false,
299
+ });
300
+
301
+ const injectionPromise = this.executionContext.evaluate(injectIframe, {
302
+ args: [url],
303
+ useIsolation: true,
304
+ });
305
+
306
+ const [fetchResult] = await Promise.all([racePromise, injectionPromise]);
307
+
308
+ await this.session.sendCommand('Target.setAutoAttach', {
309
+ flatten: true,
310
+ autoAttach: true,
311
+ waitForDebuggerOnStart: true,
312
+ });
313
+
314
+ return fetchResult;
139
315
  }
140
316
  }
141
317
 
@@ -71,7 +71,7 @@ class GatherRunner {
71
71
  log.time(status);
72
72
  try {
73
73
  const requestedUrl = passContext.url;
74
- const {mainDocumentUrl, warnings} = await navigation.gotoURL(driver, requestedUrl, {
74
+ const {finalUrl, warnings} = await navigation.gotoURL(driver, requestedUrl, {
75
75
  waitUntil: passContext.passConfig.recordTrace ?
76
76
  ['load', 'fcp'] : ['load'],
77
77
  debugNavigation: passContext.settings.debugNavigation,
@@ -79,12 +79,7 @@ class GatherRunner {
79
79
  maxWaitForLoad: passContext.settings.maxWaitForLoad,
80
80
  ...passContext.passConfig,
81
81
  });
82
- passContext.url = mainDocumentUrl;
83
- const {URL} = passContext.baseArtifacts;
84
- if (!URL.finalUrl || !URL.mainDocumentUrl) {
85
- URL.finalUrl = mainDocumentUrl;
86
- URL.mainDocumentUrl = mainDocumentUrl;
87
- }
82
+ passContext.url = finalUrl;
88
83
  if (passContext.passConfig.loadFailureMode === 'fatal') {
89
84
  passContext.LighthouseRunWarnings.push(...warnings);
90
85
  }
@@ -183,6 +178,11 @@ class GatherRunner {
183
178
  const resetStorage = !options.settings.disableStorageReset;
184
179
  if (resetStorage) await storage.clearDataForOrigin(session, options.requestedUrl);
185
180
 
181
+ // Disable fetcher, in case a gatherer enabled it.
182
+ // This cleanup should be removed once the only usage of
183
+ // fetcher (fetching arbitrary URLs) is replaced by new protocol support.
184
+ await driver.fetcher.disable();
185
+
186
186
  await driver.disconnect();
187
187
  } catch (err) {
188
188
  // Ignore disconnecting error if browser was already closed.
@@ -407,12 +407,7 @@ class GatherRunner {
407
407
  devtoolsLogs: {},
408
408
  settings: options.settings,
409
409
  GatherContext: {gatherMode: 'navigation'},
410
- URL: {
411
- initialUrl: await options.driver.url(),
412
- requestedUrl: options.requestedUrl,
413
- mainDocumentUrl: '',
414
- finalUrl: '',
415
- },
410
+ URL: {requestedUrl: options.requestedUrl, finalUrl: options.requestedUrl},
416
411
  Timing: [],
417
412
  PageLoadError: null,
418
413
  };
@@ -430,6 +425,9 @@ class GatherRunner {
430
425
 
431
426
  const baseArtifacts = passContext.baseArtifacts;
432
427
 
428
+ // Copy redirected URL to artifact.
429
+ baseArtifacts.URL.finalUrl = passContext.url;
430
+
433
431
  // Fetch the manifest, if it exists.
434
432
  try {
435
433
  baseArtifacts.WebAppManifest = await WebAppManifest.getWebAppManifest(
@@ -519,6 +517,12 @@ class GatherRunner {
519
517
  await GatherRunner.populateBaseArtifacts(passContext);
520
518
  isFirstPass = false;
521
519
  }
520
+
521
+ // Disable fetcher for every pass, in case a gatherer enabled it.
522
+ // Noop if fetcher was never enabled.
523
+ // This cleanup should be removed once the only usage of
524
+ // fetcher (fetching arbitrary URLs) is replaced by new protocol support.
525
+ await driver.fetcher.disable();
522
526
  }
523
527
 
524
528
  await GatherRunner.disposeDriver(driver, options);
@@ -12,6 +12,9 @@ const axeLibSource = require('../../lib/axe.js').source;
12
12
  const pageFunctions = require('../../lib/page-functions.js');
13
13
 
14
14
  /**
15
+ * This is run in the page, not Lighthouse itself.
16
+ * axe.run returns a promise which fulfills with a results object
17
+ * containing any violations.
15
18
  * @return {Promise<LH.Artifacts.Accessibility>}
16
19
  */
17
20
  /* c8 ignore start */
@@ -35,11 +38,8 @@ async function runA11yChecks() {
35
38
  'wcag2aa',
36
39
  ],
37
40
  },
38
- // resultTypes doesn't limit the output of the axeResults object. Instead, if it's defined,
39
- // some expensive element identification is done only for the respective types. https://github.com/dequelabs/axe-core/blob/f62f0cf18f7b69b247b0b6362cf1ae71ffbf3a1b/lib/core/reporters/helpers/process-aggregate.js#L61-L97
40
41
  resultTypes: ['violations', 'inapplicable'],
41
42
  rules: {
42
- // Consider http://go/prcpg for expert review of the aXe rules.
43
43
  'tabindex': {enabled: true},
44
44
  'accesskeys': {enabled: true},
45
45
  'heading-order': {enabled: true},
@@ -60,12 +60,6 @@ async function runA11yChecks() {
60
60
  // https://github.com/dequelabs/axe-core/issues/2958
61
61
  'nested-interactive': {enabled: false},
62
62
  'frame-focusable-content': {enabled: false},
63
- 'aria-roledescription': {enabled: false},
64
- 'scrollable-region-focusable': {enabled: false},
65
- // TODO(paulirish): create audits and enable these 3.
66
- 'input-button-name': {enabled: false},
67
- 'role-img-alt': {enabled: false},
68
- 'select-name': {enabled: false},
69
63
  },
70
64
  });
71
65
 
@@ -76,8 +70,7 @@ async function runA11yChecks() {
76
70
  return {
77
71
  violations: axeResults.violations.map(createAxeRuleResultArtifact),
78
72
  incomplete: axeResults.incomplete.map(createAxeRuleResultArtifact),
79
- notApplicable: axeResults.inapplicable.map(result => ({id: result.id})), // FYI: inapplicable => notApplicable!
80
- passes: axeResults.passes.map(result => ({id: result.id})),
73
+ notApplicable: axeResults.inapplicable.map(result => ({id: result.id})),
81
74
  version: axeResults.testEngine.version,
82
75
  };
83
76
  }
@@ -157,11 +150,6 @@ class Accessibility extends FRGatherer {
157
150
  supportedModes: ['snapshot', 'navigation'],
158
151
  };
159
152
 
160
- static pageFns = {
161
- runA11yChecks,
162
- createAxeRuleResultArtifact,
163
- };
164
-
165
153
  /**
166
154
  * @param {LH.Gatherer.FRTransitionalContext} passContext
167
155
  * @return {Promise<LH.Artifacts.Accessibility>}
@@ -101,7 +101,6 @@ class ConsoleMessages extends FRGatherer {
101
101
  stackTrace: event.exceptionDetails.stackTrace,
102
102
  timestamp: event.timestamp,
103
103
  url: event.exceptionDetails.url,
104
- scriptId: event.exceptionDetails.scriptId,
105
104
  lineNumber: event.exceptionDetails.lineNumber,
106
105
  columnNumber: event.exceptionDetails.columnNumber,
107
106
  };
@@ -118,7 +117,7 @@ class ConsoleMessages extends FRGatherer {
118
117
 
119
118
  // JS events have a stack trace, which we use to get the column.
120
119
  // CSS/HTML events only expose a line number.
121
- const firstStackFrame = event.entry.stackTrace?.callFrames[0];
120
+ const {columnNumber} = event.entry.stackTrace?.callFrames[0] || {};
122
121
 
123
122
  this._logEntries.push({
124
123
  eventType: 'protocolLog',
@@ -128,9 +127,8 @@ class ConsoleMessages extends FRGatherer {
128
127
  stackTrace,
129
128
  timestamp,
130
129
  url,
131
- scriptId: firstStackFrame?.scriptId,
132
130
  lineNumber,
133
- columnNumber: firstStackFrame?.columnNumber,
131
+ columnNumber,
134
132
  });
135
133
  }
136
134
 
@@ -12,8 +12,7 @@ const FRGatherer = require('../../../fraggle-rock/gather/base-gatherer.js');
12
12
  /**
13
13
  * Get and return `name`, `publicId`, `systemId` from
14
14
  * `document.doctype`
15
- * and `compatMode` from `document` to check `quirks-mode`
16
- * @return {{name: string, publicId: string, systemId: string, documentCompatMode: string} | null}
15
+ * @return {{name: string, publicId: string, systemId: string} | null}
17
16
  */
18
17
  function getDoctype() {
19
18
  // An example of this is warnerbros.com/archive/spacejam/movie/jam.htm
@@ -21,9 +20,8 @@ function getDoctype() {
21
20
  return null;
22
21
  }
23
22
 
24
- const documentCompatMode = document.compatMode;
25
23
  const {name, publicId, systemId} = document.doctype;
26
- return {name, publicId, systemId, documentCompatMode};
24
+ return {name, publicId, systemId};
27
25
  }
28
26
 
29
27
  class Doctype extends FRGatherer {
@@ -15,7 +15,6 @@ const {waitForNetworkIdle} = require('../driver/wait-for-condition.js');
15
15
 
16
16
  // JPEG quality setting
17
17
  // Exploration and examples of reports using different quality settings: https://docs.google.com/document/d/1ZSffucIca9XDW2eEwfoevrk-OTl7WQFeMf0CgeJAA8M/edit#
18
- // Note: this analysis was done for JPEG, but now we use WEBP.
19
18
  const FULL_PAGE_SCREENSHOT_QUALITY = 30;
20
19
 
21
20
  /**
@@ -119,10 +118,10 @@ class FullPageScreenshot extends FRGatherer {
119
118
  await context.driver.executionContext.evaluate(waitForDoubleRaf, {args: []});
120
119
 
121
120
  const result = await session.sendCommand('Page.captureScreenshot', {
122
- format: 'webp',
121
+ format: 'jpeg',
123
122
  quality: FULL_PAGE_SCREENSHOT_QUALITY,
124
123
  });
125
- const data = 'data:image/webp;base64,' + result.data;
124
+ const data = 'data:image/jpeg;base64,' + result.data;
126
125
 
127
126
  return {
128
127
  data,
@@ -18,8 +18,20 @@ class JsUsage extends FRGatherer {
18
18
 
19
19
  constructor() {
20
20
  super();
21
+ /** @type {LH.Crdp.Debugger.ScriptParsedEvent[]} */
22
+ this._scriptParsedEvents = [];
21
23
  /** @type {LH.Crdp.Profiler.ScriptCoverage[]} */
22
24
  this._scriptUsages = [];
25
+ this.onScriptParsed = this.onScriptParsed.bind(this);
26
+ }
27
+
28
+ /**
29
+ * @param {LH.Crdp.Debugger.ScriptParsedEvent} event
30
+ */
31
+ onScriptParsed(event) {
32
+ if (event.embedderName) {
33
+ this._scriptParsedEvents.push(event);
34
+ }
23
35
  }
24
36
 
25
37
  /**
@@ -43,27 +55,90 @@ class JsUsage extends FRGatherer {
43
55
  }
44
56
 
45
57
  /**
58
+ * @param {LH.Gatherer.FRTransitionalContext} context
59
+ */
60
+ async startSensitiveInstrumentation(context) {
61
+ const session = context.driver.defaultSession;
62
+ session.on('Debugger.scriptParsed', this.onScriptParsed);
63
+ await session.sendCommand('Debugger.enable');
64
+ }
65
+
66
+ /**
67
+ * @param {LH.Gatherer.FRTransitionalContext} context
68
+ */
69
+ async stopSensitiveInstrumentation(context) {
70
+ const session = context.driver.defaultSession;
71
+ await session.sendCommand('Debugger.disable');
72
+ session.off('Debugger.scriptParsed', this.onScriptParsed);
73
+ }
74
+
75
+ /**
76
+ * Usages alone do not always generate an exhaustive list of scripts in timespan and snapshot.
77
+ * For audits which use this for url/scriptId mappings, we can include an empty usage object.
78
+ *
79
+ * @param {Record<string, Array<LH.Crdp.Profiler.ScriptCoverage>>} usageByUrl
80
+ */
81
+ _addMissingScriptIds(usageByUrl) {
82
+ for (const scriptParsedEvent of this._scriptParsedEvents) {
83
+ const url = scriptParsedEvent.embedderName;
84
+ if (!url) continue;
85
+
86
+ const scripts = usageByUrl[url] || [];
87
+ if (!scripts.find(s => s.scriptId === scriptParsedEvent.scriptId)) {
88
+ scripts.push({
89
+ url,
90
+ scriptId: scriptParsedEvent.scriptId,
91
+ functions: [],
92
+ });
93
+ }
94
+ usageByUrl[url] = scripts;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * @param {LH.Gatherer.FRTransitionalContext} context
46
100
  * @return {Promise<LH.Artifacts['JsUsage']>}
47
101
  */
48
- async getArtifact() {
49
- /** @type {Record<string, LH.Crdp.Profiler.ScriptCoverage>} */
50
- const usageByScriptId = {};
102
+ async getArtifact(context) {
103
+ /** @type {Record<string, Array<LH.Crdp.Profiler.ScriptCoverage>>} */
104
+ const usageByUrl = {};
105
+
106
+ // Force `Debugger.scriptParsed` events for url to scriptId mappings in snapshot mode.
107
+ if (context.gatherMode === 'snapshot') {
108
+ await this.startSensitiveInstrumentation(context);
109
+ await this.stopSensitiveInstrumentation(context);
110
+ }
51
111
 
52
112
  for (const scriptUsage of this._scriptUsages) {
53
- // If `url` is blank, that means the script was dynamically
54
- // created (eval, new Function, onload, ...)
55
- if (scriptUsage.url === '' || scriptUsage.url === '_lighthouse-eval.js') {
56
- // We currently don't consider coverage of dynamic scripts, and we definitely don't want
57
- // coverage of code Lighthouse ran to inspect the page, so we ignore this ScriptCoverage.
58
- // Audits would work the same without this, it is only an optimization (not tracking coverage
59
- // for scripts we don't care about).
113
+ // `ScriptCoverage.url` can be overridden by a magic sourceURL comment.
114
+ // Get the associated ScriptParsedEvent and use embedderName, which is the original url.
115
+ // See https://chromium-review.googlesource.com/c/v8/v8/+/2317310
116
+ let url = scriptUsage.url;
117
+ const scriptParsedEvent =
118
+ this._scriptParsedEvents.find(e => e.scriptId === scriptUsage.scriptId);
119
+ if (scriptParsedEvent?.embedderName) {
120
+ url = scriptParsedEvent.embedderName;
121
+ }
122
+
123
+ // If `url` is blank, that means the script was anonymous (eval, new Function, onload, ...).
124
+ // Or, it's because it was code Lighthouse over the protocol via `Runtime.evaluate`.
125
+ // We currently don't consider coverage of anonymous scripts, and we definitely don't want
126
+ // coverage of code Lighthouse ran to inspect the page, so we ignore this ScriptCoverage if
127
+ // url is blank.
128
+ if (scriptUsage.url === '' || (scriptParsedEvent && scriptParsedEvent.embedderName === '')) {
60
129
  continue;
61
130
  }
62
131
 
63
- usageByScriptId[scriptUsage.scriptId] = scriptUsage;
132
+ const scripts = usageByUrl[url] || [];
133
+ scripts.push(scriptUsage);
134
+ usageByUrl[url] = scripts;
135
+ }
136
+
137
+ if (context.gatherMode !== 'navigation') {
138
+ this._addMissingScriptIds(usageByUrl);
64
139
  }
65
140
 
66
- return usageByScriptId;
141
+ return usageByUrl;
67
142
  }
68
143
  }
69
144