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