Skip to main content

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}