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
53static 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});";
103const OUTER_HTML: &str = r###"{let rv = ''; if(document.doctype){rv+=new XMLSerializer().serializeToString(document.doctype);} if(document.documentElement){rv+=document.documentElement.outerHTML;} rv}"###;
105
106fn 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
116fn 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
135fn 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
146fn 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 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 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 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 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 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 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 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 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 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 pub async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
265 self.command_future(cmd)?.await
266 }
267
268 pub fn command_future<T: Command>(&self, cmd: T) -> Result<CommandFuture<T>> {
270 self.inner.command_future(cmd)
271 }
272
273 pub fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
275 self.inner.http_future(cmd)
276 }
277
278 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 Ok(())
374 }
375
376 pub async fn wait_for_navigation_response(&self) -> Result<ArcHttpRequest> {
382 self.inner.wait_for_navigation().await
383 }
384
385 pub async fn wait_for_navigation(&self) -> Result<&Self> {
387 self.inner.wait_for_navigation().await?;
388 Ok(self)
389 }
390
391 pub async fn goto(&self, params: impl Into<NavigateParams>) -> Result<&Self> {
395 let res = self.execute(params.into()).await?;
396
397 if let Some(err) = res.result.error_text {
398 return Err(CdpError::ChromeMessage(err));
399 }
400
401 Ok(self)
402 }
403
404 pub fn target_id(&self) -> &TargetId {
406 self.inner.target_id()
407 }
408
409 pub fn session_id(&self) -> &SessionId {
411 self.inner.session_id()
412 }
413
414 pub fn opener_id(&self) -> &Option<TargetId> {
416 self.inner.opener_id()
417 }
418
419 pub async fn frame_name(&self, frame_id: FrameId) -> Result<Option<String>> {
421 let (tx, rx) = oneshot_channel();
422 self.inner
423 .sender()
424 .clone()
425 .send(TargetMessage::Name(GetName {
426 frame_id: Some(frame_id),
427 tx,
428 }))
429 .await?;
430 Ok(rx.await?)
431 }
432
433 pub async fn authenticate(&self, credentials: Credentials) -> Result<()> {
434 self.inner
435 .sender()
436 .clone()
437 .send(TargetMessage::Authenticate(credentials))
438 .await?;
439
440 Ok(())
441 }
442
443 pub async fn url(&self) -> Result<Option<String>> {
445 let (tx, rx) = oneshot_channel();
446 self.inner
447 .sender()
448 .clone()
449 .send(TargetMessage::Url(GetUrl::new(tx)))
450 .await?;
451 Ok(rx.await?)
452 }
453
454 pub async fn frame_url(&self, frame_id: FrameId) -> Result<Option<String>> {
456 let (tx, rx) = oneshot_channel();
457 self.inner
458 .sender()
459 .clone()
460 .send(TargetMessage::Url(GetUrl {
461 frame_id: Some(frame_id),
462 tx,
463 }))
464 .await?;
465 Ok(rx.await?)
466 }
467
468 pub async fn frame_parent(&self, frame_id: FrameId) -> Result<Option<FrameId>> {
470 let (tx, rx) = oneshot_channel();
471 self.inner
472 .sender()
473 .clone()
474 .send(TargetMessage::Parent(GetParent { frame_id, tx }))
475 .await?;
476 Ok(rx.await?)
477 }
478
479 pub async fn mainframe(&self) -> Result<Option<FrameId>> {
481 let (tx, rx) = oneshot_channel();
482 self.inner
483 .sender()
484 .clone()
485 .send(TargetMessage::MainFrame(tx))
486 .await?;
487 Ok(rx.await?)
488 }
489
490 pub async fn frames(&self) -> Result<Vec<FrameId>> {
492 let (tx, rx) = oneshot_channel();
493 self.inner
494 .sender()
495 .clone()
496 .send(TargetMessage::AllFrames(tx))
497 .await?;
498 Ok(rx.await?)
499 }
500
501 pub async fn set_user_agent(
503 &self,
504 params: impl Into<SetUserAgentOverrideParams>,
505 ) -> Result<&Self> {
506 self.execute(params.into()).await?;
507 Ok(self)
508 }
509
510 pub async fn user_agent(&self) -> Result<String> {
512 Ok(self.inner.version().await?.user_agent)
513 }
514
515 pub async fn get_document(&self) -> Result<Node> {
520 let mut cmd = GetDocumentParams::default();
521 cmd.depth = Some(-1);
522 cmd.pierce = Some(true);
523
524 let resp = self.execute(cmd).await?;
525
526 Ok(resp.result.root)
527 }
528
529 pub async fn find_element(&self, selector: impl Into<String>) -> Result<Element> {
534 let root = self.get_document().await?.node_id;
535 let node_id = self.inner.find_element(selector, root).await?;
536 Element::new(Arc::clone(&self.inner), node_id).await
537 }
538
539 pub async fn outer_html(&self) -> Result<String> {
541 let root = self.get_document().await?;
542 let element = Element::new(Arc::clone(&self.inner), root.node_id).await?;
543 self.inner
544 .outer_html(
545 element.remote_object_id,
546 element.node_id,
547 element.backend_node_id,
548 )
549 .await
550 }
551
552 pub async fn find_elements(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
554 let root = self.get_document().await?.node_id;
555 let node_ids = self.inner.find_elements(selector, root).await?;
556 Element::from_nodes(&self.inner, &node_ids).await
557 }
558
559 pub async fn find_xpath(&self, selector: impl Into<String>) -> Result<Element> {
564 self.get_document().await?;
565 let node_id = self.inner.find_xpaths(selector).await?[0];
566 Element::new(Arc::clone(&self.inner), node_id).await
567 }
568
569 pub async fn find_xpaths(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
571 self.get_document().await?;
572 let node_ids = self.inner.find_xpaths(selector).await?;
573 Element::from_nodes(&self.inner, &node_ids).await
574 }
575
576 pub async fn describe_node(&self, node_id: NodeId) -> Result<Node> {
578 let resp = self
579 .execute(DescribeNodeParams::builder().node_id(node_id).build())
580 .await?;
581 Ok(resp.result.node)
582 }
583
584 pub async fn close(self) -> Result<()> {
587 self.execute(CloseParams::default()).await?;
588 Ok(())
589 }
590
591 pub async fn click(&self, point: Point) -> Result<&Self> {
653 self.inner.click(point).await?;
654 Ok(self)
655 }
656
657 pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
661 self.inner.move_mouse(point).await?;
662 Ok(self)
663 }
664
665 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
667 self.inner.screenshot(params).await
668 }
669
670 pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
672 self.inner.print_to_pdf(params).await
673 }
674
675 pub async fn save_screenshot(
699 &self,
700 params: impl Into<ScreenshotParams>,
701 output: impl AsRef<Path>,
702 ) -> Result<Vec<u8>> {
703 let img = self.screenshot(params).await?;
704 utils::write(output.as_ref(), &img).await?;
705 Ok(img)
706 }
707
708 pub async fn pdf(&self, params: PrintToPdfParams) -> Result<Vec<u8>> {
714 let res = self.execute(params).await?;
715 Ok(utils::base64::decode(&res.data)?)
716 }
717
718 pub async fn save_pdf(
723 &self,
724 opts: PrintToPdfParams,
725 output: impl AsRef<Path>,
726 ) -> Result<Vec<u8>> {
727 let pdf = self.pdf(opts).await?;
728 utils::write(output.as_ref(), &pdf).await?;
729 Ok(pdf)
730 }
731
732 pub async fn bring_to_front(&self) -> Result<&Self> {
734 self.execute(BringToFrontParams::default()).await?;
735 Ok(self)
736 }
737
738 pub async fn emulate_media_features(&self, features: Vec<MediaFeature>) -> Result<&Self> {
740 self.execute(SetEmulatedMediaParams::builder().features(features).build())
741 .await?;
742 Ok(self)
743 }
744
745 pub async fn emulate_media_type(
748 &self,
749 media_type: impl Into<MediaTypeParams>,
750 ) -> Result<&Self> {
751 self.execute(
752 SetEmulatedMediaParams::builder()
753 .media(media_type.into())
754 .build(),
755 )
756 .await?;
757 Ok(self)
758 }
759
760 pub async fn emulate_timezone(
762 &self,
763 timezoune_id: impl Into<SetTimezoneOverrideParams>,
764 ) -> Result<&Self> {
765 self.execute(timezoune_id.into()).await?;
766 Ok(self)
767 }
768
769 pub async fn emulate_locale(
771 &self,
772 locale: impl Into<SetLocaleOverrideParams>,
773 ) -> Result<&Self> {
774 self.execute(locale.into()).await?;
775 Ok(self)
776 }
777
778 pub async fn emulate_geolocation(
780 &self,
781 geolocation: impl Into<SetGeolocationOverrideParams>,
782 ) -> Result<&Self> {
783 self.execute(geolocation.into()).await?;
784 Ok(self)
785 }
786
787 pub async fn reload(&self) -> Result<&Self> {
801 self.execute(ReloadParams::default()).await?;
802 self.wait_for_navigation().await
803 }
804
805 pub async fn enable_log(&self) -> Result<&Self> {
812 self.execute(browser_protocol::log::EnableParams::default())
813 .await?;
814 Ok(self)
815 }
816
817 pub async fn disable_log(&self) -> Result<&Self> {
823 self.execute(browser_protocol::log::DisableParams::default())
824 .await?;
825 Ok(self)
826 }
827
828 pub async fn enable_runtime(&self) -> Result<&Self> {
830 self.execute(js_protocol::runtime::EnableParams::default())
831 .await?;
832 Ok(self)
833 }
834
835 pub async fn disable_runtime(&self) -> Result<&Self> {
837 self.execute(js_protocol::runtime::DisableParams::default())
838 .await?;
839 Ok(self)
840 }
841
842 pub async fn enable_debugger(&self) -> Result<&Self> {
844 self.execute(js_protocol::debugger::EnableParams::default())
845 .await?;
846 Ok(self)
847 }
848
849 pub async fn disable_debugger(&self) -> Result<&Self> {
851 self.execute(js_protocol::debugger::DisableParams::default())
852 .await?;
853 Ok(self)
854 }
855
856 pub async fn enable_dom(&self) -> Result<&Self> {
858 self.execute(browser_protocol::dom::EnableParams::default())
859 .await?;
860 Ok(self)
861 }
862
863 pub async fn disable_dom(&self) -> Result<&Self> {
865 self.execute(browser_protocol::dom::DisableParams::default())
866 .await?;
867 Ok(self)
868 }
869
870 pub async fn enable_css(&self) -> Result<&Self> {
872 self.execute(browser_protocol::css::EnableParams::default())
873 .await?;
874 Ok(self)
875 }
876
877 pub async fn disable_css(&self) -> Result<&Self> {
879 self.execute(browser_protocol::css::DisableParams::default())
880 .await?;
881 Ok(self)
882 }
883
884 pub async fn activate(&self) -> Result<&Self> {
886 self.inner.activate().await?;
887 Ok(self)
888 }
889
890 pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
892 Ok(self
893 .execute(GetCookiesParams::default())
894 .await?
895 .result
896 .cookies)
897 }
898
899 pub async fn set_cookie(&self, cookie: impl Into<CookieParam>) -> Result<&Self> {
915 let mut cookie = cookie.into();
916 if let Some(url) = cookie.url.as_ref() {
917 validate_cookie_url(url)?;
918 } else {
919 let url = self
920 .url()
921 .await?
922 .ok_or_else(|| CdpError::msg("Page url not found"))?;
923 validate_cookie_url(&url)?;
924 if url.starts_with("http") {
925 cookie.url = Some(url);
926 }
927 }
928 self.execute(DeleteCookiesParams::from_cookie(&cookie))
929 .await?;
930 self.execute(SetCookiesParams::new(vec![cookie])).await?;
931 Ok(self)
932 }
933
934 pub async fn set_cookies(&self, mut cookies: Vec<CookieParam>) -> Result<&Self> {
936 let url = self
937 .url()
938 .await?
939 .ok_or_else(|| CdpError::msg("Page url not found"))?;
940 let is_http = url.starts_with("http");
941 if !is_http {
942 validate_cookie_url(&url)?;
943 }
944
945 for cookie in &mut cookies {
946 if let Some(url) = cookie.url.as_ref() {
947 validate_cookie_url(url)?;
948 } else if is_http {
949 cookie.url = Some(url.clone());
950 }
951 }
952 self.delete_cookies_unchecked(cookies.iter().map(DeleteCookiesParams::from_cookie))
953 .await?;
954
955 self.execute(SetCookiesParams::new(cookies)).await?;
956 Ok(self)
957 }
958
959 pub async fn delete_cookie(&self, cookie: impl Into<DeleteCookiesParams>) -> Result<&Self> {
961 let mut cookie = cookie.into();
962 if cookie.url.is_none() {
963 let url = self
964 .url()
965 .await?
966 .ok_or_else(|| CdpError::msg("Page url not found"))?;
967 if url.starts_with("http") {
968 cookie.url = Some(url);
969 }
970 }
971 self.execute(cookie).await?;
972 Ok(self)
973 }
974
975 pub async fn delete_cookies(&self, mut cookies: Vec<DeleteCookiesParams>) -> Result<&Self> {
977 let mut url: Option<(String, bool)> = None;
978 for cookie in &mut cookies {
979 if cookie.url.is_none() {
980 if let Some((url, is_http)) = url.as_ref() {
981 if *is_http {
982 cookie.url = Some(url.clone())
983 }
984 } else {
985 let page_url = self
986 .url()
987 .await?
988 .ok_or_else(|| CdpError::msg("Page url not found"))?;
989 let is_http = page_url.starts_with("http");
990 if is_http {
991 cookie.url = Some(page_url.clone())
992 }
993 url = Some((page_url, is_http));
994 }
995 }
996 }
997 self.delete_cookies_unchecked(cookies.into_iter()).await?;
998 Ok(self)
999 }
1000
1001 async fn delete_cookies_unchecked(
1004 &self,
1005 cookies: impl Iterator<Item = DeleteCookiesParams>,
1006 ) -> Result<&Self> {
1007 let mut cmds = stream::iter(cookies.into_iter().map(|cookie| self.execute(cookie)))
1009 .buffer_unordered(5);
1010 while let Some(resp) = cmds.next().await {
1011 resp?;
1012 }
1013 Ok(self)
1014 }
1015
1016 pub async fn get_title(&self) -> Result<Option<String>> {
1018 let result = self.evaluate("document.title").await?;
1019
1020 let title: String = result.into_value()?;
1021
1022 if title.is_empty() {
1023 Ok(None)
1024 } else {
1025 Ok(Some(title))
1026 }
1027 }
1028
1029 pub async fn metrics(&self) -> Result<Vec<Metric>> {
1031 Ok(self
1032 .execute(GetMetricsParams::default())
1033 .await?
1034 .result
1035 .metrics)
1036 }
1037
1038 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
1040 self.inner.layout_metrics().await
1041 }
1042
1043 pub async fn evaluate_expression(
1066 &self,
1067 evaluate: impl Into<EvaluateParams>,
1068 ) -> Result<EvaluationResult> {
1069 self.inner.evaluate_expression(evaluate).await
1070 }
1071
1072 pub async fn evaluate(&self, evaluate: impl Into<Evaluation>) -> Result<EvaluationResult> {
1133 match evaluate.into() {
1134 Evaluation::Expression(mut expr) => {
1135 if expr.context_id.is_none() {
1136 expr.context_id = self.execution_context().await?;
1137 }
1138 let fallback = expr.eval_as_function_fallback.and_then(|p| {
1139 if p {
1140 Some(expr.clone())
1141 } else {
1142 None
1143 }
1144 });
1145 let res = self.evaluate_expression(expr).await?;
1146
1147 if res.object().r#type == RemoteObjectType::Function {
1148 if let Some(fallback) = fallback {
1150 return self.evaluate_function(fallback).await;
1151 }
1152 }
1153 Ok(res)
1154 }
1155 Evaluation::Function(fun) => Ok(self.evaluate_function(fun).await?),
1156 }
1157 }
1158
1159 pub async fn evaluate_function(
1212 &self,
1213 evaluate: impl Into<CallFunctionOnParams>,
1214 ) -> Result<EvaluationResult> {
1215 self.inner.evaluate_function(evaluate).await
1216 }
1217
1218 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
1221 self.inner.execution_context().await
1222 }
1223
1224 pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
1230 self.inner.secondary_execution_context().await
1231 }
1232
1233 pub async fn frame_execution_context(
1234 &self,
1235 frame_id: FrameId,
1236 ) -> Result<Option<ExecutionContextId>> {
1237 self.inner.frame_execution_context(frame_id).await
1238 }
1239
1240 pub async fn frame_secondary_execution_context(
1241 &self,
1242 frame_id: FrameId,
1243 ) -> Result<Option<ExecutionContextId>> {
1244 self.inner.frame_secondary_execution_context(frame_id).await
1245 }
1246
1247 pub async fn evaluate_on_new_document(
1250 &self,
1251 script: impl Into<AddScriptToEvaluateOnNewDocumentParams>,
1252 ) -> Result<ScriptIdentifier> {
1253 Ok(self.execute(script.into()).await?.result.identifier)
1254 }
1255
1256 pub async fn set_content(&self, html: impl AsRef<str>) -> Result<&Self> {
1270 let mut call = CallFunctionOnParams::builder()
1271 .function_declaration(
1272 "(html) => {
1273 document.open();
1274 document.write(html);
1275 document.close();
1276 }",
1277 )
1278 .argument(
1279 CallArgument::builder()
1280 .value(serde_json::json!(html.as_ref()))
1281 .build(),
1282 )
1283 .build()
1284 .unwrap();
1285
1286 call.execution_context_id = self
1287 .inner
1288 .execution_context_for_world(None, DOMWorldKind::Secondary)
1289 .await?;
1290
1291 self.evaluate_function(call).await?;
1292 self.wait_for_navigation().await
1295 }
1296
1297 pub async fn content(&self) -> Result<String> {
1299 Ok(self.evaluate(OUTER_HTML).await?.into_value()?)
1300 }
1301
1302 #[cfg(feature = "bytes")]
1303 pub async fn content_bytes(&self) -> Result<Vec<u8>> {
1305 Ok(self.evaluate(OUTER_HTML).await?.into_bytes()?)
1306 }
1307
1308 #[cfg(feature = "bytes")]
1309 pub async fn outer_html_bytes(&self) -> Result<Vec<u8>> {
1311 Ok(self.outer_html().await?.into())
1312 }
1313
1314 pub async fn get_script_source(&self, script_id: impl Into<String>) -> Result<String> {
1318 Ok(self
1319 .execute(GetScriptSourceParams::new(ScriptId::from(script_id.into())))
1320 .await?
1321 .result
1322 .script_source)
1323 }
1324}
1325
1326impl From<Arc<PageInner>> for Page {
1327 fn from(inner: Arc<PageInner>) -> Self {
1328 Self { inner }
1329 }
1330}
1331
1332pub(crate) fn validate_cookie_url(url: &str) -> Result<()> {
1333 if url.starts_with("data:") {
1334 Err(CdpError::msg("Data URL page can not have cookie"))
1335 } else if url == "about:blank" {
1336 Err(CdpError::msg("Blank page can not have cookie"))
1337 } else {
1338 Ok(())
1339 }
1340}
1341
1342#[derive(Debug, Default)]
1344pub struct ScreenshotParams {
1345 pub cdp_params: CaptureScreenshotParams,
1347 pub full_page: Option<bool>,
1349 pub omit_background: Option<bool>,
1351}
1352
1353impl ScreenshotParams {
1354 pub fn builder() -> ScreenshotParamsBuilder {
1355 Default::default()
1356 }
1357
1358 pub(crate) fn full_page(&self) -> bool {
1359 self.full_page.unwrap_or(false)
1360 }
1361
1362 pub(crate) fn omit_background(&self) -> bool {
1363 self.omit_background.unwrap_or(false)
1364 && self
1365 .cdp_params
1366 .format
1367 .as_ref()
1368 .map_or(true, |f| f == &CaptureScreenshotFormat::Png)
1369 }
1370}
1371
1372#[derive(Debug, Default)]
1374pub struct ScreenshotParamsBuilder {
1375 cdp_params: CaptureScreenshotParams,
1376 full_page: Option<bool>,
1377 omit_background: Option<bool>,
1378}
1379
1380impl ScreenshotParamsBuilder {
1381 pub fn format(mut self, format: impl Into<CaptureScreenshotFormat>) -> Self {
1383 self.cdp_params.format = Some(format.into());
1384 self
1385 }
1386
1387 pub fn quality(mut self, quality: impl Into<i64>) -> Self {
1389 self.cdp_params.quality = Some(quality.into());
1390 self
1391 }
1392
1393 pub fn clip(mut self, clip: impl Into<Viewport>) -> Self {
1395 self.cdp_params.clip = Some(clip.into());
1396 self
1397 }
1398
1399 pub fn from_surface(mut self, from_surface: impl Into<bool>) -> Self {
1401 self.cdp_params.from_surface = Some(from_surface.into());
1402 self
1403 }
1404
1405 pub fn capture_beyond_viewport(mut self, capture_beyond_viewport: impl Into<bool>) -> Self {
1407 self.cdp_params.capture_beyond_viewport = Some(capture_beyond_viewport.into());
1408 self
1409 }
1410
1411 pub fn full_page(mut self, full_page: impl Into<bool>) -> Self {
1413 self.full_page = Some(full_page.into());
1414 self
1415 }
1416
1417 pub fn omit_background(mut self, omit_background: impl Into<bool>) -> Self {
1419 self.omit_background = Some(omit_background.into());
1420 self
1421 }
1422
1423 pub fn build(self) -> ScreenshotParams {
1424 ScreenshotParams {
1425 cdp_params: self.cdp_params,
1426 full_page: self.full_page,
1427 omit_background: self.omit_background,
1428 }
1429 }
1430}
1431
1432impl From<CaptureScreenshotParams> for ScreenshotParams {
1433 fn from(cdp_params: CaptureScreenshotParams) -> Self {
1434 Self {
1435 cdp_params,
1436 ..Default::default()
1437 }
1438 }
1439}
1440
1441#[derive(Debug, Clone, Copy, Default)]
1442pub enum MediaTypeParams {
1443 #[default]
1445 Null,
1446 Screen,
1448 Print,
1450}
1451impl From<MediaTypeParams> for String {
1452 fn from(media_type: MediaTypeParams) -> Self {
1453 match media_type {
1454 MediaTypeParams::Null => "null".to_string(),
1455 MediaTypeParams::Screen => "screen".to_string(),
1456 MediaTypeParams::Print => "print".to_string(),
1457 }
1458 }
1459}