Skip to main content

playwright_cdp/
mouse.rs

1//! `Mouse` — pointer input via the CDP `Input` domain.
2
3use crate::error::Result;
4use crate::options::MouseOptions;
5use crate::page::Page;
6use crate::types::MouseButton;
7use parking_lot::Mutex;
8use serde_json::{json, Value};
9use std::sync::Arc;
10use std::time::Duration;
11
12/// Mouse input device. The last-known pointer position is shared across clones
13/// created from the same page (mirroring Playwright's persistent `page.mouse()`),
14/// so `down()`/`up()` can fire at the position set by the most recent `move_to()`.
15#[derive(Clone)]
16pub struct Mouse {
17    page: Page,
18    pos: Arc<Mutex<(f64, f64)>>,
19}
20
21impl Mouse {
22    /// Wire this mouse to a page-wide shared position cell.
23    pub(crate) fn with_pos(page: Page, pos: Arc<Mutex<(f64, f64)>>) -> Self {
24        Self { page, pos }
25    }
26
27    fn pos(&self) -> (f64, f64) {
28        *self.pos.lock()
29    }
30
31    async fn dispatch(
32        &self,
33        r#type: &str,
34        x: f64,
35        y: f64,
36        button: MouseButton,
37        buttons: u8,
38        click_count: u32,
39    ) -> Result<()> {
40        self.page
41            .session()
42            .send(
43                "Input.dispatchMouseEvent",
44                json!({
45                    "type": r#type,
46                    "x": x,
47                    "y": y,
48                    "button": button.as_str(),
49                    "buttons": buttons,
50                    "clickCount": click_count,
51                }),
52            )
53            .await
54            .map(|_: Value| ())
55    }
56
57    /// Move the pointer to `(x, y)`.
58    pub async fn move_to(&self, x: f64, y: f64, _options: Option<MouseOptions>) -> Result<()> {
59        *self.pos.lock() = (x, y);
60        self.dispatch("mouseMoved", x, y, MouseButton::Left, 0, 0).await
61    }
62
63    /// Press the (currently positioned) button down.
64    pub async fn down(&self, options: Option<MouseOptions>) -> Result<()> {
65        let opts = options.unwrap_or_default();
66        let button = opts.button.unwrap_or_default();
67        let (x, y) = self.pos();
68        let count = opts.click_count.unwrap_or(1);
69        self.dispatch("mousePressed", x, y, button, button.bitmask(), count).await
70    }
71
72    /// Release the (currently positioned) button.
73    pub async fn up(&self, options: Option<MouseOptions>) -> Result<()> {
74        let opts = options.unwrap_or_default();
75        let button = opts.button.unwrap_or_default();
76        let (x, y) = self.pos();
77        let count = opts.click_count.unwrap_or(1);
78        self.dispatch("mouseReleased", x, y, button, 0, count).await
79    }
80
81    /// Move to `(x, y)` and click once (press + release).
82    pub async fn click(&self, x: f64, y: f64, options: Option<MouseOptions>) -> Result<()> {
83        self.perform_click(x, y, options, 1).await
84    }
85
86    /// Move to `(x, y)` and double-click.
87    pub async fn dblclick(&self, x: f64, y: f64, options: Option<MouseOptions>) -> Result<()> {
88        self.perform_click(x, y, options, 2).await
89    }
90
91    async fn perform_click(
92        &self,
93        x: f64,
94        y: f64,
95        options: Option<MouseOptions>,
96        default_count: u32,
97    ) -> Result<()> {
98        let opts = options.unwrap_or_default();
99        let button = opts.button.unwrap_or_default();
100        let count = opts.click_count.unwrap_or(default_count);
101        let delay = opts.delay.map(|ms| Duration::from_millis(ms as u64));
102
103        *self.pos.lock() = (x, y);
104        self.dispatch("mouseMoved", x, y, MouseButton::Left, 0, 0).await?;
105        for i in 0..count {
106            self.dispatch("mousePressed", x, y, button, button.bitmask(), i + 1).await?;
107            self.dispatch("mouseReleased", x, y, button, 0, i + 1).await?;
108            if let Some(d) = delay {
109                if i + 1 < count {
110                    tokio::time::sleep(d).await;
111                }
112            }
113        }
114        Ok(())
115    }
116
117    /// Scroll by `(delta_x, delta_y)` at the current pointer position.
118    pub async fn wheel(&self, delta_x: f64, delta_y: f64) -> Result<()> {
119        let (x, y) = self.pos();
120        self.page
121            .session()
122            .send(
123                "Input.dispatchMouseEvent",
124                json!({
125                    "type": "mouseWheel",
126                    "x": x,
127                    "y": y,
128                    "deltaX": delta_x,
129                    "deltaY": delta_y,
130                    "button": "none",
131                    "buttons": 0,
132                }),
133            )
134            .await
135            .map(|_: Value| ())
136    }
137}