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 mut deadline = tokio::time::Instant::now();
401 for step in &path {
402 self.send_command(DispatchMouseEventParams::new(
403 DispatchMouseEventType::MouseMoved,
404 step.point.x,
405 step.point.y,
406 ))
407 .await?;
408 deadline += step.delay;
413 tokio::time::sleep_until(deadline).await;
414 }
415 Ok(self)
416 }
417
418 pub fn mouse_position(&self) -> Point {
420 self.smart_mouse.position()
421 }
422
423 pub async fn scroll_by(
426 &self,
427 delta_x: f64,
428 delta_y: f64,
429 behavior: ScrollBehavior,
430 ) -> Result<&Self> {
431 let behavior_str = match behavior {
432 ScrollBehavior::Auto => "auto",
433 ScrollBehavior::Instant => "instant",
434 ScrollBehavior::Smooth => "smooth",
435 };
436
437 self.evaluate_expression(format!(
438 "window.scrollBy({{top: {}, left: {}, behavior: '{}'}});",
439 delta_y, delta_x, behavior_str
440 ))
441 .await?;
442
443 Ok(self)
444 }
445
446 pub async fn drag(
451 &self,
452 drag_type: DispatchDragEventType,
453 point: Point,
454 drag_data: DragData,
455 modifiers: Option<i64>,
456 ) -> Result<&Self> {
457 let mut params: DispatchDragEventParams =
458 DispatchDragEventParams::new(drag_type, point.x, point.y, drag_data);
459
460 if let Some(modifiers) = modifiers {
461 params.modifiers = Some(modifiers);
462 }
463
464 self.execute(params).await?;
465 Ok(self)
466 }
467
468 pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
471 let mut params: DispatchMouseEventParams =
472 DispatchMouseEventParams::new(DispatchMouseEventType::MouseWheel, point.x, point.y);
473
474 params.delta_x = Some(delta.delta_x);
475 params.delta_y = Some(delta.delta_y);
476
477 self.execute(params).await?;
478 Ok(self)
479 }
480
481 pub async fn click_with_count_base(
483 &self,
484 point: Point,
485 click_count: impl Into<i64>,
486 modifiers: impl Into<i64>,
487 button: impl Into<MouseButton>,
488 ) -> Result<&Self> {
489 let cmd = DispatchMouseEventParams::builder()
490 .x(point.x)
491 .y(point.y)
492 .button(button)
493 .click_count(click_count)
494 .modifiers(modifiers);
495
496 if let Ok(cmd) = cmd
497 .clone()
498 .r#type(DispatchMouseEventType::MousePressed)
499 .build()
500 {
501 self.move_mouse(point).await?.send_command(cmd).await?;
502 }
503
504 if let Ok(cmd) = cmd.r#type(DispatchMouseEventType::MouseReleased).build() {
505 self.execute(cmd).await?;
506 }
507
508 self.smart_mouse.set_position(point);
509 Ok(self)
510 }
511
512 pub async fn click_smooth(&self, point: Point) -> Result<&Self> {
514 self.move_mouse_smooth(point).await?;
515 self.click(point).await
516 }
517
518 pub async fn click_with_count(
520 &self,
521 point: Point,
522 click_count: impl Into<i64>,
523 modifiers: impl Into<i64>,
524 ) -> Result<&Self> {
525 self.click_with_count_base(point, click_count, modifiers, MouseButton::Left)
526 .await
527 }
528
529 pub async fn right_click_with_count(
531 &self,
532 point: Point,
533 click_count: impl Into<i64>,
534 modifiers: impl Into<i64>,
535 ) -> Result<&Self> {
536 self.click_with_count_base(point, click_count, modifiers, MouseButton::Right)
537 .await
538 }
539
540 pub async fn middle_click_with_count(
542 &self,
543 point: Point,
544 click_count: impl Into<i64>,
545 modifiers: impl Into<i64>,
546 ) -> Result<&Self> {
547 self.click_with_count_base(point, click_count, modifiers, MouseButton::Middle)
548 .await
549 }
550
551 pub async fn back_click_with_count(
553 &self,
554 point: Point,
555 click_count: impl Into<i64>,
556 modifiers: impl Into<i64>,
557 ) -> Result<&Self> {
558 self.click_with_count_base(point, click_count, modifiers, MouseButton::Back)
559 .await
560 }
561
562 pub async fn forward_click_with_count(
564 &self,
565 point: Point,
566 click_count: impl Into<i64>,
567 modifiers: impl Into<i64>,
568 ) -> Result<&Self> {
569 self.click_with_count_base(point, click_count, modifiers, MouseButton::Forward)
570 .await
571 }
572
573 pub async fn click_and_drag(
575 &self,
576 from: Point,
577 to: Point,
578 modifiers: impl Into<i64>,
579 ) -> Result<&Self> {
580 let modifiers = modifiers.into();
581 let click_count = 1;
582
583 let cmd = DispatchMouseEventParams::builder()
584 .button(MouseButton::Left)
585 .click_count(click_count)
586 .modifiers(modifiers);
587
588 if let Ok(cmd) = cmd
589 .clone()
590 .x(from.x)
591 .y(from.y)
592 .r#type(DispatchMouseEventType::MousePressed)
593 .build()
594 {
595 self.move_mouse(from).await?.send_command(cmd).await?;
596 }
597
598 if let Ok(cmd) = cmd
599 .clone()
600 .x(to.x)
601 .y(to.y)
602 .r#type(DispatchMouseEventType::MouseMoved)
603 .build()
604 {
605 self.move_mouse(to).await?.send_command(cmd).await?;
606 }
607
608 if let Ok(cmd) = cmd
609 .r#type(DispatchMouseEventType::MouseReleased)
610 .x(to.x)
611 .y(to.y)
612 .build()
613 {
614 self.send_command(cmd).await?;
615 }
616
617 self.smart_mouse.set_position(to);
618 Ok(self)
619 }
620
621 pub async fn click_and_drag_smooth(
624 &self,
625 from: Point,
626 to: Point,
627 modifiers: impl Into<i64>,
628 ) -> Result<&Self> {
629 let modifiers = modifiers.into();
630
631 self.move_mouse_smooth(from).await?;
633
634 if let Ok(cmd) = DispatchMouseEventParams::builder()
636 .x(from.x)
637 .y(from.y)
638 .button(MouseButton::Left)
639 .click_count(1)
640 .modifiers(modifiers)
641 .r#type(DispatchMouseEventType::MousePressed)
642 .build()
643 {
644 self.send_command(cmd).await?;
645 }
646
647 let path = self.smart_mouse.path_to(to);
649 for step in &path {
650 if let Ok(cmd) = DispatchMouseEventParams::builder()
651 .x(step.point.x)
652 .y(step.point.y)
653 .button(MouseButton::Left)
654 .modifiers(modifiers)
655 .r#type(DispatchMouseEventType::MouseMoved)
656 .build()
657 {
658 self.send_command(cmd).await?;
659 }
660 tokio::time::sleep(step.delay).await;
661 }
662
663 if let Ok(cmd) = DispatchMouseEventParams::builder()
665 .x(to.x)
666 .y(to.y)
667 .button(MouseButton::Left)
668 .click_count(1)
669 .modifiers(modifiers)
670 .r#type(DispatchMouseEventType::MouseReleased)
671 .build()
672 {
673 self.send_command(cmd).await?;
674 }
675
676 Ok(self)
677 }
678
679 pub async fn click(&self, point: Point) -> Result<&Self> {
681 self.click_with_count(point, 1, 0).await
682 }
683
684 pub async fn double_click(&self, point: Point) -> Result<&Self> {
686 self.click_with_count(point, 2, 0).await
687 }
688
689 pub async fn right_click(&self, point: Point) -> Result<&Self> {
691 self.right_click_with_count(point, 1, 0).await
692 }
693
694 pub async fn middle_click(&self, point: Point) -> Result<&Self> {
696 self.middle_click_with_count(point, 1, 0).await
697 }
698
699 pub async fn back_click(&self, point: Point) -> Result<&Self> {
701 self.back_click_with_count(point, 1, 0).await
702 }
703
704 pub async fn forward_click(&self, point: Point) -> Result<&Self> {
706 self.forward_click_with_count(point, 1, 0).await
707 }
708
709 pub async fn click_with_modifier(
711 &self,
712 point: Point,
713 modifiers: impl Into<i64>,
714 ) -> Result<&Self> {
715 self.click_with_count(point, 1, modifiers).await
716 }
717
718 pub async fn right_click_with_modifier(
720 &self,
721 point: Point,
722 modifiers: impl Into<i64>,
723 ) -> Result<&Self> {
724 self.right_click_with_count(point, 1, modifiers).await
725 }
726
727 pub async fn middle_click_with_modifier(
729 &self,
730 point: Point,
731 modifiers: impl Into<i64>,
732 ) -> Result<&Self> {
733 self.middle_click_with_count(point, 1, modifiers).await
734 }
735
736 pub async fn double_click_with_modifier(
738 &self,
739 point: Point,
740 modifiers: impl Into<i64>,
741 ) -> Result<&Self> {
742 self.click_with_count(point, 2, modifiers).await
743 }
744
745 pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
754 for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
755 self._press_key(c, None).await?;
756 }
757 Ok(self)
758 }
759
760 pub async fn get_full_ax_tree(
762 &self,
763 depth: Option<i64>,
764 frame_id: Option<FrameId>,
765 ) -> Result<GetFullAxTreeReturns> {
766 let mut builder = GetFullAxTreeParamsBuilder::default();
767
768 if let Some(depth) = depth {
769 builder = builder.depth(depth);
770 }
771
772 if let Some(frame_id) = frame_id {
773 builder = builder.frame_id(frame_id);
774 }
775
776 let resp = self.execute(builder.build()).await?;
777
778 Ok(resp.result)
779 }
780
781 pub async fn get_partial_ax_tree(
783 &self,
784 node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
785 backend_node_id: Option<BackendNodeId>,
786 object_id: Option<RemoteObjectId>,
787 fetch_relatives: Option<bool>,
788 ) -> Result<GetPartialAxTreeReturns> {
789 let mut builder = GetPartialAxTreeParamsBuilder::default();
790
791 if let Some(node_id) = node_id {
792 builder = builder.node_id(node_id);
793 }
794
795 if let Some(backend_node_id) = backend_node_id {
796 builder = builder.backend_node_id(backend_node_id);
797 }
798
799 if let Some(object_id) = object_id {
800 builder = builder.object_id(object_id);
801 }
802
803 if let Some(fetch_relatives) = fetch_relatives {
804 builder = builder.fetch_relatives(fetch_relatives);
805 }
806
807 let resp = self.execute(builder.build()).await?;
808
809 Ok(resp.result)
810 }
811
812 pub async fn type_str_with_modifier(
821 &self,
822 input: impl AsRef<str>,
823 modifiers: Option<i64>,
824 ) -> Result<&Self> {
825 for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
826 self._press_key(c, modifiers).await?;
827 }
828 Ok(self)
829 }
830
831 async fn _press_key(&self, key: impl AsRef<str>, modifiers: Option<i64>) -> Result<&Self> {
834 let key = key.as_ref();
835 let key_definition = keys::get_key_definition(key)
836 .ok_or_else(|| CdpError::msg(format!("Key not found: {key}")))?;
837 let mut cmd = DispatchKeyEventParams::builder();
838
839 let key_down_event_type = if let Some(txt) = key_definition.text {
842 cmd = cmd.text(txt);
843 DispatchKeyEventType::KeyDown
844 } else if key_definition.key.len() == 1 {
845 cmd = cmd.text(key_definition.key);
846 DispatchKeyEventType::KeyDown
847 } else {
848 DispatchKeyEventType::RawKeyDown
849 };
850
851 cmd = cmd
852 .r#type(DispatchKeyEventType::KeyDown)
853 .key(key_definition.key)
854 .code(key_definition.code)
855 .windows_virtual_key_code(key_definition.key_code)
856 .native_virtual_key_code(key_definition.key_code);
857
858 if let Some(modifiers) = modifiers {
859 cmd = cmd.modifiers(modifiers);
860 }
861
862 if let Ok(cmd) = cmd.clone().r#type(key_down_event_type).build() {
863 self.execute(cmd).await?;
864 }
865
866 if let Ok(cmd) = cmd.r#type(DispatchKeyEventType::KeyUp).build() {
867 self.execute(cmd).await?;
868 }
869
870 Ok(self)
871 }
872
873 pub async fn press_key(&self, key: impl AsRef<str>) -> Result<&Self> {
876 self._press_key(key, None).await
877 }
878
879 pub async fn press_key_with_modifier(
882 &self,
883 key: impl AsRef<str>,
884 modifiers: Option<i64>,
885 ) -> Result<&Self> {
886 self._press_key(key, modifiers).await
887 }
888
889 pub async fn call_js_fn(
892 &self,
893 function_declaration: impl Into<String>,
894 await_promise: bool,
895 remote_object_id: RemoteObjectId,
896 ) -> Result<CallFunctionOnReturns> {
897 if let Ok(resp) = CallFunctionOnParams::builder()
898 .object_id(remote_object_id)
899 .function_declaration(function_declaration)
900 .generate_preview(true)
901 .await_promise(await_promise)
902 .build()
903 {
904 let resp = self.execute(resp).await?;
905 Ok(resp.result)
906 } else {
907 Err(CdpError::NotFound)
908 }
909 }
910
911 pub async fn evaluate_expression(
912 &self,
913 evaluate: impl Into<EvaluateParams>,
914 ) -> Result<EvaluationResult> {
915 let mut evaluate = evaluate.into();
916 if evaluate.context_id.is_none() {
917 evaluate.context_id = self.execution_context().await?;
918 }
919 if evaluate.await_promise.is_none() {
920 evaluate.await_promise = Some(true);
921 }
922 if evaluate.return_by_value.is_none() {
923 evaluate.return_by_value = Some(true);
924 }
925
926 let resp = self.execute(evaluate).await?.result;
929
930 if let Some(exception) = resp.exception_details {
931 return Err(CdpError::JavascriptException(Box::new(exception)));
932 }
933
934 Ok(EvaluationResult::new(resp.result))
935 }
936
937 pub async fn evaluate_function(
938 &self,
939 evaluate: impl Into<CallFunctionOnParams>,
940 ) -> Result<EvaluationResult> {
941 let mut evaluate = evaluate.into();
942 if evaluate.execution_context_id.is_none() {
943 evaluate.execution_context_id = self.execution_context().await?;
944 }
945 if evaluate.await_promise.is_none() {
946 evaluate.await_promise = Some(true);
947 }
948 if evaluate.return_by_value.is_none() {
949 evaluate.return_by_value = Some(true);
950 }
951
952 let resp = self.execute(evaluate).await?.result;
955 if let Some(exception) = resp.exception_details {
956 return Err(CdpError::JavascriptException(Box::new(exception)));
957 }
958 Ok(EvaluationResult::new(resp.result))
959 }
960
961 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
962 self.execution_context_for_world(None, DOMWorldKind::Main)
963 .await
964 }
965
966 pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
967 self.execution_context_for_world(None, DOMWorldKind::Secondary)
968 .await
969 }
970
971 pub async fn frame_execution_context(
972 &self,
973 frame_id: FrameId,
974 ) -> Result<Option<ExecutionContextId>> {
975 self.execution_context_for_world(Some(frame_id), DOMWorldKind::Main)
976 .await
977 }
978
979 pub async fn frame_secondary_execution_context(
980 &self,
981 frame_id: FrameId,
982 ) -> Result<Option<ExecutionContextId>> {
983 self.execution_context_for_world(Some(frame_id), DOMWorldKind::Secondary)
984 .await
985 }
986
987 pub async fn execution_context_for_world(
988 &self,
989 frame_id: Option<FrameId>,
990 dom_world: DOMWorldKind,
991 ) -> Result<Option<ExecutionContextId>> {
992 let (tx, rx) = oneshot_channel();
993 let msg = TargetMessage::GetExecutionContext(GetExecutionContext {
994 dom_world,
995 frame_id,
996 tx,
997 });
998 match self.sender.try_send(msg) {
999 Ok(()) => {}
1000 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
1001 tokio::time::timeout(self.request_timeout, self.sender.send(msg))
1002 .await
1003 .map_err(|_| CdpError::Timeout)?
1004 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
1005 }
1006 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
1007 return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
1008 }
1009 }
1010 Ok(tokio::time::timeout(self.request_timeout, rx)
1011 .await
1012 .map_err(|_| CdpError::Timeout)??)
1013 }
1014
1015 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
1017 Ok(self
1018 .execute(GetLayoutMetricsParams::default())
1019 .await?
1020 .result)
1021 }
1022
1023 pub async fn set_bypass_csp(&self, enabled: bool) -> Result<&Self> {
1025 self.execute(SetBypassCspParams::new(enabled)).await?;
1026 Ok(self)
1027 }
1028
1029 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
1031 self.activate().await?;
1032 let params = params.into();
1033 let full_page = params.full_page();
1034 let omit_background = params.omit_background();
1035
1036 let mut cdp_params = params.cdp_params;
1037
1038 if full_page {
1039 let metrics = self.layout_metrics().await?;
1040 let width = metrics.css_content_size.width;
1041 let height = metrics.css_content_size.height;
1042
1043 cdp_params.clip = Some(Viewport {
1044 x: 0.,
1045 y: 0.,
1046 width,
1047 height,
1048 scale: 1.,
1049 });
1050
1051 self.execute(SetDeviceMetricsOverrideParams::new(
1052 width as i64,
1053 height as i64,
1054 1.,
1055 false,
1056 ))
1057 .await?;
1058 }
1059
1060 if omit_background {
1061 self.execute(SetDefaultBackgroundColorOverrideParams {
1062 color: Some(Rgba {
1063 r: 0,
1064 g: 0,
1065 b: 0,
1066 a: Some(0.),
1067 }),
1068 })
1069 .await?;
1070 }
1071
1072 let res = self.execute(cdp_params).await?.result;
1073
1074 if omit_background {
1075 self.send_command(SetDefaultBackgroundColorOverrideParams { color: None })
1076 .await?;
1077 }
1078
1079 if full_page {
1080 self.send_command(ClearDeviceMetricsOverrideParams {})
1081 .await?;
1082 }
1083
1084 Ok(utils::base64::decode(&res.data)?)
1085 }
1086
1087 pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1089 self.activate().await?;
1090 let params = params.into();
1091
1092 let res = self.execute(params).await?.result;
1093
1094 Ok(utils::base64::decode(&res.data)?)
1095 }
1096}
1097
1098pub(crate) async fn execute<T: Command>(
1099 cmd: T,
1100 sender: PageSender,
1101 session: Option<SessionId>,
1102 request_timeout: std::time::Duration,
1103) -> Result<CommandResponse<T::Response>> {
1104 let method = cmd.identifier();
1105 let rx = send_command(cmd, sender, session, request_timeout).await?;
1106 let resp = tokio::time::timeout(request_timeout, rx)
1107 .await
1108 .map_err(|_| CdpError::Timeout)???;
1109 to_command_response::<T>(resp, method)
1110}
1111
1112pub(crate) async fn send_command<T: Command>(
1117 cmd: T,
1118 sender: PageSender,
1119 session: Option<SessionId>,
1120 request_timeout: std::time::Duration,
1121) -> Result<tokio::sync::oneshot::Receiver<Result<chromiumoxide_types::Response, CdpError>>> {
1122 let (tx, rx) = oneshot_channel();
1123 let msg = CommandMessage::with_session(cmd, tx, session)?;
1124 let target_msg = TargetMessage::Command(msg);
1125 match sender.try_send(target_msg) {
1126 Ok(()) => {}
1127 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
1128 tokio::time::timeout(request_timeout, sender.send(msg))
1129 .await
1130 .map_err(|_| CdpError::Timeout)?
1131 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
1132 }
1133 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
1134 return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
1135 }
1136 }
1137 Ok(rx)
1138}
1139
1140#[cfg(test)]
1141mod page_channel_capacity_tests {
1142 use super::{PageHandle, DEFAULT_PAGE_CHANNEL_CAPACITY};
1162 use crate::handler::target::TargetMessage;
1163 use chromiumoxide_cdp::cdp::browser_protocol::target::{SessionId, TargetId};
1164 use std::time::Duration;
1165 use tokio::sync::mpsc::error::TrySendError;
1166
1167 fn make_msg() -> TargetMessage {
1171 TargetMessage::BlockNetwork(false)
1172 }
1173
1174 fn make_handle(capacity: usize) -> PageHandle {
1175 PageHandle::with_capacity(
1176 TargetId::from("t".to_string()),
1177 SessionId::from("s".to_string()),
1178 None,
1179 Duration::from_secs(30),
1180 None,
1181 capacity,
1182 )
1183 }
1184
1185 fn observed_capacity(handle: &PageHandle, upper_bound: usize) -> usize {
1189 let sender = &handle.page.sender;
1193 let mut sent = 0;
1194 for _ in 0..upper_bound {
1195 match sender.try_send(make_msg()) {
1196 Ok(()) => sent += 1,
1197 Err(TrySendError::Full(_)) => return sent,
1198 Err(TrySendError::Closed(_)) => {
1199 panic!("channel unexpectedly closed at {sent} sends")
1200 }
1201 }
1202 }
1203 sent
1204 }
1205
1206 #[test]
1207 fn new_delegates_to_default_capacity() {
1208 let handle = PageHandle::new(
1209 TargetId::from("t".to_string()),
1210 SessionId::from("s".to_string()),
1211 None,
1212 Duration::from_secs(30),
1213 None,
1214 );
1215 let n = observed_capacity(&handle, DEFAULT_PAGE_CHANNEL_CAPACITY + 16);
1216 assert_eq!(
1217 n, DEFAULT_PAGE_CHANNEL_CAPACITY,
1218 "legacy PageHandle::new must preserve the 2048-slot default"
1219 );
1220 }
1221
1222 #[test]
1223 fn with_capacity_respects_arbitrary_value() {
1224 for n in [1_usize, 4, 16, 64] {
1227 let handle = make_handle(n);
1228 assert_eq!(
1229 observed_capacity(&handle, n + 16),
1230 n,
1231 "with_capacity({n}) should produce exactly {n} slots",
1232 );
1233 }
1234 }
1235
1236 #[test]
1237 fn zero_capacity_is_clamped_to_one_and_does_not_panic() {
1238 let handle = make_handle(0);
1241 assert_eq!(
1242 observed_capacity(&handle, 4),
1243 1,
1244 "zero capacity must clamp to 1 and not panic"
1245 );
1246 }
1247}