Skip to main content

xa11y_core/
provider.rs

1use crate::element::ElementData;
2use crate::error::{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    /// Find the application owning process `pid` — one attempt, no polling.
52    ///
53    /// This is the single-shot discovery primitive behind `App::by_pid`; the
54    /// core's lookup loop owns the retry/timeout policy. Returns
55    /// [`Error::SelectorNotMatched`] when no accessible application for `pid`
56    /// exists *yet* — the signal the poll loop retries on — and any other
57    /// error when the lookup itself failed (permissions, platform API
58    /// failure), which short-circuits the poll.
59    ///
60    /// The default implementation filters [`list_apps`](Self::list_apps) by
61    /// `pid`, and its not-matched error carries enumeration diagnostics (how
62    /// many apps were visible and how many had no resolvable pid) so a
63    /// timeout on an opaque CI runner says *why* the app was invisible.
64    /// Backends override this where the platform supports reaching a process
65    /// directly (`AXUIElementCreateApplication` on macOS, a UIA `ProcessId`
66    /// property search on Windows): direct attach is immune to enumeration
67    /// blind spots such as top-level windows that are still unnamed while the
68    /// app starts up.
69    fn app_by_pid(&self, pid: u32) -> Result<ElementData> {
70        let apps = self.list_apps()?;
71        let total = apps.len();
72        let unresolved = apps.iter().filter(|a| a.pid.is_none()).count();
73        if let Some(data) = apps.into_iter().find(|a| a.pid == Some(pid)) {
74            return Ok(data);
75        }
76        // The enumeration counts are structured diagnosis fields (tenet 6),
77        // not prose baked into the selector — bindings expose them as
78        // attributes, and `App::by_pid_with` merges in the full candidate
79        // list at its terminal failure site. Cheap to build (two counters),
80        // so attaching it per poll tick is fine.
81        Err(
82            Error::selector_not_matched(format!("application[pid={pid}]")).diagnose(
83                crate::error::Diagnosis {
84                    last_observed: Some(format!(
85                        "enumerated {total} running apps, {unresolved} without a resolvable pid"
86                    )),
87                    ..Default::default()
88                },
89            ),
90        )
91    }
92
93    /// Search for elements matching a selector.
94    ///
95    /// The selector is already parsed by the core — providers match against it
96    /// during traversal and can prune subtrees that can't match.
97    ///
98    /// `root` is the subtree root to search under (always present; use
99    /// [`list_apps`](Self::list_apps) to discover application roots first).
100    /// If `limit` is `Some(n)`, stops after finding `n` matches.
101    /// If `max_depth` is `Some(d)`, does not descend deeper than `d` levels.
102    ///
103    /// This is a thin convenience wrapper that re-uses
104    /// [`find_elements_group`](Self::find_elements_group) with a single-clause
105    /// group. Backends do **not** override this method — they override
106    /// `find_elements_group`, and the single-clause case runs through the
107    /// same native code path as multi-clause queries.
108    fn find_elements(
109        &self,
110        root: &ElementData,
111        selector: &Selector,
112        limit: Option<usize>,
113        max_depth: Option<u32>,
114    ) -> Result<Vec<ElementData>> {
115        let group = SelectorGroup {
116            clauses: vec![selector.clone()],
117        };
118        self.find_elements_group(root, &group, limit, max_depth)
119    }
120
121    /// Search for elements matching any clause of a comma-separated selector
122    /// group, scoped to `root`'s subtree.
123    ///
124    /// This is the **primary search primitive** each backend should override.
125    /// A native implementation performs ONE platform-level subtree query/walk
126    /// (e.g. `FindAllBuildCache(TreeScope_Subtree)` on Windows, one DFS over
127    /// AT-SPI children on Linux, one AX walk on macOS) and evaluates every
128    /// clause inline against each visited element. This avoids the
129    /// per-clause-walk perf cliff and also keeps the cross-clause dedup
130    /// correct: because everything happens inside one call, each platform
131    /// node is visited once and platform identity is stable.
132    ///
133    /// `root` is non-optional: app discovery now goes through
134    /// [`list_apps`](Self::list_apps) and any rootless multi-app search is
135    /// performed by the caller (e.g. `Locator`) by enumerating apps and
136    /// invoking this method per app.
137    ///
138    /// The default implementation traverses via [`get_children`](Self::get_children)
139    /// and uses tree-path identity for cross-clause merging. It's the
140    /// fallback for backends that haven't shipped a native override yet —
141    /// the same path the test/mock provider exercises.
142    fn find_elements_group(
143        &self,
144        root: &ElementData,
145        group: &SelectorGroup,
146        limit: Option<usize>,
147        max_depth: Option<u32>,
148    ) -> Result<Vec<ElementData>> {
149        crate::selector::find_elements_in_tree_group(
150            |el| self.get_children(el),
151            Some(root),
152            group,
153            limit,
154            max_depth,
155        )
156    }
157
158    /// Narrow candidates through remaining selector segments (Child/Descendant
159    /// combinators), deduplicate, apply final :nth and limit.
160    fn narrow_multi_segment(
161        &self,
162        mut candidates: Vec<ElementData>,
163        segments: &[SelectorSegment],
164        max_depth: u32,
165        limit: Option<usize>,
166    ) -> Result<Vec<ElementData>> {
167        for segment in segments {
168            let mut next_candidates = Vec::new();
169            for candidate in &candidates {
170                match segment.combinator {
171                    Combinator::Child => {
172                        let children = self.get_children(Some(candidate))?;
173                        for child in children {
174                            if matches_simple(&child, &segment.simple) {
175                                next_candidates.push(child);
176                            }
177                        }
178                    }
179                    Combinator::Descendant => {
180                        let sub_selector = Selector {
181                            segments: vec![SelectorSegment {
182                                combinator: Combinator::Root,
183                                simple: segment.simple.clone(),
184                            }],
185                        };
186                        let mut sub_results =
187                            self.find_elements(candidate, &sub_selector, None, Some(max_depth))?;
188                        next_candidates.append(&mut sub_results);
189                    }
190                    Combinator::Root => unreachable!(),
191                }
192            }
193            let mut seen = std::collections::HashSet::new();
194            next_candidates.retain(|e| seen.insert(e.handle));
195            candidates = next_candidates;
196        }
197
198        // Apply :nth on last segment
199        if let Some(nth) = segments.last().and_then(|s| s.simple.nth) {
200            if nth <= candidates.len() {
201                candidates = vec![candidates.remove(nth - 1)];
202            } else {
203                candidates.clear();
204            }
205        }
206
207        if let Some(limit) = limit {
208            candidates.truncate(limit);
209        }
210
211        Ok(candidates)
212    }
213
214    // ── Common actions ──────────────────────────────────────────────
215
216    /// Click / tap / invoke the element.
217    fn press(&self, element: &ElementData) -> Result<()>;
218
219    /// Set keyboard focus to the element.
220    fn focus(&self, element: &ElementData) -> Result<()>;
221
222    /// Remove keyboard focus from the element.
223    fn blur(&self, element: &ElementData) -> Result<()>;
224
225    /// Toggle a checkbox or switch.
226    fn toggle(&self, element: &ElementData) -> Result<()>;
227
228    /// Select an item in a list, tab group, or menu.
229    fn select(&self, element: &ElementData) -> Result<()>;
230
231    /// Expand a collapsible element (combo box, tree item, disclosure).
232    fn expand(&self, element: &ElementData) -> Result<()>;
233
234    /// Collapse an expanded element.
235    fn collapse(&self, element: &ElementData) -> Result<()>;
236
237    /// Show the element's context menu or dropdown.
238    fn show_menu(&self, element: &ElementData) -> Result<()>;
239
240    /// Increment a slider or spinner by one step.
241    fn increment(&self, element: &ElementData) -> Result<()>;
242
243    /// Decrement a slider or spinner by one step.
244    fn decrement(&self, element: &ElementData) -> Result<()>;
245
246    /// Scroll the element into the visible area.
247    fn scroll_into_view(&self, element: &ElementData) -> Result<()>;
248
249    // ── Typed operations ────────────────────────────────────────────
250
251    /// Set the text value of the element.
252    fn set_value(&self, element: &ElementData, value: &str) -> Result<()>;
253
254    /// Set the numeric value of the element (slider, spinner).
255    fn set_numeric_value(&self, element: &ElementData, value: f64) -> Result<()>;
256
257    /// Insert text at the current cursor position.
258    fn type_text(&self, element: &ElementData, text: &str) -> Result<()>;
259
260    /// Select a text range (0-based character offsets).
261    fn set_text_selection(&self, element: &ElementData, start: u32, end: u32) -> Result<()>;
262
263    // ── Generic action escape hatch ─────────────────────────────────
264
265    /// Perform an action by `snake_case` name.
266    ///
267    /// This is the escape hatch for platform-specific actions not covered by
268    /// the first-class methods above. The provider converts the name to the
269    /// platform's convention (e.g. `"custom_thing"` → `"AXCustomThing"` on
270    /// macOS) and invokes it.
271    ///
272    /// Well-known action names (`"press"`, `"focus"`, etc.) should also work
273    /// here — providers should delegate to the corresponding method.
274    fn perform_action(&self, element: &ElementData, action: &str) -> Result<()>;
275
276    // ── Events ──────────────────────────────────────────────────────
277
278    /// Subscribe to all accessibility events for an application.
279    ///
280    /// The element should be an application-level element (role=Application).
281    /// The provider extracts the PID from `element.pid`.
282    ///
283    /// Returns a [`Subscription`] that receives events until dropped.
284    fn subscribe(&self, element: &ElementData) -> Result<Subscription>;
285}
286
287// Blanket impl so shared references to a provider act as providers themselves.
288// Used by the umbrella crate's singleton (a `&'static dyn Provider` wrapped in
289// `Arc<_>`) and by any caller that wants to share a provider via `&T`. The
290// orphan rules keep this collision-free for downstream crates because `xa11y-core`
291// owns the `Provider` trait.
292impl<T: Provider + ?Sized> Provider for &T {
293    fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>> {
294        (**self).get_children(element)
295    }
296    fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>> {
297        (**self).get_parent(element)
298    }
299    fn list_apps(&self) -> Result<Vec<ElementData>> {
300        (**self).list_apps()
301    }
302    // Delegated explicitly (despite having a default impl) so a concrete
303    // provider's PID-direct override isn't bypassed when it's used through a
304    // shared reference — the default body on `&T` would re-route through
305    // `list_apps` and silently lose the override.
306    fn app_by_pid(&self, pid: u32) -> Result<ElementData> {
307        (**self).app_by_pid(pid)
308    }
309    fn find_elements(
310        &self,
311        root: &ElementData,
312        selector: &Selector,
313        limit: Option<usize>,
314        max_depth: Option<u32>,
315    ) -> Result<Vec<ElementData>> {
316        (**self).find_elements(root, selector, limit, max_depth)
317    }
318    fn find_elements_group(
319        &self,
320        root: &ElementData,
321        group: &SelectorGroup,
322        limit: Option<usize>,
323        max_depth: Option<u32>,
324    ) -> Result<Vec<ElementData>> {
325        (**self).find_elements_group(root, group, limit, max_depth)
326    }
327    fn narrow_multi_segment(
328        &self,
329        candidates: Vec<ElementData>,
330        segments: &[SelectorSegment],
331        max_depth: u32,
332        limit: Option<usize>,
333    ) -> Result<Vec<ElementData>> {
334        (**self).narrow_multi_segment(candidates, segments, max_depth, limit)
335    }
336    fn press(&self, element: &ElementData) -> Result<()> {
337        (**self).press(element)
338    }
339    fn focus(&self, element: &ElementData) -> Result<()> {
340        (**self).focus(element)
341    }
342    fn blur(&self, element: &ElementData) -> Result<()> {
343        (**self).blur(element)
344    }
345    fn toggle(&self, element: &ElementData) -> Result<()> {
346        (**self).toggle(element)
347    }
348    fn select(&self, element: &ElementData) -> Result<()> {
349        (**self).select(element)
350    }
351    fn expand(&self, element: &ElementData) -> Result<()> {
352        (**self).expand(element)
353    }
354    fn collapse(&self, element: &ElementData) -> Result<()> {
355        (**self).collapse(element)
356    }
357    fn show_menu(&self, element: &ElementData) -> Result<()> {
358        (**self).show_menu(element)
359    }
360    fn increment(&self, element: &ElementData) -> Result<()> {
361        (**self).increment(element)
362    }
363    fn decrement(&self, element: &ElementData) -> Result<()> {
364        (**self).decrement(element)
365    }
366    fn scroll_into_view(&self, element: &ElementData) -> Result<()> {
367        (**self).scroll_into_view(element)
368    }
369    fn set_value(&self, element: &ElementData, value: &str) -> Result<()> {
370        (**self).set_value(element, value)
371    }
372    fn set_numeric_value(&self, element: &ElementData, value: f64) -> Result<()> {
373        (**self).set_numeric_value(element, value)
374    }
375    fn type_text(&self, element: &ElementData, text: &str) -> Result<()> {
376        (**self).type_text(element, text)
377    }
378    fn set_text_selection(&self, element: &ElementData, start: u32, end: u32) -> Result<()> {
379        (**self).set_text_selection(element, start, end)
380    }
381    fn perform_action(&self, element: &ElementData, action: &str) -> Result<()> {
382        (**self).perform_action(element, action)
383    }
384    fn subscribe(&self, element: &ElementData) -> Result<Subscription> {
385        (**self).subscribe(element)
386    }
387}