1use crate::error::Result;
2use crate::page::Page;
3use cdp_protocol::emulation::{
4 ClearGeolocationOverride, ClearGeolocationOverrideReturnObject, MediaFeature, SetEmulatedMedia,
5 SetEmulatedMediaReturnObject, SetGeolocationOverride, SetGeolocationOverrideReturnObject,
6 SetLocaleOverride, SetLocaleOverrideReturnObject, SetTimezoneOverride,
7 SetTimezoneOverrideReturnObject, SetUserAgentOverride, SetUserAgentOverrideReturnObject,
8 UserAgentBrandVersion, UserAgentMetadata,
9};
10use std::sync::Arc;
11
12#[derive(Clone, Debug, Default)]
14pub struct EmulationConfig {
15 pub geolocation: Option<Geolocation>,
16 pub timezone_id: Option<String>,
17 pub locale: Option<String>,
18 pub media: Option<MediaEmulation>,
19 pub user_agent: Option<UserAgentOverride>,
20}
21
22impl EmulationConfig {
23 pub fn with_geolocation(mut self, geolocation: Geolocation) -> Self {
24 self.geolocation = Some(geolocation);
25 self
26 }
27
28 pub fn with_timezone<T: Into<String>>(mut self, timezone: T) -> Self {
29 self.timezone_id = Some(timezone.into());
30 self
31 }
32
33 pub fn with_locale<T: Into<String>>(mut self, locale: T) -> Self {
34 self.locale = Some(locale.into());
35 self
36 }
37
38 pub fn with_media(mut self, media: MediaEmulation) -> Self {
39 self.media = Some(media);
40 self
41 }
42
43 pub fn with_user_agent(mut self, override_data: UserAgentOverride) -> Self {
44 self.user_agent = Some(override_data);
45 self
46 }
47}
48
49#[derive(Clone, Debug)]
51pub struct Geolocation {
52 pub latitude: f64,
53 pub longitude: f64,
54 pub accuracy: Option<f64>,
55 pub altitude: Option<f64>,
56 pub altitude_accuracy: Option<f64>,
57 pub heading: Option<f64>,
58 pub speed: Option<f64>,
59}
60
61impl Geolocation {
62 pub fn new(latitude: f64, longitude: f64) -> Self {
63 Self {
64 latitude,
65 longitude,
66 accuracy: None,
67 altitude: None,
68 altitude_accuracy: None,
69 heading: None,
70 speed: None,
71 }
72 }
73
74 pub fn with_accuracy(mut self, accuracy: f64) -> Self {
75 self.accuracy = Some(accuracy);
76 self
77 }
78
79 pub fn with_altitude(mut self, altitude: f64) -> Self {
80 self.altitude = Some(altitude);
81 self
82 }
83
84 pub fn with_altitude_accuracy(mut self, altitude_accuracy: f64) -> Self {
85 self.altitude_accuracy = Some(altitude_accuracy);
86 self
87 }
88
89 pub fn with_heading(mut self, heading: f64) -> Self {
90 self.heading = Some(heading);
91 self
92 }
93
94 pub fn with_speed(mut self, speed: f64) -> Self {
95 self.speed = Some(speed);
96 self
97 }
98}
99
100impl Default for Geolocation {
101 fn default() -> Self {
102 Self::new(0.0, 0.0)
103 }
104}
105
106#[derive(Clone, Debug, Default)]
108pub struct MediaEmulation {
109 pub media_type: Option<String>,
110 pub features: Vec<MediaFeatureOverride>,
111}
112
113impl MediaEmulation {
114 pub fn with_media_type<T: Into<String>>(mut self, media: T) -> Self {
115 self.media_type = Some(media.into());
116 self
117 }
118
119 pub fn with_feature(mut self, feature: MediaFeatureOverride) -> Self {
120 self.features.push(feature);
121 self
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct MediaFeatureOverride {
128 pub name: String,
129 pub value: String,
130}
131
132impl MediaFeatureOverride {
133 pub fn new<N: Into<String>, V: Into<String>>(name: N, value: V) -> Self {
134 Self {
135 name: name.into(),
136 value: value.into(),
137 }
138 }
139}
140
141#[derive(Clone, Debug)]
143pub struct UserAgentOverride {
144 pub user_agent: String,
145 pub accept_language: Option<String>,
146 pub platform: Option<String>,
147 pub metadata: Option<UserAgentMetadataOverride>,
148}
149
150impl UserAgentOverride {
151 pub fn new<T: Into<String>>(user_agent: T) -> Self {
152 Self {
153 user_agent: user_agent.into(),
154 accept_language: None,
155 platform: None,
156 metadata: None,
157 }
158 }
159
160 pub fn with_accept_language<T: Into<String>>(mut self, value: T) -> Self {
161 self.accept_language = Some(value.into());
162 self
163 }
164
165 pub fn with_platform<T: Into<String>>(mut self, value: T) -> Self {
166 self.platform = Some(value.into());
167 self
168 }
169
170 pub fn with_metadata(mut self, metadata: UserAgentMetadataOverride) -> Self {
171 self.metadata = Some(metadata);
172 self
173 }
174}
175
176#[derive(Clone, Debug, Default)]
178pub struct UserAgentMetadataOverride {
179 pub brands: Vec<UserAgentBrand>,
180 pub full_version_list: Vec<UserAgentBrand>,
181 pub full_version: Option<String>,
182 pub platform: Option<String>,
183 pub platform_version: Option<String>,
184 pub architecture: Option<String>,
185 pub model: Option<String>,
186 pub mobile: Option<bool>,
187 pub bitness: Option<String>,
188 pub wow64: Option<bool>,
189 pub form_factors: Vec<String>,
190}
191
192impl UserAgentMetadataOverride {
193 pub fn with_brand(mut self, brand: UserAgentBrand) -> Self {
194 self.brands.push(brand);
195 self
196 }
197
198 pub fn with_full_version_entry(mut self, brand: UserAgentBrand) -> Self {
199 self.full_version_list.push(brand);
200 self
201 }
202
203 pub fn with_full_version<T: Into<String>>(mut self, version: T) -> Self {
204 self.full_version = Some(version.into());
205 self
206 }
207
208 pub fn with_platform<T: Into<String>>(mut self, platform: T) -> Self {
209 self.platform = Some(platform.into());
210 self
211 }
212
213 pub fn with_platform_version<T: Into<String>>(mut self, version: T) -> Self {
214 self.platform_version = Some(version.into());
215 self
216 }
217
218 pub fn with_architecture<T: Into<String>>(mut self, arch: T) -> Self {
219 self.architecture = Some(arch.into());
220 self
221 }
222
223 pub fn with_model<T: Into<String>>(mut self, model: T) -> Self {
224 self.model = Some(model.into());
225 self
226 }
227
228 pub fn with_mobile(mut self, mobile: bool) -> Self {
229 self.mobile = Some(mobile);
230 self
231 }
232
233 pub fn with_bitness<T: Into<String>>(mut self, bitness: T) -> Self {
234 self.bitness = Some(bitness.into());
235 self
236 }
237
238 pub fn with_wow64(mut self, wow64: bool) -> Self {
239 self.wow64 = Some(wow64);
240 self
241 }
242
243 pub fn with_form_factor<T: Into<String>>(mut self, factor: T) -> Self {
244 self.form_factors.push(factor.into());
245 self
246 }
247
248 fn to_cdp(&self) -> UserAgentMetadata {
249 UserAgentMetadata {
250 brands: if self.brands.is_empty() {
251 None
252 } else {
253 Some(self.brands.iter().map(|brand| brand.to_cdp()).collect())
254 },
255 full_version_list: if self.full_version_list.is_empty() {
256 None
257 } else {
258 Some(
259 self.full_version_list
260 .iter()
261 .map(|brand| brand.to_cdp())
262 .collect(),
263 )
264 },
265 full_version: self.full_version.clone(),
266 platform: self.platform.clone().unwrap_or_default(),
267 platform_version: self.platform_version.clone().unwrap_or_default(),
268 architecture: self.architecture.clone().unwrap_or_default(),
269 model: self.model.clone().unwrap_or_default(),
270 mobile: self.mobile.unwrap_or(false),
271 bitness: self.bitness.clone(),
272 wow_64: self.wow64,
273 form_factors: if self.form_factors.is_empty() {
274 None
275 } else {
276 Some(self.form_factors.clone())
277 },
278 }
279 }
280}
281
282#[derive(Clone, Debug)]
284pub struct UserAgentBrand {
285 pub brand: String,
286 pub version: String,
287}
288
289impl UserAgentBrand {
290 pub fn new<B: Into<String>, V: Into<String>>(brand: B, version: V) -> Self {
291 Self {
292 brand: brand.into(),
293 version: version.into(),
294 }
295 }
296
297 fn to_cdp(&self) -> UserAgentBrandVersion {
298 UserAgentBrandVersion {
299 brand: self.brand.clone(),
300 version: self.version.clone(),
301 }
302 }
303}
304
305pub struct EmulationController {
307 page: Arc<Page>,
308}
309
310impl EmulationController {
311 pub(crate) fn new(page: Arc<Page>) -> Self {
312 Self { page }
313 }
314
315 pub async fn apply_config(&self, config: &EmulationConfig) -> Result<()> {
329 if let Some(geolocation) = &config.geolocation {
330 self.set_geolocation(geolocation.clone()).await?;
331 }
332 if let Some(timezone) = &config.timezone_id {
333 self.set_timezone(timezone).await?;
334 }
335 if let Some(locale) = &config.locale {
336 self.set_locale(Some(locale.as_str())).await?;
337 }
338 if let Some(media) = &config.media {
339 self.set_media(media.clone()).await?;
340 }
341 if let Some(user_agent) = &config.user_agent {
342 self.set_user_agent(user_agent.clone()).await?;
343 }
344 Ok(())
345 }
346
347 pub async fn set_geolocation(&self, geolocation: Geolocation) -> Result<()> {
349 let method = SetGeolocationOverride {
350 latitude: Some(geolocation.latitude),
351 longitude: Some(geolocation.longitude),
352 accuracy: geolocation.accuracy,
353 altitude: geolocation.altitude,
354 altitude_accuracy: geolocation.altitude_accuracy,
355 heading: geolocation.heading,
356 speed: geolocation.speed,
357 };
358 let _: SetGeolocationOverrideReturnObject =
359 self.page.session.send_command(method, None).await?;
360 Ok(())
361 }
362
363 pub async fn clear_geolocation(&self) -> Result<()> {
365 let method = ClearGeolocationOverride(None);
366 let _: ClearGeolocationOverrideReturnObject =
367 self.page.session.send_command(method, None).await?;
368 Ok(())
369 }
370
371 pub async fn set_timezone<T: Into<String>>(&self, timezone_id: T) -> Result<()> {
373 let method = SetTimezoneOverride {
374 timezone_id: timezone_id.into(),
375 };
376 let _: SetTimezoneOverrideReturnObject =
377 self.page.session.send_command(method, None).await?;
378 Ok(())
379 }
380
381 pub async fn reset_timezone(&self) -> Result<()> {
383 self.set_timezone("").await
384 }
385
386 pub async fn set_locale(&self, locale: Option<&str>) -> Result<()> {
388 let method = SetLocaleOverride {
389 locale: locale.map(|value| value.to_string()),
390 };
391 let _: SetLocaleOverrideReturnObject = self.page.session.send_command(method, None).await?;
392 Ok(())
393 }
394
395 pub async fn set_media(&self, media: MediaEmulation) -> Result<()> {
397 let method = SetEmulatedMedia {
398 media: media.media_type.clone(),
399 features: to_cdp_media_features(&media.features),
400 };
401 let _: SetEmulatedMediaReturnObject = self.page.session.send_command(method, None).await?;
402 Ok(())
403 }
404
405 pub async fn clear_media(&self) -> Result<()> {
407 let method = SetEmulatedMedia {
408 media: None,
409 features: None,
410 };
411 let _: SetEmulatedMediaReturnObject = self.page.session.send_command(method, None).await?;
412 Ok(())
413 }
414
415 pub async fn set_user_agent(&self, override_data: UserAgentOverride) -> Result<()> {
417 let method = SetUserAgentOverride {
418 user_agent: override_data.user_agent,
419 accept_language: override_data.accept_language,
420 platform: override_data.platform,
421 user_agent_metadata: override_data
422 .metadata
423 .as_ref()
424 .map(UserAgentMetadataOverride::to_cdp),
425 };
426 let _: SetUserAgentOverrideReturnObject =
427 self.page.session.send_command(method, None).await?;
428 Ok(())
429 }
430}
431
432fn to_cdp_media_features(features: &[MediaFeatureOverride]) -> Option<Vec<MediaFeature>> {
433 if features.is_empty() {
434 None
435 } else {
436 Some(
437 features
438 .iter()
439 .map(|feature| MediaFeature {
440 name: feature.name.clone(),
441 value: feature.value.clone(),
442 })
443 .collect(),
444 )
445 }
446}