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};
5use crate::shell::ShellSurfaceKind;
6
7/// Platform backend trait for accessibility tree access.
8///
9/// Providers implement lazy, on-demand tree navigation. Elements are identified
10/// by their [`ElementData`] (which contains a provider-specific `handle` for
11/// looking up the underlying platform object).
12///
13/// # Action model
14///
15/// Common actions are first-class methods with proper typed signatures.
16/// Platform-specific or custom actions use [`perform_action`](Self::perform_action)
17/// as an escape hatch — it takes a `snake_case` action name string.
18///
19/// Providers should check platform permissions in their constructor (`new()`)
20/// and return [`Error::PermissionDenied`](crate::Error::PermissionDenied) if
21/// required permissions are not granted.
22pub trait Provider: Send + Sync {
23    // ── Tree navigation ─────────────────────────────────────────────
24
25    /// Get direct children of an element.
26    ///
27    /// If `element` is `None`, returns top-level application elements.
28    fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>>;
29
30    /// Get the parent of an element.
31    ///
32    /// Returns `None` for top-level (application) elements.
33    fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>>;
34
35    /// Enumerate top-level applications visible to this provider.
36    ///
37    /// Backends return one `ElementData` per application — typically with
38    /// `role=Application`, but Windows returns `role=Window` for top-level
39    /// HWNDs because UIA exposes applications as their top-level window. On
40    /// Windows a multi-window process therefore yields one entry per top-level
41    /// window (main window + modal dialog are separate entries; issue #304),
42    /// whereas macOS and Linux return one `Application` element per process.
43    /// This is the dedicated discovery primitive: it replaces the previous
44    /// `find_elements(None, application_selector, .., depth=0)` idiom and
45    /// lets each backend batch the platform-specific enumeration (CGWindowList
46    /// on macOS, the AT-SPI registry on Linux, UIA's desktop root on Windows)
47    /// in one place.
48    ///
49    /// Required: every `Provider` implements this explicitly. There's no
50    /// default impl — discovery and subtree search have different cost
51    /// profiles, and silently routing app discovery through `get_children`
52    /// would hide the difference from implementors.
53    fn list_apps(&self) -> Result<Vec<ElementData>>;
54
55    /// Find the application owning process `pid` — one attempt, no polling.
56    ///
57    /// This is the single-shot discovery primitive behind `App::by_pid`; the
58    /// core's lookup loop owns the retry/timeout policy. Returns
59    /// [`Error::SelectorNotMatched`] when no accessible application for `pid`
60    /// exists *yet* — the signal the poll loop retries on — and any other
61    /// error when the lookup itself failed (permissions, platform API
62    /// failure), which short-circuits the poll.
63    ///
64    /// The default implementation filters [`list_apps`](Self::list_apps) by
65    /// `pid`, and its not-matched error carries enumeration diagnostics (how
66    /// many apps were visible and how many had no resolvable pid) so a
67    /// timeout on an opaque CI runner says *why* the app was invisible.
68    /// Backends override this where the platform supports reaching a process
69    /// directly (`AXUIElementCreateApplication` on macOS, a UIA `ProcessId`
70    /// property search on Windows): direct attach is immune to enumeration
71    /// blind spots such as top-level windows that are still unnamed while the
72    /// app starts up.
73    fn app_by_pid(&self, pid: u32) -> Result<ElementData> {
74        let apps = self.list_apps()?;
75        let total = apps.len();
76        let unresolved = apps.iter().filter(|a| a.pid.is_none()).count();
77        if let Some(data) = apps.into_iter().find(|a| a.pid == Some(pid)) {
78            return Ok(data);
79        }
80        // The enumeration counts are structured diagnosis fields (tenet 6),
81        // not prose baked into the selector — bindings expose them as
82        // attributes, and `App::by_pid_with` merges in the full candidate
83        // list at its terminal failure site. Cheap to build (two counters),
84        // so attaching it per poll tick is fine.
85        Err(
86            Error::selector_not_matched(format!("application[pid={pid}]")).diagnose(
87                crate::error::Diagnosis {
88                    last_observed: Some(format!(
89                        "enumerated {total} running apps, {unresolved} without a resolvable pid"
90                    )),
91                    ..Default::default()
92                },
93            ),
94        )
95    }
96
97    /// Identify the application that currently holds the system foreground /
98    /// input focus — one attempt, no polling.
99    ///
100    /// Each backend uses its platform's canonical foreground mechanism: the
101    /// system-wide `AXUIElement`'s focused-application attribute (macOS),
102    /// `GetForegroundWindow` + `ElementFromHandle` (Windows), and the focused
103    /// element's `Application` ancestor in the AT-SPI registry (Linux). The
104    /// returned `ElementData` has the same shape [`list_apps`](Self::list_apps)
105    /// produces for that app — in particular its `pid` lines up — so the core
106    /// can tag the foreground entry within an enumeration by pid.
107    ///
108    /// Returns [`Error::SelectorNotMatched`] when no application currently
109    /// holds focus (e.g. focus rests on the desktop / shell, or the screen is
110    /// locked). That is a legitimate state, not a failure: the core treats it
111    /// as "nothing is foreground" — every listed app stays untagged — rather
112    /// than surfacing an error. Any other error (permission, platform API
113    /// failure) is a real failure and propagates.
114    ///
115    /// Required: every `Provider` implements this explicitly. Like
116    /// [`list_apps`](Self::list_apps) there is no default impl — there is no
117    /// portable way to derive the foreground app, so a silent no-op default
118    /// would hide an unimplemented backend (tenet 1).
119    fn focused_app(&self) -> Result<ElementData>;
120
121    /// Enumerate OS shell surfaces — taskbars, panels, docks, menu bars,
122    /// status items, the desktop, and any transient shell flyout currently
123    /// open. One entry per surface, each a real platform element usable as a
124    /// search root.
125    ///
126    /// The listing is live: transient surfaces
127    /// ([`ShellSurfaceKind::Flyout`]) appear only while they are on screen,
128    /// and enumerating NEVER opens, closes, or presses anything. Required —
129    /// no default impl, for the same reason [`list_apps`](Self::list_apps)
130    /// has none: enumeration is platform-specific, and a silent empty default
131    /// would hide an unimplemented backend (tenet 1).
132    ///
133    /// The kind is part of the primitive's contract, so it travels in the
134    /// signature where the compiler sees it rather than being smuggled
135    /// through `raw`. Backends return the platform root untouched;
136    /// `ShellSurface::list_with` additionally stamps `raw["shell_kind"]` on
137    /// the surface root, in one place, so a consumer holding only an
138    /// [`Element`](crate::Element) can read the kind back. That stamp is a
139    /// readback, not a selector: it is on the root alone, a rooted locator
140    /// only emits descendants of its root, and `TreeNode` carries no `raw`.
141    fn list_shell_surfaces(&self) -> Result<Vec<(ShellSurfaceKind, ElementData)>>;
142
143    /// Search for elements matching a selector.
144    ///
145    /// The selector is already parsed by the core — providers match against it
146    /// during traversal and can prune subtrees that can't match.
147    ///
148    /// `root` is the subtree root to search under (always present; use
149    /// [`list_apps`](Self::list_apps) to discover application roots first).
150    /// If `limit` is `Some(n)`, stops after finding `n` matches.
151    /// If `max_depth` is `Some(d)`, does not descend deeper than `d` levels.
152    ///
153    /// This is a thin convenience wrapper that re-uses
154    /// [`find_elements_group`](Self::find_elements_group) with a single-clause
155    /// group. Backends do **not** override this method — they override
156    /// `find_elements_group`, and the single-clause case runs through the
157    /// same native code path as multi-clause queries.
158    fn find_elements(
159        &self,
160        root: &ElementData,
161        selector: &Selector,
162        limit: Option<usize>,
163        max_depth: Option<u32>,
164    ) -> Result<Vec<ElementData>> {
165        let group = SelectorGroup {
166            clauses: vec![selector.clone()],
167        };
168        self.find_elements_group(root, &group, limit, max_depth)
169    }
170
171    /// Search for elements matching any clause of a comma-separated selector
172    /// group, scoped to `root`'s subtree.
173    ///
174    /// This is the **primary search primitive** each backend should override.
175    /// A native implementation performs ONE platform-level subtree query/walk
176    /// (e.g. `FindAllBuildCache(TreeScope_Subtree)` on Windows, one DFS over
177    /// AT-SPI children on Linux, one AX walk on macOS) and evaluates every
178    /// clause inline against each visited element. This avoids the
179    /// per-clause-walk perf cliff and also keeps the cross-clause dedup
180    /// correct: because everything happens inside one call, each platform
181    /// node is visited once and platform identity is stable.
182    ///
183    /// `root` is non-optional: app discovery now goes through
184    /// [`list_apps`](Self::list_apps) and any rootless multi-app search is
185    /// performed by the caller (e.g. `Locator`) by enumerating apps and
186    /// invoking this method per app.
187    ///
188    /// The default implementation traverses via [`get_children`](Self::get_children)
189    /// and uses tree-path identity for cross-clause merging. It's the
190    /// fallback for backends that haven't shipped a native override yet —
191    /// the same path the test/mock provider exercises.
192    fn find_elements_group(
193        &self,
194        root: &ElementData,
195        group: &SelectorGroup,
196        limit: Option<usize>,
197        max_depth: Option<u32>,
198    ) -> Result<Vec<ElementData>> {
199        crate::selector::find_elements_in_tree_group(
200            |el| self.get_children(el),
201            Some(root),
202            group,
203            limit,
204            max_depth,
205        )
206    }
207
208    /// Narrow candidates through remaining selector segments (Child/Descendant
209    /// combinators), deduplicate, apply final :nth and limit.
210    fn narrow_multi_segment(
211        &self,
212        mut candidates: Vec<ElementData>,
213        segments: &[SelectorSegment],
214        max_depth: u32,
215        limit: Option<usize>,
216    ) -> Result<Vec<ElementData>> {
217        for segment in segments {
218            let mut next_candidates = Vec::new();
219            for candidate in &candidates {
220                match segment.combinator {
221                    Combinator::Child => {
222                        let children = self.get_children(Some(candidate))?;
223                        for child in children {
224                            if matches_simple(&child, &segment.simple) {
225                                next_candidates.push(child);
226                            }
227                        }
228                    }
229                    Combinator::Descendant => {
230                        let sub_selector = Selector {
231                            segments: vec![SelectorSegment {
232                                combinator: Combinator::Root,
233                                simple: segment.simple.clone(),
234                            }],
235                        };
236                        let mut sub_results =
237                            self.find_elements(candidate, &sub_selector, None, Some(max_depth))?;
238                        next_candidates.append(&mut sub_results);
239                    }
240                    Combinator::Root => unreachable!(),
241                }
242            }
243            let mut seen = std::collections::HashSet::new();
244            next_candidates.retain(|e| seen.insert(e.handle));
245            candidates = next_candidates;
246        }
247
248        // Apply :nth on last segment
249        if let Some(nth) = segments.last().and_then(|s| s.simple.nth) {
250            if nth <= candidates.len() {
251                candidates = vec![candidates.remove(nth - 1)];
252            } else {
253                candidates.clear();
254            }
255        }
256
257        if let Some(limit) = limit {
258            candidates.truncate(limit);
259        }
260
261        Ok(candidates)
262    }
263
264    // ── Common actions ──────────────────────────────────────────────
265
266    /// Click / tap / invoke the element.
267    fn press(&self, element: &ElementData) -> Result<()>;
268
269    /// Set keyboard focus to the element.
270    fn focus(&self, element: &ElementData) -> Result<()>;
271
272    /// Remove keyboard focus from the element.
273    fn blur(&self, element: &ElementData) -> Result<()>;
274
275    /// Toggle a checkbox or switch.
276    fn toggle(&self, element: &ElementData) -> Result<()>;
277
278    /// Select an item in a list, tab group, or menu.
279    fn select(&self, element: &ElementData) -> Result<()>;
280
281    /// Expand a collapsible element (combo box, tree item, disclosure).
282    fn expand(&self, element: &ElementData) -> Result<()>;
283
284    /// Collapse an expanded element.
285    fn collapse(&self, element: &ElementData) -> Result<()>;
286
287    /// Show the element's context menu or dropdown.
288    fn show_menu(&self, element: &ElementData) -> Result<()>;
289
290    /// Increment a slider or spinner by one step.
291    fn increment(&self, element: &ElementData) -> Result<()>;
292
293    /// Decrement a slider or spinner by one step.
294    fn decrement(&self, element: &ElementData) -> Result<()>;
295
296    /// Scroll the element into the visible area.
297    fn scroll_into_view(&self, element: &ElementData) -> Result<()>;
298
299    // ── Typed operations ────────────────────────────────────────────
300
301    /// Set the text value of the element.
302    fn set_value(&self, element: &ElementData, value: &str) -> Result<()>;
303
304    /// Set the numeric value of the element (slider, spinner).
305    fn set_numeric_value(&self, element: &ElementData, value: f64) -> Result<()>;
306
307    /// Insert text at the current cursor position.
308    fn type_text(&self, element: &ElementData, text: &str) -> Result<()>;
309
310    /// Select a text range (0-based character offsets).
311    fn set_text_selection(&self, element: &ElementData, start: u32, end: u32) -> Result<()>;
312
313    // ── Generic action escape hatch ─────────────────────────────────
314
315    /// Perform an action by `snake_case` name.
316    ///
317    /// This is the escape hatch for platform-specific actions not covered by
318    /// the first-class methods above. The provider converts the name to the
319    /// platform's convention (e.g. `"custom_thing"` → `"AXCustomThing"` on
320    /// macOS) and invokes it.
321    ///
322    /// Well-known action names (`"press"`, `"focus"`, etc.) should also work
323    /// here — providers should delegate to the corresponding method.
324    fn perform_action(&self, element: &ElementData, action: &str) -> Result<()>;
325
326    // ── Events ──────────────────────────────────────────────────────
327
328    /// Subscribe to all accessibility events for an application.
329    ///
330    /// The element should be an application-level element (role=Application).
331    /// The provider extracts the PID from `element.pid`.
332    ///
333    /// Returns a [`Subscription`] that receives events until dropped.
334    fn subscribe(&self, element: &ElementData) -> Result<Subscription>;
335}
336
337// Blanket impl so shared references to a provider act as providers themselves.
338// Used by the umbrella crate's singleton (a `&'static dyn Provider` wrapped in
339// `Arc<_>`) and by any caller that wants to share a provider via `&T`. The
340// orphan rules keep this collision-free for downstream crates because `xa11y-core`
341// owns the `Provider` trait.
342impl<T: Provider + ?Sized> Provider for &T {
343    fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>> {
344        (**self).get_children(element)
345    }
346    fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>> {
347        (**self).get_parent(element)
348    }
349    fn list_apps(&self) -> Result<Vec<ElementData>> {
350        (**self).list_apps()
351    }
352    // Delegated explicitly (despite having a default impl) so a concrete
353    // provider's PID-direct override isn't bypassed when it's used through a
354    // shared reference — the default body on `&T` would re-route through
355    // `list_apps` and silently lose the override.
356    fn app_by_pid(&self, pid: u32) -> Result<ElementData> {
357        (**self).app_by_pid(pid)
358    }
359    fn focused_app(&self) -> Result<ElementData> {
360        (**self).focused_app()
361    }
362    fn list_shell_surfaces(&self) -> Result<Vec<(ShellSurfaceKind, ElementData)>> {
363        (**self).list_shell_surfaces()
364    }
365    fn find_elements(
366        &self,
367        root: &ElementData,
368        selector: &Selector,
369        limit: Option<usize>,
370        max_depth: Option<u32>,
371    ) -> Result<Vec<ElementData>> {
372        (**self).find_elements(root, selector, limit, max_depth)
373    }
374    fn find_elements_group(
375        &self,
376        root: &ElementData,
377        group: &SelectorGroup,
378        limit: Option<usize>,
379        max_depth: Option<u32>,
380    ) -> Result<Vec<ElementData>> {
381        (**self).find_elements_group(root, group, limit, max_depth)
382    }
383    fn narrow_multi_segment(
384        &self,
385        candidates: Vec<ElementData>,
386        segments: &[SelectorSegment],
387        max_depth: u32,
388        limit: Option<usize>,
389    ) -> Result<Vec<ElementData>> {
390        (**self).narrow_multi_segment(candidates, segments, max_depth, limit)
391    }
392    fn press(&self, element: &ElementData) -> Result<()> {
393        (**self).press(element)
394    }
395    fn focus(&self, element: &ElementData) -> Result<()> {
396        (**self).focus(element)
397    }
398    fn blur(&self, element: &ElementData) -> Result<()> {
399        (**self).blur(element)
400    }
401    fn toggle(&self, element: &ElementData) -> Result<()> {
402        (**self).toggle(element)
403    }
404    fn select(&self, element: &ElementData) -> Result<()> {
405        (**self).select(element)
406    }
407    fn expand(&self, element: &ElementData) -> Result<()> {
408        (**self).expand(element)
409    }
410    fn collapse(&self, element: &ElementData) -> Result<()> {
411        (**self).collapse(element)
412    }
413    fn show_menu(&self, element: &ElementData) -> Result<()> {
414        (**self).show_menu(element)
415    }
416    fn increment(&self, element: &ElementData) -> Result<()> {
417        (**self).increment(element)
418    }
419    fn decrement(&self, element: &ElementData) -> Result<()> {
420        (**self).decrement(element)
421    }
422    fn scroll_into_view(&self, element: &ElementData) -> Result<()> {
423        (**self).scroll_into_view(element)
424    }
425    fn set_value(&self, element: &ElementData, value: &str) -> Result<()> {
426        (**self).set_value(element, value)
427    }
428    fn set_numeric_value(&self, element: &ElementData, value: f64) -> Result<()> {
429        (**self).set_numeric_value(element, value)
430    }
431    fn type_text(&self, element: &ElementData, text: &str) -> Result<()> {
432        (**self).type_text(element, text)
433    }
434    fn set_text_selection(&self, element: &ElementData, start: u32, end: u32) -> Result<()> {
435        (**self).set_text_selection(element, start, end)
436    }
437    fn perform_action(&self, element: &ElementData, action: &str) -> Result<()> {
438        (**self).perform_action(element, action)
439    }
440    fn subscribe(&self, element: &ElementData) -> Result<Subscription> {
441        (**self).subscribe(element)
442    }
443}