firefox_webdriver/browser/
element.rs

1//! DOM element interaction and manipulation.
2//!
3//! Elements are identified by UUID and stored in the content script's
4//! internal `Map<UUID, Element>`.
5//!
6//! # Example
7//!
8//! ```ignore
9//! let element = tab.find_element("#submit-button").await?;
10//!
11//! // Get properties
12//! let text = element.get_text().await?;
13//! let value = element.get_value().await?;
14//!
15//! // Interact
16//! element.click().await?;
17//! element.type_text("Hello, World!").await?;
18//! ```
19
20// ============================================================================
21// Imports
22// ============================================================================
23
24use std::fmt;
25use std::sync::Arc;
26
27use serde_json::Value;
28
29use crate::error::{Error, Result};
30use crate::identifiers::{ElementId, FrameId, SessionId, TabId};
31use crate::protocol::{Command, ElementCommand, InputCommand, Request, Response};
32
33use super::Window;
34
35// ============================================================================
36// Types
37// ============================================================================
38
39/// Internal shared state for an element.
40pub(crate) struct ElementInner {
41    /// This element's unique ID.
42    pub id: ElementId,
43
44    /// Tab ID where this element exists.
45    pub tab_id: TabId,
46
47    /// Frame ID where this element exists.
48    pub frame_id: FrameId,
49
50    /// Session ID.
51    pub session_id: SessionId,
52
53    /// Parent window.
54    pub window: Option<Window>,
55}
56
57// ============================================================================
58// Element
59// ============================================================================
60
61/// A handle to a DOM element in a browser tab.
62///
63/// Elements are identified by a UUID stored in the extension's content script.
64/// Operations use generic dynamic property access (`element[method]()`).
65///
66/// # Example
67///
68/// ```ignore
69/// let element = tab.find_element("input[name='email']").await?;
70///
71/// // Set value and submit
72/// element.set_value("user@example.com").await?;
73/// element.type_text("\n").await?; // Press Enter
74/// ```
75#[derive(Clone)]
76pub struct Element {
77    /// Shared inner state.
78    pub(crate) inner: Arc<ElementInner>,
79}
80
81// ============================================================================
82// Element - Display
83// ============================================================================
84
85impl fmt::Debug for Element {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        f.debug_struct("Element")
88            .field("id", &self.inner.id)
89            .field("tab_id", &self.inner.tab_id)
90            .field("frame_id", &self.inner.frame_id)
91            .finish_non_exhaustive()
92    }
93}
94
95// ============================================================================
96// Element - Constructor
97// ============================================================================
98
99impl Element {
100    /// Creates a new element handle.
101    pub(crate) fn new(
102        id: ElementId,
103        tab_id: TabId,
104        frame_id: FrameId,
105        session_id: SessionId,
106        window: Option<Window>,
107    ) -> Self {
108        Self {
109            inner: Arc::new(ElementInner {
110                id,
111                tab_id,
112                frame_id,
113                session_id,
114                window,
115            }),
116        }
117    }
118}
119
120// ============================================================================
121// Element - Accessors
122// ============================================================================
123
124impl Element {
125    /// Returns this element's ID.
126    #[inline]
127    #[must_use]
128    pub fn id(&self) -> &ElementId {
129        &self.inner.id
130    }
131
132    /// Returns the tab ID where this element exists.
133    #[inline]
134    #[must_use]
135    pub fn tab_id(&self) -> TabId {
136        self.inner.tab_id
137    }
138
139    /// Returns the frame ID where this element exists.
140    #[inline]
141    #[must_use]
142    pub fn frame_id(&self) -> FrameId {
143        self.inner.frame_id
144    }
145}
146
147// ============================================================================
148// Element - Actions
149// ============================================================================
150
151impl Element {
152    /// Clicks the element.
153    ///
154    /// Uses `element.click()` internally.
155    pub async fn click(&self) -> Result<()> {
156        self.call_method("click", vec![]).await?;
157        Ok(())
158    }
159
160    /// Focuses the element.
161    pub async fn focus(&self) -> Result<()> {
162        self.call_method("focus", vec![]).await?;
163        Ok(())
164    }
165
166    /// Blurs (unfocuses) the element.
167    pub async fn blur(&self) -> Result<()> {
168        self.call_method("blur", vec![]).await?;
169        Ok(())
170    }
171
172    /// Clears the element's value.
173    ///
174    /// Sets `element.value = ""`.
175    pub async fn clear(&self) -> Result<()> {
176        self.set_property("value", Value::String(String::new()))
177            .await
178    }
179}
180
181// ============================================================================
182// Element - Properties
183// ============================================================================
184
185impl Element {
186    /// Gets the element's text content.
187    pub async fn get_text(&self) -> Result<String> {
188        let value = self.get_property("textContent").await?;
189        Ok(value.as_str().unwrap_or("").to_string())
190    }
191
192    /// Gets the element's inner HTML.
193    pub async fn get_inner_html(&self) -> Result<String> {
194        let value = self.get_property("innerHTML").await?;
195        Ok(value.as_str().unwrap_or("").to_string())
196    }
197
198    /// Gets the element's value (for input elements).
199    pub async fn get_value(&self) -> Result<String> {
200        let value = self.get_property("value").await?;
201        Ok(value.as_str().unwrap_or("").to_string())
202    }
203
204    /// Sets the element's value (for input elements).
205    pub async fn set_value(&self, value: &str) -> Result<()> {
206        self.set_property("value", Value::String(value.to_string()))
207            .await
208    }
209
210    /// Gets an attribute value.
211    ///
212    /// Returns `None` if the attribute doesn't exist.
213    pub async fn get_attribute(&self, name: &str) -> Result<Option<String>> {
214        let result = self
215            .call_method("getAttribute", vec![Value::String(name.to_string())])
216            .await?;
217        Ok(result.as_str().map(|s| s.to_string()))
218    }
219
220    /// Checks if the element is displayed.
221    ///
222    /// Returns `false` if `offsetParent` is null (element is hidden).
223    pub async fn is_displayed(&self) -> Result<bool> {
224        let offset_parent = self.get_property("offsetParent").await?;
225        Ok(!offset_parent.is_null())
226    }
227
228    /// Checks if the element is enabled.
229    ///
230    /// Returns `true` if `disabled` property is false or absent.
231    pub async fn is_enabled(&self) -> Result<bool> {
232        let disabled = self.get_property("disabled").await?;
233        Ok(!disabled.as_bool().unwrap_or(false))
234    }
235}
236
237// ============================================================================
238// Element - Keyboard Input
239// ============================================================================
240
241impl Element {
242    /// Types a single key with optional modifiers.
243    ///
244    /// Dispatches full keyboard event sequence: keydown → input → keypress → keyup.
245    ///
246    /// # Arguments
247    ///
248    /// * `key` - Key value (e.g., "a", "Enter")
249    /// * `code` - Key code (e.g., "KeyA", "Enter")
250    /// * `key_code` - Legacy keyCode number
251    /// * `printable` - Whether key produces visible output
252    /// * `ctrl` - Ctrl modifier
253    /// * `shift` - Shift modifier
254    /// * `alt` - Alt modifier
255    /// * `meta` - Meta modifier
256    #[allow(clippy::too_many_arguments)]
257    pub async fn type_key(
258        &self,
259        key: &str,
260        code: &str,
261        key_code: u32,
262        printable: bool,
263        ctrl: bool,
264        shift: bool,
265        alt: bool,
266        meta: bool,
267    ) -> Result<()> {
268        let command = Command::Input(InputCommand::TypeKey {
269            element_id: self.inner.id.clone(),
270            key: key.to_string(),
271            code: code.to_string(),
272            key_code,
273            printable,
274            ctrl,
275            shift,
276            alt,
277            meta,
278        });
279
280        self.send_command(command).await?;
281        Ok(())
282    }
283
284    /// Types a character with default key properties.
285    ///
286    /// Convenience method that uses `type_text` internally for reliability.
287    pub async fn type_char(&self, c: char) -> Result<()> {
288        self.type_text(&c.to_string()).await
289    }
290
291    /// Types a text string character by character.
292    ///
293    /// Each character goes through full keyboard event sequence.
294    /// This is slower but more realistic than `set_value`.
295    ///
296    /// # Example
297    ///
298    /// ```ignore
299    /// element.type_text("Hello, World!").await?;
300    /// ```
301    pub async fn type_text(&self, text: &str) -> Result<()> {
302        let command = Command::Input(InputCommand::TypeText {
303            element_id: self.inner.id.clone(),
304            text: text.to_string(),
305        });
306
307        self.send_command(command).await?;
308        Ok(())
309    }
310}
311
312// ============================================================================
313// Element - Mouse Input
314// ============================================================================
315
316impl Element {
317    /// Clicks the element using mouse events.
318    ///
319    /// Dispatches: mousemove → mousedown → mouseup → click.
320    /// This is more realistic than `click()` which uses `element.click()`.
321    ///
322    /// # Arguments
323    ///
324    /// * `button` - Mouse button (0=left, 1=middle, 2=right)
325    pub async fn mouse_click(&self, button: u8) -> Result<()> {
326        let command = Command::Input(InputCommand::MouseClick {
327            element_id: Some(self.inner.id.clone()),
328            x: None,
329            y: None,
330            button,
331        });
332
333        self.send_command(command).await?;
334        Ok(())
335    }
336
337    /// Moves mouse to the element center.
338    pub async fn mouse_move(&self) -> Result<()> {
339        let command = Command::Input(InputCommand::MouseMove {
340            element_id: Some(self.inner.id.clone()),
341            x: None,
342            y: None,
343        });
344
345        self.send_command(command).await?;
346        Ok(())
347    }
348
349    /// Presses mouse button down on the element (without release).
350    ///
351    /// Dispatches only mousedown event.
352    /// Use with `mouse_up()` for drag operations.
353    ///
354    /// # Arguments
355    ///
356    /// * `button` - Mouse button (0=left, 1=middle, 2=right)
357    pub async fn mouse_down(&self, button: u8) -> Result<()> {
358        let command = Command::Input(InputCommand::MouseDown {
359            element_id: Some(self.inner.id.clone()),
360            x: None,
361            y: None,
362            button,
363        });
364
365        self.send_command(command).await?;
366        Ok(())
367    }
368
369    /// Releases mouse button on the element.
370    ///
371    /// Dispatches only mouseup event.
372    /// Use with `mouse_down()` for drag operations.
373    ///
374    /// # Arguments
375    ///
376    /// * `button` - Mouse button (0=left, 1=middle, 2=right)
377    pub async fn mouse_up(&self, button: u8) -> Result<()> {
378        let command = Command::Input(InputCommand::MouseUp {
379            element_id: Some(self.inner.id.clone()),
380            x: None,
381            y: None,
382            button,
383        });
384
385        self.send_command(command).await?;
386        Ok(())
387    }
388}
389
390// ============================================================================
391// Element - Nested Search
392// ============================================================================
393
394impl Element {
395    /// Finds a child element by CSS selector.
396    ///
397    /// # Errors
398    ///
399    /// Returns [`Error::ElementNotFound`] if no matching element exists.
400    pub async fn find_element(&self, selector: &str) -> Result<Element> {
401        let command = Command::Element(ElementCommand::Find {
402            selector: selector.to_string(),
403            parent_id: Some(self.inner.id.clone()),
404        });
405
406        let response = self.send_command(command).await?;
407
408        let element_id = response
409            .result
410            .as_ref()
411            .and_then(|v| v.get("elementId"))
412            .and_then(|v| v.as_str())
413            .ok_or_else(|| {
414                Error::element_not_found(selector, self.inner.tab_id, self.inner.frame_id)
415            })?;
416
417        Ok(Element::new(
418            ElementId::new(element_id),
419            self.inner.tab_id,
420            self.inner.frame_id,
421            self.inner.session_id,
422            self.inner.window.clone(),
423        ))
424    }
425
426    /// Finds all child elements matching a CSS selector.
427    ///
428    /// Returns an empty vector if no elements match.
429    pub async fn find_elements(&self, selector: &str) -> Result<Vec<Element>> {
430        let command = Command::Element(ElementCommand::FindAll {
431            selector: selector.to_string(),
432            parent_id: Some(self.inner.id.clone()),
433        });
434
435        let response = self.send_command(command).await?;
436
437        let element_ids = response
438            .result
439            .as_ref()
440            .and_then(|v| v.get("elementIds"))
441            .and_then(|v| v.as_array())
442            .map(|arr| {
443                arr.iter()
444                    .filter_map(|v| v.as_str())
445                    .map(|id| {
446                        Element::new(
447                            ElementId::new(id),
448                            self.inner.tab_id,
449                            self.inner.frame_id,
450                            self.inner.session_id,
451                            self.inner.window.clone(),
452                        )
453                    })
454                    .collect()
455            })
456            .unwrap_or_default();
457
458        Ok(element_ids)
459    }
460}
461
462// ============================================================================
463// Element - Generic Property Access
464// ============================================================================
465
466impl Element {
467    /// Gets a property value via `element[name]`.
468    ///
469    /// # Arguments
470    ///
471    /// * `name` - Property name (e.g., "value", "textContent")
472    pub async fn get_property(&self, name: &str) -> Result<Value> {
473        let command = Command::Element(ElementCommand::GetProperty {
474            element_id: self.inner.id.clone(),
475            name: name.to_string(),
476        });
477
478        let response = self.send_command(command).await?;
479
480        Ok(response
481            .result
482            .and_then(|v| v.get("value").cloned())
483            .unwrap_or(Value::Null))
484    }
485
486    /// Sets a property value via `element[name] = value`.
487    ///
488    /// # Arguments
489    ///
490    /// * `name` - Property name
491    /// * `value` - Value to set
492    pub async fn set_property(&self, name: &str, value: Value) -> Result<()> {
493        let command = Command::Element(ElementCommand::SetProperty {
494            element_id: self.inner.id.clone(),
495            name: name.to_string(),
496            value,
497        });
498
499        self.send_command(command).await?;
500        Ok(())
501    }
502
503    /// Calls a method via `element[name](...args)`.
504    ///
505    /// # Arguments
506    ///
507    /// * `name` - Method name
508    /// * `args` - Method arguments
509    pub async fn call_method(&self, name: &str, args: Vec<Value>) -> Result<Value> {
510        let command = Command::Element(ElementCommand::CallMethod {
511            element_id: self.inner.id.clone(),
512            name: name.to_string(),
513            args,
514        });
515
516        let response = self.send_command(command).await?;
517
518        Ok(response
519            .result
520            .and_then(|v| v.get("value").cloned())
521            .unwrap_or(Value::Null))
522    }
523}
524
525// ============================================================================
526// Element - Internal
527// ============================================================================
528
529impl Element {
530    /// Sends a command and returns the response.
531    async fn send_command(&self, command: Command) -> Result<Response> {
532        let window = self
533            .inner
534            .window
535            .as_ref()
536            .ok_or_else(|| Error::protocol("Element has no associated window"))?;
537
538        let request = Request::new(self.inner.tab_id, self.inner.frame_id, command);
539
540        window
541            .inner
542            .pool
543            .send(window.inner.session_id, request)
544            .await
545    }
546}
547
548// ============================================================================
549// Tests
550// ============================================================================
551
552#[cfg(test)]
553mod tests {
554    use super::Element;
555
556    #[test]
557    fn test_element_is_clone() {
558        fn assert_clone<T: Clone>() {}
559        assert_clone::<Element>();
560    }
561
562    #[test]
563    fn test_element_is_debug() {
564        fn assert_debug<T: std::fmt::Debug>() {}
565        assert_debug::<Element>();
566    }
567}