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, UserAgentMetadataBuilder,
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 #[deprecated]
182 pub full_version: Option<String>,
183 pub platform: Option<String>,
184 pub platform_version: Option<String>,
185 pub architecture: Option<String>,
186 pub model: Option<String>,
187 pub mobile: Option<bool>,
188 pub bitness: Option<String>,
189 pub wow64: Option<bool>,
190 pub form_factors: Vec<String>,
191}
192
193impl UserAgentMetadataOverride {
194 pub fn with_brand(mut self, brand: UserAgentBrand) -> Self {
195 self.brands.push(brand);
196 self
197 }
198
199 pub fn with_full_version_entry(mut self, brand: UserAgentBrand) -> Self {
200 self.full_version_list.push(brand);
201 self
202 }
203
204 #[deprecated]
205 #[allow(deprecated)]
206 pub fn with_full_version<T: Into<String>>(mut self, version: T) -> Self {
207 self.full_version = Some(version.into());
208 self
209 }
210
211 pub fn with_platform<T: Into<String>>(mut self, platform: T) -> Self {
212 self.platform = Some(platform.into());
213 self
214 }
215
216 pub fn with_platform_version<T: Into<String>>(mut self, version: T) -> Self {
217 self.platform_version = Some(version.into());
218 self
219 }
220
221 pub fn with_architecture<T: Into<String>>(mut self, arch: T) -> Self {
222 self.architecture = Some(arch.into());
223 self
224 }
225
226 pub fn with_model<T: Into<String>>(mut self, model: T) -> Self {
227 self.model = Some(model.into());
228 self
229 }
230
231 pub fn with_mobile(mut self, mobile: bool) -> Self {
232 self.mobile = Some(mobile);
233 self
234 }
235
236 pub fn with_bitness<T: Into<String>>(mut self, bitness: T) -> Self {
237 self.bitness = Some(bitness.into());
238 self
239 }
240
241 pub fn with_wow64(mut self, wow64: bool) -> Self {
242 self.wow64 = Some(wow64);
243 self
244 }
245
246 pub fn with_form_factor<T: Into<String>>(mut self, factor: T) -> Self {
247 self.form_factors.push(factor.into());
248 self
249 }
250
251 fn to_cdp(&self) -> UserAgentMetadata {
252 let mut builder = UserAgentMetadataBuilder::default();
253
254 if !self.brands.is_empty() {
255 builder.brands(
256 self.brands
257 .iter()
258 .map(|brand| brand.to_cdp())
259 .collect::<Vec<_>>(),
260 );
261 }
262
263 if !self.full_version_list.is_empty() {
264 builder.full_version_list(
265 self.full_version_list
266 .iter()
267 .map(|brand| brand.to_cdp())
268 .collect::<Vec<_>>(),
269 );
270 }
271
272 #[allow(deprecated)]
273 if let Some(version) = &self.full_version {
274 builder.full_version(version.clone());
275 }
276
277 builder.platform(self.platform.clone().unwrap_or_default());
278 builder.platform_version(self.platform_version.clone().unwrap_or_default());
279 builder.architecture(self.architecture.clone().unwrap_or_default());
280 builder.model(self.model.clone().unwrap_or_default());
281 builder.mobile(self.mobile.unwrap_or(false));
282
283 if let Some(bitness) = &self.bitness {
284 builder.bitness(bitness.clone());
285 }
286
287 if let Some(wow64) = self.wow64 {
288 builder.wow_64(wow64);
289 }
290
291 if !self.form_factors.is_empty() {
292 builder.form_factors(self.form_factors.clone());
293 }
294
295 builder.build().expect("Failed to build UserAgentMetadata")
296 }
297}
298
299#[derive(Clone, Debug)]
301pub struct UserAgentBrand {
302 pub brand: String,
303 pub version: String,
304}
305
306impl UserAgentBrand {
307 pub fn new<B: Into<String>, V: Into<String>>(brand: B, version: V) -> Self {
308 Self {
309 brand: brand.into(),
310 version: version.into(),
311 }
312 }
313
314 fn to_cdp(&self) -> UserAgentBrandVersion {
315 UserAgentBrandVersion {
316 brand: self.brand.clone(),
317 version: self.version.clone(),
318 }
319 }
320}
321
322pub struct EmulationController {
324 page: Arc<Page>,
325}
326
327impl EmulationController {
328 pub(crate) fn new(page: Arc<Page>) -> Self {
329 Self { page }
330 }
331
332 pub async fn apply_config(&self, config: &EmulationConfig) -> Result<()> {
346 if let Some(geolocation) = &config.geolocation {
347 self.set_geolocation(geolocation.clone()).await?;
348 }
349 if let Some(timezone) = &config.timezone_id {
350 self.set_timezone(timezone).await?;
351 }
352 if let Some(locale) = &config.locale {
353 self.set_locale(Some(locale.as_str())).await?;
354 }
355 if let Some(media) = &config.media {
356 self.set_media(media.clone()).await?;
357 }
358 if let Some(user_agent) = &config.user_agent {
359 self.set_user_agent(user_agent.clone()).await?;
360 }
361 Ok(())
362 }
363
364 pub async fn set_geolocation(&self, geolocation: Geolocation) -> Result<()> {
366 let method = SetGeolocationOverride {
367 latitude: Some(geolocation.latitude),
368 longitude: Some(geolocation.longitude),
369 accuracy: geolocation.accuracy,
370 altitude: geolocation.altitude,
371 altitude_accuracy: geolocation.altitude_accuracy,
372 heading: geolocation.heading,
373 speed: geolocation.speed,
374 };
375 let _: SetGeolocationOverrideReturnObject =
376 self.page.session.send_command(method, None).await?;
377 Ok(())
378 }
379
380 pub async fn clear_geolocation(&self) -> Result<()> {
382 let method = ClearGeolocationOverride(None);
383 let _: ClearGeolocationOverrideReturnObject =
384 self.page.session.send_command(method, None).await?;
385 Ok(())
386 }
387
388 pub async fn set_timezone<T: Into<String>>(&self, timezone_id: T) -> Result<()> {
390 let method = SetTimezoneOverride {
391 timezone_id: timezone_id.into(),
392 };
393 let _: SetTimezoneOverrideReturnObject =
394 self.page.session.send_command(method, None).await?;
395 Ok(())
396 }
397
398 pub async fn reset_timezone(&self) -> Result<()> {
400 self.set_timezone("").await
401 }
402
403 pub async fn set_locale(&self, locale: Option<&str>) -> Result<()> {
405 let method = SetLocaleOverride {
406 locale: locale.map(|value| value.to_string()),
407 };
408 let _: SetLocaleOverrideReturnObject = self.page.session.send_command(method, None).await?;
409 Ok(())
410 }
411
412 pub async fn set_media(&self, media: MediaEmulation) -> Result<()> {
414 let method = SetEmulatedMedia {
415 media: media.media_type.clone(),
416 features: to_cdp_media_features(&media.features),
417 };
418 let _: SetEmulatedMediaReturnObject = self.page.session.send_command(method, None).await?;
419 Ok(())
420 }
421
422 pub async fn clear_media(&self) -> Result<()> {
424 let method = SetEmulatedMedia {
425 media: None,
426 features: None,
427 };
428 let _: SetEmulatedMediaReturnObject = self.page.session.send_command(method, None).await?;
429 Ok(())
430 }
431
432 pub async fn set_user_agent(&self, override_data: UserAgentOverride) -> Result<()> {
434 let method = SetUserAgentOverride {
435 user_agent: override_data.user_agent,
436 accept_language: override_data.accept_language,
437 platform: override_data.platform,
438 user_agent_metadata: override_data
439 .metadata
440 .as_ref()
441 .map(UserAgentMetadataOverride::to_cdp),
442 };
443 let _: SetUserAgentOverrideReturnObject =
444 self.page.session.send_command(method, None).await?;
445 Ok(())
446 }
447}
448
449fn to_cdp_media_features(features: &[MediaFeatureOverride]) -> Option<Vec<MediaFeature>> {
450 if features.is_empty() {
451 None
452 } else {
453 Some(
454 features
455 .iter()
456 .map(|feature| MediaFeature {
457 name: feature.name.clone(),
458 value: feature.value.clone(),
459 })
460 .collect(),
461 )
462 }
463}