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
66impl PageHandle {
67 pub fn new(
68 target_id: TargetId,
69 session_id: SessionId,
70 opener_id: Option<TargetId>,
71 request_timeout: std::time::Duration,
72 page_wake: Option<Arc<Notify>>,
73 ) -> Self {
74 let (commands, rx) = channel(2048);
75 let page = PageInner {
76 target_id,
77 session_id,
78 opener_id,
79 sender: PageSender::new(commands, page_wake),
80 smart_mouse: SmartMouse::new(),
81 request_timeout,
82 };
83 ACTIVE_PAGES.fetch_add(1, Ordering::Relaxed);
84 Self {
85 rx,
86 page: Arc::new(page),
87 }
88 }
89
90 pub(crate) fn inner(&self) -> &Arc<PageInner> {
91 &self.page
92 }
93}
94
95#[derive(Debug)]
96pub(crate) struct PageInner {
97 target_id: TargetId,
99 session_id: SessionId,
101 opener_id: Option<TargetId>,
103 sender: PageSender,
105 pub(crate) smart_mouse: SmartMouse,
107 request_timeout: std::time::Duration,
109}
110
111impl Drop for PageInner {
112 fn drop(&mut self) {
113 ACTIVE_PAGES.fetch_sub(1, Ordering::Relaxed);
114 }
115}
116
117impl PageInner {
118 pub(crate) async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
120 execute(
121 cmd,
122 self.sender.clone(),
123 Some(self.session_id.clone()),
124 self.request_timeout,
125 )
126 .await
127 }
128
129 pub(crate) async fn send_command<T: Command>(&self, cmd: T) -> Result<&Self> {
131 let _ = send_command(
132 cmd,
133 self.sender.clone(),
134 Some(self.session_id.clone()),
135 self.request_timeout,
136 )
137 .await;
138 Ok(self)
139 }
140
141 pub(crate) fn command_future<T: Command>(&self, cmd: T) -> Result<CommandFuture<T>> {
143 CommandFuture::new(
144 cmd,
145 self.sender.clone(),
146 Some(self.session_id.clone()),
147 self.request_timeout,
148 )
149 }
150
151 pub(crate) fn wait_for_navigation(&self) -> TargetMessageFuture<ArcHttpRequest> {
153 TargetMessageFuture::<ArcHttpRequest>::wait_for_navigation(
154 self.sender.clone(),
155 self.request_timeout,
156 )
157 }
158
159 pub(crate) fn wait_for_dom_content_loaded(&self) -> TargetMessageFuture<ArcHttpRequest> {
161 TargetMessageFuture::<ArcHttpRequest>::wait_for_dom_content_loaded(
162 self.sender.clone(),
163 self.request_timeout,
164 )
165 }
166
167 pub(crate) fn wait_for_load(&self) -> TargetMessageFuture<ArcHttpRequest> {
169 TargetMessageFuture::<ArcHttpRequest>::wait_for_load(
170 self.sender.clone(),
171 self.request_timeout,
172 )
173 }
174
175 pub(crate) fn wait_for_network_idle(&self) -> TargetMessageFuture<ArcHttpRequest> {
177 TargetMessageFuture::<ArcHttpRequest>::wait_for_network_idle(
178 self.sender.clone(),
179 self.request_timeout,
180 )
181 }
182
183 pub(crate) fn wait_for_network_almost_idle(&self) -> TargetMessageFuture<ArcHttpRequest> {
185 TargetMessageFuture::<ArcHttpRequest>::wait_for_network_almost_idle(
186 self.sender.clone(),
187 self.request_timeout,
188 )
189 }
190
191 pub(crate) fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
194 Ok(HttpFuture::new(
195 self.sender.clone(),
196 self.command_future(cmd)?,
197 self.request_timeout,
198 ))
199 }
200
201 pub fn target_id(&self) -> &TargetId {
203 &self.target_id
204 }
205
206 pub fn session_id(&self) -> &SessionId {
208 &self.session_id
209 }
210
211 pub fn opener_id(&self) -> &Option<TargetId> {
213 &self.opener_id
214 }
215
216 pub(crate) async fn send_msg(&self, msg: TargetMessage) -> Result<()> {
220 match self.sender.try_send(msg) {
221 Ok(()) => Ok(()),
222 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
223 tokio::time::timeout(self.request_timeout, self.sender.send(msg))
224 .await
225 .map_err(|_| CdpError::Timeout)?
226 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
227 Ok(())
228 }
229 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
230 Err(CdpError::ChannelSendError(crate::error::ChannelError::Send))
231 }
232 }
233 }
234
235 pub(crate) async fn recv_msg<T>(&self, rx: tokio::sync::oneshot::Receiver<T>) -> Result<T> {
237 tokio::time::timeout(self.request_timeout, rx)
238 .await
239 .map_err(|_| CdpError::Timeout)?
240 .map_err(|e| CdpError::ChannelSendError(crate::error::ChannelError::Canceled(e)))
241 }
242
243 pub async fn find_element(&self, selector: impl Into<String>, node: NodeId) -> Result<NodeId> {
246 Ok(self
247 .execute(QuerySelectorParams::new(node, selector))
248 .await?
249 .node_id)
250 }
251
252 pub async fn outer_html(
254 &self,
255 object_id: RemoteObjectId,
256 node_id: NodeId,
257 backend_node_id: BackendNodeId,
258 ) -> Result<String> {
259 let cmd = GetOuterHtmlParams {
260 backend_node_id: Some(backend_node_id),
261 node_id: Some(node_id),
262 object_id: Some(object_id),
263 ..Default::default()
264 };
265
266 let chromiumoxide_types::CommandResponse { result, .. } = self.execute(cmd).await?;
267
268 Ok(result.outer_html)
269 }
270
271 pub async fn activate(&self) -> Result<&Self> {
273 self.execute(ActivateTargetParams::new(self.target_id().clone()))
274 .await?;
275 Ok(self)
276 }
277
278 pub async fn version(&self) -> Result<GetVersionReturns> {
280 Ok(self.execute(GetVersionParams::default()).await?.result)
281 }
282
283 pub(crate) async fn find_elements(
285 &self,
286 selector: impl Into<String>,
287 node: NodeId,
288 ) -> Result<Vec<NodeId>> {
289 Ok(self
290 .execute(QuerySelectorAllParams::new(node, selector))
291 .await?
292 .result
293 .node_ids)
294 }
295
296 pub async fn find_xpaths(&self, query: impl Into<String>) -> Result<Vec<NodeId>> {
298 let perform_search_returns = self
299 .execute(PerformSearchParams {
300 query: query.into(),
301 include_user_agent_shadow_dom: Some(true),
302 })
303 .await?
304 .result;
305
306 let search_results = self
307 .execute(GetSearchResultsParams::new(
308 perform_search_returns.search_id.clone(),
309 0,
310 perform_search_returns.result_count,
311 ))
312 .await?
313 .result;
314
315 self.execute(DiscardSearchResultsParams::new(
316 perform_search_returns.search_id,
317 ))
318 .await?;
319
320 Ok(search_results.node_ids)
321 }
322
323 pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
326 self.smart_mouse.set_position(point);
327 self.execute(DispatchMouseEventParams::new(
328 DispatchMouseEventType::MouseMoved,
329 point.x,
330 point.y,
331 ))
332 .await?;
333 Ok(self)
334 }
335
336 pub async fn move_mouse_smooth(&self, target: Point) -> Result<&Self> {
339 let path = self.smart_mouse.path_to(target);
340 for step in &path {
341 self.execute(DispatchMouseEventParams::new(
342 DispatchMouseEventType::MouseMoved,
343 step.point.x,
344 step.point.y,
345 ))
346 .await?;
347 tokio::time::sleep(step.delay).await;
348 }
349 Ok(self)
350 }
351
352 pub fn mouse_position(&self) -> Point {
354 self.smart_mouse.position()
355 }
356
357 pub async fn scroll_by(
360 &self,
361 delta_x: f64,
362 delta_y: f64,
363 behavior: ScrollBehavior,
364 ) -> Result<&Self> {
365 let behavior_str = match behavior {
366 ScrollBehavior::Auto => "auto",
367 ScrollBehavior::Instant => "instant",
368 ScrollBehavior::Smooth => "smooth",
369 };
370
371 self.evaluate_expression(format!(
372 "window.scrollBy({{top: {}, left: {}, behavior: '{}'}});",
373 delta_y, delta_x, behavior_str
374 ))
375 .await?;
376
377 Ok(self)
378 }
379
380 pub async fn drag(
385 &self,
386 drag_type: DispatchDragEventType,
387 point: Point,
388 drag_data: DragData,
389 modifiers: Option<i64>,
390 ) -> Result<&Self> {
391 let mut params: DispatchDragEventParams =
392 DispatchDragEventParams::new(drag_type, point.x, point.y, drag_data);
393
394 if let Some(modifiers) = modifiers {
395 params.modifiers = Some(modifiers);
396 }
397
398 self.execute(params).await?;
399 Ok(self)
400 }
401
402 pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
405 let mut params: DispatchMouseEventParams =
406 DispatchMouseEventParams::new(DispatchMouseEventType::MouseWheel, point.x, point.y);
407
408 params.delta_x = Some(delta.delta_x);
409 params.delta_y = Some(delta.delta_y);
410
411 self.execute(params).await?;
412 Ok(self)
413 }
414
415 pub async fn click_with_count_base(
417 &self,
418 point: Point,
419 click_count: impl Into<i64>,
420 modifiers: impl Into<i64>,
421 button: impl Into<MouseButton>,
422 ) -> Result<&Self> {
423 let cmd = DispatchMouseEventParams::builder()
424 .x(point.x)
425 .y(point.y)
426 .button(button)
427 .click_count(click_count)
428 .modifiers(modifiers);
429
430 if let Ok(cmd) = cmd
431 .clone()
432 .r#type(DispatchMouseEventType::MousePressed)
433 .build()
434 {
435 self.move_mouse(point).await?.send_command(cmd).await?;
436 }
437
438 if let Ok(cmd) = cmd.r#type(DispatchMouseEventType::MouseReleased).build() {
439 self.execute(cmd).await?;
440 }
441
442 self.smart_mouse.set_position(point);
443 Ok(self)
444 }
445
446 pub async fn click_smooth(&self, point: Point) -> Result<&Self> {
448 self.move_mouse_smooth(point).await?;
449 self.click(point).await
450 }
451
452 pub async fn click_with_count(
454 &self,
455 point: Point,
456 click_count: impl Into<i64>,
457 modifiers: impl Into<i64>,
458 ) -> Result<&Self> {
459 self.click_with_count_base(point, click_count, modifiers, MouseButton::Left)
460 .await
461 }
462
463 pub async fn right_click_with_count(
465 &self,
466 point: Point,
467 click_count: impl Into<i64>,
468 modifiers: impl Into<i64>,
469 ) -> Result<&Self> {
470 self.click_with_count_base(point, click_count, modifiers, MouseButton::Right)
471 .await
472 }
473
474 pub async fn middle_click_with_count(
476 &self,
477 point: Point,
478 click_count: impl Into<i64>,
479 modifiers: impl Into<i64>,
480 ) -> Result<&Self> {
481 self.click_with_count_base(point, click_count, modifiers, MouseButton::Middle)
482 .await
483 }
484
485 pub async fn back_click_with_count(
487 &self,
488 point: Point,
489 click_count: impl Into<i64>,
490 modifiers: impl Into<i64>,
491 ) -> Result<&Self> {
492 self.click_with_count_base(point, click_count, modifiers, MouseButton::Back)
493 .await
494 }
495
496 pub async fn forward_click_with_count(
498 &self,
499 point: Point,
500 click_count: impl Into<i64>,
501 modifiers: impl Into<i64>,
502 ) -> Result<&Self> {
503 self.click_with_count_base(point, click_count, modifiers, MouseButton::Forward)
504 .await
505 }
506
507 pub async fn click_and_drag(
509 &self,
510 from: Point,
511 to: Point,
512 modifiers: impl Into<i64>,
513 ) -> Result<&Self> {
514 let modifiers = modifiers.into();
515 let click_count = 1;
516
517 let cmd = DispatchMouseEventParams::builder()
518 .button(MouseButton::Left)
519 .click_count(click_count)
520 .modifiers(modifiers);
521
522 if let Ok(cmd) = cmd
523 .clone()
524 .x(from.x)
525 .y(from.y)
526 .r#type(DispatchMouseEventType::MousePressed)
527 .build()
528 {
529 self.move_mouse(from).await?.send_command(cmd).await?;
530 }
531
532 if let Ok(cmd) = cmd
533 .clone()
534 .x(to.x)
535 .y(to.y)
536 .r#type(DispatchMouseEventType::MouseMoved)
537 .build()
538 {
539 self.move_mouse(to).await?.send_command(cmd).await?;
540 }
541
542 if let Ok(cmd) = cmd
543 .r#type(DispatchMouseEventType::MouseReleased)
544 .x(to.x)
545 .y(to.y)
546 .build()
547 {
548 self.send_command(cmd).await?;
549 }
550
551 self.smart_mouse.set_position(to);
552 Ok(self)
553 }
554
555 pub async fn click_and_drag_smooth(
558 &self,
559 from: Point,
560 to: Point,
561 modifiers: impl Into<i64>,
562 ) -> Result<&Self> {
563 let modifiers = modifiers.into();
564
565 self.move_mouse_smooth(from).await?;
567
568 if let Ok(cmd) = DispatchMouseEventParams::builder()
570 .x(from.x)
571 .y(from.y)
572 .button(MouseButton::Left)
573 .click_count(1)
574 .modifiers(modifiers)
575 .r#type(DispatchMouseEventType::MousePressed)
576 .build()
577 {
578 self.send_command(cmd).await?;
579 }
580
581 let path = self.smart_mouse.path_to(to);
583 for step in &path {
584 if let Ok(cmd) = DispatchMouseEventParams::builder()
585 .x(step.point.x)
586 .y(step.point.y)
587 .button(MouseButton::Left)
588 .modifiers(modifiers)
589 .r#type(DispatchMouseEventType::MouseMoved)
590 .build()
591 {
592 self.send_command(cmd).await?;
593 }
594 tokio::time::sleep(step.delay).await;
595 }
596
597 if let Ok(cmd) = DispatchMouseEventParams::builder()
599 .x(to.x)
600 .y(to.y)
601 .button(MouseButton::Left)
602 .click_count(1)
603 .modifiers(modifiers)
604 .r#type(DispatchMouseEventType::MouseReleased)
605 .build()
606 {
607 self.send_command(cmd).await?;
608 }
609
610 Ok(self)
611 }
612
613 pub async fn click(&self, point: Point) -> Result<&Self> {
615 self.click_with_count(point, 1, 0).await
616 }
617
618 pub async fn double_click(&self, point: Point) -> Result<&Self> {
620 self.click_with_count(point, 2, 0).await
621 }
622
623 pub async fn right_click(&self, point: Point) -> Result<&Self> {
625 self.right_click_with_count(point, 1, 0).await
626 }
627
628 pub async fn middle_click(&self, point: Point) -> Result<&Self> {
630 self.middle_click_with_count(point, 1, 0).await
631 }
632
633 pub async fn back_click(&self, point: Point) -> Result<&Self> {
635 self.back_click_with_count(point, 1, 0).await
636 }
637
638 pub async fn forward_click(&self, point: Point) -> Result<&Self> {
640 self.forward_click_with_count(point, 1, 0).await
641 }
642
643 pub async fn click_with_modifier(
645 &self,
646 point: Point,
647 modifiers: impl Into<i64>,
648 ) -> Result<&Self> {
649 self.click_with_count(point, 1, modifiers).await
650 }
651
652 pub async fn right_click_with_modifier(
654 &self,
655 point: Point,
656 modifiers: impl Into<i64>,
657 ) -> Result<&Self> {
658 self.right_click_with_count(point, 1, modifiers).await
659 }
660
661 pub async fn middle_click_with_modifier(
663 &self,
664 point: Point,
665 modifiers: impl Into<i64>,
666 ) -> Result<&Self> {
667 self.middle_click_with_count(point, 1, modifiers).await
668 }
669
670 pub async fn double_click_with_modifier(
672 &self,
673 point: Point,
674 modifiers: impl Into<i64>,
675 ) -> Result<&Self> {
676 self.click_with_count(point, 2, modifiers).await
677 }
678
679 pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
688 for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
689 self._press_key(c, None).await?;
690 }
691 Ok(self)
692 }
693
694 pub async fn get_full_ax_tree(
696 &self,
697 depth: Option<i64>,
698 frame_id: Option<FrameId>,
699 ) -> Result<GetFullAxTreeReturns> {
700 let mut builder = GetFullAxTreeParamsBuilder::default();
701
702 if let Some(depth) = depth {
703 builder = builder.depth(depth);
704 }
705
706 if let Some(frame_id) = frame_id {
707 builder = builder.frame_id(frame_id);
708 }
709
710 let resp = self.execute(builder.build()).await?;
711
712 Ok(resp.result)
713 }
714
715 pub async fn get_partial_ax_tree(
717 &self,
718 node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
719 backend_node_id: Option<BackendNodeId>,
720 object_id: Option<RemoteObjectId>,
721 fetch_relatives: Option<bool>,
722 ) -> Result<GetPartialAxTreeReturns> {
723 let mut builder = GetPartialAxTreeParamsBuilder::default();
724
725 if let Some(node_id) = node_id {
726 builder = builder.node_id(node_id);
727 }
728
729 if let Some(backend_node_id) = backend_node_id {
730 builder = builder.backend_node_id(backend_node_id);
731 }
732
733 if let Some(object_id) = object_id {
734 builder = builder.object_id(object_id);
735 }
736
737 if let Some(fetch_relatives) = fetch_relatives {
738 builder = builder.fetch_relatives(fetch_relatives);
739 }
740
741 let resp = self.execute(builder.build()).await?;
742
743 Ok(resp.result)
744 }
745
746 pub async fn type_str_with_modifier(
755 &self,
756 input: impl AsRef<str>,
757 modifiers: Option<i64>,
758 ) -> Result<&Self> {
759 for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
760 self._press_key(c, modifiers).await?;
761 }
762 Ok(self)
763 }
764
765 async fn _press_key(&self, key: impl AsRef<str>, modifiers: Option<i64>) -> Result<&Self> {
768 let key = key.as_ref();
769 let key_definition = keys::get_key_definition(key)
770 .ok_or_else(|| CdpError::msg(format!("Key not found: {key}")))?;
771 let mut cmd = DispatchKeyEventParams::builder();
772
773 let key_down_event_type = if let Some(txt) = key_definition.text {
776 cmd = cmd.text(txt);
777 DispatchKeyEventType::KeyDown
778 } else if key_definition.key.len() == 1 {
779 cmd = cmd.text(key_definition.key);
780 DispatchKeyEventType::KeyDown
781 } else {
782 DispatchKeyEventType::RawKeyDown
783 };
784
785 cmd = cmd
786 .r#type(DispatchKeyEventType::KeyDown)
787 .key(key_definition.key)
788 .code(key_definition.code)
789 .windows_virtual_key_code(key_definition.key_code)
790 .native_virtual_key_code(key_definition.key_code);
791
792 if let Some(modifiers) = modifiers {
793 cmd = cmd.modifiers(modifiers);
794 }
795
796 if let Ok(cmd) = cmd.clone().r#type(key_down_event_type).build() {
797 self.execute(cmd).await?;
798 }
799
800 if let Ok(cmd) = cmd.r#type(DispatchKeyEventType::KeyUp).build() {
801 self.execute(cmd).await?;
802 }
803
804 Ok(self)
805 }
806
807 pub async fn press_key(&self, key: impl AsRef<str>) -> Result<&Self> {
810 self._press_key(key, None).await
811 }
812
813 pub async fn press_key_with_modifier(
816 &self,
817 key: impl AsRef<str>,
818 modifiers: Option<i64>,
819 ) -> Result<&Self> {
820 self._press_key(key, modifiers).await
821 }
822
823 pub async fn call_js_fn(
826 &self,
827 function_declaration: impl Into<String>,
828 await_promise: bool,
829 remote_object_id: RemoteObjectId,
830 ) -> Result<CallFunctionOnReturns> {
831 if let Ok(resp) = CallFunctionOnParams::builder()
832 .object_id(remote_object_id)
833 .function_declaration(function_declaration)
834 .generate_preview(true)
835 .await_promise(await_promise)
836 .build()
837 {
838 let resp = self.execute(resp).await?;
839 Ok(resp.result)
840 } else {
841 Err(CdpError::NotFound)
842 }
843 }
844
845 pub async fn evaluate_expression(
846 &self,
847 evaluate: impl Into<EvaluateParams>,
848 ) -> Result<EvaluationResult> {
849 let mut evaluate = evaluate.into();
850 if evaluate.context_id.is_none() {
851 evaluate.context_id = self.execution_context().await?;
852 }
853 if evaluate.await_promise.is_none() {
854 evaluate.await_promise = Some(true);
855 }
856 if evaluate.return_by_value.is_none() {
857 evaluate.return_by_value = Some(true);
858 }
859
860 let resp = self.execute(evaluate).await?.result;
863
864 if let Some(exception) = resp.exception_details {
865 return Err(CdpError::JavascriptException(Box::new(exception)));
866 }
867
868 Ok(EvaluationResult::new(resp.result))
869 }
870
871 pub async fn evaluate_function(
872 &self,
873 evaluate: impl Into<CallFunctionOnParams>,
874 ) -> Result<EvaluationResult> {
875 let mut evaluate = evaluate.into();
876 if evaluate.execution_context_id.is_none() {
877 evaluate.execution_context_id = self.execution_context().await?;
878 }
879 if evaluate.await_promise.is_none() {
880 evaluate.await_promise = Some(true);
881 }
882 if evaluate.return_by_value.is_none() {
883 evaluate.return_by_value = Some(true);
884 }
885
886 let resp = self.execute(evaluate).await?.result;
889 if let Some(exception) = resp.exception_details {
890 return Err(CdpError::JavascriptException(Box::new(exception)));
891 }
892 Ok(EvaluationResult::new(resp.result))
893 }
894
895 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
896 self.execution_context_for_world(None, DOMWorldKind::Main)
897 .await
898 }
899
900 pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
901 self.execution_context_for_world(None, DOMWorldKind::Secondary)
902 .await
903 }
904
905 pub async fn frame_execution_context(
906 &self,
907 frame_id: FrameId,
908 ) -> Result<Option<ExecutionContextId>> {
909 self.execution_context_for_world(Some(frame_id), DOMWorldKind::Main)
910 .await
911 }
912
913 pub async fn frame_secondary_execution_context(
914 &self,
915 frame_id: FrameId,
916 ) -> Result<Option<ExecutionContextId>> {
917 self.execution_context_for_world(Some(frame_id), DOMWorldKind::Secondary)
918 .await
919 }
920
921 pub async fn execution_context_for_world(
922 &self,
923 frame_id: Option<FrameId>,
924 dom_world: DOMWorldKind,
925 ) -> Result<Option<ExecutionContextId>> {
926 let (tx, rx) = oneshot_channel();
927 let msg = TargetMessage::GetExecutionContext(GetExecutionContext {
928 dom_world,
929 frame_id,
930 tx,
931 });
932 match self.sender.try_send(msg) {
933 Ok(()) => {}
934 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
935 tokio::time::timeout(self.request_timeout, self.sender.send(msg))
936 .await
937 .map_err(|_| CdpError::Timeout)?
938 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
939 }
940 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
941 return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
942 }
943 }
944 Ok(tokio::time::timeout(self.request_timeout, rx)
945 .await
946 .map_err(|_| CdpError::Timeout)??)
947 }
948
949 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
951 Ok(self
952 .execute(GetLayoutMetricsParams::default())
953 .await?
954 .result)
955 }
956
957 pub async fn set_bypass_csp(&self, enabled: bool) -> Result<&Self> {
959 self.execute(SetBypassCspParams::new(enabled)).await?;
960 Ok(self)
961 }
962
963 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
965 self.activate().await?;
966 let params = params.into();
967 let full_page = params.full_page();
968 let omit_background = params.omit_background();
969
970 let mut cdp_params = params.cdp_params;
971
972 if full_page {
973 let metrics = self.layout_metrics().await?;
974 let width = metrics.css_content_size.width;
975 let height = metrics.css_content_size.height;
976
977 cdp_params.clip = Some(Viewport {
978 x: 0.,
979 y: 0.,
980 width,
981 height,
982 scale: 1.,
983 });
984
985 self.execute(SetDeviceMetricsOverrideParams::new(
986 width as i64,
987 height as i64,
988 1.,
989 false,
990 ))
991 .await?;
992 }
993
994 if omit_background {
995 self.execute(SetDefaultBackgroundColorOverrideParams {
996 color: Some(Rgba {
997 r: 0,
998 g: 0,
999 b: 0,
1000 a: Some(0.),
1001 }),
1002 })
1003 .await?;
1004 }
1005
1006 let res = self.execute(cdp_params).await?.result;
1007
1008 if omit_background {
1009 self.send_command(SetDefaultBackgroundColorOverrideParams { color: None })
1010 .await?;
1011 }
1012
1013 if full_page {
1014 self.send_command(ClearDeviceMetricsOverrideParams {})
1015 .await?;
1016 }
1017
1018 Ok(utils::base64::decode(&res.data)?)
1019 }
1020
1021 pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1023 self.activate().await?;
1024 let params = params.into();
1025
1026 let res = self.execute(params).await?.result;
1027
1028 Ok(utils::base64::decode(&res.data)?)
1029 }
1030}
1031
1032pub(crate) async fn execute<T: Command>(
1033 cmd: T,
1034 sender: PageSender,
1035 session: Option<SessionId>,
1036 request_timeout: std::time::Duration,
1037) -> Result<CommandResponse<T::Response>> {
1038 let method = cmd.identifier();
1039 let rx = send_command(cmd, sender, session, request_timeout).await?;
1040 let resp = tokio::time::timeout(request_timeout, rx)
1041 .await
1042 .map_err(|_| CdpError::Timeout)???;
1043 to_command_response::<T>(resp, method)
1044}
1045
1046pub(crate) async fn send_command<T: Command>(
1051 cmd: T,
1052 sender: PageSender,
1053 session: Option<SessionId>,
1054 request_timeout: std::time::Duration,
1055) -> Result<tokio::sync::oneshot::Receiver<Result<chromiumoxide_types::Response, CdpError>>> {
1056 let (tx, rx) = oneshot_channel();
1057 let msg = CommandMessage::with_session(cmd, tx, session)?;
1058 let target_msg = TargetMessage::Command(msg);
1059 match sender.try_send(target_msg) {
1060 Ok(()) => {}
1061 Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
1062 tokio::time::timeout(request_timeout, sender.send(msg))
1063 .await
1064 .map_err(|_| CdpError::Timeout)?
1065 .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
1066 }
1067 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
1068 return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
1069 }
1070 }
1071 Ok(rx)
1072}