lighthouse 9.5.0-dev.20220619 → 9.5.0-dev.20220622
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/lighthouse-cli/bin.js +8 -2
- package/lighthouse-cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +71 -0
- package/lighthouse-cli/test/smokehouse/report-assert-test.js +264 -0
- package/lighthouse-cli/test/smokehouse/report-assert.js +39 -35
- package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +1 -1
- package/lighthouse-core/config/config-helpers.js +15 -0
- package/lighthouse-core/fraggle-rock/config/config.js +45 -1
- package/lighthouse-core/fraggle-rock/gather/driver.js +13 -5
- package/lighthouse-core/fraggle-rock/gather/session.js +23 -67
- package/lighthouse-core/gather/driver/navigation.js +1 -1
- package/lighthouse-core/gather/driver/network-monitor.js +14 -59
- package/lighthouse-core/gather/driver/target-manager.js +110 -45
- package/lighthouse-core/gather/driver.js +24 -16
- package/lighthouse-core/gather/gatherers/devtools-log.js +7 -11
- package/lighthouse-core/gather/gatherers/full-page-screenshot.js +1 -1
- package/lighthouse-core/index.js +24 -9
- package/lighthouse-core/lib/cdt/Common.js +13 -0
- package/lighthouse-core/lib/cdt/Platform.js +3 -0
- package/lighthouse-core/lib/cdt/generated/ParsedURL.js +178 -0
- package/lighthouse-core/lib/cdt/generated/SourceMap.js +155 -62
- package/package.json +2 -2
- package/third-party/devtools-tests/README.md +14 -0
- package/third-party/devtools-tests/e2e/lighthouse/generate-report_test.ts +29 -0
- package/third-party/devtools-tests/e2e/lighthouse/indexeddb-warning_test.ts +29 -0
- package/third-party/devtools-tests/e2e/resources/lighthouse/lighthouse-storage.html +11 -0
- package/third-party/download-content-shell/README.md +1 -0
- package/third-party/download-content-shell/download-content-shell.js +6 -1
- package/types/gatherer.d.ts +5 -2
- package/types/protocol.d.ts +19 -17
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const log = require('lighthouse-logger');
|
|
9
|
-
const ProtocolSession = require('./session.js');
|
|
10
9
|
const ExecutionContext = require('../../gather/driver/execution-context.js');
|
|
11
10
|
const Fetcher = require('../../gather/fetcher.js');
|
|
11
|
+
const TargetManager = require('../../gather/driver/target-manager.js');
|
|
12
12
|
|
|
13
13
|
/** @return {*} */
|
|
14
14
|
const throwNotConnectedFn = () => {
|
|
@@ -24,8 +24,6 @@ const throwingSession = {
|
|
|
24
24
|
on: throwNotConnectedFn,
|
|
25
25
|
once: throwNotConnectedFn,
|
|
26
26
|
off: throwNotConnectedFn,
|
|
27
|
-
addProtocolMessageListener: throwNotConnectedFn,
|
|
28
|
-
removeProtocolMessageListener: throwNotConnectedFn,
|
|
29
27
|
sendCommand: throwNotConnectedFn,
|
|
30
28
|
dispose: throwNotConnectedFn,
|
|
31
29
|
};
|
|
@@ -37,6 +35,8 @@ class Driver {
|
|
|
37
35
|
*/
|
|
38
36
|
constructor(page) {
|
|
39
37
|
this._page = page;
|
|
38
|
+
/** @type {TargetManager|undefined} */
|
|
39
|
+
this._targetManager = undefined;
|
|
40
40
|
/** @type {ExecutionContext|undefined} */
|
|
41
41
|
this._executionContext = undefined;
|
|
42
42
|
/** @type {Fetcher|undefined} */
|
|
@@ -57,6 +57,11 @@ class Driver {
|
|
|
57
57
|
return this._fetcher;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
get targetManager() {
|
|
61
|
+
if (!this._targetManager) return throwNotConnectedFn();
|
|
62
|
+
return this._targetManager;
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
/** @return {Promise<string>} */
|
|
61
66
|
async url() {
|
|
62
67
|
return this._page.url();
|
|
@@ -67,8 +72,10 @@ class Driver {
|
|
|
67
72
|
if (this.defaultSession !== throwingSession) return;
|
|
68
73
|
const status = {msg: 'Connecting to browser', id: 'lh:driver:connect'};
|
|
69
74
|
log.time(status);
|
|
70
|
-
const
|
|
71
|
-
this.
|
|
75
|
+
const cdpSession = await this._page.target().createCDPSession();
|
|
76
|
+
this._targetManager = new TargetManager(cdpSession);
|
|
77
|
+
await this._targetManager.enable();
|
|
78
|
+
this.defaultSession = this._targetManager.rootSession();
|
|
72
79
|
this._executionContext = new ExecutionContext(this.defaultSession);
|
|
73
80
|
this._fetcher = new Fetcher(this.defaultSession);
|
|
74
81
|
log.timeEnd(status);
|
|
@@ -77,6 +84,7 @@ class Driver {
|
|
|
77
84
|
/** @return {Promise<void>} */
|
|
78
85
|
async disconnect() {
|
|
79
86
|
if (this.defaultSession === throwingSession) return;
|
|
87
|
+
this._targetManager?.disable();
|
|
80
88
|
await this.defaultSession.dispose();
|
|
81
89
|
}
|
|
82
90
|
}
|
|
@@ -5,31 +5,45 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const EventEmitter = require('events').EventEmitter;
|
|
8
9
|
const LHError = require('../../lib/lh-error.js');
|
|
9
10
|
|
|
10
11
|
// Controls how long to wait for a response after sending a DevTools protocol command.
|
|
11
12
|
const DEFAULT_PROTOCOL_TIMEOUT = 30000;
|
|
12
13
|
|
|
14
|
+
/** @typedef {new () => LH.Protocol.StrictEventEmitter<LH.CrdpEvents>} CrdpEventMessageEmitter */
|
|
15
|
+
const CrdpEventEmitter = /** @type {CrdpEventMessageEmitter} */ (EventEmitter);
|
|
16
|
+
|
|
13
17
|
/** @implements {LH.Gatherer.FRProtocolSession} */
|
|
14
|
-
class ProtocolSession {
|
|
18
|
+
class ProtocolSession extends CrdpEventEmitter {
|
|
15
19
|
/**
|
|
16
20
|
* @param {LH.Puppeteer.CDPSession} cdpSession
|
|
17
21
|
*/
|
|
18
22
|
constructor(cdpSession) {
|
|
23
|
+
super();
|
|
24
|
+
|
|
19
25
|
this._cdpSession = cdpSession;
|
|
20
26
|
/** @type {LH.Crdp.Target.TargetInfo|undefined} */
|
|
21
27
|
this._targetInfo = undefined;
|
|
22
28
|
/** @type {number|undefined} */
|
|
23
29
|
this._nextProtocolTimeout = undefined;
|
|
24
|
-
|
|
25
|
-
this.
|
|
30
|
+
|
|
31
|
+
this._handleProtocolEvent = this._handleProtocolEvent.bind(this);
|
|
32
|
+
this._cdpSession.on('*', this._handleProtocolEvent);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
id() {
|
|
36
|
+
return this._cdpSession.id();
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Re-emit protocol events from the underlying CDPSession.
|
|
41
|
+
* @template {keyof LH.CrdpEvents} E
|
|
42
|
+
* @param {E} method
|
|
43
|
+
* @param {LH.CrdpEvents[E]} params
|
|
44
|
+
*/
|
|
45
|
+
_handleProtocolEvent(method, ...params) {
|
|
46
|
+
this.emit(method, ...params);
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
/** @param {LH.Crdp.Target.TargetInfo} targetInfo */
|
|
@@ -58,64 +72,6 @@ class ProtocolSession {
|
|
|
58
72
|
this._nextProtocolTimeout = ms;
|
|
59
73
|
}
|
|
60
74
|
|
|
61
|
-
/**
|
|
62
|
-
* Bind listeners for protocol events.
|
|
63
|
-
* @template {keyof LH.CrdpEvents} E
|
|
64
|
-
* @param {E} eventName
|
|
65
|
-
* @param {(...args: LH.CrdpEvents[E]) => void} callback
|
|
66
|
-
*/
|
|
67
|
-
on(eventName, callback) {
|
|
68
|
-
this._cdpSession.on(eventName, /** @type {*} */ (callback));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Bind listeners for protocol events.
|
|
73
|
-
* @template {keyof LH.CrdpEvents} E
|
|
74
|
-
* @param {E} eventName
|
|
75
|
-
* @param {(...args: LH.CrdpEvents[E]) => void} callback
|
|
76
|
-
*/
|
|
77
|
-
once(eventName, callback) {
|
|
78
|
-
this._cdpSession.once(eventName, /** @type {*} */ (callback));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Bind to puppeteer's '*' event that fires for *any* protocol event,
|
|
83
|
-
* and wrap it with data about the protocol message instead of just the event.
|
|
84
|
-
* @param {(payload: LH.Protocol.RawEventMessage) => void} callback
|
|
85
|
-
*/
|
|
86
|
-
addProtocolMessageListener(callback) {
|
|
87
|
-
/**
|
|
88
|
-
* @param {any} method
|
|
89
|
-
* @param {any} event
|
|
90
|
-
*/
|
|
91
|
-
const listener = (method, event) => callback({
|
|
92
|
-
method,
|
|
93
|
-
params: event,
|
|
94
|
-
sessionId: this.sessionId(),
|
|
95
|
-
});
|
|
96
|
-
this._callbackMap.set(callback, listener);
|
|
97
|
-
this._cdpSession.on('*', /** @type {*} */ (listener));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @param {(payload: LH.Protocol.RawEventMessage) => void} callback
|
|
102
|
-
*/
|
|
103
|
-
removeProtocolMessageListener(callback) {
|
|
104
|
-
const listener = this._callbackMap.get(callback);
|
|
105
|
-
if (!listener) return;
|
|
106
|
-
this._cdpSession.off('*', /** @type {*} */ (listener));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Bind listeners for protocol events.
|
|
111
|
-
* @template {keyof LH.CrdpEvents} E
|
|
112
|
-
* @param {E} eventName
|
|
113
|
-
* @param {(...args: LH.CrdpEvents[E]) => void} callback
|
|
114
|
-
*/
|
|
115
|
-
off(eventName, callback) {
|
|
116
|
-
this._cdpSession.off(eventName, /** @type {*} */ (callback));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
75
|
/**
|
|
120
76
|
* @template {keyof LH.CrdpCommands} C
|
|
121
77
|
* @param {C} method
|
|
@@ -149,7 +105,7 @@ class ProtocolSession {
|
|
|
149
105
|
* @return {Promise<void>}
|
|
150
106
|
*/
|
|
151
107
|
async dispose() {
|
|
152
|
-
this._cdpSession.
|
|
108
|
+
this._cdpSession.off('*', this._handleProtocolEvent);
|
|
153
109
|
await this._cdpSession.detach();
|
|
154
110
|
}
|
|
155
111
|
}
|
|
@@ -89,7 +89,7 @@ async function gotoURL(driver, requestor, options) {
|
|
|
89
89
|
log.time(status);
|
|
90
90
|
|
|
91
91
|
const session = driver.defaultSession;
|
|
92
|
-
const networkMonitor = new NetworkMonitor(driver.
|
|
92
|
+
const networkMonitor = new NetworkMonitor(driver.targetManager);
|
|
93
93
|
|
|
94
94
|
// Enable the events and network monitor needed to track navigation progress.
|
|
95
95
|
await networkMonitor.enable();
|
|
@@ -15,38 +15,34 @@ 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');
|
|
19
18
|
|
|
20
19
|
/** @typedef {import('../../lib/network-recorder.js').NetworkRecorderEvent} NetworkRecorderEvent */
|
|
21
20
|
/** @typedef {'network-2-idle'|'network-critical-idle'|'networkidle'|'networkbusy'|'network-critical-busy'|'network-2-busy'} NetworkMonitorEvent_ */
|
|
22
21
|
/** @typedef {NetworkRecorderEvent|NetworkMonitorEvent_} NetworkMonitorEvent */
|
|
23
|
-
/** @typedef {Record<NetworkMonitorEvent_, []> & Record<NetworkRecorderEvent, [NetworkRequest]>
|
|
22
|
+
/** @typedef {Record<NetworkMonitorEvent_, []> & Record<NetworkRecorderEvent, [NetworkRequest]>} NetworkMonitorEventMap */
|
|
24
23
|
/** @typedef {LH.Protocol.StrictEventEmitter<NetworkMonitorEventMap>} NetworkMonitorEmitter */
|
|
25
24
|
|
|
26
25
|
/** @implements {NetworkMonitorEmitter} */
|
|
27
26
|
class NetworkMonitor {
|
|
28
27
|
/** @type {NetworkRecorder|undefined} */
|
|
29
28
|
_networkRecorder = undefined;
|
|
30
|
-
/** @type {TargetManager|undefined} */
|
|
31
|
-
_targetManager = undefined;
|
|
32
29
|
/** @type {Array<LH.Crdp.Page.Frame>} */
|
|
33
30
|
_frameNavigations = [];
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
// TODO(FR-COMPAT): switch to real TargetManager when legacy removed.
|
|
33
|
+
/** @param {LH.Gatherer.FRTransitionalDriver['targetManager']} targetManager */
|
|
34
|
+
constructor(targetManager) {
|
|
35
|
+
/** @type {LH.Gatherer.FRTransitionalDriver['targetManager']} */
|
|
36
|
+
this._targetManager = targetManager;
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/** @type {Map<string, LH.Gatherer.FRProtocolSession>} */
|
|
42
|
-
this._sessions = new Map();
|
|
38
|
+
/** @type {LH.Gatherer.FRProtocolSession} */
|
|
39
|
+
this._session = targetManager.rootSession();
|
|
43
40
|
|
|
44
41
|
/** @param {LH.Crdp.Page.FrameNavigatedEvent} event */
|
|
45
42
|
this._onFrameNavigated = event => this._frameNavigations.push(event.frame);
|
|
46
43
|
|
|
47
44
|
/** @param {LH.Protocol.RawEventMessage} event */
|
|
48
45
|
this._onProtocolMessage = event => {
|
|
49
|
-
this.emit('protocolmessage', event);
|
|
50
46
|
if (!this._networkRecorder) return;
|
|
51
47
|
this._networkRecorder.dispatch(event);
|
|
52
48
|
};
|
|
@@ -67,33 +63,18 @@ class NetworkMonitor {
|
|
|
67
63
|
this.removeListener = emitter.removeListener.bind(emitter);
|
|
68
64
|
/** @type {typeof emitter['removeAllListeners']} */
|
|
69
65
|
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');
|
|
66
|
+
/** @type {typeof emitter['listenerCount']} */
|
|
67
|
+
this.listenerCount = emitter.listenerCount.bind(emitter);
|
|
82
68
|
}
|
|
83
69
|
|
|
84
70
|
/**
|
|
85
71
|
* @return {Promise<void>}
|
|
86
72
|
*/
|
|
87
73
|
async enable() {
|
|
88
|
-
if (this.
|
|
74
|
+
if (this._networkRecorder) return;
|
|
89
75
|
|
|
90
76
|
this._frameNavigations = [];
|
|
91
|
-
this._sessions = new Map();
|
|
92
77
|
this._networkRecorder = new NetworkRecorder();
|
|
93
|
-
/** @type {LH.Puppeteer.CDPSession} */
|
|
94
|
-
// @ts-expect-error - temporarily reach in to get CDPSession
|
|
95
|
-
const rootCdpSession = this._session._cdpSession;
|
|
96
|
-
this._targetManager = new TargetManager(rootCdpSession);
|
|
97
78
|
|
|
98
79
|
/**
|
|
99
80
|
* Reemit the same network recorder events.
|
|
@@ -109,46 +90,20 @@ class NetworkMonitor {
|
|
|
109
90
|
this._networkRecorder.on('requestfinished', reEmit('requestfinished'));
|
|
110
91
|
|
|
111
92
|
this._session.on('Page.frameNavigated', this._onFrameNavigated);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// Legacy driver does its own target management.
|
|
115
|
-
// @ts-expect-error
|
|
116
|
-
const isLegacyRunner = Boolean(this._session._domainEnabledCounts);
|
|
117
|
-
if (isLegacyRunner) {
|
|
118
|
-
this._session.addProtocolMessageListener(this._onProtocolMessage);
|
|
119
|
-
} else {
|
|
120
|
-
this._targetManager.addTargetAttachedListener(this._onTargetAttached);
|
|
121
|
-
await this._targetManager.enable();
|
|
122
|
-
}
|
|
93
|
+
this._targetManager.on('protocolevent', this._onProtocolMessage);
|
|
123
94
|
}
|
|
124
95
|
|
|
125
96
|
/**
|
|
126
97
|
* @return {Promise<void>}
|
|
127
98
|
*/
|
|
128
99
|
async disable() {
|
|
129
|
-
if (!this.
|
|
100
|
+
if (!this._networkRecorder) return;
|
|
130
101
|
|
|
131
102
|
this._session.off('Page.frameNavigated', this._onFrameNavigated);
|
|
132
|
-
|
|
133
|
-
// Legacy driver does its own target management.
|
|
134
|
-
// @ts-expect-error
|
|
135
|
-
const isLegacyRunner = Boolean(this._session._domainEnabledCounts);
|
|
136
|
-
if (isLegacyRunner) {
|
|
137
|
-
this._session.removeProtocolMessageListener(this._onProtocolMessage);
|
|
138
|
-
} else {
|
|
139
|
-
this._targetManager.removeTargetAttachedListener(this._onTargetAttached);
|
|
140
|
-
|
|
141
|
-
for (const session of this._sessions.values()) {
|
|
142
|
-
session.removeProtocolMessageListener(this._onProtocolMessage);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
await this._targetManager.disable();
|
|
146
|
-
}
|
|
103
|
+
this._targetManager.off('protocolevent', this._onProtocolMessage);
|
|
147
104
|
|
|
148
105
|
this._frameNavigations = [];
|
|
149
106
|
this._networkRecorder = undefined;
|
|
150
|
-
this._targetManager = undefined;
|
|
151
|
-
this._sessions = new Map();
|
|
152
107
|
}
|
|
153
108
|
|
|
154
109
|
/** @return {Promise<{requestedUrl?: string, mainDocumentUrl?: string}>} */
|
|
@@ -5,70 +5,125 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
* @fileoverview This class tracks multiple targets (the page itself and its OOPIFs) and allows consumers to
|
|
10
|
-
* listen for protocol events before each target is resumed.
|
|
11
|
-
*/
|
|
12
|
-
|
|
8
|
+
const EventEmitter = require('events').EventEmitter;
|
|
13
9
|
const log = require('lighthouse-logger');
|
|
14
10
|
const ProtocolSession = require('../../fraggle-rock/gather/session.js');
|
|
15
11
|
|
|
16
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {{
|
|
14
|
+
* target: LH.Crdp.Target.TargetInfo,
|
|
15
|
+
* cdpSession: LH.Puppeteer.CDPSession,
|
|
16
|
+
* session: LH.Gatherer.FRProtocolSession,
|
|
17
|
+
* protocolListener: (event: unknown) => void,
|
|
18
|
+
* }} TargetWithSession
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Add protocol event types to EventEmitter.
|
|
22
|
+
/** @typedef {{'protocolevent': [LH.Protocol.RawEventMessage]}} ProtocolEventRecord */
|
|
23
|
+
/** @typedef {new () => LH.Protocol.StrictEventEmitter<ProtocolEventRecord>} ProtocolEventMessageEmitter */
|
|
24
|
+
const ProtocolEventEmitter = /** @type {ProtocolEventMessageEmitter} */ (EventEmitter);
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Tracks targets (the page itself, its iframes, their iframes, etc) as they
|
|
28
|
+
* appear and allows listeners to the flattened protocol events from all targets.
|
|
29
|
+
*/
|
|
30
|
+
class TargetManager extends ProtocolEventEmitter {
|
|
19
31
|
/** @param {LH.Puppeteer.CDPSession} cdpSession */
|
|
20
32
|
constructor(cdpSession) {
|
|
33
|
+
super();
|
|
34
|
+
|
|
21
35
|
this._enabled = false;
|
|
22
36
|
this._rootCdpSession = cdpSession;
|
|
23
37
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
38
|
+
/**
|
|
39
|
+
* A map of target id to target/session information. Used to ensure unique
|
|
40
|
+
* attached targets.
|
|
41
|
+
* @type {Map<string, TargetWithSession>}
|
|
42
|
+
*/
|
|
43
|
+
this._targetIdToTargets = new Map();
|
|
26
44
|
|
|
27
45
|
this._onSessionAttached = this._onSessionAttached.bind(this);
|
|
46
|
+
this._onFrameNavigated = this._onFrameNavigated.bind(this);
|
|
47
|
+
}
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
/**
|
|
50
|
+
* @param {LH.Crdp.Page.FrameNavigatedEvent} frameNavigatedEvent
|
|
51
|
+
*/
|
|
52
|
+
async _onFrameNavigated(frameNavigatedEvent) {
|
|
53
|
+
// Child frames are handled in `_onSessionAttached`.
|
|
54
|
+
if (frameNavigatedEvent.frame.parentId) return;
|
|
55
|
+
|
|
56
|
+
// It's not entirely clear when this is necessary, but when the page switches processes on
|
|
57
|
+
// navigating from about:blank to the `requestedUrl`, resetting `setAutoAttach` has been
|
|
58
|
+
// necessary in the past.
|
|
59
|
+
await this._rootCdpSession.send('Target.setAutoAttach', {
|
|
60
|
+
autoAttach: true,
|
|
61
|
+
flatten: true,
|
|
62
|
+
waitForDebuggerOnStart: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
31
65
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} sessionId
|
|
68
|
+
* @return {LH.Gatherer.FRProtocolSession}
|
|
69
|
+
*/
|
|
70
|
+
_findSession(sessionId) {
|
|
71
|
+
for (const {session, cdpSession} of this._targetIdToTargets.values()) {
|
|
72
|
+
if (cdpSession.id() === sessionId) return session;
|
|
73
|
+
}
|
|
36
74
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
75
|
+
throw new Error(`session ${sessionId} not found`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns the root session.
|
|
80
|
+
* @return {LH.Gatherer.FRProtocolSession}
|
|
81
|
+
*/
|
|
82
|
+
rootSession() {
|
|
83
|
+
const rootSessionId = this._rootCdpSession.id();
|
|
84
|
+
return this._findSession(rootSessionId);
|
|
46
85
|
}
|
|
47
86
|
|
|
48
87
|
/**
|
|
49
88
|
* @param {LH.Puppeteer.CDPSession} cdpSession
|
|
50
89
|
*/
|
|
51
90
|
async _onSessionAttached(cdpSession) {
|
|
52
|
-
const
|
|
91
|
+
const newSession = new ProtocolSession(cdpSession);
|
|
53
92
|
|
|
54
93
|
try {
|
|
55
|
-
const target = await
|
|
94
|
+
const target = await newSession.sendCommand('Target.getTargetInfo').catch(() => null);
|
|
56
95
|
const targetType = target?.targetInfo?.type;
|
|
57
96
|
const hasValidTargetType = targetType === 'page' || targetType === 'iframe';
|
|
97
|
+
// TODO: should detach from target in this case?
|
|
98
|
+
// See pptr: https://github.com/puppeteer/puppeteer/blob/733cbecf487c71483bee8350e37030edb24bc021/src/common/Page.ts#L495-L526
|
|
58
99
|
if (!target || !hasValidTargetType) return;
|
|
59
100
|
|
|
101
|
+
// No need to continue if target has already been seen.
|
|
60
102
|
const targetId = target.targetInfo.targetId;
|
|
61
|
-
session.setTargetInfo(target.targetInfo);
|
|
62
103
|
if (this._targetIdToTargets.has(targetId)) return;
|
|
63
104
|
|
|
105
|
+
newSession.setTargetInfo(target.targetInfo);
|
|
64
106
|
const targetName = target.targetInfo.url || target.targetInfo.targetId;
|
|
65
107
|
log.verbose('target-manager', `target ${targetName} attached`);
|
|
66
108
|
|
|
67
|
-
const
|
|
109
|
+
const trueProtocolListener = this._getProtocolEventListener(newSession.id());
|
|
110
|
+
/** @type {(event: unknown) => void} */
|
|
111
|
+
// @ts-expect-error - pptr currently typed only for single arg emits.
|
|
112
|
+
const protocolListener = trueProtocolListener;
|
|
113
|
+
cdpSession.on('*', protocolListener);
|
|
114
|
+
|
|
115
|
+
const targetWithSession = {
|
|
116
|
+
target: target.targetInfo,
|
|
117
|
+
cdpSession,
|
|
118
|
+
session: newSession,
|
|
119
|
+
protocolListener,
|
|
120
|
+
};
|
|
68
121
|
this._targetIdToTargets.set(targetId, targetWithSession);
|
|
69
|
-
for (const listener of this._listeners) await listener(targetWithSession);
|
|
70
122
|
|
|
71
|
-
|
|
123
|
+
// We want to receive information about network requests from iframes, so enable the Network domain.
|
|
124
|
+
await newSession.sendCommand('Network.enable');
|
|
125
|
+
// We also want to receive information about subtargets of subtargets, so make sure we autoattach recursively.
|
|
126
|
+
await newSession.sendCommand('Target.setAutoAttach', {
|
|
72
127
|
autoAttach: true,
|
|
73
128
|
flatten: true,
|
|
74
129
|
waitForDebuggerOnStart: true,
|
|
@@ -80,10 +135,30 @@ class TargetManager {
|
|
|
80
135
|
throw err;
|
|
81
136
|
} finally {
|
|
82
137
|
// Resume the target if it was paused, but if it's unnecessary, we don't care about the error.
|
|
83
|
-
await
|
|
138
|
+
await newSession.sendCommand('Runtime.runIfWaitingForDebugger').catch(() => {});
|
|
84
139
|
}
|
|
85
140
|
}
|
|
86
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Returns a listener for all protocol events from session, and augments the
|
|
144
|
+
* event with the sessionId.
|
|
145
|
+
* @param {string} sessionId
|
|
146
|
+
*/
|
|
147
|
+
_getProtocolEventListener(sessionId) {
|
|
148
|
+
/**
|
|
149
|
+
* @template {keyof LH.Protocol.RawEventMessageRecord} EventName
|
|
150
|
+
* @param {EventName} method
|
|
151
|
+
* @param {LH.Protocol.RawEventMessageRecord[EventName]['params']} params
|
|
152
|
+
*/
|
|
153
|
+
const onProtocolEvent = (method, params) => {
|
|
154
|
+
// Cast because tsc 4.7 still can't quite track the dependent parameters.
|
|
155
|
+
const payload = /** @type {LH.Protocol.RawEventMessage} */ ({method, params, sessionId});
|
|
156
|
+
this.emit('protocolevent', payload);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return onProtocolEvent;
|
|
160
|
+
}
|
|
161
|
+
|
|
87
162
|
/**
|
|
88
163
|
* @return {Promise<void>}
|
|
89
164
|
*/
|
|
@@ -115,23 +190,13 @@ class TargetManager {
|
|
|
115
190
|
// No need to remove listener if connection is already closed.
|
|
116
191
|
this._rootCdpSession.connection()?.off('sessionattached', this._onSessionAttached);
|
|
117
192
|
|
|
193
|
+
for (const {cdpSession, protocolListener} of this._targetIdToTargets.values()) {
|
|
194
|
+
cdpSession.off('*', protocolListener);
|
|
195
|
+
}
|
|
196
|
+
|
|
118
197
|
this._enabled = false;
|
|
119
198
|
this._targetIdToTargets = new Map();
|
|
120
199
|
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* @param {(targetWithSession: TargetWithSession) => Promise<void>|void} listener
|
|
124
|
-
*/
|
|
125
|
-
addTargetAttachedListener(listener) {
|
|
126
|
-
this._listeners.push(listener);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* @param {(targetWithSession: TargetWithSession) => Promise<void>|void} listener
|
|
131
|
-
*/
|
|
132
|
-
removeTargetAttachedListener(listener) {
|
|
133
|
-
this._listeners = this._listeners.filter(entry => entry !== listener);
|
|
134
|
-
}
|
|
135
200
|
}
|
|
136
201
|
|
|
137
202
|
module.exports = TargetManager;
|
|
@@ -96,6 +96,30 @@ class Driver {
|
|
|
96
96
|
this.evaluate = this.executionContext.evaluate.bind(this.executionContext);
|
|
97
97
|
/** @private @deprecated Only available for plugin backcompat. */
|
|
98
98
|
this.evaluateAsync = this.executionContext.evaluateAsync.bind(this.executionContext);
|
|
99
|
+
|
|
100
|
+
// A shim for sufficient coverage of targetManager functionality. Exposes the target
|
|
101
|
+
// management that legacy driver already handles (see this._handleTargetAttached).
|
|
102
|
+
this.targetManager = {
|
|
103
|
+
rootSession: () => {
|
|
104
|
+
return this.defaultSession;
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* Bind to *any* protocol event.
|
|
108
|
+
* @param {'protocolevent'} event
|
|
109
|
+
* @param {(payload: LH.Protocol.RawEventMessage) => void} callback
|
|
110
|
+
*/
|
|
111
|
+
on: (event, callback) => {
|
|
112
|
+
this._connection.on('protocolevent', callback);
|
|
113
|
+
},
|
|
114
|
+
/**
|
|
115
|
+
* Unbind to *any* protocol event.
|
|
116
|
+
* @param {'protocolevent'} event
|
|
117
|
+
* @param {(payload: LH.Protocol.RawEventMessage) => void} callback
|
|
118
|
+
*/
|
|
119
|
+
off: (event, callback) => {
|
|
120
|
+
this._connection.off('protocolevent', callback);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
99
123
|
}
|
|
100
124
|
|
|
101
125
|
/** @deprecated - Not available on Fraggle Rock driver. */
|
|
@@ -187,22 +211,6 @@ class Driver {
|
|
|
187
211
|
this._eventEmitter.removeListener(eventName, cb);
|
|
188
212
|
}
|
|
189
213
|
|
|
190
|
-
/**
|
|
191
|
-
* Bind to *any* protocol event.
|
|
192
|
-
* @param {(payload: LH.Protocol.RawEventMessage) => void} callback
|
|
193
|
-
*/
|
|
194
|
-
addProtocolMessageListener(callback) {
|
|
195
|
-
this._connection.on('protocolevent', callback);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Unbind to *any* protocol event.
|
|
200
|
-
* @param {(payload: LH.Protocol.RawEventMessage) => void} callback
|
|
201
|
-
*/
|
|
202
|
-
removeProtocolMessageListener(callback) {
|
|
203
|
-
this._connection.off('protocolevent', callback);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
214
|
/** @param {LH.Crdp.Target.TargetInfo} targetInfo */
|
|
207
215
|
setTargetInfo(targetInfo) { // eslint-disable-line no-unused-vars
|
|
208
216
|
// OOPIF handling in legacy driver is implicit.
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* This protocol log can be used to recreate the network records using lib/network-recorder.js.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
const NetworkMonitor = require('../driver/network-monitor.js');
|
|
15
14
|
const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
|
|
16
15
|
|
|
17
16
|
class DevtoolsLog extends FRGatherer {
|
|
@@ -26,9 +25,6 @@ class DevtoolsLog extends FRGatherer {
|
|
|
26
25
|
constructor() {
|
|
27
26
|
super();
|
|
28
27
|
|
|
29
|
-
/** @type {NetworkMonitor|undefined} */
|
|
30
|
-
this._networkMonitor = undefined;
|
|
31
|
-
|
|
32
28
|
this._messageLog = new DevtoolsMessageLog(/^(Page|Network|Target|Runtime)\./);
|
|
33
29
|
|
|
34
30
|
/** @param {LH.Protocol.RawEventMessage} e */
|
|
@@ -42,16 +38,16 @@ class DevtoolsLog extends FRGatherer {
|
|
|
42
38
|
this._messageLog.reset();
|
|
43
39
|
this._messageLog.beginRecording();
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this._networkMonitor.enable();
|
|
41
|
+
driver.targetManager.on('protocolevent', this._onProtocolMessage);
|
|
42
|
+
await driver.defaultSession.sendCommand('Page.enable');
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
/**
|
|
46
|
+
* @param {LH.Gatherer.FRTransitionalContext} passContext
|
|
47
|
+
*/
|
|
48
|
+
async stopSensitiveInstrumentation({driver}) {
|
|
52
49
|
this._messageLog.endRecording();
|
|
53
|
-
|
|
54
|
-
this._networkMonitor.off('protocolmessage', this._onProtocolMessage);
|
|
50
|
+
driver.targetManager.off('protocolevent', this._onProtocolMessage);
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
/**
|
|
@@ -89,7 +89,7 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
89
89
|
const height = Math.min(fullHeight, maxTextureSize);
|
|
90
90
|
|
|
91
91
|
// Setup network monitor before we change the viewport.
|
|
92
|
-
const networkMonitor = new NetworkMonitor(
|
|
92
|
+
const networkMonitor = new NetworkMonitor(context.driver.targetManager);
|
|
93
93
|
const waitForNetworkIdleResult = waitForNetworkIdle(session, networkMonitor, {
|
|
94
94
|
pretendDCLAlreadyFired: true,
|
|
95
95
|
networkQuietThresholdMs: 1000,
|