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