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