-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
emulation.js
160 lines (145 loc) · 4.85 KB
/
emulation.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
/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
const NO_THROTTLING_METRICS = {
latency: 0,
downloadThroughput: 0,
uploadThroughput: 0,
offline: false,
};
const NO_CPU_THROTTLE_METRICS = {
rate: 1,
};
/**
* @param {string} userAgent
* @param {LH.Config.Settings['formFactor']} formFactor
* @return {LH.Crdp.Emulation.SetUserAgentOverrideRequest['userAgentMetadata']}
*/
function parseUseragentIntoMetadata(userAgent, formFactor) {
const match = userAgent.match(/Chrome\/([\d.]+)/); // eg 'Chrome/(71.0.3577.0)'
const fullVersion = match?.[1] || '99.0.1234.0';
const [version] = fullVersion.split('.', 1);
const brands = [
{brand: 'Chromium', version},
{brand: 'Google Chrome', version},
];
const motoGPowerDetails = {
platform: 'Android',
platformVersion: '11.0',
architecture: '',
model: 'moto g power (2022)',
};
const macDesktopDetails = {
platform: 'macOS',
platformVersion: '10.15.7',
architecture: 'x86',
model: '',
};
const mobile = formFactor === 'mobile';
return {
brands,
fullVersion,
// Since config users can supply a custom useragent, they likely are emulating something
// other than Moto G Power and MacOS Desktop.
// TODO: Determine how to thoughtfully expose this metadata/client-hints configurability.
...(mobile ? motoGPowerDetails : macDesktopDetails),
mobile,
};
}
/**
* @param {LH.Gatherer.ProtocolSession} session
* @param {LH.Config.Settings} settings
* @return {Promise<void>}
*/
async function emulate(session, settings) {
if (settings.emulatedUserAgent !== false) {
const userAgent = /** @type {string} */ (settings.emulatedUserAgent);
await session.sendCommand('Network.setUserAgentOverride', {
userAgent,
userAgentMetadata: parseUseragentIntoMetadata(userAgent, settings.formFactor),
});
}
// See devtools-entry for one usecase for disabling screenEmulation
if (settings.screenEmulation.disabled !== true) {
const {width, height, deviceScaleFactor, mobile} = settings.screenEmulation;
const params = {width, height, deviceScaleFactor, mobile};
await session.sendCommand('Emulation.setDeviceMetricsOverride', params);
await session.sendCommand('Emulation.setTouchEmulationEnabled', {
enabled: params.mobile,
});
}
}
/**
* Sets the throttling options specified in config settings, clearing existing network throttling if
* throttlingMethod is not `devtools` (but not CPU throttling, suspected requirement of WPT-compat).
*
* @param {LH.Gatherer.ProtocolSession} session
* @param {LH.Config.Settings} settings
* @return {Promise<void>}
*/
async function throttle(session, settings) {
if (settings.throttlingMethod !== 'devtools') return clearNetworkThrottling(session);
await Promise.all([
enableNetworkThrottling(session, settings.throttling),
enableCPUThrottling(session, settings.throttling),
]);
}
/**
* @param {LH.Gatherer.ProtocolSession} session
* @return {Promise<void>}
*/
async function clearThrottling(session) {
await Promise.all([clearNetworkThrottling(session), clearCPUThrottling(session)]);
}
/**
* @param {LH.Gatherer.ProtocolSession} session
* @param {Required<LH.ThrottlingSettings>} throttlingSettings
* @return {Promise<void>}
*/
function enableNetworkThrottling(session, throttlingSettings) {
/** @type {LH.Crdp.Network.EmulateNetworkConditionsRequest} */
const conditions = {
offline: false,
latency: throttlingSettings.requestLatencyMs || 0,
downloadThroughput: throttlingSettings.downloadThroughputKbps || 0,
uploadThroughput: throttlingSettings.uploadThroughputKbps || 0,
};
// DevTools expects throughput in bytes per second rather than kbps
conditions.downloadThroughput = Math.floor(conditions.downloadThroughput * 1024 / 8);
conditions.uploadThroughput = Math.floor(conditions.uploadThroughput * 1024 / 8);
return session.sendCommand('Network.emulateNetworkConditions', conditions);
}
/**
* @param {LH.Gatherer.ProtocolSession} session
* @return {Promise<void>}
*/
function clearNetworkThrottling(session) {
return session.sendCommandAndIgnore('Network.emulateNetworkConditions', NO_THROTTLING_METRICS);
}
/**
* @param {LH.Gatherer.ProtocolSession} session
* @param {Required<LH.ThrottlingSettings>} throttlingSettings
* @return {Promise<void>}
*/
function enableCPUThrottling(session, throttlingSettings) {
const rate = throttlingSettings.cpuSlowdownMultiplier;
return session.sendCommand('Emulation.setCPUThrottlingRate', {rate});
}
/**
* @param {LH.Gatherer.ProtocolSession} session
* @return {Promise<void>}
*/
function clearCPUThrottling(session) {
return session.sendCommandAndIgnore('Emulation.setCPUThrottlingRate', NO_CPU_THROTTLE_METRICS);
}
export {
emulate,
throttle,
clearThrottling,
enableNetworkThrottling,
clearNetworkThrottling,
enableCPUThrottling,
clearCPUThrottling,
};