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