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