chromiumoxide/
page.rs

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