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