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