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}