chromiumoxide/
page.rs

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