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