Skip to main content

playwright_cdp/
touchscreen.rs

1//! `Touchscreen` — touch input via the CDP `Input.dispatchTouchEvent`.
2//!
3//! Mirrors Playwright's `page.touchscreen()`. The first tap lazily enables
4//! touch emulation once per page (best-effort, errors ignored) so synthetic
5//! touch events reach the page's JS event listeners.
6
7use crate::error::Result;
8use crate::page::Page;
9use crate::types::Position;
10use serde_json::{json, Value};
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::Arc;
13
14/// Touch input device. Touch emulation is enabled once, lazily, on the first
15/// [`Touchscreen::tap`]; the enabled flag is shared across all clones produced
16/// from the same page (mirroring Playwright's persistent `page.touchscreen()`).
17#[derive(Clone)]
18pub struct Touchscreen {
19    page: Page,
20    touch_emulation_started: Arc<AtomicBool>,
21}
22
23impl Touchscreen {
24    /// Wire this touchscreen to a page-wide shared emulation-enabled flag.
25    pub(crate) fn with_flag(page: Page, flag: Arc<AtomicBool>) -> Self {
26        Self {
27            page,
28            touch_emulation_started: flag,
29        }
30    }
31
32    /// Enable touch emulation once (best-effort). Idempotent across clones.
33    async fn ensure_touch_emulation(&self) {
34        if self
35            .touch_emulation_started
36            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
37            .is_ok()
38        {
39            // setTouchEmulationEnabled is the primary knob; its failure (e.g.
40            // on a build without the Emulation domain) is non-fatal — we still
41            // attempt to dispatch the touch events themselves.
42            let _ = self
43                .page
44                .session()
45                .send(
46                    "Emulation.setTouchEmulationEnabled",
47                    json!({ "enabled": true }),
48                )
49                .await;
50        }
51    }
52
53    /// Dispatch a single tap (touchStart + touchEnd) at viewport coordinates
54    /// `(x, y)`, mirroring Playwright's `touchscreen.tap(x, y)`.
55    pub async fn tap(&self, x: f64, y: f64) -> Result<()> {
56        self.ensure_touch_emulation().await;
57
58        self.page
59            .session()
60            .send(
61                "Input.dispatchTouchEvent",
62                json!({
63                    "type": "touchStart",
64                    "touchPoints": [ { "x": x, "y": y } ],
65                    "modifiers": 0,
66                }),
67            )
68            .await
69            .map(|_: Value| ())?;
70
71        self.page
72            .session()
73            .send(
74                "Input.dispatchTouchEvent",
75                json!({
76                    "type": "touchEnd",
77                    "touchPoints": [],
78                    "modifiers": 0,
79                }),
80            )
81            .await
82            .map(|_: Value| ())
83    }
84
85    /// Convenience: tap at a [`Position`] (viewport coordinates).
86    pub async fn tap_point(&self, p: Position) -> Result<()> {
87        self.tap(p.x, p.y).await
88    }
89}