Skip to main content

ferridriver_script/bindings/
keyboard.rs

1//! `KeyboardJs`: wrapper around `ferridriver::Page::keyboard()`.
2//!
3//! Mirrors Playwright's `page.keyboard.*` namespace: `down(key)`,
4//! `up(key)`, `press(key)` without a selector (acts on whatever element
5//! currently has focus).
6
7use std::sync::Arc;
8
9use ferridriver::Page;
10use rquickjs::JsLifetime;
11use rquickjs::class::Trace;
12
13use rquickjs::function::Opt;
14use serde::Deserialize;
15
16use crate::bindings::convert::{FerriResultExt, serde_from_js};
17
18#[derive(Debug, Default, Deserialize)]
19#[serde(default)]
20struct JsKeyDelay {
21  delay: Option<u64>,
22}
23
24#[derive(Debug, Default, Deserialize)]
25#[serde(default, rename_all = "camelCase")]
26struct JsKeyType {
27  delay: Option<u64>,
28  named_keys: Option<bool>,
29}
30
31fn parse_delay<'js>(ctx: &rquickjs::Ctx<'js>, v: Opt<rquickjs::Value<'js>>) -> rquickjs::Result<Option<u64>> {
32  match v.0 {
33    Some(val) if !val.is_undefined() && !val.is_null() => Ok(serde_from_js::<JsKeyDelay>(ctx, val)?.delay),
34    _ => Ok(None),
35  }
36}
37
38fn parse_type_options<'js>(
39  ctx: &rquickjs::Ctx<'js>,
40  v: Opt<rquickjs::Value<'js>>,
41) -> rquickjs::Result<Option<ferridriver::page::KeyboardTypeOptions>> {
42  match v.0 {
43    Some(val) if !val.is_undefined() && !val.is_null() => {
44      let parsed = serde_from_js::<JsKeyType>(ctx, val)?;
45      Ok(Some(ferridriver::page::KeyboardTypeOptions {
46        delay: parsed.delay,
47        named_keys: parsed.named_keys,
48      }))
49    },
50    _ => Ok(None),
51  }
52}
53
54#[derive(JsLifetime, Trace)]
55#[rquickjs::class(rename = "Keyboard")]
56pub struct KeyboardJs {
57  #[qjs(skip_trace)]
58  page: Arc<Page>,
59}
60
61impl KeyboardJs {
62  #[must_use]
63  pub fn new(page: Arc<Page>) -> Self {
64    Self { page }
65  }
66}
67
68#[rquickjs::methods]
69impl KeyboardJs {
70  /// Dispatch a `keydown` event for `key` on the currently focused element.
71  #[qjs(rename = "down")]
72  pub async fn down(&self, key: String) -> rquickjs::Result<()> {
73    self.page.keyboard().down(&key).await.into_js()
74  }
75
76  /// Dispatch a `keyup` event for `key` on the currently focused element.
77  #[qjs(rename = "up")]
78  pub async fn up(&self, key: String) -> rquickjs::Result<()> {
79    self.page.keyboard().up(&key).await.into_js()
80  }
81
82  /// `keyboard.press(key, options?: { delay? })`.
83  #[qjs(rename = "press")]
84  pub async fn press<'js>(
85    &self,
86    ctx: rquickjs::Ctx<'js>,
87    key: String,
88    options: Opt<rquickjs::Value<'js>>,
89  ) -> rquickjs::Result<()> {
90    let delay = parse_delay(&ctx, options)?;
91    let opts = delay.map(|d| ferridriver::page::KeyboardPressOptions { delay: Some(d) });
92    self.page.keyboard().press(&key, opts).await.into_js()
93  }
94
95  /// `keyboard.type(text, options?: { delay?, namedKeys? })`.
96  #[qjs(rename = "type")]
97  pub async fn type_<'js>(
98    &self,
99    ctx: rquickjs::Ctx<'js>,
100    text: String,
101    options: Opt<rquickjs::Value<'js>>,
102  ) -> rquickjs::Result<()> {
103    let opts = parse_type_options(&ctx, options)?;
104    self.page.keyboard().r#type(&text, opts).await.into_js()
105  }
106
107  /// `keyboard.insertText(text)` — `input` event only, no key events.
108  #[qjs(rename = "insertText")]
109  pub async fn insert_text(&self, text: String) -> rquickjs::Result<()> {
110    self.page.keyboard().insert_text(&text).await.into_js()
111  }
112}