use std::borrow::Cow;
use anyhow::Result;
use chromiumoxide::{Browser as CdpBrowser, Command, Method};
use chromiumoxide_types::MethodId;
use serde::Serialize;
use serde_json::{json, Value};
#[derive(Serialize)]
pub struct RawCommand {
#[serde(skip)]
method: &'static str,
#[serde(flatten)]
params: Value,
}
impl Method for RawCommand {
fn identifier(&self) -> MethodId {
Cow::Borrowed(self.method)
}
}
impl Command for RawCommand {
type Response = Value;
}
pub struct Motion<'a> {
browser: &'a CdpBrowser,
}
impl<'a> Motion<'a> {
pub fn new(browser: &'a CdpBrowser) -> Self {
Self { browser }
}
async fn call(&self, method: &'static str, params: Value) -> Result<Value> {
let r = self.browser.execute(RawCommand { method, params }).await?;
Ok(r.result.clone())
}
fn ms(v: &Value) -> f64 {
v.get("durationMs").and_then(Value::as_f64).unwrap_or(0.0)
}
pub async fn create_pointer(&self, x: f64, y: f64) -> Result<()> {
self.call("Motion.createPointer", json!({ "x": x, "y": y })).await?;
Ok(())
}
pub async fn create_pointer_with(
&self,
x: f64,
y: f64,
pace_scale: Option<f64>,
seed: Option<i64>,
) -> Result<()> {
let mut p = json!({ "x": x, "y": y });
if let Some(v) = pace_scale {
p["paceScale"] = json!(v);
}
if let Some(v) = seed {
p["seed"] = json!(v);
}
self.call("Motion.createPointer", p).await?;
Ok(())
}
pub async fn glide_to(&self, x: f64, y: f64, target_width: Option<f64>) -> Result<f64> {
let mut p = json!({ "x": x, "y": y });
if let Some(w) = target_width {
p["targetWidth"] = json!(w);
}
Ok(Self::ms(&self.call("Motion.glideTo", p).await?))
}
pub async fn tap(&self, button: &str, click_count: u8) -> Result<f64> {
let p = json!({ "button": button, "clickCount": click_count });
Ok(Self::ms(&self.call("Motion.tap", p).await?))
}
pub async fn drag_to(
&self,
x: f64,
y: f64,
target_width: Option<f64>,
button: &str,
) -> Result<f64> {
let mut p = json!({ "x": x, "y": y, "button": button });
if let Some(w) = target_width {
p["targetWidth"] = json!(w);
}
Ok(Self::ms(&self.call("Motion.dragTo", p).await?))
}
pub async fn wheel(&self, delta_y: f64, delta_x: Option<f64>) -> Result<f64> {
let mut p = json!({ "deltaY": delta_y });
if let Some(dx) = delta_x {
p["deltaX"] = json!(dx);
}
Ok(Self::ms(&self.call("Motion.wheel", p).await?))
}
pub async fn enter_text(&self, text: &str, allow_typos: bool) -> Result<f64> {
let p = json!({ "text": text, "allowTypos": allow_typos });
Ok(Self::ms(&self.call("Motion.enterText", p).await?))
}
pub async fn press_key(&self, key: &str, modifiers: &[&str]) -> Result<f64> {
let mut p = json!({ "key": key });
if !modifiers.is_empty() {
p["modifiers"] = json!(modifiers);
}
Ok(Self::ms(&self.call("Motion.pressKey", p).await?))
}
pub async fn destroy_pointer(&self) -> Result<()> {
self.call("Motion.destroyPointer", json!({})).await?;
Ok(())
}
pub async fn touch_tap(
&self,
x: f64,
y: f64,
target_width: Option<f64>,
tap_count: u8,
) -> Result<f64> {
let mut p = json!({ "x": x, "y": y, "tapCount": tap_count });
if let Some(w) = target_width {
p["targetWidth"] = json!(w);
}
Ok(Self::ms(&self.call("Motion.touchTap", p).await?))
}
pub async fn touch_long_press(&self, x: f64, y: f64, hold_ms: Option<f64>) -> Result<f64> {
let mut p = json!({ "x": x, "y": y });
if let Some(v) = hold_ms {
p["holdMs"] = json!(v);
}
Ok(Self::ms(&self.call("Motion.touchLongPress", p).await?))
}
pub async fn touch_swipe(
&self,
from: (f64, f64),
to: (f64, f64),
flick: Option<bool>,
) -> Result<f64> {
let mut p = json!({ "fromX": from.0, "fromY": from.1, "toX": to.0, "toY": to.1 });
if let Some(v) = flick {
p["flick"] = json!(v);
}
Ok(Self::ms(&self.call("Motion.touchSwipe", p).await?))
}
pub async fn touch_drag(
&self,
from: (f64, f64),
to: (f64, f64),
hold_ms: Option<f64>,
) -> Result<f64> {
let mut p = json!({ "fromX": from.0, "fromY": from.1, "toX": to.0, "toY": to.1 });
if let Some(v) = hold_ms {
p["holdMs"] = json!(v);
}
Ok(Self::ms(&self.call("Motion.touchDrag", p).await?))
}
pub async fn pinch(&self, x: f64, y: f64, scale: f64, rotation: Option<f64>) -> Result<f64> {
let mut p = json!({ "x": x, "y": y, "scale": scale });
if let Some(v) = rotation {
p["rotation"] = json!(v);
}
Ok(Self::ms(&self.call("Motion.pinch", p).await?))
}
pub async fn set_orientation(&self, angle: i32, turn_ms: Option<f64>) -> Result<Value> {
let mut p = json!({ "angle": angle });
if let Some(v) = turn_ms {
p["turnMs"] = json!(v);
}
self.call("Motion.setOrientation", p).await
}
}