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    /// Full set of element attributes — both normalized properties and
65    /// platform-specific ones — keyed by `snake_case` names. Named properties
66    /// (name, value, enabled, etc.) also appear here.
67    #[serde(default)]
68    pub attributes: HashMap<String, serde_json::Value>,
69
70    /// Platform-specific raw data
71    pub raw: RawPlatformData,
72
73    /// Opaque handle for the provider to look up the platform object.
74    /// Not serialized — only valid within the provider that created it.
75    #[serde(skip, default)]
76    pub handle: u64,
77}
78
79impl ElementData {
80    /// Populate the `attributes` map from the struct's named properties.
81    /// Providers should call this after constructing `ElementData` to ensure
82    /// normalized attributes are present in the map.
83    pub fn populate_attributes(&mut self) {
84        use serde_json::Value;
85        let a = &mut self.attributes;
86
87        a.insert(
88            "role".into(),
89            Value::String(self.role.to_snake_case().to_string()),
90        );
91        if let Some(ref n) = self.name {
92            a.insert("name".into(), Value::String(n.clone()));
93        }
94        if let Some(ref v) = self.value {
95            a.insert("value".into(), Value::String(v.clone()));
96        }
97        if let Some(ref d) = self.description {
98            a.insert("description".into(), Value::String(d.clone()));
99        }
100        if let Some(ref b) = self.bounds {
101            a.insert(
102                "bounds".into(),
103                serde_json::json!({
104                    "x": b.x, "y": b.y, "width": b.width, "height": b.height
105                }),
106            );
107        }
108        if let Some(nv) = self.numeric_value {
109            if let Some(n) = serde_json::Number::from_f64(nv) {
110                a.insert("numeric_value".into(), Value::Number(n));
111            }
112        }
113        if let Some(nv) = self.min_value {
114            if let Some(n) = serde_json::Number::from_f64(nv) {
115                a.insert("min_value".into(), Value::Number(n));
116            }
117        }
118        if let Some(nv) = self.max_value {
119            if let Some(n) = serde_json::Number::from_f64(nv) {
120                a.insert("max_value".into(), Value::Number(n));
121            }
122        }
123        if let Some(ref sid) = self.stable_id {
124            a.insert("stable_id".into(), Value::String(sid.clone()));
125        }
126        a.insert("enabled".into(), Value::Bool(self.states.enabled));
127        a.insert("visible".into(), Value::Bool(self.states.visible));
128        a.insert("focused".into(), Value::Bool(self.states.focused));
129        a.insert("focusable".into(), Value::Bool(self.states.focusable));
130        a.insert("selected".into(), Value::Bool(self.states.selected));
131        a.insert("editable".into(), Value::Bool(self.states.editable));
132        a.insert("modal".into(), Value::Bool(self.states.modal));
133        a.insert("required".into(), Value::Bool(self.states.required));
134        a.insert("busy".into(), Value::Bool(self.states.busy));
135        if let Some(exp) = self.states.expanded {
136            a.insert("expanded".into(), Value::Bool(exp));
137        }
138        if let Some(ref chk) = self.states.checked {
139            let s = match chk {
140                Toggled::On => "on",
141                Toggled::Off => "off",
142                Toggled::Mixed => "mixed",
143            };
144            a.insert("checked".into(), Value::String(s.into()));
145        }
146    }
147}
148
149/// A live element with lazy navigation via a provider reference.
150///
151/// `Element` dereferences to [`ElementData`], so all properties (`role`, `name`,
152/// `value`, `states`, etc.) are accessible via field access. Navigation
153/// methods (`parent()`, `children()`) call the provider on demand.
154///
155/// Elements are cheap to clone (they share the provider via `Arc`).
156#[derive(Clone)]
157pub struct Element {
158    data: ElementData,
159    provider: Arc<dyn Provider>,
160}
161
162impl Deref for Element {
163    type Target = ElementData;
164
165    fn deref(&self) -> &ElementData {
166        &self.data
167    }
168}
169
170impl fmt::Debug for Element {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        fmt::Debug::fmt(&self.data, f)
173    }
174}
175
176impl fmt::Display for Element {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        let name_part = self
179            .data
180            .name
181            .as_ref()
182            .map(|n| format!(" \"{}\"", n))
183            .unwrap_or_default();
184        let value_part = self
185            .data
186            .value
187            .as_ref()
188            .map(|v| format!(" value=\"{}\"", v))
189            .unwrap_or_default();
190        write!(
191            f,
192            "{}{}{}",
193            self.data.role.to_snake_case(),
194            name_part,
195            value_part,
196        )
197    }
198}
199
200impl Serialize for Element {
201    fn serialize<S: serde::Serializer>(
202        &self,
203        serializer: S,
204    ) -> std::result::Result<S::Ok, S::Error> {
205        self.data.serialize(serializer)
206    }
207}
208
209impl Element {
210    /// Create an Element from raw data and a provider reference.
211    pub fn new(data: ElementData, provider: Arc<dyn Provider>) -> Self {
212        Self { data, provider }
213    }
214
215    /// Get the underlying ElementData.
216    pub fn data(&self) -> &ElementData {
217        &self.data
218    }
219
220    /// Get the provider reference.
221    pub fn provider(&self) -> &Arc<dyn Provider> {
222        &self.provider
223    }
224
225    /// Get direct children of this element.
226    ///
227    /// Each call queries the provider — results are not cached.
228    pub fn children(&self) -> crate::error::Result<Vec<Element>> {
229        let children = self.provider.get_children(Some(&self.data))?;
230        Ok(children
231            .into_iter()
232            .map(|d| Element::new(d, Arc::clone(&self.provider)))
233            .collect())
234    }
235
236    /// Get the parent element, if any (root-level elements have no parent).
237    ///
238    /// Each call queries the provider — results are not cached.
239    pub fn parent(&self) -> crate::error::Result<Option<Element>> {
240        let parent = self.provider.get_parent(&self.data)?;
241        Ok(parent.map(|d| Element::new(d, Arc::clone(&self.provider))))
242    }
243
244    /// Get the process ID from the element data.
245    pub fn pid(&self) -> Option<u32> {
246        self.data.pid
247    }
248}
249
250/// Boolean state flags for an element.
251///
252/// **Semantics for non-applicable states:** When a state doesn't apply to an
253/// element's role, the backend uses the platform's reported value or defaults:
254/// - `enabled`: `true` (elements are enabled unless explicitly disabled)
255/// - `visible`: `true` (elements are visible unless explicitly hidden/offscreen)
256/// - `focused`, `focusable`, `modal`, `selected`, `editable`, `required`, `busy`: `false`
257///
258/// States that are inherently inapplicable use `Option`: `checked` is `None`
259/// for non-checkable elements, `expanded` is `None` for non-expandable elements.
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261pub struct StateSet {
262    pub enabled: bool,
263    pub visible: bool,
264    pub focused: bool,
265    /// None = not checkable
266    pub checked: Option<Toggled>,
267    pub selected: bool,
268    /// None = not expandable
269    pub expanded: Option<bool>,
270    pub editable: bool,
271    /// Whether the element can receive keyboard focus
272    pub focusable: bool,
273    /// Whether the element is a modal dialog
274    pub modal: bool,
275    /// Form field required
276    pub required: bool,
277    /// Async operation in progress
278    pub busy: bool,
279}
280
281impl Default for StateSet {
282    fn default() -> Self {
283        Self {
284            enabled: true,
285            visible: true,
286            focused: false,
287            checked: None,
288            selected: false,
289            expanded: None,
290            editable: false,
291            focusable: false,
292            modal: false,
293            required: false,
294            busy: false,
295        }
296    }
297}
298
299/// Tri-state toggle value.
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
301pub enum Toggled {
302    Off,
303    On,
304    /// Indeterminate / tri-state
305    Mixed,
306}
307
308/// Screen-pixel bounding rectangle (origin + size).
309/// `x`/`y` are signed to support negative multi-monitor coordinates.
310/// `width`/`height` are unsigned (always non-negative).
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
312pub struct Rect {
313    pub x: i32,
314    pub y: i32,
315    pub width: u32,
316    pub height: u32,
317}
318
319/// Platform-specific raw data attached to every element.
320///
321/// An untyped key-value map containing the original platform-specific data
322/// exactly as the platform reported it. Keys use `snake_case` naming. This is
323/// the escape hatch for consumers who need full platform fidelity.
324pub type RawPlatformData = HashMap<String, serde_json::Value>;