lighthouse 8.4.0 → 8.5.0-dev.20210921
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/changelog.md +79 -0
- package/flow-report/assets/styles.css +85 -61
- package/flow-report/src/app.tsx +6 -2
- package/flow-report/src/common.tsx +0 -41
- package/flow-report/src/icons.tsx +11 -0
- package/flow-report/src/summary/summary.tsx +32 -25
- package/flow-report/src/topbar.tsx +60 -0
- package/flow-report/src/util.ts +0 -1
- package/flow-report/src/wrappers/{gauge.tsx → category-score.tsx} +11 -5
- package/flow-report/test/summary/summary-test.tsx +3 -3
- package/flow-report/tsconfig.json +6 -22
- package/{lighthouse-core/gather/gatherers/gather-context.js → lighthouse-cli/.eslintrc.cjs} +20 -18
- package/lighthouse-cli/bin.js +31 -21
- package/lighthouse-cli/cli-flags.js +12 -7
- package/lighthouse-cli/commands/commands.js +2 -7
- package/lighthouse-cli/commands/list-audits.js +2 -2
- package/lighthouse-cli/commands/list-trace-categories.js +2 -2
- package/lighthouse-cli/index.js +3 -1
- package/lighthouse-cli/package.json +4 -0
- package/lighthouse-cli/printer.js +4 -3
- package/lighthouse-cli/run.js +14 -15
- package/lighthouse-cli/sentry-prompt.js +5 -6
- package/lighthouse-cli/test/smokehouse/frontends/back-compat-util.js +1 -1
- package/lighthouse-cli/test/smokehouse/frontends/lib.js +5 -4
- package/lighthouse-cli/test/smokehouse/frontends/node.js +1 -1
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +93 -14
- package/lighthouse-cli/test/smokehouse/lib/child-process-error.js +1 -1
- package/lighthouse-cli/test/smokehouse/lib/concurrent-mapper.js +1 -1
- package/lighthouse-cli/test/smokehouse/lib/local-console.js +1 -1
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +20 -16
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +15 -12
- package/lighthouse-cli/test/smokehouse/report-assert.js +23 -13
- package/lighthouse-cli/test/smokehouse/smokehouse.js +32 -11
- package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +0 -2
- package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +6 -1
- package/lighthouse-core/audits/dobetterweb/js-libraries.js +5 -0
- package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -0
- package/lighthouse-core/audits/lcp-lazy-loaded.js +1 -0
- package/lighthouse-core/audits/oopif-iframe-test-audit.js +30 -0
- package/lighthouse-core/computed/metrics/metric.js +2 -1
- package/lighthouse-core/config/config-helpers.js +47 -3
- package/lighthouse-core/config/config.js +3 -46
- package/lighthouse-core/config/default-config.js +0 -1
- package/lighthouse-core/fraggle-rock/config/config.js +22 -4
- package/lighthouse-core/fraggle-rock/config/default-config.js +0 -9
- package/lighthouse-core/fraggle-rock/config/filters.js +3 -0
- package/lighthouse-core/fraggle-rock/config/validation.js +19 -1
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +8 -3
- package/lighthouse-core/fraggle-rock/gather/driver.js +10 -0
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +40 -19
- package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +3 -0
- package/lighthouse-core/fraggle-rock/gather/session.js +44 -1
- package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +4 -1
- package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +5 -1
- package/lighthouse-core/gather/devtools-log.js +9 -3
- package/lighthouse-core/gather/driver/network-monitor.js +57 -13
- package/lighthouse-core/gather/driver/prepare.js +43 -17
- package/lighthouse-core/gather/driver/target-manager.js +125 -0
- package/lighthouse-core/gather/driver.js +20 -0
- package/lighthouse-core/gather/fetcher.js +2 -2
- package/lighthouse-core/gather/gather-runner.js +1 -1
- package/lighthouse-core/gather/gatherers/css-usage.js +0 -2
- package/lighthouse-core/gather/gatherers/devtools-log.js +11 -9
- package/lighthouse-core/gather/gatherers/js-usage.js +0 -1
- package/lighthouse-core/gather/gatherers/script-elements.js +4 -5
- package/lighthouse-core/gather/gatherers/seo/robots-txt.js +3 -3
- package/lighthouse-core/gather/gatherers/trace.js +4 -5
- package/lighthouse-core/index.js +4 -0
- package/lighthouse-core/lib/emulation.js +0 -1
- package/lighthouse-core/lib/i18n/locales/en-US.json +6 -0
- package/lighthouse-core/lib/i18n/locales/en-XL.json +6 -0
- package/lighthouse-core/lib/network-recorder.js +8 -0
- package/lighthouse-core/lib/proto-preprocessor.js +5 -10
- package/lighthouse-core/runner.js +1 -3
- package/package.json +12 -9
- package/readme.md +2 -0
- package/report/assets/styles.css +120 -12
- package/report/assets/templates.html +13 -0
- package/report/clients/psi.js +5 -1
- package/report/generator/tsconfig.json +5 -16
- package/report/renderer/category-renderer.js +58 -5
- package/report/renderer/components.js +25 -5
- package/report/renderer/performance-category-renderer.js +5 -5
- package/report/renderer/pwa-category-renderer.js +10 -0
- package/report/renderer/report-renderer.js +12 -3
- package/report/renderer/report-ui-features.js +1 -4
- package/report/test/renderer/category-renderer-test.js +19 -0
- package/report/tsconfig.json +6 -21
- package/report/types/report-result.d.ts +1 -1
- package/root.js +3 -1
- package/third-party/snyk/snapshot.json +2 -2
- package/tsconfig-all.json +15 -0
- package/tsconfig-base.json +24 -0
- package/tsconfig.json +8 -18
- package/types/artifacts.d.ts +7 -7
- package/types/config.d.ts +1 -1
- package/types/gatherer.d.ts +7 -1
- package/types/lhr/flow.d.ts +0 -1
- package/types/lhr/lhr.d.ts +5 -1
- package/types/lhr/tsconfig.json +4 -14
- package/types/protocol.d.ts +2 -0
- package/types/smokehouse.d.ts +2 -1
- package/dist/report/flow.js +0 -149
- package/dist/report/standalone.js +0 -228
- package/flow-report/test/common-test.tsx +0 -131
- package/lighthouse-core/gather/gatherers/host-form-factor.js +0 -31
- package/lighthouse-core/gather/gatherers/host-user-agent.js +0 -29
|
@@ -47,10 +47,7 @@ const artifacts = {
|
|
|
47
47
|
FontSize: '',
|
|
48
48
|
FormElements: '',
|
|
49
49
|
FullPageScreenshot: '',
|
|
50
|
-
GatherContext: '',
|
|
51
50
|
GlobalListeners: '',
|
|
52
|
-
HostFormFactor: '',
|
|
53
|
-
HostUserAgent: '',
|
|
54
51
|
IFrameElements: '',
|
|
55
52
|
ImageElements: '',
|
|
56
53
|
InstallabilityErrors: '',
|
|
@@ -85,8 +82,6 @@ for (const key of Object.keys(artifacts)) {
|
|
|
85
82
|
const defaultConfig = {
|
|
86
83
|
artifacts: [
|
|
87
84
|
// Artifacts which can be depended on come first.
|
|
88
|
-
{id: artifacts.HostUserAgent, gatherer: 'host-user-agent'},
|
|
89
|
-
{id: artifacts.HostFormFactor, gatherer: 'host-form-factor'},
|
|
90
85
|
{id: artifacts.DevtoolsLog, gatherer: 'devtools-log'},
|
|
91
86
|
{id: artifacts.Trace, gatherer: 'trace'},
|
|
92
87
|
|
|
@@ -103,7 +98,6 @@ const defaultConfig = {
|
|
|
103
98
|
{id: artifacts.FontSize, gatherer: 'seo/font-size'},
|
|
104
99
|
{id: artifacts.FormElements, gatherer: 'form-elements'},
|
|
105
100
|
{id: artifacts.FullPageScreenshot, gatherer: 'full-page-screenshot'},
|
|
106
|
-
{id: artifacts.GatherContext, gatherer: 'gather-context'},
|
|
107
101
|
{id: artifacts.GlobalListeners, gatherer: 'global-listeners'},
|
|
108
102
|
{id: artifacts.IFrameElements, gatherer: 'iframe-elements'},
|
|
109
103
|
{id: artifacts.ImageElements, gatherer: 'image-elements'},
|
|
@@ -142,8 +136,6 @@ const defaultConfig = {
|
|
|
142
136
|
cpuQuietThresholdMs: 1000,
|
|
143
137
|
artifacts: [
|
|
144
138
|
// Artifacts which can be depended on come first.
|
|
145
|
-
artifacts.HostUserAgent,
|
|
146
|
-
artifacts.HostFormFactor,
|
|
147
139
|
artifacts.DevtoolsLog,
|
|
148
140
|
artifacts.Trace,
|
|
149
141
|
|
|
@@ -158,7 +150,6 @@ const defaultConfig = {
|
|
|
158
150
|
artifacts.EmbeddedContent,
|
|
159
151
|
artifacts.FontSize,
|
|
160
152
|
artifacts.FormElements,
|
|
161
|
-
artifacts.GatherContext,
|
|
162
153
|
artifacts.GlobalListeners,
|
|
163
154
|
artifacts.IFrameElements,
|
|
164
155
|
artifacts.ImageElements,
|
|
@@ -38,6 +38,22 @@ function isValidArtifactDependency(dependent, dependency) {
|
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Throws if pluginName is invalid or (somehow) collides with a category in the
|
|
43
|
+
* configJSON being added to.
|
|
44
|
+
* @param {LH.Config.Json} configJSON
|
|
45
|
+
* @param {string} pluginName
|
|
46
|
+
*/
|
|
47
|
+
function assertValidPluginName(configJSON, pluginName) {
|
|
48
|
+
if (!pluginName.startsWith('lighthouse-plugin-')) {
|
|
49
|
+
throw new Error(`plugin name '${pluginName}' does not start with 'lighthouse-plugin-'`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (configJSON.categories && configJSON.categories[pluginName]) {
|
|
53
|
+
throw new Error(`plugin name '${pluginName}' not allowed because it is the id of a category already found in config`); // eslint-disable-line max-len
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
/**
|
|
42
58
|
* Throws an error if the provided object does not implement the required Fraggle Rock gatherer interface.
|
|
43
59
|
* @param {LH.Config.AnyFRGathererDefn} gathererDefn
|
|
@@ -123,9 +139,10 @@ function assertValidAudit(auditDefinition) {
|
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
// If it'll have a ✔ or ✖ displayed alongside the result, it should have failureTitle
|
|
142
|
+
const scoreDisplayMode = implementation.meta.scoreDisplayMode || Audit.SCORING_MODES.BINARY;
|
|
126
143
|
if (
|
|
127
144
|
!i18n.isStringOrIcuMessage(implementation.meta.failureTitle) &&
|
|
128
|
-
|
|
145
|
+
scoreDisplayMode === Audit.SCORING_MODES.BINARY
|
|
129
146
|
) {
|
|
130
147
|
throw new Error(`${auditName} has no meta.failureTitle and should.`);
|
|
131
148
|
}
|
|
@@ -283,6 +300,7 @@ function throwInvalidArtifactDependency(artifactId, dependencyKey) {
|
|
|
283
300
|
module.exports = {
|
|
284
301
|
isFRGathererDefn,
|
|
285
302
|
isValidArtifactDependency,
|
|
303
|
+
assertValidPluginName,
|
|
286
304
|
assertValidFRGatherer,
|
|
287
305
|
assertValidFRNavigations,
|
|
288
306
|
assertValidAudit,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
const log = require('lighthouse-logger');
|
|
9
9
|
const isEqual = require('lodash.isequal');
|
|
10
10
|
const {
|
|
11
|
+
getBrowserVersion,
|
|
11
12
|
getBenchmarkIndex,
|
|
12
13
|
getEnvironmentWarnings,
|
|
13
14
|
} = require('../../gather/driver/environment.js');
|
|
@@ -15,10 +16,12 @@ const {
|
|
|
15
16
|
/**
|
|
16
17
|
* @param {LH.Config.FRConfig} config
|
|
17
18
|
* @param {LH.Gatherer.FRTransitionalDriver} driver
|
|
19
|
+
* @param {{gatherMode: LH.Gatherer.GatherMode}} context
|
|
18
20
|
* @return {Promise<LH.BaseArtifacts>}
|
|
19
21
|
*/
|
|
20
|
-
async function getBaseArtifacts(config, driver) {
|
|
22
|
+
async function getBaseArtifacts(config, driver, context) {
|
|
21
23
|
const BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
|
|
24
|
+
const {userAgent} = await getBrowserVersion(driver.defaultSession);
|
|
22
25
|
|
|
23
26
|
return {
|
|
24
27
|
// Meta artifacts.
|
|
@@ -28,12 +31,14 @@ async function getBaseArtifacts(config, driver) {
|
|
|
28
31
|
settings: config.settings,
|
|
29
32
|
// Environment artifacts that can always be computed.
|
|
30
33
|
BenchmarkIndex,
|
|
34
|
+
HostUserAgent: userAgent,
|
|
35
|
+
HostFormFactor: userAgent.includes('Android') || userAgent.includes('Mobile') ?
|
|
36
|
+
'mobile' : 'desktop',
|
|
31
37
|
// Contextual artifacts whose collection changes based on gather mode.
|
|
32
38
|
URL: {requestedUrl: '', finalUrl: ''},
|
|
33
39
|
PageLoadError: null,
|
|
40
|
+
GatherContext: context,
|
|
34
41
|
// Artifacts that have been replaced by regular gatherers in Fraggle Rock.
|
|
35
|
-
HostFormFactor: 'mobile',
|
|
36
|
-
HostUserAgent: '',
|
|
37
42
|
Stacks: [],
|
|
38
43
|
NetworkUserAgent: '',
|
|
39
44
|
WebAppManifest: null,
|
|
@@ -16,6 +16,7 @@ const throwNotConnectedFn = () => {
|
|
|
16
16
|
|
|
17
17
|
/** @type {LH.Gatherer.FRProtocolSession} */
|
|
18
18
|
const defaultSession = {
|
|
19
|
+
setTargetInfo: throwNotConnectedFn,
|
|
19
20
|
hasNextProtocolTimeout: throwNotConnectedFn,
|
|
20
21
|
getNextProtocolTimeout: throwNotConnectedFn,
|
|
21
22
|
setNextProtocolTimeout: throwNotConnectedFn,
|
|
@@ -24,7 +25,10 @@ const defaultSession = {
|
|
|
24
25
|
off: throwNotConnectedFn,
|
|
25
26
|
addProtocolMessageListener: throwNotConnectedFn,
|
|
26
27
|
removeProtocolMessageListener: throwNotConnectedFn,
|
|
28
|
+
addSessionAttachedListener: throwNotConnectedFn,
|
|
29
|
+
removeSessionAttachedListener: throwNotConnectedFn,
|
|
27
30
|
sendCommand: throwNotConnectedFn,
|
|
31
|
+
dispose: throwNotConnectedFn,
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
/** @implements {LH.Gatherer.FRTransitionalDriver} */
|
|
@@ -69,6 +73,12 @@ class Driver {
|
|
|
69
73
|
this._executionContext = new ExecutionContext(this._session);
|
|
70
74
|
this._fetcher = new Fetcher(this._session, this._executionContext);
|
|
71
75
|
}
|
|
76
|
+
|
|
77
|
+
/** @return {Promise<void>} */
|
|
78
|
+
async disconnect() {
|
|
79
|
+
if (!this._session) return;
|
|
80
|
+
await this._session.dispose();
|
|
81
|
+
}
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
module.exports = Driver;
|
|
@@ -23,6 +23,7 @@ const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
|
|
|
23
23
|
const i18n = require('../../lib/i18n/i18n.js');
|
|
24
24
|
const LighthouseError = require('../../lib/lh-error.js');
|
|
25
25
|
const {getPageLoadError} = require('../../lib/navigation-error.js');
|
|
26
|
+
const Trace = require('../../gather/gatherers/trace.js');
|
|
26
27
|
const DevtoolsLog = require('../../gather/gatherers/devtools-log.js');
|
|
27
28
|
const NetworkRecords = require('../../computed/network-records.js');
|
|
28
29
|
|
|
@@ -32,6 +33,7 @@ const NetworkRecords = require('../../computed/network-records.js');
|
|
|
32
33
|
* @property {LH.Config.FRConfig} config
|
|
33
34
|
* @property {LH.Config.NavigationDefn} navigation
|
|
34
35
|
* @property {string} requestedUrl
|
|
36
|
+
* @property {LH.FRBaseArtifacts} baseArtifacts
|
|
35
37
|
* @property {Map<string, LH.ArbitraryEqualityMap>} computedCache
|
|
36
38
|
*/
|
|
37
39
|
|
|
@@ -45,7 +47,7 @@ async function _setup({driver, config, requestedUrl}) {
|
|
|
45
47
|
await driver.connect();
|
|
46
48
|
await gotoURL(driver, defaultNavigationConfig.blankPage, {waitUntil: ['navigated']});
|
|
47
49
|
|
|
48
|
-
const baseArtifacts = await getBaseArtifacts(config, driver);
|
|
50
|
+
const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'navigation'});
|
|
49
51
|
baseArtifacts.URL.requestedUrl = requestedUrl;
|
|
50
52
|
|
|
51
53
|
await prepare.prepareTargetForNavigationMode(driver, config.settings);
|
|
@@ -105,21 +107,36 @@ async function _navigate(navigationContext) {
|
|
|
105
107
|
/**
|
|
106
108
|
* @param {NavigationContext} navigationContext
|
|
107
109
|
* @param {PhaseState} phaseState
|
|
108
|
-
* @return {Promise<{devtoolsLog
|
|
110
|
+
* @return {Promise<{devtoolsLog?: LH.DevtoolsLog, records?: Array<LH.Artifacts.NetworkRequest>, trace?: LH.Trace}>}
|
|
109
111
|
*/
|
|
110
|
-
async function
|
|
112
|
+
async function _collectDebugData(navigationContext, phaseState) {
|
|
111
113
|
const devtoolsLogArtifactDefn = phaseState.artifactDefinitions.find(
|
|
112
114
|
definition => definition.gatherer.instance.meta.symbol === DevtoolsLog.symbol
|
|
113
115
|
);
|
|
114
|
-
|
|
116
|
+
const traceArtifactDefn = phaseState.artifactDefinitions.find(
|
|
117
|
+
definition => definition.gatherer.instance.meta.symbol === Trace.symbol
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const artifactDefinitions = [devtoolsLogArtifactDefn, traceArtifactDefn].filter(
|
|
121
|
+
/**
|
|
122
|
+
* @param {LH.Config.AnyArtifactDefn | undefined} defn
|
|
123
|
+
* @return {defn is LH.Config.AnyArtifactDefn}
|
|
124
|
+
*/
|
|
125
|
+
defn => Boolean(defn)
|
|
126
|
+
);
|
|
127
|
+
if (!artifactDefinitions.length) return {};
|
|
115
128
|
|
|
116
|
-
const devtoolsLogArtifactId = devtoolsLogArtifactDefn.id;
|
|
117
|
-
const artifactDefinitions = [devtoolsLogArtifactDefn];
|
|
118
129
|
await collectPhaseArtifacts({...phaseState, phase: 'getArtifact', artifactDefinitions});
|
|
130
|
+
const getArtifactState = phaseState.artifactState.getArtifact;
|
|
131
|
+
|
|
132
|
+
const devtoolsLogArtifactId = devtoolsLogArtifactDefn && devtoolsLogArtifactDefn.id;
|
|
133
|
+
const devtoolsLog = devtoolsLogArtifactId && await getArtifactState[devtoolsLogArtifactId];
|
|
134
|
+
const records = devtoolsLog && await NetworkRecords.request(devtoolsLog, navigationContext);
|
|
135
|
+
|
|
136
|
+
const traceArtifactId = traceArtifactDefn && traceArtifactDefn.id;
|
|
137
|
+
const trace = traceArtifactId && await getArtifactState[traceArtifactId];
|
|
119
138
|
|
|
120
|
-
|
|
121
|
-
const records = await NetworkRecords.request(devtoolsLog, navigationContext);
|
|
122
|
-
return {devtoolsLog, records};
|
|
139
|
+
return {devtoolsLog, records, trace};
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
/**
|
|
@@ -137,12 +154,12 @@ async function _computeNavigationResult(
|
|
|
137
154
|
) {
|
|
138
155
|
const {navigationError, finalUrl} = navigateResult;
|
|
139
156
|
const warnings = [...setupResult.warnings, ...navigateResult.warnings];
|
|
140
|
-
const
|
|
141
|
-
const pageLoadError =
|
|
157
|
+
const debugData = await _collectDebugData(navigationContext, phaseState);
|
|
158
|
+
const pageLoadError = debugData.records
|
|
142
159
|
? getPageLoadError(navigationError, {
|
|
143
160
|
url: finalUrl,
|
|
144
161
|
loadFailureMode: navigationContext.navigation.loadFailureMode,
|
|
145
|
-
networkRecords:
|
|
162
|
+
networkRecords: debugData.records,
|
|
146
163
|
})
|
|
147
164
|
: navigationError;
|
|
148
165
|
|
|
@@ -154,7 +171,8 @@ async function _computeNavigationResult(
|
|
|
154
171
|
/** @type {Partial<LH.GathererArtifacts>} */
|
|
155
172
|
const artifacts = {};
|
|
156
173
|
const pageLoadErrorId = `pageLoadError-${navigationContext.navigation.id}`;
|
|
157
|
-
if (
|
|
174
|
+
if (debugData.devtoolsLog) artifacts.devtoolsLogs = {[pageLoadErrorId]: debugData.devtoolsLog};
|
|
175
|
+
if (debugData.trace) artifacts.traces = {[pageLoadErrorId]: debugData.trace};
|
|
158
176
|
|
|
159
177
|
return {
|
|
160
178
|
finalUrl,
|
|
@@ -182,6 +200,7 @@ async function _navigation(navigationContext) {
|
|
|
182
200
|
computedCache: navigationContext.computedCache,
|
|
183
201
|
artifactDefinitions: navigationContext.navigation.artifacts,
|
|
184
202
|
artifactState,
|
|
203
|
+
baseArtifacts: navigationContext.baseArtifacts,
|
|
185
204
|
settings: navigationContext.config.settings,
|
|
186
205
|
};
|
|
187
206
|
|
|
@@ -197,10 +216,10 @@ async function _navigation(navigationContext) {
|
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
/**
|
|
200
|
-
* @param {{driver: Driver, config: LH.Config.FRConfig, requestedUrl: string; computedCache: NavigationContext['computedCache']}} args
|
|
219
|
+
* @param {{driver: Driver, config: LH.Config.FRConfig, requestedUrl: string; baseArtifacts: LH.FRBaseArtifacts, computedCache: NavigationContext['computedCache']}} args
|
|
201
220
|
* @return {Promise<{artifacts: Partial<LH.FRArtifacts & LH.FRBaseArtifacts>}>}
|
|
202
221
|
*/
|
|
203
|
-
async function _navigations({driver, config, requestedUrl, computedCache}) {
|
|
222
|
+
async function _navigations({driver, config, requestedUrl, baseArtifacts, computedCache}) {
|
|
204
223
|
if (!config.navigations) throw new Error('No navigations configured');
|
|
205
224
|
|
|
206
225
|
/** @type {Partial<LH.FRArtifacts & LH.FRBaseArtifacts>} */
|
|
@@ -214,6 +233,7 @@ async function _navigations({driver, config, requestedUrl, computedCache}) {
|
|
|
214
233
|
navigation,
|
|
215
234
|
requestedUrl,
|
|
216
235
|
config,
|
|
236
|
+
baseArtifacts,
|
|
217
237
|
computedCache,
|
|
218
238
|
};
|
|
219
239
|
|
|
@@ -243,7 +263,7 @@ async function _cleanup({requestedUrl, driver, config}) {
|
|
|
243
263
|
const didResetStorage = !config.settings.disableStorageReset;
|
|
244
264
|
if (didResetStorage) await storage.clearDataForOrigin(driver.defaultSession, requestedUrl);
|
|
245
265
|
|
|
246
|
-
|
|
266
|
+
await driver.disconnect();
|
|
247
267
|
}
|
|
248
268
|
|
|
249
269
|
/**
|
|
@@ -258,9 +278,10 @@ async function navigation(options) {
|
|
|
258
278
|
return Runner.run(
|
|
259
279
|
async () => {
|
|
260
280
|
const driver = new Driver(page);
|
|
261
|
-
const
|
|
262
|
-
const {
|
|
263
|
-
await
|
|
281
|
+
const context = {driver, config, requestedUrl};
|
|
282
|
+
const {baseArtifacts} = await _setup(context);
|
|
283
|
+
const {artifacts} = await _navigations({...context, baseArtifacts, computedCache});
|
|
284
|
+
await _cleanup(context);
|
|
264
285
|
|
|
265
286
|
return finalizeArtifacts(baseArtifacts, artifacts);
|
|
266
287
|
},
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @property {import('./driver.js')} driver
|
|
11
11
|
* @property {Array<LH.Config.AnyArtifactDefn>} artifactDefinitions
|
|
12
12
|
* @property {ArtifactState} artifactState
|
|
13
|
+
* @property {LH.FRBaseArtifacts} baseArtifacts
|
|
13
14
|
* @property {LH.Gatherer.FRGatherPhase} phase
|
|
14
15
|
* @property {LH.Gatherer.GatherMode} gatherMode
|
|
15
16
|
* @property {Map<string, LH.ArbitraryEqualityMap>} computedCache
|
|
@@ -68,6 +69,7 @@ async function collectPhaseArtifacts(options) {
|
|
|
68
69
|
driver,
|
|
69
70
|
artifactDefinitions,
|
|
70
71
|
artifactState,
|
|
72
|
+
baseArtifacts,
|
|
71
73
|
phase,
|
|
72
74
|
gatherMode,
|
|
73
75
|
computedCache,
|
|
@@ -91,6 +93,7 @@ async function collectPhaseArtifacts(options) {
|
|
|
91
93
|
url: await driver.url(),
|
|
92
94
|
gatherMode,
|
|
93
95
|
driver,
|
|
96
|
+
baseArtifacts,
|
|
94
97
|
dependencies,
|
|
95
98
|
computedCache,
|
|
96
99
|
settings,
|
|
@@ -18,8 +18,12 @@ class ProtocolSession {
|
|
|
18
18
|
*/
|
|
19
19
|
constructor(session) {
|
|
20
20
|
this._session = session;
|
|
21
|
+
/** @type {LH.Crdp.Target.TargetInfo|undefined} */
|
|
22
|
+
this._targetInfo = undefined;
|
|
21
23
|
/** @type {number|undefined} */
|
|
22
24
|
this._nextProtocolTimeout = undefined;
|
|
25
|
+
/** @type {WeakMap<any, any>} */
|
|
26
|
+
this._callbackMap = new WeakMap();
|
|
23
27
|
|
|
24
28
|
// FIXME: Monkeypatch puppeteer to be able to listen to *all* protocol events.
|
|
25
29
|
// This patched method will now emit a copy of every event on `*`.
|
|
@@ -27,13 +31,22 @@ class ProtocolSession {
|
|
|
27
31
|
// @ts-expect-error - Test for the monkeypatch.
|
|
28
32
|
if (originalEmit[SessionEmitMonkeypatch]) return;
|
|
29
33
|
session.emit = (method, ...args) => {
|
|
30
|
-
|
|
34
|
+
// OOPIF sessions need to emit their sessionId so downstream processors can recognize
|
|
35
|
+
// the target the event came from.
|
|
36
|
+
const sessionId = this._targetInfo && this._targetInfo.type === 'iframe' ?
|
|
37
|
+
this._targetInfo.targetId : undefined;
|
|
38
|
+
originalEmit.call(session, '*', {method, params: args[0], sessionId});
|
|
31
39
|
return originalEmit.call(session, method, ...args);
|
|
32
40
|
};
|
|
33
41
|
// @ts-expect-error - It's monkeypatching 🤷♂️.
|
|
34
42
|
session.emit[SessionEmitMonkeypatch] = true;
|
|
35
43
|
}
|
|
36
44
|
|
|
45
|
+
/** @param {LH.Crdp.Target.TargetInfo} targetInfo */
|
|
46
|
+
setTargetInfo(targetInfo) {
|
|
47
|
+
this._targetInfo = targetInfo;
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
/**
|
|
38
51
|
* @return {boolean}
|
|
39
52
|
*/
|
|
@@ -75,6 +88,27 @@ class ProtocolSession {
|
|
|
75
88
|
this._session.once(eventName, /** @type {*} */ (callback));
|
|
76
89
|
}
|
|
77
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Bind to the puppeteer `sessionattached` listener and return an LH ProtocolSession.
|
|
93
|
+
* @param {(session: ProtocolSession) => void} callback
|
|
94
|
+
*/
|
|
95
|
+
addSessionAttachedListener(callback) {
|
|
96
|
+
/** @param {import('puppeteer').CDPSession} session */
|
|
97
|
+
const listener = session => callback(new ProtocolSession(session));
|
|
98
|
+
this._callbackMap.set(callback, listener);
|
|
99
|
+
this._session.connection().on('sessionattached', listener);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Unbind to the puppeteer `sessionattached` listener.
|
|
104
|
+
* @param {(session: ProtocolSession) => void} callback
|
|
105
|
+
*/
|
|
106
|
+
removeSessionAttachedListener(callback) {
|
|
107
|
+
const listener = this._callbackMap.get(callback);
|
|
108
|
+
if (!listener) return;
|
|
109
|
+
this._session.connection().off('sessionattached', listener);
|
|
110
|
+
}
|
|
111
|
+
|
|
78
112
|
/**
|
|
79
113
|
* Bind to our custom event that fires for *any* protocol event.
|
|
80
114
|
* @param {(payload: LH.Protocol.RawEventMessage) => void} callback
|
|
@@ -129,6 +163,15 @@ class ProtocolSession {
|
|
|
129
163
|
if (timeout) clearTimeout(timeout);
|
|
130
164
|
});
|
|
131
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Disposes of a session so that it can no longer talk to Chrome.
|
|
169
|
+
* @return {Promise<void>}
|
|
170
|
+
*/
|
|
171
|
+
async dispose() {
|
|
172
|
+
this._session.removeAllListeners();
|
|
173
|
+
await this._session.detach();
|
|
174
|
+
}
|
|
132
175
|
}
|
|
133
176
|
|
|
134
177
|
module.exports = ProtocolSession;
|
|
@@ -28,7 +28,7 @@ async function snapshot(options) {
|
|
|
28
28
|
|
|
29
29
|
return Runner.run(
|
|
30
30
|
async () => {
|
|
31
|
-
const baseArtifacts = await getBaseArtifacts(config, driver);
|
|
31
|
+
const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'snapshot'});
|
|
32
32
|
baseArtifacts.URL.requestedUrl = url;
|
|
33
33
|
baseArtifacts.URL.finalUrl = url;
|
|
34
34
|
|
|
@@ -38,12 +38,15 @@ async function snapshot(options) {
|
|
|
38
38
|
phase: 'getArtifact',
|
|
39
39
|
gatherMode: 'snapshot',
|
|
40
40
|
driver,
|
|
41
|
+
baseArtifacts,
|
|
41
42
|
artifactDefinitions,
|
|
42
43
|
artifactState,
|
|
43
44
|
computedCache,
|
|
44
45
|
settings: config.settings,
|
|
45
46
|
});
|
|
46
47
|
|
|
48
|
+
await driver.disconnect();
|
|
49
|
+
|
|
47
50
|
const artifacts = await awaitArtifacts(artifactState);
|
|
48
51
|
return finalizeArtifacts(baseArtifacts, artifacts);
|
|
49
52
|
},
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
collectPhaseArtifacts,
|
|
13
13
|
awaitArtifacts,
|
|
14
14
|
} = require('./runner-helpers.js');
|
|
15
|
+
const {prepareTargetForTimespanMode} = require('../../gather/driver/prepare.js');
|
|
15
16
|
const {initializeConfig} = require('../config/config.js');
|
|
16
17
|
const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
|
|
17
18
|
|
|
@@ -29,17 +30,20 @@ async function startTimespan(options) {
|
|
|
29
30
|
const computedCache = new Map();
|
|
30
31
|
const artifactDefinitions = config.artifacts || [];
|
|
31
32
|
const requestedUrl = await options.page.url();
|
|
33
|
+
const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'timespan'});
|
|
32
34
|
const artifactState = getEmptyArtifactState();
|
|
33
35
|
/** @type {Omit<import('./runner-helpers.js').CollectPhaseArtifactOptions, 'phase'>} */
|
|
34
36
|
const phaseOptions = {
|
|
35
37
|
driver,
|
|
36
38
|
artifactDefinitions,
|
|
37
39
|
artifactState,
|
|
40
|
+
baseArtifacts,
|
|
38
41
|
computedCache,
|
|
39
42
|
gatherMode: 'timespan',
|
|
40
43
|
settings: config.settings,
|
|
41
44
|
};
|
|
42
45
|
|
|
46
|
+
await prepareTargetForTimespanMode(driver, config.settings);
|
|
43
47
|
await collectPhaseArtifacts({phase: 'startInstrumentation', ...phaseOptions});
|
|
44
48
|
await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseOptions});
|
|
45
49
|
|
|
@@ -48,13 +52,13 @@ async function startTimespan(options) {
|
|
|
48
52
|
const finalUrl = await options.page.url();
|
|
49
53
|
return Runner.run(
|
|
50
54
|
async () => {
|
|
51
|
-
const baseArtifacts = await getBaseArtifacts(config, driver);
|
|
52
55
|
baseArtifacts.URL.requestedUrl = requestedUrl;
|
|
53
56
|
baseArtifacts.URL.finalUrl = finalUrl;
|
|
54
57
|
|
|
55
58
|
await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseOptions});
|
|
56
59
|
await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseOptions});
|
|
57
60
|
await collectPhaseArtifacts({phase: 'getArtifact', ...phaseOptions});
|
|
61
|
+
await driver.disconnect();
|
|
58
62
|
|
|
59
63
|
const artifacts = await awaitArtifacts(artifactState);
|
|
60
64
|
return finalizeArtifacts(baseArtifacts, artifacts);
|
|
@@ -46,9 +46,15 @@ class DevtoolsLog {
|
|
|
46
46
|
* @param {LH.Protocol.RawEventMessage} message
|
|
47
47
|
*/
|
|
48
48
|
record(message) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
// We're not recording, skip the rest of the checks.
|
|
50
|
+
if (!this._isRecording) return;
|
|
51
|
+
// The event was likely an internal puppeteer method that uses Symbols.
|
|
52
|
+
if (typeof message.method !== 'string') return;
|
|
53
|
+
// The event didn't pass our filter, do not record it.
|
|
54
|
+
if (this._filter && !this._filter.test(message.method)) return;
|
|
55
|
+
|
|
56
|
+
// We passed all the checks, record the message.
|
|
57
|
+
this._messages.push(message);
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
60
|
|
|
@@ -15,48 +15,82 @@ const {EventEmitter} = require('events');
|
|
|
15
15
|
const NetworkRecorder = require('../../lib/network-recorder.js');
|
|
16
16
|
const NetworkRequest = require('../../lib/network-request.js');
|
|
17
17
|
const URL = require('../../lib/url-shim.js');
|
|
18
|
+
const TargetManager = require('./target-manager.js');
|
|
18
19
|
|
|
19
20
|
/** @typedef {import('../../lib/network-recorder.js').NetworkRecorderEvent} NetworkRecorderEvent */
|
|
20
21
|
/** @typedef {'network-2-idle'|'network-critical-idle'|'networkidle'|'networkbusy'|'network-critical-busy'|'network-2-busy'} NetworkMonitorEvent_ */
|
|
21
22
|
/** @typedef {NetworkRecorderEvent|NetworkMonitorEvent_} NetworkMonitorEvent */
|
|
23
|
+
/** @typedef {Record<NetworkMonitorEvent_, []> & Record<NetworkRecorderEvent, [NetworkRequest]> & {protocolmessage: [LH.Protocol.RawEventMessage]}} NetworkMonitorEventMap */
|
|
24
|
+
/** @typedef {LH.Protocol.StrictEventEmitter<NetworkMonitorEventMap>} NetworkMonitorEmitter */
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
/** @implements {NetworkMonitorEmitter} */
|
|
27
|
+
class NetworkMonitor {
|
|
24
28
|
/** @type {NetworkRecorder|undefined} */
|
|
25
29
|
_networkRecorder = undefined;
|
|
30
|
+
/** @type {TargetManager|undefined} */
|
|
31
|
+
_targetManager = undefined;
|
|
26
32
|
/** @type {Array<LH.Crdp.Page.Frame>} */
|
|
27
33
|
_frameNavigations = [];
|
|
28
34
|
|
|
29
35
|
/** @param {LH.Gatherer.FRProtocolSession} session */
|
|
30
36
|
constructor(session) {
|
|
31
|
-
super();
|
|
32
37
|
this._session = session;
|
|
33
38
|
|
|
39
|
+
this._onTargetAttached = this._onTargetAttached.bind(this);
|
|
40
|
+
|
|
41
|
+
/** @type {Map<string, LH.Gatherer.FRProtocolSession>} */
|
|
42
|
+
this._sessions = new Map();
|
|
43
|
+
|
|
34
44
|
/** @param {LH.Crdp.Page.FrameNavigatedEvent} event */
|
|
35
45
|
this._onFrameNavigated = event => this._frameNavigations.push(event.frame);
|
|
36
46
|
|
|
37
47
|
/** @param {LH.Protocol.RawEventMessage} event */
|
|
38
48
|
this._onProtocolMessage = event => {
|
|
49
|
+
this.emit('protocolmessage', event);
|
|
39
50
|
if (!this._networkRecorder) return;
|
|
40
51
|
this._networkRecorder.dispatch(event);
|
|
41
52
|
};
|
|
42
53
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
// Attach the event emitter types to this class.
|
|
55
|
+
const emitter = /** @type {NetworkMonitorEmitter} */ (new EventEmitter());
|
|
56
|
+
/** @type {typeof emitter['emit']} */
|
|
57
|
+
this.emit = emitter.emit.bind(emitter);
|
|
58
|
+
/** @type {typeof emitter['on']} */
|
|
59
|
+
this.on = emitter.on.bind(emitter);
|
|
60
|
+
/** @type {typeof emitter['once']} */
|
|
61
|
+
this.once = emitter.once.bind(emitter);
|
|
62
|
+
/** @type {typeof emitter['off']} */
|
|
63
|
+
this.off = emitter.off.bind(emitter);
|
|
64
|
+
/** @type {typeof emitter['addListener']} */
|
|
65
|
+
this.addListener = emitter.addListener.bind(emitter);
|
|
66
|
+
/** @type {typeof emitter['removeListener']} */
|
|
67
|
+
this.removeListener = emitter.removeListener.bind(emitter);
|
|
68
|
+
/** @type {typeof emitter['removeAllListeners']} */
|
|
69
|
+
this.removeAllListeners = emitter.removeAllListeners.bind(emitter);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {{target: {targetId: string}, session: LH.Gatherer.FRProtocolSession}} session
|
|
74
|
+
*/
|
|
75
|
+
async _onTargetAttached({session, target}) {
|
|
76
|
+
const targetId = target.targetId;
|
|
77
|
+
|
|
78
|
+
this._sessions.set(targetId, session);
|
|
79
|
+
session.addProtocolMessageListener(this._onProtocolMessage);
|
|
80
|
+
|
|
81
|
+
await session.sendCommand('Network.enable');
|
|
50
82
|
}
|
|
51
83
|
|
|
52
84
|
/**
|
|
53
85
|
* @return {Promise<void>}
|
|
54
86
|
*/
|
|
55
87
|
async enable() {
|
|
56
|
-
if (this.
|
|
88
|
+
if (this._targetManager) return;
|
|
57
89
|
|
|
58
90
|
this._frameNavigations = [];
|
|
91
|
+
this._sessions = new Map();
|
|
59
92
|
this._networkRecorder = new NetworkRecorder();
|
|
93
|
+
this._targetManager = new TargetManager(this._session);
|
|
60
94
|
|
|
61
95
|
/**
|
|
62
96
|
* Reemit the same network recorder events.
|
|
@@ -72,21 +106,31 @@ class NetworkMonitor extends EventEmitter {
|
|
|
72
106
|
this._networkRecorder.on('requestloaded', reEmit('requestloaded'));
|
|
73
107
|
|
|
74
108
|
this._session.on('Page.frameNavigated', this._onFrameNavigated);
|
|
75
|
-
this.
|
|
109
|
+
this._targetManager.addTargetAttachedListener(this._onTargetAttached);
|
|
76
110
|
|
|
77
111
|
await this._session.sendCommand('Page.enable');
|
|
78
|
-
await this.
|
|
112
|
+
await this._targetManager.enable();
|
|
79
113
|
}
|
|
80
114
|
|
|
81
115
|
/**
|
|
82
116
|
* @return {Promise<void>}
|
|
83
117
|
*/
|
|
84
118
|
async disable() {
|
|
119
|
+
if (!this._targetManager) return;
|
|
120
|
+
|
|
85
121
|
this._session.off('Page.frameNavigated', this._onFrameNavigated);
|
|
86
|
-
this.
|
|
122
|
+
this._targetManager.removeTargetAttachedListener(this._onTargetAttached);
|
|
123
|
+
|
|
124
|
+
for (const session of this._sessions.values()) {
|
|
125
|
+
session.removeProtocolMessageListener(this._onProtocolMessage);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await this._targetManager.disable();
|
|
87
129
|
|
|
88
130
|
this._frameNavigations = [];
|
|
89
131
|
this._networkRecorder = undefined;
|
|
132
|
+
this._targetManager = undefined;
|
|
133
|
+
this._sessions = new Map();
|
|
90
134
|
}
|
|
91
135
|
|
|
92
136
|
/** @return {Promise<string | undefined>} */
|