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