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