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