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