Skip to main content

xa11y_core/
provider.rs

1use crate::element::ElementData;
2use crate::error::Result;
3use crate::event_provider::Subscription;
4use crate::selector::{matches_simple, Combinator, Selector, 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    /// Search for elements matching a selector.
35    ///
36    /// The selector is already parsed by the core — providers match against it
37    /// during traversal and can prune subtrees that can't match.
38    ///
39    /// If `root` is `None`, searches from the system root (all applications).
40    /// If `limit` is `Some(n)`, stops after finding `n` matches.
41    /// If `max_depth` is `Some(d)`, does not descend deeper than `d` levels.
42    ///
43    /// The default implementation traverses via [`get_children`](Self::get_children).
44    fn find_elements(
45        &self,
46        root: Option<&ElementData>,
47        selector: &Selector,
48        limit: Option<usize>,
49        max_depth: Option<u32>,
50    ) -> Result<Vec<ElementData>> {
51        crate::selector::find_elements_in_tree(
52            |el| self.get_children(el),
53            root,
54            selector,
55            limit,
56            max_depth,
57        )
58    }
59
60    /// Narrow candidates through remaining selector segments (Child/Descendant
61    /// combinators), deduplicate, apply final :nth and limit.
62    fn narrow_multi_segment(
63        &self,
64        mut candidates: Vec<ElementData>,
65        segments: &[SelectorSegment],
66        max_depth: u32,
67        limit: Option<usize>,
68    ) -> Result<Vec<ElementData>> {
69        for segment in segments {
70            let mut next_candidates = Vec::new();
71            for candidate in &candidates {
72                match segment.combinator {
73                    Combinator::Child => {
74                        let children = self.get_children(Some(candidate))?;
75                        for child in children {
76                            if matches_simple(&child, &segment.simple) {
77                                next_candidates.push(child);
78                            }
79                        }
80                    }
81                    Combinator::Descendant => {
82                        let sub_selector = Selector {
83                            segments: vec![SelectorSegment {
84                                combinator: Combinator::Root,
85                                simple: segment.simple.clone(),
86                            }],
87                        };
88                        let mut sub_results = self.find_elements(
89                            Some(candidate),
90                            &sub_selector,
91                            None,
92                            Some(max_depth),
93                        )?;
94                        next_candidates.append(&mut sub_results);
95                    }
96                    Combinator::Root => unreachable!(),
97                }
98            }
99            let mut seen = std::collections::HashSet::new();
100            next_candidates.retain(|e| seen.insert(e.handle));
101            candidates = next_candidates;
102        }
103
104        // Apply :nth on last segment
105        if let Some(nth) = segments.last().and_then(|s| s.simple.nth) {
106            if nth <= candidates.len() {
107                candidates = vec![candidates.remove(nth - 1)];
108            } else {
109                candidates.clear();
110            }
111        }
112
113        if let Some(limit) = limit {
114            candidates.truncate(limit);
115        }
116
117        Ok(candidates)
118    }
119
120    // ── Common actions ──────────────────────────────────────────────
121
122    /// Click / tap / invoke the element.
123    fn press(&self, element: &ElementData) -> Result<()>;
124
125    /// Set keyboard focus to the element.
126    fn focus(&self, element: &ElementData) -> Result<()>;
127
128    /// Remove keyboard focus from the element.
129    fn blur(&self, element: &ElementData) -> Result<()>;
130
131    /// Toggle a checkbox or switch.
132    fn toggle(&self, element: &ElementData) -> Result<()>;
133
134    /// Select an item in a list, tab group, or menu.
135    fn select(&self, element: &ElementData) -> Result<()>;
136
137    /// Expand a collapsible element (combo box, tree item, disclosure).
138    fn expand(&self, element: &ElementData) -> Result<()>;
139
140    /// Collapse an expanded element.
141    fn collapse(&self, element: &ElementData) -> Result<()>;
142
143    /// Show the element's context menu or dropdown.
144    fn show_menu(&self, element: &ElementData) -> Result<()>;
145
146    /// Increment a slider or spinner by one step.
147    fn increment(&self, element: &ElementData) -> Result<()>;
148
149    /// Decrement a slider or spinner by one step.
150    fn decrement(&self, element: &ElementData) -> Result<()>;
151
152    /// Scroll the element into the visible area.
153    fn scroll_into_view(&self, element: &ElementData) -> Result<()>;
154
155    // ── Typed operations ────────────────────────────────────────────
156
157    /// Set the text value of the element.
158    fn set_value(&self, element: &ElementData, value: &str) -> Result<()>;
159
160    /// Set the numeric value of the element (slider, spinner).
161    fn set_numeric_value(&self, element: &ElementData, value: f64) -> Result<()>;
162
163    /// Insert text at the current cursor position.
164    fn type_text(&self, element: &ElementData, text: &str) -> Result<()>;
165
166    /// Select a text range (0-based character offsets).
167    fn set_text_selection(&self, element: &ElementData, start: u32, end: u32) -> Result<()>;
168
169    // ── Generic action escape hatch ─────────────────────────────────
170
171    /// Perform an action by `snake_case` name.
172    ///
173    /// This is the escape hatch for platform-specific actions not covered by
174    /// the first-class methods above. The provider converts the name to the
175    /// platform's convention (e.g. `"custom_thing"` → `"AXCustomThing"` on
176    /// macOS) and invokes it.
177    ///
178    /// Well-known action names (`"press"`, `"focus"`, etc.) should also work
179    /// here — providers should delegate to the corresponding method.
180    fn perform_action(&self, element: &ElementData, action: &str) -> Result<()>;
181
182    // ── Events ──────────────────────────────────────────────────────
183
184    /// Subscribe to all accessibility events for an application.
185    ///
186    /// The element should be an application-level element (role=Application).
187    /// The provider extracts the PID from `element.pid`.
188    ///
189    /// Returns a [`Subscription`] that receives events until dropped.
190    fn subscribe(&self, element: &ElementData) -> Result<Subscription>;
191}
192
193// Blanket impl so shared references to a provider act as providers themselves.
194// Used by the umbrella crate's singleton (a `&'static dyn Provider` wrapped in
195// `Arc<_>`) and by any caller that wants to share a provider via `&T`. The
196// orphan rules keep this collision-free for downstream crates because `xa11y-core`
197// owns the `Provider` trait.
198impl<T: Provider + ?Sized> Provider for &T {
199    fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>> {
200        (**self).get_children(element)
201    }
202    fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>> {
203        (**self).get_parent(element)
204    }
205    fn find_elements(
206        &self,
207        root: Option<&ElementData>,
208        selector: &Selector,
209        limit: Option<usize>,
210        max_depth: Option<u32>,
211    ) -> Result<Vec<ElementData>> {
212        (**self).find_elements(root, selector, limit, max_depth)
213    }
214    fn narrow_multi_segment(
215        &self,
216        candidates: Vec<ElementData>,
217        segments: &[SelectorSegment],
218        max_depth: u32,
219        limit: Option<usize>,
220    ) -> Result<Vec<ElementData>> {
221        (**self).narrow_multi_segment(candidates, segments, max_depth, limit)
222    }
223    fn press(&self, element: &ElementData) -> Result<()> {
224        (**self).press(element)
225    }
226    fn focus(&self, element: &ElementData) -> Result<()> {
227        (**self).focus(element)
228    }
229    fn blur(&self, element: &ElementData) -> Result<()> {
230        (**self).blur(element)
231    }
232    fn toggle(&self, element: &ElementData) -> Result<()> {
233        (**self).toggle(element)
234    }
235    fn select(&self, element: &ElementData) -> Result<()> {
236        (**self).select(element)
237    }
238    fn expand(&self, element: &ElementData) -> Result<()> {
239        (**self).expand(element)
240    }
241    fn collapse(&self, element: &ElementData) -> Result<()> {
242        (**self).collapse(element)
243    }
244    fn show_menu(&self, element: &ElementData) -> Result<()> {
245        (**self).show_menu(element)
246    }
247    fn increment(&self, element: &ElementData) -> Result<()> {
248        (**self).increment(element)
249    }
250    fn decrement(&self, element: &ElementData) -> Result<()> {
251        (**self).decrement(element)
252    }
253    fn scroll_into_view(&self, element: &ElementData) -> Result<()> {
254        (**self).scroll_into_view(element)
255    }
256    fn set_value(&self, element: &ElementData, value: &str) -> Result<()> {
257        (**self).set_value(element, value)
258    }
259    fn set_numeric_value(&self, element: &ElementData, value: f64) -> Result<()> {
260        (**self).set_numeric_value(element, value)
261    }
262    fn type_text(&self, element: &ElementData, text: &str) -> Result<()> {
263        (**self).type_text(element, text)
264    }
265    fn set_text_selection(&self, element: &ElementData, start: u32, end: u32) -> Result<()> {
266        (**self).set_text_selection(element, start, end)
267    }
268    fn perform_action(&self, element: &ElementData, action: &str) -> Result<()> {
269        (**self).perform_action(element, action)
270    }
271    fn subscribe(&self, element: &ElementData) -> Result<Subscription> {
272        (**self).subscribe(element)
273    }
274}