Skip to main content

chromiumoxide/handler/
page.rs

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
49/// Global count of live `PageInner` instances. Incremented on creation,
50/// decremented on `Drop`. Used to dynamically tune memory-sensitive
51/// thresholds (e.g. CDP body-streaming chunk size) under high concurrency.
52static ACTIVE_PAGES: AtomicUsize = AtomicUsize::new(0);
53
54/// Returns the number of currently live page instances across the process.
55#[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    /// The page target ID.
98    target_id: TargetId,
99    /// The session ID.
100    session_id: SessionId,
101    /// The opener ID.
102    opener_id: Option<TargetId>,
103    /// The sender for the target (with optional handler notification).
104    sender: PageSender,
105    /// Smart mouse with position tracking and human-like movement.
106    pub(crate) smart_mouse: SmartMouse,
107    /// The request timeout for CDP commands issued from this page.
108    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    /// Execute a PDL command and return its response
119    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    /// Execute a PDL command without waiting for the response.
130    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    /// Create a PDL command future
142    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    /// This creates navigation future with the final http response when the page is loaded
152    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    /// This creates navigation future with the final http response when the page network is idle
160    pub(crate) fn wait_for_network_idle(&self) -> TargetMessageFuture<ArcHttpRequest> {
161        TargetMessageFuture::<ArcHttpRequest>::wait_for_network_idle(
162            self.sender.clone(),
163            self.request_timeout,
164        )
165    }
166
167    /// This creates navigation future with the final http response when the page network is almost idle
168    pub(crate) fn wait_for_network_almost_idle(&self) -> TargetMessageFuture<ArcHttpRequest> {
169        TargetMessageFuture::<ArcHttpRequest>::wait_for_network_almost_idle(
170            self.sender.clone(),
171            self.request_timeout,
172        )
173    }
174
175    /// This creates HTTP future with navigation and responds with the final
176    /// http response when the page is loaded
177    pub(crate) fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
178        Ok(HttpFuture::new(
179            self.sender.clone(),
180            self.command_future(cmd)?,
181            self.request_timeout,
182        ))
183    }
184
185    /// The identifier of this page's target
186    pub fn target_id(&self) -> &TargetId {
187        &self.target_id
188    }
189
190    /// The identifier of this page's target's session
191    pub fn session_id(&self) -> &SessionId {
192        &self.session_id
193    }
194
195    /// The identifier of this page's target's opener target
196    pub fn opener_id(&self) -> &Option<TargetId> {
197        &self.opener_id
198    }
199
200    /// Send a `TargetMessage` with the page's request timeout.
201    /// Uses a `try_send` fast path to avoid the async overhead when
202    /// the channel has capacity (common case under normal load).
203    pub(crate) async fn send_msg(&self, msg: TargetMessage) -> Result<()> {
204        match self.sender.try_send(msg) {
205            Ok(()) => Ok(()),
206            Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
207                tokio::time::timeout(self.request_timeout, self.sender.send(msg))
208                    .await
209                    .map_err(|_| CdpError::Timeout)?
210                    .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
211                Ok(())
212            }
213            Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
214                Err(CdpError::ChannelSendError(crate::error::ChannelError::Send))
215            }
216        }
217    }
218
219    /// Await a oneshot response with the page's request timeout.
220    pub(crate) async fn recv_msg<T>(&self, rx: tokio::sync::oneshot::Receiver<T>) -> Result<T> {
221        tokio::time::timeout(self.request_timeout, rx)
222            .await
223            .map_err(|_| CdpError::Timeout)?
224            .map_err(|e| CdpError::ChannelSendError(crate::error::ChannelError::Canceled(e)))
225    }
226
227    /// Returns the first element in the node which matches the given CSS
228    /// selector.
229    pub async fn find_element(&self, selector: impl Into<String>, node: NodeId) -> Result<NodeId> {
230        Ok(self
231            .execute(QuerySelectorParams::new(node, selector))
232            .await?
233            .node_id)
234    }
235
236    /// Returns the outer html of the page.
237    pub async fn outer_html(
238        &self,
239        object_id: RemoteObjectId,
240        node_id: NodeId,
241        backend_node_id: BackendNodeId,
242    ) -> Result<String> {
243        let cmd = GetOuterHtmlParams {
244            backend_node_id: Some(backend_node_id),
245            node_id: Some(node_id),
246            object_id: Some(object_id),
247            ..Default::default()
248        };
249
250        let chromiumoxide_types::CommandResponse { result, .. } = self.execute(cmd).await?;
251
252        Ok(result.outer_html)
253    }
254
255    /// Activates (focuses) the target.
256    pub async fn activate(&self) -> Result<&Self> {
257        self.execute(ActivateTargetParams::new(self.target_id().clone()))
258            .await?;
259        Ok(self)
260    }
261
262    /// Version information about the browser
263    pub async fn version(&self) -> Result<GetVersionReturns> {
264        Ok(self.execute(GetVersionParams::default()).await?.result)
265    }
266
267    /// Return all `Element`s inside the node that match the given selector
268    pub(crate) async fn find_elements(
269        &self,
270        selector: impl Into<String>,
271        node: NodeId,
272    ) -> Result<Vec<NodeId>> {
273        Ok(self
274            .execute(QuerySelectorAllParams::new(node, selector))
275            .await?
276            .result
277            .node_ids)
278    }
279
280    /// Returns all elements which matches the given xpath selector
281    pub async fn find_xpaths(&self, query: impl Into<String>) -> Result<Vec<NodeId>> {
282        let perform_search_returns = self
283            .execute(PerformSearchParams {
284                query: query.into(),
285                include_user_agent_shadow_dom: Some(true),
286            })
287            .await?
288            .result;
289
290        let search_results = self
291            .execute(GetSearchResultsParams::new(
292                perform_search_returns.search_id.clone(),
293                0,
294                perform_search_returns.result_count,
295            ))
296            .await?
297            .result;
298
299        self.execute(DiscardSearchResultsParams::new(
300            perform_search_returns.search_id,
301        ))
302        .await?;
303
304        Ok(search_results.node_ids)
305    }
306
307    /// Moves the mouse to this point (dispatches a mouseMoved event).
308    /// Also updates the tracked mouse position.
309    pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
310        self.smart_mouse.set_position(point);
311        self.execute(DispatchMouseEventParams::new(
312            DispatchMouseEventType::MouseMoved,
313            point.x,
314            point.y,
315        ))
316        .await?;
317        Ok(self)
318    }
319
320    /// Moves the mouse to `target` along a human-like bezier curve path,
321    /// dispatching intermediate `mouseMoved` events with natural timing.
322    pub async fn move_mouse_smooth(&self, target: Point) -> Result<&Self> {
323        let path = self.smart_mouse.path_to(target);
324        for step in &path {
325            self.execute(DispatchMouseEventParams::new(
326                DispatchMouseEventType::MouseMoved,
327                step.point.x,
328                step.point.y,
329            ))
330            .await?;
331            tokio::time::sleep(step.delay).await;
332        }
333        Ok(self)
334    }
335
336    /// Returns the current tracked mouse position.
337    pub fn mouse_position(&self) -> Point {
338        self.smart_mouse.position()
339    }
340
341    /// Scrolls the current page by the specified horizontal and vertical offsets.
342    /// This method helps when Chrome version may not support certain CDP dispatch events.
343    pub async fn scroll_by(
344        &self,
345        delta_x: f64,
346        delta_y: f64,
347        behavior: ScrollBehavior,
348    ) -> Result<&Self> {
349        let behavior_str = match behavior {
350            ScrollBehavior::Auto => "auto",
351            ScrollBehavior::Instant => "instant",
352            ScrollBehavior::Smooth => "smooth",
353        };
354
355        self.evaluate_expression(format!(
356            "window.scrollBy({{top: {}, left: {}, behavior: '{}'}});",
357            delta_y, delta_x, behavior_str
358        ))
359        .await?;
360
361        Ok(self)
362    }
363
364    /// Dispatches a `DragEvent`, moving the element to the given `point`.
365    ///
366    /// `point.x` defines the horizontal target, and `point.y` the vertical mouse position.
367    /// Accepts `drag_type`, `drag_data`, and optional keyboard `modifiers`.
368    pub async fn drag(
369        &self,
370        drag_type: DispatchDragEventType,
371        point: Point,
372        drag_data: DragData,
373        modifiers: Option<i64>,
374    ) -> Result<&Self> {
375        let mut params: DispatchDragEventParams =
376            DispatchDragEventParams::new(drag_type, point.x, point.y, drag_data);
377
378        if let Some(modifiers) = modifiers {
379            params.modifiers = Some(modifiers);
380        }
381
382        self.execute(params).await?;
383        Ok(self)
384    }
385
386    /// Moves the mouse to this point (dispatches a mouseWheel event).
387    /// If you get an error use page.scroll_by instead.
388    pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
389        let mut params: DispatchMouseEventParams =
390            DispatchMouseEventParams::new(DispatchMouseEventType::MouseWheel, point.x, point.y);
391
392        params.delta_x = Some(delta.delta_x);
393        params.delta_y = Some(delta.delta_y);
394
395        self.execute(params).await?;
396        Ok(self)
397    }
398
399    /// Performs a mouse click event at the point's location with the amount of clicks and modifier.
400    pub async fn click_with_count_base(
401        &self,
402        point: Point,
403        click_count: impl Into<i64>,
404        modifiers: impl Into<i64>,
405        button: impl Into<MouseButton>,
406    ) -> Result<&Self> {
407        let cmd = DispatchMouseEventParams::builder()
408            .x(point.x)
409            .y(point.y)
410            .button(button)
411            .click_count(click_count)
412            .modifiers(modifiers);
413
414        if let Ok(cmd) = cmd
415            .clone()
416            .r#type(DispatchMouseEventType::MousePressed)
417            .build()
418        {
419            self.move_mouse(point).await?.send_command(cmd).await?;
420        }
421
422        if let Ok(cmd) = cmd.r#type(DispatchMouseEventType::MouseReleased).build() {
423            self.execute(cmd).await?;
424        }
425
426        self.smart_mouse.set_position(point);
427        Ok(self)
428    }
429
430    /// Move smoothly to `point` with human-like movement, then click.
431    pub async fn click_smooth(&self, point: Point) -> Result<&Self> {
432        self.move_mouse_smooth(point).await?;
433        self.click(point).await
434    }
435
436    /// Performs a mouse click event at the point's location with the amount of clicks and modifier.
437    pub async fn click_with_count(
438        &self,
439        point: Point,
440        click_count: impl Into<i64>,
441        modifiers: impl Into<i64>,
442    ) -> Result<&Self> {
443        self.click_with_count_base(point, click_count, modifiers, MouseButton::Left)
444            .await
445    }
446
447    /// Performs a mouse right click event at the point's location with the amount of clicks and modifier.
448    pub async fn right_click_with_count(
449        &self,
450        point: Point,
451        click_count: impl Into<i64>,
452        modifiers: impl Into<i64>,
453    ) -> Result<&Self> {
454        self.click_with_count_base(point, click_count, modifiers, MouseButton::Right)
455            .await
456    }
457
458    /// Performs a mouse middle click event at the point's location with the amount of clicks and modifier.
459    pub async fn middle_click_with_count(
460        &self,
461        point: Point,
462        click_count: impl Into<i64>,
463        modifiers: impl Into<i64>,
464    ) -> Result<&Self> {
465        self.click_with_count_base(point, click_count, modifiers, MouseButton::Middle)
466            .await
467    }
468
469    /// Performs a mouse back click event at the point's location with the amount of clicks and modifier.
470    pub async fn back_click_with_count(
471        &self,
472        point: Point,
473        click_count: impl Into<i64>,
474        modifiers: impl Into<i64>,
475    ) -> Result<&Self> {
476        self.click_with_count_base(point, click_count, modifiers, MouseButton::Back)
477            .await
478    }
479
480    /// Performs a mouse forward click event at the point's location with the amount of clicks and modifier.
481    pub async fn forward_click_with_count(
482        &self,
483        point: Point,
484        click_count: impl Into<i64>,
485        modifiers: impl Into<i64>,
486    ) -> Result<&Self> {
487        self.click_with_count_base(point, click_count, modifiers, MouseButton::Forward)
488            .await
489    }
490
491    /// Performs a click-and-drag from one point to another with optional modifiers.
492    pub async fn click_and_drag(
493        &self,
494        from: Point,
495        to: Point,
496        modifiers: impl Into<i64>,
497    ) -> Result<&Self> {
498        let modifiers = modifiers.into();
499        let click_count = 1;
500
501        let cmd = DispatchMouseEventParams::builder()
502            .button(MouseButton::Left)
503            .click_count(click_count)
504            .modifiers(modifiers);
505
506        if let Ok(cmd) = cmd
507            .clone()
508            .x(from.x)
509            .y(from.y)
510            .r#type(DispatchMouseEventType::MousePressed)
511            .build()
512        {
513            self.move_mouse(from).await?.send_command(cmd).await?;
514        }
515
516        if let Ok(cmd) = cmd
517            .clone()
518            .x(to.x)
519            .y(to.y)
520            .r#type(DispatchMouseEventType::MouseMoved)
521            .build()
522        {
523            self.move_mouse(to).await?.send_command(cmd).await?;
524        }
525
526        if let Ok(cmd) = cmd
527            .r#type(DispatchMouseEventType::MouseReleased)
528            .x(to.x)
529            .y(to.y)
530            .build()
531        {
532            self.send_command(cmd).await?;
533        }
534
535        self.smart_mouse.set_position(to);
536        Ok(self)
537    }
538
539    /// Performs a smooth click-and-drag: moves to `from` with a bezier path,
540    /// presses, drags along a bezier path to `to`, then releases.
541    pub async fn click_and_drag_smooth(
542        &self,
543        from: Point,
544        to: Point,
545        modifiers: impl Into<i64>,
546    ) -> Result<&Self> {
547        let modifiers = modifiers.into();
548
549        // Smooth move to the starting point
550        self.move_mouse_smooth(from).await?;
551
552        // Press at starting point
553        if let Ok(cmd) = DispatchMouseEventParams::builder()
554            .x(from.x)
555            .y(from.y)
556            .button(MouseButton::Left)
557            .click_count(1)
558            .modifiers(modifiers)
559            .r#type(DispatchMouseEventType::MousePressed)
560            .build()
561        {
562            self.send_command(cmd).await?;
563        }
564
565        // Smooth drag to destination (dispatching MouseMoved with button held)
566        let path = self.smart_mouse.path_to(to);
567        for step in &path {
568            if let Ok(cmd) = DispatchMouseEventParams::builder()
569                .x(step.point.x)
570                .y(step.point.y)
571                .button(MouseButton::Left)
572                .modifiers(modifiers)
573                .r#type(DispatchMouseEventType::MouseMoved)
574                .build()
575            {
576                self.send_command(cmd).await?;
577            }
578            tokio::time::sleep(step.delay).await;
579        }
580
581        // Release at destination
582        if let Ok(cmd) = DispatchMouseEventParams::builder()
583            .x(to.x)
584            .y(to.y)
585            .button(MouseButton::Left)
586            .click_count(1)
587            .modifiers(modifiers)
588            .r#type(DispatchMouseEventType::MouseReleased)
589            .build()
590        {
591            self.send_command(cmd).await?;
592        }
593
594        Ok(self)
595    }
596
597    /// Performs a mouse click event at the point's location
598    pub async fn click(&self, point: Point) -> Result<&Self> {
599        self.click_with_count(point, 1, 0).await
600    }
601
602    /// Performs a mouse double click event at the point's location
603    pub async fn double_click(&self, point: Point) -> Result<&Self> {
604        self.click_with_count(point, 2, 0).await
605    }
606
607    /// Performs a mouse right click event at the point's location
608    pub async fn right_click(&self, point: Point) -> Result<&Self> {
609        self.right_click_with_count(point, 1, 0).await
610    }
611
612    /// Performs a mouse middle click event at the point's location
613    pub async fn middle_click(&self, point: Point) -> Result<&Self> {
614        self.middle_click_with_count(point, 1, 0).await
615    }
616
617    /// Performs a mouse back click event at the point's location
618    pub async fn back_click(&self, point: Point) -> Result<&Self> {
619        self.back_click_with_count(point, 1, 0).await
620    }
621
622    /// Performs a mouse forward click event at the point's location
623    pub async fn forward_click(&self, point: Point) -> Result<&Self> {
624        self.forward_click_with_count(point, 1, 0).await
625    }
626
627    /// Performs a mouse click event at the point's location and modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
628    pub async fn click_with_modifier(
629        &self,
630        point: Point,
631        modifiers: impl Into<i64>,
632    ) -> Result<&Self> {
633        self.click_with_count(point, 1, modifiers).await
634    }
635
636    /// Performs a mouse right click event at the point's location and modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
637    pub async fn right_click_with_modifier(
638        &self,
639        point: Point,
640        modifiers: impl Into<i64>,
641    ) -> Result<&Self> {
642        self.right_click_with_count(point, 1, modifiers).await
643    }
644
645    /// Performs a mouse middle click event at the point's location and modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
646    pub async fn middle_click_with_modifier(
647        &self,
648        point: Point,
649        modifiers: impl Into<i64>,
650    ) -> Result<&Self> {
651        self.middle_click_with_count(point, 1, modifiers).await
652    }
653
654    /// Performs a mouse double click event at the point's location and modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
655    pub async fn double_click_with_modifier(
656        &self,
657        point: Point,
658        modifiers: impl Into<i64>,
659    ) -> Result<&Self> {
660        self.click_with_count(point, 2, modifiers).await
661    }
662
663    /// This simulates pressing keys on the page.
664    ///
665    /// # Note The `input` is treated as series of `KeyDefinition`s, where each
666    /// char is inserted as a separate keystroke. So sending
667    /// `page.type_str("Enter")` will be processed as a series of single
668    /// keystrokes:  `["E", "n", "t", "e", "r"]`. To simulate pressing the
669    /// actual Enter key instead use `page.press_key(
670    /// keys::get_key_definition("Enter").unwrap())`.
671    pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
672        for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
673            self._press_key(c, None).await?;
674        }
675        Ok(self)
676    }
677
678    /// Fetches the entire accessibility tree for the root Document
679    pub async fn get_full_ax_tree(
680        &self,
681        depth: Option<i64>,
682        frame_id: Option<FrameId>,
683    ) -> Result<GetFullAxTreeReturns> {
684        let mut builder = GetFullAxTreeParamsBuilder::default();
685
686        if let Some(depth) = depth {
687            builder = builder.depth(depth);
688        }
689
690        if let Some(frame_id) = frame_id {
691            builder = builder.frame_id(frame_id);
692        }
693
694        let resp = self.execute(builder.build()).await?;
695
696        Ok(resp.result)
697    }
698
699    /// Fetches the accessibility node and partial accessibility tree for this DOM node, if it exists.
700    pub async fn get_partial_ax_tree(
701        &self,
702        node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
703        backend_node_id: Option<BackendNodeId>,
704        object_id: Option<RemoteObjectId>,
705        fetch_relatives: Option<bool>,
706    ) -> Result<GetPartialAxTreeReturns> {
707        let mut builder = GetPartialAxTreeParamsBuilder::default();
708
709        if let Some(node_id) = node_id {
710            builder = builder.node_id(node_id);
711        }
712
713        if let Some(backend_node_id) = backend_node_id {
714            builder = builder.backend_node_id(backend_node_id);
715        }
716
717        if let Some(object_id) = object_id {
718            builder = builder.object_id(object_id);
719        }
720
721        if let Some(fetch_relatives) = fetch_relatives {
722            builder = builder.fetch_relatives(fetch_relatives);
723        }
724
725        let resp = self.execute(builder.build()).await?;
726
727        Ok(resp.result)
728    }
729
730    /// This simulates pressing keys on the page.
731    ///
732    /// # Note The `input` is treated as series of `KeyDefinition`s, where each
733    /// char is inserted as a separate keystroke. So sending
734    /// `page.type_str("Enter")` will be processed as a series of single
735    /// keystrokes:  `["E", "n", "t", "e", "r"]`. To simulate pressing the
736    /// actual Enter key instead use `page.press_key(
737    /// keys::get_key_definition("Enter").unwrap())`.
738    pub async fn type_str_with_modifier(
739        &self,
740        input: impl AsRef<str>,
741        modifiers: Option<i64>,
742    ) -> Result<&Self> {
743        for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
744            self._press_key(c, modifiers).await?;
745        }
746        Ok(self)
747    }
748
749    /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
750    /// keys.
751    async fn _press_key(&self, key: impl AsRef<str>, modifiers: Option<i64>) -> Result<&Self> {
752        let key = key.as_ref();
753        let key_definition = keys::get_key_definition(key)
754            .ok_or_else(|| CdpError::msg(format!("Key not found: {key}")))?;
755        let mut cmd = DispatchKeyEventParams::builder();
756
757        // See https://github.com/GoogleChrome/puppeteer/blob/62da2366c65b335751896afbb0206f23c61436f1/lib/Input.js#L114-L115
758        // And https://github.com/GoogleChrome/puppeteer/blob/62da2366c65b335751896afbb0206f23c61436f1/lib/Input.js#L52
759        let key_down_event_type = if let Some(txt) = key_definition.text {
760            cmd = cmd.text(txt);
761            DispatchKeyEventType::KeyDown
762        } else if key_definition.key.len() == 1 {
763            cmd = cmd.text(key_definition.key);
764            DispatchKeyEventType::KeyDown
765        } else {
766            DispatchKeyEventType::RawKeyDown
767        };
768
769        cmd = cmd
770            .r#type(DispatchKeyEventType::KeyDown)
771            .key(key_definition.key)
772            .code(key_definition.code)
773            .windows_virtual_key_code(key_definition.key_code)
774            .native_virtual_key_code(key_definition.key_code);
775
776        if let Some(modifiers) = modifiers {
777            cmd = cmd.modifiers(modifiers);
778        }
779
780        if let Ok(cmd) = cmd.clone().r#type(key_down_event_type).build() {
781            self.execute(cmd).await?;
782        }
783
784        if let Ok(cmd) = cmd.r#type(DispatchKeyEventType::KeyUp).build() {
785            self.execute(cmd).await?;
786        }
787
788        Ok(self)
789    }
790
791    /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
792    /// keys.
793    pub async fn press_key(&self, key: impl AsRef<str>) -> Result<&Self> {
794        self._press_key(key, None).await
795    }
796
797    /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
798    /// keys and modifiers.
799    pub async fn press_key_with_modifier(
800        &self,
801        key: impl AsRef<str>,
802        modifiers: Option<i64>,
803    ) -> Result<&Self> {
804        self._press_key(key, modifiers).await
805    }
806
807    /// Calls function with given declaration on the remote object with the
808    /// matching id
809    pub async fn call_js_fn(
810        &self,
811        function_declaration: impl Into<String>,
812        await_promise: bool,
813        remote_object_id: RemoteObjectId,
814    ) -> Result<CallFunctionOnReturns> {
815        if let Ok(resp) = CallFunctionOnParams::builder()
816            .object_id(remote_object_id)
817            .function_declaration(function_declaration)
818            .generate_preview(true)
819            .await_promise(await_promise)
820            .build()
821        {
822            let resp = self.execute(resp).await?;
823            Ok(resp.result)
824        } else {
825            Err(CdpError::NotFound)
826        }
827    }
828
829    pub async fn evaluate_expression(
830        &self,
831        evaluate: impl Into<EvaluateParams>,
832    ) -> Result<EvaluationResult> {
833        let mut evaluate = evaluate.into();
834        if evaluate.context_id.is_none() {
835            evaluate.context_id = self.execution_context().await?;
836        }
837        if evaluate.await_promise.is_none() {
838            evaluate.await_promise = Some(true);
839        }
840        if evaluate.return_by_value.is_none() {
841            evaluate.return_by_value = Some(true);
842        }
843
844        // evaluate.silent = Some(true);
845
846        let resp = self.execute(evaluate).await?.result;
847
848        if let Some(exception) = resp.exception_details {
849            return Err(CdpError::JavascriptException(Box::new(exception)));
850        }
851
852        Ok(EvaluationResult::new(resp.result))
853    }
854
855    pub async fn evaluate_function(
856        &self,
857        evaluate: impl Into<CallFunctionOnParams>,
858    ) -> Result<EvaluationResult> {
859        let mut evaluate = evaluate.into();
860        if evaluate.execution_context_id.is_none() {
861            evaluate.execution_context_id = self.execution_context().await?;
862        }
863        if evaluate.await_promise.is_none() {
864            evaluate.await_promise = Some(true);
865        }
866        if evaluate.return_by_value.is_none() {
867            evaluate.return_by_value = Some(true);
868        }
869
870        // evaluate.silent = Some(true);
871
872        let resp = self.execute(evaluate).await?.result;
873        if let Some(exception) = resp.exception_details {
874            return Err(CdpError::JavascriptException(Box::new(exception)));
875        }
876        Ok(EvaluationResult::new(resp.result))
877    }
878
879    pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
880        self.execution_context_for_world(None, DOMWorldKind::Main)
881            .await
882    }
883
884    pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
885        self.execution_context_for_world(None, DOMWorldKind::Secondary)
886            .await
887    }
888
889    pub async fn frame_execution_context(
890        &self,
891        frame_id: FrameId,
892    ) -> Result<Option<ExecutionContextId>> {
893        self.execution_context_for_world(Some(frame_id), DOMWorldKind::Main)
894            .await
895    }
896
897    pub async fn frame_secondary_execution_context(
898        &self,
899        frame_id: FrameId,
900    ) -> Result<Option<ExecutionContextId>> {
901        self.execution_context_for_world(Some(frame_id), DOMWorldKind::Secondary)
902            .await
903    }
904
905    pub async fn execution_context_for_world(
906        &self,
907        frame_id: Option<FrameId>,
908        dom_world: DOMWorldKind,
909    ) -> Result<Option<ExecutionContextId>> {
910        let (tx, rx) = oneshot_channel();
911        let msg = TargetMessage::GetExecutionContext(GetExecutionContext {
912            dom_world,
913            frame_id,
914            tx,
915        });
916        match self.sender.try_send(msg) {
917            Ok(()) => {}
918            Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
919                tokio::time::timeout(self.request_timeout, self.sender.send(msg))
920                    .await
921                    .map_err(|_| CdpError::Timeout)?
922                    .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
923            }
924            Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
925                return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
926            }
927        }
928        Ok(tokio::time::timeout(self.request_timeout, rx)
929            .await
930            .map_err(|_| CdpError::Timeout)??)
931    }
932
933    /// Returns metrics relating to the layout of the page
934    pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
935        Ok(self
936            .execute(GetLayoutMetricsParams::default())
937            .await?
938            .result)
939    }
940
941    /// Enable page Content Security Policy by-passing.
942    pub async fn set_bypass_csp(&self, enabled: bool) -> Result<&Self> {
943        self.execute(SetBypassCspParams::new(enabled)).await?;
944        Ok(self)
945    }
946
947    /// Take a screenshot of the page.
948    pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
949        self.activate().await?;
950        let params = params.into();
951        let full_page = params.full_page();
952        let omit_background = params.omit_background();
953
954        let mut cdp_params = params.cdp_params;
955
956        if full_page {
957            let metrics = self.layout_metrics().await?;
958            let width = metrics.css_content_size.width;
959            let height = metrics.css_content_size.height;
960
961            cdp_params.clip = Some(Viewport {
962                x: 0.,
963                y: 0.,
964                width,
965                height,
966                scale: 1.,
967            });
968
969            self.execute(SetDeviceMetricsOverrideParams::new(
970                width as i64,
971                height as i64,
972                1.,
973                false,
974            ))
975            .await?;
976        }
977
978        if omit_background {
979            self.execute(SetDefaultBackgroundColorOverrideParams {
980                color: Some(Rgba {
981                    r: 0,
982                    g: 0,
983                    b: 0,
984                    a: Some(0.),
985                }),
986            })
987            .await?;
988        }
989
990        let res = self.execute(cdp_params).await?.result;
991
992        if omit_background {
993            self.send_command(SetDefaultBackgroundColorOverrideParams { color: None })
994                .await?;
995        }
996
997        if full_page {
998            self.send_command(ClearDeviceMetricsOverrideParams {})
999                .await?;
1000        }
1001
1002        Ok(utils::base64::decode(&res.data)?)
1003    }
1004
1005    /// Convert the page to PDF.
1006    pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1007        self.activate().await?;
1008        let params = params.into();
1009
1010        let res = self.execute(params).await?.result;
1011
1012        Ok(utils::base64::decode(&res.data)?)
1013    }
1014}
1015
1016pub(crate) async fn execute<T: Command>(
1017    cmd: T,
1018    sender: PageSender,
1019    session: Option<SessionId>,
1020    request_timeout: std::time::Duration,
1021) -> Result<CommandResponse<T::Response>> {
1022    let method = cmd.identifier();
1023    let rx = send_command(cmd, sender, session, request_timeout).await?;
1024    let resp = tokio::time::timeout(request_timeout, rx)
1025        .await
1026        .map_err(|_| CdpError::Timeout)???;
1027    to_command_response::<T>(resp, method)
1028}
1029
1030/// Execute a command without waiting.
1031///
1032/// Uses a `try_send` fast path to avoid async overhead when the channel has
1033/// capacity (common case). Falls back to an async send with timeout when full.
1034pub(crate) async fn send_command<T: Command>(
1035    cmd: T,
1036    sender: PageSender,
1037    session: Option<SessionId>,
1038    request_timeout: std::time::Duration,
1039) -> Result<tokio::sync::oneshot::Receiver<Result<chromiumoxide_types::Response, CdpError>>> {
1040    let (tx, rx) = oneshot_channel();
1041    let msg = CommandMessage::with_session(cmd, tx, session)?;
1042    let target_msg = TargetMessage::Command(msg);
1043    match sender.try_send(target_msg) {
1044        Ok(()) => {}
1045        Err(tokio::sync::mpsc::error::TrySendError::Full(msg)) => {
1046            tokio::time::timeout(request_timeout, sender.send(msg))
1047                .await
1048                .map_err(|_| CdpError::Timeout)?
1049                .map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
1050        }
1051        Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
1052            return Err(CdpError::ChannelSendError(crate::error::ChannelError::Send));
1053        }
1054    }
1055    Ok(rx)
1056}