lighthouse 9.5.0-dev.20220328 → 9.5.0-dev.20220331
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/dist/report/bundle.esm.js +20 -18
- package/dist/report/flow.js +1 -1
- package/dist/report/standalone.js +1 -1
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +28 -18
- package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -1
- package/lighthouse-core/audits/byte-efficiency/efficient-animated-content.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +3 -2
- package/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/uses-text-compression.js +1 -1
- package/lighthouse-core/audits/dobetterweb/uses-http2.js +2 -1
- package/lighthouse-core/audits/long-tasks.js +3 -2
- package/lighthouse-core/audits/metrics/first-contentful-paint-3g.js +2 -2
- package/lighthouse-core/audits/metrics/first-contentful-paint.js +3 -2
- package/lighthouse-core/audits/metrics/first-meaningful-paint.js +3 -2
- package/lighthouse-core/audits/metrics/interactive.js +3 -2
- package/lighthouse-core/audits/metrics/largest-contentful-paint.js +3 -2
- package/lighthouse-core/audits/metrics/max-potential-fid.js +3 -2
- package/lighthouse-core/audits/metrics/speed-index.js +3 -2
- package/lighthouse-core/audits/metrics/total-blocking-time.js +3 -2
- package/lighthouse-core/audits/metrics.js +3 -2
- package/lighthouse-core/audits/predictive-perf.js +2 -1
- package/lighthouse-core/audits/preload-lcp-image.js +3 -3
- package/lighthouse-core/audits/redirects.js +1 -1
- package/lighthouse-core/audits/script-treemap-data.js +31 -32
- package/lighthouse-core/audits/timing-budget.js +3 -2
- package/lighthouse-core/audits/uses-rel-preconnect.js +1 -1
- package/lighthouse-core/audits/uses-rel-preload.js +1 -1
- package/lighthouse-core/computed/critical-request-chains.js +2 -9
- package/lighthouse-core/computed/main-resource.js +1 -1
- package/lighthouse-core/computed/metrics/first-contentful-paint-all-frames.js +1 -1
- package/lighthouse-core/computed/metrics/first-contentful-paint.js +1 -1
- package/lighthouse-core/computed/metrics/first-meaningful-paint.js +1 -1
- package/lighthouse-core/computed/metrics/interactive.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-first-contentful-paint.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-interactive.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-max-potential-fid.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-metric.js +3 -5
- package/lighthouse-core/computed/metrics/lantern-speed-index.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +1 -1
- package/lighthouse-core/computed/metrics/largest-contentful-paint-all-frames.js +1 -1
- package/lighthouse-core/computed/metrics/largest-contentful-paint.js +1 -1
- package/lighthouse-core/computed/metrics/max-potential-fid.js +1 -1
- package/lighthouse-core/computed/metrics/metric.js +1 -0
- package/lighthouse-core/computed/metrics/speed-index.js +1 -1
- package/lighthouse-core/computed/metrics/timing-summary.js +13 -6
- package/lighthouse-core/computed/metrics/total-blocking-time.js +1 -1
- package/lighthouse-core/computed/page-dependency-graph.js +54 -31
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +8 -1
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +32 -22
- package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +0 -3
- package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +6 -3
- package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +7 -5
- package/lighthouse-core/gather/connections/cri.js +1 -0
- package/lighthouse-core/gather/driver/execution-context.js +4 -2
- package/lighthouse-core/gather/driver/navigation.js +9 -9
- package/lighthouse-core/gather/driver/network-monitor.js +16 -3
- package/lighthouse-core/gather/driver.js +5 -0
- package/lighthouse-core/gather/gather-runner.js +13 -7
- package/lighthouse-core/gather/gatherers/js-usage.js +7 -6
- package/lighthouse-core/gather/gatherers/scripts.js +20 -2
- package/lighthouse-core/gather/gatherers/seo/robots-txt.js +2 -1
- package/lighthouse-core/gather/gatherers/web-app-manifest.js +2 -2
- package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +8 -29
- package/lighthouse-core/lib/navigation-error.js +13 -4
- package/lighthouse-core/util-commonjs.js +20 -18
- package/package.json +3 -3
- package/report/renderer/util.js +20 -18
- package/shared/localization/locales/en-US.json +1 -1
- package/shared/localization/locales/en-XL.json +1 -1
- package/types/artifacts.d.ts +25 -2
- package/types/gatherer.d.ts +1 -2
|
@@ -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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
const
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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<{
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
228
|
-
|
|
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);
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
* @property {LH.Gatherer.GatherMode} gatherMode
|
|
16
16
|
* @property {Map<string, LH.ArbitraryEqualityMap>} computedCache
|
|
17
17
|
* @property {LH.Config.Settings} settings
|
|
18
|
-
* @property {string} url
|
|
19
18
|
*/
|
|
20
19
|
|
|
21
20
|
/** @typedef {Record<string, Promise<any>>} IntermediateArtifacts */
|
|
@@ -75,7 +74,6 @@ async function collectPhaseArtifacts(options) {
|
|
|
75
74
|
gatherMode,
|
|
76
75
|
computedCache,
|
|
77
76
|
settings,
|
|
78
|
-
url,
|
|
79
77
|
} = options;
|
|
80
78
|
const priorPhase = phaseToPriorPhase[phase];
|
|
81
79
|
const priorPhaseArtifacts = (priorPhase && artifactState[priorPhase]) || {};
|
|
@@ -92,7 +90,6 @@ async function collectPhaseArtifacts(options) {
|
|
|
92
90
|
: /** @type {Dependencies} */ ({});
|
|
93
91
|
|
|
94
92
|
return gatherer[phase]({
|
|
95
|
-
url,
|
|
96
93
|
gatherMode,
|
|
97
94
|
driver,
|
|
98
95
|
baseArtifacts,
|
|
@@ -36,13 +36,16 @@ 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
|
|
40
|
-
|
|
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();
|
|
44
48
|
await collectPhaseArtifacts({
|
|
45
|
-
url,
|
|
46
49
|
phase: 'getArtifact',
|
|
47
50
|
gatherMode: 'snapshot',
|
|
48
51
|
driver,
|
|
@@ -32,12 +32,11 @@ 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
|
|
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,
|
|
41
40
|
driver,
|
|
42
41
|
artifactDefinitions,
|
|
43
42
|
artifactState,
|
|
@@ -54,13 +53,16 @@ async function startTimespanGather(options) {
|
|
|
54
53
|
return {
|
|
55
54
|
async endTimespanGather() {
|
|
56
55
|
const finalUrl = await driver.url();
|
|
57
|
-
phaseOptions.url = finalUrl;
|
|
58
56
|
|
|
59
57
|
const runnerOptions = {config, computedCache};
|
|
60
58
|
const artifacts = await Runner.gather(
|
|
61
59
|
async () => {
|
|
62
|
-
baseArtifacts.URL
|
|
63
|
-
|
|
60
|
+
baseArtifacts.URL = {
|
|
61
|
+
initialUrl,
|
|
62
|
+
// TODO: Remove requestedUrl from timespan mode
|
|
63
|
+
requestedUrl: initialUrl,
|
|
64
|
+
finalUrl,
|
|
65
|
+
};
|
|
64
66
|
|
|
65
67
|
await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseOptions});
|
|
66
68
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
154
|
-
warnings: getNavigationWarnings({timedOut,
|
|
153
|
+
mainDocumentUrl,
|
|
154
|
+
warnings: getNavigationWarnings({timedOut, mainDocumentUrl, requestedUrl}),
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
|
-
* @param {{timedOut: boolean, requestedUrl: string,
|
|
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,
|
|
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,
|
|
169
|
+
if (!URL.equalWithExcludedFragments(requestedUrl, mainDocumentUrl)) {
|
|
170
170
|
warnings.push(str_(UIStrings.warningRedirected, {
|
|
171
171
|
requested: requestedUrl,
|
|
172
|
-
final:
|
|
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,
|
|
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
|
|
148
|
-
|
|
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 {
|
|
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 =
|
|
83
|
-
|
|
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: {
|
|
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
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
72
|
-
|
|
88
|
+
if (!isLighthouseRuntimeEvaluateScript(event.params)) {
|
|
89
|
+
this._scriptParsedEvents.push(event.params);
|
|
90
|
+
}
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
93
|
|
|
@@ -46,7 +46,8 @@ class RobotsTxt extends FRGatherer {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const {finalUrl} = passContext.baseArtifacts.URL;
|
|
50
|
+
const robotsUrl = new URL('/robots.txt', finalUrl).href;
|
|
50
51
|
await passContext.driver.fetcher.enable();
|
|
51
52
|
return passContext.driver.fetcher.fetchResource(robotsUrl)
|
|
52
53
|
.catch(err => ({status: null, content: null, errorMessage: err.message}));
|
|
@@ -94,8 +94,8 @@ class WebAppManifest extends FRGatherer {
|
|
|
94
94
|
*/
|
|
95
95
|
getArtifact(context) {
|
|
96
96
|
const driver = context.driver;
|
|
97
|
-
|
|
98
|
-
return WebAppManifest.getWebAppManifest(driver.defaultSession,
|
|
97
|
+
const {finalUrl} = context.baseArtifacts.URL;
|
|
98
|
+
return WebAppManifest.getWebAppManifest(driver.defaultSession, finalUrl);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|