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}