chromiumoxide/
page.rs

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