Skip to main content

xa11y_core/
element.rs

1use std::collections::HashMap;
2use std::fmt;
3use std::ops::Deref;
4use std::sync::Arc;
5
6use serde::{Deserialize, Serialize};
7
8use crate::provider::Provider;
9use crate::role::Role;
10
11/// The raw data for a single element in an accessibility tree.
12///
13/// This is the underlying data struct. Most consumers should use [`Element`],
14/// which wraps `ElementData` with a provider reference for lazy navigation.
15/// `ElementData` is used directly by provider implementors.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ElementData {
18    /// Element role
19    pub role: Role,
20
21    /// Human-readable name (title, label)
22    pub name: Option<String>,
23
24    /// Current value (text content, slider position, etc.)
25    pub value: Option<String>,
26
27    /// Supplementary description (tooltip, help text)
28    pub description: Option<String>,
29
30    /// Bounding rectangle in screen pixels
31    pub bounds: Option<Rect>,
32
33    /// Available actions reported by the platform.
34    ///
35    /// Names are `snake_case` strings — well-known actions use their standard
36    /// names (`"press"`, `"toggle"`, `"expand"`, etc.) and platform-specific
37    /// actions use their converted names (e.g. macOS `AXCustomThing` →
38    /// `"custom_thing"`).
39    pub actions: Vec<String>,
40
41    /// Current state flags
42    pub states: StateSet,
43
44    /// Numeric value for range controls (sliders, progress bars, spinners).
45    pub numeric_value: Option<f64>,
46
47    /// Minimum value for range controls.
48    pub min_value: Option<f64>,
49
50    /// Maximum value for range controls.
51    pub max_value: Option<f64>,
52
53    /// Platform-assigned stable identifier for cross-snapshot correlation.
54    /// - macOS: `AXIdentifier`
55    /// - Windows: `AutomationId`
56    /// - Linux: D-Bus `object_path`
57    ///
58    /// Not all elements have one.
59    pub stable_id: Option<String>,
60
61    /// Process ID of the application that owns this element.
62    pub pid: Option<u32>,
63
64    /// Platform-specific raw data
65    pub raw: RawPlatformData,
66
67    /// Opaque handle for the provider to look up the platform object.
68    /// Not serialized — only valid within the provider that created it.
69    #[serde(skip, default)]
70    pub handle: u64,
71}
72
73/// A live element with lazy navigation via a provider reference.
74///
75/// `Element` dereferences to [`ElementData`], so all properties (`role`, `name`,
76/// `value`, `states`, etc.) are accessible via field access. Navigation
77/// methods (`parent()`, `children()`) call the provider on demand.
78///
79/// Elements are cheap to clone (they share the provider via `Arc`).
80#[derive(Clone)]
81pub struct Element {
82    data: ElementData,
83    provider: Arc<dyn Provider>,
84}
85
86impl Deref for Element {
87    type Target = ElementData;
88
89    fn deref(&self) -> &ElementData {
90        &self.data
91    }
92}
93
94impl fmt::Debug for Element {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        fmt::Debug::fmt(&self.data, f)
97    }
98}
99
100impl fmt::Display for Element {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        let name_part = self
103            .data
104            .name
105            .as_ref()
106            .map(|n| format!(" \"{}\"", n))
107            .unwrap_or_default();
108        let value_part = self
109            .data
110            .value
111            .as_ref()
112            .map(|v| format!(" value=\"{}\"", v))
113            .unwrap_or_default();
114        write!(
115            f,
116            "{}{}{}",
117            self.data.role.to_snake_case(),
118            name_part,
119            value_part,
120        )
121    }
122}
123
124impl Serialize for Element {
125    fn serialize<S: serde::Serializer>(
126        &self,
127        serializer: S,
128    ) -> std::result::Result<S::Ok, S::Error> {
129        self.data.serialize(serializer)
130    }
131}
132
133impl Element {
134    /// Create an Element from raw data and a provider reference.
135    pub fn new(data: ElementData, provider: Arc<dyn Provider>) -> Self {
136        Self { data, provider }
137    }
138
139    /// Get the underlying ElementData.
140    pub fn data(&self) -> &ElementData {
141        &self.data
142    }
143
144    /// Get the provider reference.
145    pub fn provider(&self) -> &Arc<dyn Provider> {
146        &self.provider
147    }
148
149    /// Get direct children of this element.
150    ///
151    /// Each call queries the provider — results are not cached.
152    pub fn children(&self) -> crate::error::Result<Vec<Element>> {
153        let children = self.provider.get_children(Some(&self.data))?;
154        Ok(children
155            .into_iter()
156            .map(|d| Element::new(d, Arc::clone(&self.provider)))
157            .collect())
158    }
159
160    /// Get the parent element, if any (root-level elements have no parent).
161    ///
162    /// Each call queries the provider — results are not cached.
163    pub fn parent(&self) -> crate::error::Result<Option<Element>> {
164        let parent = self.provider.get_parent(&self.data)?;
165        Ok(parent.map(|d| Element::new(d, Arc::clone(&self.provider))))
166    }
167
168    /// Get the process ID from the element data.
169    pub fn pid(&self) -> Option<u32> {
170        self.data.pid
171    }
172}
173
174/// Boolean state flags for an element.
175///
176/// **Semantics for non-applicable states:** When a state doesn't apply to an
177/// element's role, the backend uses the platform's reported value or defaults:
178/// - `enabled`: `true` (elements are enabled unless explicitly disabled)
179/// - `visible`: `true` (elements are visible unless explicitly hidden/offscreen)
180/// - `focused`, `focusable`, `modal`, `selected`, `editable`, `required`, `busy`: `false`
181///
182/// States that are inherently inapplicable use `Option`: `checked` is `None`
183/// for non-checkable elements, `expanded` is `None` for non-expandable elements.
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185pub struct StateSet {
186    pub enabled: bool,
187    pub visible: bool,
188    pub focused: bool,
189    /// None = not checkable
190    pub checked: Option<Toggled>,
191    pub selected: bool,
192    /// None = not expandable
193    pub expanded: Option<bool>,
194    pub editable: bool,
195    /// Whether the element can receive keyboard focus
196    pub focusable: bool,
197    /// Whether the element is a modal dialog
198    pub modal: bool,
199    /// Form field required
200    pub required: bool,
201    /// Async operation in progress
202    pub busy: bool,
203}
204
205impl Default for StateSet {
206    fn default() -> Self {
207        Self {
208            enabled: true,
209            visible: true,
210            focused: false,
211            checked: None,
212            selected: false,
213            expanded: None,
214            editable: false,
215            focusable: false,
216            modal: false,
217            required: false,
218            busy: false,
219        }
220    }
221}
222
223/// Tri-state toggle value.
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
225pub enum Toggled {
226    Off,
227    On,
228    /// Indeterminate / tri-state
229    Mixed,
230}
231
232/// Screen-pixel bounding rectangle (origin + size).
233/// `x`/`y` are signed to support negative multi-monitor coordinates.
234/// `width`/`height` are unsigned (always non-negative).
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
236pub struct Rect {
237    pub x: i32,
238    pub y: i32,
239    pub width: u32,
240    pub height: u32,
241}
242
243/// Platform-specific raw data attached to every element.
244///
245/// An untyped key-value map containing the original platform-specific data
246/// exactly as the platform reported it. Keys use `snake_case` naming. This is
247/// the escape hatch for consumers who need full platform fidelity.
248pub type RawPlatformData = HashMap<String, serde_json::Value>;