1use std::sync::atomic::{AtomicUsize, Ordering};
2use std::sync::Arc;
3
4use chromiumoxide_cdp::cdp::browser_protocol::accessibility::{
5 GetFullAxTreeParamsBuilder, GetFullAxTreeReturns, GetPartialAxTreeParamsBuilder,
6 GetPartialAxTreeReturns,
7};
8use tokio::sync::mpsc::{channel, Receiver};
9use tokio::sync::oneshot::channel as oneshot_channel;
10use tokio::sync::Notify;
11
12use chromiumoxide_cdp::cdp::browser_protocol::browser::{GetVersionParams, GetVersionReturns};
13use chromiumoxide_cdp::cdp::browser_protocol::dom::{
14 BackendNodeId, DiscardSearchResultsParams, GetOuterHtmlParams, GetSearchResultsParams, NodeId,
15 PerformSearchParams, QuerySelectorAllParams, QuerySelectorParams, Rgba,
16};
17use chromiumoxide_cdp::cdp::browser_protocol::emulation::{
18 ClearDeviceMetricsOverrideParams, SetDefaultBackgroundColorOverrideParams,
19 SetDeviceMetricsOverrideParams,
20};
21use chromiumoxide_cdp::cdp::browser_protocol::input::{
22 DispatchDragEventParams, DispatchDragEventType, DispatchKeyEventParams, DispatchKeyEventType,
23 DispatchMouseEventParams, DispatchMouseEventType, DragData, MouseButton,
24};
25use chromiumoxide_cdp::cdp::browser_protocol::page::{
26 FrameId, GetLayoutMetricsParams, GetLayoutMetricsReturns, PrintToPdfParams, SetBypassCspParams,
27 Viewport,
28};
29use chromiumoxide_cdp::cdp::browser_protocol::target::{ActivateTargetParams, SessionId, TargetId};
30use chromiumoxide_cdp::cdp::js_protocol::runtime::{
31 CallFunctionOnParams, CallFunctionOnReturns, EvaluateParams, ExecutionContextId, RemoteObjectId,
32};
33use chromiumoxide_types::{Command, CommandResponse};
34
35use crate::cmd::{to_command_response, CommandMessage};
36use crate::error::{CdpError, Result};
37use crate::handler::commandfuture::CommandFuture;
38use crate::handler::domworld::DOMWorldKind;
39use crate::handler::httpfuture::HttpFuture;
40use crate::handler::sender::PageSender;
41use crate::handler::target::{GetExecutionContext, TargetMessage};
42use crate::handler::target_message_future::TargetMessageFuture;
43use crate::js::EvaluationResult;
44use crate::layout::{Delta, Point, ScrollBehavior};
45use crate::mouse::SmartMouse;
46use crate::page::ScreenshotParams;
47use crate::{keys, utils, ArcHttpRequest};
48
49static ACTIVE_PAGES: AtomicUsize = AtomicUsize::new(0);
53
54#[inline]
56pub fn active_page_count() -> usize {
57 ACTIVE_PAGES.load(Ordering::Relaxed)
58}
59
60#[derive(Debug)]
61pub struct PageHandle {
62 pub(crate) rx: Receiver<TargetMessage>,
63 page: Arc<PageInner>,
64}
65
66pub(crate) const DEFAULT_PAGE_CHANNEL_CAPACITY: usize = 2048;
75
76impl PageHandle {
77 pub fn new(
81 target_id: TargetId,
82 session_id: SessionId,
83 opener_id: Option<TargetId>,
84 request_timeout: std::time::Duration,
85 page_wake: Option<Arc<Notify>>,
86 ) -> Self {
87 Self::with_capacity(
88 target_id,
89 session_id,
90 opener_id,
91 request_timeout,
92 page_wake,
93 DEFAULT_PAGE_CHANNEL_CAPACITY,
94 )
95 }
96
97 pub fn with_capacity(
109 target_id: TargetId,
110 session_id: SessionId,
111 opener_id: Option<TargetId>,
112 request_timeout: std::time::Duration,
113 page_wake: Option<Arc<Notify>>,
114 capacity: usize,
115 ) -> Self {
116 let (commands, rx) = channel(capacity.max(1));
117 let page = PageInner {
118 target_id,
119 session_id,
120 opener_id,
121 sender: PageSender::new(commands, page_wake),
122 smart_mouse: SmartMouse::new(),
123 request_timeout,
124 };
125 ACTIVE_PAGES.fetch_add(1, Ordering::Relaxed);
126 Self {
127 rx,
128 page: Arc::new(page),
129 }
130 }
131
132 pub(crate) fn inner(&self) -> &Arc<PageInner> {
133 &self.page
134 }
135}
136
137#[derive(Debug)]
138pub(crate) struct PageInner {
139 target_id: TargetId,
141 session_id: SessionId,
143 opener_id: Option<TargetId>,
145 sender: PageSender,
147 pub(crate) smart_mouse: SmartMouse,
149 request_timeout: std::time::Duration,
151}
152
153impl Drop for PageInner {
154 fn drop(&mut self) {
155 ACTIVE_PAGES.fetch_sub(1, Ordering::Relaxed);
156 }
157}
158
159impl PageInner {
160 pub(crate) async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
162 execute(
163 cmd,
164 self.sender.clone(),
165 Some(self.session_id.clone()),
166 self.request_timeout,
167 )
168 .await
169 }
170
171 pub(crate) async fn send_command<T: Command>(&self, cmd: T) -> Result<&Self> {
173 let _ = send_command(
174 cmd,
175 self.sender.clone(),
176 Some(self.session_id.clone()),
177 self.request_timeout,
178 )
179 .await;
180 Ok(self)
181 }
182
183 pub(crate) fn command_future<T: Command>(&self, cmd: T) -> Result<CommandFuture<T>> {
185 CommandFuture::new(
186 cmd,
187 self.sender.clone(),
188 Some(self.session_id.clone()),
189 self.request_timeout,
190 )
191 }
192
193 pub(crate) fn wait_for_navigation(&self) -> TargetMessageFuture<ArcHttpRequest> {
195 TargetMessageFuture::<ArcHttpRequest>::wait_for_navigation(
196 self.sender.clone(),
197 self.request_timeout,
198 )
199 }
200
201 pub(crate) fn wait_for_dom_content_loaded(&self) -> TargetMessageFuture<ArcHttpRequest> {
203 TargetMessageFuture::<ArcHttpRequest>::wait_for_dom_content_loaded(
204 self.sender.clone(),
205 self.request_timeout,
206 )
207 }
208
209 pub(crate) fn wait_for_load(&self) -> TargetMessageFuture<ArcHttpRequest> {
211 TargetMessageFuture::<ArcHttpRequest>::wait_for_load(
212 self.sender.clone(),
213 self.request_timeout,
214 )
215 }
216
217 pub(crate) fn wait_for_network_idle(&self) -> TargetMessageFuture<ArcHttpRequest> {
219 TargetMessageFuture::<ArcHttpRequest>::wait_for_network_idle(
220 self.sender.clone(),
221 self.request_timeout,
222 )
223 }
224
225 pub(crate) fn wait_for_network_almost_idle(&self) -> TargetMessageFuture<ArcHttpRequest> {
227 TargetMessageFuture::<ArcHttpRequest>::wait_for_network_almost_idle(
228 self.sender.clone(),
229 self.request_timeout,
230 )
231 }
232
233 pub(crate) fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
236 Ok(HttpFuture::new(
237 self.sender.clone(),
238 self.command_future(cmd)?,
239 self.request_timeout,
240 ))
241 }
242
243 pub fn target_id(&self) -> &TargetId {
245 &self.target_id
246 }
247
248 pub fn session_id(&self) -> &SessionId {
250 &self.session_id
251 }
252
253 pub fn opener_id(&self) -> &Option<TargetId> {
255 &self.opener_id
256 }
257
258 pub(crate) async fn send_msg(&self, msg: TargetMessage) -> Result<()> {
262 match self.sender.try_send(msg) {
263 Ok(()) => Ok(()),
264 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
265 tokio::time::timeout(self.request_timeout, self.sender.send(msg))
266 .await
267 .map_err(|_| CdpError::Timeout)?
268 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
269 Ok(())
270 }
271 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
272 Err(CdpError::ChannelSendError(crate::error::ChannelError::Send))
273 }
274 }
275 }
276
277 pub(crate) async fn recv_msg<T>(&self, rx: tokio::sync::oneshot::Receiver<T>) -> Result<T> {
279 tokio::time::timeout(self.request_timeout, rx)
280 .await
281 .map_err(|_| CdpError::Timeout)?
282 .map_err(|e| CdpError::ChannelSendError(crate::error::ChannelError::Canceled(e)))
283 }
284
285 pub async fn find_element(&self, selector: impl Into<String>, node: NodeId) -> Result<NodeId> {
288 Ok(self
289 .execute(QuerySelectorParams::new(node, selector))
290 .await?
291 .node_id)
292 }
293
294 pub async fn outer_html(
296 &self,
297 object_id: RemoteObjectId,
298 node_id: NodeId,
299 backend_node_id: BackendNodeId,
300 ) -> Result<String> {
301 let cmd = GetOuterHtmlParams {
302 backend_node_id: Some(backend_node_id),
303 node_id: Some(node_id),
304 object_id: Some(object_id),
305 ..Default::default()
306 };
307
308 let chromiumoxide_types::CommandResponse { result, .. } = self.execute(cmd).await?;
309
310 Ok(result.outer_html)
311 }
312
313 pub async fn activate(&self) -> Result<&Self> {
315 self.execute(ActivateTargetParams::new(self.target_id().clone()))
316 .await?;
317 Ok(self)
318 }
319
320 pub async fn version(&self) -> Result<GetVersionReturns> {
322 Ok(self.execute(GetVersionParams::default()).await?.result)
323 }
324
325 pub(crate) async fn find_elements(
327 &self,
328 selector: impl Into<String>,
329 node: NodeId,
330 ) -> Result<Vec<NodeId>> {
331 Ok(self
332 .execute(QuerySelectorAllParams::new(node, selector))
333 .await?
334 .result
335 .node_ids)
336 }
337
338 pub async fn find_xpaths(&self, query: impl Into<String>) -> Result<Vec<NodeId>> {
340 let perform_search_returns = self
341 .execute(PerformSearchParams {
342 query: query.into(),
343 include_user_agent_shadow_dom: Some(true),
344 })
345 .await?
346 .result;
347
348 let search_results = self
349 .execute(GetSearchResultsParams::new(
350 perform_search_returns.search_id.clone(),
351 0,
352 perform_search_returns.result_count,
353 ))
354 .await?
355 .result;
356
357 self.execute(DiscardSearchResultsParams::new(
358 perform_search_returns.search_id,
359 ))
360 .await?;
361
362 Ok(search_results.node_ids)
363 }
364
365 pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
368 self.smart_mouse.set_position(point);
369 self.execute(DispatchMouseEventParams::new(
370 DispatchMouseEventType::MouseMoved,
371 point.x,
372 point.y,
373 ))
374 .await?;
375 Ok(self)
376 }
377
378 pub async fn move_mouse_smooth(&self, target: Point) -> Result<&Self> {
399 let path = self.smart_mouse.path_to(target);
400 let last_idx = path.len().saturating_sub(1);
401 let mut deadline = tokio::time::Instant::now();
402 for (i, step) in path.iter().enumerate() {
403 self.send_command(DispatchMouseEventParams::new(
404 DispatchMouseEventType::MouseMoved,
405 step.point.x,
406 step.point.y,
407 ))
408 .await?;
409 if i < last_idx {
418 deadline += step.delay;
419 tokio::time::sleep_until(deadline).await;
420 }
421 }
422 Ok(self)
423 }
424
425 pub fn mouse_position(&self) -> Point {
427 self.smart_mouse.position()
428 }
429
430 pub async fn scroll_by(
433 &self,
434 delta_x: f64,
435 delta_y: f64,
436 behavior: ScrollBehavior,
437 ) -> Result<&Self> {
438 let behavior_str = match behavior {
439 ScrollBehavior::Auto => "auto",
440 ScrollBehavior::Instant => "instant",
441 ScrollBehavior::Smooth => "smooth",
442 };
443
444 self.evaluate_expression(format!(
445 "window.scrollBy({{top: {}, left: {}, behavior: '{}'}});",
446 delta_y, delta_x, behavior_str
447 ))
448 .await?;
449
450 Ok(self)
451 }
452
453 pub async fn drag(
458 &self,
459 drag_type: DispatchDragEventType,
460 point: Point,
461 drag_data: DragData,
462 modifiers: Option<i64>,
463 ) -> Result<&Self> {
464 let mut params: DispatchDragEventParams =
465 DispatchDragEventParams::new(drag_type, point.x, point.y, drag_data);
466
467 if let Some(modifiers) = modifiers {
468 params.modifiers = Some(modifiers);
469 }
470
471 self.execute(params).await?;
472 Ok(self)
473 }
474
475 pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
478 let mut params: DispatchMouseEventParams =
479 DispatchMouseEventParams::new(DispatchMouseEventType::MouseWheel, point.x, point.y);
480
481 params.delta_x = Some(delta.delta_x);
482 params.delta_y = Some(delta.delta_y);
483
484 self.execute(params).await?;
485 Ok(self)
486 }
487
488 pub async fn click_with_count_base(
490 &self,
491 point: Point,
492 click_count: impl Into<i64>,
493 modifiers: impl Into<i64>,
494 button: impl Into<MouseButton>,
495 ) -> Result<&Self> {
496 let cmd = DispatchMouseEventParams::builder()
497 .x(point.x)
498 .y(point.y)
499 .button(button)
500 .click_count(click_count)
501 .modifiers(modifiers);
502
503 if let Ok(cmd) = cmd
504 .clone()
505 .r#type(DispatchMouseEventType::MousePressed)
506 .build()
507 {
508 self.move_mouse(point).await?.send_command(cmd).await?;
509 }
510
511 if let Ok(cmd) = cmd.r#type(DispatchMouseEventType::MouseReleased).build() {
512 self.execute(cmd).await?;
513 }
514
515 self.smart_mouse.set_position(point);
516 Ok(self)
517 }
518
519 pub async fn click_smooth(&self, point: Point) -> Result<&Self> {
521 self.move_mouse_smooth(point).await?;
522 self.click(point).await
523 }
524
525 pub async fn click_with_count(
527 &self,
528 point: Point,
529 click_count: impl Into<i64>,
530 modifiers: impl Into<i64>,
531 ) -> Result<&Self> {
532 self.click_with_count_base(point, click_count, modifiers, MouseButton::Left)
533 .await
534 }
535
536 pub async fn right_click_with_count(
538 &self,
539 point: Point,
540 click_count: impl Into<i64>,
541 modifiers: impl Into<i64>,
542 ) -> Result<&Self> {
543 self.click_with_count_base(point, click_count, modifiers, MouseButton::Right)
544 .await
545 }
546
547 pub async fn middle_click_with_count(
549 &self,
550 point: Point,
551 click_count: impl Into<i64>,
552 modifiers: impl Into<i64>,
553 ) -> Result<&Self> {
554 self.click_with_count_base(point, click_count, modifiers, MouseButton::Middle)
555 .await
556 }
557
558 pub async fn back_click_with_count(
560 &self,
561 point: Point,
562 click_count: impl Into<i64>,
563 modifiers: impl Into<i64>,
564 ) -> Result<&Self> {
565 self.click_with_count_base(point, click_count, modifiers, MouseButton::Back)
566 .await
567 }
568
569 pub async fn forward_click_with_count(
571 &self,
572 point: Point,
573 click_count: impl Into<i64>,
574 modifiers: impl Into<i64>,
575 ) -> Result<&Self> {
576 self.click_with_count_base(point, click_count, modifiers, MouseButton::Forward)
577 .await
578 }
579
580 pub async fn click_and_drag(
582 &self,
583 from: Point,
584 to: Point,
585 modifiers: impl Into<i64>,
586 ) -> Result<&Self> {
587 let modifiers = modifiers.into();
588 let click_count = 1;
589
590 let cmd = DispatchMouseEventParams::builder()
591 .button(MouseButton::Left)
592 .click_count(click_count)
593 .modifiers(modifiers);
594
595 if let Ok(cmd) = cmd
596 .clone()
597 .x(from.x)
598 .y(from.y)
599 .r#type(DispatchMouseEventType::MousePressed)
600 .build()
601 {
602 self.move_mouse(from).await?.send_command(cmd).await?;
603 }
604
605 if let Ok(cmd) = cmd
606 .clone()
607 .x(to.x)
608 .y(to.y)
609 .r#type(DispatchMouseEventType::MouseMoved)
610 .build()
611 {
612 self.move_mouse(to).await?.send_command(cmd).await?;
613 }
614
615 if let Ok(cmd) = cmd
616 .r#type(DispatchMouseEventType::MouseReleased)
617 .x(to.x)
618 .y(to.y)
619 .build()
620 {
621 self.send_command(cmd).await?;
622 }
623
624 self.smart_mouse.set_position(to);
625 Ok(self)
626 }
627
628 pub async fn click_and_drag_smooth(
631 &self,
632 from: Point,
633 to: Point,
634 modifiers: impl Into<i64>,
635 ) -> Result<&Self> {
636 let modifiers = modifiers.into();
637
638 self.move_mouse_smooth(from).await?;
640
641 if let Ok(cmd) = DispatchMouseEventParams::builder()
643 .x(from.x)
644 .y(from.y)
645 .button(MouseButton::Left)
646 .click_count(1)
647 .modifiers(modifiers)
648 .r#type(DispatchMouseEventType::MousePressed)
649 .build()
650 {
651 self.send_command(cmd).await?;
652 }
653
654 let path = self.smart_mouse.path_to(to);
663 let last_idx = path.len().saturating_sub(1);
664 let mut deadline = tokio::time::Instant::now();
665 for (i, step) in path.iter().enumerate() {
666 if let Ok(cmd) = DispatchMouseEventParams::builder()
667 .x(step.point.x)
668 .y(step.point.y)
669 .button(MouseButton::Left)
670 .modifiers(modifiers)
671 .r#type(DispatchMouseEventType::MouseMoved)
672 .build()
673 {
674 self.send_command(cmd).await?;
675 }
676 if i < last_idx {
677 deadline += step.delay;
678 tokio::time::sleep_until(deadline).await;
679 }
680 }
681
682 if let Ok(cmd) = DispatchMouseEventParams::builder()
684 .x(to.x)
685 .y(to.y)
686 .button(MouseButton::Left)
687 .click_count(1)
688 .modifiers(modifiers)
689 .r#type(DispatchMouseEventType::MouseReleased)
690 .build()
691 {
692 self.send_command(cmd).await?;
693 }
694
695 Ok(self)
696 }
697
698 pub async fn click(&self, point: Point) -> Result<&Self> {
700 self.click_with_count(point, 1, 0).await
701 }
702
703 pub async fn double_click(&self, point: Point) -> Result<&Self> {
705 self.click_with_count(point, 2, 0).await
706 }
707
708 pub async fn right_click(&self, point: Point) -> Result<&Self> {
710 self.right_click_with_count(point, 1, 0).await
711 }
712
713 pub async fn middle_click(&self, point: Point) -> Result<&Self> {
715 self.middle_click_with_count(point, 1, 0).await
716 }
717
718 pub async fn back_click(&self, point: Point) -> Result<&Self> {
720 self.back_click_with_count(point, 1, 0).await
721 }
722
723 pub async fn forward_click(&self, point: Point) -> Result<&Self> {
725 self.forward_click_with_count(point, 1, 0).await
726 }
727
728 pub async fn click_with_modifier(
730 &self,
731 point: Point,
732 modifiers: impl Into<i64>,
733 ) -> Result<&Self> {
734 self.click_with_count(point, 1, modifiers).await
735 }
736
737 pub async fn right_click_with_modifier(
739 &self,
740 point: Point,
741 modifiers: impl Into<i64>,
742 ) -> Result<&Self> {
743 self.right_click_with_count(point, 1, modifiers).await
744 }
745
746 pub async fn middle_click_with_modifier(
748 &self,
749 point: Point,
750 modifiers: impl Into<i64>,
751 ) -> Result<&Self> {
752 self.middle_click_with_count(point, 1, modifiers).await
753 }
754
755 pub async fn double_click_with_modifier(
757 &self,
758 point: Point,
759 modifiers: impl Into<i64>,
760 ) -> Result<&Self> {
761 self.click_with_count(point, 2, modifiers).await
762 }
763
764 pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
773 for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
774 self._press_key(c, None).await?;
775 }
776 Ok(self)
777 }
778
779 pub async fn get_full_ax_tree(
781 &self,
782 depth: Option<i64>,
783 frame_id: Option<FrameId>,
784 ) -> Result<GetFullAxTreeReturns> {
785 let mut builder = GetFullAxTreeParamsBuilder::default();
786
787 if let Some(depth) = depth {
788 builder = builder.depth(depth);
789 }
790
791 if let Some(frame_id) = frame_id {
792 builder = builder.frame_id(frame_id);
793 }
794
795 let resp = self.execute(builder.build()).await?;
796
797 Ok(resp.result)
798 }
799
800 pub async fn get_partial_ax_tree(
802 &self,
803 node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
804 backend_node_id: Option<BackendNodeId>,
805 object_id: Option<RemoteObjectId>,
806 fetch_relatives: Option<bool>,
807 ) -> Result<GetPartialAxTreeReturns> {
808 let mut builder = GetPartialAxTreeParamsBuilder::default();
809
810 if let Some(node_id) = node_id {
811 builder = builder.node_id(node_id);
812 }
813
814 if let Some(backend_node_id) = backend_node_id {
815 builder = builder.backend_node_id(backend_node_id);
816 }
817
818 if let Some(object_id) = object_id {
819 builder = builder.object_id(object_id);
820 }
821
822 if let Some(fetch_relatives) = fetch_relatives {
823 builder = builder.fetch_relatives(fetch_relatives);
824 }
825
826 let resp = self.execute(builder.build()).await?;
827
828 Ok(resp.result)
829 }
830
831 pub async fn type_str_with_modifier(
840 &self,
841 input: impl AsRef<str>,
842 modifiers: Option<i64>,
843 ) -> Result<&Self> {
844 for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
845 self._press_key(c, modifiers).await?;
846 }
847 Ok(self)
848 }
849
850 async fn _press_key(&self, key: impl AsRef<str>, modifiers: Option<i64>) -> Result<&Self> {
853 let key = key.as_ref();
854 let key_definition = keys::get_key_definition(key)
855 .ok_or_else(|| CdpError::msg(format!("Key not found: {key}")))?;
856 let mut cmd = DispatchKeyEventParams::builder();
857
858 let key_down_event_type = if let Some(txt) = key_definition.text {
861 cmd = cmd.text(txt);
862 DispatchKeyEventType::KeyDown
863 } else if key_definition.key.len() == 1 {
864 cmd = cmd.text(key_definition.key);
865 DispatchKeyEventType::KeyDown
866 } else {
867 DispatchKeyEventType::RawKeyDown
868 };
869
870 cmd = cmd
871 .r#type(DispatchKeyEventType::KeyDown)
872 .key(key_definition.key)
873 .code(key_definition.code)
874 .windows_virtual_key_code(key_definition.key_code)
875 .native_virtual_key_code(key_definition.key_code);
876
877 if let Some(modifiers) = modifiers {
878 cmd = cmd.modifiers(modifiers);
879 }
880
881 let key_down = cmd.clone().r#type(key_down_event_type).build().ok();
882 let key_up = cmd.r#type(DispatchKeyEventType::KeyUp).build().ok();
883
884 match (key_down, key_up) {
885 (Some(kd), Some(ku)) => {
886 tokio::try_join!(self.execute(kd), self.execute(ku))?;
887 }
888 (Some(kd), None) => {
889 self.execute(kd).await?;
890 }
891 (None, Some(ku)) => {
892 self.execute(ku).await?;
893 }
894 (None, None) => {}
895 }
896
897 Ok(self)
898 }
899
900 pub async fn press_key(&self, key: impl AsRef<str>) -> Result<&Self> {
903 self._press_key(key, None).await
904 }
905
906 pub async fn press_key_with_modifier(
909 &self,
910 key: impl AsRef<str>,
911 modifiers: Option<i64>,
912 ) -> Result<&Self> {
913 self._press_key(key, modifiers).await
914 }
915
916 pub async fn call_js_fn(
919 &self,
920 function_declaration: impl Into<String>,
921 await_promise: bool,
922 remote_object_id: RemoteObjectId,
923 ) -> Result<CallFunctionOnReturns> {
924 if let Ok(resp) = CallFunctionOnParams::builder()
925 .object_id(remote_object_id)
926 .function_declaration(function_declaration)
927 .generate_preview(true)
928 .await_promise(await_promise)
929 .build()
930 {
931 let resp = self.execute(resp).await?;
932 Ok(resp.result)
933 } else {
934 Err(CdpError::NotFound)
935 }
936 }
937
938 pub async fn evaluate_expression(
939 &self,
940 evaluate: impl Into<EvaluateParams>,
941 ) -> Result<EvaluationResult> {
942 let mut evaluate = evaluate.into();
943 if evaluate.context_id.is_none() {
944 evaluate.context_id = self.execution_context().await?;
945 }
946 if evaluate.await_promise.is_none() {
947 evaluate.await_promise = Some(true);
948 }
949 if evaluate.return_by_value.is_none() {
950 evaluate.return_by_value = Some(true);
951 }
952
953 let resp = self.execute(evaluate).await?.result;
956
957 if let Some(exception) = resp.exception_details {
958 return Err(CdpError::JavascriptException(Box::new(exception)));
959 }
960
961 Ok(EvaluationResult::new(resp.result))
962 }
963
964 pub async fn evaluate_function(
965 &self,
966 evaluate: impl Into<CallFunctionOnParams>,
967 ) -> Result<EvaluationResult> {
968 let mut evaluate = evaluate.into();
969 if evaluate.execution_context_id.is_none() {
970 evaluate.execution_context_id = self.execution_context().await?;
971 }
972 if evaluate.await_promise.is_none() {
973 evaluate.await_promise = Some(true);
974 }
975 if evaluate.return_by_value.is_none() {
976 evaluate.return_by_value = Some(true);
977 }
978
979 let resp = self.execute(evaluate).await?.result;
982 if let Some(exception) = resp.exception_details {
983 return Err(CdpError::JavascriptException(Box::new(exception)));
984 }
985 Ok(EvaluationResult::new(resp.result))
986 }
987
988 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
989 self.execution_context_for_world(None, DOMWorldKind::Main)
990 .await
991 }
992
993 pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
994 self.execution_context_for_world(None, DOMWorldKind::Secondary)
995 .await
996 }
997
998 pub async fn frame_execution_context(
999 &self,
1000 frame_id: FrameId,
1001 ) -> Result<Option<ExecutionContextId>> {
1002 self.execution_context_for_world(Some(frame_id), DOMWorldKind::Main)
1003 .await
1004 }
1005
1006 pub async fn frame_secondary_execution_context(
1007 &self,
1008 frame_id: FrameId,
1009 ) -> Result<Option<ExecutionContextId>> {
1010 self.execution_context_for_world(Some(frame_id), DOMWorldKind::Secondary)
1011 .await
1012 }
1013
1014 pub async fn execution_context_for_world(
1015 &self,
1016 frame_id: Option<FrameId>,
1017 dom_world: DOMWorldKind,
1018 ) -> Result<Option<ExecutionContextId>> {
1019 let (tx, rx) = oneshot_channel();
1020 let msg = TargetMessage::GetExecutionContext(GetExecutionContext {
1021 dom_world,
1022 frame_id,
1023 tx,
1024 });
1025 match self.sender.try_send(msg) {
1026 Ok(()) => {}
1027 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
1028 tokio::time::timeout(self.request_timeout, self.sender.send(msg))
1029 .await
1030 .map_err(|_| CdpError::Timeout)?
1031 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
1032 }
1033 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
1034 return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
1035 }
1036 }
1037 Ok(tokio::time::timeout(self.request_timeout, rx)
1038 .await
1039 .map_err(|_| CdpError::Timeout)??)
1040 }
1041
1042 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
1044 Ok(self
1045 .execute(GetLayoutMetricsParams::default())
1046 .await?
1047 .result)
1048 }
1049
1050 pub async fn set_bypass_csp(&self, enabled: bool) -> Result<&Self> {
1052 self.execute(SetBypassCspParams::new(enabled)).await?;
1053 Ok(self)
1054 }
1055
1056 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
1058 self.activate().await?;
1059 let params = params.into();
1060 let full_page = params.full_page();
1061 let omit_background = params.omit_background();
1062
1063 let mut cdp_params = params.cdp_params;
1064
1065 if full_page {
1066 let metrics = self.layout_metrics().await?;
1067 let width = metrics.css_content_size.width;
1068 let height = metrics.css_content_size.height;
1069
1070 cdp_params.clip = Some(Viewport {
1071 x: 0.,
1072 y: 0.,
1073 width,
1074 height,
1075 scale: 1.,
1076 });
1077
1078 self.execute(SetDeviceMetricsOverrideParams::new(
1079 width as i64,
1080 height as i64,
1081 1.,
1082 false,
1083 ))
1084 .await?;
1085 }
1086
1087 if omit_background {
1088 self.execute(SetDefaultBackgroundColorOverrideParams {
1089 color: Some(Rgba {
1090 r: 0,
1091 g: 0,
1092 b: 0,
1093 a: Some(0.),
1094 }),
1095 })
1096 .await?;
1097 }
1098
1099 let res = self.execute(cdp_params).await?.result;
1100
1101 if omit_background {
1102 self.send_command(SetDefaultBackgroundColorOverrideParams { color: None })
1103 .await?;
1104 }
1105
1106 if full_page {
1107 self.send_command(ClearDeviceMetricsOverrideParams {})
1108 .await?;
1109 }
1110
1111 Ok(utils::base64::decode(&res.data)?)
1112 }
1113
1114 pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1116 self.activate().await?;
1117 let params = params.into();
1118
1119 let res = self.execute(params).await?.result;
1120
1121 Ok(utils::base64::decode(&res.data)?)
1122 }
1123}
1124
1125pub(crate) async fn execute<T: Command>(
1126 cmd: T,
1127 sender: PageSender,
1128 session: Option<SessionId>,
1129 request_timeout: std::time::Duration,
1130) -> Result<CommandResponse<T::Response>> {
1131 let method = cmd.identifier();
1132 let rx = send_command(cmd, sender, session, request_timeout).await?;
1133 let resp = tokio::time::timeout(request_timeout, rx)
1134 .await
1135 .map_err(|_| CdpError::Timeout)???;
1136 to_command_response::<T>(resp, method)
1137}
1138
1139pub(crate) async fn send_command<T: Command>(
1144 cmd: T,
1145 sender: PageSender,
1146 session: Option<SessionId>,
1147 request_timeout: std::time::Duration,
1148) -> Result<tokio::sync::oneshot::Receiver<Result<chromiumoxide_types::Response, CdpError>>> {
1149 let (tx, rx) = oneshot_channel();
1150 let msg = CommandMessage::with_session(cmd, tx, session)?;
1151 let target_msg = TargetMessage::Command(msg);
1152 match sender.try_send(target_msg) {
1153 Ok(()) => {}
1154 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
1155 tokio::time::timeout(request_timeout, sender.send(msg))
1156 .await
1157 .map_err(|_| CdpError::Timeout)?
1158 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
1159 }
1160 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
1161 return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
1162 }
1163 }
1164 Ok(rx)
1165}
1166
1167#[cfg(test)]
1168mod page_channel_capacity_tests {
1169 use super::{PageHandle, DEFAULT_PAGE_CHANNEL_CAPACITY};
1189 use crate::handler::target::TargetMessage;
1190 use chromiumoxide_cdp::cdp::browser_protocol::target::{SessionId, TargetId};
1191 use std::time::Duration;
1192 use tokio::sync::mpsc::error::TrySendError;
1193
1194 fn make_msg() -> TargetMessage {
1198 TargetMessage::BlockNetwork(false)
1199 }
1200
1201 fn make_handle(capacity: usize) -> PageHandle {
1202 PageHandle::with_capacity(
1203 TargetId::from("t".to_string()),
1204 SessionId::from("s".to_string()),
1205 None,
1206 Duration::from_secs(30),
1207 None,
1208 capacity,
1209 )
1210 }
1211
1212 fn observed_capacity(handle: &PageHandle, upper_bound: usize) -> usize {
1216 let sender = &handle.page.sender;
1220 let mut sent = 0;
1221 for _ in 0..upper_bound {
1222 match sender.try_send(make_msg()) {
1223 Ok(()) => sent += 1,
1224 Err(TrySendError::Full(_)) => return sent,
1225 Err(TrySendError::Closed(_)) => {
1226 panic!("channel unexpectedly closed at {sent} sends")
1227 }
1228 }
1229 }
1230 sent
1231 }
1232
1233 #[test]
1234 fn new_delegates_to_default_capacity() {
1235 let handle = PageHandle::new(
1236 TargetId::from("t".to_string()),
1237 SessionId::from("s".to_string()),
1238 None,
1239 Duration::from_secs(30),
1240 None,
1241 );
1242 let n = observed_capacity(&handle, DEFAULT_PAGE_CHANNEL_CAPACITY + 16);
1243 assert_eq!(
1244 n, DEFAULT_PAGE_CHANNEL_CAPACITY,
1245 "legacy PageHandle::new must preserve the 2048-slot default"
1246 );
1247 }
1248
1249 #[test]
1250 fn with_capacity_respects_arbitrary_value() {
1251 for n in [1_usize, 4, 16, 64] {
1254 let handle = make_handle(n);
1255 assert_eq!(
1256 observed_capacity(&handle, n + 16),
1257 n,
1258 "with_capacity({n}) should produce exactly {n} slots",
1259 );
1260 }
1261 }
1262
1263 #[test]
1264 fn zero_capacity_is_clamped_to_one_and_does_not_panic() {
1265 let handle = make_handle(0);
1268 assert_eq!(
1269 observed_capacity(&handle, 4),
1270 1,
1271 "zero capacity must clamp to 1 and not panic"
1272 );
1273 }
1274}