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