chromiumoxide/
page.rs

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