lighthouse 9.3.1-dev.20220209 → 9.3.1-dev.20220210

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.
@@ -208,7 +208,7 @@ async function runLighthouseWithFraggleRock(url, flags, config, launchedChrome)
208
208
  const page = await browser.newPage();
209
209
  flags.channel = 'fraggle-rock-cli';
210
210
  const configContext = {configPath: flags.configPath, settingsOverrides: flags};
211
- return fraggleRock.navigation({url, page, config, configContext});
211
+ return fraggleRock.navigation(url, {page, config, configContext});
212
212
  }
213
213
 
214
214
  /**
@@ -35,7 +35,7 @@ const NetworkRecords = require('../../computed/network-records.js');
35
35
  * @property {Driver} driver
36
36
  * @property {LH.Config.FRConfig} config
37
37
  * @property {LH.Config.NavigationDefn} navigation
38
- * @property {string} requestedUrl
38
+ * @property {LH.NavigationRequestor} requestor
39
39
  * @property {LH.FRBaseArtifacts} baseArtifacts
40
40
  * @property {Map<string, LH.ArbitraryEqualityMap>} computedCache
41
41
  * @property {InternalOptions} [options]
@@ -44,17 +44,17 @@ const NetworkRecords = require('../../computed/network-records.js');
44
44
  /** @typedef {Omit<Parameters<typeof collectPhaseArtifacts>[0], 'phase'>} PhaseState */
45
45
 
46
46
  /**
47
- * @param {{driver: Driver, config: LH.Config.FRConfig, requestedUrl: string, options?: InternalOptions}} args
47
+ * @param {{driver: Driver, config: LH.Config.FRConfig, requestor: LH.NavigationRequestor, options?: InternalOptions}} args
48
48
  * @return {Promise<{baseArtifacts: LH.FRBaseArtifacts}>}
49
49
  */
50
- async function _setup({driver, config, requestedUrl, options}) {
50
+ async function _setup({driver, config, requestor, options}) {
51
51
  await driver.connect();
52
52
  if (!options?.skipAboutBlank) {
53
53
  await gotoURL(driver, defaultNavigationConfig.blankPage, {waitUntil: ['navigated']});
54
54
  }
55
55
 
56
56
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'navigation'});
57
- baseArtifacts.URL.requestedUrl = requestedUrl;
57
+ if (typeof requestor === 'string') baseArtifacts.URL.requestedUrl = requestor;
58
58
 
59
59
  await prepare.prepareTargetForNavigationMode(driver, config.settings);
60
60
 
@@ -65,7 +65,7 @@ async function _setup({driver, config, requestedUrl, options}) {
65
65
  * @param {NavigationContext} navigationContext
66
66
  * @return {Promise<{warnings: Array<LH.IcuMessage>}>}
67
67
  */
68
- async function _setupNavigation({requestedUrl, driver, navigation, config, options}) {
68
+ async function _setupNavigation({requestor, driver, navigation, config, options}) {
69
69
  if (!options?.skipAboutBlank) {
70
70
  await gotoURL(driver, navigation.blankPage, {...navigation, waitUntil: ['navigated']});
71
71
  }
@@ -74,7 +74,7 @@ async function _setupNavigation({requestedUrl, driver, navigation, config, optio
74
74
  config.settings,
75
75
  {
76
76
  ...navigation,
77
- url: requestedUrl,
77
+ requestor,
78
78
  }
79
79
  );
80
80
 
@@ -90,25 +90,25 @@ async function _cleanupNavigation({driver}) {
90
90
 
91
91
  /**
92
92
  * @param {NavigationContext} navigationContext
93
- * @return {Promise<{finalUrl: string, navigationError: LH.LighthouseError | undefined, warnings: Array<LH.IcuMessage>}>}
93
+ * @return {Promise<{requestedUrl: string, finalUrl: string, navigationError: LH.LighthouseError | undefined, warnings: Array<LH.IcuMessage>}>}
94
94
  */
95
95
  async function _navigate(navigationContext) {
96
- const {driver, config, requestedUrl} = navigationContext;
96
+ const {driver, config, requestor} = navigationContext;
97
97
 
98
98
  try {
99
- const {finalUrl, warnings} = await gotoURL(driver, requestedUrl, {
99
+ const {requestedUrl, finalUrl, warnings} = await gotoURL(driver, requestor, {
100
100
  ...navigationContext.navigation,
101
101
  debugNavigation: config.settings.debugNavigation,
102
102
  maxWaitForFcp: config.settings.maxWaitForFcp,
103
103
  maxWaitForLoad: config.settings.maxWaitForLoad,
104
104
  waitUntil: navigationContext.navigation.pauseAfterFcpMs ? ['fcp', 'load'] : ['load'],
105
105
  });
106
-
107
- return {finalUrl, navigationError: undefined, warnings};
106
+ return {requestedUrl, finalUrl, navigationError: undefined, warnings};
108
107
  } catch (err) {
109
108
  if (!(err instanceof LighthouseError)) throw err;
110
109
  if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG') throw err;
111
- return {finalUrl: requestedUrl, navigationError: err, warnings: []};
110
+ if (typeof requestor !== 'string') throw err;
111
+ return {requestedUrl: requestor, finalUrl: requestor, navigationError: err, warnings: []};
112
112
  }
113
113
  }
114
114
 
@@ -152,7 +152,7 @@ async function _collectDebugData(navigationContext, phaseState) {
152
152
  * @param {PhaseState} phaseState
153
153
  * @param {Awaited<ReturnType<typeof _setupNavigation>>} setupResult
154
154
  * @param {Awaited<ReturnType<typeof _navigate>>} navigateResult
155
- * @return {Promise<{finalUrl: string, artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
155
+ * @return {Promise<{requestedUrl: string, finalUrl: string, artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
156
156
  */
157
157
  async function _computeNavigationResult(
158
158
  navigationContext,
@@ -174,7 +174,7 @@ async function _computeNavigationResult(
174
174
  if (pageLoadError) {
175
175
  const locale = navigationContext.config.settings.locale;
176
176
  const localizedMessage = format.getFormatted(pageLoadError.friendlyMessage, locale);
177
- log.error('NavigationRunner', localizedMessage, navigationContext.requestedUrl);
177
+ log.error('NavigationRunner', localizedMessage, navigateResult.requestedUrl);
178
178
 
179
179
  /** @type {Partial<LH.GathererArtifacts>} */
180
180
  const artifacts = {};
@@ -183,6 +183,7 @@ async function _computeNavigationResult(
183
183
  if (debugData.trace) artifacts.traces = {[pageLoadErrorId]: debugData.trace};
184
184
 
185
185
  return {
186
+ requestedUrl: navigateResult.requestedUrl,
186
187
  finalUrl,
187
188
  pageLoadError,
188
189
  artifacts,
@@ -192,7 +193,13 @@ async function _computeNavigationResult(
192
193
  await collectPhaseArtifacts({phase: 'getArtifact', ...phaseState});
193
194
 
194
195
  const artifacts = await awaitArtifacts(phaseState.artifactState);
195
- return {finalUrl, artifacts, warnings, pageLoadError: undefined};
196
+ return {
197
+ requestedUrl: navigateResult.requestedUrl,
198
+ finalUrl,
199
+ artifacts,
200
+ warnings,
201
+ pageLoadError: undefined,
202
+ };
196
203
  }
197
204
  }
198
205
 
@@ -224,10 +231,10 @@ async function _navigation(navigationContext) {
224
231
  }
225
232
 
226
233
  /**
227
- * @param {{driver: Driver, config: LH.Config.FRConfig, requestedUrl: string; baseArtifacts: LH.FRBaseArtifacts, computedCache: NavigationContext['computedCache'], options?: InternalOptions}} args
234
+ * @param {{driver: Driver, config: LH.Config.FRConfig, requestor: LH.NavigationRequestor; baseArtifacts: LH.FRBaseArtifacts, computedCache: NavigationContext['computedCache'], options?: InternalOptions}} args
228
235
  * @return {Promise<{artifacts: Partial<LH.FRArtifacts & LH.FRBaseArtifacts>}>}
229
236
  */
230
- async function _navigations({driver, config, requestedUrl, baseArtifacts, computedCache, options}) {
237
+ async function _navigations({driver, config, requestor, baseArtifacts, computedCache, options}) {
231
238
  if (!config.navigations) throw new Error('No navigations configured');
232
239
 
233
240
  /** @type {Partial<LH.FRArtifacts & LH.FRBaseArtifacts>} */
@@ -239,7 +246,7 @@ async function _navigations({driver, config, requestedUrl, baseArtifacts, comput
239
246
  const navigationContext = {
240
247
  driver,
241
248
  navigation,
242
- requestedUrl,
249
+ requestor,
243
250
  config,
244
251
  baseArtifacts,
245
252
  computedCache,
@@ -254,7 +261,10 @@ async function _navigations({driver, config, requestedUrl, baseArtifacts, comput
254
261
  shouldHaltNavigations = true;
255
262
  }
256
263
 
257
- artifacts.URL = {requestedUrl, finalUrl: navigationResult.finalUrl};
264
+ artifacts.URL = {
265
+ requestedUrl: navigationResult.requestedUrl,
266
+ finalUrl: navigationResult.finalUrl,
267
+ };
258
268
  }
259
269
 
260
270
  LighthouseRunWarnings.push(...navigationResult.warnings);
@@ -266,33 +276,43 @@ async function _navigations({driver, config, requestedUrl, baseArtifacts, comput
266
276
  }
267
277
 
268
278
  /**
269
- * @param {{requestedUrl: string, driver: Driver, config: LH.Config.FRConfig}} args
279
+ * @param {{requestedUrl?: string, driver: Driver, config: LH.Config.FRConfig}} args
270
280
  */
271
281
  async function _cleanup({requestedUrl, driver, config}) {
272
- const didResetStorage = !config.settings.disableStorageReset;
282
+ const didResetStorage = !config.settings.disableStorageReset && requestedUrl;
273
283
  if (didResetStorage) await storage.clearDataForOrigin(driver.defaultSession, requestedUrl);
274
284
 
275
285
  await driver.disconnect();
276
286
  }
277
287
 
278
288
  /**
279
- * @param {{url: string, page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
289
+ * @param {LH.NavigationRequestor} requestor
290
+ * @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
280
291
  * @return {Promise<LH.RunnerResult|undefined>}
281
292
  */
282
- async function navigation(options) {
283
- const {url, page, configContext = {}} = options;
293
+ async function navigation(requestor, options) {
294
+ const {page, configContext = {}} = options;
284
295
  const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'navigation'});
285
296
  const computedCache = new Map();
286
297
  const internalOptions = {
287
298
  skipAboutBlank: configContext.skipAboutBlank,
288
299
  };
289
300
 
301
+ // We can't trigger the navigation through user interaction if we reset the page before starting.
302
+ if (typeof requestor !== 'string') {
303
+ internalOptions.skipAboutBlank = true;
304
+ }
305
+
290
306
  const runnerOptions = {config, computedCache};
291
307
  const artifacts = await Runner.gather(
292
308
  async () => {
293
309
  const driver = new Driver(page);
294
- const requestedUrl = URL.normalizeUrl(url);
295
- const context = {driver, config, requestedUrl, options: internalOptions};
310
+ const context = {
311
+ driver,
312
+ config,
313
+ requestor: typeof requestor === 'string' ? URL.normalizeUrl(requestor) : requestor,
314
+ options: internalOptions,
315
+ };
296
316
  const {baseArtifacts} = await _setup(context);
297
317
  const {artifacts} = await _navigations({...context, baseArtifacts, computedCache});
298
318
  await _cleanup(context);
@@ -54,11 +54,10 @@ class UserFlow {
54
54
  }
55
55
 
56
56
  /**
57
- * @param {string} url
58
57
  * @param {StepOptions=} stepOptions
59
58
  */
60
- _getNextNavigationOptions(url, stepOptions) {
61
- const options = {url, ...this.options, ...stepOptions};
59
+ _getNextNavigationOptions(stepOptions) {
60
+ const options = {...this.options, ...stepOptions};
62
61
  const configContext = {...options.configContext};
63
62
  const settingsOverrides = {...configContext.settingsOverrides};
64
63
 
@@ -81,13 +80,16 @@ class UserFlow {
81
80
  }
82
81
 
83
82
  /**
84
- * @param {string} url
83
+ * @param {LH.NavigationRequestor} requestor
85
84
  * @param {StepOptions=} stepOptions
86
85
  */
87
- async navigate(url, stepOptions) {
86
+ async navigate(requestor, stepOptions) {
88
87
  if (this.currentTimespan) throw Error('Timespan already in progress');
89
88
 
90
- const result = await navigation(this._getNextNavigationOptions(url, stepOptions));
89
+ const result = await navigation(
90
+ requestor,
91
+ this._getNextNavigationOptions(stepOptions)
92
+ );
91
93
  if (!result) throw Error('Navigation returned undefined');
92
94
 
93
95
  const providedName = stepOptions?.stepName;
@@ -78,12 +78,14 @@ function resolveWaitForFullyLoadedOptions(options) {
78
78
  * navigations.
79
79
  *
80
80
  * @param {LH.Gatherer.FRTransitionalDriver} driver
81
- * @param {string} url
81
+ * @param {LH.NavigationRequestor} requestor
82
82
  * @param {NavigationOptions} options
83
- * @return {Promise<{finalUrl: string, warnings: Array<LH.IcuMessage>}>}
83
+ * @return {Promise<{requestedUrl: string, finalUrl: string, warnings: Array<LH.IcuMessage>}>}
84
84
  */
85
- async function gotoURL(driver, url, options) {
86
- const status = {msg: `Navigating to ${url}`, id: 'lh:driver:navigate'};
85
+ async function gotoURL(driver, requestor, options) {
86
+ const status = typeof requestor === 'string' ?
87
+ {msg: `Navigating to ${requestor}`, id: 'lh:driver:navigate'} :
88
+ {msg: 'Navigating using a user defined function', id: 'lh:driver:navigate'};
87
89
  log.time(status);
88
90
 
89
91
  const session = driver.defaultSession;
@@ -94,9 +96,14 @@ async function gotoURL(driver, url, options) {
94
96
  await session.sendCommand('Page.enable');
95
97
  await session.sendCommand('Page.setLifecycleEventsEnabled', {enabled: true});
96
98
 
97
- // No timeout needed for Page.navigate. See https://github.com/GoogleChrome/lighthouse/pull/6413
98
- session.setNextProtocolTimeout(Infinity);
99
- const waitforPageNavigateCmd = session.sendCommand('Page.navigate', {url});
99
+ let waitForNavigationTriggered;
100
+ if (typeof requestor === 'string') {
101
+ // No timeout needed for Page.navigate. See https://github.com/GoogleChrome/lighthouse/pull/6413
102
+ session.setNextProtocolTimeout(Infinity);
103
+ waitForNavigationTriggered = session.sendCommand('Page.navigate', {url: requestor});
104
+ } else {
105
+ waitForNavigationTriggered = requestor();
106
+ }
100
107
 
101
108
  const waitForNavigated = options.waitUntil.includes('navigated');
102
109
  const waitForLoad = options.waitUntil.includes('load');
@@ -119,10 +126,21 @@ async function gotoURL(driver, url, options) {
119
126
 
120
127
  const waitConditions = await Promise.all(waitConditionPromises);
121
128
  const timedOut = waitConditions.some(condition => condition.timedOut);
122
- const finalUrl = (await networkMonitor.getFinalNavigationUrl()) || url;
129
+ const navigationUrls = await networkMonitor.getNavigationUrls();
130
+
131
+ let requestedUrl = navigationUrls.requestedUrl;
132
+ if (typeof requestor === 'string') {
133
+ if (requestor !== requestedUrl) {
134
+ log.error('Navigation', 'Provided URL did not match initial navigation URL');
135
+ }
136
+ requestedUrl = requestor;
137
+ }
138
+ if (!requestedUrl) throw Error('No navigations detected when running user defined requestor.');
139
+
140
+ const finalUrl = navigationUrls.finalUrl || requestedUrl;
123
141
 
124
142
  // Bring `Page.navigate` errors back into the promise chain. See https://github.com/GoogleChrome/lighthouse/pull/6739.
125
- await waitforPageNavigateCmd;
143
+ await waitForNavigationTriggered;
126
144
  await networkMonitor.disable();
127
145
 
128
146
  if (options.debugNavigation) {
@@ -131,8 +149,9 @@ async function gotoURL(driver, url, options) {
131
149
 
132
150
  log.timeEnd(status);
133
151
  return {
152
+ requestedUrl,
134
153
  finalUrl,
135
- warnings: getNavigationWarnings({timedOut, finalUrl, requestedUrl: url}),
154
+ warnings: getNavigationWarnings({timedOut, finalUrl, requestedUrl}),
136
155
  };
137
156
  }
138
157
 
@@ -133,18 +133,20 @@ class NetworkMonitor {
133
133
  this._sessions = new Map();
134
134
  }
135
135
 
136
- /** @return {Promise<string | undefined>} */
137
- async getFinalNavigationUrl() {
136
+ /** @return {Promise<{requestedUrl?: string, finalUrl?: string}>} */
137
+ async getNavigationUrls() {
138
138
  const frameNavigations = this._frameNavigations;
139
- if (!frameNavigations.length) return undefined;
139
+ if (!frameNavigations.length) return {};
140
140
 
141
141
  const resourceTreeResponse = await this._session.sendCommand('Page.getResourceTree');
142
142
  const mainFrameId = resourceTreeResponse.frameTree.frame.id;
143
143
  const mainFrameNavigations = frameNavigations.filter(frame => frame.id === mainFrameId);
144
- const finalNavigation = mainFrameNavigations[mainFrameNavigations.length - 1];
145
- if (!finalNavigation) log.warn('NetworkMonitor', 'No detected navigations');
144
+ if (!mainFrameNavigations.length) log.warn('NetworkMonitor', 'No detected navigations');
146
145
 
147
- return finalNavigation?.url;
146
+ return {
147
+ requestedUrl: mainFrameNavigations[0]?.url,
148
+ finalUrl: mainFrameNavigations[mainFrameNavigations.length - 1]?.url,
149
+ };
148
150
  }
149
151
 
150
152
  /**
@@ -71,17 +71,17 @@ async function dismissJavaScriptDialogs(session) {
71
71
 
72
72
  /**
73
73
  * @param {LH.Gatherer.FRProtocolSession} session
74
- * @param {{url: string}} navigation
74
+ * @param {string} url
75
75
  * @return {Promise<{warnings: Array<LH.IcuMessage>}>}
76
76
  */
77
- async function resetStorageForNavigation(session, navigation) {
77
+ async function resetStorageForUrl(session, url) {
78
78
  /** @type {Array<LH.IcuMessage>} */
79
79
  const warnings = [];
80
80
 
81
81
  // Reset the storage and warn if there appears to be other important data.
82
- const warning = await storage.getImportantStorageWarning(session, navigation.url);
82
+ const warning = await storage.getImportantStorageWarning(session, url);
83
83
  if (warning) warnings.push(warning);
84
- await storage.clearDataForOrigin(session, navigation.url);
84
+ await storage.clearDataForOrigin(session, url);
85
85
  await storage.clearBrowserCaches(session);
86
86
 
87
87
  return {warnings};
@@ -190,7 +190,7 @@ async function prepareTargetForNavigationMode(driver, settings) {
190
190
  *
191
191
  * @param {LH.Gatherer.FRProtocolSession} session
192
192
  * @param {LH.Config.Settings} settings
193
- * @param {Pick<LH.Config.NavigationDefn, 'disableThrottling'|'disableStorageReset'|'blockedUrlPatterns'> & {url: string}} navigation
193
+ * @param {Pick<LH.Config.NavigationDefn, 'disableThrottling'|'disableStorageReset'|'blockedUrlPatterns'> & {requestor: LH.NavigationRequestor}} navigation
194
194
  * @return {Promise<{warnings: Array<LH.IcuMessage>}>}
195
195
  */
196
196
  async function prepareTargetForIndividualNavigation(session, settings, navigation) {
@@ -200,9 +200,15 @@ async function prepareTargetForIndividualNavigation(session, settings, navigatio
200
200
  /** @type {Array<LH.IcuMessage>} */
201
201
  const warnings = [];
202
202
 
203
- const shouldResetStorage = !settings.disableStorageReset && !navigation.disableStorageReset;
203
+ const {requestor} = navigation;
204
+ const shouldResetStorage =
205
+ !settings.disableStorageReset &&
206
+ !navigation.disableStorageReset &&
207
+ // Without prior knowledge of the destination, we cannot know which URL to clear storage for.
208
+ typeof requestor === 'string';
204
209
  if (shouldResetStorage) {
205
- const {warnings: storageWarnings} = await resetStorageForNavigation(session, navigation);
210
+ const requestedUrl = requestor;
211
+ const {warnings: storageWarnings} = await resetStorageForUrl(session, requestedUrl);
206
212
  warnings.push(...storageWarnings);
207
213
  }
208
214
 
@@ -572,7 +572,7 @@ class GatherRunner {
572
572
  driver.defaultSession,
573
573
  passContext.settings,
574
574
  {
575
- url: passContext.url,
575
+ requestor: passContext.url,
576
576
  disableStorageReset: !passConfig.useThrottling,
577
577
  disableThrottling: !passConfig.useThrottling,
578
578
  blockedUrlPatterns: passConfig.blockedUrlPatterns,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.3.1-dev.20220209",
3
+ "version": "9.3.1-dev.20220210",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -463,9 +463,6 @@ limitations under the License.
463
463
  animation-delay: 2.25s, 2.25s, 2.25s;
464
464
  animation-duration: 1.25s, 1.25s, 6.25s;
465
465
  }
466
- .lh-fireworks-paused .lh-pyro > div {
467
- animation-play-state: paused;
468
- }
469
466
 
470
467
  @keyframes bang {
471
468
  to {
@@ -349,7 +349,7 @@ function createGaugePwaComponent(dom) {
349
349
  function createHeadingComponent(dom) {
350
350
  const el0 = dom.createFragment();
351
351
  const el1 = dom.createElement('style');
352
- el1.append('\n /* CSS Fireworks. Originally by Eddie Lin\n https://codepen.io/paulirish/pen/yEVMbP\n */\n .lh-pyro {\n display: none;\n z-index: 1;\n pointer-events: none;\n }\n .lh-score100 .lh-pyro {\n display: block;\n }\n .lh-score100 .lh-lighthouse stop:first-child {\n stop-color: hsla(200, 12%, 95%, 0);\n }\n .lh-score100 .lh-lighthouse stop:last-child {\n stop-color: hsla(65, 81%, 76%, 1);\n }\n\n .lh-pyro > .lh-pyro-before, .lh-pyro > .lh-pyro-after {\n position: absolute;\n width: 5px;\n height: 5px;\n border-radius: 2.5px;\n box-shadow: 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff;\n animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards;\n animation-delay: 1s, 1s, 1s;\n }\n\n .lh-pyro > .lh-pyro-after {\n animation-delay: 2.25s, 2.25s, 2.25s;\n animation-duration: 1.25s, 1.25s, 6.25s;\n }\n .lh-fireworks-paused .lh-pyro > div {\n animation-play-state: paused;\n }\n\n @keyframes bang {\n to {\n box-shadow: -70px -115.67px #47ebbc, -28px -99.67px #eb47a4, 58px -31.67px #7eeb47, 13px -141.67px #eb47c5, -19px 6.33px #7347eb, -2px -74.67px #ebd247, 24px -151.67px #eb47e0, 57px -138.67px #b4eb47, -51px -104.67px #479eeb, 62px 8.33px #ebcf47, -93px 0.33px #d547eb, -16px -118.67px #47bfeb, 53px -84.67px #47eb83, 66px -57.67px #eb47bf, -93px -65.67px #91eb47, 30px -13.67px #86eb47, -2px -59.67px #83eb47, -44px 1.33px #eb47eb, 61px -58.67px #47eb73, 5px -22.67px #47e8eb, -66px -28.67px #ebe247, 42px -123.67px #eb5547, -75px 26.33px #7beb47, 15px -52.67px #a147eb, 36px -51.67px #eb8347, -38px -12.67px #eb5547, -46px -59.67px #47eb81, 78px -114.67px #eb47ba, 15px -156.67px #eb47bf, -36px 1.33px #eb4783, -72px -86.67px #eba147, 31px -46.67px #ebe247, -68px 29.33px #47e2eb, -55px 19.33px #ebe047, -56px 27.33px #4776eb, -13px -91.67px #eb5547, -47px -138.67px #47ebc7, -18px -96.67px #eb47ac, 11px -88.67px #4783eb, -67px -28.67px #47baeb, 53px 10.33px #ba47eb, 11px 19.33px #5247eb, -5px -11.67px #eb4791, -68px -4.67px #47eba7, 95px -37.67px #eb478b, -67px -162.67px #eb5d47, -54px -120.67px #eb6847, 49px -12.67px #ebe047, 88px 8.33px #47ebda, 97px 33.33px #eb8147, 6px -71.67px #ebbc47;\n }\n }\n @keyframes gravity {\n to {\n transform: translateY(80px);\n opacity: 0;\n }\n }\n @keyframes position {\n 0%, 19.9% {\n margin-top: 4%;\n margin-left: 47%;\n }\n 20%, 39.9% {\n margin-top: 7%;\n margin-left: 30%;\n }\n 40%, 59.9% {\n margin-top: 6%;\n margin-left: 70%;\n }\n 60%, 79.9% {\n margin-top: 3%;\n margin-left: 20%;\n }\n 80%, 99.9% {\n margin-top: 3%;\n margin-left: 80%;\n }\n }\n ');
352
+ el1.append('\n /* CSS Fireworks. Originally by Eddie Lin\n https://codepen.io/paulirish/pen/yEVMbP\n */\n .lh-pyro {\n display: none;\n z-index: 1;\n pointer-events: none;\n }\n .lh-score100 .lh-pyro {\n display: block;\n }\n .lh-score100 .lh-lighthouse stop:first-child {\n stop-color: hsla(200, 12%, 95%, 0);\n }\n .lh-score100 .lh-lighthouse stop:last-child {\n stop-color: hsla(65, 81%, 76%, 1);\n }\n\n .lh-pyro > .lh-pyro-before, .lh-pyro > .lh-pyro-after {\n position: absolute;\n width: 5px;\n height: 5px;\n border-radius: 2.5px;\n box-shadow: 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff;\n animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards;\n animation-delay: 1s, 1s, 1s;\n }\n\n .lh-pyro > .lh-pyro-after {\n animation-delay: 2.25s, 2.25s, 2.25s;\n animation-duration: 1.25s, 1.25s, 6.25s;\n }\n\n @keyframes bang {\n to {\n box-shadow: -70px -115.67px #47ebbc, -28px -99.67px #eb47a4, 58px -31.67px #7eeb47, 13px -141.67px #eb47c5, -19px 6.33px #7347eb, -2px -74.67px #ebd247, 24px -151.67px #eb47e0, 57px -138.67px #b4eb47, -51px -104.67px #479eeb, 62px 8.33px #ebcf47, -93px 0.33px #d547eb, -16px -118.67px #47bfeb, 53px -84.67px #47eb83, 66px -57.67px #eb47bf, -93px -65.67px #91eb47, 30px -13.67px #86eb47, -2px -59.67px #83eb47, -44px 1.33px #eb47eb, 61px -58.67px #47eb73, 5px -22.67px #47e8eb, -66px -28.67px #ebe247, 42px -123.67px #eb5547, -75px 26.33px #7beb47, 15px -52.67px #a147eb, 36px -51.67px #eb8347, -38px -12.67px #eb5547, -46px -59.67px #47eb81, 78px -114.67px #eb47ba, 15px -156.67px #eb47bf, -36px 1.33px #eb4783, -72px -86.67px #eba147, 31px -46.67px #ebe247, -68px 29.33px #47e2eb, -55px 19.33px #ebe047, -56px 27.33px #4776eb, -13px -91.67px #eb5547, -47px -138.67px #47ebc7, -18px -96.67px #eb47ac, 11px -88.67px #4783eb, -67px -28.67px #47baeb, 53px 10.33px #ba47eb, 11px 19.33px #5247eb, -5px -11.67px #eb4791, -68px -4.67px #47eba7, 95px -37.67px #eb478b, -67px -162.67px #eb5d47, -54px -120.67px #eb6847, 49px -12.67px #ebe047, 88px 8.33px #47ebda, 97px 33.33px #eb8147, 6px -71.67px #ebbc47;\n }\n }\n @keyframes gravity {\n to {\n transform: translateY(80px);\n opacity: 0;\n }\n }\n @keyframes position {\n 0%, 19.9% {\n margin-top: 4%;\n margin-left: 47%;\n }\n 20%, 39.9% {\n margin-top: 7%;\n margin-left: 30%;\n }\n 40%, 59.9% {\n margin-top: 6%;\n margin-left: 70%;\n }\n 60%, 79.9% {\n margin-top: 3%;\n margin-left: 20%;\n }\n 80%, 99.9% {\n margin-top: 3%;\n margin-left: 80%;\n }\n }\n ');
353
353
  el0.append(el1);
354
354
  const el2 = dom.createElement('div', 'lh-header-container');
355
355
  const el3 = dom.createElement('div', 'lh-scores-wrapper-placeholder');
@@ -180,9 +180,6 @@ export class ReportUIFeatures {
180
180
  _enableFireworks() {
181
181
  const scoresContainer = this._dom.find('.lh-scores-container', this._dom.rootEl);
182
182
  scoresContainer.classList.add('lh-score100');
183
- scoresContainer.addEventListener('click', _ => {
184
- scoresContainer.classList.toggle('lh-fireworks-paused');
185
- });
186
183
  }
187
184
 
188
185
  _setupMediaQueryListeners() {
@@ -26,6 +26,7 @@ import Treemap_ from './lhr/treemap';
26
26
  declare global {
27
27
  module LH {
28
28
  export type ArbitraryEqualityMap = ArbitraryEqualityMap_;
29
+ export type NavigationRequestor = string | (() => Promise<void> | void);
29
30
 
30
31
  // artifacts.d.ts
31
32
  export import Artifacts = Artifacts_.Artifacts;