chromiumoxide/page.rs
1use std::path::Path;
2use std::sync::Arc;
3
4use chromiumoxide_cdp::cdp::browser_protocol::accessibility::{
5 GetFullAxTreeReturns, GetPartialAxTreeReturns,
6};
7use chromiumoxide_cdp::cdp::browser_protocol::emulation::{
8 MediaFeature, SetDeviceMetricsOverrideParams, SetEmulatedMediaParams,
9 SetGeolocationOverrideParams, SetHardwareConcurrencyOverrideParams, SetLocaleOverrideParams,
10 SetTimezoneOverrideParams, UserAgentBrandVersion, UserAgentMetadata,
11};
12use chromiumoxide_cdp::cdp::browser_protocol::input::{DispatchDragEventType, DragData};
13use chromiumoxide_cdp::cdp::browser_protocol::network::{
14 Cookie, CookieParam, DeleteCookiesParams, GetCookiesParams, SetBlockedUrLsParams,
15 SetCookiesParams, SetExtraHttpHeadersParams, SetUserAgentOverrideParams, TimeSinceEpoch,
16};
17use chromiumoxide_cdp::cdp::browser_protocol::page::*;
18use chromiumoxide_cdp::cdp::browser_protocol::performance::{GetMetricsParams, Metric};
19use chromiumoxide_cdp::cdp::browser_protocol::target::{SessionId, TargetId};
20use chromiumoxide_cdp::cdp::browser_protocol::{dom::*, emulation};
21use chromiumoxide_cdp::cdp::js_protocol;
22use chromiumoxide_cdp::cdp::js_protocol::debugger::GetScriptSourceParams;
23use chromiumoxide_cdp::cdp::js_protocol::runtime::{
24 AddBindingParams, CallArgument, CallFunctionOnParams, EvaluateParams, ExecutionContextId,
25 RemoteObjectType, ScriptId,
26};
27use chromiumoxide_cdp::cdp::{browser_protocol, IntoEventKind};
28use chromiumoxide_types::*;
29use futures::channel::mpsc::unbounded;
30use futures::channel::oneshot::channel as oneshot_channel;
31use futures::{stream, SinkExt, StreamExt};
32use spider_fingerprint::configs::{AgentOs, Tier};
33
34use crate::auth::Credentials;
35use crate::element::Element;
36use crate::error::{CdpError, Result};
37use crate::handler::commandfuture::CommandFuture;
38use crate::handler::domworld::DOMWorldKind;
39use crate::handler::httpfuture::HttpFuture;
40use crate::handler::target::{GetName, GetParent, GetUrl, TargetMessage};
41use crate::handler::PageInner;
42use crate::javascript::extract::{generate_marker_js, FULL_XML_SERIALIZER_JS, OUTER_HTML};
43use crate::js::{Evaluation, EvaluationResult};
44use crate::layout::{Delta, Point, ScrollBehavior};
45use crate::listeners::{EventListenerRequest, EventStream};
46use crate::{utils, ArcHttpRequest};
47use aho_corasick::AhoCorasick;
48
49lazy_static::lazy_static! {
50 /// Determine the platform used.
51 static ref PLATFORM_MATCHER: AhoCorasick = {
52 AhoCorasick::builder()
53 .match_kind(aho_corasick::MatchKind::LeftmostFirst)
54 .ascii_case_insensitive(true)
55 .build([
56 "ipad", // 0
57 "ipod", // 1
58 "iphone", // 2
59 "android", // 3
60 "macintosh", // 4
61 "mac os x", // 5
62 "windows", // 6
63 "linux", // 7
64 ])
65 .expect("valid pattern")
66 };
67}
68
69/// Determine the platform used from a user-agent.
70pub fn platform_from_user_agent(user_agent: &str) -> &'static str {
71 match PLATFORM_MATCHER.find(user_agent) {
72 Some(mat) => match mat.pattern().as_usize() {
73 0 => "iPad",
74 1 => "iPod",
75 2 => "iPhone",
76 3 => "Linux armv8l",
77 4 | 5 => "MacIntel",
78 6 => "Win32",
79 7 => "Linux x86_64",
80 _ => "",
81 },
82 None => "",
83 }
84}
85
86#[derive(Debug, Clone)]
87pub struct Page {
88 inner: Arc<PageInner>,
89}
90
91impl Page {
92 /// Add a custom script to eval on new document immediately.
93 pub async fn add_script_to_evaluate_immediately_on_new_document(
94 &self,
95 source: Option<String>,
96 ) -> Result<&Self> {
97 if source.is_some() {
98 let source = source.unwrap_or_default();
99
100 if !source.is_empty() {
101 self.execute(AddScriptToEvaluateOnNewDocumentParams {
102 source,
103 world_name: None,
104 include_command_line_api: None,
105 run_immediately: Some(true),
106 })
107 .await?;
108 }
109 }
110 Ok(self)
111 }
112
113 /// Add a custom script to eval on new document.
114 pub async fn add_script_to_evaluate_on_new_document(
115 &self,
116 source: Option<String>,
117 ) -> Result<&Self> {
118 if source.is_some() {
119 let source = source.unwrap_or_default();
120
121 if !source.is_empty() {
122 self.execute(AddScriptToEvaluateOnNewDocumentParams {
123 source,
124 world_name: None,
125 include_command_line_api: None,
126 run_immediately: None,
127 })
128 .await?;
129 }
130 }
131 Ok(self)
132 }
133
134 /// Removes the `navigator.webdriver` property
135 /// changes permissions, pluggins rendering contexts and the `window.chrome`
136 /// property to make it harder to detect the scraper as a bot.
137 pub async fn _enable_real_emulation(
138 &self,
139 user_agent: &str,
140 config: &spider_fingerprint::EmulationConfiguration,
141 viewport: &Option<&spider_fingerprint::spoof_viewport::Viewport>,
142 custom_script: Option<&str>,
143 ) -> Result<&Self> {
144 let emulation_script = spider_fingerprint::emulate(
145 &user_agent,
146 &config,
147 &viewport,
148 &custom_script.as_ref().map(|s| Box::new(s.to_string())),
149 )
150 .unwrap_or_default();
151
152 let source = if let Some(cs) = custom_script {
153 format!(
154 "{};{};",
155 emulation_script,
156 spider_fingerprint::wrap_eval_script(&cs)
157 )
158 } else {
159 emulation_script
160 };
161
162 self.add_script_to_evaluate_on_new_document(Some(source))
163 .await?;
164
165 Ok(self)
166 }
167
168 /// Removes the `navigator.webdriver` property
169 /// changes permissions, pluggins rendering contexts and the `window.chrome`
170 /// property to make it harder to detect the scraper as a bot
171 pub async fn _enable_stealth_mode(
172 &self,
173 custom_script: Option<&str>,
174 os: Option<AgentOs>,
175 tier: Option<Tier>,
176 ) -> Result<&Self> {
177 let os = os.unwrap_or_default();
178 let tier = match tier {
179 Some(tier) => tier,
180 _ => Tier::Basic,
181 };
182
183 let source = if let Some(cs) = custom_script {
184 format!(
185 "{};{};",
186 spider_fingerprint::build_stealth_script(tier, os),
187 spider_fingerprint::wrap_eval_script(&cs)
188 )
189 } else {
190 spider_fingerprint::build_stealth_script(tier, os)
191 };
192
193 self.add_script_to_evaluate_on_new_document(Some(source))
194 .await?;
195
196 Ok(self)
197 }
198
199 /// Changes your user_agent, removes the `navigator.webdriver` property
200 /// changes permissions, pluggins rendering contexts and the `window.chrome`
201 /// property to make it harder to detect the scraper as a bot
202 pub async fn enable_stealth_mode(&self) -> Result<&Self> {
203 let _ = self._enable_stealth_mode(None, None, None).await;
204
205 Ok(self)
206 }
207
208 /// Changes your user_agent, removes the `navigator.webdriver` property
209 /// changes permissions, pluggins rendering contexts and the `window.chrome`
210 /// property to make it harder to detect the scraper as a bot
211 pub async fn enable_stealth_mode_os(
212 &self,
213 os: Option<AgentOs>,
214 tier: Option<Tier>,
215 ) -> Result<&Self> {
216 let _ = self._enable_stealth_mode(None, os, tier).await;
217
218 Ok(self)
219 }
220
221 /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
222 /// changes permissions, pluggins rendering contexts and the `window.chrome`
223 /// property to make it harder to detect the scraper as a bot
224 pub async fn enable_stealth_mode_with_agent(&self, ua: &str) -> Result<&Self> {
225 let _ = tokio::join!(
226 self._enable_stealth_mode(None, None, None),
227 self.set_user_agent(ua)
228 );
229 Ok(self)
230 }
231
232 /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
233 /// changes permissions, pluggins rendering contexts and the `window.chrome`
234 /// property to make it harder to detect the scraper as a bot. Also add dialog polyfill to prevent blocking the page.
235 pub async fn enable_stealth_mode_with_dimiss_dialogs(&self, ua: &str) -> Result<&Self> {
236 let _ = tokio::join!(
237 self._enable_stealth_mode(
238 Some(spider_fingerprint::spoofs::DISABLE_DIALOGS),
239 None,
240 None
241 ),
242 self.set_user_agent(ua)
243 );
244 Ok(self)
245 }
246
247 /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
248 /// changes permissions, pluggins rendering contexts and the `window.chrome`
249 /// property to make it harder to detect the scraper as a bot. Also add dialog polyfill to prevent blocking the page.
250 pub async fn enable_stealth_mode_with_agent_and_dimiss_dialogs(
251 &self,
252 ua: &str,
253 ) -> Result<&Self> {
254 let _ = tokio::join!(
255 self._enable_stealth_mode(
256 Some(spider_fingerprint::spoofs::DISABLE_DIALOGS),
257 None,
258 None
259 ),
260 self.set_user_agent(ua)
261 );
262 Ok(self)
263 }
264
265 /// Enable page Content Security Policy by-passing.
266 pub async fn set_bypass_csp(&self, enabled: bool) -> Result<&Self> {
267 self.inner.set_bypass_csp(enabled).await?;
268 Ok(self)
269 }
270
271 /// Sets `window.chrome` on frame creation and console.log methods.
272 pub async fn hide_chrome(&self) -> Result<&Self, CdpError> {
273 self.execute(AddScriptToEvaluateOnNewDocumentParams {
274 source: spider_fingerprint::spoofs::HIDE_CHROME.to_string(),
275 world_name: None,
276 include_command_line_api: None,
277 run_immediately: None,
278 })
279 .await?;
280 Ok(self)
281 }
282
283 /// Obfuscates WebGL vendor on frame creation
284 pub async fn hide_webgl_vendor(&self) -> Result<&Self, CdpError> {
285 self.execute(AddScriptToEvaluateOnNewDocumentParams {
286 source: spider_fingerprint::spoofs::HIDE_WEBGL.to_string(),
287 world_name: None,
288 include_command_line_api: None,
289 run_immediately: None,
290 })
291 .await?;
292 Ok(self)
293 }
294
295 /// Obfuscates browser plugins and hides the navigator object on frame creation
296 pub async fn hide_plugins(&self) -> Result<&Self, CdpError> {
297 self.execute(AddScriptToEvaluateOnNewDocumentParams {
298 source: spider_fingerprint::generate_hide_plugins(),
299 world_name: None,
300 include_command_line_api: None,
301 run_immediately: None,
302 })
303 .await?;
304
305 Ok(self)
306 }
307
308 /// Obfuscates browser permissions on frame creation
309 pub async fn hide_permissions(&self) -> Result<&Self, CdpError> {
310 self.execute(AddScriptToEvaluateOnNewDocumentParams {
311 source: spider_fingerprint::spoofs::HIDE_PERMISSIONS.to_string(),
312 world_name: None,
313 include_command_line_api: None,
314 run_immediately: None,
315 })
316 .await?;
317 Ok(self)
318 }
319
320 /// Removes the `navigator.webdriver` property on frame creation
321 pub async fn hide_webdriver(&self) -> Result<&Self, CdpError> {
322 self.execute(AddScriptToEvaluateOnNewDocumentParams {
323 source: spider_fingerprint::spoofs::HIDE_WEBDRIVER.to_string(),
324 world_name: None,
325 include_command_line_api: None,
326 run_immediately: None,
327 })
328 .await?;
329 Ok(self)
330 }
331
332 /// Execute a command and return the `Command::Response`
333 pub async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
334 self.command_future(cmd)?.await
335 }
336
337 /// Execute a command and return the `Command::Response`
338 pub async fn send_command<T: Command>(&self, cmd: T) -> Result<&Self> {
339 let _ = self.inner.send_command(cmd).await;
340 Ok(self)
341 }
342
343 /// Execute a command and return the `Command::Response`
344 pub fn command_future<T: Command>(&self, cmd: T) -> Result<CommandFuture<T>> {
345 self.inner.command_future(cmd)
346 }
347
348 /// Execute a command and return the `Command::Response`
349 pub fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
350 self.inner.http_future(cmd)
351 }
352
353 /// Adds an event listener to the `Target` and returns the receiver part as
354 /// `EventStream`
355 ///
356 /// An `EventStream` receives every `Event` the `Target` receives.
357 /// All event listener get notified with the same event, so registering
358 /// multiple listeners for the same event is possible.
359 ///
360 /// Custom events rely on being deserializable from the received json params
361 /// in the `EventMessage`. Custom Events are caught by the `CdpEvent::Other`
362 /// variant. If there are mulitple custom event listener is registered
363 /// for the same event, identified by the `MethodType::method_id` function,
364 /// the `Target` tries to deserialize the json using the type of the event
365 /// listener. Upon success the `Target` then notifies all listeners with the
366 /// deserialized event. This means, while it is possible to register
367 /// different types for the same custom event, only the type of first
368 /// registered event listener will be used. The subsequent listeners, that
369 /// registered for the same event but with another type won't be able to
370 /// receive anything and therefor will come up empty until all their
371 /// preceding event listeners are dropped and they become the first (or
372 /// longest) registered event listener for an event.
373 ///
374 /// # Example Listen for canceled animations
375 /// ```no_run
376 /// # use chromiumoxide::page::Page;
377 /// # use chromiumoxide::error::Result;
378 /// # use chromiumoxide_cdp::cdp::browser_protocol::animation::EventAnimationCanceled;
379 /// # use futures::StreamExt;
380 /// # async fn demo(page: Page) -> Result<()> {
381 /// let mut events = page.event_listener::<EventAnimationCanceled>().await?;
382 /// while let Some(event) = events.next().await {
383 /// //..
384 /// }
385 /// # Ok(())
386 /// # }
387 /// ```
388 ///
389 /// # Example Liste for a custom event
390 ///
391 /// ```no_run
392 /// # use chromiumoxide::page::Page;
393 /// # use chromiumoxide::error::Result;
394 /// # use futures::StreamExt;
395 /// # use serde::Deserialize;
396 /// # use chromiumoxide::types::{MethodId, MethodType};
397 /// # use chromiumoxide::cdp::CustomEvent;
398 /// # async fn demo(page: Page) -> Result<()> {
399 /// #[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
400 /// struct MyCustomEvent {
401 /// name: String,
402 /// }
403 /// impl MethodType for MyCustomEvent {
404 /// fn method_id() -> MethodId {
405 /// "Custom.Event".into()
406 /// }
407 /// }
408 /// impl CustomEvent for MyCustomEvent {}
409 /// let mut events = page.event_listener::<MyCustomEvent>().await?;
410 /// while let Some(event) = events.next().await {
411 /// //..
412 /// }
413 ///
414 /// # Ok(())
415 /// # }
416 /// ```
417 pub async fn event_listener<T: IntoEventKind>(&self) -> Result<EventStream<T>> {
418 let (tx, rx) = unbounded();
419
420 self.inner
421 .sender()
422 .clone()
423 .send(TargetMessage::AddEventListener(
424 EventListenerRequest::new::<T>(tx),
425 ))
426 .await?;
427
428 Ok(EventStream::new(rx))
429 }
430
431 pub async fn expose_function(
432 &self,
433 name: impl Into<String>,
434 function: impl AsRef<str>,
435 ) -> Result<()> {
436 let name = name.into();
437 let expression = utils::evaluation_string(function, &["exposedFun", name.as_str()]);
438
439 self.execute(AddBindingParams::new(name)).await?;
440 self.execute(AddScriptToEvaluateOnNewDocumentParams::new(
441 expression.clone(),
442 ))
443 .await?;
444
445 // TODO add execution context tracking for frames
446 //let frames = self.frames().await?;
447
448 Ok(())
449 }
450
451 /// This resolves once the navigation finished and the page is loaded.
452 ///
453 /// This is necessary after an interaction with the page that may trigger a
454 /// navigation (`click`, `press_key`) in order to wait until the new browser
455 /// page is loaded
456 pub async fn wait_for_navigation_response(&self) -> Result<ArcHttpRequest> {
457 self.inner.wait_for_navigation().await
458 }
459
460 /// Same as `wait_for_navigation_response` but returns `Self` instead
461 pub async fn wait_for_navigation(&self) -> Result<&Self> {
462 self.inner.wait_for_navigation().await?;
463 Ok(self)
464 }
465
466 /// Navigate directly to the given URL checking the HTTP cache first.
467 ///
468 /// This resolves directly after the requested URL is fully loaded. Does nothing without the 'cache' feature on.
469 #[cfg(feature = "cache")]
470 pub async fn goto_with_cache(
471 &self,
472 params: impl Into<NavigateParams>,
473 auth_opt: Option<&str>,
474 ) -> Result<&Self> {
475 use crate::cache::{get_cached_url, rewrite_base_tag};
476 let navigate_params: NavigateParams = params.into();
477 let mut force_navigate = true;
478
479 // todo: pull in the headers from auth.
480 if let Some(source) = get_cached_url(&navigate_params.url, auth_opt).await {
481 let html = rewrite_base_tag(&source, &Some(&navigate_params.url)).await;
482 if let Ok(frame_id) = self.mainframe().await {
483 if let Err(e) = self
484 .execute(browser_protocol::page::SetDocumentContentParams {
485 frame_id: frame_id.unwrap_or_default(),
486 html,
487 })
488 .await
489 {
490 tracing::error!("Set Content Error({:?}) - {:?}", e, &navigate_params.url);
491 force_navigate = false;
492 if let crate::page::CdpError::Timeout = e {
493 force_navigate = true;
494 }
495 } else {
496 tracing::info!("Found cached url - ({:?})", &navigate_params.url);
497 force_navigate = false;
498 }
499 }
500 }
501
502 if force_navigate {
503 let res = self.execute(navigate_params).await?;
504
505 if let Some(err) = res.result.error_text {
506 return Err(CdpError::ChromeMessage(err));
507 }
508 }
509
510 Ok(self)
511 }
512
513 /// Navigate directly to the given URL.
514 ///
515 /// This resolves directly after the requested URL is fully loaded. Does nothing without the 'cache' feature on.
516 #[cfg(not(feature = "cache"))]
517 pub async fn goto_with_cache(
518 &self,
519 params: impl Into<NavigateParams>,
520 _auth_opt: Option<&str>,
521 ) -> Result<&Self> {
522 let res = self.execute(params.into()).await?;
523
524 if let Some(err) = res.result.error_text {
525 return Err(CdpError::ChromeMessage(err));
526 }
527
528 Ok(self)
529 }
530
531 /// Navigate directly to the given URL.
532 ///
533 /// This resolves directly after the requested URL is fully loaded.
534 pub async fn goto(&self, params: impl Into<NavigateParams>) -> Result<&Self> {
535 let res = self.execute(params.into()).await?;
536
537 if let Some(err) = res.result.error_text {
538 return Err(CdpError::ChromeMessage(err));
539 }
540
541 Ok(self)
542 }
543
544 /// The identifier of the `Target` this page belongs to
545 pub fn target_id(&self) -> &TargetId {
546 self.inner.target_id()
547 }
548
549 /// The identifier of the `Session` target of this page is attached to
550 pub fn session_id(&self) -> &SessionId {
551 self.inner.session_id()
552 }
553
554 /// The identifier of the `Session` target of this page is attached to
555 pub fn opener_id(&self) -> &Option<TargetId> {
556 self.inner.opener_id()
557 }
558
559 /// Returns the name of the frame
560 pub async fn frame_name(&self, frame_id: FrameId) -> Result<Option<String>> {
561 let (tx, rx) = oneshot_channel();
562 self.inner
563 .sender()
564 .clone()
565 .send(TargetMessage::Name(GetName {
566 frame_id: Some(frame_id),
567 tx,
568 }))
569 .await?;
570 Ok(rx.await?)
571 }
572
573 pub async fn authenticate(&self, credentials: Credentials) -> Result<()> {
574 self.inner
575 .sender()
576 .clone()
577 .send(TargetMessage::Authenticate(credentials))
578 .await?;
579
580 Ok(())
581 }
582
583 /// Returns the current url of the page
584 pub async fn url(&self) -> Result<Option<String>> {
585 let (tx, rx) = oneshot_channel();
586 self.inner
587 .sender()
588 .clone()
589 .send(TargetMessage::Url(GetUrl::new(tx)))
590 .await?;
591 Ok(rx.await?)
592 }
593
594 /// Returns the current url of the frame
595 pub async fn frame_url(&self, frame_id: FrameId) -> Result<Option<String>> {
596 let (tx, rx) = oneshot_channel();
597 self.inner
598 .sender()
599 .clone()
600 .send(TargetMessage::Url(GetUrl {
601 frame_id: Some(frame_id),
602 tx,
603 }))
604 .await?;
605 Ok(rx.await?)
606 }
607
608 /// Returns the parent id of the frame
609 pub async fn frame_parent(&self, frame_id: FrameId) -> Result<Option<FrameId>> {
610 let (tx, rx) = oneshot_channel();
611 self.inner
612 .sender()
613 .clone()
614 .send(TargetMessage::Parent(GetParent { frame_id, tx }))
615 .await?;
616 Ok(rx.await?)
617 }
618
619 /// Return the main frame of the page
620 pub async fn mainframe(&self) -> Result<Option<FrameId>> {
621 let (tx, rx) = oneshot_channel();
622 self.inner
623 .sender()
624 .clone()
625 .send(TargetMessage::MainFrame(tx))
626 .await?;
627 Ok(rx.await?)
628 }
629
630 /// Return the frames of the page
631 pub async fn frames(&self) -> Result<Vec<FrameId>> {
632 let (tx, rx) = oneshot_channel();
633 self.inner
634 .sender()
635 .clone()
636 .send(TargetMessage::AllFrames(tx))
637 .await?;
638 Ok(rx.await?)
639 }
640
641 /// Allows overriding user agent with the given string.
642 pub async fn set_extra_headers(
643 &self,
644 params: impl Into<SetExtraHttpHeadersParams>,
645 ) -> Result<&Self> {
646 self.execute(params.into()).await?;
647 Ok(self)
648 }
649
650 /// Generate the user-agent metadata params
651 pub fn generate_user_agent_metadata(
652 default_params: &SetUserAgentOverrideParams,
653 ) -> Option<UserAgentMetadata> {
654 let ua_data = spider_fingerprint::spoof_user_agent::build_high_entropy_data(&Some(
655 &default_params.user_agent,
656 ));
657 let windows = ua_data.platform == "Windows";
658
659 let brands = ua_data
660 .full_version_list
661 .iter()
662 .map(|b| {
663 let b = b.clone();
664 UserAgentBrandVersion::new(b.brand, b.version)
665 })
666 .collect::<Vec<_>>();
667
668 let full_versions = ua_data
669 .full_version_list
670 .into_iter()
671 .map(|b| UserAgentBrandVersion::new(b.brand, b.version))
672 .collect::<Vec<_>>();
673
674 let user_agent_metadata_builder = emulation::UserAgentMetadata::builder()
675 .architecture(ua_data.architecture)
676 .bitness(ua_data.bitness)
677 .model(ua_data.model)
678 .platform_version(ua_data.platform_version)
679 .brands(brands)
680 .full_version_lists(full_versions)
681 .platform(ua_data.platform)
682 .mobile(ua_data.mobile);
683
684 let user_agent_metadata_builder = if windows {
685 user_agent_metadata_builder.wow64(ua_data.wow64_ness)
686 } else {
687 user_agent_metadata_builder
688 };
689
690 if let Ok(user_agent_metadata) = user_agent_metadata_builder.build() {
691 Some(user_agent_metadata)
692 } else {
693 None
694 }
695 }
696
697 /// Allows overriding the user-agent for the [network](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setUserAgentOverride) and [emulation](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setUserAgentOverride ) with the given string.
698 async fn set_user_agent_base(
699 &self,
700 params: impl Into<SetUserAgentOverrideParams>,
701 metadata: bool,
702 emulate: bool,
703 accept_language: Option<String>,
704 ) -> Result<&Self> {
705 let mut default_params: SetUserAgentOverrideParams = params.into();
706
707 if default_params.platform.is_none() {
708 let platform = platform_from_user_agent(&default_params.user_agent);
709 if !platform.is_empty() {
710 default_params.platform = Some(platform.into());
711 }
712 }
713
714 default_params.accept_language = accept_language;
715
716 if default_params.user_agent_metadata.is_none() && metadata {
717 let user_agent_metadata = Self::generate_user_agent_metadata(&default_params);
718 if let Some(user_agent_metadata) = user_agent_metadata {
719 default_params.user_agent_metadata = Some(user_agent_metadata);
720 }
721 }
722
723 if emulate {
724 let default_params1 = default_params.clone();
725
726 let mut set_emulation_agent_override =
727 chromiumoxide_cdp::cdp::browser_protocol::emulation::SetUserAgentOverrideParams::new(
728 default_params1.user_agent,
729 );
730
731 set_emulation_agent_override.accept_language = default_params1.accept_language;
732 set_emulation_agent_override.platform = default_params1.platform;
733 set_emulation_agent_override.user_agent_metadata = default_params1.user_agent_metadata;
734
735 tokio::try_join!(
736 self.execute(default_params),
737 self.execute(set_emulation_agent_override)
738 )?;
739 } else {
740 self.execute(default_params).await?;
741 }
742
743 Ok(self)
744 }
745
746 /// Allows overriding the user-agent for the [network](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setUserAgentOverride) with the given string.
747 pub async fn set_user_agent(
748 &self,
749 params: impl Into<SetUserAgentOverrideParams>,
750 ) -> Result<&Self> {
751 self.set_user_agent_base(params, true, true, None).await
752 }
753
754 /// Allows overriding the user-agent for the [network](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setUserAgentOverride), [emulation](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setUserAgentOverride ), and userAgentMetadata with the given string.
755 pub async fn set_user_agent_advanced(
756 &self,
757 params: impl Into<SetUserAgentOverrideParams>,
758 metadata: bool,
759 emulate: bool,
760 accept_language: Option<String>,
761 ) -> Result<&Self> {
762 self.set_user_agent_base(params, metadata, emulate, accept_language)
763 .await
764 }
765
766 /// Returns the user agent of the browser
767 pub async fn user_agent(&self) -> Result<String> {
768 Ok(self.inner.version().await?.user_agent)
769 }
770
771 /// Returns the root DOM node (and optionally the subtree) of the page.
772 ///
773 /// # Note: This does not return the actual HTML document of the page. To
774 /// retrieve the HTML content of the page see `Page::content`.
775 pub async fn get_document(&self) -> Result<Node> {
776 let mut cmd = GetDocumentParams::default();
777 cmd.depth = Some(-1);
778 cmd.pierce = Some(true);
779
780 let resp = self.execute(cmd).await?;
781
782 Ok(resp.result.root)
783 }
784
785 /// Returns the first element in the document which matches the given CSS
786 /// selector.
787 ///
788 /// Execute a query selector on the document's node.
789 pub async fn find_element(&self, selector: impl Into<String>) -> Result<Element> {
790 let root = self.get_document().await?.node_id;
791 let node_id = self.inner.find_element(selector, root).await?;
792 Element::new(Arc::clone(&self.inner), node_id).await
793 }
794
795 /// Returns the outer HTML of the page.
796 pub async fn outer_html(&self) -> Result<String> {
797 let root = self.get_document().await?;
798 let element = Element::new(Arc::clone(&self.inner), root.node_id).await?;
799 self.inner
800 .outer_html(
801 element.remote_object_id,
802 element.node_id,
803 element.backend_node_id,
804 )
805 .await
806 }
807
808 /// Return all `Element`s in the document that match the given selector
809 pub async fn find_elements(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
810 let root = self.get_document().await?.node_id;
811 let node_ids = self.inner.find_elements(selector, root).await?;
812 Element::from_nodes(&self.inner, &node_ids).await
813 }
814
815 /// Returns the first element in the document which matches the given xpath
816 /// selector.
817 ///
818 /// Execute a xpath selector on the document's node.
819 pub async fn find_xpath(&self, selector: impl Into<String>) -> Result<Element> {
820 self.get_document().await?;
821 let node_id = self.inner.find_xpaths(selector).await?[0];
822 Element::new(Arc::clone(&self.inner), node_id).await
823 }
824
825 /// Return all `Element`s in the document that match the given xpath selector
826 pub async fn find_xpaths(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
827 self.get_document().await?;
828 let node_ids = self.inner.find_xpaths(selector).await?;
829 Element::from_nodes(&self.inner, &node_ids).await
830 }
831
832 /// Describes node given its id
833 pub async fn describe_node(&self, node_id: NodeId) -> Result<Node> {
834 let resp = self
835 .execute(DescribeNodeParams::builder().node_id(node_id).build())
836 .await?;
837 Ok(resp.result.node)
838 }
839
840 /// Tries to close page, running its beforeunload hooks, if any.
841 /// Calls Page.close with [`CloseParams`]
842 pub async fn close(self) -> Result<()> {
843 self.execute(CloseParams::default()).await?;
844 Ok(())
845 }
846
847 /// Performs a single mouse click event at the point's location.
848 ///
849 /// This scrolls the point into view first, then executes a
850 /// `DispatchMouseEventParams` command of type `MouseLeft` with
851 /// `MousePressed` as single click and then releases the mouse with an
852 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
853 /// `MouseReleased`
854 ///
855 /// Bear in mind that if `click()` triggers a navigation the new page is not
856 /// immediately loaded when `click()` resolves. To wait until navigation is
857 /// finished an additional `wait_for_navigation()` is required:
858 ///
859 /// # Example
860 ///
861 /// Trigger a navigation and wait until the triggered navigation is finished
862 ///
863 /// ```no_run
864 /// # use chromiumoxide::page::Page;
865 /// # use chromiumoxide::error::Result;
866 /// # use chromiumoxide::layout::Point;
867 /// # async fn demo(page: Page, point: Point) -> Result<()> {
868 /// let html = page.click(point).await?.wait_for_navigation().await?.content();
869 /// # Ok(())
870 /// # }
871 /// ```
872 ///
873 /// # Example
874 ///
875 /// Perform custom click
876 ///
877 /// ```no_run
878 /// # use chromiumoxide::page::Page;
879 /// # use chromiumoxide::error::Result;
880 /// # use chromiumoxide::layout::Point;
881 /// # use chromiumoxide_cdp::cdp::browser_protocol::input::{DispatchMouseEventParams, MouseButton, DispatchMouseEventType};
882 /// # async fn demo(page: Page, point: Point) -> Result<()> {
883 /// // double click
884 /// let cmd = DispatchMouseEventParams::builder()
885 /// .x(point.x)
886 /// .y(point.y)
887 /// .button(MouseButton::Left)
888 /// .click_count(2);
889 ///
890 /// page.move_mouse(point).await?.execute(
891 /// cmd.clone()
892 /// .r#type(DispatchMouseEventType::MousePressed)
893 /// .build()
894 /// .unwrap(),
895 /// )
896 /// .await?;
897 ///
898 /// page.execute(
899 /// cmd.r#type(DispatchMouseEventType::MouseReleased)
900 /// .build()
901 /// .unwrap(),
902 /// )
903 /// .await?;
904 ///
905 /// # Ok(())
906 /// # }
907 /// ```
908 pub async fn click(&self, point: Point) -> Result<&Self> {
909 self.inner.click(point).await?;
910 Ok(self)
911 }
912
913 /// Performs a single mouse click event at the point's location and generate a marker.
914 pub(crate) async fn click_with_highlight_base(
915 &self,
916 point: Point,
917 color: Rgba,
918 ) -> Result<&Self> {
919 use chromiumoxide_cdp::cdp::browser_protocol::overlay::HighlightRectParams;
920 let x = point.x.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
921 let y = point.y.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
922
923 let highlight_params = HighlightRectParams {
924 x,
925 y,
926 width: 15,
927 height: 15,
928 color: Some(color),
929 outline_color: Some(Rgba::new(255, 255, 255)),
930 };
931
932 let _ = tokio::join!(self.click(point), self.execute(highlight_params));
933 Ok(self)
934 }
935
936 /// Performs a single mouse click event at the point's location and generate a highlight to the nearest element.
937 /// Make sure page.enable_overlay is called first.
938 pub async fn click_with_highlight(&self, point: Point) -> Result<&Self> {
939 let mut color = Rgba::new(255, 0, 0);
940 color.a = Some(1.0);
941 self.click_with_highlight_base(point, color).await?;
942 Ok(self)
943 }
944
945 /// Performs a single mouse click event at the point's location and generate a highlight to the nearest element with the color.
946 /// Make sure page.enable_overlay is called first.
947 pub async fn click_with_highlight_color(&self, point: Point, color: Rgba) -> Result<&Self> {
948 self.click_with_highlight_base(point, color).await?;
949 Ok(self)
950 }
951
952 /// Performs a single mouse click event at the point's location and generate a marker with pure JS. Useful for debugging.
953 pub async fn click_with_marker(&self, point: Point) -> Result<&Self> {
954 let _ = tokio::join!(
955 self.click(point),
956 self.evaluate(generate_marker_js(point.x, point.y))
957 );
958
959 Ok(self)
960 }
961
962 /// Performs a double mouse click event at the point's location.
963 ///
964 /// This scrolls the point into view first, then executes a
965 /// `DispatchMouseEventParams` command of type `MouseLeft` with
966 /// `MousePressed` as single click and then releases the mouse with an
967 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
968 /// `MouseReleased`
969 ///
970 /// Bear in mind that if `click()` triggers a navigation the new page is not
971 /// immediately loaded when `click()` resolves. To wait until navigation is
972 /// finished an additional `wait_for_navigation()` is required:
973 ///
974 /// # Example
975 ///
976 /// Trigger a navigation and wait until the triggered navigation is finished
977 ///
978 /// ```no_run
979 /// # use chromiumoxide::page::Page;
980 /// # use chromiumoxide::error::Result;
981 /// # use chromiumoxide::layout::Point;
982 /// # async fn demo(page: Page, point: Point) -> Result<()> {
983 /// let html = page.click(point).await?.wait_for_navigation().await?.content();
984 /// # Ok(())
985 /// # }
986 /// ```
987 /// ```
988 pub async fn double_click(&self, point: Point) -> Result<&Self> {
989 self.inner.double_click(point).await?;
990 Ok(self)
991 }
992
993 /// Performs a right mouse click event at the point's location.
994 ///
995 /// This scrolls the point into view first, then executes a
996 /// `DispatchMouseEventParams` command of type `MouseLeft` with
997 /// `MousePressed` as single click and then releases the mouse with an
998 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
999 /// `MouseReleased`
1000 ///
1001 /// Bear in mind that if `click()` triggers a navigation the new page is not
1002 /// immediately loaded when `click()` resolves. To wait until navigation is
1003 /// finished an additional `wait_for_navigation()` is required:
1004 ///
1005 /// # Example
1006 ///
1007 /// Trigger a navigation and wait until the triggered navigation is finished
1008 ///
1009 /// ```no_run
1010 /// # use chromiumoxide::page::Page;
1011 /// # use chromiumoxide::error::Result;
1012 /// # use chromiumoxide::layout::Point;
1013 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1014 /// let html = page.right_click(point).await?.wait_for_navigation().await?.content();
1015 /// # Ok(())
1016 /// # }
1017 /// ```
1018 /// ```
1019 pub async fn right_click(&self, point: Point) -> Result<&Self> {
1020 self.inner.right_click(point).await?;
1021 Ok(self)
1022 }
1023
1024 /// Performs a middle mouse click event at the point's location.
1025 ///
1026 /// This scrolls the point into view first, then executes a
1027 /// `DispatchMouseEventParams` command of type `MouseLeft` with
1028 /// `MousePressed` as single click and then releases the mouse with an
1029 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1030 /// `MouseReleased`
1031 ///
1032 /// Bear in mind that if `click()` triggers a navigation the new page is not
1033 /// immediately loaded when `click()` resolves. To wait until navigation is
1034 /// finished an additional `wait_for_navigation()` is required:
1035 ///
1036 /// # Example
1037 ///
1038 /// Trigger a navigation and wait until the triggered navigation is finished
1039 ///
1040 /// ```no_run
1041 /// # use chromiumoxide::page::Page;
1042 /// # use chromiumoxide::error::Result;
1043 /// # use chromiumoxide::layout::Point;
1044 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1045 /// let html = page.middle_click(point).await?.wait_for_navigation().await?.content();
1046 /// # Ok(())
1047 /// # }
1048 /// ```
1049 /// ```
1050 pub async fn middle_click(&self, point: Point) -> Result<&Self> {
1051 self.inner.middle_click(point).await?;
1052 Ok(self)
1053 }
1054
1055 /// Performs a back mouse click event at the point's location.
1056 ///
1057 /// This scrolls the point into view first, then executes a
1058 /// `DispatchMouseEventParams` command of type `MouseBack` with
1059 /// `MousePressed` as single click and then releases the mouse with an
1060 /// additional `DispatchMouseEventParams` of type `MouseBack` with
1061 /// `MouseReleased`
1062 ///
1063 /// Bear in mind that if `click()` triggers a navigation the new page is not
1064 /// immediately loaded when `click()` resolves. To wait until navigation is
1065 /// finished an additional `wait_for_navigation()` is required:
1066 ///
1067 /// # Example
1068 ///
1069 /// Trigger a navigation and wait until the triggered navigation is finished
1070 ///
1071 /// ```no_run
1072 /// # use chromiumoxide::page::Page;
1073 /// # use chromiumoxide::error::Result;
1074 /// # use chromiumoxide::layout::Point;
1075 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1076 /// let html = page.back_click(point).await?.wait_for_navigation().await?.content();
1077 /// # Ok(())
1078 /// # }
1079 /// ```
1080 /// ```
1081 pub async fn back_click(&self, point: Point) -> Result<&Self> {
1082 self.inner.back_click(point).await?;
1083 Ok(self)
1084 }
1085
1086 /// Performs a forward mouse click event at the point's location.
1087 ///
1088 /// This scrolls the point into view first, then executes a
1089 /// `DispatchMouseEventParams` command of type `MouseForward` with
1090 /// `MousePressed` as single click and then releases the mouse with an
1091 /// additional `DispatchMouseEventParams` of type `MouseForward` with
1092 /// `MouseReleased`
1093 ///
1094 /// Bear in mind that if `click()` triggers a navigation the new page is not
1095 /// immediately loaded when `click()` resolves. To wait until navigation is
1096 /// finished an additional `wait_for_navigation()` is required:
1097 ///
1098 /// # Example
1099 ///
1100 /// Trigger a navigation and wait until the triggered navigation is finished
1101 ///
1102 /// ```no_run
1103 /// # use chromiumoxide::page::Page;
1104 /// # use chromiumoxide::error::Result;
1105 /// # use chromiumoxide::layout::Point;
1106 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1107 /// let html = page.forward_click(point).await?.wait_for_navigation().await?.content();
1108 /// # Ok(())
1109 /// # }
1110 /// ```
1111 /// ```
1112 pub async fn forward_click(&self, point: Point) -> Result<&Self> {
1113 self.inner.forward_click(point).await?;
1114 Ok(self)
1115 }
1116
1117 /// Performs a single mouse click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1118 ///
1119 /// This scrolls the point into view first, then executes a
1120 /// `DispatchMouseEventParams` command of type `MouseLeft` with
1121 /// `MousePressed` as single click and then releases the mouse with an
1122 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1123 /// `MouseReleased`
1124 ///
1125 /// Bear in mind that if `click()` triggers a navigation the new page is not
1126 /// immediately loaded when `click()` resolves. To wait until navigation is
1127 /// finished an additional `wait_for_navigation()` is required:
1128 ///
1129 /// # Example
1130 ///
1131 /// Trigger a navigation and wait until the triggered navigation is finished
1132 ///
1133 /// ```no_run
1134 /// # use chromiumoxide::page::Page;
1135 /// # use chromiumoxide::error::Result;
1136 /// # use chromiumoxide::layout::Point;
1137 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1138 /// let html = page.click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1139 /// # Ok(())
1140 /// # }
1141 /// ```
1142 /// ```
1143 pub async fn click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1144 self.inner.click_with_modifier(point, modifiers).await?;
1145 Ok(self)
1146 }
1147
1148 /// Performs a single mouse right click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1149 ///
1150 /// This scrolls the point into view first, then executes a
1151 /// `DispatchMouseEventParams` command of type `MouseLeft` with
1152 /// `MousePressed` as single click and then releases the mouse with an
1153 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1154 /// `MouseReleased`
1155 ///
1156 /// # Example
1157 ///
1158 /// Trigger a navigation and wait until the triggered navigation is finished
1159 ///
1160 /// ```no_run
1161 /// # use chromiumoxide::page::Page;
1162 /// # use chromiumoxide::error::Result;
1163 /// # use chromiumoxide::layout::Point;
1164 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1165 /// let html = page.right_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1166 /// # Ok(())
1167 /// # }
1168 /// ```
1169 /// ```
1170 pub async fn right_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1171 self.inner
1172 .right_click_with_modifier(point, modifiers)
1173 .await?;
1174 Ok(self)
1175 }
1176
1177 /// Performs a single mouse middle click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1178 ///
1179 /// This scrolls the point into view first, then executes a
1180 /// `DispatchMouseEventParams` command of type `MouseLeft` with
1181 /// `MousePressed` as single click and then releases the mouse with an
1182 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1183 /// `MouseReleased`
1184 ///
1185 /// # Example
1186 ///
1187 /// Trigger a navigation and wait until the triggered navigation is finished
1188 ///
1189 /// ```no_run
1190 /// # use chromiumoxide::page::Page;
1191 /// # use chromiumoxide::error::Result;
1192 /// # use chromiumoxide::layout::Point;
1193 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1194 /// let html = page.middle_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1195 /// # Ok(())
1196 /// # }
1197 /// ```
1198 /// ```
1199 pub async fn middle_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1200 self.inner
1201 .middle_click_with_modifier(point, modifiers)
1202 .await?;
1203 Ok(self)
1204 }
1205
1206 /// Performs keyboard typing.
1207 ///
1208 /// # Example
1209 ///
1210 /// ```no_run
1211 /// # use chromiumoxide::page::Page;
1212 /// # use chromiumoxide::error::Result;
1213 /// # use chromiumoxide::layout::Point;
1214 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1215 /// let html = page.type_str("abc").await?.content();
1216 /// # Ok(())
1217 /// # }
1218 /// ```
1219 pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
1220 self.inner.type_str(input).await?;
1221 Ok(self)
1222 }
1223
1224 /// Performs keyboard typing with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1225 ///
1226 /// # Example
1227 ///
1228 /// ```no_run
1229 /// # use chromiumoxide::page::Page;
1230 /// # use chromiumoxide::error::Result;
1231 /// # use chromiumoxide::layout::Point;
1232 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1233 /// let html = page.type_str_with_modifier("abc", Some(1)).await?.content();
1234 /// # Ok(())
1235 /// # }
1236 /// ```
1237 pub async fn type_str_with_modifier(
1238 &self,
1239 input: impl AsRef<str>,
1240 modifiers: Option<i64>,
1241 ) -> Result<&Self> {
1242 self.inner.type_str_with_modifier(input, modifiers).await?;
1243 Ok(self)
1244 }
1245
1246 /// Performs a click-and-drag mouse event from a starting point to a destination.
1247 ///
1248 /// This scrolls both points into view and dispatches a sequence of `DispatchMouseEventParams`
1249 /// commands in order: a `MousePressed` event at the start location, followed by a `MouseMoved`
1250 /// event to the end location, and finally a `MouseReleased` event to complete the drag.
1251 ///
1252 /// This is useful for dragging UI elements, sliders, or simulating mouse gestures.
1253 ///
1254 /// # Example
1255 ///
1256 /// Perform a drag from point A to point B using the Shift modifier:
1257 ///
1258 /// ```no_run
1259 /// # use chromiumoxide::page::Page;
1260 /// # use chromiumoxide::error::Result;
1261 /// # use chromiumoxide::layout::Point;
1262 /// # async fn demo(page: Page, from: Point, to: Point) -> Result<()> {
1263 /// page.click_and_drag_with_modifier(from, to, 8).await?;
1264 /// Ok(())
1265 /// # }
1266 /// ```
1267 pub async fn click_and_drag(&self, from: Point, to: Point) -> Result<&Self> {
1268 self.inner.click_and_drag(from, to, 0).await?;
1269 Ok(self)
1270 }
1271
1272 /// Performs a click-and-drag mouse event from a starting point to a destination,
1273 /// with optional keyboard modifiers: Alt = 1, Ctrl = 2, Meta/Command = 4, Shift = 8 (default: 0).
1274 ///
1275 /// This scrolls both points into view and dispatches a sequence of `DispatchMouseEventParams`
1276 /// commands in order: a `MousePressed` event at the start location, followed by a `MouseMoved`
1277 /// event to the end location, and finally a `MouseReleased` event to complete the drag.
1278 ///
1279 /// This is useful for dragging UI elements, sliders, or simulating mouse gestures.
1280 ///
1281 /// # Example
1282 ///
1283 /// Perform a drag from point A to point B using the Shift modifier:
1284 ///
1285 /// ```no_run
1286 /// # use chromiumoxide::page::Page;
1287 /// # use chromiumoxide::error::Result;
1288 /// # use chromiumoxide::layout::Point;
1289 /// # async fn demo(page: Page, from: Point, to: Point) -> Result<()> {
1290 /// page.click_and_drag_with_modifier(from, to, 8).await?;
1291 /// Ok(())
1292 /// # }
1293 /// ```
1294 pub async fn click_and_drag_with_modifier(
1295 &self,
1296 from: Point,
1297 to: Point,
1298 modifiers: i64,
1299 ) -> Result<&Self> {
1300 self.inner.click_and_drag(from, to, modifiers).await?;
1301 Ok(self)
1302 }
1303
1304 /// Performs a double mouse click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1305 ///
1306 /// This scrolls the point into view first, then executes a
1307 /// `DispatchMouseEventParams` command of type `MouseLeft` with
1308 /// `MousePressed` as single click and then releases the mouse with an
1309 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1310 /// `MouseReleased`
1311 ///
1312 /// Bear in mind that if `click()` triggers a navigation the new page is not
1313 /// immediately loaded when `click()` resolves. To wait until navigation is
1314 /// finished an additional `wait_for_navigation()` is required:
1315 ///
1316 /// # Example
1317 ///
1318 /// Trigger a navigation and wait until the triggered navigation is finished
1319 ///
1320 /// ```no_run
1321 /// # use chromiumoxide::page::Page;
1322 /// # use chromiumoxide::error::Result;
1323 /// # use chromiumoxide::layout::Point;
1324 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1325 /// let html = page.double_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1326 /// # Ok(())
1327 /// # }
1328 /// ```
1329 /// ```
1330 pub async fn double_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1331 self.inner
1332 .double_click_with_modifier(point, modifiers)
1333 .await?;
1334 Ok(self)
1335 }
1336
1337 /// Dispatches a `mouseMoved` event and moves the mouse to the position of
1338 /// the `point` where `Point.x` is the horizontal position of the mouse and
1339 /// `Point.y` the vertical position of the mouse.
1340 pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
1341 self.inner.move_mouse(point).await?;
1342 Ok(self)
1343 }
1344
1345 /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
1346 /// keys.
1347 pub async fn press_key(&self, input: impl AsRef<str>) -> Result<&Self> {
1348 self.inner.press_key(input).await?;
1349 Ok(self)
1350 }
1351
1352 /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
1353 /// keys with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0)..
1354 pub async fn press_key_with_modifier(
1355 &self,
1356 input: impl AsRef<str>,
1357 modifiers: i64,
1358 ) -> Result<&Self> {
1359 self.inner
1360 .press_key_with_modifier(input, Some(modifiers))
1361 .await?;
1362 Ok(self)
1363 }
1364
1365 /// Dispatches a `DragEvent`, moving the element to the given `point`.
1366 ///
1367 /// `point.x` defines the horizontal target, and `point.y` the vertical mouse position.
1368 /// Accepts `drag_type`, `drag_data`, and optional keyboard `modifiers`.
1369 pub async fn drag(
1370 &self,
1371 drag_type: DispatchDragEventType,
1372 point: Point,
1373 drag_data: DragData,
1374 modifiers: Option<i64>,
1375 ) -> Result<&Self> {
1376 self.inner
1377 .drag(drag_type, point, drag_data, modifiers)
1378 .await?;
1379 Ok(self)
1380 }
1381 /// Fetches the entire accessibility tree for the root Document
1382 ///
1383 /// # Example
1384 ///
1385 /// ```no_run
1386 /// # use chromiumoxide::page::Page;
1387 /// # use chromiumoxide::error::Result;
1388 /// # use chromiumoxide::cdp::browser_protocol::page::FrameId;
1389 /// # async fn demo_get_full_ax_tree(page: Page, depth: Option<i64>, frame_id: Option<FrameId>) -> Result<()> {
1390 /// let tree = page.get_full_ax_tree(None, None).await;
1391 /// # Ok(())
1392 /// # }
1393 /// ```
1394 pub async fn get_full_ax_tree(
1395 &self,
1396 depth: Option<i64>,
1397 frame_id: Option<FrameId>,
1398 ) -> Result<GetFullAxTreeReturns> {
1399 self.inner.get_full_ax_tree(depth, frame_id).await
1400 }
1401
1402 /// Fetches the partial accessibility tree for the root Document
1403 ///
1404 /// # Example
1405 ///
1406 /// ```no_run
1407 /// # use chromiumoxide::page::Page;
1408 /// # use chromiumoxide::error::Result;
1409 /// # use chromiumoxide::cdp::browser_protocol::dom::BackendNodeId;
1410 /// # async fn demo_get_partial_ax_tree(page: Page, node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>, backend_node_id: Option<BackendNodeId>, object_id: Option<chromiumoxide_cdp::cdp::js_protocol::runtime::RemoteObjectId>, fetch_relatives: Option<bool>,) -> Result<()> {
1411 /// let tree = page.get_partial_ax_tree(node_id, backend_node_id, object_id, fetch_relatives).await;
1412 /// # Ok(())
1413 /// # }
1414 /// ```
1415 pub async fn get_partial_ax_tree(
1416 &self,
1417 node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
1418 backend_node_id: Option<BackendNodeId>,
1419 object_id: Option<chromiumoxide_cdp::cdp::js_protocol::runtime::RemoteObjectId>,
1420 fetch_relatives: Option<bool>,
1421 ) -> Result<GetPartialAxTreeReturns> {
1422 self.inner
1423 .get_partial_ax_tree(node_id, backend_node_id, object_id, fetch_relatives)
1424 .await
1425 }
1426
1427 /// Dispatches a `mouseWheel` event and moves the mouse to the position of
1428 /// the `point` where `Point.x` is the horizontal position of the mouse and
1429 /// `Point.y` the vertical position of the mouse.
1430 pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
1431 self.inner.scroll(point, delta).await?;
1432 Ok(self)
1433 }
1434
1435 /// Scrolls the current page by the specified horizontal and vertical offsets.
1436 /// This method helps when Chrome version may not support certain CDP dispatch events.
1437 pub async fn scroll_by(
1438 &self,
1439 delta_x: f64,
1440 delta_y: f64,
1441 behavior: ScrollBehavior,
1442 ) -> Result<&Self> {
1443 self.inner.scroll_by(delta_x, delta_y, behavior).await?;
1444 Ok(self)
1445 }
1446
1447 /// Take a screenshot of the current page
1448 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
1449 self.inner.screenshot(params).await
1450 }
1451
1452 /// Take a screenshot of the current page
1453 pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1454 self.inner.print_to_pdf(params).await
1455 }
1456
1457 /// Save a screenshot of the page
1458 ///
1459 /// # Example save a png file of a website
1460 ///
1461 /// ```no_run
1462 /// # use chromiumoxide::page::{Page, ScreenshotParams};
1463 /// # use chromiumoxide::error::Result;
1464 /// # use chromiumoxide_cdp::cdp::browser_protocol::page::CaptureScreenshotFormat;
1465 /// # async fn demo(page: Page) -> Result<()> {
1466 /// page.goto("http://example.com")
1467 /// .await?
1468 /// .save_screenshot(
1469 /// ScreenshotParams::builder()
1470 /// .format(CaptureScreenshotFormat::Png)
1471 /// .full_page(true)
1472 /// .omit_background(true)
1473 /// .build(),
1474 /// "example.png",
1475 /// )
1476 /// .await?;
1477 /// # Ok(())
1478 /// # }
1479 /// ```
1480 pub async fn save_screenshot(
1481 &self,
1482 params: impl Into<ScreenshotParams>,
1483 output: impl AsRef<Path>,
1484 ) -> Result<Vec<u8>> {
1485 let img = self.screenshot(params).await?;
1486 utils::write(output.as_ref(), &img).await?;
1487 Ok(img)
1488 }
1489
1490 /// Print the current page as pdf.
1491 ///
1492 /// See [`PrintToPdfParams`]
1493 ///
1494 /// # Note Generating a pdf is currently only supported in Chrome headless.
1495 pub async fn pdf(&self, params: PrintToPdfParams) -> Result<Vec<u8>> {
1496 let res = self.execute(params).await?;
1497 Ok(utils::base64::decode(&res.data)?)
1498 }
1499
1500 /// Save the current page as pdf as file to the `output` path and return the
1501 /// pdf contents.
1502 ///
1503 /// # Note Generating a pdf is currently only supported in Chrome headless.
1504 pub async fn save_pdf(
1505 &self,
1506 opts: PrintToPdfParams,
1507 output: impl AsRef<Path>,
1508 ) -> Result<Vec<u8>> {
1509 let pdf = self.pdf(opts).await?;
1510 utils::write(output.as_ref(), &pdf).await?;
1511 Ok(pdf)
1512 }
1513
1514 /// Brings page to front (activates tab)
1515 pub async fn bring_to_front(&self) -> Result<&Self> {
1516 self.execute(BringToFrontParams::default()).await?;
1517 Ok(self)
1518 }
1519
1520 /// Turns on virtual time for all frames (replacing real-time with a synthetic time source) and sets the current virtual time policy. Note this supersedes any previous time budget.
1521 pub async fn enable_virtual_time_with_budget(
1522 &self,
1523 budget_ms: f64,
1524 policy: Option<chromiumoxide_cdp::cdp::browser_protocol::emulation::VirtualTimePolicy>,
1525 max_virtual_time_task_starvation_count: Option<i64>,
1526 initial_virtual_time: Option<TimeSinceEpoch>,
1527 ) -> Result<&Self> {
1528 let params =
1529 chromiumoxide_cdp::cdp::browser_protocol::emulation::SetVirtualTimePolicyParams {
1530 policy: policy.unwrap_or(
1531 chromiumoxide_cdp::cdp::browser_protocol::emulation::VirtualTimePolicy::Advance,
1532 ),
1533 budget: Some(budget_ms),
1534 max_virtual_time_task_starvation_count: max_virtual_time_task_starvation_count
1535 .or(Some(10_000)),
1536 initial_virtual_time,
1537 };
1538 self.execute(params).await?;
1539 Ok(self)
1540 }
1541
1542 /// Emulates hardware concurrency.
1543 pub async fn emulate_hardware_concurrency(&self, hardware_concurrency: i64) -> Result<&Self> {
1544 self.execute(SetHardwareConcurrencyOverrideParams::new(
1545 hardware_concurrency,
1546 ))
1547 .await?;
1548 Ok(self)
1549 }
1550
1551 /// Emulates the given media type or media feature for CSS media queries
1552 pub async fn emulate_media_features(&self, features: Vec<MediaFeature>) -> Result<&Self> {
1553 self.execute(SetEmulatedMediaParams::builder().features(features).build())
1554 .await?;
1555 Ok(self)
1556 }
1557
1558 /// Changes the CSS media type of the page
1559 // Based on https://pptr.dev/api/puppeteer.page.emulatemediatype
1560 pub async fn emulate_media_type(
1561 &self,
1562 media_type: impl Into<MediaTypeParams>,
1563 ) -> Result<&Self> {
1564 self.execute(
1565 SetEmulatedMediaParams::builder()
1566 .media(media_type.into())
1567 .build(),
1568 )
1569 .await?;
1570 Ok(self)
1571 }
1572
1573 /// Overrides default host system timezone
1574 pub async fn emulate_timezone(
1575 &self,
1576 timezoune_id: impl Into<SetTimezoneOverrideParams>,
1577 ) -> Result<&Self> {
1578 self.execute(timezoune_id.into()).await?;
1579 Ok(self)
1580 }
1581
1582 /// Overrides default host system locale with the specified one
1583 pub async fn emulate_locale(
1584 &self,
1585 locale: impl Into<SetLocaleOverrideParams>,
1586 ) -> Result<&Self> {
1587 self.execute(locale.into()).await?;
1588 Ok(self)
1589 }
1590
1591 /// Overrides default viewport
1592 pub async fn emulate_viewport(
1593 &self,
1594 viewport: impl Into<SetDeviceMetricsOverrideParams>,
1595 ) -> Result<&Self> {
1596 self.execute(viewport.into()).await?;
1597 Ok(self)
1598 }
1599
1600 /// Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position unavailable.
1601 pub async fn emulate_geolocation(
1602 &self,
1603 geolocation: impl Into<SetGeolocationOverrideParams>,
1604 ) -> Result<&Self> {
1605 self.execute(geolocation.into()).await?;
1606 Ok(self)
1607 }
1608
1609 /// Reloads given page
1610 ///
1611 /// To reload ignoring cache run:
1612 /// ```no_run
1613 /// # use chromiumoxide::page::Page;
1614 /// # use chromiumoxide::error::Result;
1615 /// # use chromiumoxide_cdp::cdp::browser_protocol::page::ReloadParams;
1616 /// # async fn demo(page: Page) -> Result<()> {
1617 /// page.execute(ReloadParams::builder().ignore_cache(true).build()).await?;
1618 /// page.wait_for_navigation().await?;
1619 /// # Ok(())
1620 /// # }
1621 /// ```
1622 pub async fn reload(&self) -> Result<&Self> {
1623 self.execute(ReloadParams::default()).await?;
1624 self.wait_for_navigation().await
1625 }
1626
1627 /// Reloads given page without waiting for navigation.
1628 ///
1629 /// To reload ignoring cache run:
1630 /// ```no_run
1631 /// # use chromiumoxide::page::Page;
1632 /// # use chromiumoxide::error::Result;
1633 /// # use chromiumoxide_cdp::cdp::browser_protocol::page::ReloadParams;
1634 /// # async fn demo(page: Page) -> Result<()> {
1635 /// page.execute(ReloadParams::builder().ignore_cache(true).build()).await?;
1636 /// # Ok(())
1637 /// # }
1638 /// ```
1639 pub async fn reload_no_wait(&self) -> Result<&Self> {
1640 self.execute(ReloadParams::default()).await?;
1641 Ok(self)
1642 }
1643
1644 /// Enables ServiceWorkers. Disabled by default.
1645 /// See https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-enable
1646 pub async fn enable_service_workers(&self) -> Result<&Self> {
1647 self.execute(browser_protocol::service_worker::EnableParams::default())
1648 .await?;
1649 Ok(self)
1650 }
1651
1652 /// Disables ServiceWorker. Disabled by default.
1653 /// See https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-enable
1654 pub async fn disable_service_workers(&self) -> Result<&Self> {
1655 self.execute(browser_protocol::service_worker::DisableParams::default())
1656 .await?;
1657 Ok(self)
1658 }
1659
1660 /// Enables Performances. Disabled by default.
1661 /// See https://chromedevtools.github.io/devtools-protocol/tot/Performance#method-enable
1662 pub async fn enable_performance(&self) -> Result<&Self> {
1663 self.execute(browser_protocol::performance::EnableParams::default())
1664 .await?;
1665 Ok(self)
1666 }
1667
1668 /// Disables Performances. Disabled by default.
1669 /// See https://chromedevtools.github.io/devtools-protocol/tot/Performance#method-disable
1670 pub async fn disable_performance(&self) -> Result<&Self> {
1671 self.execute(browser_protocol::performance::DisableParams::default())
1672 .await?;
1673 Ok(self)
1674 }
1675
1676 /// Enables Overlay domain notifications. Disabled by default.
1677 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-enable
1678 pub async fn enable_overlay(&self) -> Result<&Self> {
1679 self.execute(browser_protocol::overlay::EnableParams::default())
1680 .await?;
1681 Ok(self)
1682 }
1683
1684 /// Disables Overlay domain notifications. Disabled by default.
1685 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-enable
1686 pub async fn disable_overlay(&self) -> Result<&Self> {
1687 self.execute(browser_protocol::overlay::DisableParams::default())
1688 .await?;
1689 Ok(self)
1690 }
1691
1692 /// Enables Overlay domain paint rectangles. Disabled by default.
1693 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-setShowPaintRects
1694 pub async fn enable_paint_rectangles(&self) -> Result<&Self> {
1695 self.execute(browser_protocol::overlay::SetShowPaintRectsParams::new(
1696 true,
1697 ))
1698 .await?;
1699 Ok(self)
1700 }
1701
1702 /// Disabled Overlay domain paint rectangles. Disabled by default.
1703 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-setShowPaintRects
1704 pub async fn disable_paint_rectangles(&self) -> Result<&Self> {
1705 self.execute(browser_protocol::overlay::SetShowPaintRectsParams::new(
1706 false,
1707 ))
1708 .await?;
1709 Ok(self)
1710 }
1711
1712 /// Enables log domain. Disabled by default.
1713 ///
1714 /// Sends the entries collected so far to the client by means of the
1715 /// entryAdded notification.
1716 ///
1717 /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-enable
1718 pub async fn enable_log(&self) -> Result<&Self> {
1719 self.execute(browser_protocol::log::EnableParams::default())
1720 .await?;
1721 Ok(self)
1722 }
1723
1724 /// Disables log domain
1725 ///
1726 /// Prevents further log entries from being reported to the client
1727 ///
1728 /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-disable
1729 pub async fn disable_log(&self) -> Result<&Self> {
1730 self.execute(browser_protocol::log::DisableParams::default())
1731 .await?;
1732 Ok(self)
1733 }
1734
1735 /// Enables runtime domain. Activated by default.
1736 pub async fn enable_runtime(&self) -> Result<&Self> {
1737 self.execute(js_protocol::runtime::EnableParams::default())
1738 .await?;
1739 Ok(self)
1740 }
1741
1742 /// Enables the network.
1743 pub async fn enable_network(&self) -> Result<&Self> {
1744 self.execute(browser_protocol::network::EnableParams::default())
1745 .await?;
1746 Ok(self)
1747 }
1748
1749 /// Disables the network.
1750 pub async fn disable_network(&self) -> Result<&Self> {
1751 self.execute(browser_protocol::network::DisableParams::default())
1752 .await?;
1753 Ok(self)
1754 }
1755
1756 /// Disables runtime domain.
1757 pub async fn disable_runtime(&self) -> Result<&Self> {
1758 self.execute(js_protocol::runtime::DisableParams::default())
1759 .await?;
1760 Ok(self)
1761 }
1762
1763 /// Enables Debugger. Enabled by default.
1764 pub async fn enable_debugger(&self) -> Result<&Self> {
1765 self.execute(js_protocol::debugger::EnableParams::default())
1766 .await?;
1767 Ok(self)
1768 }
1769
1770 /// Disables Debugger.
1771 pub async fn disable_debugger(&self) -> Result<&Self> {
1772 self.execute(js_protocol::debugger::DisableParams::default())
1773 .await?;
1774 Ok(self)
1775 }
1776
1777 /// Enables page domain notifications. Enabled by default.
1778 /// See https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-enable
1779 pub async fn enable_page(&self) -> Result<&Self> {
1780 self.execute(browser_protocol::page::EnableParams::default())
1781 .await?;
1782 Ok(self)
1783 }
1784
1785 /// Disables page domain notifications. Disabled by default.
1786 /// See https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-disable
1787 pub async fn disable_page(&self) -> Result<&Self> {
1788 self.execute(browser_protocol::page::EnableParams::default())
1789 .await?;
1790 Ok(self)
1791 }
1792
1793 // Enables DOM agent
1794 pub async fn enable_dom(&self) -> Result<&Self> {
1795 self.execute(browser_protocol::dom::EnableParams::default())
1796 .await?;
1797 Ok(self)
1798 }
1799
1800 // Disables DOM agent
1801 pub async fn disable_dom(&self) -> Result<&Self> {
1802 self.execute(browser_protocol::dom::DisableParams::default())
1803 .await?;
1804 Ok(self)
1805 }
1806
1807 // Enables the CSS agent
1808 pub async fn enable_css(&self) -> Result<&Self> {
1809 self.execute(browser_protocol::css::EnableParams::default())
1810 .await?;
1811 Ok(self)
1812 }
1813
1814 // Disables the CSS agent
1815 pub async fn disable_css(&self) -> Result<&Self> {
1816 self.execute(browser_protocol::css::DisableParams::default())
1817 .await?;
1818 Ok(self)
1819 }
1820
1821 /// Block urls from networking.
1822 ///
1823 /// Prevents further networking
1824 ///
1825 /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1826 pub async fn set_blocked_urls(&self, urls: Vec<String>) -> Result<&Self> {
1827 self.execute(SetBlockedUrLsParams::new(urls)).await?;
1828 Ok(self)
1829 }
1830
1831 /// Force the page stop all navigations and pending resource fetches.
1832 /// See https://chromedevtools.github.io/devtools-protocol/tot/Page#method-stopLoading
1833 pub async fn stop_loading(&self) -> Result<&Self> {
1834 self.execute(browser_protocol::page::StopLoadingParams::default())
1835 .await?;
1836 Ok(self)
1837 }
1838
1839 /// Block all urls from networking.
1840 ///
1841 /// Prevents further networking
1842 ///
1843 /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1844 pub async fn block_all_urls(&self) -> Result<&Self> {
1845 self.execute(SetBlockedUrLsParams::new(vec!["*".into()]))
1846 .await?;
1847 Ok(self)
1848 }
1849
1850 /// Force the page stop all navigations and pending resource fetches for the rest of the page life.
1851 /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1852 /// See https://chromedevtools.github.io/devtools-protocol/tot/Page#method-stopLoading
1853 pub async fn force_stop_all(&self) -> Result<&Self> {
1854 let _ = tokio::join!(
1855 self.stop_loading(),
1856 self.set_blocked_urls(vec!["*".to_string()])
1857 );
1858 Ok(self)
1859 }
1860
1861 /// Activates (focuses) the target.
1862 pub async fn activate(&self) -> Result<&Self> {
1863 self.inner.activate().await?;
1864 Ok(self)
1865 }
1866
1867 /// Returns all cookies that match the tab's current URL.
1868 pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
1869 Ok(self
1870 .execute(GetCookiesParams::default())
1871 .await?
1872 .result
1873 .cookies)
1874 }
1875
1876 /// Set a single cookie
1877 ///
1878 /// This fails if the cookie's url or if not provided, the page's url is
1879 /// `about:blank` or a `data:` url.
1880 ///
1881 /// # Example
1882 /// ```no_run
1883 /// # use chromiumoxide::page::Page;
1884 /// # use chromiumoxide::error::Result;
1885 /// # use chromiumoxide_cdp::cdp::browser_protocol::network::CookieParam;
1886 /// # async fn demo(page: Page) -> Result<()> {
1887 /// page.set_cookie(CookieParam::new("Cookie-name", "Cookie-value")).await?;
1888 /// # Ok(())
1889 /// # }
1890 /// ```
1891 pub async fn set_cookie(&self, cookie: impl Into<CookieParam>) -> Result<&Self> {
1892 let mut cookie = cookie.into();
1893 if let Some(url) = cookie.url.as_ref() {
1894 validate_cookie_url(url)?;
1895 } else {
1896 let url = self
1897 .url()
1898 .await?
1899 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1900 validate_cookie_url(&url)?;
1901 if url.starts_with("http") {
1902 cookie.url = Some(url);
1903 }
1904 }
1905 self.execute(DeleteCookiesParams::from_cookie(&cookie))
1906 .await?;
1907 self.execute(SetCookiesParams::new(vec![cookie])).await?;
1908 Ok(self)
1909 }
1910
1911 /// Set all the cookies
1912 pub async fn set_cookies(&self, mut cookies: Vec<CookieParam>) -> Result<&Self> {
1913 let url = self
1914 .url()
1915 .await?
1916 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1917 let is_http = url.starts_with("http");
1918 if !is_http {
1919 validate_cookie_url(&url)?;
1920 }
1921
1922 for cookie in &mut cookies {
1923 if let Some(url) = cookie.url.as_ref() {
1924 validate_cookie_url(url)?;
1925 } else if is_http {
1926 cookie.url = Some(url.clone());
1927 }
1928 }
1929 self.delete_cookies_unchecked(cookies.iter().map(DeleteCookiesParams::from_cookie))
1930 .await?;
1931
1932 self.execute(SetCookiesParams::new(cookies)).await?;
1933 Ok(self)
1934 }
1935
1936 /// Delete a single cookie
1937 pub async fn delete_cookie(&self, cookie: impl Into<DeleteCookiesParams>) -> Result<&Self> {
1938 let mut cookie = cookie.into();
1939 if cookie.url.is_none() {
1940 let url = self
1941 .url()
1942 .await?
1943 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1944 if url.starts_with("http") {
1945 cookie.url = Some(url);
1946 }
1947 }
1948 self.execute(cookie).await?;
1949 Ok(self)
1950 }
1951
1952 /// Delete all the cookies
1953 pub async fn delete_cookies(&self, mut cookies: Vec<DeleteCookiesParams>) -> Result<&Self> {
1954 let mut url: Option<(String, bool)> = None;
1955 for cookie in &mut cookies {
1956 if cookie.url.is_none() {
1957 if let Some((url, is_http)) = url.as_ref() {
1958 if *is_http {
1959 cookie.url = Some(url.clone())
1960 }
1961 } else {
1962 let page_url = self
1963 .url()
1964 .await?
1965 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1966 let is_http = page_url.starts_with("http");
1967 if is_http {
1968 cookie.url = Some(page_url.clone())
1969 }
1970 url = Some((page_url, is_http));
1971 }
1972 }
1973 }
1974 self.delete_cookies_unchecked(cookies.into_iter()).await?;
1975 Ok(self)
1976 }
1977
1978 /// Convenience method that prevents another channel roundtrip to get the
1979 /// url and validate it
1980 async fn delete_cookies_unchecked(
1981 &self,
1982 cookies: impl Iterator<Item = DeleteCookiesParams>,
1983 ) -> Result<&Self> {
1984 // NOTE: the buffer size is arbitrary
1985 let mut cmds = stream::iter(cookies.into_iter().map(|cookie| self.execute(cookie)))
1986 .buffer_unordered(5);
1987 while let Some(resp) = cmds.next().await {
1988 resp?;
1989 }
1990 Ok(self)
1991 }
1992
1993 /// Returns the title of the document.
1994 pub async fn get_title(&self) -> Result<Option<String>> {
1995 let result = self.evaluate("document.title").await?;
1996
1997 let title: String = result.into_value()?;
1998
1999 if title.is_empty() {
2000 Ok(None)
2001 } else {
2002 Ok(Some(title))
2003 }
2004 }
2005
2006 /// Retrieve current values of run-time metrics. Enable the 'collect_metrics flag to auto init 'Performance.enable'.
2007 pub async fn metrics(&self) -> Result<Vec<Metric>> {
2008 Ok(self
2009 .execute(GetMetricsParams::default())
2010 .await?
2011 .result
2012 .metrics)
2013 }
2014
2015 /// Returns metrics relating to the layout of the page
2016 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
2017 self.inner.layout_metrics().await
2018 }
2019
2020 /// Start a background guard that counts **wire bytes** (compressed on the network)
2021 /// and force-stops the page once `max_bytes` is exceeded.
2022 ///
2023 /// - Uses CDP Network.dataReceived -> `encodedDataLength`
2024 /// - Calls `Page.stopLoading()` when the cap is hit
2025 /// - Optionally closes the tab after stopping
2026 ///
2027 /// Returns a JoinHandle you can `.await` or just detach.
2028 pub async fn start_wire_bytes_budget_background(
2029 &self,
2030 max_bytes: u64,
2031 close_on_exceed: Option<bool>,
2032 enable_networking: Option<bool>,
2033 sent_and_received: Option<bool>,
2034 ) -> Result<tokio::task::JoinHandle<()>> {
2035 // prevent re-enabling the network - by default this should be enabled.
2036 if enable_networking.unwrap_or(false) {
2037 let _ = self.enable_network().await;
2038 }
2039
2040 let close_on_exceed = close_on_exceed.unwrap_or_default();
2041 let track_all = sent_and_received.unwrap_or_default();
2042
2043 let mut rx = self
2044 .event_listener::<crate::page::browser_protocol::network::EventDataReceived>()
2045 .await
2046 .map_err(|e| CdpError::msg(format!("event_listener failed: {e}")))?;
2047
2048 let page = self.clone();
2049
2050 let handle = tokio::spawn(async move {
2051 let mut total_bytes: u64 = 0;
2052
2053 while let Some(ev) = rx.next().await {
2054 let encoded = ev.encoded_data_length.max(0) as u64;
2055 let data_length = if track_all {
2056 ev.data_length.max(0) as u64
2057 } else {
2058 0
2059 };
2060 total_bytes = total_bytes.saturating_add(encoded + data_length);
2061 if total_bytes > max_bytes {
2062 let _ = page.force_stop_all().await;
2063 if close_on_exceed {
2064 let _ = page.close().await;
2065 }
2066 break;
2067 }
2068 }
2069 });
2070
2071 Ok(handle)
2072 }
2073
2074 /// Start a guard that counts **wire bytes** (compressed on the network)
2075 /// and force-stops the page once `max_bytes` is exceeded.
2076 ///
2077 /// - Uses CDP Network.dataReceived -> `encodedDataLength`
2078 /// - Calls `Page.stopLoading()` when the cap is hit
2079 /// - Optionally closes the tab after stopping
2080 ///
2081 /// Returns a JoinHandle you can `.await` or just detach.
2082 pub async fn start_wire_bytes_budget(
2083 &self,
2084 max_bytes: u64,
2085 close_on_exceed: Option<bool>,
2086 enable_networking: Option<bool>,
2087 ) -> Result<()> {
2088 // prevent re-enabling the network - by default this should be enabled.
2089 if enable_networking.unwrap_or(false) {
2090 let _ = self.enable_network().await;
2091 }
2092
2093 let close_on_exceed = close_on_exceed.unwrap_or_default();
2094 let mut rx = self
2095 .event_listener::<crate::page::browser_protocol::network::EventDataReceived>()
2096 .await
2097 .map_err(|e| CdpError::msg(format!("event_listener failed: {e}")))?;
2098
2099 let page = self.clone();
2100
2101 let mut total_bytes: u64 = 0;
2102
2103 while let Some(ev) = rx.next().await {
2104 total_bytes = total_bytes.saturating_add(ev.encoded_data_length.max(0) as u64);
2105 if total_bytes > max_bytes {
2106 let _ = page.force_stop_all().await;
2107 if close_on_exceed {
2108 let _ = page.close().await;
2109 }
2110 break;
2111 }
2112 }
2113
2114 Ok(())
2115 }
2116
2117 /// This evaluates strictly as expression.
2118 ///
2119 /// Same as `Page::evaluate` but no fallback or any attempts to detect
2120 /// whether the expression is actually a function. However you can
2121 /// submit a function evaluation string:
2122 ///
2123 /// # Example Evaluate function call as expression
2124 ///
2125 /// This will take the arguments `(1,2)` and will call the function
2126 ///
2127 /// ```no_run
2128 /// # use chromiumoxide::page::Page;
2129 /// # use chromiumoxide::error::Result;
2130 /// # async fn demo(page: Page) -> Result<()> {
2131 /// let sum: usize = page
2132 /// .evaluate_expression("((a,b) => {return a + b;})(1,2)")
2133 /// .await?
2134 /// .into_value()?;
2135 /// assert_eq!(sum, 3);
2136 /// # Ok(())
2137 /// # }
2138 /// ```
2139 pub async fn evaluate_expression(
2140 &self,
2141 evaluate: impl Into<EvaluateParams>,
2142 ) -> Result<EvaluationResult> {
2143 self.inner.evaluate_expression(evaluate).await
2144 }
2145
2146 /// Evaluates an expression or function in the page's context and returns
2147 /// the result.
2148 ///
2149 /// In contrast to `Page::evaluate_expression` this is capable of handling
2150 /// function calls and expressions alike. This takes anything that is
2151 /// `Into<Evaluation>`. When passing a `String` or `str`, this will try to
2152 /// detect whether it is a function or an expression. JS function detection
2153 /// is not very sophisticated but works for general cases (`(async)
2154 /// functions` and arrow functions). If you want a string statement
2155 /// specifically evaluated as expression or function either use the
2156 /// designated functions `Page::evaluate_function` or
2157 /// `Page::evaluate_expression` or use the proper parameter type for
2158 /// `Page::execute`: `EvaluateParams` for strict expression evaluation or
2159 /// `CallFunctionOnParams` for strict function evaluation.
2160 ///
2161 /// If you don't trust the js function detection and are not sure whether
2162 /// the statement is an expression or of type function (arrow functions: `()
2163 /// => {..}`), you should pass it as `EvaluateParams` and set the
2164 /// `EvaluateParams::eval_as_function_fallback` option. This will first
2165 /// try to evaluate it as expression and if the result comes back
2166 /// evaluated as `RemoteObjectType::Function` it will submit the
2167 /// statement again but as function:
2168 ///
2169 /// # Example Evaluate function statement as expression with fallback
2170 /// option
2171 ///
2172 /// ```no_run
2173 /// # use chromiumoxide::page::Page;
2174 /// # use chromiumoxide::error::Result;
2175 /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{EvaluateParams, RemoteObjectType};
2176 /// # async fn demo(page: Page) -> Result<()> {
2177 /// let eval = EvaluateParams::builder().expression("() => {return 42;}");
2178 /// // this will fail because the `EvaluationResult` returned by the browser will be
2179 /// // of type `Function`
2180 /// let result = page
2181 /// .evaluate(eval.clone().build().unwrap())
2182 /// .await?;
2183 /// assert_eq!(result.object().r#type, RemoteObjectType::Function);
2184 /// assert!(result.into_value::<usize>().is_err());
2185 ///
2186 /// // This will also fail on the first try but it detects that the browser evaluated the
2187 /// // statement as function and then evaluate it again but as function
2188 /// let sum: usize = page
2189 /// .evaluate(eval.eval_as_function_fallback(true).build().unwrap())
2190 /// .await?
2191 /// .into_value()?;
2192 /// # Ok(())
2193 /// # }
2194 /// ```
2195 ///
2196 /// # Example Evaluate basic expression
2197 /// ```no_run
2198 /// # use chromiumoxide::page::Page;
2199 /// # use chromiumoxide::error::Result;
2200 /// # async fn demo(page: Page) -> Result<()> {
2201 /// let sum:usize = page.evaluate("1 + 2").await?.into_value()?;
2202 /// assert_eq!(sum, 3);
2203 /// # Ok(())
2204 /// # }
2205 /// ```
2206 pub async fn evaluate(&self, evaluate: impl Into<Evaluation>) -> Result<EvaluationResult> {
2207 match evaluate.into() {
2208 Evaluation::Expression(mut expr) => {
2209 if expr.context_id.is_none() {
2210 expr.context_id = self.execution_context().await?;
2211 }
2212 let fallback = expr.eval_as_function_fallback.and_then(|p| {
2213 if p {
2214 Some(expr.clone())
2215 } else {
2216 None
2217 }
2218 });
2219 let res = self.evaluate_expression(expr).await?;
2220
2221 if res.object().r#type == RemoteObjectType::Function {
2222 // expression was actually a function
2223 if let Some(fallback) = fallback {
2224 return self.evaluate_function(fallback).await;
2225 }
2226 }
2227 Ok(res)
2228 }
2229 Evaluation::Function(fun) => Ok(self.evaluate_function(fun).await?),
2230 }
2231 }
2232
2233 /// Eexecutes a function withinthe page's context and returns the result.
2234 ///
2235 /// # Example Evaluate a promise
2236 /// This will wait until the promise resolves and then returns the result.
2237 /// ```no_run
2238 /// # use chromiumoxide::page::Page;
2239 /// # use chromiumoxide::error::Result;
2240 /// # async fn demo(page: Page) -> Result<()> {
2241 /// let sum:usize = page.evaluate_function("() => Promise.resolve(1 + 2)").await?.into_value()?;
2242 /// assert_eq!(sum, 3);
2243 /// # Ok(())
2244 /// # }
2245 /// ```
2246 ///
2247 /// # Example Evaluate an async function
2248 /// ```no_run
2249 /// # use chromiumoxide::page::Page;
2250 /// # use chromiumoxide::error::Result;
2251 /// # async fn demo(page: Page) -> Result<()> {
2252 /// let val:usize = page.evaluate_function("async function() {return 42;}").await?.into_value()?;
2253 /// assert_eq!(val, 42);
2254 /// # Ok(())
2255 /// # }
2256 /// ```
2257 /// # Example Construct a function call
2258 ///
2259 /// ```no_run
2260 /// # use chromiumoxide::page::Page;
2261 /// # use chromiumoxide::error::Result;
2262 /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{CallFunctionOnParams, CallArgument};
2263 /// # async fn demo(page: Page) -> Result<()> {
2264 /// let call = CallFunctionOnParams::builder()
2265 /// .function_declaration(
2266 /// "(a,b) => { return a + b;}"
2267 /// )
2268 /// .argument(
2269 /// CallArgument::builder()
2270 /// .value(serde_json::json!(1))
2271 /// .build(),
2272 /// )
2273 /// .argument(
2274 /// CallArgument::builder()
2275 /// .value(serde_json::json!(2))
2276 /// .build(),
2277 /// )
2278 /// .build()
2279 /// .unwrap();
2280 /// let sum:usize = page.evaluate_function(call).await?.into_value()?;
2281 /// assert_eq!(sum, 3);
2282 /// # Ok(())
2283 /// # }
2284 /// ```
2285 pub async fn evaluate_function(
2286 &self,
2287 evaluate: impl Into<CallFunctionOnParams>,
2288 ) -> Result<EvaluationResult> {
2289 self.inner.evaluate_function(evaluate).await
2290 }
2291
2292 /// Returns the default execution context identifier of this page that
2293 /// represents the context for JavaScript execution.
2294 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
2295 self.inner.execution_context().await
2296 }
2297
2298 /// Returns the secondary execution context identifier of this page that
2299 /// represents the context for JavaScript execution for manipulating the
2300 /// DOM.
2301 ///
2302 /// See `Page::set_contents`
2303 pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
2304 self.inner.secondary_execution_context().await
2305 }
2306
2307 #[cfg(feature = "cache")]
2308 /// Spawn a cache listener to store resources to memory. This does nothing without the 'cache' flag.
2309 pub async fn spawn_cache_listener(&self, auth: Option<String>) -> Result<&Self> {
2310 use crate::cache::spawn_response_cache_listener;
2311 spawn_response_cache_listener(self.clone(), auth).await?;
2312 Ok(self)
2313 }
2314
2315 #[cfg(feature = "cache")]
2316 /// Spawn a cache intercepter to load resources to memory. This does nothing without the 'cache' flag.
2317 pub async fn spawn_cache_intercepter(
2318 &self,
2319 auth: Option<String>,
2320 policy: Option<crate::cache::BasicCachePolicy>,
2321 ) -> Result<&Self> {
2322 use crate::cache::spawn_fetch_cache_interceptor;
2323 spawn_fetch_cache_interceptor(self.clone(), auth, policy).await?;
2324 Ok(self)
2325 }
2326
2327 #[cfg(not(feature = "cache"))]
2328 /// Spawn a cache listener to store resources to memory. This does nothing without the 'cache' flag.
2329 pub async fn spawn_cache_listener(&self, _auth: Option<String>) -> Result<&Self> {
2330 Ok(self)
2331 }
2332
2333 pub async fn frame_execution_context(
2334 &self,
2335 frame_id: FrameId,
2336 ) -> Result<Option<ExecutionContextId>> {
2337 self.inner.frame_execution_context(frame_id).await
2338 }
2339
2340 pub async fn frame_secondary_execution_context(
2341 &self,
2342 frame_id: FrameId,
2343 ) -> Result<Option<ExecutionContextId>> {
2344 self.inner.frame_secondary_execution_context(frame_id).await
2345 }
2346
2347 /// Evaluates given script in every frame upon creation (before loading
2348 /// frame's scripts)
2349 pub async fn evaluate_on_new_document(
2350 &self,
2351 script: impl Into<AddScriptToEvaluateOnNewDocumentParams>,
2352 ) -> Result<ScriptIdentifier> {
2353 Ok(self.execute(script.into()).await?.result.identifier)
2354 }
2355
2356 /// Set the content of the frame.
2357 ///
2358 /// # Example
2359 /// ```no_run
2360 /// # use chromiumoxide::page::Page;
2361 /// # use chromiumoxide::error::Result;
2362 /// # async fn demo(page: Page) -> Result<()> {
2363 /// page.set_content("<body>
2364 /// <h1>This was set via chromiumoxide</h1>
2365 /// </body>").await?;
2366 /// # Ok(())
2367 /// # }
2368 /// ```
2369 pub async fn set_content(&self, html: impl AsRef<str>) -> Result<&Self> {
2370 if let Ok(mut call) = CallFunctionOnParams::builder()
2371 .function_declaration(
2372 "(html) => {
2373 document.open();
2374 document.write(html);
2375 document.close();
2376 }",
2377 )
2378 .argument(
2379 CallArgument::builder()
2380 .value(serde_json::json!(html.as_ref()))
2381 .build(),
2382 )
2383 .build()
2384 {
2385 call.execution_context_id = self
2386 .inner
2387 .execution_context_for_world(None, DOMWorldKind::Secondary)
2388 .await?;
2389 self.evaluate_function(call).await?;
2390 }
2391 // relying that document.open() will reset frame lifecycle with "init"
2392 // lifecycle event. @see https://crrev.com/608658
2393 self.wait_for_navigation().await
2394 }
2395
2396 /// Returns the HTML content of the page.
2397 pub async fn content(&self) -> Result<String> {
2398 Ok(self.evaluate(OUTER_HTML).await?.into_value()?)
2399 }
2400
2401 /// Returns the HTML content of the page
2402 pub async fn content_bytes(&self) -> Result<Vec<u8>> {
2403 Ok(self.evaluate(OUTER_HTML).await?.into_bytes()?)
2404 }
2405
2406 /// Returns the full serialized content of the page (HTML or XML)
2407 pub async fn content_bytes_xml(&self) -> Result<Vec<u8>> {
2408 Ok(self.evaluate(FULL_XML_SERIALIZER_JS).await?.into_bytes()?)
2409 }
2410
2411 /// Returns the HTML outer html of the page
2412 pub async fn outer_html_bytes(&self) -> Result<Vec<u8>> {
2413 Ok(self.outer_html().await?.into())
2414 }
2415
2416 /// Enable Chrome's experimental ad filter on all sites.
2417 pub async fn set_ad_blocking_enabled(&self, enabled: bool) -> Result<&Self> {
2418 self.execute(SetAdBlockingEnabledParams::new(enabled))
2419 .await?;
2420 Ok(self)
2421 }
2422
2423 /// Start to screencast a frame.
2424 pub async fn start_screencast(
2425 &self,
2426 params: impl Into<StartScreencastParams>,
2427 ) -> Result<&Self> {
2428 self.execute(params.into()).await?;
2429 Ok(self)
2430 }
2431
2432 /// Acknowledges that a screencast frame has been received by the frontend.
2433 pub async fn ack_screencast(
2434 &self,
2435 params: impl Into<ScreencastFrameAckParams>,
2436 ) -> Result<&Self> {
2437 self.execute(params.into()).await?;
2438 Ok(self)
2439 }
2440
2441 /// Stop screencast a frame.
2442 pub async fn stop_screencast(&self, params: impl Into<StopScreencastParams>) -> Result<&Self> {
2443 self.execute(params.into()).await?;
2444 Ok(self)
2445 }
2446
2447 /// Returns source for the script with given id.
2448 ///
2449 /// Debugger must be enabled.
2450 pub async fn get_script_source(&self, script_id: impl Into<String>) -> Result<String> {
2451 Ok(self
2452 .execute(GetScriptSourceParams::new(ScriptId::from(script_id.into())))
2453 .await?
2454 .result
2455 .script_source)
2456 }
2457}
2458
2459impl From<Arc<PageInner>> for Page {
2460 fn from(inner: Arc<PageInner>) -> Self {
2461 Self { inner }
2462 }
2463}
2464
2465pub(crate) fn validate_cookie_url(url: &str) -> Result<()> {
2466 if url.starts_with("data:") {
2467 Err(CdpError::msg("Data URL page can not have cookie"))
2468 } else if url == "about:blank" {
2469 Err(CdpError::msg("Blank page can not have cookie"))
2470 } else {
2471 Ok(())
2472 }
2473}
2474
2475/// Page screenshot parameters with extra options.
2476#[derive(Debug, Default)]
2477pub struct ScreenshotParams {
2478 /// Chrome DevTools Protocol screenshot options.
2479 pub cdp_params: CaptureScreenshotParams,
2480 /// Take full page screenshot.
2481 pub full_page: Option<bool>,
2482 /// Make the background transparent (png only).
2483 pub omit_background: Option<bool>,
2484}
2485
2486impl ScreenshotParams {
2487 pub fn builder() -> ScreenshotParamsBuilder {
2488 Default::default()
2489 }
2490
2491 pub(crate) fn full_page(&self) -> bool {
2492 self.full_page.unwrap_or(false)
2493 }
2494
2495 pub(crate) fn omit_background(&self) -> bool {
2496 self.omit_background.unwrap_or(false)
2497 && self
2498 .cdp_params
2499 .format
2500 .as_ref()
2501 .map_or(true, |f| f == &CaptureScreenshotFormat::Png)
2502 }
2503}
2504
2505/// Page screenshot parameters builder with extra options.
2506#[derive(Debug, Default)]
2507pub struct ScreenshotParamsBuilder {
2508 /// The cdp params.
2509 cdp_params: CaptureScreenshotParams,
2510 /// Full page screenshot?
2511 full_page: Option<bool>,
2512 /// Hide the background.
2513 omit_background: Option<bool>,
2514}
2515
2516impl ScreenshotParamsBuilder {
2517 /// Image compression format (defaults to png).
2518 pub fn format(mut self, format: impl Into<CaptureScreenshotFormat>) -> Self {
2519 self.cdp_params.format = Some(format.into());
2520 self
2521 }
2522
2523 /// Compression quality from range [0..100] (jpeg only).
2524 pub fn quality(mut self, quality: impl Into<i64>) -> Self {
2525 self.cdp_params.quality = Some(quality.into());
2526 self
2527 }
2528
2529 /// Capture the screenshot of a given region only.
2530 pub fn clip(mut self, clip: impl Into<Viewport>) -> Self {
2531 self.cdp_params.clip = Some(clip.into());
2532 self
2533 }
2534
2535 /// Capture the screenshot from the surface, rather than the view (defaults to true).
2536 pub fn from_surface(mut self, from_surface: impl Into<bool>) -> Self {
2537 self.cdp_params.from_surface = Some(from_surface.into());
2538 self
2539 }
2540
2541 /// Capture the screenshot beyond the viewport (defaults to false).
2542 pub fn capture_beyond_viewport(mut self, capture_beyond_viewport: impl Into<bool>) -> Self {
2543 self.cdp_params.capture_beyond_viewport = Some(capture_beyond_viewport.into());
2544 self
2545 }
2546
2547 /// Full page screen capture.
2548 pub fn full_page(mut self, full_page: impl Into<bool>) -> Self {
2549 self.full_page = Some(full_page.into());
2550 self
2551 }
2552
2553 /// Make the background transparent (png only)
2554 pub fn omit_background(mut self, omit_background: impl Into<bool>) -> Self {
2555 self.omit_background = Some(omit_background.into());
2556 self
2557 }
2558
2559 pub fn build(self) -> ScreenshotParams {
2560 ScreenshotParams {
2561 cdp_params: self.cdp_params,
2562 full_page: self.full_page,
2563 omit_background: self.omit_background,
2564 }
2565 }
2566}
2567
2568impl From<CaptureScreenshotParams> for ScreenshotParams {
2569 fn from(cdp_params: CaptureScreenshotParams) -> Self {
2570 Self {
2571 cdp_params,
2572 ..Default::default()
2573 }
2574 }
2575}
2576
2577#[derive(Debug, Clone, Copy, Default)]
2578pub enum MediaTypeParams {
2579 /// Default CSS media type behavior for page and print
2580 #[default]
2581 Null,
2582 /// Force screen CSS media type for page and print
2583 Screen,
2584 /// Force print CSS media type for page and print
2585 Print,
2586}
2587impl From<MediaTypeParams> for String {
2588 fn from(media_type: MediaTypeParams) -> Self {
2589 match media_type {
2590 MediaTypeParams::Null => "null".to_string(),
2591 MediaTypeParams::Screen => "screen".to_string(),
2592 MediaTypeParams::Print => "print".to_string(),
2593 }
2594 }
2595}