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?;
509 if let Some(dwell) = self.smart_mouse.pre_click_dwell() {
515 tokio::time::sleep(dwell).await;
516 }
517 self.send_command(cmd).await?;
518 }
519
520 if let Ok(cmd) = cmd.r#type(DispatchMouseEventType::MouseReleased).build() {
521 self.execute(cmd).await?;
522 }
523
524 self.smart_mouse.set_position(point);
525 Ok(self)
526 }
527
528 pub async fn click_smooth(&self, point: Point) -> Result<&Self> {
530 self.move_mouse_smooth(point).await?;
531 self.click(point).await
532 }
533
534 pub async fn click_with_count(
536 &self,
537 point: Point,
538 click_count: impl Into<i64>,
539 modifiers: impl Into<i64>,
540 ) -> Result<&Self> {
541 self.click_with_count_base(point, click_count, modifiers, MouseButton::Left)
542 .await
543 }
544
545 pub async fn right_click_with_count(
547 &self,
548 point: Point,
549 click_count: impl Into<i64>,
550 modifiers: impl Into<i64>,
551 ) -> Result<&Self> {
552 self.click_with_count_base(point, click_count, modifiers, MouseButton::Right)
553 .await
554 }
555
556 pub async fn middle_click_with_count(
558 &self,
559 point: Point,
560 click_count: impl Into<i64>,
561 modifiers: impl Into<i64>,
562 ) -> Result<&Self> {
563 self.click_with_count_base(point, click_count, modifiers, MouseButton::Middle)
564 .await
565 }
566
567 pub async fn back_click_with_count(
569 &self,
570 point: Point,
571 click_count: impl Into<i64>,
572 modifiers: impl Into<i64>,
573 ) -> Result<&Self> {
574 self.click_with_count_base(point, click_count, modifiers, MouseButton::Back)
575 .await
576 }
577
578 pub async fn forward_click_with_count(
580 &self,
581 point: Point,
582 click_count: impl Into<i64>,
583 modifiers: impl Into<i64>,
584 ) -> Result<&Self> {
585 self.click_with_count_base(point, click_count, modifiers, MouseButton::Forward)
586 .await
587 }
588
589 pub async fn click_and_drag(
591 &self,
592 from: Point,
593 to: Point,
594 modifiers: impl Into<i64>,
595 ) -> Result<&Self> {
596 let modifiers = modifiers.into();
597 let click_count = 1;
598
599 let cmd = DispatchMouseEventParams::builder()
600 .button(MouseButton::Left)
601 .click_count(click_count)
602 .modifiers(modifiers);
603
604 if let Ok(cmd) = cmd
605 .clone()
606 .x(from.x)
607 .y(from.y)
608 .r#type(DispatchMouseEventType::MousePressed)
609 .build()
610 {
611 self.move_mouse(from).await?.send_command(cmd).await?;
612 }
613
614 if let Ok(cmd) = cmd
615 .clone()
616 .x(to.x)
617 .y(to.y)
618 .r#type(DispatchMouseEventType::MouseMoved)
619 .build()
620 {
621 self.move_mouse(to).await?.send_command(cmd).await?;
622 }
623
624 if let Ok(cmd) = cmd
625 .r#type(DispatchMouseEventType::MouseReleased)
626 .x(to.x)
627 .y(to.y)
628 .build()
629 {
630 self.send_command(cmd).await?;
631 }
632
633 self.smart_mouse.set_position(to);
634 Ok(self)
635 }
636
637 pub async fn click_and_drag_smooth(
640 &self,
641 from: Point,
642 to: Point,
643 modifiers: impl Into<i64>,
644 ) -> Result<&Self> {
645 let modifiers = modifiers.into();
646
647 self.move_mouse_smooth(from).await?;
649
650 if let Ok(cmd) = DispatchMouseEventParams::builder()
652 .x(from.x)
653 .y(from.y)
654 .button(MouseButton::Left)
655 .click_count(1)
656 .modifiers(modifiers)
657 .r#type(DispatchMouseEventType::MousePressed)
658 .build()
659 {
660 self.send_command(cmd).await?;
661 }
662
663 let path = self.smart_mouse.path_to(to);
672 let last_idx = path.len().saturating_sub(1);
673 let mut deadline = tokio::time::Instant::now();
674 for (i, step) in path.iter().enumerate() {
675 if let Ok(cmd) = DispatchMouseEventParams::builder()
676 .x(step.point.x)
677 .y(step.point.y)
678 .button(MouseButton::Left)
679 .modifiers(modifiers)
680 .r#type(DispatchMouseEventType::MouseMoved)
681 .build()
682 {
683 self.send_command(cmd).await?;
684 }
685 if i < last_idx {
686 deadline += step.delay;
687 tokio::time::sleep_until(deadline).await;
688 }
689 }
690
691 if let Ok(cmd) = DispatchMouseEventParams::builder()
693 .x(to.x)
694 .y(to.y)
695 .button(MouseButton::Left)
696 .click_count(1)
697 .modifiers(modifiers)
698 .r#type(DispatchMouseEventType::MouseReleased)
699 .build()
700 {
701 self.send_command(cmd).await?;
702 }
703
704 Ok(self)
705 }
706
707 pub async fn click(&self, point: Point) -> Result<&Self> {
709 self.click_with_count(point, 1, 0).await
710 }
711
712 pub async fn double_click(&self, point: Point) -> Result<&Self> {
714 self.click_with_count(point, 2, 0).await
715 }
716
717 pub async fn right_click(&self, point: Point) -> Result<&Self> {
719 self.right_click_with_count(point, 1, 0).await
720 }
721
722 pub async fn middle_click(&self, point: Point) -> Result<&Self> {
724 self.middle_click_with_count(point, 1, 0).await
725 }
726
727 pub async fn back_click(&self, point: Point) -> Result<&Self> {
729 self.back_click_with_count(point, 1, 0).await
730 }
731
732 pub async fn forward_click(&self, point: Point) -> Result<&Self> {
734 self.forward_click_with_count(point, 1, 0).await
735 }
736
737 pub async fn click_with_modifier(
739 &self,
740 point: Point,
741 modifiers: impl Into<i64>,
742 ) -> Result<&Self> {
743 self.click_with_count(point, 1, modifiers).await
744 }
745
746 pub async fn right_click_with_modifier(
748 &self,
749 point: Point,
750 modifiers: impl Into<i64>,
751 ) -> Result<&Self> {
752 self.right_click_with_count(point, 1, modifiers).await
753 }
754
755 pub async fn middle_click_with_modifier(
757 &self,
758 point: Point,
759 modifiers: impl Into<i64>,
760 ) -> Result<&Self> {
761 self.middle_click_with_count(point, 1, modifiers).await
762 }
763
764 pub async fn double_click_with_modifier(
766 &self,
767 point: Point,
768 modifiers: impl Into<i64>,
769 ) -> Result<&Self> {
770 self.click_with_count(point, 2, modifiers).await
771 }
772
773 pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
782 for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
783 self._press_key(c, None).await?;
784 }
785 Ok(self)
786 }
787
788 pub async fn get_full_ax_tree(
790 &self,
791 depth: Option<i64>,
792 frame_id: Option<FrameId>,
793 ) -> Result<GetFullAxTreeReturns> {
794 let mut builder = GetFullAxTreeParamsBuilder::default();
795
796 if let Some(depth) = depth {
797 builder = builder.depth(depth);
798 }
799
800 if let Some(frame_id) = frame_id {
801 builder = builder.frame_id(frame_id);
802 }
803
804 let resp = self.execute(builder.build()).await?;
805
806 Ok(resp.result)
807 }
808
809 pub async fn get_partial_ax_tree(
811 &self,
812 node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
813 backend_node_id: Option<BackendNodeId>,
814 object_id: Option<RemoteObjectId>,
815 fetch_relatives: Option<bool>,
816 ) -> Result<GetPartialAxTreeReturns> {
817 let mut builder = GetPartialAxTreeParamsBuilder::default();
818
819 if let Some(node_id) = node_id {
820 builder = builder.node_id(node_id);
821 }
822
823 if let Some(backend_node_id) = backend_node_id {
824 builder = builder.backend_node_id(backend_node_id);
825 }
826
827 if let Some(object_id) = object_id {
828 builder = builder.object_id(object_id);
829 }
830
831 if let Some(fetch_relatives) = fetch_relatives {
832 builder = builder.fetch_relatives(fetch_relatives);
833 }
834
835 let resp = self.execute(builder.build()).await?;
836
837 Ok(resp.result)
838 }
839
840 pub async fn type_str_with_modifier(
849 &self,
850 input: impl AsRef<str>,
851 modifiers: Option<i64>,
852 ) -> Result<&Self> {
853 for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
854 self._press_key(c, modifiers).await?;
855 }
856 Ok(self)
857 }
858
859 async fn _press_key(&self, key: impl AsRef<str>, modifiers: Option<i64>) -> Result<&Self> {
862 let key = key.as_ref();
863 let key_definition = keys::get_key_definition(key)
864 .ok_or_else(|| CdpError::msg(format!("Key not found: {key}")))?;
865 let mut cmd = DispatchKeyEventParams::builder();
866
867 let key_down_event_type = if let Some(txt) = key_definition.text {
870 cmd = cmd.text(txt);
871 DispatchKeyEventType::KeyDown
872 } else if key_definition.key.len() == 1 {
873 cmd = cmd.text(key_definition.key);
874 DispatchKeyEventType::KeyDown
875 } else {
876 DispatchKeyEventType::RawKeyDown
877 };
878
879 cmd = cmd
880 .r#type(DispatchKeyEventType::KeyDown)
881 .key(key_definition.key)
882 .code(key_definition.code)
883 .windows_virtual_key_code(key_definition.key_code)
884 .native_virtual_key_code(key_definition.key_code);
885
886 if let Some(modifiers) = modifiers {
887 cmd = cmd.modifiers(modifiers);
888 }
889
890 let key_down = cmd.clone().r#type(key_down_event_type).build().ok();
891 let key_up = cmd.r#type(DispatchKeyEventType::KeyUp).build().ok();
892
893 match (key_down, key_up) {
894 (Some(kd), Some(ku)) => {
895 tokio::try_join!(self.execute(kd), self.execute(ku))?;
896 }
897 (Some(kd), None) => {
898 self.execute(kd).await?;
899 }
900 (None, Some(ku)) => {
901 self.execute(ku).await?;
902 }
903 (None, None) => {}
904 }
905
906 Ok(self)
907 }
908
909 pub async fn press_key(&self, key: impl AsRef<str>) -> Result<&Self> {
912 self._press_key(key, None).await
913 }
914
915 pub async fn press_key_with_modifier(
918 &self,
919 key: impl AsRef<str>,
920 modifiers: Option<i64>,
921 ) -> Result<&Self> {
922 self._press_key(key, modifiers).await
923 }
924
925 pub async fn call_js_fn(
928 &self,
929 function_declaration: impl Into<String>,
930 await_promise: bool,
931 remote_object_id: RemoteObjectId,
932 ) -> Result<CallFunctionOnReturns> {
933 if let Ok(resp) = CallFunctionOnParams::builder()
934 .object_id(remote_object_id)
935 .function_declaration(function_declaration)
936 .generate_preview(true)
937 .await_promise(await_promise)
938 .build()
939 {
940 let resp = self.execute(resp).await?;
941 Ok(resp.result)
942 } else {
943 Err(CdpError::NotFound)
944 }
945 }
946
947 pub async fn evaluate_expression(
948 &self,
949 evaluate: impl Into<EvaluateParams>,
950 ) -> Result<EvaluationResult> {
951 let mut evaluate = evaluate.into();
952 if evaluate.context_id.is_none() {
953 evaluate.context_id = self.execution_context().await?;
954 }
955 if evaluate.await_promise.is_none() {
956 evaluate.await_promise = Some(true);
957 }
958 if evaluate.return_by_value.is_none() {
959 evaluate.return_by_value = Some(true);
960 }
961
962 let resp = self.execute(evaluate).await?.result;
965
966 if let Some(exception) = resp.exception_details {
967 return Err(CdpError::JavascriptException(Box::new(exception)));
968 }
969
970 Ok(EvaluationResult::new(resp.result))
971 }
972
973 pub async fn evaluate_function(
974 &self,
975 evaluate: impl Into<CallFunctionOnParams>,
976 ) -> Result<EvaluationResult> {
977 let mut evaluate = evaluate.into();
978 if evaluate.execution_context_id.is_none() {
979 evaluate.execution_context_id = self.execution_context().await?;
980 }
981 if evaluate.await_promise.is_none() {
982 evaluate.await_promise = Some(true);
983 }
984 if evaluate.return_by_value.is_none() {
985 evaluate.return_by_value = Some(true);
986 }
987
988 let resp = self.execute(evaluate).await?.result;
991 if let Some(exception) = resp.exception_details {
992 return Err(CdpError::JavascriptException(Box::new(exception)));
993 }
994 Ok(EvaluationResult::new(resp.result))
995 }
996
997 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
998 self.execution_context_for_world(None, DOMWorldKind::Main)
999 .await
1000 }
1001
1002 pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
1003 self.execution_context_for_world(None, DOMWorldKind::Secondary)
1004 .await
1005 }
1006
1007 pub async fn frame_execution_context(
1008 &self,
1009 frame_id: FrameId,
1010 ) -> Result<Option<ExecutionContextId>> {
1011 self.execution_context_for_world(Some(frame_id), DOMWorldKind::Main)
1012 .await
1013 }
1014
1015 pub async fn frame_secondary_execution_context(
1016 &self,
1017 frame_id: FrameId,
1018 ) -> Result<Option<ExecutionContextId>> {
1019 self.execution_context_for_world(Some(frame_id), DOMWorldKind::Secondary)
1020 .await
1021 }
1022
1023 pub async fn execution_context_for_world(
1024 &self,
1025 frame_id: Option<FrameId>,
1026 dom_world: DOMWorldKind,
1027 ) -> Result<Option<ExecutionContextId>> {
1028 let (tx, rx) = oneshot_channel();
1029 let msg = TargetMessage::GetExecutionContext(GetExecutionContext {
1030 dom_world,
1031 frame_id,
1032 tx,
1033 });
1034 match self.sender.try_send(msg) {
1035 Ok(()) => {}
1036 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
1037 tokio::time::timeout(self.request_timeout, self.sender.send(msg))
1038 .await
1039 .map_err(|_| CdpError::Timeout)?
1040 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
1041 }
1042 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
1043 return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
1044 }
1045 }
1046 Ok(tokio::time::timeout(self.request_timeout, rx)
1047 .await
1048 .map_err(|_| CdpError::Timeout)??)
1049 }
1050
1051 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
1053 Ok(self
1054 .execute(GetLayoutMetricsParams::default())
1055 .await?
1056 .result)
1057 }
1058
1059 pub async fn set_bypass_csp(&self, enabled: bool) -> Result<&Self> {
1061 self.execute(SetBypassCspParams::new(enabled)).await?;
1062 Ok(self)
1063 }
1064
1065 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
1067 self.activate().await?;
1068 let params = params.into();
1069 let full_page = params.full_page();
1070 let omit_background = params.omit_background();
1071
1072 let mut cdp_params = params.cdp_params;
1073
1074 if full_page {
1075 let metrics = self.layout_metrics().await?;
1076 let width = metrics.css_content_size.width;
1077 let height = metrics.css_content_size.height;
1078
1079 cdp_params.clip = Some(Viewport {
1080 x: 0.,
1081 y: 0.,
1082 width,
1083 height,
1084 scale: 1.,
1085 });
1086
1087 self.execute(SetDeviceMetricsOverrideParams::new(
1088 width as i64,
1089 height as i64,
1090 1.,
1091 false,
1092 ))
1093 .await?;
1094 }
1095
1096 if omit_background {
1097 self.execute(SetDefaultBackgroundColorOverrideParams {
1098 color: Some(Rgba {
1099 r: 0,
1100 g: 0,
1101 b: 0,
1102 a: Some(0.),
1103 }),
1104 })
1105 .await?;
1106 }
1107
1108 let res = self.execute(cdp_params).await?.result;
1109
1110 if omit_background {
1111 self.send_command(SetDefaultBackgroundColorOverrideParams { color: None })
1112 .await?;
1113 }
1114
1115 if full_page {
1116 self.send_command(ClearDeviceMetricsOverrideParams {})
1117 .await?;
1118 }
1119
1120 Ok(utils::base64::decode(&res.data)?)
1121 }
1122
1123 pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1125 self.activate().await?;
1126 let params = params.into();
1127
1128 let res = self.execute(params).await?.result;
1129
1130 Ok(utils::base64::decode(&res.data)?)
1131 }
1132}
1133
1134pub(crate) async fn execute<T: Command>(
1135 cmd: T,
1136 sender: PageSender,
1137 session: Option<SessionId>,
1138 request_timeout: std::time::Duration,
1139) -> Result<CommandResponse<T::Response>> {
1140 let method = cmd.identifier();
1141 let rx = send_command(cmd, sender, session, request_timeout).await?;
1142 let resp = tokio::time::timeout(request_timeout, rx)
1143 .await
1144 .map_err(|_| CdpError::Timeout)???;
1145 to_command_response::<T>(resp, method)
1146}
1147
1148pub(crate) async fn send_command<T: Command>(
1153 cmd: T,
1154 sender: PageSender,
1155 session: Option<SessionId>,
1156 request_timeout: std::time::Duration,
1157) -> Result<tokio::sync::oneshot::Receiver<Result<chromiumoxide_types::Response, CdpError>>> {
1158 let (tx, rx) = oneshot_channel();
1159 let msg = CommandMessage::with_session(cmd, tx, session)?;
1160 let target_msg = TargetMessage::Command(msg);
1161 match sender.try_send(target_msg) {
1162 Ok(()) => {}
1163 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
1164 tokio::time::timeout(request_timeout, sender.send(msg))
1165 .await
1166 .map_err(|_| CdpError::Timeout)?
1167 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
1168 }
1169 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
1170 return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
1171 }
1172 }
1173 Ok(rx)
1174}
1175
1176#[cfg(test)]
1177mod page_channel_capacity_tests {
1178 use super::{PageHandle, DEFAULT_PAGE_CHANNEL_CAPACITY};
1198 use crate::handler::target::TargetMessage;
1199 use chromiumoxide_cdp::cdp::browser_protocol::target::{SessionId, TargetId};
1200 use std::time::Duration;
1201 use tokio::sync::mpsc::error::TrySendError;
1202
1203 fn make_msg() -> TargetMessage {
1207 TargetMessage::BlockNetwork(false)
1208 }
1209
1210 fn make_handle(capacity: usize) -> PageHandle {
1211 PageHandle::with_capacity(
1212 TargetId::from("t".to_string()),
1213 SessionId::from("s".to_string()),
1214 None,
1215 Duration::from_secs(30),
1216 None,
1217 capacity,
1218 )
1219 }
1220
1221 fn observed_capacity(handle: &PageHandle, upper_bound: usize) -> usize {
1225 let sender = &handle.page.sender;
1229 let mut sent = 0;
1230 for _ in 0..upper_bound {
1231 match sender.try_send(make_msg()) {
1232 Ok(()) => sent += 1,
1233 Err(TrySendError::Full(_)) => return sent,
1234 Err(TrySendError::Closed(_)) => {
1235 panic!("channel unexpectedly closed at {sent} sends")
1236 }
1237 }
1238 }
1239 sent
1240 }
1241
1242 #[test]
1243 fn new_delegates_to_default_capacity() {
1244 let handle = PageHandle::new(
1245 TargetId::from("t".to_string()),
1246 SessionId::from("s".to_string()),
1247 None,
1248 Duration::from_secs(30),
1249 None,
1250 );
1251 let n = observed_capacity(&handle, DEFAULT_PAGE_CHANNEL_CAPACITY + 16);
1252 assert_eq!(
1253 n, DEFAULT_PAGE_CHANNEL_CAPACITY,
1254 "legacy PageHandle::new must preserve the 2048-slot default"
1255 );
1256 }
1257
1258 #[test]
1259 fn with_capacity_respects_arbitrary_value() {
1260 for n in [1_usize, 4, 16, 64] {
1263 let handle = make_handle(n);
1264 assert_eq!(
1265 observed_capacity(&handle, n + 16),
1266 n,
1267 "with_capacity({n}) should produce exactly {n} slots",
1268 );
1269 }
1270 }
1271
1272 #[test]
1273 fn zero_capacity_is_clamped_to_one_and_does_not_panic() {
1274 let handle = make_handle(0);
1277 assert_eq!(
1278 observed_capacity(&handle, 4),
1279 1,
1280 "zero capacity must clamp to 1 and not panic"
1281 );
1282 }
1283}