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