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}