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.
- package/cli/bin.js +6 -6
- package/cli/cli-flags.js +5 -1
- package/cli/run.js +1 -1
- package/cli/test/smokehouse/core-tests.js +2 -0
- package/cli/test/smokehouse/lighthouse-runners/bundle.js +9 -9
- package/cli/test/smokehouse/lighthouse-runners/cli.js +7 -7
- package/cli/test/smokehouse/lighthouse-runners/devtools.js +3 -3
- package/cli/test/smokehouse/readme.md +1 -1
- package/cli/test/smokehouse/report-assert.js +11 -7
- package/cli/test/smokehouse/smokehouse.js +13 -13
- package/core/audits/seo/is-crawlable.js +114 -42
- package/core/config/config-helpers.js +11 -11
- package/core/config/config-plugin.js +2 -2
- package/core/config/config.js +15 -15
- package/core/config/constants.js +1 -0
- package/core/config/default-config.js +2 -3
- package/core/config/desktop-config.js +1 -1
- package/core/config/experimental-config.js +1 -1
- package/core/config/filters.js +8 -4
- package/core/config/full-config.js +1 -1
- package/core/config/lr-desktop-config.js +1 -1
- package/core/config/lr-mobile-config.js +1 -1
- package/core/config/perf-config.js +1 -1
- package/core/config/validation.js +4 -4
- package/core/gather/gatherers/accessibility.js +15 -1
- package/core/gather/gatherers/full-page-screenshot.js +51 -40
- package/core/gather/gatherers/seo/tap-targets.js +39 -19
- package/core/gather/navigation-runner.js +1 -1
- package/core/gather/snapshot-runner.js +1 -1
- package/core/gather/timespan-runner.js +1 -1
- package/core/index.js +10 -10
- package/core/legacy/config/config.js +31 -33
- package/core/legacy/config/legacy-default-config.js +1 -1
- package/core/legacy/gather/driver.js +0 -17
- package/core/legacy/gather/gather-runner.js +0 -6
- package/core/runner.js +2 -0
- package/core/user-flow.js +3 -3
- package/core/util.cjs +43 -3
- package/dist/report/bundle.esm.js +53 -22
- package/dist/report/flow.js +5 -5
- package/dist/report/standalone.js +4 -4
- package/flow-report/src/common.tsx +3 -3
- package/flow-report/src/util.ts +0 -11
- package/flow-report/test/common-test.tsx +4 -7
- package/flow-report/test/summary/summary-test.tsx +1 -1
- package/package.json +1 -1
- package/readme.md +1 -0
- package/report/renderer/details-renderer.js +1 -2
- package/report/renderer/element-screenshot-renderer.js +4 -4
- package/report/renderer/report-renderer.js +3 -7
- package/report/renderer/report-ui-features.js +3 -7
- package/report/renderer/util.js +43 -3
- package/report/test/generator/report-generator-test.js +1 -1
- package/report/test/renderer/util-test.js +22 -0
- package/report/test-assets/faux-psi.js +3 -3
- package/types/artifacts.d.ts +1 -11
- package/types/config.d.ts +18 -18
- package/types/lhr/audit-details.d.ts +0 -17
- package/types/lhr/lhr.d.ts +13 -0
- package/types/lhr/settings.d.ts +2 -0
- package/types/smokehouse.d.ts +4 -3
- package/types/user-flow.d.ts +1 -1
- 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
|
|
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(
|
|
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 (!
|
|
148
|
-
|
|
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
|
-
|
|
157
|
+
config = deepCloneConfigJson(config);
|
|
158
158
|
|
|
159
159
|
// Extend the default config if specified
|
|
160
|
-
if (
|
|
161
|
-
if (
|
|
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
|
-
|
|
165
|
-
deepCloneConfigJson(legacyDefaultConfig),
|
|
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
|
-
|
|
172
|
+
config = await mergePlugins(config, configDir, flags);
|
|
173
173
|
|
|
174
|
-
const settings = resolveSettings(
|
|
174
|
+
const settings = resolveSettings(config.settings || {}, flags);
|
|
175
175
|
|
|
176
176
|
// Augment passes with necessary defaults and require gatherers.
|
|
177
|
-
const passesWithDefaults = LegacyResolvedConfig.augmentPassesWithDefaults(
|
|
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(
|
|
181
|
+
const audits = await LegacyResolvedConfig.requireAudits(config.audits, configDir);
|
|
182
182
|
|
|
183
|
-
const resolvedConfig = new LegacyResolvedConfig(
|
|
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
|
|
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(
|
|
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 =
|
|
202
|
+
this.categories = config.categories || null;
|
|
203
203
|
/** @type {?Record<string, LH.Config.Group>} */
|
|
204
|
-
this.groups =
|
|
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
|
|
249
|
-
* @param {LH.Config
|
|
250
|
-
* @return {LH.Config
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
329
|
+
const config = options.config;
|
|
330
330
|
const {gatherMode} = artifacts.GatherContext;
|
|
331
|
-
const {resolvedConfig} = await initializeConfig(gatherMode,
|
|
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 (
|
|
560
|
+
if (isScreenEmulationDisabled) {
|
|
521
561
|
deviceEmulation = Util.i18n.strings.runtimeNoEmulation;
|
|
522
|
-
} else if (!
|
|
562
|
+
} else if (!isScreenEmulationMobile) {
|
|
523
563
|
deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation;
|
|
524
564
|
}
|
|
525
565
|
|
|
526
|
-
const screenEmulation =
|
|
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 (
|
|
556
|
+
if (isScreenEmulationDisabled) {
|
|
517
557
|
deviceEmulation = Util.i18n.strings.runtimeNoEmulation;
|
|
518
|
-
} else if (!
|
|
558
|
+
} else if (!isScreenEmulationMobile) {
|
|
519
559
|
deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation;
|
|
520
560
|
}
|
|
521
561
|
|
|
522
|
-
const screenEmulation =
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|