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
46pub 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
58const OUTER_HTML: &str = r###"{let rv = ''; if(document.doctype){rv+=new XMLSerializer().serializeToString(document.doctype);} if(document.documentElement){rv+=document.documentElement.outerHTML;} rv}"###;
60const 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
63fn generate_hide_plugins() -> String {
65 format!("{}", PLUGIN_AND_MIMETYPE_SPOOF)
66}
67
68#[derive(PartialEq)]
70enum Tier {
71 Basic,
73 Mid,
75 Full,
77}
78
79fn 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
94pub fn wrap_eval_script(source: &str) -> String {
96 format!(r#"(()=>{{{}}})();"#, source)
97}
98
99impl Page {
100 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 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 pub async fn enable_stealth_mode(&self) -> Result<()> {
142 let _ = self._enable_stealth_mode(None).await;
143
144 Ok(())
145 }
146
147 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 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 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 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 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 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 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 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 pub async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
240 self.command_future(cmd)?.await
241 }
242
243 pub fn command_future<T: Command>(&self, cmd: T) -> Result<CommandFuture<T>> {
245 self.inner.command_future(cmd)
246 }
247
248 pub fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
250 self.inner.http_future(cmd)
251 }
252
253 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 Ok(())
349 }
350
351 pub async fn wait_for_navigation_response(&self) -> Result<ArcHttpRequest> {
357 self.inner.wait_for_navigation().await
358 }
359
360 pub async fn wait_for_navigation(&self) -> Result<&Self> {
362 self.inner.wait_for_navigation().await?;
363 Ok(self)
364 }
365
366 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 pub fn target_id(&self) -> &TargetId {
381 self.inner.target_id()
382 }
383
384 pub fn session_id(&self) -> &SessionId {
386 self.inner.session_id()
387 }
388
389 pub fn opener_id(&self) -> &Option<TargetId> {
391 self.inner.opener_id()
392 }
393
394 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 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 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 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 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 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 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 pub async fn user_agent(&self) -> Result<String> {
487 Ok(self.inner.version().await?.user_agent)
488 }
489
490 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 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 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 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 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 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 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 pub async fn close(self) -> Result<()> {
562 self.execute(CloseParams::default()).await?;
563 Ok(())
564 }
565
566 pub async fn click(&self, point: Point) -> Result<&Self> {
628 self.inner.click(point).await?;
629 Ok(self)
630 }
631
632 pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
636 self.inner.move_mouse(point).await?;
637 Ok(self)
638 }
639
640 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
642 self.inner.screenshot(params).await
643 }
644
645 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 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 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 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 pub async fn bring_to_front(&self) -> Result<&Self> {
709 self.execute(BringToFrontParams::default()).await?;
710 Ok(self)
711 }
712
713 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 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 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 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 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 pub async fn reload(&self) -> Result<&Self> {
776 self.execute(ReloadParams::default()).await?;
777 self.wait_for_navigation().await
778 }
779
780 pub async fn enable_log(&self) -> Result<&Self> {
787 self.execute(browser_protocol::log::EnableParams::default())
788 .await?;
789 Ok(self)
790 }
791
792 pub async fn disable_log(&self) -> Result<&Self> {
798 self.execute(browser_protocol::log::DisableParams::default())
799 .await?;
800 Ok(self)
801 }
802
803 pub async fn enable_runtime(&self) -> Result<&Self> {
805 self.execute(js_protocol::runtime::EnableParams::default())
806 .await?;
807 Ok(self)
808 }
809
810 pub async fn disable_runtime(&self) -> Result<&Self> {
812 self.execute(js_protocol::runtime::DisableParams::default())
813 .await?;
814 Ok(self)
815 }
816
817 pub async fn enable_debugger(&self) -> Result<&Self> {
819 self.execute(js_protocol::debugger::EnableParams::default())
820 .await?;
821 Ok(self)
822 }
823
824 pub async fn disable_debugger(&self) -> Result<&Self> {
826 self.execute(js_protocol::debugger::DisableParams::default())
827 .await?;
828 Ok(self)
829 }
830
831 pub async fn enable_dom(&self) -> Result<&Self> {
833 self.execute(browser_protocol::dom::EnableParams::default())
834 .await?;
835 Ok(self)
836 }
837
838 pub async fn disable_dom(&self) -> Result<&Self> {
840 self.execute(browser_protocol::dom::DisableParams::default())
841 .await?;
842 Ok(self)
843 }
844
845 pub async fn enable_css(&self) -> Result<&Self> {
847 self.execute(browser_protocol::css::EnableParams::default())
848 .await?;
849 Ok(self)
850 }
851
852 pub async fn disable_css(&self) -> Result<&Self> {
854 self.execute(browser_protocol::css::DisableParams::default())
855 .await?;
856 Ok(self)
857 }
858
859 pub async fn activate(&self) -> Result<&Self> {
861 self.inner.activate().await?;
862 Ok(self)
863 }
864
865 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 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 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 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 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 async fn delete_cookies_unchecked(
979 &self,
980 cookies: impl Iterator<Item = DeleteCookiesParams>,
981 ) -> Result<&Self> {
982 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 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 pub async fn metrics(&self) -> Result<Vec<Metric>> {
1006 Ok(self
1007 .execute(GetMetricsParams::default())
1008 .await?
1009 .result
1010 .metrics)
1011 }
1012
1013 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
1015 self.inner.layout_metrics().await
1016 }
1017
1018 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 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 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 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 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
1196 self.inner.execution_context().await
1197 }
1198
1199 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 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 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 self.wait_for_navigation().await
1270 }
1271
1272 pub async fn content(&self) -> Result<String> {
1274 Ok(self.evaluate(OUTER_HTML).await?.into_value()?)
1275 }
1276
1277 #[cfg(feature = "bytes")]
1278 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 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 pub async fn outer_html_bytes(&self) -> Result<Vec<u8>> {
1292 Ok(self.outer_html().await?.into())
1293 }
1294
1295 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#[derive(Debug, Default)]
1325pub struct ScreenshotParams {
1326 pub cdp_params: CaptureScreenshotParams,
1328 pub full_page: Option<bool>,
1330 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#[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 pub fn format(mut self, format: impl Into<CaptureScreenshotFormat>) -> Self {
1364 self.cdp_params.format = Some(format.into());
1365 self
1366 }
1367
1368 pub fn quality(mut self, quality: impl Into<i64>) -> Self {
1370 self.cdp_params.quality = Some(quality.into());
1371 self
1372 }
1373
1374 pub fn clip(mut self, clip: impl Into<Viewport>) -> Self {
1376 self.cdp_params.clip = Some(clip.into());
1377 self
1378 }
1379
1380 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 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 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 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]
1426 Null,
1427 Screen,
1429 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}