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        if let Some(err) = res.result.error_text {
397            return Err(CdpError::ChromeMessage(err));
398        }
399
400        Ok(self)
401    }
402
403    /// The identifier of the `Target` this page belongs to
404    pub fn target_id(&self) -> &TargetId {
405        self.inner.target_id()
406    }
407
408    /// The identifier of the `Session` target of this page is attached to
409    pub fn session_id(&self) -> &SessionId {
410        self.inner.session_id()
411    }
412
413    /// The identifier of the `Session` target of this page is attached to
414    pub fn opener_id(&self) -> &Option<TargetId> {
415        self.inner.opener_id()
416    }
417
418    /// Returns the name of the frame
419    pub async fn frame_name(&self, frame_id: FrameId) -> Result<Option<String>> {
420        let (tx, rx) = oneshot_channel();
421        self.inner
422            .sender()
423            .clone()
424            .send(TargetMessage::Name(GetName {
425                frame_id: Some(frame_id),
426                tx,
427            }))
428            .await?;
429        Ok(rx.await?)
430    }
431
432    pub async fn authenticate(&self, credentials: Credentials) -> Result<()> {
433        self.inner
434            .sender()
435            .clone()
436            .send(TargetMessage::Authenticate(credentials))
437            .await?;
438
439        Ok(())
440    }
441
442    /// Returns the current url of the page
443    pub async fn url(&self) -> Result<Option<String>> {
444        let (tx, rx) = oneshot_channel();
445        self.inner
446            .sender()
447            .clone()
448            .send(TargetMessage::Url(GetUrl::new(tx)))
449            .await?;
450        Ok(rx.await?)
451    }
452
453    /// Returns the current url of the frame
454    pub async fn frame_url(&self, frame_id: FrameId) -> Result<Option<String>> {
455        let (tx, rx) = oneshot_channel();
456        self.inner
457            .sender()
458            .clone()
459            .send(TargetMessage::Url(GetUrl {
460                frame_id: Some(frame_id),
461                tx,
462            }))
463            .await?;
464        Ok(rx.await?)
465    }
466
467    /// Returns the parent id of the frame
468    pub async fn frame_parent(&self, frame_id: FrameId) -> Result<Option<FrameId>> {
469        let (tx, rx) = oneshot_channel();
470        self.inner
471            .sender()
472            .clone()
473            .send(TargetMessage::Parent(GetParent { frame_id, tx }))
474            .await?;
475        Ok(rx.await?)
476    }
477
478    /// Return the main frame of the page
479    pub async fn mainframe(&self) -> Result<Option<FrameId>> {
480        let (tx, rx) = oneshot_channel();
481        self.inner
482            .sender()
483            .clone()
484            .send(TargetMessage::MainFrame(tx))
485            .await?;
486        Ok(rx.await?)
487    }
488
489    /// Return the frames of the page
490    pub async fn frames(&self) -> Result<Vec<FrameId>> {
491        let (tx, rx) = oneshot_channel();
492        self.inner
493            .sender()
494            .clone()
495            .send(TargetMessage::AllFrames(tx))
496            .await?;
497        Ok(rx.await?)
498    }
499
500    /// Allows overriding user agent with the given string.
501    pub async fn set_user_agent(
502        &self,
503        params: impl Into<SetUserAgentOverrideParams>,
504    ) -> Result<&Self> {
505        self.execute(params.into()).await?;
506        Ok(self)
507    }
508
509    /// Returns the user agent of the browser
510    pub async fn user_agent(&self) -> Result<String> {
511        Ok(self.inner.version().await?.user_agent)
512    }
513
514    /// Returns the root DOM node (and optionally the subtree) of the page.
515    ///
516    /// # Note: This does not return the actual HTML document of the page. To
517    /// retrieve the HTML content of the page see `Page::content`.
518    pub async fn get_document(&self) -> Result<Node> {
519        let mut cmd = GetDocumentParams::default();
520        cmd.depth = Some(-1);
521        cmd.pierce = Some(true);
522
523        let resp = self.execute(cmd).await?;
524
525        Ok(resp.result.root)
526    }
527
528    /// Returns the first element in the document which matches the given CSS
529    /// selector.
530    ///
531    /// Execute a query selector on the document's node.
532    pub async fn find_element(&self, selector: impl Into<String>) -> Result<Element> {
533        let root = self.get_document().await?.node_id;
534        let node_id = self.inner.find_element(selector, root).await?;
535        Element::new(Arc::clone(&self.inner), node_id).await
536    }
537
538    /// Returns the outer HTML of the page.
539    pub async fn outer_html(&self) -> Result<String> {
540        let root = self.get_document().await?;
541        let element = Element::new(Arc::clone(&self.inner), root.node_id).await?;
542        self.inner
543            .outer_html(
544                element.remote_object_id,
545                element.node_id,
546                element.backend_node_id,
547            )
548            .await
549    }
550
551    /// Return all `Element`s in the document that match the given selector
552    pub async fn find_elements(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
553        let root = self.get_document().await?.node_id;
554        let node_ids = self.inner.find_elements(selector, root).await?;
555        Element::from_nodes(&self.inner, &node_ids).await
556    }
557
558    /// Returns the first element in the document which matches the given xpath
559    /// selector.
560    ///
561    /// Execute a xpath selector on the document's node.
562    pub async fn find_xpath(&self, selector: impl Into<String>) -> Result<Element> {
563        self.get_document().await?;
564        let node_id = self.inner.find_xpaths(selector).await?[0];
565        Element::new(Arc::clone(&self.inner), node_id).await
566    }
567
568    /// Return all `Element`s in the document that match the given xpath selector
569    pub async fn find_xpaths(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
570        self.get_document().await?;
571        let node_ids = self.inner.find_xpaths(selector).await?;
572        Element::from_nodes(&self.inner, &node_ids).await
573    }
574
575    /// Describes node given its id
576    pub async fn describe_node(&self, node_id: NodeId) -> Result<Node> {
577        let resp = self
578            .execute(DescribeNodeParams::builder().node_id(node_id).build())
579            .await?;
580        Ok(resp.result.node)
581    }
582
583    /// Tries to close page, running its beforeunload hooks, if any.
584    /// Calls Page.close with [`CloseParams`]
585    pub async fn close(self) -> Result<()> {
586        self.execute(CloseParams::default()).await?;
587        Ok(())
588    }
589
590    /// Performs a single mouse click event at the point's location.
591    ///
592    /// This scrolls the point into view first, then executes a
593    /// `DispatchMouseEventParams` command of type `MouseLeft` with
594    /// `MousePressed` as single click and then releases the mouse with an
595    /// additional `DispatchMouseEventParams` of type `MouseLeft` with
596    /// `MouseReleased`
597    ///
598    /// Bear in mind that if `click()` triggers a navigation the new page is not
599    /// immediately loaded when `click()` resolves. To wait until navigation is
600    /// finished an additional `wait_for_navigation()` is required:
601    ///
602    /// # Example
603    ///
604    /// Trigger a navigation and wait until the triggered navigation is finished
605    ///
606    /// ```no_run
607    /// # use chromiumoxide::page::Page;
608    /// # use chromiumoxide::error::Result;
609    /// # use chromiumoxide::layout::Point;
610    /// # async fn demo(page: Page, point: Point) -> Result<()> {
611    ///     let html = page.click(point).await?.wait_for_navigation().await?.content();
612    ///     # Ok(())
613    /// # }
614    /// ```
615    ///
616    /// # Example
617    ///
618    /// Perform custom click
619    ///
620    /// ```no_run
621    /// # use chromiumoxide::page::Page;
622    /// # use chromiumoxide::error::Result;
623    /// # use chromiumoxide::layout::Point;
624    /// # use chromiumoxide_cdp::cdp::browser_protocol::input::{DispatchMouseEventParams, MouseButton, DispatchMouseEventType};
625    /// # async fn demo(page: Page, point: Point) -> Result<()> {
626    ///      // double click
627    ///      let cmd = DispatchMouseEventParams::builder()
628    ///             .x(point.x)
629    ///             .y(point.y)
630    ///             .button(MouseButton::Left)
631    ///             .click_count(2);
632    ///
633    ///         page.move_mouse(point).await?.execute(
634    ///             cmd.clone()
635    ///                 .r#type(DispatchMouseEventType::MousePressed)
636    ///                 .build()
637    ///                 .unwrap(),
638    ///         )
639    ///         .await?;
640    ///
641    ///         page.execute(
642    ///             cmd.r#type(DispatchMouseEventType::MouseReleased)
643    ///                 .build()
644    ///                 .unwrap(),
645    ///         )
646    ///         .await?;
647    ///
648    ///     # Ok(())
649    /// # }
650    /// ```
651    pub async fn click(&self, point: Point) -> Result<&Self> {
652        self.inner.click(point).await?;
653        Ok(self)
654    }
655
656    /// Dispatches a `mousemove` event and moves the mouse to the position of
657    /// the `point` where `Point.x` is the horizontal position of the mouse and
658    /// `Point.y` the vertical position of the mouse.
659    pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
660        self.inner.move_mouse(point).await?;
661        Ok(self)
662    }
663
664    /// Take a screenshot of the current page
665    pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
666        self.inner.screenshot(params).await
667    }
668
669    /// Take a screenshot of the current page
670    pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
671        self.inner.print_to_pdf(params).await
672    }
673
674    /// Save a screenshot of the page
675    ///
676    /// # Example save a png file of a website
677    ///
678    /// ```no_run
679    /// # use chromiumoxide::page::{Page, ScreenshotParams};
680    /// # use chromiumoxide::error::Result;
681    /// # use chromiumoxide_cdp::cdp::browser_protocol::page::CaptureScreenshotFormat;
682    /// # async fn demo(page: Page) -> Result<()> {
683    ///         page.goto("http://example.com")
684    ///             .await?
685    ///             .save_screenshot(
686    ///             ScreenshotParams::builder()
687    ///                 .format(CaptureScreenshotFormat::Png)
688    ///                 .full_page(true)
689    ///                 .omit_background(true)
690    ///                 .build(),
691    ///             "example.png",
692    ///             )
693    ///             .await?;
694    ///     # Ok(())
695    /// # }
696    /// ```
697    pub async fn save_screenshot(
698        &self,
699        params: impl Into<ScreenshotParams>,
700        output: impl AsRef<Path>,
701    ) -> Result<Vec<u8>> {
702        let img = self.screenshot(params).await?;
703        utils::write(output.as_ref(), &img).await?;
704        Ok(img)
705    }
706
707    /// Print the current page as pdf.
708    ///
709    /// See [`PrintToPdfParams`]
710    ///
711    /// # Note Generating a pdf is currently only supported in Chrome headless.
712    pub async fn pdf(&self, params: PrintToPdfParams) -> Result<Vec<u8>> {
713        let res = self.execute(params).await?;
714        Ok(utils::base64::decode(&res.data)?)
715    }
716
717    /// Save the current page as pdf as file to the `output` path and return the
718    /// pdf contents.
719    ///
720    /// # Note Generating a pdf is currently only supported in Chrome headless.
721    pub async fn save_pdf(
722        &self,
723        opts: PrintToPdfParams,
724        output: impl AsRef<Path>,
725    ) -> Result<Vec<u8>> {
726        let pdf = self.pdf(opts).await?;
727        utils::write(output.as_ref(), &pdf).await?;
728        Ok(pdf)
729    }
730
731    /// Brings page to front (activates tab)
732    pub async fn bring_to_front(&self) -> Result<&Self> {
733        self.execute(BringToFrontParams::default()).await?;
734        Ok(self)
735    }
736
737    /// Emulates the given media type or media feature for CSS media queries
738    pub async fn emulate_media_features(&self, features: Vec<MediaFeature>) -> Result<&Self> {
739        self.execute(SetEmulatedMediaParams::builder().features(features).build())
740            .await?;
741        Ok(self)
742    }
743
744    /// Changes the CSS media type of the page
745    // Based on https://pptr.dev/api/puppeteer.page.emulatemediatype
746    pub async fn emulate_media_type(
747        &self,
748        media_type: impl Into<MediaTypeParams>,
749    ) -> Result<&Self> {
750        self.execute(
751            SetEmulatedMediaParams::builder()
752                .media(media_type.into())
753                .build(),
754        )
755        .await?;
756        Ok(self)
757    }
758
759    /// Overrides default host system timezone
760    pub async fn emulate_timezone(
761        &self,
762        timezoune_id: impl Into<SetTimezoneOverrideParams>,
763    ) -> Result<&Self> {
764        self.execute(timezoune_id.into()).await?;
765        Ok(self)
766    }
767
768    /// Overrides default host system locale with the specified one
769    pub async fn emulate_locale(
770        &self,
771        locale: impl Into<SetLocaleOverrideParams>,
772    ) -> Result<&Self> {
773        self.execute(locale.into()).await?;
774        Ok(self)
775    }
776
777    /// Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position unavailable.
778    pub async fn emulate_geolocation(
779        &self,
780        geolocation: impl Into<SetGeolocationOverrideParams>,
781    ) -> Result<&Self> {
782        self.execute(geolocation.into()).await?;
783        Ok(self)
784    }
785
786    /// Reloads given page
787    ///
788    /// To reload ignoring cache run:
789    /// ```no_run
790    /// # use chromiumoxide::page::Page;
791    /// # use chromiumoxide::error::Result;
792    /// # use chromiumoxide_cdp::cdp::browser_protocol::page::ReloadParams;
793    /// # async fn demo(page: Page) -> Result<()> {
794    ///     page.execute(ReloadParams::builder().ignore_cache(true).build()).await?;
795    ///     page.wait_for_navigation().await?;
796    ///     # Ok(())
797    /// # }
798    /// ```
799    pub async fn reload(&self) -> Result<&Self> {
800        self.execute(ReloadParams::default()).await?;
801        self.wait_for_navigation().await
802    }
803
804    /// Enables log domain. Enabled by default.
805    ///
806    /// Sends the entries collected so far to the client by means of the
807    /// entryAdded notification.
808    ///
809    /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-enable
810    pub async fn enable_log(&self) -> Result<&Self> {
811        self.execute(browser_protocol::log::EnableParams::default())
812            .await?;
813        Ok(self)
814    }
815
816    /// Disables log domain
817    ///
818    /// Prevents further log entries from being reported to the client
819    ///
820    /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-disable
821    pub async fn disable_log(&self) -> Result<&Self> {
822        self.execute(browser_protocol::log::DisableParams::default())
823            .await?;
824        Ok(self)
825    }
826
827    /// Enables runtime domain. Activated by default.
828    pub async fn enable_runtime(&self) -> Result<&Self> {
829        self.execute(js_protocol::runtime::EnableParams::default())
830            .await?;
831        Ok(self)
832    }
833
834    /// Disables runtime domain
835    pub async fn disable_runtime(&self) -> Result<&Self> {
836        self.execute(js_protocol::runtime::DisableParams::default())
837            .await?;
838        Ok(self)
839    }
840
841    /// Enables Debugger. Enabled by default.
842    pub async fn enable_debugger(&self) -> Result<&Self> {
843        self.execute(js_protocol::debugger::EnableParams::default())
844            .await?;
845        Ok(self)
846    }
847
848    /// Disables Debugger.
849    pub async fn disable_debugger(&self) -> Result<&Self> {
850        self.execute(js_protocol::debugger::DisableParams::default())
851            .await?;
852        Ok(self)
853    }
854
855    // Enables DOM agent
856    pub async fn enable_dom(&self) -> Result<&Self> {
857        self.execute(browser_protocol::dom::EnableParams::default())
858            .await?;
859        Ok(self)
860    }
861
862    // Disables DOM agent
863    pub async fn disable_dom(&self) -> Result<&Self> {
864        self.execute(browser_protocol::dom::DisableParams::default())
865            .await?;
866        Ok(self)
867    }
868
869    // Enables the CSS agent
870    pub async fn enable_css(&self) -> Result<&Self> {
871        self.execute(browser_protocol::css::EnableParams::default())
872            .await?;
873        Ok(self)
874    }
875
876    // Disables the CSS agent
877    pub async fn disable_css(&self) -> Result<&Self> {
878        self.execute(browser_protocol::css::DisableParams::default())
879            .await?;
880        Ok(self)
881    }
882
883    /// Activates (focuses) the target.
884    pub async fn activate(&self) -> Result<&Self> {
885        self.inner.activate().await?;
886        Ok(self)
887    }
888
889    /// Returns all cookies that match the tab's current URL.
890    pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
891        Ok(self
892            .execute(GetCookiesParams::default())
893            .await?
894            .result
895            .cookies)
896    }
897
898    /// Set a single cookie
899    ///
900    /// This fails if the cookie's url or if not provided, the page's url is
901    /// `about:blank` or a `data:` url.
902    ///
903    /// # Example
904    /// ```no_run
905    /// # use chromiumoxide::page::Page;
906    /// # use chromiumoxide::error::Result;
907    /// # use chromiumoxide_cdp::cdp::browser_protocol::network::CookieParam;
908    /// # async fn demo(page: Page) -> Result<()> {
909    ///     page.set_cookie(CookieParam::new("Cookie-name", "Cookie-value")).await?;
910    ///     # Ok(())
911    /// # }
912    /// ```
913    pub async fn set_cookie(&self, cookie: impl Into<CookieParam>) -> Result<&Self> {
914        let mut cookie = cookie.into();
915        if let Some(url) = cookie.url.as_ref() {
916            validate_cookie_url(url)?;
917        } else {
918            let url = self
919                .url()
920                .await?
921                .ok_or_else(|| CdpError::msg("Page url not found"))?;
922            validate_cookie_url(&url)?;
923            if url.starts_with("http") {
924                cookie.url = Some(url);
925            }
926        }
927        self.execute(DeleteCookiesParams::from_cookie(&cookie))
928            .await?;
929        self.execute(SetCookiesParams::new(vec![cookie])).await?;
930        Ok(self)
931    }
932
933    /// Set all the cookies
934    pub async fn set_cookies(&self, mut cookies: Vec<CookieParam>) -> Result<&Self> {
935        let url = self
936            .url()
937            .await?
938            .ok_or_else(|| CdpError::msg("Page url not found"))?;
939        let is_http = url.starts_with("http");
940        if !is_http {
941            validate_cookie_url(&url)?;
942        }
943
944        for cookie in &mut cookies {
945            if let Some(url) = cookie.url.as_ref() {
946                validate_cookie_url(url)?;
947            } else if is_http {
948                cookie.url = Some(url.clone());
949            }
950        }
951        self.delete_cookies_unchecked(cookies.iter().map(DeleteCookiesParams::from_cookie))
952            .await?;
953
954        self.execute(SetCookiesParams::new(cookies)).await?;
955        Ok(self)
956    }
957
958    /// Delete a single cookie
959    pub async fn delete_cookie(&self, cookie: impl Into<DeleteCookiesParams>) -> Result<&Self> {
960        let mut cookie = cookie.into();
961        if cookie.url.is_none() {
962            let url = self
963                .url()
964                .await?
965                .ok_or_else(|| CdpError::msg("Page url not found"))?;
966            if url.starts_with("http") {
967                cookie.url = Some(url);
968            }
969        }
970        self.execute(cookie).await?;
971        Ok(self)
972    }
973
974    /// Delete all the cookies
975    pub async fn delete_cookies(&self, mut cookies: Vec<DeleteCookiesParams>) -> Result<&Self> {
976        let mut url: Option<(String, bool)> = None;
977        for cookie in &mut cookies {
978            if cookie.url.is_none() {
979                if let Some((url, is_http)) = url.as_ref() {
980                    if *is_http {
981                        cookie.url = Some(url.clone())
982                    }
983                } else {
984                    let page_url = self
985                        .url()
986                        .await?
987                        .ok_or_else(|| CdpError::msg("Page url not found"))?;
988                    let is_http = page_url.starts_with("http");
989                    if is_http {
990                        cookie.url = Some(page_url.clone())
991                    }
992                    url = Some((page_url, is_http));
993                }
994            }
995        }
996        self.delete_cookies_unchecked(cookies.into_iter()).await?;
997        Ok(self)
998    }
999
1000    /// Convenience method that prevents another channel roundtrip to get the
1001    /// url and validate it
1002    async fn delete_cookies_unchecked(
1003        &self,
1004        cookies: impl Iterator<Item = DeleteCookiesParams>,
1005    ) -> Result<&Self> {
1006        // NOTE: the buffer size is arbitrary
1007        let mut cmds = stream::iter(cookies.into_iter().map(|cookie| self.execute(cookie)))
1008            .buffer_unordered(5);
1009        while let Some(resp) = cmds.next().await {
1010            resp?;
1011        }
1012        Ok(self)
1013    }
1014
1015    /// Returns the title of the document.
1016    pub async fn get_title(&self) -> Result<Option<String>> {
1017        let result = self.evaluate("document.title").await?;
1018
1019        let title: String = result.into_value()?;
1020
1021        if title.is_empty() {
1022            Ok(None)
1023        } else {
1024            Ok(Some(title))
1025        }
1026    }
1027
1028    /// Retrieve current values of run-time metrics.
1029    pub async fn metrics(&self) -> Result<Vec<Metric>> {
1030        Ok(self
1031            .execute(GetMetricsParams::default())
1032            .await?
1033            .result
1034            .metrics)
1035    }
1036
1037    /// Returns metrics relating to the layout of the page
1038    pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
1039        self.inner.layout_metrics().await
1040    }
1041
1042    /// This evaluates strictly as expression.
1043    ///
1044    /// Same as `Page::evaluate` but no fallback or any attempts to detect
1045    /// whether the expression is actually a function. However you can
1046    /// submit a function evaluation string:
1047    ///
1048    /// # Example Evaluate function call as expression
1049    ///
1050    /// This will take the arguments `(1,2)` and will call the function
1051    ///
1052    /// ```no_run
1053    /// # use chromiumoxide::page::Page;
1054    /// # use chromiumoxide::error::Result;
1055    /// # async fn demo(page: Page) -> Result<()> {
1056    ///     let sum: usize = page
1057    ///         .evaluate_expression("((a,b) => {return a + b;})(1,2)")
1058    ///         .await?
1059    ///         .into_value()?;
1060    ///     assert_eq!(sum, 3);
1061    ///     # Ok(())
1062    /// # }
1063    /// ```
1064    pub async fn evaluate_expression(
1065        &self,
1066        evaluate: impl Into<EvaluateParams>,
1067    ) -> Result<EvaluationResult> {
1068        self.inner.evaluate_expression(evaluate).await
1069    }
1070
1071    /// Evaluates an expression or function in the page's context and returns
1072    /// the result.
1073    ///
1074    /// In contrast to `Page::evaluate_expression` this is capable of handling
1075    /// function calls and expressions alike. This takes anything that is
1076    /// `Into<Evaluation>`. When passing a `String` or `str`, this will try to
1077    /// detect whether it is a function or an expression. JS function detection
1078    /// is not very sophisticated but works for general cases (`(async)
1079    /// functions` and arrow functions). If you want a string statement
1080    /// specifically evaluated as expression or function either use the
1081    /// designated functions `Page::evaluate_function` or
1082    /// `Page::evaluate_expression` or use the proper parameter type for
1083    /// `Page::execute`:  `EvaluateParams` for strict expression evaluation or
1084    /// `CallFunctionOnParams` for strict function evaluation.
1085    ///
1086    /// If you don't trust the js function detection and are not sure whether
1087    /// the statement is an expression or of type function (arrow functions: `()
1088    /// => {..}`), you should pass it as `EvaluateParams` and set the
1089    /// `EvaluateParams::eval_as_function_fallback` option. This will first
1090    /// try to evaluate it as expression and if the result comes back
1091    /// evaluated as `RemoteObjectType::Function` it will submit the
1092    /// statement again but as function:
1093    ///
1094    ///  # Example Evaluate function statement as expression with fallback
1095    /// option
1096    ///
1097    /// ```no_run
1098    /// # use chromiumoxide::page::Page;
1099    /// # use chromiumoxide::error::Result;
1100    /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{EvaluateParams, RemoteObjectType};
1101    /// # async fn demo(page: Page) -> Result<()> {
1102    ///     let eval = EvaluateParams::builder().expression("() => {return 42;}");
1103    ///     // this will fail because the `EvaluationResult` returned by the browser will be
1104    ///     // of type `Function`
1105    ///     let result = page
1106    ///                 .evaluate(eval.clone().build().unwrap())
1107    ///                 .await?;
1108    ///     assert_eq!(result.object().r#type, RemoteObjectType::Function);
1109    ///     assert!(result.into_value::<usize>().is_err());
1110    ///
1111    ///     // This will also fail on the first try but it detects that the browser evaluated the
1112    ///     // statement as function and then evaluate it again but as function
1113    ///     let sum: usize = page
1114    ///         .evaluate(eval.eval_as_function_fallback(true).build().unwrap())
1115    ///         .await?
1116    ///         .into_value()?;
1117    ///     # Ok(())
1118    /// # }
1119    /// ```
1120    ///
1121    /// # Example Evaluate basic expression
1122    /// ```no_run
1123    /// # use chromiumoxide::page::Page;
1124    /// # use chromiumoxide::error::Result;
1125    /// # async fn demo(page: Page) -> Result<()> {
1126    ///     let sum:usize = page.evaluate("1 + 2").await?.into_value()?;
1127    ///     assert_eq!(sum, 3);
1128    ///     # Ok(())
1129    /// # }
1130    /// ```
1131    pub async fn evaluate(&self, evaluate: impl Into<Evaluation>) -> Result<EvaluationResult> {
1132        match evaluate.into() {
1133            Evaluation::Expression(mut expr) => {
1134                if expr.context_id.is_none() {
1135                    expr.context_id = self.execution_context().await?;
1136                }
1137                let fallback = expr.eval_as_function_fallback.and_then(|p| {
1138                    if p {
1139                        Some(expr.clone())
1140                    } else {
1141                        None
1142                    }
1143                });
1144                let res = self.evaluate_expression(expr).await?;
1145
1146                if res.object().r#type == RemoteObjectType::Function {
1147                    // expression was actually a function
1148                    if let Some(fallback) = fallback {
1149                        return self.evaluate_function(fallback).await;
1150                    }
1151                }
1152                Ok(res)
1153            }
1154            Evaluation::Function(fun) => Ok(self.evaluate_function(fun).await?),
1155        }
1156    }
1157
1158    /// Eexecutes a function withinthe page's context and returns the result.
1159    ///
1160    /// # Example Evaluate a promise
1161    /// This will wait until the promise resolves and then returns the result.
1162    /// ```no_run
1163    /// # use chromiumoxide::page::Page;
1164    /// # use chromiumoxide::error::Result;
1165    /// # async fn demo(page: Page) -> Result<()> {
1166    ///     let sum:usize = page.evaluate_function("() => Promise.resolve(1 + 2)").await?.into_value()?;
1167    ///     assert_eq!(sum, 3);
1168    ///     # Ok(())
1169    /// # }
1170    /// ```
1171    ///
1172    /// # Example Evaluate an async function
1173    /// ```no_run
1174    /// # use chromiumoxide::page::Page;
1175    /// # use chromiumoxide::error::Result;
1176    /// # async fn demo(page: Page) -> Result<()> {
1177    ///     let val:usize = page.evaluate_function("async function() {return 42;}").await?.into_value()?;
1178    ///     assert_eq!(val, 42);
1179    ///     # Ok(())
1180    /// # }
1181    /// ```
1182    /// # Example Construct a function call
1183    ///
1184    /// ```no_run
1185    /// # use chromiumoxide::page::Page;
1186    /// # use chromiumoxide::error::Result;
1187    /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{CallFunctionOnParams, CallArgument};
1188    /// # async fn demo(page: Page) -> Result<()> {
1189    ///     let call = CallFunctionOnParams::builder()
1190    ///            .function_declaration(
1191    ///                "(a,b) => { return a + b;}"
1192    ///            )
1193    ///            .argument(
1194    ///                CallArgument::builder()
1195    ///                    .value(serde_json::json!(1))
1196    ///                    .build(),
1197    ///            )
1198    ///            .argument(
1199    ///                CallArgument::builder()
1200    ///                    .value(serde_json::json!(2))
1201    ///                    .build(),
1202    ///            )
1203    ///            .build()
1204    ///            .unwrap();
1205    ///     let sum:usize = page.evaluate_function(call).await?.into_value()?;
1206    ///     assert_eq!(sum, 3);
1207    ///     # Ok(())
1208    /// # }
1209    /// ```
1210    pub async fn evaluate_function(
1211        &self,
1212        evaluate: impl Into<CallFunctionOnParams>,
1213    ) -> Result<EvaluationResult> {
1214        self.inner.evaluate_function(evaluate).await
1215    }
1216
1217    /// Returns the default execution context identifier of this page that
1218    /// represents the context for JavaScript execution.
1219    pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
1220        self.inner.execution_context().await
1221    }
1222
1223    /// Returns the secondary execution context identifier of this page that
1224    /// represents the context for JavaScript execution for manipulating the
1225    /// DOM.
1226    ///
1227    /// See `Page::set_contents`
1228    pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
1229        self.inner.secondary_execution_context().await
1230    }
1231
1232    pub async fn frame_execution_context(
1233        &self,
1234        frame_id: FrameId,
1235    ) -> Result<Option<ExecutionContextId>> {
1236        self.inner.frame_execution_context(frame_id).await
1237    }
1238
1239    pub async fn frame_secondary_execution_context(
1240        &self,
1241        frame_id: FrameId,
1242    ) -> Result<Option<ExecutionContextId>> {
1243        self.inner.frame_secondary_execution_context(frame_id).await
1244    }
1245
1246    /// Evaluates given script in every frame upon creation (before loading
1247    /// frame's scripts)
1248    pub async fn evaluate_on_new_document(
1249        &self,
1250        script: impl Into<AddScriptToEvaluateOnNewDocumentParams>,
1251    ) -> Result<ScriptIdentifier> {
1252        Ok(self.execute(script.into()).await?.result.identifier)
1253    }
1254
1255    /// Set the content of the frame.
1256    ///
1257    /// # Example
1258    /// ```no_run
1259    /// # use chromiumoxide::page::Page;
1260    /// # use chromiumoxide::error::Result;
1261    /// # async fn demo(page: Page) -> Result<()> {
1262    ///     page.set_content("<body>
1263    ///  <h1>This was set via chromiumoxide</h1>
1264    ///  </body>").await?;
1265    ///     # Ok(())
1266    /// # }
1267    /// ```
1268    pub async fn set_content(&self, html: impl AsRef<str>) -> Result<&Self> {
1269        let mut call = CallFunctionOnParams::builder()
1270            .function_declaration(
1271                "(html) => {
1272            document.open();
1273            document.write(html);
1274            document.close();
1275        }",
1276            )
1277            .argument(
1278                CallArgument::builder()
1279                    .value(serde_json::json!(html.as_ref()))
1280                    .build(),
1281            )
1282            .build()
1283            .unwrap();
1284
1285        call.execution_context_id = self
1286            .inner
1287            .execution_context_for_world(None, DOMWorldKind::Secondary)
1288            .await?;
1289
1290        self.evaluate_function(call).await?;
1291        // relying that document.open() will reset frame lifecycle with "init"
1292        // lifecycle event. @see https://crrev.com/608658
1293        self.wait_for_navigation().await
1294    }
1295
1296    /// Returns the HTML content of the page.
1297    pub async fn content(&self) -> Result<String> {
1298        Ok(self.evaluate(OUTER_HTML).await?.into_value()?)
1299    }
1300
1301    #[cfg(feature = "bytes")]
1302    /// Returns the HTML content of the page
1303    pub async fn content_bytes(&self) -> Result<bytes::Bytes> {
1304        Ok(self.evaluate(OUTER_HTML).await?.into_value()?)
1305    }
1306
1307    #[cfg(feature = "bytes")]
1308    /// Returns the HTML outer html of the page
1309    pub async fn outer_html_bytes(&self) -> Result<bytes::Bytes> {
1310        Ok(self.outer_html().await?.into())
1311    }
1312
1313    /// Returns source for the script with given id.
1314    ///
1315    /// Debugger must be enabled.
1316    pub async fn get_script_source(&self, script_id: impl Into<String>) -> Result<String> {
1317        Ok(self
1318            .execute(GetScriptSourceParams::new(ScriptId::from(script_id.into())))
1319            .await?
1320            .result
1321            .script_source)
1322    }
1323}
1324
1325impl From<Arc<PageInner>> for Page {
1326    fn from(inner: Arc<PageInner>) -> Self {
1327        Self { inner }
1328    }
1329}
1330
1331pub(crate) fn validate_cookie_url(url: &str) -> Result<()> {
1332    if url.starts_with("data:") {
1333        Err(CdpError::msg("Data URL page can not have cookie"))
1334    } else if url == "about:blank" {
1335        Err(CdpError::msg("Blank page can not have cookie"))
1336    } else {
1337        Ok(())
1338    }
1339}
1340
1341/// Page screenshot parameters with extra options.
1342#[derive(Debug, Default)]
1343pub struct ScreenshotParams {
1344    /// Chrome DevTools Protocol screenshot options.
1345    pub cdp_params: CaptureScreenshotParams,
1346    /// Take full page screenshot.
1347    pub full_page: Option<bool>,
1348    /// Make the background transparent (png only).
1349    pub omit_background: Option<bool>,
1350}
1351
1352impl ScreenshotParams {
1353    pub fn builder() -> ScreenshotParamsBuilder {
1354        Default::default()
1355    }
1356
1357    pub(crate) fn full_page(&self) -> bool {
1358        self.full_page.unwrap_or(false)
1359    }
1360
1361    pub(crate) fn omit_background(&self) -> bool {
1362        self.omit_background.unwrap_or(false)
1363            && self
1364                .cdp_params
1365                .format
1366                .as_ref()
1367                .map_or(true, |f| f == &CaptureScreenshotFormat::Png)
1368    }
1369}
1370
1371/// Page screenshot parameters builder with extra options.
1372#[derive(Debug, Default)]
1373pub struct ScreenshotParamsBuilder {
1374    cdp_params: CaptureScreenshotParams,
1375    full_page: Option<bool>,
1376    omit_background: Option<bool>,
1377}
1378
1379impl ScreenshotParamsBuilder {
1380    /// Image compression format (defaults to png).
1381    pub fn format(mut self, format: impl Into<CaptureScreenshotFormat>) -> Self {
1382        self.cdp_params.format = Some(format.into());
1383        self
1384    }
1385
1386    /// Compression quality from range [0..100] (jpeg only).
1387    pub fn quality(mut self, quality: impl Into<i64>) -> Self {
1388        self.cdp_params.quality = Some(quality.into());
1389        self
1390    }
1391
1392    /// Capture the screenshot of a given region only.
1393    pub fn clip(mut self, clip: impl Into<Viewport>) -> Self {
1394        self.cdp_params.clip = Some(clip.into());
1395        self
1396    }
1397
1398    /// Capture the screenshot from the surface, rather than the view (defaults to true).
1399    pub fn from_surface(mut self, from_surface: impl Into<bool>) -> Self {
1400        self.cdp_params.from_surface = Some(from_surface.into());
1401        self
1402    }
1403
1404    /// Capture the screenshot beyond the viewport (defaults to false).
1405    pub fn capture_beyond_viewport(mut self, capture_beyond_viewport: impl Into<bool>) -> Self {
1406        self.cdp_params.capture_beyond_viewport = Some(capture_beyond_viewport.into());
1407        self
1408    }
1409
1410    /// Full page screen capture.
1411    pub fn full_page(mut self, full_page: impl Into<bool>) -> Self {
1412        self.full_page = Some(full_page.into());
1413        self
1414    }
1415
1416    /// Make the background transparent (png only)
1417    pub fn omit_background(mut self, omit_background: impl Into<bool>) -> Self {
1418        self.omit_background = Some(omit_background.into());
1419        self
1420    }
1421
1422    pub fn build(self) -> ScreenshotParams {
1423        ScreenshotParams {
1424            cdp_params: self.cdp_params,
1425            full_page: self.full_page,
1426            omit_background: self.omit_background,
1427        }
1428    }
1429}
1430
1431impl From<CaptureScreenshotParams> for ScreenshotParams {
1432    fn from(cdp_params: CaptureScreenshotParams) -> Self {
1433        Self {
1434            cdp_params,
1435            ..Default::default()
1436        }
1437    }
1438}
1439
1440#[derive(Debug, Clone, Copy, Default)]
1441pub enum MediaTypeParams {
1442    /// Default CSS media type behavior for page and print
1443    #[default]
1444    Null,
1445    /// Force screen CSS media type for page and print
1446    Screen,
1447    /// Force print CSS media type for page and print
1448    Print,
1449}
1450impl From<MediaTypeParams> for String {
1451    fn from(media_type: MediaTypeParams) -> Self {
1452        match media_type {
1453            MediaTypeParams::Null => "null".to_string(),
1454            MediaTypeParams::Screen => "screen".to_string(),
1455            MediaTypeParams::Print => "print".to_string(),
1456        }
1457    }
1458}