lighthouse 9.5.0-dev.20230111 → 9.5.0-dev.20230113

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 (63) hide show
  1. package/cli/bin.js +6 -6
  2. package/cli/cli-flags.js +5 -1
  3. package/cli/run.js +1 -1
  4. package/cli/test/smokehouse/core-tests.js +2 -0
  5. package/cli/test/smokehouse/lighthouse-runners/bundle.js +9 -9
  6. package/cli/test/smokehouse/lighthouse-runners/cli.js +7 -7
  7. package/cli/test/smokehouse/lighthouse-runners/devtools.js +3 -3
  8. package/cli/test/smokehouse/readme.md +1 -1
  9. package/cli/test/smokehouse/report-assert.js +11 -7
  10. package/cli/test/smokehouse/smokehouse.js +13 -13
  11. package/core/audits/seo/is-crawlable.js +114 -42
  12. package/core/config/config-helpers.js +11 -11
  13. package/core/config/config-plugin.js +2 -2
  14. package/core/config/config.js +15 -15
  15. package/core/config/constants.js +1 -0
  16. package/core/config/default-config.js +2 -3
  17. package/core/config/desktop-config.js +1 -1
  18. package/core/config/experimental-config.js +1 -1
  19. package/core/config/filters.js +8 -4
  20. package/core/config/full-config.js +1 -1
  21. package/core/config/lr-desktop-config.js +1 -1
  22. package/core/config/lr-mobile-config.js +1 -1
  23. package/core/config/perf-config.js +1 -1
  24. package/core/config/validation.js +4 -4
  25. package/core/gather/gatherers/accessibility.js +15 -1
  26. package/core/gather/gatherers/full-page-screenshot.js +51 -40
  27. package/core/gather/gatherers/seo/tap-targets.js +39 -19
  28. package/core/gather/navigation-runner.js +1 -1
  29. package/core/gather/snapshot-runner.js +1 -1
  30. package/core/gather/timespan-runner.js +1 -1
  31. package/core/index.js +10 -10
  32. package/core/legacy/config/config.js +31 -33
  33. package/core/legacy/config/legacy-default-config.js +1 -1
  34. package/core/legacy/gather/driver.js +0 -17
  35. package/core/legacy/gather/gather-runner.js +0 -6
  36. package/core/runner.js +2 -0
  37. package/core/user-flow.js +3 -3
  38. package/core/util.cjs +43 -3
  39. package/dist/report/bundle.esm.js +53 -22
  40. package/dist/report/flow.js +5 -5
  41. package/dist/report/standalone.js +4 -4
  42. package/flow-report/src/common.tsx +3 -3
  43. package/flow-report/src/util.ts +0 -11
  44. package/flow-report/test/common-test.tsx +4 -7
  45. package/flow-report/test/summary/summary-test.tsx +1 -1
  46. package/package.json +1 -1
  47. package/readme.md +1 -0
  48. package/report/renderer/details-renderer.js +1 -2
  49. package/report/renderer/element-screenshot-renderer.js +4 -4
  50. package/report/renderer/report-renderer.js +3 -7
  51. package/report/renderer/report-ui-features.js +3 -7
  52. package/report/renderer/util.js +43 -3
  53. package/report/test/generator/report-generator-test.js +1 -1
  54. package/report/test/renderer/util-test.js +22 -0
  55. package/report/test-assets/faux-psi.js +3 -3
  56. package/types/artifacts.d.ts +1 -11
  57. package/types/config.d.ts +18 -18
  58. package/types/lhr/audit-details.d.ts +0 -17
  59. package/types/lhr/lhr.d.ts +13 -0
  60. package/types/lhr/settings.d.ts +2 -0
  61. package/types/smokehouse.d.ts +4 -3
  62. package/types/user-flow.d.ts +1 -1
  63. package/core/audits/full-page-screenshot.js +0 -42
@@ -79,7 +79,7 @@ function assertValidPasses(passes, audits) {
79
79
  const gatherer = gathererDefn.instance;
80
80
  foundGatherers.add(gatherer.name);
81
81
  const isGatherRequiredByAudits = requestedGatherers.has(gatherer.name);
82
- if (!isGatherRequiredByAudits) {
82
+ if (!isGatherRequiredByAudits && gatherer.name !== 'FullPageScreenshot') {
83
83
  const msg = `${gatherer.name} gatherer requested, however no audit requires it.`;
84
84
  log.warn('config', msg);
85
85
  }
@@ -135,17 +135,17 @@ class LegacyResolvedConfig {
135
135
  /**
136
136
  * Resolves the provided config (inherits from extended config, if set), resolves
137
137
  * all referenced modules, and validates.
138
- * @param {LH.Config.Json=} configJSON If not provided, uses the default config.
138
+ * @param {LH.Config=} config If not provided, uses the default config.
139
139
  * @param {LH.Flags=} flags
140
140
  * @return {Promise<LegacyResolvedConfig>}
141
141
  */
142
- static async fromJson(configJSON, flags) {
142
+ static async fromJson(config, flags) {
143
143
  const status = {msg: 'Create config', id: 'lh:init:config'};
144
144
  log.time(status, 'verbose');
145
145
  let configPath = flags?.configPath;
146
146
 
147
- if (!configJSON) {
148
- configJSON = legacyDefaultConfig;
147
+ if (!config) {
148
+ config = legacyDefaultConfig;
149
149
  configPath = path.resolve(getModuleDirectory(import.meta), defaultConfigPath);
150
150
  }
151
151
 
@@ -154,33 +154,33 @@ class LegacyResolvedConfig {
154
154
  }
155
155
 
156
156
  // We don't want to mutate the original config object
157
- configJSON = deepCloneConfigJson(configJSON);
157
+ config = deepCloneConfigJson(config);
158
158
 
159
159
  // Extend the default config if specified
160
- if (configJSON.extends) {
161
- if (configJSON.extends !== 'lighthouse:default') {
160
+ if (config.extends) {
161
+ if (config.extends !== 'lighthouse:default') {
162
162
  throw new Error('`lighthouse:default` is the only valid extension method.');
163
163
  }
164
- configJSON = LegacyResolvedConfig.extendConfigJSON(
165
- deepCloneConfigJson(legacyDefaultConfig), configJSON);
164
+ config = LegacyResolvedConfig.extendConfigJSON(
165
+ deepCloneConfigJson(legacyDefaultConfig), config);
166
166
  }
167
167
 
168
168
  // The directory of the config path, if one was provided.
169
169
  const configDir = configPath ? path.dirname(configPath) : undefined;
170
170
 
171
171
  // Validate and merge in plugins (if any).
172
- configJSON = await mergePlugins(configJSON, configDir, flags);
172
+ config = await mergePlugins(config, configDir, flags);
173
173
 
174
- const settings = resolveSettings(configJSON.settings || {}, flags);
174
+ const settings = resolveSettings(config.settings || {}, flags);
175
175
 
176
176
  // Augment passes with necessary defaults and require gatherers.
177
- const passesWithDefaults = LegacyResolvedConfig.augmentPassesWithDefaults(configJSON.passes);
177
+ const passesWithDefaults = LegacyResolvedConfig.augmentPassesWithDefaults(config.passes);
178
178
  LegacyResolvedConfig.adjustDefaultPassForThrottling(settings, passesWithDefaults);
179
179
  const passes = await LegacyResolvedConfig.requireGatherers(passesWithDefaults, configDir);
180
180
 
181
- const audits = await LegacyResolvedConfig.requireAudits(configJSON.audits, configDir);
181
+ const audits = await LegacyResolvedConfig.requireAudits(config.audits, configDir);
182
182
 
183
- const resolvedConfig = new LegacyResolvedConfig(configJSON, {settings, passes, audits});
183
+ const resolvedConfig = new LegacyResolvedConfig(config, {settings, passes, audits});
184
184
  log.timeEnd(status);
185
185
  return resolvedConfig;
186
186
  }
@@ -188,10 +188,10 @@ class LegacyResolvedConfig {
188
188
  /**
189
189
  * @deprecated `Config.fromJson` should be used instead.
190
190
  * @constructor
191
- * @param {LH.Config.Json} configJSON
191
+ * @param {LH.Config} config
192
192
  * @param {{settings: LH.Config.Settings, passes: ?LH.Config.Pass[], audits: ?LH.Config.AuditDefn[]}} opts
193
193
  */
194
- constructor(configJSON, opts) {
194
+ constructor(config, opts) {
195
195
  /** @type {LH.Config.Settings} */
196
196
  this.settings = opts.settings;
197
197
  /** @type {?Array<LH.Config.Pass>} */
@@ -199,9 +199,9 @@ class LegacyResolvedConfig {
199
199
  /** @type {?Array<LH.Config.AuditDefn>} */
200
200
  this.audits = opts.audits;
201
201
  /** @type {?Record<string, LH.Config.Category>} */
202
- this.categories = configJSON.categories || null;
202
+ this.categories = config.categories || null;
203
203
  /** @type {?Record<string, LH.Config.Group>} */
204
- this.groups = configJSON.groups || null;
204
+ this.groups = config.groups || null;
205
205
 
206
206
  LegacyResolvedConfig.filterConfigIfNeeded(this);
207
207
 
@@ -245,9 +245,9 @@ class LegacyResolvedConfig {
245
245
  }
246
246
 
247
247
  /**
248
- * @param {LH.Config.Json} baseJSON The JSON of the configuration to extend
249
- * @param {LH.Config.Json} extendJSON The JSON of the extensions
250
- * @return {LH.Config.Json}
248
+ * @param {LH.Config} baseJSON The JSON of the configuration to extend
249
+ * @param {LH.Config} extendJSON The JSON of the extensions
250
+ * @return {LH.Config}
251
251
  */
252
252
  static extendConfigJSON(baseJSON, extendJSON) {
253
253
  if (extendJSON.passes && baseJSON.passes) {
@@ -270,7 +270,7 @@ class LegacyResolvedConfig {
270
270
  }
271
271
 
272
272
  /**
273
- * @param {LH.Config.Json['passes']} passes
273
+ * @param {LH.Config['passes']} passes
274
274
  * @return {?Array<Required<LH.Config.PassJson>>}
275
275
  */
276
276
  static augmentPassesWithDefaults(passes) {
@@ -313,7 +313,8 @@ class LegacyResolvedConfig {
313
313
  */
314
314
  static filterConfigIfNeeded(config) {
315
315
  const settings = config.settings;
316
- if (!settings.onlyCategories && !settings.onlyAudits && !settings.skipAudits) {
316
+ // eslint-disable-next-line max-len
317
+ if (!settings.onlyCategories && !settings.onlyAudits && !settings.skipAudits && !settings.disableFullPageScreenshot) {
317
318
  return;
318
319
  }
319
320
 
@@ -328,6 +329,11 @@ class LegacyResolvedConfig {
328
329
  // 3. Resolve which gatherers will need to run
329
330
  const requestedGathererIds = LegacyResolvedConfig.getGatherersRequestedByAudits(audits);
330
331
 
332
+ // Always include FullPageScreenshot, unless explictly told not to.
333
+ if (!settings.disableFullPageScreenshot) {
334
+ requestedGathererIds.add('FullPageScreenshot');
335
+ }
336
+
331
337
  // 4. Filter to only the neccessary passes
332
338
  const passes =
333
339
  LegacyResolvedConfig.generatePassesNeededByGatherers(config.passes, requestedGathererIds);
@@ -413,14 +419,6 @@ class LegacyResolvedConfig {
413
419
  }
414
420
  });
415
421
 
416
- // The `full-page-screenshot` audit belongs to no category, but we still want to include
417
- // it (unless explictly excluded) because there are audits in every category that can use it.
418
- const explicitlyExcludesFullPageScreenshot =
419
- settings.skipAudits && settings.skipAudits.includes('full-page-screenshot');
420
- if (!explicitlyExcludesFullPageScreenshot && (settings.onlyCategories || settings.skipAudits)) {
421
- includedAudits.add('full-page-screenshot');
422
- }
423
-
424
422
  return {categories, requestedAuditNames: includedAudits};
425
423
  }
426
424
 
@@ -488,7 +486,7 @@ class LegacyResolvedConfig {
488
486
  * Take an array of audits and audit paths and require any paths (possibly
489
487
  * relative to the optional `configDir`) using `resolveModulePath`,
490
488
  * leaving only an array of AuditDefns.
491
- * @param {LH.Config.Json['audits']} audits
489
+ * @param {LH.Config['audits']} audits
492
490
  * @param {string=} configDir
493
491
  * @return {Promise<LegacyResolvedConfig['audits']>}
494
492
  */
@@ -10,7 +10,7 @@
10
10
 
11
11
  import defaultConfig from '../../config/default-config.js';
12
12
 
13
- /** @type {LH.Config.Json} */
13
+ /** @type {LH.Config} */
14
14
  const legacyDefaultConfig = JSON.parse(JSON.stringify(defaultConfig));
15
15
  if (!legacyDefaultConfig.categories) {
16
16
  throw new Error('Default config should always have categories');
@@ -405,23 +405,6 @@ class Driver {
405
405
  return fetchResponseBodyFromCache(this.defaultSession, requestId, timeout);
406
406
  }
407
407
 
408
- /**
409
- * @param {{x: number, y: number}} position
410
- * @return {Promise<void>}
411
- */
412
- scrollTo(position) {
413
- const scrollExpression = `window.scrollTo(${position.x}, ${position.y})`;
414
- return this.executionContext.evaluateAsync(scrollExpression, {useIsolation: true});
415
- }
416
-
417
- /**
418
- * @return {Promise<{x: number, y: number}>}
419
- */
420
- getScrollPosition() {
421
- return this.executionContext.evaluateAsync(`({x: window.scrollX, y: window.scrollY})`,
422
- {useIsolation: true});
423
- }
424
-
425
408
  /**
426
409
  * @param {{additionalTraceCategories?: string|null}=} settings
427
410
  * @return {Promise<void>}
@@ -313,17 +313,12 @@ class GatherRunner {
313
313
  * @return {Promise<void>}
314
314
  */
315
315
  static async afterPass(passContext, loadData, gathererResults) {
316
- const driver = passContext.driver;
317
316
  const config = passContext.passConfig;
318
317
  const gatherers = config.gatherers;
319
318
 
320
319
  const apStatus = {msg: `Running afterPass methods`, id: `lh:gather:afterPass`};
321
320
  log.time(apStatus, 'verbose');
322
321
 
323
- // Some gatherers scroll the page which can cause unexpected results for other gatherers.
324
- // We reset the scroll position in between each gatherer.
325
- const scrollPosition = await driver.getScrollPosition();
326
-
327
322
  for (const gathererDefn of gatherers) {
328
323
  const gatherer = gathererDefn.instance;
329
324
  const status = {
@@ -339,7 +334,6 @@ class GatherRunner {
339
334
  gathererResult.push(artifactPromise);
340
335
  gathererResults[gatherer.name] = gathererResult;
341
336
  await artifactPromise.catch(() => {});
342
- await driver.scrollTo(scrollPosition);
343
337
  log.timeEnd(status);
344
338
  }
345
339
  log.timeEnd(apStatus);
package/core/runner.js CHANGED
@@ -110,6 +110,8 @@ class Runner {
110
110
  categories,
111
111
  categoryGroups: resolvedConfig.groups || undefined,
112
112
  stackPacks: stackPacks.getStackPacks(artifacts.Stacks),
113
+ fullPageScreenshot: resolvedConfig.settings.disableFullPageScreenshot ?
114
+ undefined : artifacts.FullPageScreenshot,
113
115
  timing: this._getTiming(artifacts),
114
116
  i18n: {
115
117
  rendererFormattedStrings: format.getRendererFormattedStrings(settings.locale),
package/core/user-flow.js CHANGED
@@ -308,7 +308,7 @@ function getFlowName(name, gatherSteps) {
308
308
 
309
309
  /**
310
310
  * @param {Array<LH.UserFlow.GatherStep>} gatherSteps
311
- * @param {{name?: string, config?: LH.Config.Json, gatherStepRunnerOptions?: GatherStepRunnerOptions}} options
311
+ * @param {{name?: string, config?: LH.Config, gatherStepRunnerOptions?: GatherStepRunnerOptions}} options
312
312
  */
313
313
  async function auditGatherSteps(gatherSteps, options) {
314
314
  if (!gatherSteps.length) {
@@ -326,9 +326,9 @@ async function auditGatherSteps(gatherSteps, options) {
326
326
  // If the gather step is not active, we must recreate the runner options.
327
327
  if (!runnerOptions) {
328
328
  // Step specific configs take precedence over a config for the entire flow.
329
- const configJson = options.config;
329
+ const config = options.config;
330
330
  const {gatherMode} = artifacts.GatherContext;
331
- const {resolvedConfig} = await initializeConfig(gatherMode, configJson, flags);
331
+ const {resolvedConfig} = await initializeConfig(gatherMode, config, flags);
332
332
  runnerOptions = {
333
333
  resolvedConfig,
334
334
  computedCache: new Map(),
package/core/util.cjs CHANGED
@@ -216,9 +216,39 @@ class Util {
216
216
  });
217
217
  }
218
218
 
219
+ // In 10.0, full-page-screenshot became a top-level property on the LHR.
220
+ if (clone.audits['full-page-screenshot']) {
221
+ const details = /** @type {LH.Result.FullPageScreenshot=} */ (
222
+ clone.audits['full-page-screenshot'].details);
223
+ if (details) {
224
+ clone.fullPageScreenshot = {
225
+ screenshot: details.screenshot,
226
+ nodes: details.nodes,
227
+ };
228
+ } else {
229
+ clone.fullPageScreenshot = null;
230
+ }
231
+ delete clone.audits['full-page-screenshot'];
232
+ }
233
+
219
234
  return clone;
220
235
  }
221
236
 
237
+ /**
238
+ * @param {LH.Result} lhr
239
+ * @return {LH.Result.FullPageScreenshot=}
240
+ */
241
+ static getFullPageScreenshot(lhr) {
242
+ if (lhr.fullPageScreenshot) {
243
+ return lhr.fullPageScreenshot;
244
+ }
245
+
246
+ // Prior to 10.0.
247
+ const details = /** @type {LH.Result.FullPageScreenshot=} */ (
248
+ lhr.audits['full-page-screenshot']?.details);
249
+ return details;
250
+ }
251
+
222
252
  /**
223
253
  * Used to determine if the "passed" for the purposes of showing up in the "failed" or "passed"
224
254
  * sections of the report.
@@ -516,14 +546,24 @@ class Util {
516
546
  summary = cpuThrottling = networkThrottling = Util.i18n.strings.runtimeUnknown;
517
547
  }
518
548
 
549
+ // devtools-entry.js always sets `screenEmulation.disabled` when using mobile emulation,
550
+ // because we handle the emulation outside of Lighthouse. Since the screen truly is emulated
551
+ // as a mobile device, ignore `.disabled` in devtools and just check the form factor
552
+ const isScreenEmulationDisabled = settings.channel === 'devtools' ?
553
+ false :
554
+ settings.screenEmulation.disabled;
555
+ const isScreenEmulationMobile = settings.channel === 'devtools' ?
556
+ settings.formFactor === 'mobile' :
557
+ settings.screenEmulation.mobile;
558
+
519
559
  let deviceEmulation = Util.i18n.strings.runtimeMobileEmulation;
520
- if (settings.screenEmulation.disabled) {
560
+ if (isScreenEmulationDisabled) {
521
561
  deviceEmulation = Util.i18n.strings.runtimeNoEmulation;
522
- } else if (!settings.screenEmulation.mobile) {
562
+ } else if (!isScreenEmulationMobile) {
523
563
  deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation;
524
564
  }
525
565
 
526
- const screenEmulation = settings.screenEmulation.disabled ?
566
+ const screenEmulation = isScreenEmulationDisabled ?
527
567
  undefined :
528
568
  // eslint-disable-next-line max-len
529
569
  `${settings.screenEmulation.width}x${settings.screenEmulation.height}, DPR ${settings.screenEmulation.deviceScaleFactor}`;
@@ -212,9 +212,39 @@ class Util {
212
212
  });
213
213
  }
214
214
 
215
+ // In 10.0, full-page-screenshot became a top-level property on the LHR.
216
+ if (clone.audits['full-page-screenshot']) {
217
+ const details = /** @type {LH.Result.FullPageScreenshot=} */ (
218
+ clone.audits['full-page-screenshot'].details);
219
+ if (details) {
220
+ clone.fullPageScreenshot = {
221
+ screenshot: details.screenshot,
222
+ nodes: details.nodes,
223
+ };
224
+ } else {
225
+ clone.fullPageScreenshot = null;
226
+ }
227
+ delete clone.audits['full-page-screenshot'];
228
+ }
229
+
215
230
  return clone;
216
231
  }
217
232
 
233
+ /**
234
+ * @param {LH.Result} lhr
235
+ * @return {LH.Result.FullPageScreenshot=}
236
+ */
237
+ static getFullPageScreenshot(lhr) {
238
+ if (lhr.fullPageScreenshot) {
239
+ return lhr.fullPageScreenshot;
240
+ }
241
+
242
+ // Prior to 10.0.
243
+ const details = /** @type {LH.Result.FullPageScreenshot=} */ (
244
+ lhr.audits['full-page-screenshot']?.details);
245
+ return details;
246
+ }
247
+
218
248
  /**
219
249
  * Used to determine if the "passed" for the purposes of showing up in the "failed" or "passed"
220
250
  * sections of the report.
@@ -512,14 +542,24 @@ class Util {
512
542
  summary = cpuThrottling = networkThrottling = Util.i18n.strings.runtimeUnknown;
513
543
  }
514
544
 
545
+ // devtools-entry.js always sets `screenEmulation.disabled` when using mobile emulation,
546
+ // because we handle the emulation outside of Lighthouse. Since the screen truly is emulated
547
+ // as a mobile device, ignore `.disabled` in devtools and just check the form factor
548
+ const isScreenEmulationDisabled = settings.channel === 'devtools' ?
549
+ false :
550
+ settings.screenEmulation.disabled;
551
+ const isScreenEmulationMobile = settings.channel === 'devtools' ?
552
+ settings.formFactor === 'mobile' :
553
+ settings.screenEmulation.mobile;
554
+
515
555
  let deviceEmulation = Util.i18n.strings.runtimeMobileEmulation;
516
- if (settings.screenEmulation.disabled) {
556
+ if (isScreenEmulationDisabled) {
517
557
  deviceEmulation = Util.i18n.strings.runtimeNoEmulation;
518
- } else if (!settings.screenEmulation.mobile) {
558
+ } else if (!isScreenEmulationMobile) {
519
559
  deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation;
520
560
  }
521
561
 
522
- const screenEmulation = settings.screenEmulation.disabled ?
562
+ const screenEmulation = isScreenEmulationDisabled ?
523
563
  undefined :
524
564
  // eslint-disable-next-line max-len
525
565
  `${settings.screenEmulation.width}x${settings.screenEmulation.height}, DPR ${settings.screenEmulation.deviceScaleFactor}`;
@@ -2626,7 +2666,7 @@ const CRCRenderer = CriticalRequestChainRenderer;
2626
2666
  */
2627
2667
 
2628
2668
  /**
2629
- * @param {LH.Audit.Details.FullPageScreenshot['screenshot']} screenshot
2669
+ * @param {LH.Result.FullPageScreenshot['screenshot']} screenshot
2630
2670
  * @param {LH.Audit.Details.Rect} rect
2631
2671
  * @return {boolean}
2632
2672
  */
@@ -2732,7 +2772,7 @@ class ElementScreenshotRenderer {
2732
2772
  * Allows for multiple Lighthouse reports to be rendered on the page, each with their
2733
2773
  * own full page screenshot.
2734
2774
  * @param {HTMLElement} el
2735
- * @param {LH.Audit.Details.FullPageScreenshot['screenshot']} screenshot
2775
+ * @param {LH.Result.FullPageScreenshot['screenshot']} screenshot
2736
2776
  */
2737
2777
  static installFullPageScreenshot(el, screenshot) {
2738
2778
  el.style.setProperty('--element-screenshot-url', `url('${screenshot.data}')`);
@@ -2814,7 +2854,7 @@ class ElementScreenshotRenderer {
2814
2854
  * Used to render both the thumbnail preview in details tables and the full-page screenshot in the lightbox.
2815
2855
  * Returns null if element rect is outside screenshot bounds.
2816
2856
  * @param {DOM} dom
2817
- * @param {LH.Audit.Details.FullPageScreenshot['screenshot']} screenshot
2857
+ * @param {LH.Result.FullPageScreenshot['screenshot']} screenshot
2818
2858
  * @param {Rect} elementRectSC Region of screenshot to highlight.
2819
2859
  * @param {Size} maxRenderSizeDC e.g. maxThumbnailSize or maxLightboxSize.
2820
2860
  * @return {Element|null}
@@ -2909,7 +2949,7 @@ const URL_PREFIXES = ['http://', 'https://', 'data:'];
2909
2949
  class DetailsRenderer {
2910
2950
  /**
2911
2951
  * @param {DOM} dom
2912
- * @param {{fullPageScreenshot?: LH.Audit.Details.FullPageScreenshot}} [options]
2952
+ * @param {{fullPageScreenshot?: LH.Result.FullPageScreenshot}} [options]
2913
2953
  */
2914
2954
  constructor(dom, options = {}) {
2915
2955
  this._dom = dom;
@@ -2935,7 +2975,6 @@ class DetailsRenderer {
2935
2975
  // Internal-only details, not for rendering.
2936
2976
  case 'screenshot':
2937
2977
  case 'debugdata':
2938
- case 'full-page-screenshot':
2939
2978
  case 'treemap-data':
2940
2979
  return null;
2941
2980
 
@@ -4519,12 +4558,8 @@ class ReportRenderer {
4519
4558
  Util.i18n = i18n;
4520
4559
  Util.reportJson = report;
4521
4560
 
4522
- const fullPageScreenshot =
4523
- report.audits['full-page-screenshot']?.details &&
4524
- report.audits['full-page-screenshot'].details.type === 'full-page-screenshot' ?
4525
- report.audits['full-page-screenshot'].details : undefined;
4526
4561
  const detailsRenderer = new DetailsRenderer(this._dom, {
4527
- fullPageScreenshot,
4562
+ fullPageScreenshot: report.fullPageScreenshot ?? undefined,
4528
4563
  });
4529
4564
 
4530
4565
  const categoryRenderer = new CategoryRenderer(this._dom, detailsRenderer);
@@ -4595,9 +4630,9 @@ class ReportRenderer {
4595
4630
  reportSection.append(this._renderReportFooter(report));
4596
4631
  reportContainer.append(headerContainer, reportSection);
4597
4632
 
4598
- if (fullPageScreenshot) {
4633
+ if (report.fullPageScreenshot) {
4599
4634
  ElementScreenshotRenderer.installFullPageScreenshot(
4600
- this._dom.rootEl, fullPageScreenshot.screenshot);
4635
+ this._dom.rootEl, report.fullPageScreenshot.screenshot);
4601
4636
  }
4602
4637
 
4603
4638
  return reportFragment;
@@ -5456,6 +5491,7 @@ class ReportUIFeatures {
5456
5491
  */
5457
5492
  initFeatures(lhr) {
5458
5493
  this.json = lhr;
5494
+ this._fullPageScreenshot = Util.getFullPageScreenshot(lhr);
5459
5495
 
5460
5496
  if (this._topbar) {
5461
5497
  this._topbar.enable(lhr);
@@ -5695,18 +5731,13 @@ class ReportUIFeatures {
5695
5731
  * @param {Element} rootEl
5696
5732
  */
5697
5733
  _setupElementScreenshotOverlay(rootEl) {
5698
- const fullPageScreenshot =
5699
- this.json.audits['full-page-screenshot'] &&
5700
- this.json.audits['full-page-screenshot'].details &&
5701
- this.json.audits['full-page-screenshot'].details.type === 'full-page-screenshot' &&
5702
- this.json.audits['full-page-screenshot'].details;
5703
- if (!fullPageScreenshot) return;
5734
+ if (!this._fullPageScreenshot) return;
5704
5735
 
5705
5736
  ElementScreenshotRenderer.installOverlayFeature({
5706
5737
  dom: this._dom,
5707
5738
  rootEl: rootEl,
5708
5739
  overlayContainerEl: rootEl,
5709
- fullPageScreenshot,
5740
+ fullPageScreenshot: this._fullPageScreenshot,
5710
5741
  });
5711
5742
  }
5712
5743