chromiumoxide/
page.rs

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