Skip to main content

xcelerate_core/
element.rs

1use crate::page::Page;
2use crate::error::XcelerateResult;
3use std::sync::Arc;
4
5/// Represents an HTML element in the DOM.
6#[derive(uniffi::Object)]
7pub struct Element {
8    pub(crate) page: Arc<Page>,
9    pub(crate) object_id: String,
10}
11
12#[uniffi::export(async_runtime = "tokio")]
13impl Element {
14    /// Clicks the element.
15    pub async fn click(self: Arc<Self>) -> XcelerateResult<Arc<Self>> {
16        self.call_js("function() { this.click(); }".to_string()).await?;
17        Ok(self)
18    }
19
20    pub async fn type_text(self: Arc<Self>, text: String) -> XcelerateResult<Arc<Self>> {
21        // 1. Focus the element first
22        self.clone().focus().await?;
23
24        // 2. Dispatch key events for each character
25        for c in text.chars() {
26            let mut params = browser_protocol::input::DispatchKeyEventParams {
27                type_: "char".into(),
28                ..Default::default()
29            };
30            params.text = Some(c.to_string());
31            params.unmodifiedText = Some(c.to_string());
32
33            self.page.client.execute_with_session(
34                Some(&self.page.session_id),
35                params
36            ).await?;
37            
38            // Subtle delay to mimic human typing
39            tokio::time::sleep(std::time::Duration::from_millis(50)).await;
40        }
41        
42        Ok(self)
43    }
44
45    /// Hovers over the element.
46    pub async fn hover(self: Arc<Self>) -> XcelerateResult<Arc<Self>> {
47        self.call_js("function() { this.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); }".to_string()).await?;
48        Ok(self)
49    }
50
51    /// Focuses the element.
52    pub async fn focus(self: Arc<Self>) -> XcelerateResult<Arc<Self>> {
53        self.call_js("function() { this.focus(); }".to_string()).await?;
54        Ok(self)
55    }
56
57    /// Returns the visible text of the element.
58    pub async fn text(&self) -> XcelerateResult<String> {
59        let res = self.call_js("function() { return this.innerText; }".to_string()).await?;
60        Ok(res.result.value.and_then(|v| v.as_str().map(|s| s.to_string())).unwrap_or_default())
61    }
62
63    /// Returns the value of a specific attribute.
64    pub async fn attribute(&self, name: String) -> XcelerateResult<Option<String>> {
65        let js = format!("function() {{ return this.getAttribute('{}'); }}", name);
66        let res = self.call_js(js).await?;
67        Ok(res.result.value.and_then(|v| v.as_str().map(|s| s.to_string())))
68    }
69
70    /// Returns the inner HTML of the element.
71    pub async fn inner_html(&self) -> XcelerateResult<String> {
72        let res = self.call_js("function() { return this.innerHTML; }".to_string()).await?;
73        Ok(res.result.value.and_then(|v| v.as_str().map(|s| s.to_string())).unwrap_or_default())
74    }
75}
76
77impl Element {
78    /// Helper to call JS on this element.
79    async fn call_js(&self, js: String) -> XcelerateResult<js_protocol::runtime::CallFunctionOnReturns> {
80        self.page.client.execute_with_session(
81            Some(&self.page.session_id),
82            js_protocol::runtime::CallFunctionOnParams {
83                functionDeclaration: js,
84                objectId: Some(self.object_id.clone()),
85                ..Default::default()
86            }
87        ).await
88    }
89}