ferridriver_script/bindings/
element_handle.rs1use ferridriver::ElementHandle;
8use ferridriver::backend::ImageFormat;
9use rquickjs::JsLifetime;
10use rquickjs::class::Trace;
11
12use crate::bindings::convert::{
13 FerriResultExt, extract_page_function, quickjs_arg_to_serialized, serialized_value_to_quickjs,
14};
15
16fn parse_screenshot_format<'js>(
21 _ctx: &rquickjs::Ctx<'js>,
22 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
23) -> rquickjs::Result<ImageFormat> {
24 let Some(opts_val) = options.0 else {
25 return Ok(ImageFormat::Png);
26 };
27 if opts_val.is_undefined() || opts_val.is_null() {
28 return Ok(ImageFormat::Png);
29 }
30 let Some(obj) = opts_val.as_object() else {
31 return Ok(ImageFormat::Png);
32 };
33 let type_field: Option<String> = obj.get("type").ok();
34 match type_field.as_deref() {
35 None | Some("" | "png") => Ok(ImageFormat::Png),
36 Some("jpeg" | "jpg") => Ok(ImageFormat::Jpeg),
37 Some("webp") => Ok(ImageFormat::Webp),
38 Some(other) => Err(rquickjs::Error::new_from_js_message(
39 "screenshot",
40 "invalid format",
41 &format!("unsupported screenshot type {other:?}; expected 'png' | 'jpeg' | 'webp'"),
42 )),
43 }
44}
45
46#[derive(JsLifetime, Trace)]
48#[rquickjs::class(rename = "ElementHandle")]
49pub struct ElementHandleJs {
50 #[qjs(skip_trace)]
51 inner: ElementHandle,
52}
53
54impl ElementHandleJs {
55 #[must_use]
56 pub fn new(inner: ElementHandle) -> Self {
57 Self { inner }
58 }
59
60 #[must_use]
61 pub fn inner(&self) -> &ElementHandle {
62 &self.inner
63 }
64}
65
66#[rquickjs::methods]
67impl ElementHandleJs {
68 #[qjs(rename = "isDisposed")]
71 pub fn is_disposed(&self) -> bool {
72 self.inner.is_disposed()
73 }
74
75 #[qjs(rename = "dispose")]
77 pub async fn dispose(&self) -> rquickjs::Result<()> {
78 self.inner.dispose().await.into_js()
79 }
80
81 #[qjs(rename = "asJSHandle")]
85 pub fn as_js_handle(&self) -> crate::bindings::js_handle::JSHandleJs {
86 crate::bindings::js_handle::JSHandleJs::new(self.inner.as_js_handle().clone())
87 }
88
89 #[qjs(rename = "evaluate")]
91 pub async fn evaluate<'js>(
92 &self,
93 ctx: rquickjs::Ctx<'js>,
94 page_function: rquickjs::Value<'js>,
95 arg: rquickjs::function::Opt<rquickjs::Value<'js>>,
96 ) -> rquickjs::Result<rquickjs::Value<'js>> {
97 let (source, is_fn) = extract_page_function(&ctx, page_function)?;
98 let serialized = quickjs_arg_to_serialized(&ctx, arg.0)?;
99 let result = self
100 .inner
101 .as_js_handle()
102 .evaluate(&source, serialized, is_fn)
103 .await
104 .into_js()?;
105 serialized_value_to_quickjs(&ctx, &result)
106 }
107
108 #[qjs(rename = "evaluateHandle")]
110 pub async fn evaluate_handle<'js>(
111 &self,
112 ctx: rquickjs::Ctx<'js>,
113 page_function: rquickjs::Value<'js>,
114 arg: rquickjs::function::Opt<rquickjs::Value<'js>>,
115 ) -> rquickjs::Result<crate::bindings::js_handle::JSHandleJs> {
116 let (source, is_fn) = extract_page_function(&ctx, page_function)?;
117 let serialized = quickjs_arg_to_serialized(&ctx, arg.0)?;
118 let handle = self
119 .inner
120 .as_js_handle()
121 .evaluate_handle(&source, serialized, is_fn)
122 .await
123 .into_js()?;
124 Ok(crate::bindings::js_handle::JSHandleJs::new(handle))
125 }
126
127 #[qjs(rename = "innerHTML")]
130 pub async fn inner_html(&self) -> rquickjs::Result<String> {
131 self.inner.inner_html().await.into_js()
132 }
133
134 #[qjs(rename = "innerText")]
135 pub async fn inner_text(&self) -> rquickjs::Result<String> {
136 self.inner.inner_text().await.into_js()
137 }
138
139 #[qjs(rename = "textContent")]
140 pub async fn text_content(&self) -> rquickjs::Result<Option<String>> {
141 self.inner.text_content().await.into_js()
142 }
143
144 #[qjs(rename = "getAttribute")]
145 pub async fn get_attribute(&self, name: String) -> rquickjs::Result<Option<String>> {
146 self.inner.get_attribute(&name).await.into_js()
147 }
148
149 #[qjs(rename = "inputValue")]
150 pub async fn input_value(&self) -> rquickjs::Result<String> {
151 self.inner.input_value().await.into_js()
152 }
153
154 #[qjs(rename = "isVisible")]
157 pub async fn is_visible(&self) -> rquickjs::Result<bool> {
158 self.inner.is_visible().await.into_js()
159 }
160
161 #[qjs(rename = "isHidden")]
162 pub async fn is_hidden(&self) -> rquickjs::Result<bool> {
163 self.inner.is_hidden().await.into_js()
164 }
165
166 #[qjs(rename = "isDisabled")]
167 pub async fn is_disabled(&self) -> rquickjs::Result<bool> {
168 self.inner.is_disabled().await.into_js()
169 }
170
171 #[qjs(rename = "isEnabled")]
172 pub async fn is_enabled(&self) -> rquickjs::Result<bool> {
173 self.inner.is_enabled().await.into_js()
174 }
175
176 #[qjs(rename = "isChecked")]
177 pub async fn is_checked(&self) -> rquickjs::Result<bool> {
178 self.inner.is_checked().await.into_js()
179 }
180
181 #[qjs(rename = "isEditable")]
182 pub async fn is_editable(&self) -> rquickjs::Result<bool> {
183 self.inner.is_editable().await.into_js()
184 }
185
186 #[qjs(rename = "boundingBox")]
191 pub async fn bounding_box<'js>(&self, ctx: rquickjs::Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
192 let bbox = self.inner.bounding_box().await.into_js()?;
193 match bbox {
194 None => Ok(rquickjs::Value::new_null(ctx)),
195 Some(b) => {
196 let obj = rquickjs::Object::new(ctx.clone())?;
197 obj.set("x", b.x)?;
198 obj.set("y", b.y)?;
199 obj.set("width", b.width)?;
200 obj.set("height", b.height)?;
201 Ok(obj.into_value())
202 },
203 }
204 }
205
206 #[qjs(rename = "click")]
209 pub async fn click(&self) -> rquickjs::Result<()> {
210 self.inner.click().await.into_js()
211 }
212
213 #[qjs(rename = "dblclick")]
214 pub async fn dblclick(&self) -> rquickjs::Result<()> {
215 self.inner.dblclick().await.into_js()
216 }
217
218 #[qjs(rename = "hover")]
219 pub async fn hover(&self) -> rquickjs::Result<()> {
220 self.inner.hover().await.into_js()
221 }
222
223 #[qjs(rename = "type")]
224 pub async fn type_str(&self, text: String) -> rquickjs::Result<()> {
225 self.inner.type_str(&text).await.into_js()
226 }
227
228 #[qjs(rename = "focus")]
229 pub async fn focus(&self) -> rquickjs::Result<()> {
230 self.inner.focus().await.into_js()
231 }
232
233 #[qjs(rename = "scrollIntoViewIfNeeded")]
234 pub async fn scroll_into_view_if_needed(&self) -> rquickjs::Result<()> {
235 self.inner.scroll_into_view_if_needed().await.into_js()
236 }
237
238 #[qjs(rename = "screenshot")]
244 pub async fn screenshot<'js>(
245 &self,
246 ctx: rquickjs::Ctx<'js>,
247 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
248 ) -> rquickjs::Result<Vec<u8>> {
249 let format = parse_screenshot_format(&ctx, options)?;
250 self.inner.screenshot(format).await.into_js()
251 }
252
253 #[qjs(rename = "$eval")]
258 pub async fn dollar_eval<'js>(
259 &self,
260 ctx: rquickjs::Ctx<'js>,
261 selector: String,
262 page_function: rquickjs::Value<'js>,
263 arg: rquickjs::function::Opt<rquickjs::Value<'js>>,
264 ) -> rquickjs::Result<rquickjs::Value<'js>> {
265 let (source, _is_fn) = extract_page_function(&ctx, page_function)?;
266 let serialized = quickjs_arg_to_serialized(&ctx, arg.0)?;
267 let result = self
268 .inner
269 .eval_on_selector(&selector, &source, serialized)
270 .await
271 .into_js()?;
272 serialized_value_to_quickjs(&ctx, &result)
273 }
274
275 #[qjs(rename = "$$eval")]
278 pub async fn dollar_dollar_eval<'js>(
279 &self,
280 ctx: rquickjs::Ctx<'js>,
281 selector: String,
282 page_function: rquickjs::Value<'js>,
283 arg: rquickjs::function::Opt<rquickjs::Value<'js>>,
284 ) -> rquickjs::Result<rquickjs::Value<'js>> {
285 let (source, _is_fn) = extract_page_function(&ctx, page_function)?;
286 let serialized = quickjs_arg_to_serialized(&ctx, arg.0)?;
287 let result = self
288 .inner
289 .eval_on_selector_all(&selector, &source, serialized)
290 .await
291 .into_js()?;
292 serialized_value_to_quickjs(&ctx, &result)
293 }
294
295 #[qjs(rename = "ownerFrame")]
299 pub async fn owner_frame(&self) -> rquickjs::Result<Option<crate::bindings::frame::FrameJs>> {
300 let maybe = self.inner.owner_frame().await.into_js()?;
301 Ok(maybe.map(crate::bindings::frame::FrameJs::new))
302 }
303
304 #[qjs(rename = "contentFrame")]
306 pub async fn content_frame(&self) -> rquickjs::Result<Option<crate::bindings::frame::FrameJs>> {
307 let maybe = self.inner.content_frame().await.into_js()?;
308 Ok(maybe.map(crate::bindings::frame::FrameJs::new))
309 }
310
311 #[qjs(rename = "waitForElementState")]
315 pub async fn wait_for_element_state(
316 &self,
317 state: String,
318 timeout: rquickjs::function::Opt<f64>,
319 ) -> rquickjs::Result<()> {
320 let st = ferridriver::ElementState::parse(&state).into_js()?;
321 let timeout_ms = timeout.0.map(|ms| ms as u64);
322 self.inner.wait_for_element_state(st, timeout_ms).await.into_js()
323 }
324
325 #[qjs(rename = "waitForSelector")]
327 pub async fn wait_for_selector(
328 &self,
329 selector: String,
330 timeout: rquickjs::function::Opt<f64>,
331 ) -> rquickjs::Result<Option<ElementHandleJs>> {
332 let timeout_ms = timeout.0.map(|ms| ms as u64);
333 let maybe = self.inner.wait_for_selector(&selector, timeout_ms).await.into_js()?;
334 Ok(maybe.map(ElementHandleJs::new))
335 }
336
337 #[qjs(rename = "fill")]
341 pub async fn fill<'js>(
342 &self,
343 ctx: rquickjs::Ctx<'js>,
344 value: String,
345 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
346 ) -> rquickjs::Result<()> {
347 let opts = crate::bindings::convert::parse_fill_options(&ctx, options)?;
348 self.inner.fill(&value, opts).await.into_js()
349 }
350
351 #[qjs(rename = "check")]
353 pub async fn check<'js>(
354 &self,
355 ctx: rquickjs::Ctx<'js>,
356 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
357 ) -> rquickjs::Result<()> {
358 let opts = crate::bindings::convert::parse_check_options(&ctx, options)?;
359 self.inner.check(opts).await.into_js()
360 }
361
362 #[qjs(rename = "uncheck")]
364 pub async fn uncheck<'js>(
365 &self,
366 ctx: rquickjs::Ctx<'js>,
367 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
368 ) -> rquickjs::Result<()> {
369 let opts = crate::bindings::convert::parse_check_options(&ctx, options)?;
370 self.inner.uncheck(opts).await.into_js()
371 }
372
373 #[qjs(rename = "setChecked")]
375 pub async fn set_checked<'js>(
376 &self,
377 ctx: rquickjs::Ctx<'js>,
378 checked: bool,
379 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
380 ) -> rquickjs::Result<()> {
381 let opts = crate::bindings::convert::parse_check_options(&ctx, options)?;
382 self.inner.set_checked(checked, opts).await.into_js()
383 }
384
385 #[qjs(rename = "tap")]
387 pub async fn tap<'js>(
388 &self,
389 ctx: rquickjs::Ctx<'js>,
390 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
391 ) -> rquickjs::Result<()> {
392 let opts = crate::bindings::convert::parse_tap_options(&ctx, options)?;
393 self.inner.tap(opts).await.into_js()
394 }
395
396 #[qjs(rename = "press")]
398 pub async fn press<'js>(
399 &self,
400 ctx: rquickjs::Ctx<'js>,
401 key: String,
402 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
403 ) -> rquickjs::Result<()> {
404 let opts = crate::bindings::convert::parse_press_options(&ctx, options)?;
405 self.inner.press(&key, opts).await.into_js()
406 }
407
408 #[qjs(rename = "dispatchEvent")]
410 pub async fn dispatch_event<'js>(
411 &self,
412 ctx: rquickjs::Ctx<'js>,
413 event_type: String,
414 event_init: rquickjs::function::Opt<rquickjs::Value<'js>>,
415 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
416 ) -> rquickjs::Result<()> {
417 let init_json = match event_init.0 {
418 Some(v) if !v.is_undefined() && !v.is_null() => {
419 Some(crate::bindings::convert::serde_from_js::<serde_json::Value>(&ctx, v)?)
420 },
421 _ => None,
422 };
423 let opts = crate::bindings::convert::parse_dispatch_event_options(&ctx, options)?;
424 self.inner.dispatch_event(&event_type, init_json, opts).await.into_js()
425 }
426
427 #[qjs(rename = "selectOption")]
429 pub async fn select_option<'js>(
430 &self,
431 ctx: rquickjs::Ctx<'js>,
432 values: rquickjs::Value<'js>,
433 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
434 ) -> rquickjs::Result<Vec<String>> {
435 let values = crate::bindings::convert::parse_select_option_values(&ctx, values)?;
436 let opts = crate::bindings::convert::parse_select_option_options(&ctx, options)?;
437 self.inner.select_option(values, opts).await.into_js()
438 }
439
440 #[qjs(rename = "selectText")]
442 pub async fn select_text(&self) -> rquickjs::Result<()> {
443 self.inner.select_text().await.into_js()
444 }
445
446 #[qjs(rename = "setInputFiles")]
448 pub async fn set_input_files<'js>(
449 &self,
450 ctx: rquickjs::Ctx<'js>,
451 files: rquickjs::Value<'js>,
452 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
453 ) -> rquickjs::Result<()> {
454 let files = crate::bindings::convert::parse_input_files(&ctx, files)?;
455 let opts = crate::bindings::convert::parse_set_input_files_options(&ctx, options)?;
456 self.inner.set_input_files(files, opts).await.into_js()
457 }
458}