chromiumoxide/
page.rs

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