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
@@ -8,9 +8,10 @@
8
8
  const LinkHeader = require('http-link-header');
9
9
  const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
10
10
  const {URL} = require('../../lib/url-shim.js');
11
+ const NetworkRecords = require('../../computed/network-records.js');
12
+ const NetworkAnalyzer = require('../../lib/dependency-graph/simulator/network-analyzer.js');
11
13
  const pageFunctions = require('../../lib/page-functions.js');
12
14
  const DevtoolsLog = require('./devtools-log.js');
13
- const MainResource = require('../../computed/main-resource.js');
14
15
 
15
16
  /* globals HTMLLinkElement getNodeDetails */
16
17
 
@@ -113,12 +114,12 @@ class LinkElements extends FRGatherer {
113
114
 
114
115
  /**
115
116
  * @param {LH.Gatherer.FRTransitionalContext} context
116
- * @param {LH.Artifacts['DevtoolsLog']} devtoolsLog
117
- * @return {Promise<LH.Artifacts['LinkElements']>}
117
+ * @param {LH.Artifacts.NetworkRequest[]} networkRecords
118
+ * @return {LH.Artifacts['LinkElements']}
118
119
  */
119
- static async getLinkElementsInHeaders(context, devtoolsLog) {
120
- const mainDocument =
121
- await MainResource.request({devtoolsLog, URL: context.baseArtifacts.URL}, context);
120
+ static getLinkElementsInHeaders(context, networkRecords) {
121
+ const finalUrl = context.url;
122
+ const mainDocument = NetworkAnalyzer.findMainDocument(networkRecords, finalUrl);
122
123
 
123
124
  /** @type {LH.Artifacts['LinkElements']} */
124
125
  const linkElements = [];
@@ -129,7 +130,7 @@ class LinkElements extends FRGatherer {
129
130
  for (const link of LinkHeader.parse(header.value).refs) {
130
131
  linkElements.push({
131
132
  rel: link.rel || '',
132
- href: normalizeUrlOrNull(link.uri, context.baseArtifacts.URL.finalUrl),
133
+ href: normalizeUrlOrNull(link.uri, finalUrl),
133
134
  hrefRaw: link.uri || '',
134
135
  hreflang: link.hreflang || '',
135
136
  as: link.as || '',
@@ -145,12 +146,12 @@ class LinkElements extends FRGatherer {
145
146
 
146
147
  /**
147
148
  * @param {LH.Gatherer.FRTransitionalContext} context
148
- * @param {LH.Artifacts['DevtoolsLog']} devtoolsLog
149
+ * @param {LH.Artifacts.NetworkRequest[]} networkRecords
149
150
  * @return {Promise<LH.Artifacts['LinkElements']>}
150
151
  */
151
- async _getArtifact(context, devtoolsLog) {
152
+ async _getArtifact(context, networkRecords) {
152
153
  const fromDOM = await LinkElements.getLinkElementsInDOM(context);
153
- const fromHeaders = await LinkElements.getLinkElementsInHeaders(context, devtoolsLog);
154
+ const fromHeaders = LinkElements.getLinkElementsInHeaders(context, networkRecords);
154
155
  const linkElements = fromDOM.concat(fromHeaders);
155
156
 
156
157
  for (const link of linkElements) {
@@ -167,7 +168,7 @@ class LinkElements extends FRGatherer {
167
168
  * @return {Promise<LH.Artifacts['LinkElements']>}
168
169
  */
169
170
  async afterPass(context, loadData) {
170
- return this._getArtifact({...context, dependencies: {}}, loadData.devtoolsLog);
171
+ return this._getArtifact({...context, dependencies: {}}, loadData.networkRecords);
171
172
  }
172
173
 
173
174
  /**
@@ -175,7 +176,8 @@ class LinkElements extends FRGatherer {
175
176
  * @return {Promise<LH.Artifacts['LinkElements']>}
176
177
  */
177
178
  async getArtifact(context) {
178
- return this._getArtifact(context, context.dependencies.DevtoolsLog);
179
+ const records = await NetworkRecords.request(context.dependencies.DevtoolsLog, context);
180
+ return this._getArtifact(context, records);
179
181
  }
180
182
  }
181
183
 
@@ -6,9 +6,10 @@
6
6
  'use strict';
7
7
 
8
8
  const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
9
+ const NetworkAnalyzer = require('../../lib/dependency-graph/simulator/network-analyzer.js');
10
+ const NetworkRecords = require('../../computed/network-records.js');
9
11
  const DevtoolsLog = require('./devtools-log.js');
10
12
  const {fetchResponseBodyFromCache} = require('../driver/network.js');
11
- const MainResource = require('../../computed/main-resource.js');
12
13
 
13
14
  /**
14
15
  * Collects the content of the main html document.
@@ -23,16 +24,14 @@ class MainDocumentContent extends FRGatherer {
23
24
  /**
24
25
  *
25
26
  * @param {LH.Gatherer.FRTransitionalContext} context
26
- * @param {LH.Artifacts['DevtoolsLog']} devtoolsLog
27
+ * @param {LH.Artifacts.NetworkRequest[]} networkRecords
27
28
  * @return {Promise<LH.Artifacts['MainDocumentContent']>}
28
29
  */
29
- async _getArtifact(context, devtoolsLog) {
30
- const mainResource =
31
- await MainResource.request({devtoolsLog, URL: context.baseArtifacts.URL}, context);
30
+ async _getArtifact(context, networkRecords) {
31
+ const mainResource = NetworkAnalyzer.findMainDocument(networkRecords, context.url);
32
32
  const session = context.driver.defaultSession;
33
33
  return fetchResponseBodyFromCache(session, mainResource.requestId);
34
34
  }
35
-
36
35
  /**
37
36
  *
38
37
  * @param {LH.Gatherer.FRTransitionalContext<'DevtoolsLog'>} context
@@ -40,7 +39,8 @@ class MainDocumentContent extends FRGatherer {
40
39
  */
41
40
  async getArtifact(context) {
42
41
  const devtoolsLog = context.dependencies.DevtoolsLog;
43
- return this._getArtifact(context, devtoolsLog);
42
+ const networkRecords = await NetworkRecords.request(devtoolsLog, context);
43
+ return this._getArtifact(context, networkRecords);
44
44
  }
45
45
 
46
46
  /**
@@ -49,7 +49,7 @@ class MainDocumentContent extends FRGatherer {
49
49
  * @return {Promise<LH.Artifacts['MainDocumentContent']>}
50
50
  */
51
51
  async afterPass(passContext, loadData) {
52
- return this._getArtifact({...passContext, dependencies: {}}, loadData.devtoolsLog);
52
+ return this._getArtifact({...passContext, dependencies: {}}, loadData.networkRecords);
53
53
  }
54
54
  }
55
55
 
@@ -7,8 +7,10 @@
7
7
 
8
8
  const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
9
9
  const NetworkRecords = require('../../computed/network-records.js');
10
+ const NetworkAnalyzer = require('../../lib/dependency-graph/simulator/network-analyzer.js');
10
11
  const NetworkRequest = require('../../lib/network-request.js');
11
12
  const pageFunctions = require('../../lib/page-functions.js');
13
+ const {fetchResponseBodyFromCache} = require('../driver/network.js');
12
14
  const DevtoolsLog = require('./devtools-log.js');
13
15
 
14
16
  /* global getNodeDetails */
@@ -30,6 +32,8 @@ function collectAllScriptElements() {
30
32
  async: script.async,
31
33
  defer: script.defer,
32
34
  source: script.closest('head') ? 'head' : 'body',
35
+ content: script.src ? null : script.text,
36
+ requestId: null,
33
37
  // @ts-expect-error - getNodeDetails put into scope via stringification
34
38
  node: getNodeDetails(script),
35
39
  };
@@ -37,6 +41,27 @@ function collectAllScriptElements() {
37
41
  }
38
42
  /* c8 ignore stop */
39
43
 
44
+ /**
45
+ * @template T, U
46
+ * @param {Array<T>} values
47
+ * @param {(value: T) => Promise<U>} promiseMapper
48
+ * @param {boolean} runInSeries
49
+ * @return {Promise<Array<U>>}
50
+ */
51
+ async function runInSeriesOrParallel(values, promiseMapper, runInSeries) {
52
+ if (runInSeries) {
53
+ const results = [];
54
+ for (const value of values) {
55
+ const result = await promiseMapper(value);
56
+ results.push(result);
57
+ }
58
+ return results;
59
+ } else {
60
+ const promises = values.map(promiseMapper);
61
+ return await Promise.all(promises);
62
+ }
63
+ }
64
+
40
65
  /**
41
66
  * @fileoverview Gets JavaScript file contents.
42
67
  */
@@ -50,10 +75,13 @@ class ScriptElements extends FRGatherer {
50
75
  /**
51
76
  * @param {LH.Gatherer.FRTransitionalContext} context
52
77
  * @param {LH.Artifacts.NetworkRequest[]} networkRecords
78
+ * @param {LH.Artifacts['HostFormFactor']} formFactor
53
79
  * @return {Promise<LH.Artifacts['ScriptElements']>}
54
80
  */
55
- async _getArtifact(context, networkRecords) {
81
+ async _getArtifact(context, networkRecords, formFactor) {
82
+ const session = context.driver.defaultSession;
56
83
  const executionContext = context.driver.executionContext;
84
+ const mainResource = NetworkAnalyzer.findOptionalMainDocument(networkRecords, context.url);
57
85
 
58
86
  const scripts = await executionContext.evaluate(collectAllScriptElements, {
59
87
  args: [],
@@ -64,16 +92,34 @@ class ScriptElements extends FRGatherer {
64
92
  ],
65
93
  });
66
94
 
95
+ for (const script of scripts) {
96
+ if (mainResource && script.content) script.requestId = mainResource.requestId;
97
+ }
98
+
67
99
  const scriptRecords = networkRecords
68
- .filter(record => record.resourceType === NetworkRequest.TYPES.Script)
69
100
  // Ignore records from OOPIFs
70
- .filter(record => !record.sessionId);
101
+ .filter(record => !record.sessionId)
102
+ // Only get the content of script requests
103
+ .filter(record => record.resourceType === NetworkRequest.TYPES.Script);
104
+
105
+ // If run on a mobile device, be sensitive to memory limitations and only request one
106
+ // record at a time.
107
+ const scriptRecordContents = await runInSeriesOrParallel(
108
+ scriptRecords,
109
+ record => fetchResponseBodyFromCache(session, record.requestId).catch(() => ''),
110
+ formFactor === 'mobile' /* runInSeries */
111
+ );
71
112
 
72
113
  for (let i = 0; i < scriptRecords.length; i++) {
73
114
  const record = scriptRecords[i];
115
+ const content = scriptRecordContents[i];
116
+ if (!content) continue;
74
117
 
75
118
  const matchedScriptElement = scripts.find(script => script.src === record.url);
76
- if (!matchedScriptElement) {
119
+ if (matchedScriptElement) {
120
+ matchedScriptElement.requestId = record.requestId;
121
+ matchedScriptElement.content = content;
122
+ } else {
77
123
  scripts.push({
78
124
  type: null,
79
125
  src: record.url,
@@ -81,11 +127,12 @@ class ScriptElements extends FRGatherer {
81
127
  async: false,
82
128
  defer: false,
83
129
  source: 'network',
130
+ requestId: record.requestId,
131
+ content,
84
132
  node: null,
85
133
  });
86
134
  }
87
135
  }
88
-
89
136
  return scripts;
90
137
  }
91
138
 
@@ -94,8 +141,9 @@ class ScriptElements extends FRGatherer {
94
141
  */
95
142
  async getArtifact(context) {
96
143
  const devtoolsLog = context.dependencies.DevtoolsLog;
144
+ const formFactor = context.baseArtifacts.HostFormFactor;
97
145
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
98
- return this._getArtifact(context, networkRecords);
146
+ return this._getArtifact(context, networkRecords, formFactor);
99
147
  }
100
148
 
101
149
  /**
@@ -104,7 +152,8 @@ class ScriptElements extends FRGatherer {
104
152
  */
105
153
  async afterPass(passContext, loadData) {
106
154
  const networkRecords = loadData.networkRecords;
107
- return this._getArtifact({...passContext, dependencies: {}}, networkRecords);
155
+ const formFactor = passContext.baseArtifacts.HostFormFactor;
156
+ return this._getArtifact({...passContext, dependencies: {}}, networkRecords, formFactor);
108
157
  }
109
158
  }
110
159
 
@@ -7,6 +7,25 @@
7
7
 
8
8
  const FRGatherer = require('../../../fraggle-rock/gather/base-gatherer.js');
9
9
 
10
+ /* global fetch, location */
11
+
12
+ /** @return {Promise<LH.Artifacts['RobotsTxt']>} */
13
+ /* c8 ignore start */
14
+ async function getRobotsTxtContent() {
15
+ try {
16
+ const response = await fetch(new URL('/robots.txt', location.href).href);
17
+ if (!response.ok) {
18
+ return {status: response.status, content: null};
19
+ }
20
+
21
+ const content = await response.text();
22
+ return {status: response.status, content};
23
+ } catch (err) {
24
+ return {status: null, content: null, errorMessage: err.message};
25
+ }
26
+ }
27
+ /* c8 ignore stop */
28
+
10
29
  class RobotsTxt extends FRGatherer {
11
30
  /** @type {LH.Gatherer.GathererMeta} */
12
31
  meta = {
@@ -18,8 +37,17 @@ class RobotsTxt extends FRGatherer {
18
37
  * @return {Promise<LH.Artifacts['RobotsTxt']>}
19
38
  */
20
39
  async getArtifact(passContext) {
21
- const {finalUrl} = passContext.baseArtifacts.URL;
22
- const robotsUrl = new URL('/robots.txt', finalUrl).href;
40
+ // Iframe fetcher still has issues with CSPs.
41
+ // Only use the fetcher if we are fetching over the protocol.
42
+ if (await passContext.driver.fetcher.shouldUseLegacyFetcher()) {
43
+ return passContext.driver.executionContext.evaluate(getRobotsTxtContent, {
44
+ args: [],
45
+ useIsolation: true,
46
+ });
47
+ }
48
+
49
+ const robotsUrl = new URL('/robots.txt', passContext.url).href;
50
+ await passContext.driver.fetcher.enable();
23
51
  return passContext.driver.fetcher.fetchResource(robotsUrl)
24
52
  .catch(err => ({status: null, content: null, errorMessage: err.message}));
25
53
  }
@@ -88,7 +88,6 @@ class SourceMaps extends FRGatherer {
88
88
 
89
89
  if (!rawSourceMapUrl) {
90
90
  return {
91
- scriptId: event.scriptId,
92
91
  scriptUrl,
93
92
  errorMessage: `Could not resolve map url: ${event.sourceMapURL}`,
94
93
  };
@@ -105,14 +104,12 @@ class SourceMaps extends FRGatherer {
105
104
  map.sections = map.sections.filter(section => section.map);
106
105
  }
107
106
  return {
108
- scriptId: event.scriptId,
109
107
  scriptUrl,
110
108
  sourceMapUrl,
111
109
  map,
112
110
  };
113
111
  } catch (err) {
114
112
  return {
115
- scriptId: event.scriptId,
116
113
  scriptUrl,
117
114
  sourceMapUrl,
118
115
  errorMessage: err.toString(),
@@ -143,6 +140,7 @@ class SourceMaps extends FRGatherer {
143
140
  * @return {Promise<LH.Artifacts['SourceMaps']>}
144
141
  */
145
142
  async getArtifact(context) {
143
+ await context.driver.fetcher.enable();
146
144
  const eventProcessPromises = this._scriptParsedEvents
147
145
  .map((event) => this._retrieveMapFromScriptParsedEvent(context.driver, event));
148
146
  return Promise.all(eventProcessPromises);
@@ -22,6 +22,7 @@ const Trace = require('./trace.js');
22
22
  const ProcessedTrace = require('../../computed/processed-trace.js');
23
23
  const ProcessedNavigation = require('../../computed/processed-navigation.js');
24
24
  const LighthouseError = require('../../lib/lh-error.js');
25
+ const ComputedResponsivenes = require('../../computed/metrics/responsiveness.js');
25
26
 
26
27
  /** @typedef {{nodeId: number, score?: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[]}} TraceElementData */
27
28
 
@@ -142,6 +143,23 @@ class TraceElements extends FRGatherer {
142
143
  return topFive;
143
144
  }
144
145
 
146
+ /**
147
+ * @param {LH.Trace} trace
148
+ * @param {LH.Gatherer.FRTransitionalContext} context
149
+ * @return {Promise<TraceElementData|undefined>}
150
+ */
151
+ static async getResponsivenessElement(trace, context) {
152
+ const {settings} = context;
153
+ try {
154
+ const responsivenessEvent = await ComputedResponsivenes.request({trace, settings}, context);
155
+ if (!responsivenessEvent || responsivenessEvent.name === 'FallbackTiming') return;
156
+ return {nodeId: responsivenessEvent.args.data.nodeId};
157
+ } catch {
158
+ // Don't let responsiveness errors sink the rest of the gatherer.
159
+ return;
160
+ }
161
+ }
162
+
145
163
  /**
146
164
  * Find the node ids of elements which are animated using the Animation trace events.
147
165
  * @param {Array<LH.TraceEvent>} mainThreadEvents
@@ -237,14 +255,15 @@ class TraceElements extends FRGatherer {
237
255
 
238
256
  const lcpNodeId = largestContentfulPaintEvt?.args?.data?.nodeId;
239
257
  const clsNodeData = TraceElements.getTopLayoutShiftElements(mainThreadEvents);
240
- const animatedElementData =
241
- await this.getAnimatedElements(mainThreadEvents);
258
+ const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
259
+ const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
242
260
 
243
261
  /** @type {Map<string, TraceElementData[]>} */
244
262
  const backendNodeDataMap = new Map([
245
263
  ['largest-contentful-paint', lcpNodeId ? [{nodeId: lcpNodeId}] : []],
246
264
  ['layout-shift', clsNodeData],
247
265
  ['animation', animatedElementData],
266
+ ['responsiveness', responsivenessElementData ? [responsivenessElementData] : []],
248
267
  ]);
249
268
 
250
269
  const traceElements = [];
@@ -94,8 +94,8 @@ class WebAppManifest extends FRGatherer {
94
94
  */
95
95
  getArtifact(context) {
96
96
  const driver = context.driver;
97
- const {finalUrl} = context.baseArtifacts.URL;
98
- return WebAppManifest.getWebAppManifest(driver.defaultSession, finalUrl);
97
+
98
+ return WebAppManifest.getWebAppManifest(driver.defaultSession, context.url);
99
99
  }
100
100
  }
101
101
 
@@ -10,7 +10,6 @@ const log = require('lighthouse-logger');
10
10
  const ChromeProtocol = require('./gather/connections/cri.js');
11
11
  const Config = require('./config/config.js');
12
12
  const URL = require('./lib/url-shim.js');
13
- const fraggleRock = require('./fraggle-rock/api.js');
14
13
 
15
14
  /** @typedef {import('./gather/connections/connection.js')} Connection */
16
15
 
@@ -34,32 +33,10 @@ const fraggleRock = require('./fraggle-rock/api.js');
34
33
  * they will override any settings in the config.
35
34
  * @param {LH.Config.Json=} configJSON Configuration for the Lighthouse run. If
36
35
  * not present, the default config is used.
37
- * @param {LH.Puppeteer.Page=} page
38
- * @return {Promise<LH.RunnerResult|undefined>}
39
- */
40
- async function lighthouse(url, flags = {}, configJSON, page) {
41
- const configContext = {
42
- configPath: flags.configPath,
43
- settingsOverrides: flags,
44
- logLevel: flags.logLevel,
45
- hostname: flags.hostname,
46
- port: flags.port,
47
- };
48
- return fraggleRock.navigation(url, {page, config: configJSON, configContext});
49
- }
50
-
51
- /**
52
- * Run Lighthouse using the legacy navigation runner.
53
- * This is left in place for any clients that don't support FR navigations yet (e.g. Lightrider)
54
- * @param {string=} url The URL to test. Optional if running in auditMode.
55
- * @param {LH.Flags=} flags Optional settings for the Lighthouse run. If present,
56
- * they will override any settings in the config.
57
- * @param {LH.Config.Json=} configJSON Configuration for the Lighthouse run. If
58
- * not present, the default config is used.
59
36
  * @param {Connection=} userConnection
60
37
  * @return {Promise<LH.RunnerResult|undefined>}
61
38
  */
62
- async function legacyNavigation(url, flags = {}, configJSON, userConnection) {
39
+ async function lighthouse(url, flags = {}, configJSON, userConnection) {
63
40
  // set logging preferences, assume quiet
64
41
  flags.logLevel = flags.logLevel || 'error';
65
42
  log.setLevel(flags.logLevel);
@@ -89,12 +66,11 @@ function generateConfig(configJson, flags) {
89
66
  return new Config(configJson, flags);
90
67
  }
91
68
 
92
- lighthouse.legacyNavigation = legacyNavigation;
93
69
  lighthouse.generateConfig = generateConfig;
94
70
  lighthouse.getAuditList = Runner.getAuditList;
95
71
  lighthouse.traceCategories = require('./gather/driver.js').traceCategories;
96
72
  lighthouse.Audit = require('./audits/audit.js');
97
- lighthouse.Gatherer = require('./fraggle-rock/gather/base-gatherer.js');
73
+ lighthouse.Gatherer = require('./gather/gatherers/gatherer.js');
98
74
 
99
75
  // Explicit type reference (hidden by makeComputedArtifact) for d.ts export.
100
76
  // TODO(esmodules): should be a workaround for module.export and can be removed when in esm.
@@ -149,7 +149,7 @@ class TextSourceMap {
149
149
  }
150
150
  return mappings.slice(startIndex, endIndex);
151
151
  }
152
- /** @return {Array<{lineNumber: number, columnNumber: number, sourceURL?: string, sourceLineNumber: number, sourceColumnNumber: number, name?: string, lastColumnNumber?: number}>} */
152
+ /** @return {Array<{lineNumber: number, columnNumber: number, sourceURL?: string, sourceLineNumber, sourceColumnNumber: number, name?: string, lastColumnNumber?: number}>} */
153
153
  mappings() {
154
154
  if (this.mappingsInternal === null) {
155
155
  this.mappingsInternal = [];
@@ -6,6 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  const INITIAL_CWD = 14 * 1024;
9
+ const NetworkRequest = require('../../network-request.js');
9
10
  const URL = require('../../url-shim.js');
10
11
 
11
12
  // Assume that 40% of TTFB was server response time by default for static assets
@@ -433,20 +434,40 @@ class NetworkAnalyzer {
433
434
 
434
435
  /**
435
436
  * @param {Array<LH.Artifacts.NetworkRequest>} records
436
- * @param {string} resourceUrl
437
+ * @param {string} [finalURL]
438
+ * @return {LH.Artifacts.NetworkRequest}
439
+ */
440
+ static findMainDocument(records, finalURL) {
441
+ const mainDocument = NetworkAnalyzer.findOptionalMainDocument(records, finalURL);
442
+ if (!mainDocument) throw new Error('Unable to identify the main resource');
443
+ return mainDocument;
444
+ }
445
+
446
+ /**
447
+ * @param {Array<LH.Artifacts.NetworkRequest>} records
448
+ * @param {string} [finalURL]
437
449
  * @return {LH.Artifacts.NetworkRequest|undefined}
438
450
  */
439
- static findResourceForUrl(records, resourceUrl) {
440
- // equalWithExcludedFragments is expensive, so check that the resourceUrl starts with the request url first
441
- return records.find(request =>
442
- resourceUrl.startsWith(request.url) &&
443
- URL.equalWithExcludedFragments(request.url, resourceUrl)
444
- );
451
+ static findOptionalMainDocument(records, finalURL) {
452
+ // Try to find an exact match with the final URL first if we have one
453
+ if (finalURL) {
454
+ // equalWithExcludedFragments is expensive, so check that the finalUrl starts with the request first
455
+ const mainResource = records.find(request => finalURL.startsWith(request.url) &&
456
+ URL.equalWithExcludedFragments(request.url, finalURL));
457
+ if (mainResource) return mainResource;
458
+ // TODO: beacon !mainResource to Sentry, https://github.com/GoogleChrome/lighthouse/issues/7041
459
+ }
460
+
461
+ const documentRequests = records.filter(record => record.resourceType ===
462
+ NetworkRequest.TYPES.Document);
463
+ if (!documentRequests.length) return undefined;
464
+ // The main document is the earliest document request, using position in networkRecords array to break ties.
465
+ return documentRequests.reduce((min, r) => (r.startTime < min.startTime ? r : min));
445
466
  }
446
467
 
447
468
  /**
448
469
  * Resolves redirect chain given a main document.
449
- * See: {@link NetworkAnalyzer.findResourceForUrl}) for how to retrieve main document.
470
+ * See: {@link NetworkAnalyzer.findMainDocument}) for how to retrieve main document.
450
471
  *
451
472
  * @param {LH.Artifacts.NetworkRequest} request
452
473
  * @return {LH.Artifacts.NetworkRequest}
@@ -7,7 +7,6 @@
7
7
 
8
8
  const LHError = require('./lh-error.js');
9
9
  const NetworkAnalyzer = require('./dependency-graph/simulator/network-analyzer.js');
10
- const NetworkRequest = require('./network-request.js');
11
10
 
12
11
  /**
13
12
  * Returns an error if the original network request failed or wasn't found.
@@ -101,18 +100,10 @@ function getNonHtmlError(finalRecord) {
101
100
  function getPageLoadError(navigationError, context) {
102
101
  const {url, loadFailureMode, networkRecords} = context;
103
102
  /** @type {LH.Artifacts.NetworkRequest|undefined} */
104
- let mainRecord = NetworkAnalyzer.findResourceForUrl(networkRecords, url);
105
-
106
- // If the url doesn't give us a network request, it's possible we landed on a chrome-error:// page
107
- // In this case, just get the first document request.
108
- if (!mainRecord) {
109
- const documentRequests = networkRecords.filter(record =>
110
- record.resourceType === NetworkRequest.TYPES.Document
111
- );
112
- if (documentRequests.length) {
113
- mainRecord = documentRequests.reduce((min, r) => (r.startTime < min.startTime ? r : min));
114
- }
115
- }
103
+ let mainRecord;
104
+ try {
105
+ mainRecord = NetworkAnalyzer.findMainDocument(networkRecords, url);
106
+ } catch (_) {}
116
107
 
117
108
  // MIME Type is only set on the final redirected document request. Use this for the HTML check instead of root.
118
109
  let finalRecord;
@@ -5,25 +5,20 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- /**
9
- * @param {LH.Artifacts.Script} script
10
- * @return {boolean}
11
- */
12
- function isInline(script) {
13
- return Boolean(script.startLine || script.startColumn);
14
- }
15
-
16
8
  /**
17
9
  * @param {LH.Artifacts.NetworkRequest[]} networkRecords
18
- * @param {LH.Artifacts.Script} script
10
+ * @param {LH.Artifacts.ScriptElement|undefined} script
19
11
  * @return {LH.Artifacts.NetworkRequest|undefined}
20
12
  */
21
13
  function getRequestForScript(networkRecords, script) {
22
- let networkRequest = networkRecords.find(request => request.url === script.url);
14
+ if (!script) return;
15
+ let networkRequest = script.requestId &&
16
+ networkRecords.find(request => request.requestId === script.requestId);
17
+ if (!networkRequest) networkRequest = networkRecords.find(request => request.url === script.src);
23
18
  while (networkRequest?.redirectDestination) {
24
19
  networkRequest = networkRequest.redirectDestination;
25
20
  }
26
21
  return networkRequest;
27
22
  }
28
23
 
29
- module.exports = {getRequestForScript, isInline};
24
+ module.exports = {getRequestForScript};
@@ -236,9 +236,9 @@ class URLShim extends URL {
236
236
  }
237
237
 
238
238
  if (url.protocol === 'data:') {
239
- const match = url.pathname.match(/^(image\/(png|jpeg|svg\+xml|webp|gif|avif))[;,]/);
239
+ const match = url.pathname.match(/image\/(png|jpeg|svg\+xml|webp|gif|avif)(?=;)/);
240
240
  if (!match) return undefined;
241
- return match[1];
241
+ return match[0];
242
242
  }
243
243
 
244
244
  const match = url.pathname.toLowerCase().match(/\.(png|jpeg|jpg|svg|webp|gif|avif)$/);
@@ -158,6 +158,11 @@ class Runner {
158
158
  // No browser required, just load the artifacts from disk.
159
159
  const path = this._getDataSavePath(settings);
160
160
  artifacts = assetSaver.loadArtifacts(path);
161
+ const requestedUrl = artifacts.URL.requestedUrl;
162
+
163
+ if (!requestedUrl) {
164
+ throw new Error('Cannot run audit mode on empty URL');
165
+ }
161
166
  } else {
162
167
  const runnerStatus = {msg: 'Gather phase', id: 'lh:runner:gather'};
163
168
  log.time(runnerStatus, 'verbose');
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ 'use strict';
3
4
 
4
5
  /**
5
6
  * @fileoverview Script to launch a clean Chrome instance on-demand.