chromiumoxide/
page.rs

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