lighthouse 9.5.0-dev.20220327 → 9.5.0-dev.20220330

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 (70) hide show
  1. package/dist/report/bundle.esm.js +20 -18
  2. package/dist/report/flow.js +1 -1
  3. package/dist/report/standalone.js +1 -1
  4. package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +26 -18
  5. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
  6. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +1 -1
  7. package/lighthouse-core/audits/byte-efficiency/efficient-animated-content.js +1 -1
  8. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +15 -16
  9. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +3 -2
  10. package/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js +2 -2
  11. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +1 -1
  12. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +1 -1
  13. package/lighthouse-core/audits/byte-efficiency/uses-text-compression.js +1 -1
  14. package/lighthouse-core/audits/dobetterweb/uses-http2.js +2 -1
  15. package/lighthouse-core/audits/long-tasks.js +3 -2
  16. package/lighthouse-core/audits/metrics/first-contentful-paint-3g.js +2 -2
  17. package/lighthouse-core/audits/metrics/first-contentful-paint.js +3 -2
  18. package/lighthouse-core/audits/metrics/first-meaningful-paint.js +3 -2
  19. package/lighthouse-core/audits/metrics/interactive.js +3 -2
  20. package/lighthouse-core/audits/metrics/largest-contentful-paint.js +3 -2
  21. package/lighthouse-core/audits/metrics/max-potential-fid.js +3 -2
  22. package/lighthouse-core/audits/metrics/speed-index.js +3 -2
  23. package/lighthouse-core/audits/metrics/total-blocking-time.js +3 -2
  24. package/lighthouse-core/audits/metrics.js +3 -2
  25. package/lighthouse-core/audits/predictive-perf.js +2 -1
  26. package/lighthouse-core/audits/preload-lcp-image.js +1 -1
  27. package/lighthouse-core/audits/redirects.js +1 -1
  28. package/lighthouse-core/audits/timing-budget.js +3 -2
  29. package/lighthouse-core/audits/uses-rel-preconnect.js +1 -1
  30. package/lighthouse-core/audits/uses-rel-preload.js +1 -1
  31. package/lighthouse-core/computed/critical-request-chains.js +2 -9
  32. package/lighthouse-core/computed/main-resource.js +1 -1
  33. package/lighthouse-core/computed/metrics/first-contentful-paint-all-frames.js +1 -1
  34. package/lighthouse-core/computed/metrics/first-contentful-paint.js +1 -1
  35. package/lighthouse-core/computed/metrics/first-meaningful-paint.js +1 -1
  36. package/lighthouse-core/computed/metrics/interactive.js +1 -1
  37. package/lighthouse-core/computed/metrics/lantern-first-contentful-paint.js +1 -1
  38. package/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js +1 -1
  39. package/lighthouse-core/computed/metrics/lantern-interactive.js +1 -1
  40. package/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js +1 -1
  41. package/lighthouse-core/computed/metrics/lantern-max-potential-fid.js +1 -1
  42. package/lighthouse-core/computed/metrics/lantern-metric.js +3 -5
  43. package/lighthouse-core/computed/metrics/lantern-speed-index.js +1 -1
  44. package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +1 -1
  45. package/lighthouse-core/computed/metrics/largest-contentful-paint-all-frames.js +1 -1
  46. package/lighthouse-core/computed/metrics/largest-contentful-paint.js +1 -1
  47. package/lighthouse-core/computed/metrics/max-potential-fid.js +1 -1
  48. package/lighthouse-core/computed/metrics/metric.js +1 -0
  49. package/lighthouse-core/computed/metrics/speed-index.js +1 -1
  50. package/lighthouse-core/computed/metrics/timing-summary.js +13 -6
  51. package/lighthouse-core/computed/metrics/total-blocking-time.js +1 -1
  52. package/lighthouse-core/computed/page-dependency-graph.js +54 -31
  53. package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +8 -1
  54. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +32 -22
  55. package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +6 -2
  56. package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +8 -4
  57. package/lighthouse-core/gather/driver/execution-context.js +4 -2
  58. package/lighthouse-core/gather/driver/navigation.js +9 -9
  59. package/lighthouse-core/gather/driver/network-monitor.js +16 -3
  60. package/lighthouse-core/gather/driver.js +5 -0
  61. package/lighthouse-core/gather/gather-runner.js +13 -7
  62. package/lighthouse-core/gather/gatherers/js-usage.js +7 -6
  63. package/lighthouse-core/gather/gatherers/scripts.js +20 -2
  64. package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +8 -29
  65. package/lighthouse-core/lib/navigation-error.js +13 -4
  66. package/lighthouse-core/util-commonjs.js +20 -18
  67. package/package.json +1 -1
  68. package/report/renderer/util.js +20 -18
  69. package/types/artifacts.d.ts +25 -2
  70. package/types/gatherer.d.ts +1 -0
@@ -8,13 +8,14 @@
8
8
  const makeComputedArtifact = require('./computed-artifact.js');
9
9
  const NetworkNode = require('../lib/dependency-graph/network-node.js');
10
10
  const CPUNode = require('../lib/dependency-graph/cpu-node.js');
11
- const NetworkAnalyzer = require('../lib/dependency-graph/simulator/network-analyzer.js');
12
11
  const TracingProcessor = require('../lib/tracehouse/trace-processor.js');
13
12
  const NetworkRequest = require('../lib/network-request.js');
14
13
  const ProcessedTrace = require('./processed-trace.js');
15
14
  const NetworkRecords = require('./network-records.js');
15
+ const NetworkAnalyzer = require('../lib/dependency-graph/simulator/network-analyzer.js');
16
16
 
17
17
  /** @typedef {import('../lib/dependency-graph/base-node.js').Node} Node */
18
+ /** @typedef {Omit<LH.Artifacts['URL'], 'initialUrl'|'finalUrl'>} URLArtifact */
18
19
 
19
20
  // Shorter tasks have negligible impact on simulation results.
20
21
  const SIGNIFICANT_DUR_THRESHOLD_MS = 10;
@@ -388,37 +389,24 @@ class PageDependencyGraph {
388
389
  /**
389
390
  * @param {LH.Artifacts.ProcessedTrace} processedTrace
390
391
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
392
+ * @param {URLArtifact} URL
391
393
  * @return {Node}
392
394
  */
393
- static createGraph(processedTrace, networkRecords) {
395
+ static createGraph(processedTrace, networkRecords, URL) {
394
396
  const networkNodeOutput = PageDependencyGraph.getNetworkNodeOutput(networkRecords);
395
397
  const cpuNodes = PageDependencyGraph.getCPUNodes(processedTrace);
398
+ const {requestedUrl, mainDocumentUrl} = URL;
399
+ if (!mainDocumentUrl) throw new Error('mainDocumentUrl is required to get the main resource');
396
400
 
397
- // TODO: Remove this usage of `findMainDocument` and create a new function to get the first document request.
398
- // https://github.com/GoogleChrome/lighthouse/issues/8984
399
- //
400
- // The main document request is the earliest network request *of type document*.
401
- // This will be different from the root request when there are server redirects.
402
- const mainDocumentRequest = NetworkAnalyzer.findMainDocument(networkRecords);
403
- const mainDocumentNode = networkNodeOutput.idToNodeMap.get(mainDocumentRequest.requestId);
404
- if (!mainDocumentNode) {
405
- // mainDocumentNode should always be found.
406
- throw new Error('mainDocumentNode not found.');
407
- }
401
+ const rootRequest = NetworkAnalyzer.findResourceForUrl(networkRecords, requestedUrl);
402
+ if (!rootRequest) throw new Error('rootRequest not found');
403
+ const rootNode = networkNodeOutput.idToNodeMap.get(rootRequest.requestId);
404
+ if (!rootNode) throw new Error('rootNode not found');
408
405
 
409
- // The root request is the earliest request in the main document redirect chain.
410
- // Will be undefined if there were no redirects.
411
- const rootRequest = mainDocumentNode.record.redirects?.[0];
412
-
413
- let rootNode;
414
- if (rootRequest) {
415
- // rootNode should always be found.
416
- rootNode = rootRequest && networkNodeOutput.idToNodeMap.get(rootRequest.requestId);
417
- if (!rootNode) throw new Error('rootNode not found');
418
- } else {
419
- // Use main document as root if there were no redirects.
420
- rootNode = mainDocumentNode;
421
- }
406
+ const mainDocumentRequest = NetworkAnalyzer.findResourceForUrl(networkRecords, mainDocumentUrl);
407
+ if (!mainDocumentRequest) throw new Error('mainDocumentRequest not found');
408
+ const mainDocumentNode = networkNodeOutput.idToNodeMap.get(mainDocumentRequest.requestId);
409
+ if (!mainDocumentNode) throw new Error('mainDocumentNode not found');
422
410
 
423
411
  PageDependencyGraph.linkNetworkNodes(rootNode, networkNodeOutput);
424
412
  PageDependencyGraph.linkCPUNodes(rootNode, networkNodeOutput, cpuNodes);
@@ -464,23 +452,58 @@ class PageDependencyGraph {
464
452
  }
465
453
 
466
454
  /**
467
- * @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
455
+ * Recalculate `artifacts.URL` for clients that don't provide it.
456
+ *
457
+ * @param {LH.DevtoolsLog} devtoolsLog
458
+ * @param {LH.Artifacts.NetworkRequest[]} networkRecords
459
+ * @param {LH.Artifacts.ProcessedTrace} processedTrace
460
+ * @return {URLArtifact}
461
+ */
462
+ static getDocumentUrls(devtoolsLog, networkRecords, processedTrace) {
463
+ const mainFrameId = processedTrace.mainFrameIds.frameId;
464
+
465
+ /** @type {string|undefined} */
466
+ let requestedUrl;
467
+ /** @type {string|undefined} */
468
+ let mainDocumentUrl;
469
+ for (const event of devtoolsLog) {
470
+ if (event.method === 'Page.frameNavigated' && event.params.frame.id === mainFrameId) {
471
+ const {url} = event.params.frame;
472
+ // Only set requestedUrl on the first main frame navigation.
473
+ if (!requestedUrl) requestedUrl = url;
474
+ mainDocumentUrl = url;
475
+ }
476
+ }
477
+ if (!requestedUrl || !mainDocumentUrl) throw new Error('No main frame navigations found');
478
+
479
+ const initialRequest = NetworkAnalyzer.findResourceForUrl(networkRecords, requestedUrl);
480
+ if (initialRequest?.redirects?.length) requestedUrl = initialRequest.redirects[0].url;
481
+
482
+ return {requestedUrl, mainDocumentUrl};
483
+ }
484
+
485
+ /**
486
+ * @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, URL: LH.Artifacts['URL']}} data
468
487
  * @param {LH.Artifacts.ComputedContext} context
469
488
  * @return {Promise<Node>}
470
489
  */
471
490
  static async compute_(data, context) {
472
- const trace = data.trace;
473
- const devtoolsLog = data.devtoolsLog;
491
+ const {trace, devtoolsLog} = data;
474
492
  const [processedTrace, networkRecords] = await Promise.all([
475
493
  ProcessedTrace.request(trace, context),
476
494
  NetworkRecords.request(devtoolsLog, context),
477
495
  ]);
478
496
 
479
- return PageDependencyGraph.createGraph(processedTrace, networkRecords);
497
+ // COMPAT: Backport for pre-10.0 clients that don't pass the URL artifact here (e.g. pubads).
498
+ // Calculates the URL artifact from the processed trace and DT log.
499
+ const URL = data.URL ||
500
+ PageDependencyGraph.getDocumentUrls(devtoolsLog, networkRecords, processedTrace);
501
+
502
+ return PageDependencyGraph.createGraph(processedTrace, networkRecords, URL);
480
503
  }
481
504
  }
482
505
 
483
- module.exports = makeComputedArtifact(PageDependencyGraph, ['devtoolsLog', 'trace']);
506
+ module.exports = makeComputedArtifact(PageDependencyGraph, ['devtoolsLog', 'trace', 'URL']);
484
507
 
485
508
  /**
486
509
  * @typedef {Object} NetworkNodeOutput
@@ -35,7 +35,12 @@ async function getBaseArtifacts(config, driver, context) {
35
35
  HostFormFactor: userAgent.includes('Android') || userAgent.includes('Mobile') ?
36
36
  'mobile' : 'desktop',
37
37
  // Contextual artifacts whose collection changes based on gather mode.
38
- URL: {requestedUrl: '', finalUrl: ''},
38
+ // TODO: Make `requestedUrl` optional in timespan and snapshot modes.
39
+ URL: {
40
+ initialUrl: '',
41
+ requestedUrl: '',
42
+ finalUrl: '',
43
+ },
39
44
  PageLoadError: null,
40
45
  GatherContext: context,
41
46
  // Artifacts that have been replaced by regular gatherers in Fraggle Rock.
@@ -87,6 +92,8 @@ function finalizeArtifacts(baseArtifacts, gathererArtifacts) {
87
92
  }
88
93
 
89
94
  // Check that the runner remembered to mutate the special-case URL artifact.
95
+ // TODO: Make `requestedUrl` optional.
96
+ if (!artifacts.URL.initialUrl) throw new Error('Runner did not set initialUrl');
90
97
  if (!artifacts.URL.requestedUrl) throw new Error('Runner did not set requestedUrl');
91
98
  if (!artifacts.URL.finalUrl) throw new Error('Runner did not set finalUrl');
92
99
 
@@ -44,17 +44,16 @@ 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, requestor: LH.NavigationRequestor, options?: InternalOptions}} args
47
+ * @param {{driver: Driver, config: LH.Config.FRConfig, options?: InternalOptions}} args
48
48
  * @return {Promise<{baseArtifacts: LH.FRBaseArtifacts}>}
49
49
  */
50
- async function _setup({driver, config, requestor, options}) {
50
+ async function _setup({driver, config, 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
- if (typeof requestor === 'string') baseArtifacts.URL.requestedUrl = requestor;
58
57
 
59
58
  await prepare.prepareTargetForNavigationMode(driver, config.settings);
60
59
 
@@ -90,25 +89,32 @@ async function _cleanupNavigation({driver}) {
90
89
 
91
90
  /**
92
91
  * @param {NavigationContext} navigationContext
93
- * @return {Promise<{requestedUrl: string, finalUrl: string, navigationError: LH.LighthouseError | undefined, warnings: Array<LH.IcuMessage>}>}
92
+ * @return {Promise<{requestedUrl: string, mainDocumentUrl: string, navigationError: LH.LighthouseError | undefined, warnings: Array<LH.IcuMessage>}>}
94
93
  */
95
94
  async function _navigate(navigationContext) {
96
95
  const {driver, config, requestor} = navigationContext;
97
96
 
98
97
  try {
99
- const {requestedUrl, finalUrl, warnings} = await gotoURL(driver, requestor, {
98
+ const {requestedUrl, mainDocumentUrl, warnings} = await gotoURL(driver, requestor, {
100
99
  ...navigationContext.navigation,
101
100
  debugNavigation: config.settings.debugNavigation,
102
101
  maxWaitForFcp: config.settings.maxWaitForFcp,
103
102
  maxWaitForLoad: config.settings.maxWaitForLoad,
104
103
  waitUntil: navigationContext.navigation.pauseAfterFcpMs ? ['fcp', 'load'] : ['load'],
105
104
  });
106
- return {requestedUrl, finalUrl, navigationError: undefined, warnings};
105
+ return {requestedUrl, mainDocumentUrl, navigationError: undefined, warnings};
107
106
  } catch (err) {
108
107
  if (!(err instanceof LighthouseError)) throw err;
109
108
  if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG') throw err;
110
109
  if (typeof requestor !== 'string') throw err;
111
- return {requestedUrl: requestor, finalUrl: requestor, navigationError: err, warnings: []};
110
+
111
+ // TODO: Make the urls optional here so we don't need to throw an error with a callback requestor.
112
+ return {
113
+ requestedUrl: requestor,
114
+ mainDocumentUrl: requestor,
115
+ navigationError: err,
116
+ warnings: [],
117
+ };
112
118
  }
113
119
  }
114
120
 
@@ -152,7 +158,7 @@ async function _collectDebugData(navigationContext, phaseState) {
152
158
  * @param {PhaseState} phaseState
153
159
  * @param {Awaited<ReturnType<typeof _setupNavigation>>} setupResult
154
160
  * @param {Awaited<ReturnType<typeof _navigate>>} navigateResult
155
- * @return {Promise<{requestedUrl: string, finalUrl: string, artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
161
+ * @return {Promise<{artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
156
162
  */
157
163
  async function _computeNavigationResult(
158
164
  navigationContext,
@@ -160,12 +166,12 @@ async function _computeNavigationResult(
160
166
  setupResult,
161
167
  navigateResult
162
168
  ) {
163
- const {navigationError, finalUrl} = navigateResult;
169
+ const {navigationError, mainDocumentUrl} = navigateResult;
164
170
  const warnings = [...setupResult.warnings, ...navigateResult.warnings];
165
171
  const debugData = await _collectDebugData(navigationContext, phaseState);
166
172
  const pageLoadError = debugData.records
167
173
  ? getPageLoadError(navigationError, {
168
- url: finalUrl,
174
+ url: mainDocumentUrl,
169
175
  loadFailureMode: navigationContext.navigation.loadFailureMode,
170
176
  networkRecords: debugData.records,
171
177
  })
@@ -183,8 +189,6 @@ async function _computeNavigationResult(
183
189
  if (debugData.trace) artifacts.traces = {[pageLoadErrorId]: debugData.trace};
184
190
 
185
191
  return {
186
- requestedUrl: navigateResult.requestedUrl,
187
- finalUrl,
188
192
  pageLoadError,
189
193
  artifacts,
190
194
  warnings: [...warnings, pageLoadError.friendlyMessage],
@@ -194,8 +198,6 @@ async function _computeNavigationResult(
194
198
 
195
199
  const artifacts = await awaitArtifacts(phaseState.artifactState);
196
200
  return {
197
- requestedUrl: navigateResult.requestedUrl,
198
- finalUrl,
199
201
  artifacts,
200
202
  warnings,
201
203
  pageLoadError: undefined,
@@ -209,8 +211,9 @@ async function _computeNavigationResult(
209
211
  */
210
212
  async function _navigation(navigationContext) {
211
213
  const artifactState = getEmptyArtifactState();
214
+ const initialUrl = await navigationContext.driver.url();
212
215
  const phaseState = {
213
- url: await navigationContext.driver.url(),
216
+ url: initialUrl,
214
217
  gatherMode: /** @type {const} */ ('navigation'),
215
218
  driver: navigationContext.driver,
216
219
  computedCache: navigationContext.computedCache,
@@ -224,8 +227,20 @@ async function _navigation(navigationContext) {
224
227
  await collectPhaseArtifacts({phase: 'startInstrumentation', ...phaseState});
225
228
  await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseState});
226
229
  const navigateResult = await _navigate(navigationContext);
227
- phaseState.baseArtifacts.URL.finalUrl = navigateResult.finalUrl;
228
- phaseState.url = navigateResult.finalUrl;
230
+
231
+ // Every required url is initialized to an empty string in `getBaseArtifacts`.
232
+ // If we haven't set the required urls yet, set them here.
233
+ const {URL} = phaseState.baseArtifacts;
234
+ if (!URL.finalUrl || !URL.initialUrl) {
235
+ phaseState.baseArtifacts.URL = {
236
+ initialUrl,
237
+ requestedUrl: navigateResult.requestedUrl,
238
+ mainDocumentUrl: navigateResult.mainDocumentUrl,
239
+ finalUrl: navigateResult.mainDocumentUrl,
240
+ };
241
+ }
242
+ phaseState.url = navigateResult.mainDocumentUrl;
243
+
229
244
  await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseState});
230
245
  await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseState});
231
246
  await _cleanupNavigation(navigationContext);
@@ -263,11 +278,6 @@ async function _navigations({driver, config, requestor, baseArtifacts, computedC
263
278
  artifacts.PageLoadError = navigationResult.pageLoadError;
264
279
  shouldHaltNavigations = true;
265
280
  }
266
-
267
- artifacts.URL = {
268
- requestedUrl: navigationResult.requestedUrl,
269
- finalUrl: navigationResult.finalUrl,
270
- };
271
281
  }
272
282
 
273
283
  LighthouseRunWarnings.push(...navigationResult.warnings);
@@ -36,8 +36,12 @@ async function snapshotGather(options) {
36
36
  const artifacts = await Runner.gather(
37
37
  async () => {
38
38
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'snapshot'});
39
- baseArtifacts.URL.requestedUrl = url;
40
- baseArtifacts.URL.finalUrl = url;
39
+ baseArtifacts.URL = {
40
+ initialUrl: url,
41
+ // TODO: Remove `requestedUrl` from snapshot mode.
42
+ requestedUrl: url,
43
+ finalUrl: url,
44
+ };
41
45
 
42
46
  const artifactDefinitions = config.artifacts || [];
43
47
  const artifactState = getEmptyArtifactState();
@@ -32,12 +32,12 @@ async function startTimespanGather(options) {
32
32
  /** @type {Map<string, LH.ArbitraryEqualityMap>} */
33
33
  const computedCache = new Map();
34
34
  const artifactDefinitions = config.artifacts || [];
35
- const requestedUrl = await driver.url();
35
+ const initialUrl = await driver.url();
36
36
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'timespan'});
37
37
  const artifactState = getEmptyArtifactState();
38
38
  /** @type {Omit<import('./runner-helpers.js').CollectPhaseArtifactOptions, 'phase'>} */
39
39
  const phaseOptions = {
40
- url: requestedUrl,
40
+ url: initialUrl,
41
41
  driver,
42
42
  artifactDefinitions,
43
43
  artifactState,
@@ -59,8 +59,12 @@ async function startTimespanGather(options) {
59
59
  const runnerOptions = {config, computedCache};
60
60
  const artifacts = await Runner.gather(
61
61
  async () => {
62
- baseArtifacts.URL.requestedUrl = requestedUrl;
63
- baseArtifacts.URL.finalUrl = finalUrl;
62
+ baseArtifacts.URL = {
63
+ initialUrl,
64
+ // TODO: Remove requestedUrl from timespan mode
65
+ requestedUrl: initialUrl,
66
+ finalUrl,
67
+ };
64
68
 
65
69
  await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseOptions});
66
70
  await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseOptions});
@@ -97,7 +97,8 @@ class ExecutionContext {
97
97
  .catch(${pageFunctions.wrapRuntimeEvalErrorInBrowserString})
98
98
  .then(resolve);
99
99
  });
100
- }())`,
100
+ }())
101
+ //# sourceURL=_lighthouse-eval.js`,
101
102
  includeCommandLineAPI: true,
102
103
  awaitPromise: true,
103
104
  returnByValue: true,
@@ -195,7 +196,8 @@ class ExecutionContext {
195
196
  ${ExecutionContext._cachedNativesPreamble};
196
197
  ${depsSerialized};
197
198
  (${mainFn})(${argsSerialized});
198
- })()`;
199
+ })()
200
+ //# sourceURL=_lighthouse-eval.js`;
199
201
 
200
202
  await this._session.sendCommand('Page.addScriptToEvaluateOnNewDocument', {source: expression});
201
203
  }
@@ -80,7 +80,7 @@ function resolveWaitForFullyLoadedOptions(options) {
80
80
  * @param {LH.Gatherer.FRTransitionalDriver} driver
81
81
  * @param {LH.NavigationRequestor} requestor
82
82
  * @param {NavigationOptions} options
83
- * @return {Promise<{requestedUrl: string, finalUrl: string, warnings: Array<LH.IcuMessage>}>}
83
+ * @return {Promise<{requestedUrl: string, mainDocumentUrl: string, warnings: Array<LH.IcuMessage>}>}
84
84
  */
85
85
  async function gotoURL(driver, requestor, options) {
86
86
  const status = typeof requestor === 'string' ?
@@ -130,14 +130,14 @@ async function gotoURL(driver, requestor, options) {
130
130
 
131
131
  let requestedUrl = navigationUrls.requestedUrl;
132
132
  if (typeof requestor === 'string') {
133
- if (requestor !== requestedUrl) {
133
+ if (requestedUrl && !URL.equalWithExcludedFragments(requestor, requestedUrl)) {
134
134
  log.error('Navigation', 'Provided URL did not match initial navigation URL');
135
135
  }
136
136
  requestedUrl = requestor;
137
137
  }
138
138
  if (!requestedUrl) throw Error('No navigations detected when running user defined requestor.');
139
139
 
140
- const finalUrl = navigationUrls.finalUrl || requestedUrl;
140
+ const mainDocumentUrl = navigationUrls.mainDocumentUrl || requestedUrl;
141
141
 
142
142
  // Bring `Page.navigate` errors back into the promise chain. See https://github.com/GoogleChrome/lighthouse/pull/6739.
143
143
  await waitForNavigationTriggered;
@@ -150,26 +150,26 @@ async function gotoURL(driver, requestor, options) {
150
150
  log.timeEnd(status);
151
151
  return {
152
152
  requestedUrl,
153
- finalUrl,
154
- warnings: getNavigationWarnings({timedOut, finalUrl, requestedUrl}),
153
+ mainDocumentUrl,
154
+ warnings: getNavigationWarnings({timedOut, mainDocumentUrl, requestedUrl}),
155
155
  };
156
156
  }
157
157
 
158
158
  /**
159
- * @param {{timedOut: boolean, requestedUrl: string, finalUrl: string; }} navigation
159
+ * @param {{timedOut: boolean, requestedUrl: string, mainDocumentUrl: string; }} navigation
160
160
  * @return {Array<LH.IcuMessage>}
161
161
  */
162
162
  function getNavigationWarnings(navigation) {
163
- const {requestedUrl, finalUrl} = navigation;
163
+ const {requestedUrl, mainDocumentUrl} = navigation;
164
164
  /** @type {Array<LH.IcuMessage>} */
165
165
  const warnings = [];
166
166
 
167
167
  if (navigation.timedOut) warnings.push(str_(UIStrings.warningTimeout));
168
168
 
169
- if (!URL.equalWithExcludedFragments(requestedUrl, finalUrl)) {
169
+ if (!URL.equalWithExcludedFragments(requestedUrl, mainDocumentUrl)) {
170
170
  warnings.push(str_(UIStrings.warningRedirected, {
171
171
  requested: requestedUrl,
172
- final: finalUrl,
172
+ final: mainDocumentUrl,
173
173
  }));
174
174
  }
175
175
 
@@ -133,7 +133,7 @@ class NetworkMonitor {
133
133
  this._sessions = new Map();
134
134
  }
135
135
 
136
- /** @return {Promise<{requestedUrl?: string, finalUrl?: string}>} */
136
+ /** @return {Promise<{requestedUrl?: string, mainDocumentUrl?: string}>} */
137
137
  async getNavigationUrls() {
138
138
  const frameNavigations = this._frameNavigations;
139
139
  if (!frameNavigations.length) return {};
@@ -143,9 +143,22 @@ class NetworkMonitor {
143
143
  const mainFrameNavigations = frameNavigations.filter(frame => frame.id === mainFrameId);
144
144
  if (!mainFrameNavigations.length) log.warn('NetworkMonitor', 'No detected navigations');
145
145
 
146
+ // The requested URL is the initiator request for the first frame navigation.
147
+ /** @type {string|undefined} */
148
+ let requestedUrl = mainFrameNavigations[0]?.url;
149
+ if (this._networkRecorder) {
150
+ const records = this._networkRecorder.getRawRecords();
151
+
152
+ let initialUrlRequest = records.find(record => record.url === requestedUrl);
153
+ while (initialUrlRequest?.redirectSource) {
154
+ initialUrlRequest = initialUrlRequest.redirectSource;
155
+ requestedUrl = initialUrlRequest.url;
156
+ }
157
+ }
158
+
146
159
  return {
147
- requestedUrl: mainFrameNavigations[0]?.url,
148
- finalUrl: mainFrameNavigations[mainFrameNavigations.length - 1]?.url,
160
+ requestedUrl,
161
+ mainDocumentUrl: mainFrameNavigations[mainFrameNavigations.length - 1]?.url,
149
162
  };
150
163
  }
151
164
 
@@ -470,6 +470,11 @@ class Driver {
470
470
  this._devtoolsLog.endRecording();
471
471
  return this._devtoolsLog.messages;
472
472
  }
473
+
474
+ async url() {
475
+ const {frameTree} = await this.sendCommand('Page.getFrameTree');
476
+ return `${frameTree.frame.url}${frameTree.frame.urlFragment || ''}`;
477
+ }
473
478
  }
474
479
 
475
480
  module.exports = Driver;
@@ -71,7 +71,7 @@ class GatherRunner {
71
71
  log.time(status);
72
72
  try {
73
73
  const requestedUrl = passContext.url;
74
- const {finalUrl, warnings} = await navigation.gotoURL(driver, requestedUrl, {
74
+ const {mainDocumentUrl, warnings} = await navigation.gotoURL(driver, requestedUrl, {
75
75
  waitUntil: passContext.passConfig.recordTrace ?
76
76
  ['load', 'fcp'] : ['load'],
77
77
  debugNavigation: passContext.settings.debugNavigation,
@@ -79,8 +79,12 @@ class GatherRunner {
79
79
  maxWaitForLoad: passContext.settings.maxWaitForLoad,
80
80
  ...passContext.passConfig,
81
81
  });
82
- passContext.url = finalUrl;
83
- passContext.baseArtifacts.URL.finalUrl = finalUrl;
82
+ passContext.url = mainDocumentUrl;
83
+ const {URL} = passContext.baseArtifacts;
84
+ if (!URL.finalUrl || !URL.mainDocumentUrl) {
85
+ URL.finalUrl = mainDocumentUrl;
86
+ URL.mainDocumentUrl = mainDocumentUrl;
87
+ }
84
88
  if (passContext.passConfig.loadFailureMode === 'fatal') {
85
89
  passContext.LighthouseRunWarnings.push(...warnings);
86
90
  }
@@ -408,7 +412,12 @@ class GatherRunner {
408
412
  devtoolsLogs: {},
409
413
  settings: options.settings,
410
414
  GatherContext: {gatherMode: 'navigation'},
411
- URL: {requestedUrl: options.requestedUrl, finalUrl: options.requestedUrl},
415
+ URL: {
416
+ initialUrl: await options.driver.url(),
417
+ requestedUrl: options.requestedUrl,
418
+ mainDocumentUrl: '',
419
+ finalUrl: '',
420
+ },
412
421
  Timing: [],
413
422
  PageLoadError: null,
414
423
  };
@@ -426,9 +435,6 @@ class GatherRunner {
426
435
 
427
436
  const baseArtifacts = passContext.baseArtifacts;
428
437
 
429
- // Copy redirected URL to artifact.
430
- baseArtifacts.URL.finalUrl = passContext.url;
431
-
432
438
  // Fetch the manifest, if it exists.
433
439
  try {
434
440
  baseArtifacts.WebAppManifest = await WebAppManifest.getWebAppManifest(
@@ -50,12 +50,13 @@ class JsUsage extends FRGatherer {
50
50
  const usageByScriptId = {};
51
51
 
52
52
  for (const scriptUsage of this._scriptUsages) {
53
- // If `url` is blank, that means the script was anonymous (eval, new Function, onload, ...).
54
- // Or, it's because it was code Lighthouse over the protocol via `Runtime.evaluate`.
55
- // We currently don't consider coverage of anonymous scripts, and we definitely don't want
56
- // coverage of code Lighthouse ran to inspect the page, so we ignore this ScriptCoverage if
57
- // url is blank.
58
- if (scriptUsage.url === '') {
53
+ // If `url` is blank, that means the script was dynamically
54
+ // created (eval, new Function, onload, ...)
55
+ if (scriptUsage.url === '' || scriptUsage.url === '_lighthouse-eval.js') {
56
+ // We currently don't consider coverage of dynamic scripts, and we definitely don't want
57
+ // coverage of code Lighthouse ran to inspect the page, so we ignore this ScriptCoverage.
58
+ // Audits would work the same without this, it is only an optimization (not tracking coverage
59
+ // for scripts we don't care about).
59
60
  continue;
60
61
  }
61
62
 
@@ -28,6 +28,23 @@ async function runInSeriesOrParallel(values, promiseMapper, runInSeries) {
28
28
  }
29
29
  }
30
30
 
31
+ /**
32
+ * Returns true if the script was created via our own calls
33
+ * to Runtime.evaluate.
34
+ * @param {LH.Crdp.Debugger.ScriptParsedEvent} script
35
+ */
36
+ function isLighthouseRuntimeEvaluateScript(script) {
37
+ // Scripts created by Runtime.evaluate that run on the main session/frame
38
+ // result in an empty string for the embedderName.
39
+ // Or, it means the script was dynamically created (eval, new Function, onload, ...)
40
+ if (!script.embedderName) return true;
41
+
42
+ // Otherwise, when running our own code inside other frames, the embedderName
43
+ // is set to the frame's url. In that case, we rely on the special sourceURL that
44
+ // we set.
45
+ return script.hasSourceURL && script.url === '_lighthouse-eval.js';
46
+ }
47
+
31
48
  /**
32
49
  * @fileoverview Gets JavaScript file contents.
33
50
  */
@@ -68,8 +85,9 @@ class Scripts extends FRGatherer {
68
85
  // it also blocks scripts from the same origin but that happen to run in a different process,
69
86
  // like a worker.
70
87
  if (event.method === 'Debugger.scriptParsed' && !sessionId) {
71
- // Events without an embedderName (read: a url) are for JS that we ran over the protocol.
72
- if (event.params.embedderName) this._scriptParsedEvents.push(event.params);
88
+ if (!isLighthouseRuntimeEvaluateScript(event.params)) {
89
+ this._scriptParsedEvents.push(event.params);
90
+ }
73
91
  }
74
92
  }
75
93
 
@@ -6,7 +6,6 @@
6
6
  'use strict';
7
7
 
8
8
  const INITIAL_CWD = 14 * 1024;
9
- const NetworkRequest = require('../../network-request.js');
10
9
  const URL = require('../../url-shim.js');
11
10
 
12
11
  // Assume that 40% of TTFB was server response time by default for static assets
@@ -434,40 +433,20 @@ class NetworkAnalyzer {
434
433
 
435
434
  /**
436
435
  * @param {Array<LH.Artifacts.NetworkRequest>} records
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]
436
+ * @param {string} resourceUrl
449
437
  * @return {LH.Artifacts.NetworkRequest|undefined}
450
438
  */
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));
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
+ );
466
445
  }
467
446
 
468
447
  /**
469
448
  * Resolves redirect chain given a main document.
470
- * See: {@link NetworkAnalyzer.findMainDocument}) for how to retrieve main document.
449
+ * See: {@link NetworkAnalyzer.findResourceForUrl}) for how to retrieve main document.
471
450
  *
472
451
  * @param {LH.Artifacts.NetworkRequest} request
473
452
  * @return {LH.Artifacts.NetworkRequest}