Skip to main content

chromiumoxide/
page.rs

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