Skip to main content

xa11y_core/
element.rs

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