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}