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