chromiumoxide/
page.rs

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