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>;