1use crate::error::Result;
8use crate::protocol::Page;
9use crate::server::channel::Channel;
10use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::any::Any;
14use std::collections::HashMap;
15use std::sync::Arc;
16
17#[derive(Clone)]
51pub struct BrowserContext {
52 base: ChannelOwnerImpl,
53}
54
55impl BrowserContext {
56 pub fn new(
72 parent: Arc<dyn ChannelOwner>,
73 type_name: String,
74 guid: Arc<str>,
75 initializer: Value,
76 ) -> Result<Self> {
77 let base = ChannelOwnerImpl::new(
78 ParentOrConnection::Parent(parent),
79 type_name,
80 guid,
81 initializer,
82 );
83
84 let context = Self { base };
85
86 let channel = context.channel().clone();
89 tokio::spawn(async move {
90 let _ = channel
91 .send_no_result(
92 "updateSubscription",
93 serde_json::json!({
94 "event": "dialog",
95 "enabled": true
96 }),
97 )
98 .await;
99 });
100
101 Ok(context)
102 }
103
104 fn channel(&self) -> &Channel {
108 self.base.channel()
109 }
110
111 pub async fn add_init_script(&self, script: &str) -> Result<()> {
131 self.channel()
132 .send_no_result("addInitScript", serde_json::json!({ "source": script }))
133 .await
134 }
135
136 pub async fn new_page(&self) -> Result<Page> {
149 #[derive(Deserialize)]
151 struct NewPageResponse {
152 page: GuidRef,
153 }
154
155 #[derive(Deserialize)]
156 struct GuidRef {
157 #[serde(deserialize_with = "crate::server::connection::deserialize_arc_str")]
158 guid: Arc<str>,
159 }
160
161 let response: NewPageResponse = self
163 .channel()
164 .send("newPage", serde_json::json!({}))
165 .await?;
166
167 let page_arc = self.connection().get_object(&response.page.guid).await?;
169
170 let page = page_arc.as_any().downcast_ref::<Page>().ok_or_else(|| {
172 crate::error::Error::ProtocolError(format!(
173 "Expected Page object, got {}",
174 page_arc.type_name()
175 ))
176 })?;
177
178 Ok(page.clone())
179 }
180
181 pub async fn close(&self) -> Result<()> {
194 self.channel()
196 .send_no_result("close", serde_json::json!({}))
197 .await
198 }
199}
200
201impl ChannelOwner for BrowserContext {
202 fn guid(&self) -> &str {
203 self.base.guid()
204 }
205
206 fn type_name(&self) -> &str {
207 self.base.type_name()
208 }
209
210 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
211 self.base.parent()
212 }
213
214 fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
215 self.base.connection()
216 }
217
218 fn initializer(&self) -> &Value {
219 self.base.initializer()
220 }
221
222 fn channel(&self) -> &Channel {
223 self.base.channel()
224 }
225
226 fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
227 self.base.dispose(reason)
228 }
229
230 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
231 self.base.adopt(child)
232 }
233
234 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
235 self.base.add_child(guid, child)
236 }
237
238 fn remove_child(&self, guid: &str) {
239 self.base.remove_child(guid)
240 }
241
242 fn on_event(&self, method: &str, params: Value) {
243 match method {
244 "dialog" => {
245 if let Some(dialog_guid) = params
249 .get("dialog")
250 .and_then(|v| v.get("guid"))
251 .and_then(|v| v.as_str())
252 {
253 let connection = self.connection();
254 let dialog_guid_owned = dialog_guid.to_string();
255
256 tokio::spawn(async move {
257 let dialog_arc = match connection.get_object(&dialog_guid_owned).await {
259 Ok(obj) => obj,
260 Err(_) => return,
261 };
262
263 let dialog = match dialog_arc
265 .as_any()
266 .downcast_ref::<crate::protocol::Dialog>()
267 {
268 Some(d) => d.clone(),
269 None => return,
270 };
271
272 let page_arc = match dialog_arc.parent() {
274 Some(parent) => parent,
275 None => return,
276 };
277
278 let page = match page_arc.as_any().downcast_ref::<Page>() {
280 Some(p) => p.clone(),
281 None => return,
282 };
283
284 page.trigger_dialog_event(dialog).await;
286 });
287 }
288 }
289 _ => {
290 }
292 }
293 }
294
295 fn was_collected(&self) -> bool {
296 self.base.was_collected()
297 }
298
299 fn as_any(&self) -> &dyn Any {
300 self
301 }
302}
303
304impl std::fmt::Debug for BrowserContext {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 f.debug_struct("BrowserContext")
307 .field("guid", &self.guid())
308 .finish()
309 }
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct Viewport {
317 pub width: u32,
319 pub height: u32,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct Geolocation {
328 pub latitude: f64,
330 pub longitude: f64,
332 #[serde(skip_serializing_if = "Option::is_none")]
334 pub accuracy: Option<f64>,
335}
336
337#[derive(Debug, Clone, Default, Serialize)]
344#[serde(rename_all = "camelCase")]
345pub struct BrowserContextOptions {
346 #[serde(skip_serializing_if = "Option::is_none")]
349 pub viewport: Option<Viewport>,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub no_viewport: Option<bool>,
354
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub user_agent: Option<String>,
358
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub locale: Option<String>,
362
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub timezone_id: Option<String>,
366
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub geolocation: Option<Geolocation>,
370
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub permissions: Option<Vec<String>>,
374
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub color_scheme: Option<String>,
378
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub has_touch: Option<bool>,
382
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub is_mobile: Option<bool>,
386
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub javascript_enabled: Option<bool>,
390
391 #[serde(skip_serializing_if = "Option::is_none")]
393 pub offline: Option<bool>,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub accept_downloads: Option<bool>,
398
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub bypass_csp: Option<bool>,
402
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub ignore_https_errors: Option<bool>,
406
407 #[serde(skip_serializing_if = "Option::is_none")]
409 pub device_scale_factor: Option<f64>,
410
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub extra_http_headers: Option<HashMap<String, String>>,
414
415 #[serde(skip_serializing_if = "Option::is_none")]
417 pub base_url: Option<String>,
418}
419
420impl BrowserContextOptions {
421 pub fn builder() -> BrowserContextOptionsBuilder {
423 BrowserContextOptionsBuilder::default()
424 }
425}
426
427#[derive(Debug, Clone, Default)]
429pub struct BrowserContextOptionsBuilder {
430 viewport: Option<Viewport>,
431 no_viewport: Option<bool>,
432 user_agent: Option<String>,
433 locale: Option<String>,
434 timezone_id: Option<String>,
435 geolocation: Option<Geolocation>,
436 permissions: Option<Vec<String>>,
437 color_scheme: Option<String>,
438 has_touch: Option<bool>,
439 is_mobile: Option<bool>,
440 javascript_enabled: Option<bool>,
441 offline: Option<bool>,
442 accept_downloads: Option<bool>,
443 bypass_csp: Option<bool>,
444 ignore_https_errors: Option<bool>,
445 device_scale_factor: Option<f64>,
446 extra_http_headers: Option<HashMap<String, String>>,
447 base_url: Option<String>,
448}
449
450impl BrowserContextOptionsBuilder {
451 pub fn viewport(mut self, viewport: Viewport) -> Self {
453 self.viewport = Some(viewport);
454 self.no_viewport = None; self
456 }
457
458 pub fn no_viewport(mut self, no_viewport: bool) -> Self {
460 self.no_viewport = Some(no_viewport);
461 if no_viewport {
462 self.viewport = None; }
464 self
465 }
466
467 pub fn user_agent(mut self, user_agent: String) -> Self {
469 self.user_agent = Some(user_agent);
470 self
471 }
472
473 pub fn locale(mut self, locale: String) -> Self {
475 self.locale = Some(locale);
476 self
477 }
478
479 pub fn timezone_id(mut self, timezone_id: String) -> Self {
481 self.timezone_id = Some(timezone_id);
482 self
483 }
484
485 pub fn geolocation(mut self, geolocation: Geolocation) -> Self {
487 self.geolocation = Some(geolocation);
488 self
489 }
490
491 pub fn permissions(mut self, permissions: Vec<String>) -> Self {
493 self.permissions = Some(permissions);
494 self
495 }
496
497 pub fn color_scheme(mut self, color_scheme: String) -> Self {
499 self.color_scheme = Some(color_scheme);
500 self
501 }
502
503 pub fn has_touch(mut self, has_touch: bool) -> Self {
505 self.has_touch = Some(has_touch);
506 self
507 }
508
509 pub fn is_mobile(mut self, is_mobile: bool) -> Self {
511 self.is_mobile = Some(is_mobile);
512 self
513 }
514
515 pub fn javascript_enabled(mut self, javascript_enabled: bool) -> Self {
517 self.javascript_enabled = Some(javascript_enabled);
518 self
519 }
520
521 pub fn offline(mut self, offline: bool) -> Self {
523 self.offline = Some(offline);
524 self
525 }
526
527 pub fn accept_downloads(mut self, accept_downloads: bool) -> Self {
529 self.accept_downloads = Some(accept_downloads);
530 self
531 }
532
533 pub fn bypass_csp(mut self, bypass_csp: bool) -> Self {
535 self.bypass_csp = Some(bypass_csp);
536 self
537 }
538
539 pub fn ignore_https_errors(mut self, ignore_https_errors: bool) -> Self {
541 self.ignore_https_errors = Some(ignore_https_errors);
542 self
543 }
544
545 pub fn device_scale_factor(mut self, device_scale_factor: f64) -> Self {
547 self.device_scale_factor = Some(device_scale_factor);
548 self
549 }
550
551 pub fn extra_http_headers(mut self, extra_http_headers: HashMap<String, String>) -> Self {
553 self.extra_http_headers = Some(extra_http_headers);
554 self
555 }
556
557 pub fn base_url(mut self, base_url: String) -> Self {
559 self.base_url = Some(base_url);
560 self
561 }
562
563 pub fn build(self) -> BrowserContextOptions {
565 BrowserContextOptions {
566 viewport: self.viewport,
567 no_viewport: self.no_viewport,
568 user_agent: self.user_agent,
569 locale: self.locale,
570 timezone_id: self.timezone_id,
571 geolocation: self.geolocation,
572 permissions: self.permissions,
573 color_scheme: self.color_scheme,
574 has_touch: self.has_touch,
575 is_mobile: self.is_mobile,
576 javascript_enabled: self.javascript_enabled,
577 offline: self.offline,
578 accept_downloads: self.accept_downloads,
579 bypass_csp: self.bypass_csp,
580 ignore_https_errors: self.ignore_https_errors,
581 device_scale_factor: self.device_scale_factor,
582 extra_http_headers: self.extra_http_headers,
583 base_url: self.base_url,
584 }
585 }
586}