chromiumoxide/
page.rs

1use std::path::Path;
2use std::sync::Arc;
3
4use chromiumoxide_cdp::cdp::browser_protocol::accessibility::{
5    GetFullAxTreeReturns, GetPartialAxTreeReturns,
6};
7use chromiumoxide_cdp::cdp::browser_protocol::emulation::{
8    MediaFeature, SetDeviceMetricsOverrideParams, SetEmulatedMediaParams,
9    SetGeolocationOverrideParams, SetHardwareConcurrencyOverrideParams, SetLocaleOverrideParams,
10    SetTimezoneOverrideParams, UserAgentBrandVersion, UserAgentMetadata,
11};
12use chromiumoxide_cdp::cdp::browser_protocol::input::{DispatchDragEventType, DragData};
13use chromiumoxide_cdp::cdp::browser_protocol::network::{
14    Cookie, CookieParam, DeleteCookiesParams, GetCookiesParams, SetBlockedUrLsParams,
15    SetCookiesParams, SetExtraHttpHeadersParams, SetUserAgentOverrideParams, TimeSinceEpoch,
16};
17use chromiumoxide_cdp::cdp::browser_protocol::page::*;
18use chromiumoxide_cdp::cdp::browser_protocol::performance::{GetMetricsParams, Metric};
19use chromiumoxide_cdp::cdp::browser_protocol::target::{SessionId, TargetId};
20use chromiumoxide_cdp::cdp::browser_protocol::{dom::*, emulation};
21use chromiumoxide_cdp::cdp::js_protocol;
22use chromiumoxide_cdp::cdp::js_protocol::debugger::GetScriptSourceParams;
23use chromiumoxide_cdp::cdp::js_protocol::runtime::{
24    AddBindingParams, CallArgument, CallFunctionOnParams, EvaluateParams, ExecutionContextId,
25    RemoteObjectType, ScriptId,
26};
27use chromiumoxide_cdp::cdp::{browser_protocol, IntoEventKind};
28use chromiumoxide_types::*;
29use futures::channel::mpsc::unbounded;
30use futures::channel::oneshot::channel as oneshot_channel;
31use futures::{stream, SinkExt, StreamExt};
32use spider_fingerprint::configs::{AgentOs, Tier};
33
34use crate::auth::Credentials;
35use crate::element::Element;
36use crate::error::{CdpError, Result};
37use crate::handler::commandfuture::CommandFuture;
38use crate::handler::domworld::DOMWorldKind;
39use crate::handler::httpfuture::HttpFuture;
40use crate::handler::target::{GetName, GetParent, GetUrl, TargetMessage};
41use crate::handler::PageInner;
42use crate::javascript::extract::{generate_marker_js, FULL_XML_SERIALIZER_JS, OUTER_HTML};
43use crate::js::{Evaluation, EvaluationResult};
44use crate::layout::{Delta, Point, ScrollBehavior};
45use crate::listeners::{EventListenerRequest, EventStream};
46use crate::{utils, ArcHttpRequest};
47use aho_corasick::AhoCorasick;
48
49lazy_static::lazy_static! {
50    /// Determine the platform used.
51    static ref PLATFORM_MATCHER: AhoCorasick = {
52         AhoCorasick::builder()
53        .match_kind(aho_corasick::MatchKind::LeftmostFirst)
54        .ascii_case_insensitive(true)
55        .build([
56            "ipad",        // 0
57            "ipod",        // 1
58            "iphone",      // 2
59            "android",     // 3
60            "macintosh",   // 4
61            "mac os x",    // 5
62            "windows",     // 6
63            "linux",       // 7
64        ])
65        .expect("valid pattern")
66    };
67}
68
69/// Determine the platform used from a user-agent.
70pub fn platform_from_user_agent(user_agent: &str) -> &'static str {
71    match PLATFORM_MATCHER.find(user_agent) {
72        Some(mat) => match mat.pattern().as_usize() {
73            0 => "iPad",
74            1 => "iPod",
75            2 => "iPhone",
76            3 => "Linux armv8l",
77            4 | 5 => "MacIntel",
78            6 => "Win32",
79            7 => "Linux x86_64",
80            _ => "",
81        },
82        None => "",
83    }
84}
85
86#[derive(Debug, Clone)]
87pub struct Page {
88    inner: Arc<PageInner>,
89}
90
91impl Page {
92    /// Add a custom script to eval on new document immediately.
93    pub async fn add_script_to_evaluate_immediately_on_new_document(
94        &self,
95        source: Option<String>,
96    ) -> Result<&Self> {
97        if source.is_some() {
98            let source = source.unwrap_or_default();
99
100            if !source.is_empty() {
101                self.execute(AddScriptToEvaluateOnNewDocumentParams {
102                    source,
103                    world_name: None,
104                    include_command_line_api: None,
105                    run_immediately: Some(true),
106                })
107                .await?;
108            }
109        }
110        Ok(self)
111    }
112
113    /// Add a custom script to eval on new document.
114    pub async fn add_script_to_evaluate_on_new_document(
115        &self,
116        source: Option<String>,
117    ) -> Result<&Self> {
118        if source.is_some() {
119            let source = source.unwrap_or_default();
120
121            if !source.is_empty() {
122                self.execute(AddScriptToEvaluateOnNewDocumentParams {
123                    source,
124                    world_name: None,
125                    include_command_line_api: None,
126                    run_immediately: None,
127                })
128                .await?;
129            }
130        }
131        Ok(self)
132    }
133
134    /// Removes the `navigator.webdriver` property
135    /// changes permissions, pluggins rendering contexts and the `window.chrome`
136    /// property to make it harder to detect the scraper as a bot.
137    pub async fn _enable_real_emulation(
138        &self,
139        user_agent: &str,
140        config: &spider_fingerprint::EmulationConfiguration,
141        viewport: &Option<&spider_fingerprint::spoof_viewport::Viewport>,
142        custom_script: Option<&str>,
143    ) -> Result<&Self> {
144        let emulation_script = spider_fingerprint::emulate(
145            &user_agent,
146            &config,
147            &viewport,
148            &custom_script.as_ref().map(|s| Box::new(s.to_string())),
149        )
150        .unwrap_or_default();
151
152        let source = if let Some(cs) = custom_script {
153            format!(
154                "{};{};",
155                emulation_script,
156                spider_fingerprint::wrap_eval_script(&cs)
157            )
158        } else {
159            emulation_script
160        };
161
162        self.add_script_to_evaluate_on_new_document(Some(source))
163            .await?;
164
165        Ok(self)
166    }
167
168    /// Removes the `navigator.webdriver` property
169    /// changes permissions, pluggins rendering contexts and the `window.chrome`
170    /// property to make it harder to detect the scraper as a bot
171    pub async fn _enable_stealth_mode(
172        &self,
173        custom_script: Option<&str>,
174        os: Option<AgentOs>,
175        tier: Option<Tier>,
176    ) -> Result<&Self> {
177        let os = os.unwrap_or_default();
178        let tier = match tier {
179            Some(tier) => tier,
180            _ => Tier::Basic,
181        };
182
183        let source = if let Some(cs) = custom_script {
184            format!(
185                "{};{};",
186                spider_fingerprint::build_stealth_script(tier, os),
187                spider_fingerprint::wrap_eval_script(&cs)
188            )
189        } else {
190            spider_fingerprint::build_stealth_script(tier, os)
191        };
192
193        self.add_script_to_evaluate_on_new_document(Some(source))
194            .await?;
195
196        Ok(self)
197    }
198
199    /// Changes your user_agent, removes the `navigator.webdriver` property
200    /// changes permissions, pluggins rendering contexts and the `window.chrome`
201    /// property to make it harder to detect the scraper as a bot
202    pub async fn enable_stealth_mode(&self) -> Result<&Self> {
203        let _ = self._enable_stealth_mode(None, None, None).await;
204
205        Ok(self)
206    }
207
208    /// Changes your user_agent, removes the `navigator.webdriver` property
209    /// changes permissions, pluggins rendering contexts and the `window.chrome`
210    /// property to make it harder to detect the scraper as a bot
211    pub async fn enable_stealth_mode_os(
212        &self,
213        os: Option<AgentOs>,
214        tier: Option<Tier>,
215    ) -> Result<&Self> {
216        let _ = self._enable_stealth_mode(None, os, tier).await;
217
218        Ok(self)
219    }
220
221    /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
222    /// changes permissions, pluggins rendering contexts and the `window.chrome`
223    /// property to make it harder to detect the scraper as a bot
224    pub async fn enable_stealth_mode_with_agent(&self, ua: &str) -> Result<&Self> {
225        let _ = tokio::join!(
226            self._enable_stealth_mode(None, None, None),
227            self.set_user_agent(ua)
228        );
229        Ok(self)
230    }
231
232    /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
233    /// changes permissions, pluggins rendering contexts and the `window.chrome`
234    /// property to make it harder to detect the scraper as a bot. Also add dialog polyfill to prevent blocking the page.
235    pub async fn enable_stealth_mode_with_dimiss_dialogs(&self, ua: &str) -> Result<&Self> {
236        let _ = tokio::join!(
237            self._enable_stealth_mode(
238                Some(spider_fingerprint::spoofs::DISABLE_DIALOGS),
239                None,
240                None
241            ),
242            self.set_user_agent(ua)
243        );
244        Ok(self)
245    }
246
247    /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
248    /// changes permissions, pluggins rendering contexts and the `window.chrome`
249    /// property to make it harder to detect the scraper as a bot. Also add dialog polyfill to prevent blocking the page.
250    pub async fn enable_stealth_mode_with_agent_and_dimiss_dialogs(
251        &self,
252        ua: &str,
253    ) -> Result<&Self> {
254        let _ = tokio::join!(
255            self._enable_stealth_mode(
256                Some(spider_fingerprint::spoofs::DISABLE_DIALOGS),
257                None,
258                None
259            ),
260            self.set_user_agent(ua)
261        );
262        Ok(self)
263    }
264
265    /// Enable page Content Security Policy by-passing.
266    pub async fn set_bypass_csp(&self, enabled: bool) -> Result<&Self> {
267        self.inner.set_bypass_csp(enabled).await?;
268        Ok(self)
269    }
270
271    /// Sets `window.chrome` on frame creation and console.log methods.
272    pub async fn hide_chrome(&self) -> Result<&Self, CdpError> {
273        self.execute(AddScriptToEvaluateOnNewDocumentParams {
274            source: spider_fingerprint::spoofs::HIDE_CHROME.to_string(),
275            world_name: None,
276            include_command_line_api: None,
277            run_immediately: None,
278        })
279        .await?;
280        Ok(self)
281    }
282
283    /// Obfuscates WebGL vendor on frame creation
284    pub async fn hide_webgl_vendor(&self) -> Result<&Self, CdpError> {
285        self.execute(AddScriptToEvaluateOnNewDocumentParams {
286            source: spider_fingerprint::spoofs::HIDE_WEBGL.to_string(),
287            world_name: None,
288            include_command_line_api: None,
289            run_immediately: None,
290        })
291        .await?;
292        Ok(self)
293    }
294
295    /// Obfuscates browser plugins and hides the navigator object on frame creation
296    pub async fn hide_plugins(&self) -> Result<&Self, CdpError> {
297        self.execute(AddScriptToEvaluateOnNewDocumentParams {
298            source: spider_fingerprint::generate_hide_plugins(),
299            world_name: None,
300            include_command_line_api: None,
301            run_immediately: None,
302        })
303        .await?;
304
305        Ok(self)
306    }
307
308    /// Obfuscates browser permissions on frame creation
309    pub async fn hide_permissions(&self) -> Result<&Self, CdpError> {
310        self.execute(AddScriptToEvaluateOnNewDocumentParams {
311            source: spider_fingerprint::spoofs::HIDE_PERMISSIONS.to_string(),
312            world_name: None,
313            include_command_line_api: None,
314            run_immediately: None,
315        })
316        .await?;
317        Ok(self)
318    }
319
320    /// Removes the `navigator.webdriver` property on frame creation
321    pub async fn hide_webdriver(&self) -> Result<&Self, CdpError> {
322        self.execute(AddScriptToEvaluateOnNewDocumentParams {
323            source: spider_fingerprint::spoofs::HIDE_WEBDRIVER.to_string(),
324            world_name: None,
325            include_command_line_api: None,
326            run_immediately: None,
327        })
328        .await?;
329        Ok(self)
330    }
331
332    /// Execute a command and return the `Command::Response`
333    pub async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
334        self.command_future(cmd)?.await
335    }
336
337    /// Execute a command and return the `Command::Response`
338    pub async fn send_command<T: Command>(&self, cmd: T) -> Result<&Self> {
339        let _ = self.inner.send_command(cmd).await;
340        Ok(self)
341    }
342
343    /// Execute a command and return the `Command::Response`
344    pub fn command_future<T: Command>(&self, cmd: T) -> Result<CommandFuture<T>> {
345        self.inner.command_future(cmd)
346    }
347
348    /// Execute a command and return the `Command::Response`
349    pub fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
350        self.inner.http_future(cmd)
351    }
352
353    /// Adds an event listener to the `Target` and returns the receiver part as
354    /// `EventStream`
355    ///
356    /// An `EventStream` receives every `Event` the `Target` receives.
357    /// All event listener get notified with the same event, so registering
358    /// multiple listeners for the same event is possible.
359    ///
360    /// Custom events rely on being deserializable from the received json params
361    /// in the `EventMessage`. Custom Events are caught by the `CdpEvent::Other`
362    /// variant. If there are mulitple custom event listener is registered
363    /// for the same event, identified by the `MethodType::method_id` function,
364    /// the `Target` tries to deserialize the json using the type of the event
365    /// listener. Upon success the `Target` then notifies all listeners with the
366    /// deserialized event. This means, while it is possible to register
367    /// different types for the same custom event, only the type of first
368    /// registered event listener will be used. The subsequent listeners, that
369    /// registered for the same event but with another type won't be able to
370    /// receive anything and therefor will come up empty until all their
371    /// preceding event listeners are dropped and they become the first (or
372    /// longest) registered event listener for an event.
373    ///
374    /// # Example Listen for canceled animations
375    /// ```no_run
376    /// # use chromiumoxide::page::Page;
377    /// # use chromiumoxide::error::Result;
378    /// # use chromiumoxide_cdp::cdp::browser_protocol::animation::EventAnimationCanceled;
379    /// # use futures::StreamExt;
380    /// # async fn demo(page: Page) -> Result<()> {
381    ///     let mut events = page.event_listener::<EventAnimationCanceled>().await?;
382    ///     while let Some(event) = events.next().await {
383    ///         //..
384    ///     }
385    ///     # Ok(())
386    /// # }
387    /// ```
388    ///
389    /// # Example Liste for a custom event
390    ///
391    /// ```no_run
392    /// # use chromiumoxide::page::Page;
393    /// # use chromiumoxide::error::Result;
394    /// # use futures::StreamExt;
395    /// # use serde::Deserialize;
396    /// # use chromiumoxide::types::{MethodId, MethodType};
397    /// # use chromiumoxide::cdp::CustomEvent;
398    /// # async fn demo(page: Page) -> Result<()> {
399    ///     #[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
400    ///     struct MyCustomEvent {
401    ///         name: String,
402    ///     }
403    ///    impl MethodType for MyCustomEvent {
404    ///        fn method_id() -> MethodId {
405    ///            "Custom.Event".into()
406    ///        }
407    ///    }
408    ///    impl CustomEvent for MyCustomEvent {}
409    ///    let mut events = page.event_listener::<MyCustomEvent>().await?;
410    ///    while let Some(event) = events.next().await {
411    ///        //..
412    ///    }
413    ///
414    ///     # Ok(())
415    /// # }
416    /// ```
417    pub async fn event_listener<T: IntoEventKind>(&self) -> Result<EventStream<T>> {
418        let (tx, rx) = unbounded();
419
420        self.inner
421            .sender()
422            .clone()
423            .send(TargetMessage::AddEventListener(
424                EventListenerRequest::new::<T>(tx),
425            ))
426            .await?;
427
428        Ok(EventStream::new(rx))
429    }
430
431    pub async fn expose_function(
432        &self,
433        name: impl Into<String>,
434        function: impl AsRef<str>,
435    ) -> Result<()> {
436        let name = name.into();
437        let expression = utils::evaluation_string(function, &["exposedFun", name.as_str()]);
438
439        self.execute(AddBindingParams::new(name)).await?;
440        self.execute(AddScriptToEvaluateOnNewDocumentParams::new(
441            expression.clone(),
442        ))
443        .await?;
444
445        // TODO add execution context tracking for frames
446        //let frames = self.frames().await?;
447
448        Ok(())
449    }
450
451    /// This resolves once the navigation finished and the page is loaded.
452    ///
453    /// This is necessary after an interaction with the page that may trigger a
454    /// navigation (`click`, `press_key`) in order to wait until the new browser
455    /// page is loaded
456    pub async fn wait_for_navigation_response(&self) -> Result<ArcHttpRequest> {
457        self.inner.wait_for_navigation().await
458    }
459
460    /// Same as `wait_for_navigation_response` but returns `Self` instead
461    pub async fn wait_for_navigation(&self) -> Result<&Self> {
462        self.inner.wait_for_navigation().await?;
463        Ok(self)
464    }
465
466    /// Navigate directly to the given URL checking the HTTP cache first.
467    ///
468    /// This resolves directly after the requested URL is fully loaded. Does nothing without the 'cache' feature on.
469    #[cfg(feature = "cache")]
470    pub async fn goto_with_cache(
471        &self,
472        params: impl Into<NavigateParams>,
473        auth_opt: Option<&str>,
474    ) -> Result<&Self> {
475        use crate::cache::{get_cached_url, rewrite_base_tag};
476        let navigate_params: NavigateParams = params.into();
477        let mut force_navigate = true;
478
479        // todo: pull in the headers from auth.
480        if let Some(source) = get_cached_url(&navigate_params.url, auth_opt).await {
481            let html = rewrite_base_tag(&source, &Some(&navigate_params.url)).await;
482            if let Ok(frame_id) = self.mainframe().await {
483                if let Err(e) = self
484                    .execute(browser_protocol::page::SetDocumentContentParams {
485                        frame_id: frame_id.unwrap_or_default(),
486                        html,
487                    })
488                    .await
489                {
490                    tracing::error!("Set Content Error({:?}) - {:?}", e, &navigate_params.url);
491                    force_navigate = false;
492                    if let crate::page::CdpError::Timeout = e {
493                        force_navigate = true;
494                    }
495                } else {
496                    tracing::info!("Found cached url - ({:?})", &navigate_params.url);
497                    force_navigate = false;
498                }
499            }
500        }
501
502        if force_navigate {
503            let res = self.execute(navigate_params).await?;
504
505            if let Some(err) = res.result.error_text {
506                return Err(CdpError::ChromeMessage(err));
507            }
508        }
509
510        Ok(self)
511    }
512
513    /// Navigate directly to the given URL.
514    ///
515    /// This resolves directly after the requested URL is fully loaded. Does nothing without the 'cache' feature on.
516    #[cfg(not(feature = "cache"))]
517    pub async fn goto_with_cache(
518        &self,
519        params: impl Into<NavigateParams>,
520        _auth_opt: Option<&str>,
521    ) -> Result<&Self> {
522        let res = self.execute(params.into()).await?;
523
524        if let Some(err) = res.result.error_text {
525            return Err(CdpError::ChromeMessage(err));
526        }
527
528        Ok(self)
529    }
530
531    /// Navigate directly to the given URL.
532    ///
533    /// This resolves directly after the requested URL is fully loaded.
534    pub async fn goto(&self, params: impl Into<NavigateParams>) -> Result<&Self> {
535        let res = self.execute(params.into()).await?;
536
537        if let Some(err) = res.result.error_text {
538            return Err(CdpError::ChromeMessage(err));
539        }
540
541        Ok(self)
542    }
543
544    /// The identifier of the `Target` this page belongs to
545    pub fn target_id(&self) -> &TargetId {
546        self.inner.target_id()
547    }
548
549    /// The identifier of the `Session` target of this page is attached to
550    pub fn session_id(&self) -> &SessionId {
551        self.inner.session_id()
552    }
553
554    /// The identifier of the `Session` target of this page is attached to
555    pub fn opener_id(&self) -> &Option<TargetId> {
556        self.inner.opener_id()
557    }
558
559    /// Returns the name of the frame
560    pub async fn frame_name(&self, frame_id: FrameId) -> Result<Option<String>> {
561        let (tx, rx) = oneshot_channel();
562        self.inner
563            .sender()
564            .clone()
565            .send(TargetMessage::Name(GetName {
566                frame_id: Some(frame_id),
567                tx,
568            }))
569            .await?;
570        Ok(rx.await?)
571    }
572
573    pub async fn authenticate(&self, credentials: Credentials) -> Result<()> {
574        self.inner
575            .sender()
576            .clone()
577            .send(TargetMessage::Authenticate(credentials))
578            .await?;
579
580        Ok(())
581    }
582
583    /// Returns the current url of the page
584    pub async fn url(&self) -> Result<Option<String>> {
585        let (tx, rx) = oneshot_channel();
586        self.inner
587            .sender()
588            .clone()
589            .send(TargetMessage::Url(GetUrl::new(tx)))
590            .await?;
591        Ok(rx.await?)
592    }
593
594    /// Returns the current url of the frame
595    pub async fn frame_url(&self, frame_id: FrameId) -> Result<Option<String>> {
596        let (tx, rx) = oneshot_channel();
597        self.inner
598            .sender()
599            .clone()
600            .send(TargetMessage::Url(GetUrl {
601                frame_id: Some(frame_id),
602                tx,
603            }))
604            .await?;
605        Ok(rx.await?)
606    }
607
608    /// Returns the parent id of the frame
609    pub async fn frame_parent(&self, frame_id: FrameId) -> Result<Option<FrameId>> {
610        let (tx, rx) = oneshot_channel();
611        self.inner
612            .sender()
613            .clone()
614            .send(TargetMessage::Parent(GetParent { frame_id, tx }))
615            .await?;
616        Ok(rx.await?)
617    }
618
619    /// Return the main frame of the page
620    pub async fn mainframe(&self) -> Result<Option<FrameId>> {
621        let (tx, rx) = oneshot_channel();
622        self.inner
623            .sender()
624            .clone()
625            .send(TargetMessage::MainFrame(tx))
626            .await?;
627        Ok(rx.await?)
628    }
629
630    /// Return the frames of the page
631    pub async fn frames(&self) -> Result<Vec<FrameId>> {
632        let (tx, rx) = oneshot_channel();
633        self.inner
634            .sender()
635            .clone()
636            .send(TargetMessage::AllFrames(tx))
637            .await?;
638        Ok(rx.await?)
639    }
640
641    /// Allows overriding user agent with the given string.
642    pub async fn set_extra_headers(
643        &self,
644        params: impl Into<SetExtraHttpHeadersParams>,
645    ) -> Result<&Self> {
646        self.execute(params.into()).await?;
647        Ok(self)
648    }
649
650    /// Generate the user-agent metadata params
651    pub fn generate_user_agent_metadata(
652        default_params: &SetUserAgentOverrideParams,
653    ) -> Option<UserAgentMetadata> {
654        let ua_data = spider_fingerprint::spoof_user_agent::build_high_entropy_data(&Some(
655            &default_params.user_agent,
656        ));
657        let windows = ua_data.platform == "Windows";
658
659        let brands = ua_data
660            .full_version_list
661            .iter()
662            .map(|b| {
663                let b = b.clone();
664                UserAgentBrandVersion::new(b.brand, b.version)
665            })
666            .collect::<Vec<_>>();
667
668        let full_versions = ua_data
669            .full_version_list
670            .into_iter()
671            .map(|b| UserAgentBrandVersion::new(b.brand, b.version))
672            .collect::<Vec<_>>();
673
674        let user_agent_metadata_builder = emulation::UserAgentMetadata::builder()
675            .architecture(ua_data.architecture)
676            .bitness(ua_data.bitness)
677            .model(ua_data.model)
678            .platform_version(ua_data.platform_version)
679            .brands(brands)
680            .full_version_lists(full_versions)
681            .platform(ua_data.platform)
682            .mobile(ua_data.mobile);
683
684        let user_agent_metadata_builder = if windows {
685            user_agent_metadata_builder.wow64(ua_data.wow64_ness)
686        } else {
687            user_agent_metadata_builder
688        };
689
690        if let Ok(user_agent_metadata) = user_agent_metadata_builder.build() {
691            Some(user_agent_metadata)
692        } else {
693            None
694        }
695    }
696
697    /// Allows overriding the user-agent for the [network](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setUserAgentOverride) and [emulation](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setUserAgentOverride ) with the given string.
698    async fn set_user_agent_base(
699        &self,
700        params: impl Into<SetUserAgentOverrideParams>,
701        metadata: bool,
702        emulate: bool,
703        accept_language: Option<String>,
704    ) -> Result<&Self> {
705        let mut default_params: SetUserAgentOverrideParams = params.into();
706
707        if default_params.platform.is_none() {
708            let platform = platform_from_user_agent(&default_params.user_agent);
709            if !platform.is_empty() {
710                default_params.platform = Some(platform.into());
711            }
712        }
713
714        default_params.accept_language = accept_language;
715
716        if default_params.user_agent_metadata.is_none() && metadata {
717            let user_agent_metadata = Self::generate_user_agent_metadata(&default_params);
718            if let Some(user_agent_metadata) = user_agent_metadata {
719                default_params.user_agent_metadata = Some(user_agent_metadata);
720            }
721        }
722
723        if emulate {
724            let default_params1 = default_params.clone();
725
726            let mut set_emulation_agent_override =
727                chromiumoxide_cdp::cdp::browser_protocol::emulation::SetUserAgentOverrideParams::new(
728                    default_params1.user_agent,
729                );
730
731            set_emulation_agent_override.accept_language = default_params1.accept_language;
732            set_emulation_agent_override.platform = default_params1.platform;
733            set_emulation_agent_override.user_agent_metadata = default_params1.user_agent_metadata;
734
735            tokio::try_join!(
736                self.execute(default_params),
737                self.execute(set_emulation_agent_override)
738            )?;
739        } else {
740            self.execute(default_params).await?;
741        }
742
743        Ok(self)
744    }
745
746    /// Allows overriding the user-agent for the [network](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setUserAgentOverride) with the given string.
747    pub async fn set_user_agent(
748        &self,
749        params: impl Into<SetUserAgentOverrideParams>,
750    ) -> Result<&Self> {
751        self.set_user_agent_base(params, true, true, None).await
752    }
753
754    /// Allows overriding the user-agent for the [network](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setUserAgentOverride), [emulation](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setUserAgentOverride ), and userAgentMetadata with the given string.
755    pub async fn set_user_agent_advanced(
756        &self,
757        params: impl Into<SetUserAgentOverrideParams>,
758        metadata: bool,
759        emulate: bool,
760        accept_language: Option<String>,
761    ) -> Result<&Self> {
762        self.set_user_agent_base(params, metadata, emulate, accept_language)
763            .await
764    }
765
766    /// Returns the user agent of the browser
767    pub async fn user_agent(&self) -> Result<String> {
768        Ok(self.inner.version().await?.user_agent)
769    }
770
771    /// Returns the root DOM node (and optionally the subtree) of the page.
772    ///
773    /// # Note: This does not return the actual HTML document of the page. To
774    /// retrieve the HTML content of the page see `Page::content`.
775    pub async fn get_document(&self) -> Result<Node> {
776        let mut cmd = GetDocumentParams::default();
777        cmd.depth = Some(-1);
778        cmd.pierce = Some(true);
779
780        let resp = self.execute(cmd).await?;
781
782        Ok(resp.result.root)
783    }
784
785    /// Returns the first element in the document which matches the given CSS
786    /// selector.
787    ///
788    /// Execute a query selector on the document's node.
789    pub async fn find_element(&self, selector: impl Into<String>) -> Result<Element> {
790        let root = self.get_document().await?.node_id;
791        let node_id = self.inner.find_element(selector, root).await?;
792        Element::new(Arc::clone(&self.inner), node_id).await
793    }
794
795    /// Returns the outer HTML of the page.
796    pub async fn outer_html(&self) -> Result<String> {
797        let root = self.get_document().await?;
798        let element = Element::new(Arc::clone(&self.inner), root.node_id).await?;
799        self.inner
800            .outer_html(
801                element.remote_object_id,
802                element.node_id,
803                element.backend_node_id,
804            )
805            .await
806    }
807
808    /// Return all `Element`s in the document that match the given selector
809    pub async fn find_elements(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
810        let root = self.get_document().await?.node_id;
811        let node_ids = self.inner.find_elements(selector, root).await?;
812        Element::from_nodes(&self.inner, &node_ids).await
813    }
814
815    /// Returns the first element in the document which matches the given xpath
816    /// selector.
817    ///
818    /// Execute a xpath selector on the document's node.
819    pub async fn find_xpath(&self, selector: impl Into<String>) -> Result<Element> {
820        self.get_document().await?;
821        let node_id = self.inner.find_xpaths(selector).await?[0];
822        Element::new(Arc::clone(&self.inner), node_id).await
823    }
824
825    /// Return all `Element`s in the document that match the given xpath selector
826    pub async fn find_xpaths(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
827        self.get_document().await?;
828        let node_ids = self.inner.find_xpaths(selector).await?;
829        Element::from_nodes(&self.inner, &node_ids).await
830    }
831
832    /// Describes node given its id
833    pub async fn describe_node(&self, node_id: NodeId) -> Result<Node> {
834        let resp = self
835            .execute(DescribeNodeParams::builder().node_id(node_id).build())
836            .await?;
837        Ok(resp.result.node)
838    }
839
840    /// Tries to close page, running its beforeunload hooks, if any.
841    /// Calls Page.close with [`CloseParams`]
842    pub async fn close(self) -> Result<()> {
843        self.execute(CloseParams::default()).await?;
844        Ok(())
845    }
846
847    /// Performs a single mouse click event at the point's location.
848    ///
849    /// This scrolls the point into view first, then executes a
850    /// `DispatchMouseEventParams` command of type `MouseLeft` with
851    /// `MousePressed` as single click and then releases the mouse with an
852    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
853    /// `MouseReleased`
854    ///
855    /// Bear in mind that if `click()` triggers a navigation the new page is not
856    /// immediately loaded when `click()` resolves. To wait until navigation is
857    /// finished an additional `wait_for_navigation()` is required:
858    ///
859    /// # Example
860    ///
861    /// Trigger a navigation and wait until the triggered navigation is finished
862    ///
863    /// ```no_run
864    /// # use chromiumoxide::page::Page;
865    /// # use chromiumoxide::error::Result;
866    /// # use chromiumoxide::layout::Point;
867    /// # async fn demo(page: Page, point: Point) -> Result<()> {
868    ///     let html = page.click(point).await?.wait_for_navigation().await?.content();
869    ///     # Ok(())
870    /// # }
871    /// ```
872    ///
873    /// # Example
874    ///
875    /// Perform custom click
876    ///
877    /// ```no_run
878    /// # use chromiumoxide::page::Page;
879    /// # use chromiumoxide::error::Result;
880    /// # use chromiumoxide::layout::Point;
881    /// # use chromiumoxide_cdp::cdp::browser_protocol::input::{DispatchMouseEventParams, MouseButton, DispatchMouseEventType};
882    /// # async fn demo(page: Page, point: Point) -> Result<()> {
883    ///      // double click
884    ///      let cmd = DispatchMouseEventParams::builder()
885    ///             .x(point.x)
886    ///             .y(point.y)
887    ///             .button(MouseButton::Left)
888    ///             .click_count(2);
889    ///
890    ///         page.move_mouse(point).await?.execute(
891    ///             cmd.clone()
892    ///                 .r#type(DispatchMouseEventType::MousePressed)
893    ///                 .build()
894    ///                 .unwrap(),
895    ///         )
896    ///         .await?;
897    ///
898    ///         page.execute(
899    ///             cmd.r#type(DispatchMouseEventType::MouseReleased)
900    ///                 .build()
901    ///                 .unwrap(),
902    ///         )
903    ///         .await?;
904    ///
905    ///     # Ok(())
906    /// # }
907    /// ```
908    pub async fn click(&self, point: Point) -> Result<&Self> {
909        self.inner.click(point).await?;
910        Ok(self)
911    }
912
913    /// Performs a single mouse click event at the point's location and generate a marker.
914    pub(crate) async fn click_with_highlight_base(
915        &self,
916        point: Point,
917        color: Rgba,
918    ) -> Result<&Self> {
919        use chromiumoxide_cdp::cdp::browser_protocol::overlay::HighlightRectParams;
920        let x = point.x.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
921        let y = point.y.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
922
923        let highlight_params = HighlightRectParams {
924            x,
925            y,
926            width: 15,
927            height: 15,
928            color: Some(color),
929            outline_color: Some(Rgba::new(255, 255, 255)),
930        };
931
932        let _ = tokio::join!(self.click(point), self.execute(highlight_params));
933        Ok(self)
934    }
935
936    /// Performs a single mouse click event at the point's location and generate a highlight to the nearest element.
937    /// Make sure page.enable_overlay is called first.
938    pub async fn click_with_highlight(&self, point: Point) -> Result<&Self> {
939        let mut color = Rgba::new(255, 0, 0);
940        color.a = Some(1.0);
941        self.click_with_highlight_base(point, color).await?;
942        Ok(self)
943    }
944
945    /// Performs a single mouse click event at the point's location and generate a highlight to the nearest element with the color.
946    /// Make sure page.enable_overlay is called first.
947    pub async fn click_with_highlight_color(&self, point: Point, color: Rgba) -> Result<&Self> {
948        self.click_with_highlight_base(point, color).await?;
949        Ok(self)
950    }
951
952    /// Performs a single mouse click event at the point's location and generate a marker with pure JS. Useful for debugging.
953    pub async fn click_with_marker(&self, point: Point) -> Result<&Self> {
954        let _ = tokio::join!(
955            self.click(point),
956            self.evaluate(generate_marker_js(point.x, point.y))
957        );
958
959        Ok(self)
960    }
961
962    /// Performs a double mouse click event at the point's location.
963    ///
964    /// This scrolls the point into view first, then executes a
965    /// `DispatchMouseEventParams` command of type `MouseLeft` with
966    /// `MousePressed` as single click and then releases the mouse with an
967    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
968    /// `MouseReleased`
969    ///
970    /// Bear in mind that if `click()` triggers a navigation the new page is not
971    /// immediately loaded when `click()` resolves. To wait until navigation is
972    /// finished an additional `wait_for_navigation()` is required:
973    ///
974    /// # Example
975    ///
976    /// Trigger a navigation and wait until the triggered navigation is finished
977    ///
978    /// ```no_run
979    /// # use chromiumoxide::page::Page;
980    /// # use chromiumoxide::error::Result;
981    /// # use chromiumoxide::layout::Point;
982    /// # async fn demo(page: Page, point: Point) -> Result<()> {
983    ///     let html = page.click(point).await?.wait_for_navigation().await?.content();
984    ///     # Ok(())
985    /// # }
986    /// ```
987    /// ```
988    pub async fn double_click(&self, point: Point) -> Result<&Self> {
989        self.inner.double_click(point).await?;
990        Ok(self)
991    }
992
993    /// Performs a right mouse click event at the point's location.
994    ///
995    /// This scrolls the point into view first, then executes a
996    /// `DispatchMouseEventParams` command of type `MouseLeft` with
997    /// `MousePressed` as single click and then releases the mouse with an
998    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
999    /// `MouseReleased`
1000    ///
1001    /// Bear in mind that if `click()` triggers a navigation the new page is not
1002    /// immediately loaded when `click()` resolves. To wait until navigation is
1003    /// finished an additional `wait_for_navigation()` is required:
1004    ///
1005    /// # Example
1006    ///
1007    /// Trigger a navigation and wait until the triggered navigation is finished
1008    ///
1009    /// ```no_run
1010    /// # use chromiumoxide::page::Page;
1011    /// # use chromiumoxide::error::Result;
1012    /// # use chromiumoxide::layout::Point;
1013    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1014    ///     let html = page.right_click(point).await?.wait_for_navigation().await?.content();
1015    ///     # Ok(())
1016    /// # }
1017    /// ```
1018    /// ```
1019    pub async fn right_click(&self, point: Point) -> Result<&Self> {
1020        self.inner.right_click(point).await?;
1021        Ok(self)
1022    }
1023
1024    /// Performs a middle mouse click event at the point's location.
1025    ///
1026    /// This scrolls the point into view first, then executes a
1027    /// `DispatchMouseEventParams` command of type `MouseLeft` with
1028    /// `MousePressed` as single click and then releases the mouse with an
1029    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1030    /// `MouseReleased`
1031    ///
1032    /// Bear in mind that if `click()` triggers a navigation the new page is not
1033    /// immediately loaded when `click()` resolves. To wait until navigation is
1034    /// finished an additional `wait_for_navigation()` is required:
1035    ///
1036    /// # Example
1037    ///
1038    /// Trigger a navigation and wait until the triggered navigation is finished
1039    ///
1040    /// ```no_run
1041    /// # use chromiumoxide::page::Page;
1042    /// # use chromiumoxide::error::Result;
1043    /// # use chromiumoxide::layout::Point;
1044    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1045    ///     let html = page.middle_click(point).await?.wait_for_navigation().await?.content();
1046    ///     # Ok(())
1047    /// # }
1048    /// ```
1049    /// ```
1050    pub async fn middle_click(&self, point: Point) -> Result<&Self> {
1051        self.inner.middle_click(point).await?;
1052        Ok(self)
1053    }
1054
1055    /// Performs a back mouse click event at the point's location.
1056    ///
1057    /// This scrolls the point into view first, then executes a
1058    /// `DispatchMouseEventParams` command of type `MouseBack` with
1059    /// `MousePressed` as single click and then releases the mouse with an
1060    /// additional `DispatchMouseEventParams` of type `MouseBack` with
1061    /// `MouseReleased`
1062    ///
1063    /// Bear in mind that if `click()` triggers a navigation the new page is not
1064    /// immediately loaded when `click()` resolves. To wait until navigation is
1065    /// finished an additional `wait_for_navigation()` is required:
1066    ///
1067    /// # Example
1068    ///
1069    /// Trigger a navigation and wait until the triggered navigation is finished
1070    ///
1071    /// ```no_run
1072    /// # use chromiumoxide::page::Page;
1073    /// # use chromiumoxide::error::Result;
1074    /// # use chromiumoxide::layout::Point;
1075    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1076    ///     let html = page.back_click(point).await?.wait_for_navigation().await?.content();
1077    ///     # Ok(())
1078    /// # }
1079    /// ```
1080    /// ```
1081    pub async fn back_click(&self, point: Point) -> Result<&Self> {
1082        self.inner.back_click(point).await?;
1083        Ok(self)
1084    }
1085
1086    /// Performs a forward mouse click event at the point's location.
1087    ///
1088    /// This scrolls the point into view first, then executes a
1089    /// `DispatchMouseEventParams` command of type `MouseForward` with
1090    /// `MousePressed` as single click and then releases the mouse with an
1091    /// additional `DispatchMouseEventParams` of type `MouseForward` with
1092    /// `MouseReleased`
1093    ///
1094    /// Bear in mind that if `click()` triggers a navigation the new page is not
1095    /// immediately loaded when `click()` resolves. To wait until navigation is
1096    /// finished an additional `wait_for_navigation()` is required:
1097    ///
1098    /// # Example
1099    ///
1100    /// Trigger a navigation and wait until the triggered navigation is finished
1101    ///
1102    /// ```no_run
1103    /// # use chromiumoxide::page::Page;
1104    /// # use chromiumoxide::error::Result;
1105    /// # use chromiumoxide::layout::Point;
1106    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1107    ///     let html = page.forward_click(point).await?.wait_for_navigation().await?.content();
1108    ///     # Ok(())
1109    /// # }
1110    /// ```
1111    /// ```
1112    pub async fn forward_click(&self, point: Point) -> Result<&Self> {
1113        self.inner.forward_click(point).await?;
1114        Ok(self)
1115    }
1116
1117    /// Performs a single mouse click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1118    ///
1119    /// This scrolls the point into view first, then executes a
1120    /// `DispatchMouseEventParams` command of type `MouseLeft` with
1121    /// `MousePressed` as single click and then releases the mouse with an
1122    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1123    /// `MouseReleased`
1124    ///
1125    /// Bear in mind that if `click()` triggers a navigation the new page is not
1126    /// immediately loaded when `click()` resolves. To wait until navigation is
1127    /// finished an additional `wait_for_navigation()` is required:
1128    ///
1129    /// # Example
1130    ///
1131    /// Trigger a navigation and wait until the triggered navigation is finished
1132    ///
1133    /// ```no_run
1134    /// # use chromiumoxide::page::Page;
1135    /// # use chromiumoxide::error::Result;
1136    /// # use chromiumoxide::layout::Point;
1137    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1138    ///     let html = page.click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1139    ///     # Ok(())
1140    /// # }
1141    /// ```
1142    /// ```
1143    pub async fn click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1144        self.inner.click_with_modifier(point, modifiers).await?;
1145        Ok(self)
1146    }
1147
1148    /// Performs a single mouse right click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1149    ///
1150    /// This scrolls the point into view first, then executes a
1151    /// `DispatchMouseEventParams` command of type `MouseLeft` with
1152    /// `MousePressed` as single click and then releases the mouse with an
1153    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1154    /// `MouseReleased`
1155    ///
1156    /// # Example
1157    ///
1158    /// Trigger a navigation and wait until the triggered navigation is finished
1159    ///
1160    /// ```no_run
1161    /// # use chromiumoxide::page::Page;
1162    /// # use chromiumoxide::error::Result;
1163    /// # use chromiumoxide::layout::Point;
1164    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1165    ///     let html = page.right_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1166    ///     # Ok(())
1167    /// # }
1168    /// ```
1169    /// ```
1170    pub async fn right_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1171        self.inner
1172            .right_click_with_modifier(point, modifiers)
1173            .await?;
1174        Ok(self)
1175    }
1176
1177    /// Performs a single mouse middle click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1178    ///
1179    /// This scrolls the point into view first, then executes a
1180    /// `DispatchMouseEventParams` command of type `MouseLeft` with
1181    /// `MousePressed` as single click and then releases the mouse with an
1182    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1183    /// `MouseReleased`
1184    ///
1185    /// # Example
1186    ///
1187    /// Trigger a navigation and wait until the triggered navigation is finished
1188    ///
1189    /// ```no_run
1190    /// # use chromiumoxide::page::Page;
1191    /// # use chromiumoxide::error::Result;
1192    /// # use chromiumoxide::layout::Point;
1193    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1194    ///     let html = page.middle_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1195    ///     # Ok(())
1196    /// # }
1197    /// ```
1198    /// ```
1199    pub async fn middle_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1200        self.inner
1201            .middle_click_with_modifier(point, modifiers)
1202            .await?;
1203        Ok(self)
1204    }
1205
1206    /// Performs keyboard typing.
1207    ///
1208    /// # Example
1209    ///
1210    /// ```no_run
1211    /// # use chromiumoxide::page::Page;
1212    /// # use chromiumoxide::error::Result;
1213    /// # use chromiumoxide::layout::Point;
1214    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1215    ///     let html = page.type_str("abc").await?.content();
1216    ///     # Ok(())
1217    /// # }
1218    /// ```
1219    pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
1220        self.inner.type_str(input).await?;
1221        Ok(self)
1222    }
1223
1224    /// Performs keyboard typing with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1225    ///
1226    /// # Example
1227    ///
1228    /// ```no_run
1229    /// # use chromiumoxide::page::Page;
1230    /// # use chromiumoxide::error::Result;
1231    /// # use chromiumoxide::layout::Point;
1232    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1233    ///     let html = page.type_str_with_modifier("abc", Some(1)).await?.content();
1234    ///     # Ok(())
1235    /// # }
1236    /// ```
1237    pub async fn type_str_with_modifier(
1238        &self,
1239        input: impl AsRef<str>,
1240        modifiers: Option<i64>,
1241    ) -> Result<&Self> {
1242        self.inner.type_str_with_modifier(input, modifiers).await?;
1243        Ok(self)
1244    }
1245
1246    /// Performs a click-and-drag mouse event from a starting point to a destination.
1247    ///
1248    /// This scrolls both points into view and dispatches a sequence of `DispatchMouseEventParams`
1249    /// commands in order: a `MousePressed` event at the start location, followed by a `MouseMoved`
1250    /// event to the end location, and finally a `MouseReleased` event to complete the drag.
1251    ///
1252    /// This is useful for dragging UI elements, sliders, or simulating mouse gestures.
1253    ///
1254    /// # Example
1255    ///
1256    /// Perform a drag from point A to point B using the Shift modifier:
1257    ///
1258    /// ```no_run
1259    /// # use chromiumoxide::page::Page;
1260    /// # use chromiumoxide::error::Result;
1261    /// # use chromiumoxide::layout::Point;
1262    /// # async fn demo(page: Page, from: Point, to: Point) -> Result<()> {
1263    ///     page.click_and_drag_with_modifier(from, to, 8).await?;
1264    ///     Ok(())
1265    /// # }
1266    /// ```
1267    pub async fn click_and_drag(&self, from: Point, to: Point) -> Result<&Self> {
1268        self.inner.click_and_drag(from, to, 0).await?;
1269        Ok(self)
1270    }
1271
1272    /// Performs a click-and-drag mouse event from a starting point to a destination,
1273    /// with optional keyboard modifiers: Alt = 1, Ctrl = 2, Meta/Command = 4, Shift = 8 (default: 0).
1274    ///
1275    /// This scrolls both points into view and dispatches a sequence of `DispatchMouseEventParams`
1276    /// commands in order: a `MousePressed` event at the start location, followed by a `MouseMoved`
1277    /// event to the end location, and finally a `MouseReleased` event to complete the drag.
1278    ///
1279    /// This is useful for dragging UI elements, sliders, or simulating mouse gestures.
1280    ///
1281    /// # Example
1282    ///
1283    /// Perform a drag from point A to point B using the Shift modifier:
1284    ///
1285    /// ```no_run
1286    /// # use chromiumoxide::page::Page;
1287    /// # use chromiumoxide::error::Result;
1288    /// # use chromiumoxide::layout::Point;
1289    /// # async fn demo(page: Page, from: Point, to: Point) -> Result<()> {
1290    ///     page.click_and_drag_with_modifier(from, to, 8).await?;
1291    ///     Ok(())
1292    /// # }
1293    /// ```
1294    pub async fn click_and_drag_with_modifier(
1295        &self,
1296        from: Point,
1297        to: Point,
1298        modifiers: i64,
1299    ) -> Result<&Self> {
1300        self.inner.click_and_drag(from, to, modifiers).await?;
1301        Ok(self)
1302    }
1303
1304    /// Performs a double mouse click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1305    ///
1306    /// This scrolls the point into view first, then executes a
1307    /// `DispatchMouseEventParams` command of type `MouseLeft` with
1308    /// `MousePressed` as single click and then releases the mouse with an
1309    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1310    /// `MouseReleased`
1311    ///
1312    /// Bear in mind that if `click()` triggers a navigation the new page is not
1313    /// immediately loaded when `click()` resolves. To wait until navigation is
1314    /// finished an additional `wait_for_navigation()` is required:
1315    ///
1316    /// # Example
1317    ///
1318    /// Trigger a navigation and wait until the triggered navigation is finished
1319    ///
1320    /// ```no_run
1321    /// # use chromiumoxide::page::Page;
1322    /// # use chromiumoxide::error::Result;
1323    /// # use chromiumoxide::layout::Point;
1324    /// # async fn demo(page: Page, point: Point) -> Result<()> {
1325    ///     let html = page.double_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1326    ///     # Ok(())
1327    /// # }
1328    /// ```
1329    /// ```
1330    pub async fn double_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1331        self.inner
1332            .double_click_with_modifier(point, modifiers)
1333            .await?;
1334        Ok(self)
1335    }
1336
1337    /// Dispatches a `mouseMoved` event and moves the mouse to the position of
1338    /// the `point` where `Point.x` is the horizontal position of the mouse and
1339    /// `Point.y` the vertical position of the mouse.
1340    pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
1341        self.inner.move_mouse(point).await?;
1342        Ok(self)
1343    }
1344
1345    /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
1346    /// keys.
1347    pub async fn press_key(&self, input: impl AsRef<str>) -> Result<&Self> {
1348        self.inner.press_key(input).await?;
1349        Ok(self)
1350    }
1351
1352    /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
1353    /// keys with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0)..
1354    pub async fn press_key_with_modifier(
1355        &self,
1356        input: impl AsRef<str>,
1357        modifiers: i64,
1358    ) -> Result<&Self> {
1359        self.inner
1360            .press_key_with_modifier(input, Some(modifiers))
1361            .await?;
1362        Ok(self)
1363    }
1364
1365    /// Dispatches a `DragEvent`, moving the element to the given `point`.
1366    ///
1367    /// `point.x` defines the horizontal target, and `point.y` the vertical mouse position.
1368    /// Accepts `drag_type`, `drag_data`, and optional keyboard `modifiers`.
1369    pub async fn drag(
1370        &self,
1371        drag_type: DispatchDragEventType,
1372        point: Point,
1373        drag_data: DragData,
1374        modifiers: Option<i64>,
1375    ) -> Result<&Self> {
1376        self.inner
1377            .drag(drag_type, point, drag_data, modifiers)
1378            .await?;
1379        Ok(self)
1380    }
1381    /// Fetches the entire accessibility tree for the root Document
1382    ///
1383    /// # Example
1384    ///
1385    /// ```no_run
1386    /// # use chromiumoxide::page::Page;
1387    /// # use chromiumoxide::error::Result;
1388    /// # use chromiumoxide::cdp::browser_protocol::page::FrameId;
1389    /// # async fn demo_get_full_ax_tree(page: Page, depth: Option<i64>, frame_id: Option<FrameId>) -> Result<()> {
1390    ///     let tree = page.get_full_ax_tree(None, None).await;
1391    ///     # Ok(())
1392    /// # }
1393    /// ```
1394    pub async fn get_full_ax_tree(
1395        &self,
1396        depth: Option<i64>,
1397        frame_id: Option<FrameId>,
1398    ) -> Result<GetFullAxTreeReturns> {
1399        self.inner.get_full_ax_tree(depth, frame_id).await
1400    }
1401
1402    /// Fetches the partial accessibility tree for the root Document
1403    ///
1404    /// # Example
1405    ///
1406    /// ```no_run
1407    /// # use chromiumoxide::page::Page;
1408    /// # use chromiumoxide::error::Result;
1409    /// # use chromiumoxide::cdp::browser_protocol::dom::BackendNodeId;
1410    /// # async fn demo_get_partial_ax_tree(page: Page, node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>, backend_node_id: Option<BackendNodeId>, object_id: Option<chromiumoxide_cdp::cdp::js_protocol::runtime::RemoteObjectId>, fetch_relatives: Option<bool>,) -> Result<()> {
1411    ///     let tree = page.get_partial_ax_tree(node_id, backend_node_id, object_id, fetch_relatives).await;
1412    ///     # Ok(())
1413    /// # }
1414    /// ```
1415    pub async fn get_partial_ax_tree(
1416        &self,
1417        node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
1418        backend_node_id: Option<BackendNodeId>,
1419        object_id: Option<chromiumoxide_cdp::cdp::js_protocol::runtime::RemoteObjectId>,
1420        fetch_relatives: Option<bool>,
1421    ) -> Result<GetPartialAxTreeReturns> {
1422        self.inner
1423            .get_partial_ax_tree(node_id, backend_node_id, object_id, fetch_relatives)
1424            .await
1425    }
1426
1427    /// Dispatches a `mouseWheel` event and moves the mouse to the position of
1428    /// the `point` where `Point.x` is the horizontal position of the mouse and
1429    /// `Point.y` the vertical position of the mouse.
1430    pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
1431        self.inner.scroll(point, delta).await?;
1432        Ok(self)
1433    }
1434
1435    /// Scrolls the current page by the specified horizontal and vertical offsets.
1436    /// This method helps when Chrome version may not support certain CDP dispatch events.
1437    pub async fn scroll_by(
1438        &self,
1439        delta_x: f64,
1440        delta_y: f64,
1441        behavior: ScrollBehavior,
1442    ) -> Result<&Self> {
1443        self.inner.scroll_by(delta_x, delta_y, behavior).await?;
1444        Ok(self)
1445    }
1446
1447    /// Take a screenshot of the current page
1448    pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
1449        self.inner.screenshot(params).await
1450    }
1451
1452    /// Take a screenshot of the current page
1453    pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1454        self.inner.print_to_pdf(params).await
1455    }
1456
1457    /// Save a screenshot of the page
1458    ///
1459    /// # Example save a png file of a website
1460    ///
1461    /// ```no_run
1462    /// # use chromiumoxide::page::{Page, ScreenshotParams};
1463    /// # use chromiumoxide::error::Result;
1464    /// # use chromiumoxide_cdp::cdp::browser_protocol::page::CaptureScreenshotFormat;
1465    /// # async fn demo(page: Page) -> Result<()> {
1466    ///         page.goto("http://example.com")
1467    ///             .await?
1468    ///             .save_screenshot(
1469    ///             ScreenshotParams::builder()
1470    ///                 .format(CaptureScreenshotFormat::Png)
1471    ///                 .full_page(true)
1472    ///                 .omit_background(true)
1473    ///                 .build(),
1474    ///             "example.png",
1475    ///             )
1476    ///             .await?;
1477    ///     # Ok(())
1478    /// # }
1479    /// ```
1480    pub async fn save_screenshot(
1481        &self,
1482        params: impl Into<ScreenshotParams>,
1483        output: impl AsRef<Path>,
1484    ) -> Result<Vec<u8>> {
1485        let img = self.screenshot(params).await?;
1486        utils::write(output.as_ref(), &img).await?;
1487        Ok(img)
1488    }
1489
1490    /// Print the current page as pdf.
1491    ///
1492    /// See [`PrintToPdfParams`]
1493    ///
1494    /// # Note Generating a pdf is currently only supported in Chrome headless.
1495    pub async fn pdf(&self, params: PrintToPdfParams) -> Result<Vec<u8>> {
1496        let res = self.execute(params).await?;
1497        Ok(utils::base64::decode(&res.data)?)
1498    }
1499
1500    /// Save the current page as pdf as file to the `output` path and return the
1501    /// pdf contents.
1502    ///
1503    /// # Note Generating a pdf is currently only supported in Chrome headless.
1504    pub async fn save_pdf(
1505        &self,
1506        opts: PrintToPdfParams,
1507        output: impl AsRef<Path>,
1508    ) -> Result<Vec<u8>> {
1509        let pdf = self.pdf(opts).await?;
1510        utils::write(output.as_ref(), &pdf).await?;
1511        Ok(pdf)
1512    }
1513
1514    /// Brings page to front (activates tab)
1515    pub async fn bring_to_front(&self) -> Result<&Self> {
1516        self.execute(BringToFrontParams::default()).await?;
1517        Ok(self)
1518    }
1519
1520    /// Turns on virtual time for all frames (replacing real-time with a synthetic time source) and sets the current virtual time policy. Note this supersedes any previous time budget.
1521    pub async fn enable_virtual_time_with_budget(
1522        &self,
1523        budget_ms: f64,
1524        policy: Option<chromiumoxide_cdp::cdp::browser_protocol::emulation::VirtualTimePolicy>,
1525        max_virtual_time_task_starvation_count: Option<i64>,
1526        initial_virtual_time: Option<TimeSinceEpoch>,
1527    ) -> Result<&Self> {
1528        let params =
1529            chromiumoxide_cdp::cdp::browser_protocol::emulation::SetVirtualTimePolicyParams {
1530                policy: policy.unwrap_or(
1531                    chromiumoxide_cdp::cdp::browser_protocol::emulation::VirtualTimePolicy::Advance,
1532                ),
1533                budget: Some(budget_ms),
1534                max_virtual_time_task_starvation_count: max_virtual_time_task_starvation_count
1535                    .or(Some(10_000)),
1536                initial_virtual_time,
1537            };
1538        self.execute(params).await?;
1539        Ok(self)
1540    }
1541
1542    /// Emulates hardware concurrency.
1543    pub async fn emulate_hardware_concurrency(&self, hardware_concurrency: i64) -> Result<&Self> {
1544        self.execute(SetHardwareConcurrencyOverrideParams::new(
1545            hardware_concurrency,
1546        ))
1547        .await?;
1548        Ok(self)
1549    }
1550
1551    /// Emulates the given media type or media feature for CSS media queries
1552    pub async fn emulate_media_features(&self, features: Vec<MediaFeature>) -> Result<&Self> {
1553        self.execute(SetEmulatedMediaParams::builder().features(features).build())
1554            .await?;
1555        Ok(self)
1556    }
1557
1558    /// Changes the CSS media type of the page
1559    // Based on https://pptr.dev/api/puppeteer.page.emulatemediatype
1560    pub async fn emulate_media_type(
1561        &self,
1562        media_type: impl Into<MediaTypeParams>,
1563    ) -> Result<&Self> {
1564        self.execute(
1565            SetEmulatedMediaParams::builder()
1566                .media(media_type.into())
1567                .build(),
1568        )
1569        .await?;
1570        Ok(self)
1571    }
1572
1573    /// Overrides default host system timezone
1574    pub async fn emulate_timezone(
1575        &self,
1576        timezoune_id: impl Into<SetTimezoneOverrideParams>,
1577    ) -> Result<&Self> {
1578        self.execute(timezoune_id.into()).await?;
1579        Ok(self)
1580    }
1581
1582    /// Overrides default host system locale with the specified one
1583    pub async fn emulate_locale(
1584        &self,
1585        locale: impl Into<SetLocaleOverrideParams>,
1586    ) -> Result<&Self> {
1587        self.execute(locale.into()).await?;
1588        Ok(self)
1589    }
1590
1591    /// Overrides default viewport
1592    pub async fn emulate_viewport(
1593        &self,
1594        viewport: impl Into<SetDeviceMetricsOverrideParams>,
1595    ) -> Result<&Self> {
1596        self.execute(viewport.into()).await?;
1597        Ok(self)
1598    }
1599
1600    /// Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position unavailable.
1601    pub async fn emulate_geolocation(
1602        &self,
1603        geolocation: impl Into<SetGeolocationOverrideParams>,
1604    ) -> Result<&Self> {
1605        self.execute(geolocation.into()).await?;
1606        Ok(self)
1607    }
1608
1609    /// Reloads given page
1610    ///
1611    /// To reload ignoring cache run:
1612    /// ```no_run
1613    /// # use chromiumoxide::page::Page;
1614    /// # use chromiumoxide::error::Result;
1615    /// # use chromiumoxide_cdp::cdp::browser_protocol::page::ReloadParams;
1616    /// # async fn demo(page: Page) -> Result<()> {
1617    ///     page.execute(ReloadParams::builder().ignore_cache(true).build()).await?;
1618    ///     page.wait_for_navigation().await?;
1619    ///     # Ok(())
1620    /// # }
1621    /// ```
1622    pub async fn reload(&self) -> Result<&Self> {
1623        self.execute(ReloadParams::default()).await?;
1624        self.wait_for_navigation().await
1625    }
1626
1627    /// Reloads given page without waiting for navigation.
1628    ///
1629    /// To reload ignoring cache run:
1630    /// ```no_run
1631    /// # use chromiumoxide::page::Page;
1632    /// # use chromiumoxide::error::Result;
1633    /// # use chromiumoxide_cdp::cdp::browser_protocol::page::ReloadParams;
1634    /// # async fn demo(page: Page) -> Result<()> {
1635    ///     page.execute(ReloadParams::builder().ignore_cache(true).build()).await?;
1636    ///     # Ok(())
1637    /// # }
1638    /// ```
1639    pub async fn reload_no_wait(&self) -> Result<&Self> {
1640        self.execute(ReloadParams::default()).await?;
1641        Ok(self)
1642    }
1643
1644    /// Enables ServiceWorkers. Disabled by default.
1645    /// See https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-enable
1646    pub async fn enable_service_workers(&self) -> Result<&Self> {
1647        self.execute(browser_protocol::service_worker::EnableParams::default())
1648            .await?;
1649        Ok(self)
1650    }
1651
1652    /// Disables ServiceWorker. Disabled by default.
1653    /// See https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-enable
1654    pub async fn disable_service_workers(&self) -> Result<&Self> {
1655        self.execute(browser_protocol::service_worker::DisableParams::default())
1656            .await?;
1657        Ok(self)
1658    }
1659
1660    /// Enables Performances. Disabled by default.
1661    /// See https://chromedevtools.github.io/devtools-protocol/tot/Performance#method-enable
1662    pub async fn enable_performance(&self) -> Result<&Self> {
1663        self.execute(browser_protocol::performance::EnableParams::default())
1664            .await?;
1665        Ok(self)
1666    }
1667
1668    /// Disables Performances. Disabled by default.
1669    /// See https://chromedevtools.github.io/devtools-protocol/tot/Performance#method-disable
1670    pub async fn disable_performance(&self) -> Result<&Self> {
1671        self.execute(browser_protocol::performance::DisableParams::default())
1672            .await?;
1673        Ok(self)
1674    }
1675
1676    /// Enables Overlay domain notifications. Disabled by default.
1677    /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-enable
1678    pub async fn enable_overlay(&self) -> Result<&Self> {
1679        self.execute(browser_protocol::overlay::EnableParams::default())
1680            .await?;
1681        Ok(self)
1682    }
1683
1684    /// Disables Overlay domain notifications. Disabled by default.
1685    /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-enable
1686    pub async fn disable_overlay(&self) -> Result<&Self> {
1687        self.execute(browser_protocol::overlay::DisableParams::default())
1688            .await?;
1689        Ok(self)
1690    }
1691
1692    /// Enables Overlay domain paint rectangles. Disabled by default.
1693    /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-setShowPaintRects
1694    pub async fn enable_paint_rectangles(&self) -> Result<&Self> {
1695        self.execute(browser_protocol::overlay::SetShowPaintRectsParams::new(
1696            true,
1697        ))
1698        .await?;
1699        Ok(self)
1700    }
1701
1702    /// Disabled Overlay domain paint rectangles. Disabled by default.
1703    /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-setShowPaintRects
1704    pub async fn disable_paint_rectangles(&self) -> Result<&Self> {
1705        self.execute(browser_protocol::overlay::SetShowPaintRectsParams::new(
1706            false,
1707        ))
1708        .await?;
1709        Ok(self)
1710    }
1711
1712    /// Enables log domain. Disabled by default.
1713    ///
1714    /// Sends the entries collected so far to the client by means of the
1715    /// entryAdded notification.
1716    ///
1717    /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-enable
1718    pub async fn enable_log(&self) -> Result<&Self> {
1719        self.execute(browser_protocol::log::EnableParams::default())
1720            .await?;
1721        Ok(self)
1722    }
1723
1724    /// Disables log domain
1725    ///
1726    /// Prevents further log entries from being reported to the client
1727    ///
1728    /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-disable
1729    pub async fn disable_log(&self) -> Result<&Self> {
1730        self.execute(browser_protocol::log::DisableParams::default())
1731            .await?;
1732        Ok(self)
1733    }
1734
1735    /// Enables runtime domain. Activated by default.
1736    pub async fn enable_runtime(&self) -> Result<&Self> {
1737        self.execute(js_protocol::runtime::EnableParams::default())
1738            .await?;
1739        Ok(self)
1740    }
1741
1742    /// Enables the network.
1743    pub async fn enable_network(&self) -> Result<&Self> {
1744        self.execute(browser_protocol::network::EnableParams::default())
1745            .await?;
1746        Ok(self)
1747    }
1748
1749    /// Disables the network.
1750    pub async fn disable_network(&self) -> Result<&Self> {
1751        self.execute(browser_protocol::network::DisableParams::default())
1752            .await?;
1753        Ok(self)
1754    }
1755
1756    /// Disables runtime domain.
1757    pub async fn disable_runtime(&self) -> Result<&Self> {
1758        self.execute(js_protocol::runtime::DisableParams::default())
1759            .await?;
1760        Ok(self)
1761    }
1762
1763    /// Enables Debugger. Enabled by default.
1764    pub async fn enable_debugger(&self) -> Result<&Self> {
1765        self.execute(js_protocol::debugger::EnableParams::default())
1766            .await?;
1767        Ok(self)
1768    }
1769
1770    /// Disables Debugger.
1771    pub async fn disable_debugger(&self) -> Result<&Self> {
1772        self.execute(js_protocol::debugger::DisableParams::default())
1773            .await?;
1774        Ok(self)
1775    }
1776
1777    /// Enables page domain notifications. Enabled by default.
1778    /// See https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-enable
1779    pub async fn enable_page(&self) -> Result<&Self> {
1780        self.execute(browser_protocol::page::EnableParams::default())
1781            .await?;
1782        Ok(self)
1783    }
1784
1785    /// Disables page domain notifications. Disabled by default.
1786    /// See https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-disable
1787    pub async fn disable_page(&self) -> Result<&Self> {
1788        self.execute(browser_protocol::page::EnableParams::default())
1789            .await?;
1790        Ok(self)
1791    }
1792
1793    // Enables DOM agent
1794    pub async fn enable_dom(&self) -> Result<&Self> {
1795        self.execute(browser_protocol::dom::EnableParams::default())
1796            .await?;
1797        Ok(self)
1798    }
1799
1800    // Disables DOM agent
1801    pub async fn disable_dom(&self) -> Result<&Self> {
1802        self.execute(browser_protocol::dom::DisableParams::default())
1803            .await?;
1804        Ok(self)
1805    }
1806
1807    // Enables the CSS agent
1808    pub async fn enable_css(&self) -> Result<&Self> {
1809        self.execute(browser_protocol::css::EnableParams::default())
1810            .await?;
1811        Ok(self)
1812    }
1813
1814    // Disables the CSS agent
1815    pub async fn disable_css(&self) -> Result<&Self> {
1816        self.execute(browser_protocol::css::DisableParams::default())
1817            .await?;
1818        Ok(self)
1819    }
1820
1821    /// Block urls from networking.
1822    ///
1823    /// Prevents further networking
1824    ///
1825    /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1826    pub async fn set_blocked_urls(&self, urls: Vec<String>) -> Result<&Self> {
1827        self.execute(SetBlockedUrLsParams::new(urls)).await?;
1828        Ok(self)
1829    }
1830
1831    /// Force the page stop all navigations and pending resource fetches.
1832    /// See https://chromedevtools.github.io/devtools-protocol/tot/Page#method-stopLoading
1833    pub async fn stop_loading(&self) -> Result<&Self> {
1834        self.execute(browser_protocol::page::StopLoadingParams::default())
1835            .await?;
1836        Ok(self)
1837    }
1838
1839    /// Block all urls from networking.
1840    ///
1841    /// Prevents further networking
1842    ///
1843    /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1844    pub async fn block_all_urls(&self) -> Result<&Self> {
1845        self.execute(SetBlockedUrLsParams::new(vec!["*".into()]))
1846            .await?;
1847        Ok(self)
1848    }
1849
1850    /// Force the page stop all navigations and pending resource fetches for the rest of the page life.
1851    /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1852    /// See https://chromedevtools.github.io/devtools-protocol/tot/Page#method-stopLoading
1853    pub async fn force_stop_all(&self) -> Result<&Self> {
1854        let _ = tokio::join!(
1855            self.stop_loading(),
1856            self.set_blocked_urls(vec!["*".to_string()])
1857        );
1858        Ok(self)
1859    }
1860
1861    /// Activates (focuses) the target.
1862    pub async fn activate(&self) -> Result<&Self> {
1863        self.inner.activate().await?;
1864        Ok(self)
1865    }
1866
1867    /// Returns all cookies that match the tab's current URL.
1868    pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
1869        Ok(self
1870            .execute(GetCookiesParams::default())
1871            .await?
1872            .result
1873            .cookies)
1874    }
1875
1876    /// Set a single cookie
1877    ///
1878    /// This fails if the cookie's url or if not provided, the page's url is
1879    /// `about:blank` or a `data:` url.
1880    ///
1881    /// # Example
1882    /// ```no_run
1883    /// # use chromiumoxide::page::Page;
1884    /// # use chromiumoxide::error::Result;
1885    /// # use chromiumoxide_cdp::cdp::browser_protocol::network::CookieParam;
1886    /// # async fn demo(page: Page) -> Result<()> {
1887    ///     page.set_cookie(CookieParam::new("Cookie-name", "Cookie-value")).await?;
1888    ///     # Ok(())
1889    /// # }
1890    /// ```
1891    pub async fn set_cookie(&self, cookie: impl Into<CookieParam>) -> Result<&Self> {
1892        let mut cookie = cookie.into();
1893        if let Some(url) = cookie.url.as_ref() {
1894            validate_cookie_url(url)?;
1895        } else {
1896            let url = self
1897                .url()
1898                .await?
1899                .ok_or_else(|| CdpError::msg("Page url not found"))?;
1900            validate_cookie_url(&url)?;
1901            if url.starts_with("http") {
1902                cookie.url = Some(url);
1903            }
1904        }
1905        self.execute(DeleteCookiesParams::from_cookie(&cookie))
1906            .await?;
1907        self.execute(SetCookiesParams::new(vec![cookie])).await?;
1908        Ok(self)
1909    }
1910
1911    /// Set all the cookies
1912    pub async fn set_cookies(&self, mut cookies: Vec<CookieParam>) -> Result<&Self> {
1913        let url = self
1914            .url()
1915            .await?
1916            .ok_or_else(|| CdpError::msg("Page url not found"))?;
1917        let is_http = url.starts_with("http");
1918        if !is_http {
1919            validate_cookie_url(&url)?;
1920        }
1921
1922        for cookie in &mut cookies {
1923            if let Some(url) = cookie.url.as_ref() {
1924                validate_cookie_url(url)?;
1925            } else if is_http {
1926                cookie.url = Some(url.clone());
1927            }
1928        }
1929        self.delete_cookies_unchecked(cookies.iter().map(DeleteCookiesParams::from_cookie))
1930            .await?;
1931
1932        self.execute(SetCookiesParams::new(cookies)).await?;
1933        Ok(self)
1934    }
1935
1936    /// Delete a single cookie
1937    pub async fn delete_cookie(&self, cookie: impl Into<DeleteCookiesParams>) -> Result<&Self> {
1938        let mut cookie = cookie.into();
1939        if cookie.url.is_none() {
1940            let url = self
1941                .url()
1942                .await?
1943                .ok_or_else(|| CdpError::msg("Page url not found"))?;
1944            if url.starts_with("http") {
1945                cookie.url = Some(url);
1946            }
1947        }
1948        self.execute(cookie).await?;
1949        Ok(self)
1950    }
1951
1952    /// Delete all the cookies
1953    pub async fn delete_cookies(&self, mut cookies: Vec<DeleteCookiesParams>) -> Result<&Self> {
1954        let mut url: Option<(String, bool)> = None;
1955        for cookie in &mut cookies {
1956            if cookie.url.is_none() {
1957                if let Some((url, is_http)) = url.as_ref() {
1958                    if *is_http {
1959                        cookie.url = Some(url.clone())
1960                    }
1961                } else {
1962                    let page_url = self
1963                        .url()
1964                        .await?
1965                        .ok_or_else(|| CdpError::msg("Page url not found"))?;
1966                    let is_http = page_url.starts_with("http");
1967                    if is_http {
1968                        cookie.url = Some(page_url.clone())
1969                    }
1970                    url = Some((page_url, is_http));
1971                }
1972            }
1973        }
1974        self.delete_cookies_unchecked(cookies.into_iter()).await?;
1975        Ok(self)
1976    }
1977
1978    /// Convenience method that prevents another channel roundtrip to get the
1979    /// url and validate it
1980    async fn delete_cookies_unchecked(
1981        &self,
1982        cookies: impl Iterator<Item = DeleteCookiesParams>,
1983    ) -> Result<&Self> {
1984        // NOTE: the buffer size is arbitrary
1985        let mut cmds = stream::iter(cookies.into_iter().map(|cookie| self.execute(cookie)))
1986            .buffer_unordered(5);
1987        while let Some(resp) = cmds.next().await {
1988            resp?;
1989        }
1990        Ok(self)
1991    }
1992
1993    /// Returns the title of the document.
1994    pub async fn get_title(&self) -> Result<Option<String>> {
1995        let result = self.evaluate("document.title").await?;
1996
1997        let title: String = result.into_value()?;
1998
1999        if title.is_empty() {
2000            Ok(None)
2001        } else {
2002            Ok(Some(title))
2003        }
2004    }
2005
2006    /// Retrieve current values of run-time metrics. Enable the 'collect_metrics flag to auto init 'Performance.enable'.
2007    pub async fn metrics(&self) -> Result<Vec<Metric>> {
2008        Ok(self
2009            .execute(GetMetricsParams::default())
2010            .await?
2011            .result
2012            .metrics)
2013    }
2014
2015    /// Returns metrics relating to the layout of the page
2016    pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
2017        self.inner.layout_metrics().await
2018    }
2019
2020    /// Start a background guard that counts **wire bytes** (compressed on the network)
2021    /// and force-stops the page once `max_bytes` is exceeded.
2022    ///
2023    /// - Uses CDP Network.dataReceived -> `encodedDataLength`
2024    /// - Calls `Page.stopLoading()` when the cap is hit
2025    /// - Optionally closes the tab after stopping
2026    ///
2027    /// Returns a JoinHandle you can `.await` or just detach.
2028    pub async fn start_wire_bytes_budget_background(
2029        &self,
2030        max_bytes: u64,
2031        close_on_exceed: Option<bool>,
2032        enable_networking: Option<bool>,
2033        sent_and_received: Option<bool>,
2034    ) -> Result<tokio::task::JoinHandle<()>> {
2035        // prevent re-enabling the network - by default this should be enabled.
2036        if enable_networking.unwrap_or(false) {
2037            let _ = self.enable_network().await;
2038        }
2039
2040        let close_on_exceed = close_on_exceed.unwrap_or_default();
2041        let track_all = sent_and_received.unwrap_or_default();
2042
2043        let mut rx = self
2044            .event_listener::<crate::page::browser_protocol::network::EventDataReceived>()
2045            .await
2046            .map_err(|e| CdpError::msg(format!("event_listener failed: {e}")))?;
2047
2048        let page = self.clone();
2049
2050        let handle = tokio::spawn(async move {
2051            let mut total_bytes: u64 = 0;
2052
2053            while let Some(ev) = rx.next().await {
2054                let encoded = ev.encoded_data_length.max(0) as u64;
2055                let data_length = if track_all {
2056                    ev.data_length.max(0) as u64
2057                } else {
2058                    0
2059                };
2060                total_bytes = total_bytes.saturating_add(encoded + data_length);
2061                if total_bytes > max_bytes {
2062                    let _ = page.force_stop_all().await;
2063                    if close_on_exceed {
2064                        let _ = page.close().await;
2065                    }
2066                    break;
2067                }
2068            }
2069        });
2070
2071        Ok(handle)
2072    }
2073
2074    /// Start a guard that counts **wire bytes** (compressed on the network)
2075    /// and force-stops the page once `max_bytes` is exceeded.
2076    ///
2077    /// - Uses CDP Network.dataReceived -> `encodedDataLength`
2078    /// - Calls `Page.stopLoading()` when the cap is hit
2079    /// - Optionally closes the tab after stopping
2080    ///
2081    /// Returns a JoinHandle you can `.await` or just detach.
2082    pub async fn start_wire_bytes_budget(
2083        &self,
2084        max_bytes: u64,
2085        close_on_exceed: Option<bool>,
2086        enable_networking: Option<bool>,
2087    ) -> Result<()> {
2088        // prevent re-enabling the network - by default this should be enabled.
2089        if enable_networking.unwrap_or(false) {
2090            let _ = self.enable_network().await;
2091        }
2092
2093        let close_on_exceed = close_on_exceed.unwrap_or_default();
2094        let mut rx = self
2095            .event_listener::<crate::page::browser_protocol::network::EventDataReceived>()
2096            .await
2097            .map_err(|e| CdpError::msg(format!("event_listener failed: {e}")))?;
2098
2099        let page = self.clone();
2100
2101        let mut total_bytes: u64 = 0;
2102
2103        while let Some(ev) = rx.next().await {
2104            total_bytes = total_bytes.saturating_add(ev.encoded_data_length.max(0) as u64);
2105            if total_bytes > max_bytes {
2106                let _ = page.force_stop_all().await;
2107                if close_on_exceed {
2108                    let _ = page.close().await;
2109                }
2110                break;
2111            }
2112        }
2113
2114        Ok(())
2115    }
2116
2117    /// This evaluates strictly as expression.
2118    ///
2119    /// Same as `Page::evaluate` but no fallback or any attempts to detect
2120    /// whether the expression is actually a function. However you can
2121    /// submit a function evaluation string:
2122    ///
2123    /// # Example Evaluate function call as expression
2124    ///
2125    /// This will take the arguments `(1,2)` and will call the function
2126    ///
2127    /// ```no_run
2128    /// # use chromiumoxide::page::Page;
2129    /// # use chromiumoxide::error::Result;
2130    /// # async fn demo(page: Page) -> Result<()> {
2131    ///     let sum: usize = page
2132    ///         .evaluate_expression("((a,b) => {return a + b;})(1,2)")
2133    ///         .await?
2134    ///         .into_value()?;
2135    ///     assert_eq!(sum, 3);
2136    ///     # Ok(())
2137    /// # }
2138    /// ```
2139    pub async fn evaluate_expression(
2140        &self,
2141        evaluate: impl Into<EvaluateParams>,
2142    ) -> Result<EvaluationResult> {
2143        self.inner.evaluate_expression(evaluate).await
2144    }
2145
2146    /// Evaluates an expression or function in the page's context and returns
2147    /// the result.
2148    ///
2149    /// In contrast to `Page::evaluate_expression` this is capable of handling
2150    /// function calls and expressions alike. This takes anything that is
2151    /// `Into<Evaluation>`. When passing a `String` or `str`, this will try to
2152    /// detect whether it is a function or an expression. JS function detection
2153    /// is not very sophisticated but works for general cases (`(async)
2154    /// functions` and arrow functions). If you want a string statement
2155    /// specifically evaluated as expression or function either use the
2156    /// designated functions `Page::evaluate_function` or
2157    /// `Page::evaluate_expression` or use the proper parameter type for
2158    /// `Page::execute`:  `EvaluateParams` for strict expression evaluation or
2159    /// `CallFunctionOnParams` for strict function evaluation.
2160    ///
2161    /// If you don't trust the js function detection and are not sure whether
2162    /// the statement is an expression or of type function (arrow functions: `()
2163    /// => {..}`), you should pass it as `EvaluateParams` and set the
2164    /// `EvaluateParams::eval_as_function_fallback` option. This will first
2165    /// try to evaluate it as expression and if the result comes back
2166    /// evaluated as `RemoteObjectType::Function` it will submit the
2167    /// statement again but as function:
2168    ///
2169    ///  # Example Evaluate function statement as expression with fallback
2170    /// option
2171    ///
2172    /// ```no_run
2173    /// # use chromiumoxide::page::Page;
2174    /// # use chromiumoxide::error::Result;
2175    /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{EvaluateParams, RemoteObjectType};
2176    /// # async fn demo(page: Page) -> Result<()> {
2177    ///     let eval = EvaluateParams::builder().expression("() => {return 42;}");
2178    ///     // this will fail because the `EvaluationResult` returned by the browser will be
2179    ///     // of type `Function`
2180    ///     let result = page
2181    ///                 .evaluate(eval.clone().build().unwrap())
2182    ///                 .await?;
2183    ///     assert_eq!(result.object().r#type, RemoteObjectType::Function);
2184    ///     assert!(result.into_value::<usize>().is_err());
2185    ///
2186    ///     // This will also fail on the first try but it detects that the browser evaluated the
2187    ///     // statement as function and then evaluate it again but as function
2188    ///     let sum: usize = page
2189    ///         .evaluate(eval.eval_as_function_fallback(true).build().unwrap())
2190    ///         .await?
2191    ///         .into_value()?;
2192    ///     # Ok(())
2193    /// # }
2194    /// ```
2195    ///
2196    /// # Example Evaluate basic expression
2197    /// ```no_run
2198    /// # use chromiumoxide::page::Page;
2199    /// # use chromiumoxide::error::Result;
2200    /// # async fn demo(page: Page) -> Result<()> {
2201    ///     let sum:usize = page.evaluate("1 + 2").await?.into_value()?;
2202    ///     assert_eq!(sum, 3);
2203    ///     # Ok(())
2204    /// # }
2205    /// ```
2206    pub async fn evaluate(&self, evaluate: impl Into<Evaluation>) -> Result<EvaluationResult> {
2207        match evaluate.into() {
2208            Evaluation::Expression(mut expr) => {
2209                if expr.context_id.is_none() {
2210                    expr.context_id = self.execution_context().await?;
2211                }
2212                let fallback = expr.eval_as_function_fallback.and_then(|p| {
2213                    if p {
2214                        Some(expr.clone())
2215                    } else {
2216                        None
2217                    }
2218                });
2219                let res = self.evaluate_expression(expr).await?;
2220
2221                if res.object().r#type == RemoteObjectType::Function {
2222                    // expression was actually a function
2223                    if let Some(fallback) = fallback {
2224                        return self.evaluate_function(fallback).await;
2225                    }
2226                }
2227                Ok(res)
2228            }
2229            Evaluation::Function(fun) => Ok(self.evaluate_function(fun).await?),
2230        }
2231    }
2232
2233    /// Eexecutes a function withinthe page's context and returns the result.
2234    ///
2235    /// # Example Evaluate a promise
2236    /// This will wait until the promise resolves and then returns the result.
2237    /// ```no_run
2238    /// # use chromiumoxide::page::Page;
2239    /// # use chromiumoxide::error::Result;
2240    /// # async fn demo(page: Page) -> Result<()> {
2241    ///     let sum:usize = page.evaluate_function("() => Promise.resolve(1 + 2)").await?.into_value()?;
2242    ///     assert_eq!(sum, 3);
2243    ///     # Ok(())
2244    /// # }
2245    /// ```
2246    ///
2247    /// # Example Evaluate an async function
2248    /// ```no_run
2249    /// # use chromiumoxide::page::Page;
2250    /// # use chromiumoxide::error::Result;
2251    /// # async fn demo(page: Page) -> Result<()> {
2252    ///     let val:usize = page.evaluate_function("async function() {return 42;}").await?.into_value()?;
2253    ///     assert_eq!(val, 42);
2254    ///     # Ok(())
2255    /// # }
2256    /// ```
2257    /// # Example Construct a function call
2258    ///
2259    /// ```no_run
2260    /// # use chromiumoxide::page::Page;
2261    /// # use chromiumoxide::error::Result;
2262    /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{CallFunctionOnParams, CallArgument};
2263    /// # async fn demo(page: Page) -> Result<()> {
2264    ///     let call = CallFunctionOnParams::builder()
2265    ///            .function_declaration(
2266    ///                "(a,b) => { return a + b;}"
2267    ///            )
2268    ///            .argument(
2269    ///                CallArgument::builder()
2270    ///                    .value(serde_json::json!(1))
2271    ///                    .build(),
2272    ///            )
2273    ///            .argument(
2274    ///                CallArgument::builder()
2275    ///                    .value(serde_json::json!(2))
2276    ///                    .build(),
2277    ///            )
2278    ///            .build()
2279    ///            .unwrap();
2280    ///     let sum:usize = page.evaluate_function(call).await?.into_value()?;
2281    ///     assert_eq!(sum, 3);
2282    ///     # Ok(())
2283    /// # }
2284    /// ```
2285    pub async fn evaluate_function(
2286        &self,
2287        evaluate: impl Into<CallFunctionOnParams>,
2288    ) -> Result<EvaluationResult> {
2289        self.inner.evaluate_function(evaluate).await
2290    }
2291
2292    /// Returns the default execution context identifier of this page that
2293    /// represents the context for JavaScript execution.
2294    pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
2295        self.inner.execution_context().await
2296    }
2297
2298    /// Returns the secondary execution context identifier of this page that
2299    /// represents the context for JavaScript execution for manipulating the
2300    /// DOM.
2301    ///
2302    /// See `Page::set_contents`
2303    pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
2304        self.inner.secondary_execution_context().await
2305    }
2306
2307    #[cfg(feature = "cache")]
2308    /// Spawn a cache listener to store resources to memory. This does nothing without the 'cache' flag.
2309    pub async fn spawn_cache_listener(&self, auth: Option<String>) -> Result<&Self> {
2310        use crate::cache::spawn_response_cache_listener;
2311        spawn_response_cache_listener(self.clone(), auth).await?;
2312        Ok(self)
2313    }
2314
2315    #[cfg(feature = "cache")]
2316    /// Spawn a cache intercepter to load resources to memory. This does nothing without the 'cache' flag.
2317    pub async fn spawn_cache_intercepter(
2318        &self,
2319        auth: Option<String>,
2320        policy: Option<crate::cache::BasicCachePolicy>,
2321    ) -> Result<&Self> {
2322        use crate::cache::spawn_fetch_cache_interceptor;
2323        spawn_fetch_cache_interceptor(self.clone(), auth, policy).await?;
2324        Ok(self)
2325    }
2326
2327    #[cfg(not(feature = "cache"))]
2328    /// Spawn a cache listener to store resources to memory. This does nothing without the 'cache' flag.
2329    pub async fn spawn_cache_listener(&self, _auth: Option<String>) -> Result<&Self> {
2330        Ok(self)
2331    }
2332
2333    pub async fn frame_execution_context(
2334        &self,
2335        frame_id: FrameId,
2336    ) -> Result<Option<ExecutionContextId>> {
2337        self.inner.frame_execution_context(frame_id).await
2338    }
2339
2340    pub async fn frame_secondary_execution_context(
2341        &self,
2342        frame_id: FrameId,
2343    ) -> Result<Option<ExecutionContextId>> {
2344        self.inner.frame_secondary_execution_context(frame_id).await
2345    }
2346
2347    /// Evaluates given script in every frame upon creation (before loading
2348    /// frame's scripts)
2349    pub async fn evaluate_on_new_document(
2350        &self,
2351        script: impl Into<AddScriptToEvaluateOnNewDocumentParams>,
2352    ) -> Result<ScriptIdentifier> {
2353        Ok(self.execute(script.into()).await?.result.identifier)
2354    }
2355
2356    /// Set the content of the frame.
2357    ///
2358    /// # Example
2359    /// ```no_run
2360    /// # use chromiumoxide::page::Page;
2361    /// # use chromiumoxide::error::Result;
2362    /// # async fn demo(page: Page) -> Result<()> {
2363    ///     page.set_content("<body>
2364    ///  <h1>This was set via chromiumoxide</h1>
2365    ///  </body>").await?;
2366    ///     # Ok(())
2367    /// # }
2368    /// ```
2369    pub async fn set_content(&self, html: impl AsRef<str>) -> Result<&Self> {
2370        if let Ok(mut call) = CallFunctionOnParams::builder()
2371            .function_declaration(
2372                "(html) => {
2373            document.open();
2374            document.write(html);
2375            document.close();
2376        }",
2377            )
2378            .argument(
2379                CallArgument::builder()
2380                    .value(serde_json::json!(html.as_ref()))
2381                    .build(),
2382            )
2383            .build()
2384        {
2385            call.execution_context_id = self
2386                .inner
2387                .execution_context_for_world(None, DOMWorldKind::Secondary)
2388                .await?;
2389            self.evaluate_function(call).await?;
2390        }
2391        // relying that document.open() will reset frame lifecycle with "init"
2392        // lifecycle event. @see https://crrev.com/608658
2393        self.wait_for_navigation().await
2394    }
2395
2396    /// Returns the HTML content of the page.
2397    pub async fn content(&self) -> Result<String> {
2398        Ok(self.evaluate(OUTER_HTML).await?.into_value()?)
2399    }
2400
2401    /// Returns the HTML content of the page
2402    pub async fn content_bytes(&self) -> Result<Vec<u8>> {
2403        Ok(self.evaluate(OUTER_HTML).await?.into_bytes()?)
2404    }
2405
2406    /// Returns the full serialized content of the page (HTML or XML)
2407    pub async fn content_bytes_xml(&self) -> Result<Vec<u8>> {
2408        Ok(self.evaluate(FULL_XML_SERIALIZER_JS).await?.into_bytes()?)
2409    }
2410
2411    /// Returns the HTML outer html of the page
2412    pub async fn outer_html_bytes(&self) -> Result<Vec<u8>> {
2413        Ok(self.outer_html().await?.into())
2414    }
2415
2416    /// Enable Chrome's experimental ad filter on all sites.
2417    pub async fn set_ad_blocking_enabled(&self, enabled: bool) -> Result<&Self> {
2418        self.execute(SetAdBlockingEnabledParams::new(enabled))
2419            .await?;
2420        Ok(self)
2421    }
2422
2423    /// Start to screencast a frame.
2424    pub async fn start_screencast(
2425        &self,
2426        params: impl Into<StartScreencastParams>,
2427    ) -> Result<&Self> {
2428        self.execute(params.into()).await?;
2429        Ok(self)
2430    }
2431
2432    /// Acknowledges that a screencast frame has been received by the frontend.
2433    pub async fn ack_screencast(
2434        &self,
2435        params: impl Into<ScreencastFrameAckParams>,
2436    ) -> Result<&Self> {
2437        self.execute(params.into()).await?;
2438        Ok(self)
2439    }
2440
2441    /// Stop screencast a frame.
2442    pub async fn stop_screencast(&self, params: impl Into<StopScreencastParams>) -> Result<&Self> {
2443        self.execute(params.into()).await?;
2444        Ok(self)
2445    }
2446
2447    /// Returns source for the script with given id.
2448    ///
2449    /// Debugger must be enabled.
2450    pub async fn get_script_source(&self, script_id: impl Into<String>) -> Result<String> {
2451        Ok(self
2452            .execute(GetScriptSourceParams::new(ScriptId::from(script_id.into())))
2453            .await?
2454            .result
2455            .script_source)
2456    }
2457}
2458
2459impl From<Arc<PageInner>> for Page {
2460    fn from(inner: Arc<PageInner>) -> Self {
2461        Self { inner }
2462    }
2463}
2464
2465pub(crate) fn validate_cookie_url(url: &str) -> Result<()> {
2466    if url.starts_with("data:") {
2467        Err(CdpError::msg("Data URL page can not have cookie"))
2468    } else if url == "about:blank" {
2469        Err(CdpError::msg("Blank page can not have cookie"))
2470    } else {
2471        Ok(())
2472    }
2473}
2474
2475/// Page screenshot parameters with extra options.
2476#[derive(Debug, Default)]
2477pub struct ScreenshotParams {
2478    /// Chrome DevTools Protocol screenshot options.
2479    pub cdp_params: CaptureScreenshotParams,
2480    /// Take full page screenshot.
2481    pub full_page: Option<bool>,
2482    /// Make the background transparent (png only).
2483    pub omit_background: Option<bool>,
2484}
2485
2486impl ScreenshotParams {
2487    pub fn builder() -> ScreenshotParamsBuilder {
2488        Default::default()
2489    }
2490
2491    pub(crate) fn full_page(&self) -> bool {
2492        self.full_page.unwrap_or(false)
2493    }
2494
2495    pub(crate) fn omit_background(&self) -> bool {
2496        self.omit_background.unwrap_or(false)
2497            && self
2498                .cdp_params
2499                .format
2500                .as_ref()
2501                .map_or(true, |f| f == &CaptureScreenshotFormat::Png)
2502    }
2503}
2504
2505/// Page screenshot parameters builder with extra options.
2506#[derive(Debug, Default)]
2507pub struct ScreenshotParamsBuilder {
2508    /// The cdp params.
2509    cdp_params: CaptureScreenshotParams,
2510    /// Full page screenshot?
2511    full_page: Option<bool>,
2512    /// Hide the background.
2513    omit_background: Option<bool>,
2514}
2515
2516impl ScreenshotParamsBuilder {
2517    /// Image compression format (defaults to png).
2518    pub fn format(mut self, format: impl Into<CaptureScreenshotFormat>) -> Self {
2519        self.cdp_params.format = Some(format.into());
2520        self
2521    }
2522
2523    /// Compression quality from range [0..100] (jpeg only).
2524    pub fn quality(mut self, quality: impl Into<i64>) -> Self {
2525        self.cdp_params.quality = Some(quality.into());
2526        self
2527    }
2528
2529    /// Capture the screenshot of a given region only.
2530    pub fn clip(mut self, clip: impl Into<Viewport>) -> Self {
2531        self.cdp_params.clip = Some(clip.into());
2532        self
2533    }
2534
2535    /// Capture the screenshot from the surface, rather than the view (defaults to true).
2536    pub fn from_surface(mut self, from_surface: impl Into<bool>) -> Self {
2537        self.cdp_params.from_surface = Some(from_surface.into());
2538        self
2539    }
2540
2541    /// Capture the screenshot beyond the viewport (defaults to false).
2542    pub fn capture_beyond_viewport(mut self, capture_beyond_viewport: impl Into<bool>) -> Self {
2543        self.cdp_params.capture_beyond_viewport = Some(capture_beyond_viewport.into());
2544        self
2545    }
2546
2547    /// Full page screen capture.
2548    pub fn full_page(mut self, full_page: impl Into<bool>) -> Self {
2549        self.full_page = Some(full_page.into());
2550        self
2551    }
2552
2553    /// Make the background transparent (png only)
2554    pub fn omit_background(mut self, omit_background: impl Into<bool>) -> Self {
2555        self.omit_background = Some(omit_background.into());
2556        self
2557    }
2558
2559    pub fn build(self) -> ScreenshotParams {
2560        ScreenshotParams {
2561            cdp_params: self.cdp_params,
2562            full_page: self.full_page,
2563            omit_background: self.omit_background,
2564        }
2565    }
2566}
2567
2568impl From<CaptureScreenshotParams> for ScreenshotParams {
2569    fn from(cdp_params: CaptureScreenshotParams) -> Self {
2570        Self {
2571            cdp_params,
2572            ..Default::default()
2573        }
2574    }
2575}
2576
2577#[derive(Debug, Clone, Copy, Default)]
2578pub enum MediaTypeParams {
2579    /// Default CSS media type behavior for page and print
2580    #[default]
2581    Null,
2582    /// Force screen CSS media type for page and print
2583    Screen,
2584    /// Force print CSS media type for page and print
2585    Print,
2586}
2587impl From<MediaTypeParams> for String {
2588    fn from(media_type: MediaTypeParams) -> Self {
2589        match media_type {
2590            MediaTypeParams::Null => "null".to_string(),
2591            MediaTypeParams::Screen => "screen".to_string(),
2592            MediaTypeParams::Print => "print".to_string(),
2593        }
2594    }
2595}