lighthouse 9.5.0-dev.20220607 → 9.6.2

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 (262) hide show
  1. package/dist/report/bundle.esm.js +467 -520
  2. package/dist/report/flow.js +22 -22
  3. package/dist/report/standalone.js +19 -19
  4. package/flow-report/src/summary/category.tsx +1 -1
  5. package/flow-report/src/topbar.tsx +2 -10
  6. package/flow-report/src/util.ts +1 -1
  7. package/flow-report/test/topbar-test.tsx +8 -7
  8. package/jest.config.js +1 -1
  9. package/lighthouse-cli/.eslintrc.cjs +4 -3
  10. package/lighthouse-cli/bin.js +1 -0
  11. package/lighthouse-cli/cli-flags.js +4 -3
  12. package/lighthouse-cli/commands/commands.js +1 -0
  13. package/lighthouse-cli/commands/list-audits.js +1 -0
  14. package/lighthouse-cli/commands/list-locales.js +1 -0
  15. package/lighthouse-cli/commands/list-trace-categories.js +1 -0
  16. package/lighthouse-cli/index.js +1 -0
  17. package/lighthouse-cli/printer.js +1 -0
  18. package/lighthouse-cli/run.js +24 -9
  19. package/lighthouse-cli/sentry-prompt.js +1 -0
  20. package/lighthouse-cli/test/smokehouse/core-tests.js +1 -0
  21. package/lighthouse-cli/test/smokehouse/frontends/back-compat-util.js +1 -0
  22. package/lighthouse-cli/test/smokehouse/frontends/lib.js +1 -0
  23. package/lighthouse-cli/test/smokehouse/frontends/node.js +1 -0
  24. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +1 -0
  25. package/lighthouse-cli/test/smokehouse/lib/child-process-error.js +1 -0
  26. package/lighthouse-cli/test/smokehouse/lib/concurrent-mapper.js +1 -0
  27. package/lighthouse-cli/test/smokehouse/lib/local-console.js +1 -0
  28. package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +23 -109
  29. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +3 -2
  30. package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +19 -28
  31. package/lighthouse-cli/test/smokehouse/report-assert.js +4 -3
  32. package/lighthouse-cli/test/smokehouse/smokehouse.js +1 -1
  33. package/lighthouse-cli/test/smokehouse/version-check-test.js +3 -0
  34. package/lighthouse-cli/test/smokehouse/version-check.js +1 -0
  35. package/lighthouse-core/audits/accessibility/accesskeys.js +1 -1
  36. package/lighthouse-core/audits/accessibility/aria-allowed-attr.js +1 -1
  37. package/lighthouse-core/audits/accessibility/aria-command-name.js +1 -1
  38. package/lighthouse-core/audits/accessibility/aria-hidden-body.js +1 -1
  39. package/lighthouse-core/audits/accessibility/aria-hidden-focus.js +1 -1
  40. package/lighthouse-core/audits/accessibility/aria-input-field-name.js +1 -1
  41. package/lighthouse-core/audits/accessibility/aria-meter-name.js +1 -1
  42. package/lighthouse-core/audits/accessibility/aria-progressbar-name.js +1 -1
  43. package/lighthouse-core/audits/accessibility/aria-required-attr.js +1 -1
  44. package/lighthouse-core/audits/accessibility/aria-required-children.js +1 -1
  45. package/lighthouse-core/audits/accessibility/aria-required-parent.js +1 -1
  46. package/lighthouse-core/audits/accessibility/aria-roles.js +1 -1
  47. package/lighthouse-core/audits/accessibility/aria-toggle-field-name.js +1 -1
  48. package/lighthouse-core/audits/accessibility/aria-tooltip-name.js +1 -1
  49. package/lighthouse-core/audits/accessibility/aria-treeitem-name.js +1 -1
  50. package/lighthouse-core/audits/accessibility/aria-valid-attr-value.js +1 -1
  51. package/lighthouse-core/audits/accessibility/aria-valid-attr.js +1 -1
  52. package/lighthouse-core/audits/accessibility/button-name.js +1 -1
  53. package/lighthouse-core/audits/accessibility/bypass.js +1 -1
  54. package/lighthouse-core/audits/accessibility/color-contrast.js +1 -1
  55. package/lighthouse-core/audits/accessibility/definition-list.js +1 -1
  56. package/lighthouse-core/audits/accessibility/dlitem.js +1 -1
  57. package/lighthouse-core/audits/accessibility/document-title.js +1 -1
  58. package/lighthouse-core/audits/accessibility/duplicate-id-active.js +1 -1
  59. package/lighthouse-core/audits/accessibility/duplicate-id-aria.js +1 -1
  60. package/lighthouse-core/audits/accessibility/form-field-multiple-labels.js +1 -1
  61. package/lighthouse-core/audits/accessibility/frame-title.js +1 -1
  62. package/lighthouse-core/audits/accessibility/heading-order.js +1 -1
  63. package/lighthouse-core/audits/accessibility/html-has-lang.js +1 -1
  64. package/lighthouse-core/audits/accessibility/html-lang-valid.js +1 -1
  65. package/lighthouse-core/audits/accessibility/image-alt.js +1 -1
  66. package/lighthouse-core/audits/accessibility/input-image-alt.js +1 -1
  67. package/lighthouse-core/audits/accessibility/label.js +1 -1
  68. package/lighthouse-core/audits/accessibility/link-name.js +1 -1
  69. package/lighthouse-core/audits/accessibility/list.js +1 -1
  70. package/lighthouse-core/audits/accessibility/listitem.js +5 -5
  71. package/lighthouse-core/audits/accessibility/meta-refresh.js +1 -1
  72. package/lighthouse-core/audits/accessibility/meta-viewport.js +1 -1
  73. package/lighthouse-core/audits/accessibility/object-alt.js +1 -1
  74. package/lighthouse-core/audits/accessibility/tabindex.js +1 -1
  75. package/lighthouse-core/audits/accessibility/td-headers-attr.js +1 -1
  76. package/lighthouse-core/audits/accessibility/th-has-data-cells.js +1 -1
  77. package/lighthouse-core/audits/accessibility/valid-lang.js +1 -1
  78. package/lighthouse-core/audits/accessibility/video-caption.js +1 -1
  79. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -2
  80. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +12 -11
  81. package/lighthouse-core/audits/byte-efficiency/efficient-animated-content.js +1 -1
  82. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +24 -22
  83. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +2 -3
  84. package/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js +3 -2
  85. package/lighthouse-core/audits/byte-efficiency/unminified-css.js +5 -3
  86. package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +11 -10
  87. package/lighthouse-core/audits/byte-efficiency/unused-css-rules.js +1 -0
  88. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +8 -11
  89. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +1 -1
  90. package/lighthouse-core/audits/byte-efficiency/uses-text-compression.js +1 -1
  91. package/lighthouse-core/audits/deprecations.js +598 -8
  92. package/lighthouse-core/audits/diagnostics.js +3 -4
  93. package/lighthouse-core/audits/dobetterweb/doctype.js +9 -20
  94. package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +1 -1
  95. package/lighthouse-core/audits/dobetterweb/no-document-write.js +1 -1
  96. package/lighthouse-core/audits/dobetterweb/notification-on-start.js +1 -1
  97. package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -2
  98. package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +1 -1
  99. package/lighthouse-core/audits/errors-in-console.js +2 -2
  100. package/lighthouse-core/audits/long-tasks.js +2 -3
  101. package/lighthouse-core/audits/metrics/first-contentful-paint-3g.js +2 -2
  102. package/lighthouse-core/audits/metrics/first-contentful-paint.js +2 -3
  103. package/lighthouse-core/audits/metrics/first-meaningful-paint.js +2 -3
  104. package/lighthouse-core/audits/metrics/interactive.js +2 -3
  105. package/lighthouse-core/audits/metrics/largest-contentful-paint.js +2 -3
  106. package/lighthouse-core/audits/metrics/max-potential-fid.js +2 -3
  107. package/lighthouse-core/audits/metrics/speed-index.js +2 -3
  108. package/lighthouse-core/audits/metrics/total-blocking-time.js +2 -3
  109. package/lighthouse-core/audits/metrics.js +2 -3
  110. package/lighthouse-core/audits/no-unload-listeners.js +15 -7
  111. package/lighthouse-core/audits/predictive-perf.js +1 -2
  112. package/lighthouse-core/audits/preload-lcp-image.js +3 -3
  113. package/lighthouse-core/audits/redirects.js +1 -1
  114. package/lighthouse-core/audits/script-treemap-data.js +45 -45
  115. package/lighthouse-core/audits/seo/http-status-code.js +4 -2
  116. package/lighthouse-core/audits/seo/link-text.js +0 -2
  117. package/lighthouse-core/audits/server-response-time.js +17 -2
  118. package/lighthouse-core/audits/service-worker.js +2 -7
  119. package/lighthouse-core/audits/timing-budget.js +2 -3
  120. package/lighthouse-core/audits/uses-rel-preconnect.js +1 -1
  121. package/lighthouse-core/audits/uses-rel-preload.js +1 -1
  122. package/lighthouse-core/audits/valid-source-maps.js +14 -12
  123. package/lighthouse-core/audits/violation-audit.js +1 -1
  124. package/lighthouse-core/audits/work-during-interaction.js +2 -2
  125. package/lighthouse-core/computed/critical-request-chains.js +9 -5
  126. package/lighthouse-core/computed/js-bundles.js +11 -11
  127. package/lighthouse-core/computed/main-resource.js +2 -3
  128. package/lighthouse-core/computed/metrics/first-contentful-paint-all-frames.js +1 -1
  129. package/lighthouse-core/computed/metrics/first-contentful-paint.js +1 -1
  130. package/lighthouse-core/computed/metrics/first-meaningful-paint.js +1 -1
  131. package/lighthouse-core/computed/metrics/interactive.js +1 -1
  132. package/lighthouse-core/computed/metrics/lantern-first-contentful-paint.js +1 -1
  133. package/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js +1 -1
  134. package/lighthouse-core/computed/metrics/lantern-interactive.js +1 -1
  135. package/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js +1 -1
  136. package/lighthouse-core/computed/metrics/lantern-max-potential-fid.js +1 -1
  137. package/lighthouse-core/computed/metrics/lantern-metric.js +5 -3
  138. package/lighthouse-core/computed/metrics/lantern-speed-index.js +1 -1
  139. package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +1 -1
  140. package/lighthouse-core/computed/metrics/largest-contentful-paint-all-frames.js +1 -1
  141. package/lighthouse-core/computed/metrics/largest-contentful-paint.js +1 -1
  142. package/lighthouse-core/computed/metrics/max-potential-fid.js +1 -1
  143. package/lighthouse-core/computed/metrics/metric.js +0 -1
  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/fraggle-rock/api.js +1 -1
  155. package/lighthouse-core/fraggle-rock/config/default-config.js +0 -3
  156. package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +3 -6
  157. package/lighthouse-core/fraggle-rock/gather/driver.js +2 -2
  158. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +26 -51
  159. package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +3 -0
  160. package/lighthouse-core/fraggle-rock/gather/session.js +22 -23
  161. package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +4 -5
  162. package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +6 -6
  163. package/lighthouse-core/gather/connections/cri.js +1 -5
  164. package/lighthouse-core/gather/driver/execution-context.js +2 -5
  165. package/lighthouse-core/gather/driver/navigation.js +10 -13
  166. package/lighthouse-core/gather/driver/network-monitor.js +3 -16
  167. package/lighthouse-core/gather/driver.js +1 -6
  168. package/lighthouse-core/gather/fetcher.js +210 -34
  169. package/lighthouse-core/gather/gather-runner.js +17 -13
  170. package/lighthouse-core/gather/gatherers/accessibility.js +4 -16
  171. package/lighthouse-core/gather/gatherers/console-messages.js +2 -4
  172. package/lighthouse-core/gather/gatherers/dobetterweb/doctype.js +2 -4
  173. package/lighthouse-core/gather/gatherers/full-page-screenshot.js +2 -3
  174. package/lighthouse-core/gather/gatherers/iframe-elements.js +1 -4
  175. package/lighthouse-core/gather/gatherers/image-elements.js +0 -2
  176. package/lighthouse-core/gather/gatherers/js-usage.js +87 -12
  177. package/lighthouse-core/gather/gatherers/link-elements.js +14 -12
  178. package/lighthouse-core/gather/gatherers/main-document-content.js +8 -8
  179. package/lighthouse-core/gather/gatherers/script-elements.js +56 -7
  180. package/lighthouse-core/gather/gatherers/seo/robots-txt.js +30 -2
  181. package/lighthouse-core/gather/gatherers/source-maps.js +1 -8
  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/page-functions.js +1 -3
  188. package/lighthouse-core/lib/script-helpers.js +6 -11
  189. package/lighthouse-core/lib/url-shim.js +2 -2
  190. package/lighthouse-core/runner.js +5 -0
  191. package/lighthouse-core/scripts/manual-chrome-launcher.js +1 -0
  192. package/lighthouse-core/util-commonjs.js +6 -8
  193. package/package.json +13 -13
  194. package/readme.md +1 -1
  195. package/report/.eslintrc.cjs +1 -3
  196. package/report/assets/styles.css +5 -10
  197. package/report/assets/templates.html +9 -10
  198. package/report/clients/bundle.js +1 -0
  199. package/report/clients/standalone.js +1 -0
  200. package/report/generator/report-generator.js +21 -46
  201. package/report/renderer/api.js +2 -0
  202. package/report/renderer/category-renderer.js +24 -20
  203. package/report/renderer/components.js +304 -303
  204. package/report/renderer/crc-details-renderer.js +24 -21
  205. package/report/renderer/details-renderer.js +12 -12
  206. package/report/renderer/dom.js +7 -6
  207. package/report/renderer/drop-down-menu.js +1 -0
  208. package/report/renderer/element-screenshot-renderer.js +5 -1
  209. package/report/renderer/features-util.js +1 -0
  210. package/report/renderer/i18n.js +62 -118
  211. package/report/renderer/logger.js +1 -0
  212. package/report/renderer/open-tab.js +3 -1
  213. package/report/renderer/performance-category-renderer.js +11 -10
  214. package/report/renderer/pwa-category-renderer.js +4 -3
  215. package/report/renderer/report-renderer.js +17 -17
  216. package/report/renderer/report-ui-features.js +1 -0
  217. package/report/renderer/snippet-renderer.js +3 -2
  218. package/report/renderer/swap-locale-feature.js +1 -0
  219. package/report/renderer/text-encoding.js +1 -0
  220. package/report/renderer/topbar-features.js +1 -0
  221. package/report/renderer/util.js +6 -7
  222. package/report/test/clients/bundle-test.js +5 -0
  223. package/report/test/generator/file-namer-test.js +1 -0
  224. package/report/test/generator/report-generator-test.js +20 -25
  225. package/report/test/renderer/category-renderer-test.js +4 -3
  226. package/report/test/renderer/components-test.js +3 -0
  227. package/report/test/renderer/crc-details-renderer-test.js +3 -0
  228. package/report/test/renderer/details-renderer-test.js +3 -49
  229. package/report/test/renderer/dom-test.js +3 -0
  230. package/report/test/renderer/element-screenshot-renderer-test.js +6 -3
  231. package/report/test/renderer/i18n-test.js +25 -70
  232. package/report/test/renderer/performance-category-renderer-test.js +5 -14
  233. package/report/test/renderer/pwa-category-renderer-test.js +4 -3
  234. package/report/test/renderer/report-renderer-axe-test.js +4 -3
  235. package/report/test/renderer/report-renderer-test.js +4 -3
  236. package/report/test/renderer/report-ui-features-test.js +4 -3
  237. package/report/test/renderer/snippet-renderer-test.js +3 -0
  238. package/report/test/renderer/text-encoding-test.js +3 -0
  239. package/report/test/renderer/util-test.js +5 -4
  240. package/report/test-assets/faux-psi.js +1 -2
  241. package/shared/localization/format.js +3 -7
  242. package/shared/localization/locales/en-US.json +238 -55
  243. package/shared/localization/locales/en-XL.json +238 -55
  244. package/shared/test/localization/format-test.js +2 -8
  245. package/shared/test/localization/locales-test.js +2 -0
  246. package/shared/test/localization/swap-locale-test.js +2 -0
  247. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +2 -0
  248. package/third-party/chromium-synchronization/installability-errors-test.js +2 -0
  249. package/tsconfig.json +0 -1
  250. package/types/artifacts.d.ts +8 -47
  251. package/types/config.d.ts +0 -2
  252. package/types/externs.d.ts +2 -3
  253. package/types/gatherer.d.ts +2 -1
  254. package/types/global-lh.d.ts +0 -3
  255. package/types/lhr/lhr.d.ts +2 -2
  256. package/types/lhr/treemap.d.ts +1 -0
  257. package/changelog.md +0 -5902
  258. package/lighthouse-core/audits/script-elements-test-audit.js +0 -30
  259. package/lighthouse-core/gather/gatherers/scripts.js +0 -154
  260. package/lighthouse-core/scripts/package.json +0 -4
  261. package/report/test/.eslintrc.cjs +0 -11
  262. package/shared/test/localization/.eslintrc.cjs +0 -11
@@ -6,6 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  const LHError = require('../../lib/lh-error.js');
9
+ const SessionEmitMonkeypatch = Symbol('monkeypatch');
9
10
 
10
11
  // Controls how long to wait for a response after sending a DevTools protocol command.
11
12
  const DEFAULT_PROTOCOL_TIMEOUT = 30000;
@@ -13,7 +14,7 @@ const DEFAULT_PROTOCOL_TIMEOUT = 30000;
13
14
  /** @implements {LH.Gatherer.FRProtocolSession} */
14
15
  class ProtocolSession {
15
16
  /**
16
- * @param {LH.Puppeteer.CDPSession} session
17
+ * @param {import('puppeteer').CDPSession} session
17
18
  */
18
19
  constructor(session) {
19
20
  this._session = session;
@@ -23,12 +24,22 @@ class ProtocolSession {
23
24
  this._nextProtocolTimeout = undefined;
24
25
  /** @type {WeakMap<any, any>} */
25
26
  this._callbackMap = new WeakMap();
26
- }
27
27
 
28
- sessionId() {
29
- return this._targetInfo && this._targetInfo.type === 'iframe' ?
30
- this._targetInfo.targetId :
31
- undefined;
28
+ // FIXME: Monkeypatch puppeteer to be able to listen to *all* protocol events.
29
+ // This patched method will now emit a copy of every event on `*`.
30
+ const originalEmit = session.emit;
31
+ // @ts-expect-error - Test for the monkeypatch.
32
+ if (originalEmit[SessionEmitMonkeypatch]) return;
33
+ session.emit = (method, ...args) => {
34
+ // OOPIF sessions need to emit their sessionId so downstream processors can recognize
35
+ // the target the event came from.
36
+ const sessionId = this._targetInfo && this._targetInfo.type === 'iframe' ?
37
+ this._targetInfo.targetId : undefined;
38
+ originalEmit.call(session, '*', {method, params: args[0], sessionId});
39
+ return originalEmit.call(session, method, ...args);
40
+ };
41
+ // @ts-expect-error - It's monkeypatching 🤷‍♂️.
42
+ session.emit[SessionEmitMonkeypatch] = true;
32
43
  }
33
44
 
34
45
  /** @param {LH.Crdp.Target.TargetInfo} targetInfo */
@@ -82,7 +93,7 @@ class ProtocolSession {
82
93
  * @param {(session: ProtocolSession) => void} callback
83
94
  */
84
95
  addSessionAttachedListener(callback) {
85
- /** @param {LH.Puppeteer.CDPSession} session */
96
+ /** @param {import('puppeteer').CDPSession} session */
86
97
  const listener = session => callback(new ProtocolSession(session));
87
98
  this._callbackMap.set(callback, listener);
88
99
  this._getConnection().on('sessionattached', listener);
@@ -99,31 +110,19 @@ class ProtocolSession {
99
110
  }
100
111
 
101
112
  /**
102
- * Bind to puppeteer's '*' event that fires for *any* protocol event,
103
- * and wrap it with data about the protocol message instead of just the event.
113
+ * Bind to our custom event that fires for *any* protocol event.
104
114
  * @param {(payload: LH.Protocol.RawEventMessage) => void} callback
105
115
  */
106
116
  addProtocolMessageListener(callback) {
107
- /**
108
- * @param {any} method
109
- * @param {any} event
110
- */
111
- const listener = (method, event) => callback({
112
- method,
113
- params: event,
114
- sessionId: this.sessionId(),
115
- });
116
- this._callbackMap.set(callback, listener);
117
- this._session.on('*', /** @type {*} */ (listener));
117
+ this._session.on('*', /** @type {*} */ (callback));
118
118
  }
119
119
 
120
120
  /**
121
+ * Unbind to our custom event that fires for *any* protocol event.
121
122
  * @param {(payload: LH.Protocol.RawEventMessage) => void} callback
122
123
  */
123
124
  removeProtocolMessageListener(callback) {
124
- const listener = this._callbackMap.get(callback);
125
- if (!listener) return;
126
- this._session.off('*', /** @type {*} */ (listener));
125
+ this._session.off('*', /** @type {*} */ (callback));
127
126
  }
128
127
 
129
128
  /**
@@ -17,7 +17,7 @@ const {initializeConfig} = require('../config/config.js');
17
17
  const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
18
18
 
19
19
  /**
20
- * @param {{page: LH.Puppeteer.Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
20
+ * @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
21
21
  * @return {Promise<LH.Gatherer.FRGatherResult>}
22
22
  */
23
23
  async function snapshotGather(options) {
@@ -36,14 +36,13 @@ async function snapshotGather(options) {
36
36
  const artifacts = await Runner.gather(
37
37
  async () => {
38
38
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'snapshot'});
39
- baseArtifacts.URL = {
40
- initialUrl: url,
41
- finalUrl: url,
42
- };
39
+ baseArtifacts.URL.requestedUrl = url;
40
+ baseArtifacts.URL.finalUrl = url;
43
41
 
44
42
  const artifactDefinitions = config.artifacts || [];
45
43
  const artifactState = getEmptyArtifactState();
46
44
  await collectPhaseArtifacts({
45
+ url,
47
46
  phase: 'getArtifact',
48
47
  gatherMode: 'snapshot',
49
48
  driver,
@@ -18,7 +18,7 @@ const {initializeConfig} = require('../config/config.js');
18
18
  const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
19
19
 
20
20
  /**
21
- * @param {{page: LH.Puppeteer.Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
21
+ * @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
22
22
  * @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
23
23
  */
24
24
  async function startTimespanGather(options) {
@@ -32,11 +32,12 @@ async function startTimespanGather(options) {
32
32
  /** @type {Map<string, LH.ArbitraryEqualityMap>} */
33
33
  const computedCache = new Map();
34
34
  const artifactDefinitions = config.artifacts || [];
35
- const initialUrl = await driver.url();
35
+ const requestedUrl = await driver.url();
36
36
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'timespan'});
37
37
  const artifactState = getEmptyArtifactState();
38
38
  /** @type {Omit<import('./runner-helpers.js').CollectPhaseArtifactOptions, 'phase'>} */
39
39
  const phaseOptions = {
40
+ url: requestedUrl,
40
41
  driver,
41
42
  artifactDefinitions,
42
43
  artifactState,
@@ -53,14 +54,13 @@ async function startTimespanGather(options) {
53
54
  return {
54
55
  async endTimespanGather() {
55
56
  const finalUrl = await driver.url();
57
+ phaseOptions.url = finalUrl;
56
58
 
57
59
  const runnerOptions = {config, computedCache};
58
60
  const artifacts = await Runner.gather(
59
61
  async () => {
60
- baseArtifacts.URL = {
61
- initialUrl,
62
- finalUrl,
63
- };
62
+ baseArtifacts.URL.requestedUrl = requestedUrl;
63
+ baseArtifacts.URL.finalUrl = finalUrl;
64
64
 
65
65
  await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseOptions});
66
66
  await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseOptions});
@@ -82,14 +82,12 @@ class CriConnection extends Connection {
82
82
  */
83
83
  _runJsonCommand(command) {
84
84
  return new Promise((resolve, reject) => {
85
- const request = http.request({
86
- method: 'PUT', // GET and POST are deprecated: https://crrev.com/c/3595822
85
+ const request = http.get({
87
86
  hostname: this.hostname,
88
87
  port: this.port,
89
88
  path: '/json/' + command,
90
89
  }, response => {
91
90
  let data = '';
92
- response.setEncoding('utf8');
93
91
  response.on('data', chunk => {
94
92
  data += chunk;
95
93
  });
@@ -110,8 +108,6 @@ class CriConnection extends Connection {
110
108
  });
111
109
  });
112
110
 
113
- request.end();
114
-
115
111
  // This error handler is critical to ensuring Lighthouse exits cleanly even when Chrome crashes.
116
112
  // See https://github.com/GoogleChrome/lighthouse/pull/8583.
117
113
  request.on('error', reject);
@@ -97,8 +97,7 @@ class ExecutionContext {
97
97
  .catch(${pageFunctions.wrapRuntimeEvalErrorInBrowserString})
98
98
  .then(resolve);
99
99
  });
100
- }())
101
- //# sourceURL=_lighthouse-eval.js`,
100
+ }())`,
102
101
  includeCommandLineAPI: true,
103
102
  awaitPromise: true,
104
103
  returnByValue: true,
@@ -196,8 +195,7 @@ class ExecutionContext {
196
195
  ${ExecutionContext._cachedNativesPreamble};
197
196
  ${depsSerialized};
198
197
  (${mainFn})(${argsSerialized});
199
- })()
200
- //# sourceURL=_lighthouse-eval.js`;
198
+ })()`;
201
199
 
202
200
  await this._session.sendCommand('Page.addScriptToEvaluateOnNewDocument', {source: expression});
203
201
  }
@@ -215,7 +213,6 @@ class ExecutionContext {
215
213
  window.__nativePerformance = window.performance;
216
214
  window.__nativeFetch = window.fetch;
217
215
  window.__ElementMatches = window.Element.prototype.matches;
218
- window.__HTMLElementBoundingClientRect = window.HTMLElement.prototype.getBoundingClientRect;
219
216
  // Ensure the native `performance.now` is not overwritable.
220
217
  const performance = window.performance;
221
218
  const performanceNow = window.performance.now;
@@ -80,7 +80,7 @@ function resolveWaitForFullyLoadedOptions(options) {
80
80
  * @param {LH.Gatherer.FRTransitionalDriver} driver
81
81
  * @param {LH.NavigationRequestor} requestor
82
82
  * @param {NavigationOptions} options
83
- * @return {Promise<{requestedUrl: string, mainDocumentUrl: string, warnings: Array<LH.IcuMessage>}>}
83
+ * @return {Promise<{requestedUrl: string, finalUrl: string, warnings: Array<LH.IcuMessage>}>}
84
84
  */
85
85
  async function gotoURL(driver, requestor, options) {
86
86
  const status = typeof requestor === 'string' ?
@@ -130,17 +130,14 @@ async function gotoURL(driver, requestor, options) {
130
130
 
131
131
  let requestedUrl = navigationUrls.requestedUrl;
132
132
  if (typeof requestor === 'string') {
133
- if (requestedUrl && !URL.equalWithExcludedFragments(requestor, requestedUrl)) {
134
- log.error(
135
- 'Navigation',
136
- `Provided URL (${requestor}) did not match initial navigation URL (${requestedUrl})`
137
- );
133
+ if (requestor !== requestedUrl) {
134
+ log.error('Navigation', 'Provided URL did not match initial navigation URL');
138
135
  }
139
136
  requestedUrl = requestor;
140
137
  }
141
138
  if (!requestedUrl) throw Error('No navigations detected when running user defined requestor.');
142
139
 
143
- const mainDocumentUrl = navigationUrls.mainDocumentUrl || requestedUrl;
140
+ const finalUrl = navigationUrls.finalUrl || requestedUrl;
144
141
 
145
142
  // Bring `Page.navigate` errors back into the promise chain. See https://github.com/GoogleChrome/lighthouse/pull/6739.
146
143
  await waitForNavigationTriggered;
@@ -153,26 +150,26 @@ async function gotoURL(driver, requestor, options) {
153
150
  log.timeEnd(status);
154
151
  return {
155
152
  requestedUrl,
156
- mainDocumentUrl,
157
- warnings: getNavigationWarnings({timedOut, mainDocumentUrl, requestedUrl}),
153
+ finalUrl,
154
+ warnings: getNavigationWarnings({timedOut, finalUrl, requestedUrl}),
158
155
  };
159
156
  }
160
157
 
161
158
  /**
162
- * @param {{timedOut: boolean, requestedUrl: string, mainDocumentUrl: string; }} navigation
159
+ * @param {{timedOut: boolean, requestedUrl: string, finalUrl: string; }} navigation
163
160
  * @return {Array<LH.IcuMessage>}
164
161
  */
165
162
  function getNavigationWarnings(navigation) {
166
- const {requestedUrl, mainDocumentUrl} = navigation;
163
+ const {requestedUrl, finalUrl} = navigation;
167
164
  /** @type {Array<LH.IcuMessage>} */
168
165
  const warnings = [];
169
166
 
170
167
  if (navigation.timedOut) warnings.push(str_(UIStrings.warningTimeout));
171
168
 
172
- if (!URL.equalWithExcludedFragments(requestedUrl, mainDocumentUrl)) {
169
+ if (!URL.equalWithExcludedFragments(requestedUrl, finalUrl)) {
173
170
  warnings.push(str_(UIStrings.warningRedirected, {
174
171
  requested: requestedUrl,
175
- final: mainDocumentUrl,
172
+ final: finalUrl,
176
173
  }));
177
174
  }
178
175
 
@@ -133,7 +133,7 @@ class NetworkMonitor {
133
133
  this._sessions = new Map();
134
134
  }
135
135
 
136
- /** @return {Promise<{requestedUrl?: string, mainDocumentUrl?: string}>} */
136
+ /** @return {Promise<{requestedUrl?: string, finalUrl?: string}>} */
137
137
  async getNavigationUrls() {
138
138
  const frameNavigations = this._frameNavigations;
139
139
  if (!frameNavigations.length) return {};
@@ -143,22 +143,9 @@ class NetworkMonitor {
143
143
  const mainFrameNavigations = frameNavigations.filter(frame => frame.id === mainFrameId);
144
144
  if (!mainFrameNavigations.length) log.warn('NetworkMonitor', 'No detected navigations');
145
145
 
146
- // The requested URL is the initiator request for the first frame navigation.
147
- /** @type {string|undefined} */
148
- let requestedUrl = mainFrameNavigations[0]?.url;
149
- if (this._networkRecorder) {
150
- const records = this._networkRecorder.getRawRecords();
151
-
152
- let initialUrlRequest = records.find(record => record.url === requestedUrl);
153
- while (initialUrlRequest?.redirectSource) {
154
- initialUrlRequest = initialUrlRequest.redirectSource;
155
- requestedUrl = initialUrlRequest.url;
156
- }
157
- }
158
-
159
146
  return {
160
- requestedUrl,
161
- mainDocumentUrl: mainFrameNavigations[mainFrameNavigations.length - 1]?.url,
147
+ requestedUrl: mainFrameNavigations[0]?.url,
148
+ finalUrl: mainFrameNavigations[mainFrameNavigations.length - 1]?.url,
162
149
  };
163
150
  }
164
151
 
@@ -64,7 +64,7 @@ class Driver {
64
64
  defaultSession = this;
65
65
 
66
66
  // eslint-disable-next-line no-invalid-this
67
- fetcher = new Fetcher(this.defaultSession);
67
+ fetcher = new Fetcher(this.defaultSession, this.executionContext);
68
68
 
69
69
  /**
70
70
  * @param {Connection} connection
@@ -477,11 +477,6 @@ class Driver {
477
477
  this._devtoolsLog.endRecording();
478
478
  return this._devtoolsLog.messages;
479
479
  }
480
-
481
- async url() {
482
- const {frameTree} = await this.sendCommand('Page.getFrameTree');
483
- return `${frameTree.frame.url}${frameTree.frame.urlFragment || ''}`;
484
- }
485
480
  }
486
481
 
487
482
  module.exports = Driver;
@@ -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