-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
navigation.js
182 lines (152 loc) · 6.75 KB
/
navigation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import log from 'lighthouse-logger';
import {waitForFullyLoaded, waitForFrameNavigated, waitForUserToContinue} from './wait-for-condition.js'; // eslint-disable-line max-len
import * as constants from '../../config/constants.js';
import * as i18n from '../../lib/i18n/i18n.js';
import UrlUtils from '../../lib/url-utils.js';
const UIStrings = {
/**
* @description Warning that the web page redirected during testing and that may have affected the load.
* @example {https://example.com/requested/page} requested
* @example {https://example.com/final/resolved/page} final
*/
warningRedirected: 'The page may not be loading as expected because your test URL ' +
`({requested}) was redirected to {final}. ` +
'Try testing the second URL directly.',
/**
* @description Warning that Lighthouse timed out while waiting for the page to load.
*/
warningTimeout: 'The page loaded too slowly to finish within the time limit. ' +
'Results may be incomplete.',
};
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
// Controls how long to wait after FCP before continuing
const DEFAULT_PAUSE_AFTER_FCP = 0;
// Controls how long to wait after onLoad before continuing
const DEFAULT_PAUSE_AFTER_LOAD = 0;
// Controls how long to wait between network requests before determining the network is quiet
const DEFAULT_NETWORK_QUIET_THRESHOLD = 5000;
// Controls how long to wait between longtasks before determining the CPU is idle, off by default
const DEFAULT_CPU_QUIET_THRESHOLD = 0;
/** @typedef {{waitUntil: Array<'fcp'|'load'|'navigated'>} & Partial<LH.Config.Settings>} NavigationOptions */
/** @param {NavigationOptions} options */
function resolveWaitForFullyLoadedOptions(options) {
/* eslint-disable max-len */
let {pauseAfterFcpMs, pauseAfterLoadMs, networkQuietThresholdMs, cpuQuietThresholdMs} = options;
let maxWaitMs = options.maxWaitForLoad;
let maxFCPMs = options.maxWaitForFcp;
if (typeof pauseAfterFcpMs !== 'number') pauseAfterFcpMs = DEFAULT_PAUSE_AFTER_FCP;
if (typeof pauseAfterLoadMs !== 'number') pauseAfterLoadMs = DEFAULT_PAUSE_AFTER_LOAD;
if (typeof networkQuietThresholdMs !== 'number') {
networkQuietThresholdMs = DEFAULT_NETWORK_QUIET_THRESHOLD;
}
if (typeof cpuQuietThresholdMs !== 'number') cpuQuietThresholdMs = DEFAULT_CPU_QUIET_THRESHOLD;
if (typeof maxWaitMs !== 'number') maxWaitMs = constants.defaultSettings.maxWaitForLoad;
if (typeof maxFCPMs !== 'number') maxFCPMs = constants.defaultSettings.maxWaitForFcp;
/* eslint-enable max-len */
if (!options.waitUntil.includes('fcp')) maxFCPMs = undefined;
return {
pauseAfterFcpMs,
pauseAfterLoadMs,
networkQuietThresholdMs,
cpuQuietThresholdMs,
maxWaitForLoadedMs: maxWaitMs,
maxWaitForFcpMs: maxFCPMs,
};
}
/**
* Navigates to the given URL, assuming that the page is not already on this URL.
* Resolves on the url of the loaded page, taking into account any redirects.
* Typical use of this method involves navigating to a neutral page such as `about:blank` in between
* navigations.
*
* @param {LH.Gatherer.Driver} driver
* @param {LH.NavigationRequestor} requestor
* @param {NavigationOptions} options
* @return {Promise<{requestedUrl: string, mainDocumentUrl: string, warnings: Array<LH.IcuMessage>}>}
*/
async function gotoURL(driver, requestor, options) {
const status = typeof requestor === 'string' ?
{msg: `Navigating to ${requestor}`, id: 'lh:driver:navigate'} :
{msg: 'Navigating using a user defined function', id: 'lh:driver:navigate'};
log.time(status);
const session = driver.defaultSession;
const networkMonitor = driver.networkMonitor;
// Enable the events and network monitor needed to track navigation progress.
await session.sendCommand('Page.enable');
await session.sendCommand('Page.setLifecycleEventsEnabled', {enabled: true});
let waitForNavigationTriggered;
if (typeof requestor === 'string') {
// No timeout needed for Page.navigate. See https://github.com/GoogleChrome/lighthouse/pull/6413
session.setNextProtocolTimeout(Infinity);
waitForNavigationTriggered = session.sendCommand('Page.navigate', {url: requestor});
} else {
waitForNavigationTriggered = requestor();
}
const waitForNavigated = options.waitUntil.includes('navigated');
const waitForLoad = options.waitUntil.includes('load');
const waitForFcp = options.waitUntil.includes('fcp');
/** @type {Array<Promise<{timedOut: boolean}>>} */
const waitConditionPromises = [];
if (waitForNavigated) {
const navigatedPromise = waitForFrameNavigated(session).promise;
waitConditionPromises.push(navigatedPromise.then(() => ({timedOut: false})));
}
if (waitForLoad) {
const waitOptions = resolveWaitForFullyLoadedOptions(options);
waitConditionPromises.push(waitForFullyLoaded(session, networkMonitor, waitOptions));
} else if (waitForFcp) {
throw new Error('Cannot wait for FCP without waiting for page load');
}
const waitConditions = await Promise.race([
session.onCrashPromise(),
Promise.all(waitConditionPromises),
]);
const timedOut = waitConditions.some(condition => condition.timedOut);
const navigationUrls = await networkMonitor.getNavigationUrls();
let requestedUrl = navigationUrls.requestedUrl;
if (typeof requestor === 'string') {
if (requestedUrl && !UrlUtils.equalWithExcludedFragments(requestor, requestedUrl)) {
log.error(
'Navigation',
`Provided URL (${requestor}) did not match initial navigation URL (${requestedUrl})`
);
}
requestedUrl = requestor;
}
if (!requestedUrl) throw Error('No navigations detected when running user defined requestor.');
const mainDocumentUrl = navigationUrls.mainDocumentUrl || requestedUrl;
// Bring `Page.navigate` errors back into the promise chain. See https://github.com/GoogleChrome/lighthouse/pull/6739.
await waitForNavigationTriggered;
if (options.debugNavigation) {
await waitForUserToContinue(driver);
}
log.timeEnd(status);
return {
requestedUrl,
mainDocumentUrl,
warnings: getNavigationWarnings({timedOut, mainDocumentUrl, requestedUrl}),
};
}
/**
* @param {{timedOut: boolean, requestedUrl: string, mainDocumentUrl: string; }} navigation
* @return {Array<LH.IcuMessage>}
*/
function getNavigationWarnings(navigation) {
const {requestedUrl, mainDocumentUrl} = navigation;
/** @type {Array<LH.IcuMessage>} */
const warnings = [];
if (navigation.timedOut) warnings.push(str_(UIStrings.warningTimeout));
if (!UrlUtils.equalWithExcludedFragments(requestedUrl, mainDocumentUrl)) {
warnings.push(str_(UIStrings.warningRedirected, {
requested: requestedUrl,
final: mainDocumentUrl,
}));
}
return warnings;
}
export {gotoURL, getNavigationWarnings, UIStrings};