xa11y_core/provider.rs
1use crate::element::ElementData;
2use crate::error::Result;
3use crate::event_provider::Subscription;
4use crate::selector::{matches_simple, Combinator, Selector, SelectorGroup, SelectorSegment};
5
6/// Platform backend trait for accessibility tree access.
7///
8/// Providers implement lazy, on-demand tree navigation. Elements are identified
9/// by their [`ElementData`] (which contains a provider-specific `handle` for
10/// looking up the underlying platform object).
11///
12/// # Action model
13///
14/// Common actions are first-class methods with proper typed signatures.
15/// Platform-specific or custom actions use [`perform_action`](Self::perform_action)
16/// as an escape hatch — it takes a `snake_case` action name string.
17///
18/// Providers should check platform permissions in their constructor (`new()`)
19/// and return [`Error::PermissionDenied`](crate::Error::PermissionDenied) if
20/// required permissions are not granted.
21pub trait Provider: Send + Sync {
22 // ── Tree navigation ─────────────────────────────────────────────
23
24 /// Get direct children of an element.
25 ///
26 /// If `element` is `None`, returns top-level application elements.
27 fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>>;
28
29 /// Get the parent of an element.
30 ///
31 /// Returns `None` for top-level (application) elements.
32 fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>>;
33
34 /// Enumerate top-level applications visible to this provider.
35 ///
36 /// Backends return one `ElementData` per application — typically with
37 /// `role=Application`, but Windows returns `role=Window` for top-level
38 /// HWNDs because UIA exposes applications as their top-level window.
39 /// This is the dedicated discovery primitive: it replaces the previous
40 /// `find_elements(None, application_selector, .., depth=0)` idiom and
41 /// lets each backend batch the platform-specific enumeration (CGWindowList
42 /// on macOS, the AT-SPI registry on Linux, UIA's desktop root on Windows)
43 /// in one place.
44 ///
45 /// Required: every `Provider` implements this explicitly. There's no
46 /// default impl — discovery and subtree search have different cost
47 /// profiles, and silently routing app discovery through `get_children`
48 /// would hide the difference from implementors.
49 fn list_apps(&self) -> Result<Vec<ElementData>>;
50
51 /// Search for elements matching a selector.
52 ///
53 /// The selector is already parsed by the core — providers match against it
54 /// during traversal and can prune subtrees that can't match.
55 ///
56 /// `root` is the subtree root to search under (always present; use
57 /// [`list_apps`](Self::list_apps) to discover application roots first).
58 /// If `limit` is `Some(n)`, stops after finding `n` matches.
59 /// If `max_depth` is `Some(d)`, does not descend deeper than `d` levels.
60 ///
61 /// This is a thin convenience wrapper that re-uses
62 /// [`find_elements_group`](Self::find_elements_group) with a single-clause
63 /// group. Backends do **not** override this method — they override
64 /// `find_elements_group`, and the single-clause case runs through the
65 /// same native code path as multi-clause queries.
66 fn find_elements(
67 &self,
68 root: &ElementData,
69 selector: &Selector,
70 limit: Option<usize>,
71 max_depth: Option<u32>,
72 ) -> Result<Vec<ElementData>> {
73 let group = SelectorGroup {
74 clauses: vec![selector.clone()],
75 };
76 self.find_elements_group(root, &group, limit, max_depth)
77 }
78
79 /// Search for elements matching any clause of a comma-separated selector
80 /// group, scoped to `root`'s subtree.
81 ///
82 /// This is the **primary search primitive** each backend should override.
83 /// A native implementation performs ONE platform-level subtree query/walk
84 /// (e.g. `FindAllBuildCache(TreeScope_Subtree)` on Windows, one DFS over
85 /// AT-SPI children on Linux, one AX walk on macOS) and evaluates every
86 /// clause inline against each visited element. This avoids the
87 /// per-clause-walk perf cliff and also keeps the cross-clause dedup
88 /// correct: because everything happens inside one call, each platform
89 /// node is visited once and platform identity is stable.
90 ///
91 /// `root` is non-optional: app discovery now goes through
92 /// [`list_apps`](Self::list_apps) and any rootless multi-app search is
93 /// performed by the caller (e.g. `Locator`) by enumerating apps and
94 /// invoking this method per app.
95 ///
96 /// The default implementation traverses via [`get_children`](Self::get_children)
97 /// and uses tree-path identity for cross-clause merging. It's the
98 /// fallback for backends that haven't shipped a native override yet —
99 /// the same path the test/mock provider exercises.
100 fn find_elements_group(
101 &self,
102 root: &ElementData,
103 group: &SelectorGroup,
104 limit: Option<usize>,
105 max_depth: Option<u32>,
106 ) -> Result<Vec<ElementData>> {
107 crate::selector::find_elements_in_tree_group(
108 |el| self.get_children(el),
109 Some(root),
110 group,
111 limit,
112 max_depth,
113 )
114 }
115
116 /// Narrow candidates through remaining selector segments (Child/Descendant
117 /// combinators), deduplicate, apply final :nth and limit.
118 fn narrow_multi_segment(
119 &self,
120 mut candidates: Vec<ElementData>,
121 segments: &[SelectorSegment],
122 max_depth: u32,
123 limit: Option<usize>,
124 ) -> Result<Vec<ElementData>> {
125 for segment in segments {
126 let mut next_candidates = Vec::new();
127 for candidate in &candidates {
128 match segment.combinator {
129 Combinator::Child => {
130 let children = self.get_children(Some(candidate))?;
131 for child in children {
132 if matches_simple(&child, &segment.simple) {
133 next_candidates.push(child);
134 }
135 }
136 }
137 Combinator::Descendant => {
138 let sub_selector = Selector {
139 segments: vec![SelectorSegment {
140 combinator: Combinator::Root,
141 simple: segment.simple.clone(),
142 }],
143 };
144 let mut sub_results =
145 self.find_elements(candidate, &sub_selector, None, Some(max_depth))?;
146 next_candidates.append(&mut sub_results);
147 }
148 Combinator::Root => unreachable!(),
149 }
150 }
151 let mut seen = std::collections::HashSet::new();
152 next_candidates.retain(|e| seen.insert(e.handle));
153 candidates = next_candidates;
154 }
155
156 // Apply :nth on last segment
157 if let Some(nth) = segments.last().and_then(|s| s.simple.nth) {
158 if nth <= candidates.len() {
159 candidates = vec![candidates.remove(nth - 1)];
160 } else {
161 candidates.clear();
162 }
163 }
164
165 if let Some(limit) = limit {
166 candidates.truncate(limit);
167 }
168
169 Ok(candidates)
170 }
171
172 // ── Common actions ──────────────────────────────────────────────
173
174 /// Click / tap / invoke the element.
175 fn press(&self, element: &ElementData) -> Result<()>;
176
177 /// Set keyboard focus to the element.
178 fn focus(&self, element: &ElementData) -> Result<()>;
179
180 /// Remove keyboard focus from the element.
181 fn blur(&self, element: &ElementData) -> Result<()>;
182
183 /// Toggle a checkbox or switch.
184 fn toggle(&self, element: &ElementData) -> Result<()>;
185
186 /// Select an item in a list, tab group, or menu.
187 fn select(&self, element: &ElementData) -> Result<()>;
188
189 /// Expand a collapsible element (combo box, tree item, disclosure).
190 fn expand(&self, element: &ElementData) -> Result<()>;
191
192 /// Collapse an expanded element.
193 fn collapse(&self, element: &ElementData) -> Result<()>;
194
195 /// Show the element's context menu or dropdown.
196 fn show_menu(&self, element: &ElementData) -> Result<()>;
197
198 /// Increment a slider or spinner by one step.
199 fn increment(&self, element: &ElementData) -> Result<()>;
200
201 /// Decrement a slider or spinner by one step.
202 fn decrement(&self, element: &ElementData) -> Result<()>;
203
204 /// Scroll the element into the visible area.
205 fn scroll_into_view(&self, element: &ElementData) -> Result<()>;
206
207 // ── Typed operations ────────────────────────────────────────────
208
209 /// Set the text value of the element.
210 fn set_value(&self, element: &ElementData, value: &str) -> Result<()>;
211
212 /// Set the numeric value of the element (slider, spinner).
213 fn set_numeric_value(&self, element: &ElementData, value: f64) -> Result<()>;
214
215 /// Insert text at the current cursor position.
216 fn type_text(&self, element: &ElementData, text: &str) -> Result<()>;
217
218 /// Select a text range (0-based character offsets).
219 fn set_text_selection(&self, element: &ElementData, start: u32, end: u32) -> Result<()>;
220
221 // ── Generic action escape hatch ─────────────────────────────────
222
223 /// Perform an action by `snake_case` name.
224 ///
225 /// This is the escape hatch for platform-specific actions not covered by
226 /// the first-class methods above. The provider converts the name to the
227 /// platform's convention (e.g. `"custom_thing"` → `"AXCustomThing"` on
228 /// macOS) and invokes it.
229 ///
230 /// Well-known action names (`"press"`, `"focus"`, etc.) should also work
231 /// here — providers should delegate to the corresponding method.
232 fn perform_action(&self, element: &ElementData, action: &str) -> Result<()>;
233
234 // ── Events ──────────────────────────────────────────────────────
235
236 /// Subscribe to all accessibility events for an application.
237 ///
238 /// The element should be an application-level element (role=Application).
239 /// The provider extracts the PID from `element.pid`.
240 ///
241 /// Returns a [`Subscription`] that receives events until dropped.
242 fn subscribe(&self, element: &ElementData) -> Result<Subscription>;
243}
244
245// Blanket impl so shared references to a provider act as providers themselves.
246// Used by the umbrella crate's singleton (a `&'static dyn Provider` wrapped in
247// `Arc<_>`) and by any caller that wants to share a provider via `&T`. The
248// orphan rules keep this collision-free for downstream crates because `xa11y-core`
249// owns the `Provider` trait.
250impl<T: Provider + ?Sized> Provider for &T {
251 fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>> {
252 (**self).get_children(element)
253 }
254 fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>> {
255 (**self).get_parent(element)
256 }
257 fn list_apps(&self) -> Result<Vec<ElementData>> {
258 (**self).list_apps()
259 }
260 fn find_elements(
261 &self,
262 root: &ElementData,
263 selector: &Selector,
264 limit: Option<usize>,
265 max_depth: Option<u32>,
266 ) -> Result<Vec<ElementData>> {
267 (**self).find_elements(root, selector, limit, max_depth)
268 }
269 fn find_elements_group(
270 &self,
271 root: &ElementData,
272 group: &SelectorGroup,
273 limit: Option<usize>,
274 max_depth: Option<u32>,
275 ) -> Result<Vec<ElementData>> {
276 (**self).find_elements_group(root, group, limit, max_depth)
277 }
278 fn narrow_multi_segment(
279 &self,
280 candidates: Vec<ElementData>,
281 segments: &[SelectorSegment],
282 max_depth: u32,
283 limit: Option<usize>,
284 ) -> Result<Vec<ElementData>> {
285 (**self).narrow_multi_segment(candidates, segments, max_depth, limit)
286 }
287 fn press(&self, element: &ElementData) -> Result<()> {
288 (**self).press(element)
289 }
290 fn focus(&self, element: &ElementData) -> Result<()> {
291 (**self).focus(element)
292 }
293 fn blur(&self, element: &ElementData) -> Result<()> {
294 (**self).blur(element)
295 }
296 fn toggle(&self, element: &ElementData) -> Result<()> {
297 (**self).toggle(element)
298 }
299 fn select(&self, element: &ElementData) -> Result<()> {
300 (**self).select(element)
301 }
302 fn expand(&self, element: &ElementData) -> Result<()> {
303 (**self).expand(element)
304 }
305 fn collapse(&self, element: &ElementData) -> Result<()> {
306 (**self).collapse(element)
307 }
308 fn show_menu(&self, element: &ElementData) -> Result<()> {
309 (**self).show_menu(element)
310 }
311 fn increment(&self, element: &ElementData) -> Result<()> {
312 (**self).increment(element)
313 }
314 fn decrement(&self, element: &ElementData) -> Result<()> {
315 (**self).decrement(element)
316 }
317 fn scroll_into_view(&self, element: &ElementData) -> Result<()> {
318 (**self).scroll_into_view(element)
319 }
320 fn set_value(&self, element: &ElementData, value: &str) -> Result<()> {
321 (**self).set_value(element, value)
322 }
323 fn set_numeric_value(&self, element: &ElementData, value: f64) -> Result<()> {
324 (**self).set_numeric_value(element, value)
325 }
326 fn type_text(&self, element: &ElementData, text: &str) -> Result<()> {
327 (**self).type_text(element, text)
328 }
329 fn set_text_selection(&self, element: &ElementData, start: u32, end: u32) -> Result<()> {
330 (**self).set_text_selection(element, start, end)
331 }
332 fn perform_action(&self, element: &ElementData, action: &str) -> Result<()> {
333 (**self).perform_action(element, action)
334 }
335 fn subscribe(&self, element: &ElementData) -> Result<Subscription> {
336 (**self).subscribe(element)
337 }
338}