lighthouse 8.5.1 → 8.6.0-dev.20211016

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 (173) hide show
  1. package/changelog.md +5490 -0
  2. package/dist/report/flow.js +35 -12
  3. package/dist/report/standalone.js +20 -21
  4. package/flow-report/.eslintrc.cjs +49 -0
  5. package/flow-report/assets/styles.css +272 -9
  6. package/flow-report/jest.config.js +1 -1
  7. package/flow-report/package.json +4 -0
  8. package/flow-report/src/app.tsx +31 -9
  9. package/flow-report/src/common.tsx +86 -7
  10. package/flow-report/src/header.tsx +88 -0
  11. package/flow-report/src/help-dialog.tsx +120 -0
  12. package/flow-report/src/i18n/i18n.tsx +52 -0
  13. package/flow-report/src/i18n/localized-strings.js +11 -0
  14. package/flow-report/src/i18n/ui-strings.js +134 -0
  15. package/flow-report/src/icons.tsx +20 -3
  16. package/flow-report/src/sidebar/sidebar.tsx +11 -16
  17. package/flow-report/src/summary/category.tsx +39 -24
  18. package/flow-report/src/summary/summary.tsx +35 -32
  19. package/flow-report/src/topbar.tsx +54 -4
  20. package/flow-report/src/util.ts +26 -1
  21. package/flow-report/src/wrappers/report-renderer.tsx +1 -1
  22. package/flow-report/src/wrappers/report.tsx +6 -21
  23. package/flow-report/test/app-test.tsx +17 -13
  24. package/flow-report/test/common-test.tsx +89 -0
  25. package/flow-report/test/header-test.tsx +55 -0
  26. package/flow-report/test/sample-flow.ts +17 -0
  27. package/flow-report/test/setup/env-setup.ts +1 -0
  28. package/flow-report/test/sidebar/flow-test.tsx +6 -12
  29. package/flow-report/test/sidebar/sidebar-test.tsx +7 -13
  30. package/flow-report/test/summary/category-test.tsx +83 -34
  31. package/flow-report/test/summary/summary-test.tsx +12 -18
  32. package/flow-report/test/topbar-test.tsx +68 -0
  33. package/flow-report/test/util-test.tsx +1 -12
  34. package/flow-report/tsconfig.json +2 -0
  35. package/flow-report/types/flow-report.d.ts +2 -1
  36. package/jest.config.js +1 -15
  37. package/lighthouse-cli/bin.js +5 -0
  38. package/lighthouse-cli/cli-flags.js +10 -3
  39. package/lighthouse-cli/commands/commands.js +1 -0
  40. package/lighthouse-cli/commands/list-locales.js +16 -0
  41. package/lighthouse-cli/run.js +2 -2
  42. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
  43. package/lighthouse-cli/test/smokehouse/report-assert.js +20 -4
  44. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -1
  45. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +1 -1
  46. package/lighthouse-core/audits/seo/crawlable-anchors.js +10 -12
  47. package/lighthouse-core/computed/js-bundles.js +1 -2
  48. package/lighthouse-core/computed/unused-javascript-summary.js +3 -3
  49. package/lighthouse-core/config/config-helpers.js +1 -0
  50. package/lighthouse-core/config/config.js +2 -2
  51. package/lighthouse-core/config/default-config.js +5 -0
  52. package/lighthouse-core/fraggle-rock/api.js +10 -0
  53. package/lighthouse-core/fraggle-rock/config/filters.js +22 -3
  54. package/lighthouse-core/fraggle-rock/gather/driver.js +4 -0
  55. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +2 -2
  56. package/lighthouse-core/fraggle-rock/user-flow.js +35 -6
  57. package/lighthouse-core/gather/driver/navigation.js +5 -0
  58. package/lighthouse-core/gather/driver/prepare.js +14 -0
  59. package/lighthouse-core/gather/driver/storage.js +5 -0
  60. package/lighthouse-core/gather/gather-runner.js +2 -2
  61. package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +5 -1
  62. package/lighthouse-core/gather/gatherers/full-page-screenshot.js +30 -24
  63. package/lighthouse-core/lib/cdt/Platform.js +55 -0
  64. package/lighthouse-core/lib/cdt/SDK.js +3 -123
  65. package/lighthouse-core/lib/cdt/generated/SourceMap.js +148 -312
  66. package/lighthouse-core/lib/csp-evaluator.js +2 -1
  67. package/lighthouse-core/lib/i18n/README.md +6 -6
  68. package/lighthouse-core/lib/i18n/i18n.js +26 -348
  69. package/lighthouse-core/lib/page-functions.js +4 -4
  70. package/lighthouse-core/lib/stack-packs.js +1 -13
  71. package/lighthouse-core/runner.js +6 -6
  72. package/lighthouse-core/scripts/manual-chrome-launcher.js +4 -1
  73. package/lighthouse-core/scripts/package.json +4 -0
  74. package/lighthouse-core/util-commonjs.js +20 -4
  75. package/package.json +19 -16
  76. package/readme.md +1 -1
  77. package/report/assets/standalone-template.html +0 -1
  78. package/report/assets/styles.css +16 -41
  79. package/report/assets/templates.html +42 -4
  80. package/report/clients/psi.js +1 -0
  81. package/report/clients/standalone.js +1 -2
  82. package/report/generator/file-namer.js +17 -8
  83. package/report/generator/report-generator.js +0 -1
  84. package/report/renderer/category-renderer.js +4 -16
  85. package/report/renderer/components.js +107 -71
  86. package/report/renderer/dom.js +19 -0
  87. package/report/renderer/logger.js +35 -2
  88. package/report/renderer/performance-category-renderer.js +23 -21
  89. package/report/renderer/pwa-category-renderer.js +1 -2
  90. package/report/renderer/report-renderer.js +21 -0
  91. package/report/renderer/report-ui-features.js +7 -19
  92. package/report/renderer/swap-locale-feature.js +82 -0
  93. package/report/renderer/util.js +20 -4
  94. package/report/test/clients/psi-test.js +0 -4
  95. package/report/test/generator/file-namer-test.js +2 -2
  96. package/report/test/renderer/category-renderer-test.js +1 -1
  97. package/report/test/renderer/performance-category-renderer-test.js +12 -0
  98. package/report/test/renderer/report-renderer-axe-test.js +86 -0
  99. package/report/test/renderer/report-renderer-test.js +0 -55
  100. package/report/test/renderer/util-test.js +68 -8
  101. package/report/test-assets/faux-psi-template.html +0 -1
  102. package/report/tsconfig.json +2 -1
  103. package/report/types/html-renderer.d.ts +2 -1
  104. package/root.js +18 -0
  105. package/shared/localization/format.js +455 -0
  106. package/{lighthouse-core/lib/cdt/Common.js → shared/localization/i18n-module.js} +3 -8
  107. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ar-XB.json +139 -4
  108. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ar.json +141 -6
  109. package/{lighthouse-core/lib/i18n → shared/localization}/locales/bg.json +139 -4
  110. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ca.json +140 -5
  111. package/{lighthouse-core/lib/i18n → shared/localization}/locales/cs.json +139 -4
  112. package/{lighthouse-core/lib/i18n → shared/localization}/locales/da.json +139 -4
  113. package/{lighthouse-core/lib/i18n → shared/localization}/locales/de.json +139 -4
  114. package/{lighthouse-core/lib/i18n → shared/localization}/locales/el.json +141 -6
  115. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-GB.json +139 -4
  116. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-US.json +129 -0
  117. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-XA.json +139 -4
  118. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-XL.json +129 -0
  119. package/{lighthouse-core/lib/i18n → shared/localization}/locales/es-419.json +139 -4
  120. package/{lighthouse-core/lib/i18n → shared/localization}/locales/es.json +139 -4
  121. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fi.json +139 -4
  122. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fil.json +139 -4
  123. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fr.json +139 -4
  124. package/{lighthouse-core/lib/i18n → shared/localization}/locales/he.json +139 -4
  125. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hi.json +142 -7
  126. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hr.json +139 -4
  127. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hu.json +139 -4
  128. package/{lighthouse-core/lib/i18n → shared/localization}/locales/id.json +139 -4
  129. package/{lighthouse-core/lib/i18n → shared/localization}/locales/it.json +139 -4
  130. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ja.json +139 -4
  131. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ko.json +139 -4
  132. package/{lighthouse-core/lib/i18n → shared/localization}/locales/lt.json +139 -4
  133. package/{lighthouse-core/lib/i18n → shared/localization}/locales/lv.json +139 -4
  134. package/{lighthouse-core/lib/i18n → shared/localization}/locales/nl.json +139 -4
  135. package/{lighthouse-core/lib/i18n → shared/localization}/locales/no.json +139 -4
  136. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pl.json +139 -4
  137. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pt-PT.json +139 -4
  138. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pt.json +139 -4
  139. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ro.json +139 -4
  140. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ru.json +139 -4
  141. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sk.json +139 -4
  142. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sl.json +139 -4
  143. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sr-Latn.json +139 -4
  144. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sr.json +139 -4
  145. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sv.json +139 -4
  146. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ta.json +146 -11
  147. package/{lighthouse-core/lib/i18n → shared/localization}/locales/te.json +179 -44
  148. package/{lighthouse-core/lib/i18n → shared/localization}/locales/th.json +139 -4
  149. package/{lighthouse-core/lib/i18n → shared/localization}/locales/tr.json +139 -4
  150. package/{lighthouse-core/lib/i18n → shared/localization}/locales/uk.json +139 -4
  151. package/{lighthouse-core/lib/i18n → shared/localization}/locales/vi.json +139 -4
  152. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh-HK.json +139 -4
  153. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh-TW.json +139 -4
  154. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh.json +139 -4
  155. package/{lighthouse-core/lib/i18n → shared/localization}/locales.js +8 -1
  156. package/shared/localization/swap-flow-locale.js +20 -0
  157. package/{lighthouse-core/lib/i18n → shared/localization}/swap-locale.js +9 -7
  158. package/shared/test/localization/format-test.js +421 -0
  159. package/shared/test/localization/locales-test.js +47 -0
  160. package/shared/test/localization/swap-locale-test.js +140 -0
  161. package/shared/tsconfig.json +22 -0
  162. package/{lighthouse-core/lib → shared}/type-verifiers.js +0 -0
  163. package/shared/types/shared.d.ts +24 -0
  164. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +5 -3
  165. package/third-party/chromium-synchronization/installability-errors-test.js +2 -1
  166. package/tsconfig-all.json +1 -0
  167. package/tsconfig-base.json +1 -1
  168. package/tsconfig.json +2 -3
  169. package/types/config.d.ts +1 -0
  170. package/types/es-main.d.ts +18 -0
  171. package/types/externs.d.ts +2 -0
  172. package/types/lhr/flow.d.ts +7 -0
  173. package/types/lhr/i18n.d.ts +0 -3
@@ -8,7 +8,7 @@
8
8
  const defaultConfigPath = './default-config.js';
9
9
  const defaultConfig = require('./default-config.js');
10
10
  const constants = require('./constants.js');
11
- const i18n = require('./../lib/i18n/i18n.js');
11
+ const format = require('../../shared/localization/format.js');
12
12
  const validation = require('./../fraggle-rock/config/validation.js');
13
13
 
14
14
  const log = require('lighthouse-logger');
@@ -248,7 +248,7 @@ class Config {
248
248
  }
249
249
 
250
250
  // Printed config is more useful with localized strings.
251
- i18n.replaceIcuMessages(jsonConfig, jsonConfig.settings.locale);
251
+ format.replaceIcuMessages(jsonConfig, jsonConfig.settings.locale);
252
252
 
253
253
  return JSON.stringify(jsonConfig, null, 2);
254
254
  }
@@ -421,6 +421,7 @@ const defaultConfig = {
421
421
  categories: {
422
422
  'performance': {
423
423
  title: str_(UIStrings.performanceCategoryTitle),
424
+ supportedModes: ['navigation', 'timespan', 'snapshot'],
424
425
  auditRefs: [
425
426
  {id: 'first-contentful-paint', weight: 10, group: 'metrics', acronym: 'FCP', relevantAudits: m2a.fcpRelevantAudits},
426
427
  {id: 'speed-index', weight: 10, group: 'metrics', acronym: 'SI'},
@@ -490,6 +491,7 @@ const defaultConfig = {
490
491
  title: str_(UIStrings.a11yCategoryTitle),
491
492
  description: str_(UIStrings.a11yCategoryDescription),
492
493
  manualDescription: str_(UIStrings.a11yCategoryManualDescription),
494
+ supportedModes: ['navigation', 'snapshot'],
493
495
  // Audit weights are meant to match the aXe scoring system of
494
496
  // minor, moderate, serious, and critical.
495
497
  // See the audits listed at dequeuniversity.com/rules/axe/4.1.
@@ -554,6 +556,7 @@ const defaultConfig = {
554
556
  },
555
557
  'best-practices': {
556
558
  title: str_(UIStrings.bestPracticesCategoryTitle),
559
+ supportedModes: ['navigation', 'timespan', 'snapshot'],
557
560
  auditRefs: [
558
561
  // Trust & Safety
559
562
  {id: 'is-on-https', weight: 1, group: 'best-practices-trust-safety'},
@@ -584,6 +587,7 @@ const defaultConfig = {
584
587
  title: str_(UIStrings.seoCategoryTitle),
585
588
  description: str_(UIStrings.seoCategoryDescription),
586
589
  manualDescription: str_(UIStrings.seoCategoryManualDescription),
590
+ supportedModes: ['navigation', 'snapshot'],
587
591
  auditRefs: [
588
592
  {id: 'viewport', weight: 1, group: 'seo-mobile'},
589
593
  {id: 'document-title', weight: 1, group: 'seo-content'},
@@ -607,6 +611,7 @@ const defaultConfig = {
607
611
  title: str_(UIStrings.pwaCategoryTitle),
608
612
  description: str_(UIStrings.pwaCategoryDescription),
609
613
  manualDescription: str_(UIStrings.pwaCategoryManualDescription),
614
+ supportedModes: ['navigation'],
610
615
  auditRefs: [
611
616
  // Installable
612
617
  {id: 'installable-manifest', weight: 2, group: 'pwa-installable'},
@@ -8,9 +8,19 @@
8
8
  const {snapshot} = require('./gather/snapshot-runner.js');
9
9
  const {startTimespan} = require('./gather/timespan-runner.js');
10
10
  const {navigation} = require('./gather/navigation-runner.js');
11
+ const UserFlow = require('./user-flow.js');
12
+
13
+ /**
14
+ * @param {import('puppeteer').Page} page
15
+ * @param {UserFlow.UserFlowOptions} [options]
16
+ */
17
+ async function startFlow(page, options) {
18
+ return new UserFlow(page, options);
19
+ }
11
20
 
12
21
  module.exports = {
13
22
  snapshot,
14
23
  startTimespan,
15
24
  navigation,
25
+ startFlow,
16
26
  };
@@ -162,6 +162,23 @@ function filterAuditsByGatherMode(audits, mode) {
162
162
  });
163
163
  }
164
164
 
165
+ /**
166
+ * Optional `supportedModes` property can explicitly exclude a category even if some audits are available.
167
+ *
168
+ * @param {LH.Config.Config['categories']} categories
169
+ * @param {LH.Gatherer.GatherMode} mode
170
+ * @return {LH.Config.Config['categories']}
171
+ */
172
+ function filterCategoriesByGatherMode(categories, mode) {
173
+ if (!categories) return null;
174
+
175
+ const categoriesToKeep = Object.entries(categories)
176
+ .filter(([_, category]) => {
177
+ return !category.supportedModes || category.supportedModes.includes(mode);
178
+ });
179
+ return Object.fromEntries(categoriesToKeep);
180
+ }
181
+
165
182
  /**
166
183
  * Filters a categories object and their auditRefs down to the specified category ids.
167
184
  *
@@ -225,9 +242,10 @@ function filterCategoriesByAvailableAudits(categories, availableAudits) {
225
242
  */
226
243
  function filterConfigByGatherMode(config, mode) {
227
244
  const artifacts = filterArtifactsByGatherMode(config.artifacts, mode);
228
- const availableAudits = filterAuditsByAvailableArtifacts(config.audits, artifacts || []);
229
- const audits = filterAuditsByGatherMode(availableAudits, mode);
230
- const categories = filterCategoriesByAvailableAudits(config.categories, audits || []);
245
+ const supportedAudits = filterAuditsByGatherMode(config.audits, mode);
246
+ const audits = filterAuditsByAvailableArtifacts(supportedAudits, artifacts || []);
247
+ const supportedCategories = filterCategoriesByGatherMode(config.categories, mode);
248
+ const categories = filterCategoriesByAvailableAudits(supportedCategories, audits || []);
231
249
 
232
250
  return {
233
251
  ...config,
@@ -291,4 +309,5 @@ module.exports = {
291
309
  filterAuditsByGatherMode,
292
310
  filterCategoriesByAvailableAudits,
293
311
  filterCategoriesByExplicitFilters,
312
+ filterCategoriesByGatherMode,
294
313
  };
@@ -5,6 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
+ const log = require('lighthouse-logger');
8
9
  const ProtocolSession = require('./session.js');
9
10
  const ExecutionContext = require('../../gather/driver/execution-context.js');
10
11
  const Fetcher = require('../../gather/fetcher.js');
@@ -68,10 +69,13 @@ class Driver {
68
69
  /** @return {Promise<void>} */
69
70
  async connect() {
70
71
  if (this._session) return;
72
+ const status = {msg: 'Connecting to browser', id: 'lh:driver:connect'};
73
+ log.time(status);
71
74
  const session = await this._page.target().createCDPSession();
72
75
  this._session = this.defaultSession = new ProtocolSession(session);
73
76
  this._executionContext = new ExecutionContext(this._session);
74
77
  this._fetcher = new Fetcher(this._session, this._executionContext);
78
+ log.timeEnd(status);
75
79
  }
76
80
 
77
81
  /** @return {Promise<void>} */
@@ -20,7 +20,7 @@ const emulation = require('../../lib/emulation.js');
20
20
  const {defaultNavigationConfig} = require('../../config/constants.js');
21
21
  const {initializeConfig} = require('../config/config.js');
22
22
  const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
23
- const i18n = require('../../lib/i18n/i18n.js');
23
+ const format = require('../../../shared/localization/format.js');
24
24
  const LighthouseError = require('../../lib/lh-error.js');
25
25
  const {getPageLoadError} = require('../../lib/navigation-error.js');
26
26
  const Trace = require('../../gather/gatherers/trace.js');
@@ -165,7 +165,7 @@ async function _computeNavigationResult(
165
165
 
166
166
  if (pageLoadError) {
167
167
  const locale = navigationContext.config.settings.locale;
168
- const localizedMessage = i18n.getFormatted(pageLoadError.friendlyMessage, locale);
168
+ const localizedMessage = format.getFormatted(pageLoadError.friendlyMessage, locale);
169
169
  log.error('NavigationRunner', localizedMessage, navigationContext.requestedUrl);
170
170
 
171
171
  /** @type {Partial<LH.GathererArtifacts>} */
@@ -6,11 +6,13 @@
6
6
  'use strict';
7
7
 
8
8
  const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
9
- const {navigation, startTimespan, snapshot} = require('./api.js');
9
+ const {snapshot} = require('./gather/snapshot-runner.js');
10
+ const {startTimespan} = require('./gather/timespan-runner.js');
11
+ const {navigation} = require('./gather/navigation-runner.js');
10
12
 
11
13
  /** @typedef {Parameters<snapshot>[0]} FrOptions */
12
- /** @typedef {Omit<FrOptions, 'page'>} UserFlowOptions */
13
- /** @typedef {UserFlowOptions & {stepName?: string}} StepOptions */
14
+ /** @typedef {Omit<FrOptions, 'page'> & {name?: string}} UserFlowOptions */
15
+ /** @typedef {Omit<FrOptions, 'page'> & {stepName?: string}} StepOptions */
14
16
 
15
17
  class UserFlow {
16
18
  /**
@@ -20,6 +22,8 @@ class UserFlow {
20
22
  constructor(page, options) {
21
23
  /** @type {FrOptions} */
22
24
  this.options = {page, ...options};
25
+ /** @type {string|undefined} */
26
+ this.name = options && options.name;
23
27
  /** @type {LH.FlowResult.Step[]} */
24
28
  this.steps = [];
25
29
  }
@@ -49,6 +53,29 @@ class UserFlow {
49
53
  }
50
54
  }
51
55
 
56
+ /**
57
+ * @param {string} url
58
+ * @param {StepOptions=} stepOptions
59
+ */
60
+ _getNextNavigationOptions(url, stepOptions) {
61
+ const options = {url, ...this.options, ...stepOptions};
62
+
63
+ // On repeat navigations, we want to disable storage reset by default (i.e. it's not a cold load).
64
+ const isSubsequentNavigation = this.steps.some(step => step.lhr.gatherMode === 'navigation');
65
+ if (isSubsequentNavigation) {
66
+ const configContext = {...options.configContext};
67
+ const settingsOverrides = {...configContext.settingsOverrides};
68
+ if (settingsOverrides.disableStorageReset === undefined) {
69
+ settingsOverrides.disableStorageReset = true;
70
+ }
71
+
72
+ configContext.settingsOverrides = settingsOverrides;
73
+ options.configContext = configContext;
74
+ }
75
+
76
+ return options;
77
+ }
78
+
52
79
  /**
53
80
  * @param {string} url
54
81
  * @param {StepOptions=} stepOptions
@@ -56,8 +83,7 @@ class UserFlow {
56
83
  async navigate(url, stepOptions) {
57
84
  if (this.currentTimespan) throw Error('Timespan already in progress');
58
85
 
59
- const options = {url, ...this.options, ...stepOptions};
60
- const result = await navigation(options);
86
+ const result = await navigation(this._getNextNavigationOptions(url, stepOptions));
61
87
  if (!result) throw Error('Navigation returned undefined');
62
88
 
63
89
  const providedName = stepOptions && stepOptions.stepName;
@@ -120,7 +146,10 @@ class UserFlow {
120
146
  * @return {LH.FlowResult}
121
147
  */
122
148
  getFlowResult() {
123
- return {steps: this.steps};
149
+ if (!this.steps.length) throw Error('Need at least one step before getting the flow result');
150
+ const url = new URL(this.steps[0].lhr.finalUrl);
151
+ const name = this.name || `User flow (${url.hostname})`;
152
+ return {steps: this.steps, name};
124
153
  }
125
154
 
126
155
  /**
@@ -5,6 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
+ const log = require('lighthouse-logger');
8
9
  const NetworkMonitor = require('./network-monitor.js');
9
10
  const {waitForFullyLoaded, waitForFrameNavigated, waitForUserToContinue} = require('./wait-for-condition.js'); // eslint-disable-line max-len
10
11
  const constants = require('../../config/constants.js');
@@ -82,6 +83,9 @@ function resolveWaitForFullyLoadedOptions(options) {
82
83
  * @return {Promise<{finalUrl: string, warnings: Array<LH.IcuMessage>}>}
83
84
  */
84
85
  async function gotoURL(driver, url, options) {
86
+ const status = {msg: `Navigating to ${url}`, id: 'lh:driver:navigate'};
87
+ log.time(status);
88
+
85
89
  const session = driver.defaultSession;
86
90
  const networkMonitor = new NetworkMonitor(driver.defaultSession);
87
91
 
@@ -125,6 +129,7 @@ async function gotoURL(driver, url, options) {
125
129
  await waitForUserToContinue(driver);
126
130
  }
127
131
 
132
+ log.timeEnd(status);
128
133
  return {
129
134
  finalUrl,
130
135
  warnings: getNavigationWarnings({timedOut, finalUrl, requestedUrl: url}),
@@ -142,11 +142,16 @@ async function prepareDeviceEmulationAndAsyncStacks(driver, settings) {
142
142
  * @param {LH.Config.Settings} settings
143
143
  */
144
144
  async function prepareTargetForTimespanMode(driver, settings) {
145
+ const status = {msg: 'Preparing target for timespan mode', id: 'lh:prepare:timespanMode'};
146
+ log.time(status);
147
+
145
148
  await prepareDeviceEmulationAndAsyncStacks(driver, settings);
146
149
  await prepareThrottlingAndNetwork(driver.defaultSession, settings, {
147
150
  disableThrottling: false,
148
151
  blockedUrlPatterns: undefined,
149
152
  });
153
+
154
+ log.timeEnd(status);
150
155
  }
151
156
 
152
157
  /**
@@ -159,6 +164,9 @@ async function prepareTargetForTimespanMode(driver, settings) {
159
164
  * @param {LH.Config.Settings} settings
160
165
  */
161
166
  async function prepareTargetForNavigationMode(driver, settings) {
167
+ const status = {msg: 'Preparing target for navigation mode', id: 'lh:prepare:navigationMode'};
168
+ log.time(status);
169
+
162
170
  await prepareDeviceEmulationAndAsyncStacks(driver, settings);
163
171
 
164
172
  // Automatically handle any JavaScript dialogs to prevent a hung renderer.
@@ -171,6 +179,8 @@ async function prepareTargetForNavigationMode(driver, settings) {
171
179
  if (settings.throttlingMethod === 'simulate') {
172
180
  await shimRequestIdleCallbackOnNewDocument(driver, settings);
173
181
  }
182
+
183
+ log.timeEnd(status);
174
184
  }
175
185
 
176
186
  /**
@@ -184,6 +194,9 @@ async function prepareTargetForNavigationMode(driver, settings) {
184
194
  * @return {Promise<{warnings: Array<LH.IcuMessage>}>}
185
195
  */
186
196
  async function prepareTargetForIndividualNavigation(session, settings, navigation) {
197
+ const status = {msg: 'Preparing target for navigation', id: 'lh:prepare:navigation'};
198
+ log.time(status);
199
+
187
200
  /** @type {Array<LH.IcuMessage>} */
188
201
  const warnings = [];
189
202
 
@@ -195,6 +208,7 @@ async function prepareTargetForIndividualNavigation(session, settings, navigatio
195
208
 
196
209
  await prepareThrottlingAndNetwork(session, settings, navigation);
197
210
 
211
+ log.timeEnd(status);
198
212
  return {warnings};
199
213
  }
200
214
 
@@ -33,6 +33,9 @@ const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
33
33
  * @return {Promise<void>}
34
34
  */
35
35
  async function clearDataForOrigin(session, url) {
36
+ const status = {msg: 'Cleaning origin data', id: 'lh:storage:clearDataForOrigin'};
37
+ log.time(status);
38
+
36
39
  const origin = new URL(url).origin;
37
40
 
38
41
  // Clear some types of storage.
@@ -63,6 +66,8 @@ async function clearDataForOrigin(session, url) {
63
66
  } else {
64
67
  throw err;
65
68
  }
69
+ } finally {
70
+ log.timeEnd(status);
66
71
  }
67
72
  }
68
73
 
@@ -10,7 +10,7 @@ const NetworkRecords = require('../computed/network-records.js');
10
10
  const {getPageLoadError} = require('../lib/navigation-error.js');
11
11
  const emulation = require('../lib/emulation.js');
12
12
  const constants = require('../config/constants.js');
13
- const i18n = require('../lib/i18n/i18n.js');
13
+ const format = require('../../shared/localization/format.js');
14
14
  const {getBenchmarkIndex, getEnvironmentWarnings} = require('./driver/environment.js');
15
15
  const prepare = require('./driver/prepare.js');
16
16
  const storage = require('./driver/storage.js');
@@ -574,7 +574,7 @@ class GatherRunner {
574
574
  networkRecords: loadData.networkRecords,
575
575
  });
576
576
  if (pageLoadError) {
577
- const localizedMessage = i18n.getFormatted(pageLoadError.friendlyMessage,
577
+ const localizedMessage = format.getFormatted(pageLoadError.friendlyMessage,
578
578
  passContext.settings.locale);
579
579
  log.error('GatherRunner', localizedMessage, passContext.url);
580
580
 
@@ -20,7 +20,11 @@ const {fetchResponseBodyFromCache} = require('../../driver/network.js');
20
20
  const NetworkRecords = require('../../../computed/network-records.js');
21
21
 
22
22
  const CHROME_EXTENSION_PROTOCOL = 'chrome-extension:';
23
- const compressionHeaders = ['content-encoding', 'x-original-content-encoding'];
23
+ const compressionHeaders = [
24
+ 'content-encoding',
25
+ 'x-original-content-encoding',
26
+ 'x-content-encoding-over-network',
27
+ ];
24
28
  const compressionTypes = ['gzip', 'br', 'deflate'];
25
29
  const binaryMimeTypes = ['image', 'audio', 'video'];
26
30
  /** @type {LH.Crdp.Network.ResourceType[]} */
@@ -22,6 +22,26 @@ function snakeCaseToCamelCase(str) {
22
22
  return str.replace(/(-\w)/g, m => m[1].toUpperCase());
23
23
  }
24
24
 
25
+ /* c8 ignore start */
26
+
27
+ // eslint-disable-next-line no-inner-declarations
28
+ function getObservedDeviceMetrics() {
29
+ // Convert the Web API's snake case (landscape-primary) to camel case (landscapePrimary).
30
+ const screenOrientationType = /** @type {LH.Crdp.Emulation.ScreenOrientationType} */ (
31
+ snakeCaseToCamelCase(window.screen.orientation.type));
32
+ return {
33
+ width: document.documentElement.clientWidth,
34
+ height: document.documentElement.clientHeight,
35
+ screenOrientation: {
36
+ type: screenOrientationType,
37
+ angle: window.screen.orientation.angle,
38
+ },
39
+ deviceScaleFactor: window.devicePixelRatio,
40
+ };
41
+ }
42
+
43
+ /* c8 ignore stop */
44
+
25
45
  class FullPageScreenshot extends FRGatherer {
26
46
  /** @type {LH.Gatherer.GathererMeta} */
27
47
  meta = {
@@ -140,9 +160,17 @@ class FullPageScreenshot extends FRGatherer {
140
160
  const executionContext = context.driver.executionContext;
141
161
  const settings = context.settings;
142
162
 
143
- // In case some other program is controlling emulation, try to remember what the device looks
163
+ // In case some other program is controlling emulation, remember what the device looks
144
164
  // like now and reset after gatherer is done.
165
+ let observedDeviceMetrics;
145
166
  const lighthouseControlsEmulation = !settings.screenEmulation.disabled;
167
+ if (!lighthouseControlsEmulation) {
168
+ observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
169
+ args: [],
170
+ useIsolation: true,
171
+ deps: [snakeCaseToCamelCase],
172
+ });
173
+ }
146
174
 
147
175
  try {
148
176
  return {
@@ -153,7 +181,7 @@ class FullPageScreenshot extends FRGatherer {
153
181
  // Revert resized page.
154
182
  if (lighthouseControlsEmulation) {
155
183
  await emulation.emulate(session, settings);
156
- } else {
184
+ } else if (observedDeviceMetrics) {
157
185
  // Best effort to reset emulation to what it was.
158
186
  // https://github.com/GoogleChrome/lighthouse/pull/10716#discussion_r428970681
159
187
  // TODO: seems like this would be brittle. Should at least work for devtools, but what
@@ -161,28 +189,6 @@ class FullPageScreenshot extends FRGatherer {
161
189
  // in the LH runner api, which for ex. puppeteer consumers would setup puppeteer emulation,
162
190
  // and then just call that to reset?
163
191
  // https://github.com/GoogleChrome/lighthouse/issues/11122
164
-
165
- // eslint-disable-next-line no-inner-declarations
166
- function getObservedDeviceMetrics() {
167
- // Convert the Web API's snake case (landscape-primary) to camel case (landscapePrimary).
168
- const screenOrientationType = /** @type {LH.Crdp.Emulation.ScreenOrientationType} */ (
169
- snakeCaseToCamelCase(window.screen.orientation.type));
170
- return {
171
- width: document.documentElement.clientWidth,
172
- height: document.documentElement.clientHeight,
173
- screenOrientation: {
174
- type: screenOrientationType,
175
- angle: window.screen.orientation.angle,
176
- },
177
- deviceScaleFactor: window.devicePixelRatio,
178
- };
179
- }
180
-
181
- const observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
182
- args: [],
183
- useIsolation: true,
184
- deps: [snakeCaseToCamelCase],
185
- });
186
192
  await session.sendCommand('Emulation.setDeviceMetricsOverride', {
187
193
  mobile: settings.formFactor === 'mobile',
188
194
  ...observedDeviceMetrics,
@@ -0,0 +1,55 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
6
+ */
7
+ 'use strict';
8
+
9
+ // Functions manually copied from:
10
+ // https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/core/platform/array-utilities.ts#L125
11
+
12
+ /**
13
+ * @param {any[]} array
14
+ * @param {any} needle
15
+ * @param {any} comparator
16
+ */
17
+ function lowerBound(array, needle, comparator, left, right) {
18
+ let l = left || 0;
19
+ let r = right !== undefined ? right : array.length;
20
+ while (l < r) {
21
+ const m = (l + r) >> 1;
22
+ if (comparator(needle, array[m]) > 0) {
23
+ l = m + 1;
24
+ } else {
25
+ r = m;
26
+ }
27
+ }
28
+ return r;
29
+ }
30
+
31
+ /**
32
+ * @param {any[]} array
33
+ * @param {any} needle
34
+ * @param {any} comparator
35
+ */
36
+ function upperBound(array, needle, comparator, left, right) {
37
+ let l = left || 0;
38
+ let r = right !== undefined ? right : array.length;
39
+ while (l < r) {
40
+ const m = (l + r) >> 1;
41
+ if (comparator(needle, array[m]) >= 0) {
42
+ l = m + 1;
43
+ } else {
44
+ r = m;
45
+ }
46
+ }
47
+ return r;
48
+ }
49
+
50
+ module.exports = {
51
+ ArrayUtilities: {
52
+ lowerBound,
53
+ upperBound,
54
+ },
55
+ };
@@ -4,145 +4,25 @@
4
4
  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
5
  */
6
6
  'use strict';
7
-
8
7
  const SDK = {
9
8
  TextSourceMap: require('./generated/SourceMap.js'),
10
9
  };
11
10
 
12
- /**
13
- * CDT pollutes Array.prototype w/ `lowerBound/upperBound`. SourceMap
14
- * relies on this, but only for a couple method return values. To avoid global pollution,
15
- * we explicitly set the extension functions on the return values.
16
- *
17
- * @param {unknown[]} array
18
- */
19
- function extendArray(array) {
20
- // @ts-expect-error
21
- if (array.lowerBound) return;
22
-
23
- // @ts-expect-error
24
- array.lowerBound = lowerBound.bind(array);
25
- // @ts-expect-error
26
- array.upperBound = upperBound.bind(array);
27
-
28
- array.slice = function(start, end) {
29
- const retVal = Array.prototype.slice.call(array, start, end);
30
- extendArray(retVal);
31
- return retVal;
32
- };
33
- // @ts-expect-error
34
- array.filter = function(fn) {
35
- const retVal = Array.prototype.filter.call(array, fn);
36
- extendArray(retVal);
37
- return retVal;
38
- };
39
- }
40
-
41
- const originalMappings = SDK.TextSourceMap.prototype.mappings;
42
- SDK.TextSourceMap.prototype.mappings = function() {
43
- const mappings = originalMappings.call(this);
44
- extendArray(mappings);
45
- return mappings;
46
- };
47
-
48
- const originalReversedMappings = SDK.TextSourceMap.prototype._reversedMappings;
49
- /** @param {string} sourceURL */
50
- SDK.TextSourceMap.prototype._reversedMappings = function(sourceURL) {
51
- const mappings = originalReversedMappings.call(this, sourceURL);
52
- extendArray(mappings);
53
- return mappings;
54
- };
55
-
56
- /**
57
- * `upperBound` and `lowerBound` are copied from CDT utilities.js.
58
- * These are the only methods needed from that file.
59
- */
60
-
61
- /**
62
- * Return index of the leftmost element that is greater
63
- * than the specimen object. If there's no such element (i.e. all
64
- * elements are smaller or equal to the specimen) returns right bound.
65
- * The function works for sorted array.
66
- * When specified, |left| (inclusive) and |right| (exclusive) indices
67
- * define the search window.
68
- *
69
- * @param {!T} object
70
- * @param {function(!T,!S):number=} comparator
71
- * @param {number=} left
72
- * @param {number=} right
73
- * @return {number}
74
- * @this {Array.<!S>}
75
- * @template T,S
76
- */
77
- function upperBound(object, comparator, left, right) {
78
- // @ts-expect-error
79
- function defaultComparator(a, b) {
80
- return a < b ? -1 : (a > b ? 1 : 0);
81
- }
82
- comparator = comparator || defaultComparator;
83
- let l = left || 0;
84
- let r = right !== undefined ? right : this.length;
85
- while (l < r) {
86
- const m = (l + r) >> 1;
87
- if (comparator(object, this[m]) >= 0) {
88
- l = m + 1;
89
- } else {
90
- r = m;
91
- }
92
- }
93
- return r;
94
- }
95
-
96
- /**
97
- * Return index of the leftmost element that is equal or greater
98
- * than the specimen object. If there's no such element (i.e. all
99
- * elements are smaller than the specimen) returns right bound.
100
- * The function works for sorted array.
101
- * When specified, |left| (inclusive) and |right| (exclusive) indices
102
- * define the search window.
103
- *
104
- * @param {!T} object
105
- * @param {function(!T,!S):number=} comparator
106
- * @param {number=} left
107
- * @param {number=} right
108
- * @return {number}
109
- * @this {Array.<!S>}
110
- * @template T,S
111
- */
112
- function lowerBound(object, comparator, left, right) {
113
- // @ts-expect-error
114
- function defaultComparator(a, b) {
115
- return a < b ? -1 : (a > b ? 1 : 0);
116
- }
117
- comparator = comparator || defaultComparator;
118
- let l = left || 0;
119
- let r = right !== undefined ? right : this.length;
120
- while (l < r) {
121
- const m = (l + r) >> 1;
122
- if (comparator(object, this[m]) > 0) {
123
- l = m + 1;
124
- } else {
125
- r = m;
126
- }
127
- }
128
- return r;
129
- }
130
-
131
11
  // Add `lastColumnNumber` to mappings. This will eventually be added to CDT.
132
12
  // @ts-expect-error
133
13
  SDK.TextSourceMap.prototype.computeLastGeneratedColumns = function() {
134
14
  const mappings = this.mappings();
135
- // @ts-expect-error: `lastColumnNumber` is not on types yet.
136
- if (mappings.length && typeof mappings[0].lastColumnNumber !== 'undefined') return;
15
+ if (mappings.length && mappings[0].lastColumnNumber !== undefined) return;
137
16
 
138
17
  for (let i = 0; i < mappings.length - 1; i++) {
139
18
  const mapping = mappings[i];
140
19
  const nextMapping = mappings[i + 1];
141
20
  if (mapping.lineNumber === nextMapping.lineNumber) {
142
- // @ts-expect-error: `lastColumnNumber` is not on types yet.
143
21
  mapping.lastColumnNumber = nextMapping.columnNumber;
144
22
  }
145
23
  }
24
+
25
+ // Now, all but the last mapping on each line will have 'lastColumnNumber' set to a number.
146
26
  };
147
27
 
148
28
  module.exports = SDK;