accesskit_atspi_common/
node.rs

1// Copyright 2022 The AccessKit Authors. All rights reserved.
2// Licensed under the Apache License, Version 2.0 (found in
3// the LICENSE-APACHE file) or the MIT license (found in
4// the LICENSE-MIT file), at your option.
5
6// Derived from Chromium's accessibility abstraction.
7// Copyright 2017 The Chromium Authors. All rights reserved.
8// Use of this source code is governed by a BSD-style license that can be
9// found in the LICENSE.chromium file.
10
11use accesskit::{
12    Action, ActionData, ActionRequest, Affine, Live, NodeId, Orientation, Point, Rect, Role,
13    Toggled,
14};
15use accesskit_consumer::{FilterResult, Node, TreeState};
16use atspi_common::{
17    CoordType, Granularity, Interface, InterfaceSet, Layer, Politeness, RelationType,
18    Role as AtspiRole, ScrollType, State, StateSet,
19};
20use std::{
21    collections::HashMap,
22    hash::{Hash, Hasher},
23    iter::FusedIterator,
24    sync::{Arc, RwLock, RwLockReadGuard, Weak},
25};
26
27use crate::{
28    adapter::Adapter,
29    context::{AppContext, Context},
30    filters::filter,
31    util::*,
32    Action as AtspiAction, Error, ObjectEvent, Property, Rect as AtspiRect, Result,
33};
34
35pub(crate) struct NodeWrapper<'a>(pub(crate) &'a Node<'a>);
36
37impl NodeWrapper<'_> {
38    pub(crate) fn name(&self) -> Option<String> {
39        if self.0.label_comes_from_value() {
40            self.0.value()
41        } else {
42            self.0.label()
43        }
44    }
45
46    pub(crate) fn description(&self) -> Option<String> {
47        self.0.description()
48    }
49
50    pub(crate) fn parent_id(&self) -> Option<NodeId> {
51        self.0.parent_id()
52    }
53
54    pub(crate) fn id(&self) -> NodeId {
55        self.0.id()
56    }
57
58    fn filtered_child_ids(
59        &self,
60    ) -> impl DoubleEndedIterator<Item = NodeId> + FusedIterator<Item = NodeId> + '_ {
61        self.0.filtered_children(&filter).map(|child| child.id())
62    }
63
64    pub(crate) fn role(&self) -> AtspiRole {
65        if self.0.has_role_description() {
66            return AtspiRole::Extended;
67        }
68
69        match self.0.role() {
70            Role::Alert => AtspiRole::Notification,
71            Role::AlertDialog => AtspiRole::Alert,
72            Role::Comment | Role::Suggestion => AtspiRole::Section,
73            // TODO: See how to represent ARIA role="application"
74            Role::Application => AtspiRole::Embedded,
75            Role::Article => AtspiRole::Article,
76            Role::Audio => AtspiRole::Audio,
77            Role::Banner | Role::Header => AtspiRole::Landmark,
78            Role::Blockquote => AtspiRole::BlockQuote,
79            Role::Caret => AtspiRole::Unknown,
80            Role::Button => {
81                if self.0.toggled().is_some() {
82                    AtspiRole::ToggleButton
83                } else {
84                    AtspiRole::Button
85                }
86            }
87            Role::DefaultButton => AtspiRole::Button,
88            Role::Canvas => AtspiRole::Canvas,
89            Role::Caption => AtspiRole::Caption,
90            Role::Cell => AtspiRole::TableCell,
91            Role::CheckBox => AtspiRole::CheckBox,
92            Role::Switch => AtspiRole::ToggleButton,
93            Role::ColorWell => AtspiRole::Button,
94            Role::ColumnHeader => AtspiRole::ColumnHeader,
95            Role::ComboBox | Role::EditableComboBox => AtspiRole::ComboBox,
96            Role::Complementary => AtspiRole::Landmark,
97            Role::ContentDeletion => AtspiRole::ContentDeletion,
98            Role::ContentInsertion => AtspiRole::ContentInsertion,
99            Role::ContentInfo | Role::Footer => AtspiRole::Landmark,
100            Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue,
101            Role::DescriptionList => AtspiRole::DescriptionList,
102            Role::DescriptionListTerm => AtspiRole::DescriptionTerm,
103            Role::Details => AtspiRole::Panel,
104            Role::Dialog => AtspiRole::Dialog,
105            Role::Directory => AtspiRole::List,
106            Role::DisclosureTriangle => AtspiRole::ToggleButton,
107            Role::DocCover => AtspiRole::Image,
108            Role::DocBackLink | Role::DocBiblioRef | Role::DocGlossRef | Role::DocNoteRef => {
109                AtspiRole::Link
110            }
111            Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem,
112            Role::DocNotice | Role::DocTip => AtspiRole::Comment,
113            Role::DocFootnote => AtspiRole::Footnote,
114            Role::DocPageBreak => AtspiRole::Separator,
115            Role::DocPageFooter => AtspiRole::Footer,
116            Role::DocPageHeader => AtspiRole::Header,
117            Role::DocAcknowledgements
118            | Role::DocAfterword
119            | Role::DocAppendix
120            | Role::DocBibliography
121            | Role::DocChapter
122            | Role::DocConclusion
123            | Role::DocCredits
124            | Role::DocEndnotes
125            | Role::DocEpilogue
126            | Role::DocErrata
127            | Role::DocForeword
128            | Role::DocGlossary
129            | Role::DocIndex
130            | Role::DocIntroduction
131            | Role::DocPageList
132            | Role::DocPart
133            | Role::DocPreface
134            | Role::DocPrologue
135            | Role::DocToc => AtspiRole::Landmark,
136            Role::DocAbstract
137            | Role::DocColophon
138            | Role::DocCredit
139            | Role::DocDedication
140            | Role::DocEpigraph
141            | Role::DocExample
142            | Role::DocPullquote
143            | Role::DocQna => AtspiRole::Section,
144            Role::DocSubtitle => AtspiRole::Heading,
145            Role::Document => AtspiRole::DocumentFrame,
146            Role::EmbeddedObject => AtspiRole::Embedded,
147            // TODO: Forms which lack an accessible name are no longer
148            // exposed as forms. Forms which have accessible
149            // names should be exposed as `AtspiRole::Landmark` according to Core AAM.
150            Role::Form => AtspiRole::Form,
151            Role::Figure | Role::Feed => AtspiRole::Panel,
152            Role::GenericContainer
153            | Role::FooterAsNonLandmark
154            | Role::HeaderAsNonLandmark
155            | Role::Ruby => AtspiRole::Section,
156            Role::GraphicsDocument => AtspiRole::DocumentFrame,
157            Role::GraphicsObject => AtspiRole::Panel,
158            Role::GraphicsSymbol => AtspiRole::Image,
159            Role::Grid => AtspiRole::Table,
160            Role::Group => AtspiRole::Panel,
161            Role::Heading => AtspiRole::Heading,
162            Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame,
163            // TODO: If there are unignored children, then it should be AtspiRole::ImageMap.
164            Role::Image => AtspiRole::Image,
165            Role::TextRun => AtspiRole::Static,
166            Role::Legend => AtspiRole::Label,
167            // Layout table objects are treated the same as `Role::GenericContainer`.
168            Role::LayoutTable => AtspiRole::Section,
169            Role::LayoutTableCell => AtspiRole::Section,
170            Role::LayoutTableRow => AtspiRole::Section,
171            // TODO: Having a separate accessible object for line breaks
172            // is inconsistent with other implementations.
173            Role::LineBreak => AtspiRole::Static,
174            Role::Link => AtspiRole::Link,
175            Role::List => AtspiRole::List,
176            Role::ListBox => AtspiRole::ListBox,
177            // TODO: Use `AtspiRole::MenuItem' inside a combo box.
178            Role::ListBoxOption => AtspiRole::ListItem,
179            Role::ListGrid => AtspiRole::Table,
180            Role::ListItem => AtspiRole::ListItem,
181            // Regular list markers only expose their alternative text, but do not
182            // expose their descendants, and the descendants should be ignored. This
183            // is because the alternative text depends on the counter style and can
184            // be different from the actual (visual) marker text, and hence,
185            // inconsistent with the descendants. We treat a list marker as non-text
186            // only if it still has non-ignored descendants, which happens only when =>
187            // - The list marker itself is ignored but the descendants are not
188            // - Or the list marker contains images
189            Role::ListMarker => AtspiRole::Static,
190            Role::Log => AtspiRole::Log,
191            Role::Main => AtspiRole::Landmark,
192            Role::Mark => AtspiRole::Static,
193            Role::Math => AtspiRole::Math,
194            Role::Marquee => AtspiRole::Marquee,
195            Role::Menu | Role::MenuListPopup => AtspiRole::Menu,
196            Role::MenuBar => AtspiRole::MenuBar,
197            Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem,
198            Role::MenuItemCheckBox => AtspiRole::CheckMenuItem,
199            Role::MenuItemRadio => AtspiRole::RadioMenuItem,
200            Role::Meter => AtspiRole::LevelBar,
201            Role::Navigation => AtspiRole::Landmark,
202            Role::Note => AtspiRole::Comment,
203            Role::Pane | Role::ScrollView => AtspiRole::Panel,
204            Role::Paragraph => AtspiRole::Paragraph,
205            Role::PdfActionableHighlight => AtspiRole::Button,
206            Role::PdfRoot => AtspiRole::DocumentFrame,
207            Role::PluginObject => AtspiRole::Embedded,
208            Role::Portal => AtspiRole::Button,
209            Role::Pre => AtspiRole::Section,
210            Role::ProgressIndicator => AtspiRole::ProgressBar,
211            Role::RadioButton => AtspiRole::RadioButton,
212            Role::RadioGroup => AtspiRole::Panel,
213            Role::Region => AtspiRole::Landmark,
214            Role::RootWebArea => AtspiRole::DocumentWeb,
215            Role::Row => AtspiRole::TableRow,
216            Role::RowGroup => AtspiRole::Panel,
217            Role::RowHeader => AtspiRole::RowHeader,
218            // TODO: Generally exposed as description on <ruby> (`Role::Ruby`) element, not
219            // as its own object in the tree.
220            // However, it's possible to make a `Role::RubyAnnotation` element show up in the
221            // tree, for example by adding tabindex="0" to the source <rp> or <rt>
222            // element or making the source element the target of an aria-owns.
223            // Therefore, we need to gracefully handle it if it actually
224            // shows up in the tree.
225            Role::RubyAnnotation => AtspiRole::Static,
226            Role::Section => AtspiRole::Section,
227            Role::ScrollBar => AtspiRole::ScrollBar,
228            Role::Search => AtspiRole::Landmark,
229            Role::Slider => AtspiRole::Slider,
230            Role::SpinButton => AtspiRole::SpinButton,
231            Role::Splitter => AtspiRole::Separator,
232            Role::Label => AtspiRole::Label,
233            Role::Status => AtspiRole::StatusBar,
234            Role::SvgRoot => AtspiRole::DocumentFrame,
235            Role::Tab => AtspiRole::PageTab,
236            Role::Table => AtspiRole::Table,
237            Role::TabList => AtspiRole::PageTabList,
238            Role::TabPanel => AtspiRole::ScrollPane,
239            // TODO: This mapping should also be applied to the dfn
240            // element.
241            Role::Term => AtspiRole::DescriptionTerm,
242            Role::TitleBar => AtspiRole::TitleBar,
243            Role::TextInput
244            | Role::MultilineTextInput
245            | Role::SearchInput
246            | Role::EmailInput
247            | Role::NumberInput
248            | Role::PhoneNumberInput
249            | Role::UrlInput => AtspiRole::Entry,
250            Role::DateInput
251            | Role::DateTimeInput
252            | Role::WeekInput
253            | Role::MonthInput
254            | Role::TimeInput => AtspiRole::DateEditor,
255            Role::PasswordInput => AtspiRole::PasswordText,
256            Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => {
257                AtspiRole::Static
258            }
259            Role::Timer => AtspiRole::Timer,
260            Role::Toolbar => AtspiRole::ToolBar,
261            Role::Tooltip => AtspiRole::ToolTip,
262            Role::Tree => AtspiRole::Tree,
263            Role::TreeItem => AtspiRole::TreeItem,
264            Role::TreeGrid => AtspiRole::TreeTable,
265            Role::Video => AtspiRole::Video,
266            // In AT-SPI, elements with `AtspiRole::Frame` are windows with titles and
267            // buttons, while those with `AtspiRole::Window` are windows without those
268            // elements.
269            Role::Window => AtspiRole::Frame,
270            Role::WebView => AtspiRole::Panel,
271            Role::FigureCaption => AtspiRole::Caption,
272            // TODO: Are there special cases to consider?
273            Role::Unknown => AtspiRole::Unknown,
274            Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject,
275            Role::Terminal => AtspiRole::Terminal,
276        }
277    }
278
279    fn is_focused(&self) -> bool {
280        self.0.is_focused()
281    }
282
283    pub(crate) fn state(&self, is_window_focused: bool) -> StateSet {
284        let state = self.0;
285        let atspi_role = self.role();
286        let mut atspi_state = StateSet::empty();
287        if state.parent_id().is_none() && state.role() == Role::Window && is_window_focused {
288            atspi_state.insert(State::Active);
289        }
290        if state.is_text_input() && !state.is_read_only() {
291            atspi_state.insert(State::Editable);
292        }
293        // TODO: Focus and selection.
294        if state.is_focusable(&filter) {
295            atspi_state.insert(State::Focusable);
296        }
297        let filter_result = filter(self.0);
298        if filter_result == FilterResult::Include {
299            atspi_state.insert(State::Visible | State::Showing);
300        }
301        if state.is_required() {
302            atspi_state.insert(State::Required);
303        }
304        if state.is_multiselectable() {
305            atspi_state.insert(State::Multiselectable);
306        }
307        if let Some(orientation) = state.orientation() {
308            atspi_state.insert(if orientation == Orientation::Horizontal {
309                State::Horizontal
310            } else {
311                State::Vertical
312            });
313        }
314        if atspi_role != AtspiRole::ToggleButton && state.toggled().is_some() {
315            atspi_state.insert(State::Checkable);
316        }
317        if let Some(selected) = state.is_selected() {
318            if !state.is_disabled() {
319                atspi_state.insert(State::Selectable);
320            }
321            if selected {
322                atspi_state.insert(State::Selected);
323            }
324        }
325        if state.is_text_input() {
326            atspi_state.insert(State::SelectableText);
327            atspi_state.insert(match state.is_multiline() {
328                true => State::MultiLine,
329                false => State::SingleLine,
330            });
331        }
332
333        // Special case for indeterminate progressbar.
334        if state.role() == Role::ProgressIndicator && state.numeric_value().is_none() {
335            atspi_state.insert(State::Indeterminate);
336        }
337
338        // Toggled state
339        match state.toggled() {
340            Some(Toggled::Mixed) => atspi_state.insert(State::Indeterminate),
341            Some(Toggled::True) if atspi_role == AtspiRole::ToggleButton => {
342                atspi_state.insert(State::Pressed)
343            }
344            Some(Toggled::True) => atspi_state.insert(State::Checked),
345            _ => {}
346        }
347
348        if state.is_read_only_supported() && state.is_read_only_or_disabled() {
349            atspi_state.insert(State::ReadOnly);
350        } else {
351            atspi_state.insert(State::Enabled | State::Sensitive);
352        }
353
354        if self.is_focused() {
355            atspi_state.insert(State::Focused);
356        }
357
358        atspi_state
359    }
360
361    fn placeholder(&self) -> Option<&str> {
362        self.0.placeholder()
363    }
364
365    fn position_in_set(&self) -> Option<String> {
366        self.0.position_in_set().map(|p| (p + 1).to_string())
367    }
368
369    fn size_of_set(&self) -> Option<String> {
370        self.0
371            .size_of_set_from_container(&filter)
372            .map(|s| s.to_string())
373    }
374
375    fn attributes(&self) -> HashMap<&'static str, String> {
376        let mut attributes = HashMap::new();
377        if let Some(placeholder) = self.placeholder() {
378            attributes.insert("placeholder-text", placeholder.to_string());
379        }
380        if let Some(position_in_set) = self.position_in_set() {
381            attributes.insert("posinset", position_in_set);
382        }
383        if let Some(size_of_set) = self.size_of_set() {
384            attributes.insert("setsize", size_of_set);
385        }
386
387        attributes
388    }
389
390    fn is_root(&self) -> bool {
391        self.0.is_root()
392    }
393
394    fn supports_action(&self) -> bool {
395        self.0.is_clickable(&filter)
396    }
397
398    fn supports_component(&self) -> bool {
399        self.0.raw_bounds().is_some() || self.is_root()
400    }
401
402    fn supports_selection(&self) -> bool {
403        self.0.is_container_with_selectable_children()
404    }
405
406    fn supports_text(&self) -> bool {
407        self.0.supports_text_ranges()
408    }
409
410    fn supports_value(&self) -> bool {
411        self.current_value().is_some()
412    }
413
414    pub(crate) fn interfaces(&self) -> InterfaceSet {
415        let mut interfaces = InterfaceSet::new(Interface::Accessible);
416        if self.supports_action() {
417            interfaces.insert(Interface::Action);
418        }
419        if self.supports_component() {
420            interfaces.insert(Interface::Component);
421        }
422        if self.supports_selection() {
423            interfaces.insert(Interface::Selection);
424        }
425        if self.supports_text() {
426            interfaces.insert(Interface::Text);
427        }
428        if self.supports_value() {
429            interfaces.insert(Interface::Value);
430        }
431        interfaces
432    }
433
434    pub(crate) fn live(&self) -> Politeness {
435        let live = self.0.live();
436        match live {
437            Live::Off => Politeness::None,
438            Live::Polite => Politeness::Polite,
439            Live::Assertive => Politeness::Assertive,
440        }
441    }
442
443    fn n_actions(&self) -> i32 {
444        if self.0.is_clickable(&filter) {
445            1
446        } else {
447            0
448        }
449    }
450
451    fn get_action_name(&self, index: i32) -> String {
452        if index != 0 {
453            return String::new();
454        }
455        String::from(if self.0.is_clickable(&filter) {
456            "click"
457        } else {
458            ""
459        })
460    }
461
462    fn raw_bounds_and_transform(&self) -> (Option<Rect>, Affine) {
463        let state = self.0;
464        (state.raw_bounds(), state.direct_transform())
465    }
466
467    fn extents(&self, window_bounds: &WindowBounds, coord_type: CoordType) -> Option<Rect> {
468        let mut bounds = self.0.bounding_box();
469        if self.is_root() {
470            let window_bounds = window_bounds.inner.with_origin(Point::ZERO);
471            if !window_bounds.is_empty() {
472                if let Some(bounds) = &mut bounds {
473                    bounds.x0 = bounds.x0.min(window_bounds.x1);
474                    bounds.y0 = bounds.y0.min(window_bounds.y1);
475                    bounds.x1 = bounds.x1.min(window_bounds.x1);
476                    bounds.y1 = bounds.y1.min(window_bounds.y1);
477                } else {
478                    bounds = Some(window_bounds);
479                }
480            }
481        }
482        bounds.map(|bounds| {
483            let new_origin = window_bounds.accesskit_point_to_atspi_point(
484                bounds.origin(),
485                self.0.filtered_parent(&filter),
486                coord_type,
487            );
488            bounds.with_origin(new_origin)
489        })
490    }
491
492    fn current_value(&self) -> Option<f64> {
493        self.0.numeric_value()
494    }
495
496    pub(crate) fn notify_changes(
497        &self,
498        window_bounds: &WindowBounds,
499        adapter: &Adapter,
500        old: &NodeWrapper<'_>,
501    ) {
502        self.notify_state_changes(adapter, old);
503        self.notify_property_changes(adapter, old);
504        self.notify_bounds_changes(window_bounds, adapter, old);
505        self.notify_children_changes(adapter, old);
506    }
507
508    fn notify_state_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
509        let old_state = old.state(true);
510        let new_state = self.state(true);
511        let changed_states = old_state ^ new_state;
512        for state in changed_states.iter() {
513            if state == State::Focused {
514                // This is handled specially in `focus_moved`.
515                continue;
516            }
517            adapter.emit_object_event(
518                self.id(),
519                ObjectEvent::StateChanged(state, new_state.contains(state)),
520            );
521        }
522    }
523
524    fn notify_property_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
525        let name = self.name();
526        if name != old.name() {
527            let name = name.unwrap_or_default();
528            adapter.emit_object_event(
529                self.id(),
530                ObjectEvent::PropertyChanged(Property::Name(name.clone())),
531            );
532
533            let live = self.live();
534            if live != Politeness::None {
535                adapter.emit_object_event(self.id(), ObjectEvent::Announcement(name, live));
536            }
537        }
538        let description = self.description();
539        if description != old.description() {
540            adapter.emit_object_event(
541                self.id(),
542                ObjectEvent::PropertyChanged(Property::Description(
543                    description.unwrap_or_default(),
544                )),
545            );
546        }
547        let parent_id = self.parent_id();
548        if parent_id != old.parent_id() {
549            let parent = self
550                .0
551                .filtered_parent(&filter)
552                .map_or(NodeIdOrRoot::Root, |node| NodeIdOrRoot::Node(node.id()));
553            adapter.emit_object_event(
554                self.id(),
555                ObjectEvent::PropertyChanged(Property::Parent(parent)),
556            );
557        }
558        let role = self.role();
559        if role != old.role() {
560            adapter.emit_object_event(
561                self.id(),
562                ObjectEvent::PropertyChanged(Property::Role(role)),
563            );
564        }
565        if let Some(value) = self.current_value() {
566            if Some(value) != old.current_value() {
567                adapter.emit_object_event(
568                    self.id(),
569                    ObjectEvent::PropertyChanged(Property::Value(value)),
570                );
571            }
572        }
573    }
574
575    fn notify_bounds_changes(
576        &self,
577        window_bounds: &WindowBounds,
578        adapter: &Adapter,
579        old: &NodeWrapper<'_>,
580    ) {
581        if self.raw_bounds_and_transform() != old.raw_bounds_and_transform() {
582            if let Some(extents) = self.extents(window_bounds, CoordType::Window) {
583                adapter.emit_object_event(self.id(), ObjectEvent::BoundsChanged(extents.into()));
584            }
585        }
586    }
587
588    fn notify_children_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
589        let old_filtered_children = old.filtered_child_ids().collect::<Vec<NodeId>>();
590        let new_filtered_children = self.filtered_child_ids().collect::<Vec<NodeId>>();
591        for (index, child) in new_filtered_children.iter().enumerate() {
592            if !old_filtered_children.contains(child) {
593                adapter.emit_object_event(self.id(), ObjectEvent::ChildAdded(index, *child));
594            }
595        }
596        for child in old_filtered_children.into_iter() {
597            if !new_filtered_children.contains(&child) {
598                adapter.emit_object_event(self.id(), ObjectEvent::ChildRemoved(child));
599            }
600        }
601    }
602}
603
604#[derive(Clone)]
605pub struct PlatformNode {
606    context: Weak<Context>,
607    adapter_id: usize,
608    id: NodeId,
609}
610
611impl PlatformNode {
612    pub(crate) fn new(context: &Arc<Context>, adapter_id: usize, id: NodeId) -> Self {
613        Self {
614            context: Arc::downgrade(context),
615            adapter_id,
616            id,
617        }
618    }
619
620    fn from_adapter_root(adapter_id_and_context: &(usize, Arc<Context>)) -> Self {
621        let (adapter_id, context) = adapter_id_and_context;
622        Self::new(context, *adapter_id, context.read_tree().state().root_id())
623    }
624
625    fn upgrade_context(&self) -> Result<Arc<Context>> {
626        if let Some(context) = self.context.upgrade() {
627            Ok(context)
628        } else {
629            Err(Error::Defunct)
630        }
631    }
632
633    fn with_tree_state<F, T>(&self, f: F) -> Result<T>
634    where
635        F: FnOnce(&TreeState) -> Result<T>,
636    {
637        let context = self.upgrade_context()?;
638        let tree = context.read_tree();
639        f(tree.state())
640    }
641
642    fn with_tree_state_and_context<F, T>(&self, f: F) -> Result<T>
643    where
644        F: FnOnce(&TreeState, &Context) -> Result<T>,
645    {
646        let context = self.upgrade_context()?;
647        let tree = context.read_tree();
648        f(tree.state(), &context)
649    }
650
651    fn resolve_with_context<F, T>(&self, f: F) -> Result<T>
652    where
653        for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
654    {
655        self.with_tree_state_and_context(|state, context| {
656            if let Some(node) = state.node_by_id(self.id) {
657                f(node, context)
658            } else {
659                Err(Error::Defunct)
660            }
661        })
662    }
663
664    fn resolve_for_selection_with_context<F, T>(&self, f: F) -> Result<T>
665    where
666        for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
667    {
668        self.resolve_with_context(|node, context| {
669            let wrapper = NodeWrapper(&node);
670            if wrapper.supports_selection() {
671                f(node, context)
672            } else {
673                Err(Error::UnsupportedInterface)
674            }
675        })
676    }
677
678    fn resolve_for_text_with_context<F, T>(&self, f: F) -> Result<T>
679    where
680        for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
681    {
682        self.resolve_with_context(|node, context| {
683            let wrapper = NodeWrapper(&node);
684            if wrapper.supports_text() {
685                f(node, context)
686            } else {
687                Err(Error::UnsupportedInterface)
688            }
689        })
690    }
691
692    fn resolve<F, T>(&self, f: F) -> Result<T>
693    where
694        for<'a> F: FnOnce(Node<'a>) -> Result<T>,
695    {
696        self.resolve_with_context(|node, _| f(node))
697    }
698
699    fn resolve_for_selection<F, T>(&self, f: F) -> Result<T>
700    where
701        for<'a> F: FnOnce(Node<'a>) -> Result<T>,
702    {
703        self.resolve(|node| {
704            let wrapper = NodeWrapper(&node);
705            if wrapper.supports_selection() {
706                f(node)
707            } else {
708                Err(Error::UnsupportedInterface)
709            }
710        })
711    }
712
713    fn resolve_for_text<F, T>(&self, f: F) -> Result<T>
714    where
715        for<'a> F: FnOnce(Node<'a>) -> Result<T>,
716    {
717        self.resolve_for_text_with_context(|node, _| f(node))
718    }
719
720    fn do_action_internal<F>(&self, f: F) -> Result<()>
721    where
722        F: FnOnce(&TreeState, &Context) -> ActionRequest,
723    {
724        let context = self.upgrade_context()?;
725        let tree = context.read_tree();
726        if tree.state().has_node(self.id) {
727            let request = f(tree.state(), &context);
728            drop(tree);
729            context.do_action(request);
730            Ok(())
731        } else {
732            Err(Error::Defunct)
733        }
734    }
735
736    pub fn name(&self) -> Result<String> {
737        self.resolve(|node| {
738            let wrapper = NodeWrapper(&node);
739            Ok(wrapper.name().unwrap_or_default())
740        })
741    }
742
743    pub fn description(&self) -> Result<String> {
744        self.resolve(|node| {
745            let wrapper = NodeWrapper(&node);
746            Ok(wrapper.description().unwrap_or_default())
747        })
748    }
749
750    pub fn relative(&self, id: NodeId) -> Self {
751        Self {
752            context: self.context.clone(),
753            adapter_id: self.adapter_id,
754            id,
755        }
756    }
757
758    pub fn root(&self) -> Result<PlatformRoot> {
759        let context = self.upgrade_context()?;
760        Ok(PlatformRoot::new(&context.app_context))
761    }
762
763    pub fn toolkit_name(&self) -> Result<String> {
764        self.with_tree_state(|state| Ok(state.toolkit_name().unwrap_or_default().to_string()))
765    }
766
767    pub fn toolkit_version(&self) -> Result<String> {
768        self.with_tree_state(|state| Ok(state.toolkit_version().unwrap_or_default().to_string()))
769    }
770
771    pub fn parent(&self) -> Result<NodeIdOrRoot> {
772        self.resolve(|node| {
773            let parent = node
774                .filtered_parent(&filter)
775                .map_or(NodeIdOrRoot::Root, |node| NodeIdOrRoot::Node(node.id()));
776            Ok(parent)
777        })
778    }
779
780    pub fn child_count(&self) -> Result<i32> {
781        self.resolve(|node| {
782            i32::try_from(node.filtered_children(&filter).count())
783                .map_err(|_| Error::TooManyChildren)
784        })
785    }
786
787    pub fn adapter_id(&self) -> usize {
788        self.adapter_id
789    }
790
791    pub fn id(&self) -> NodeId {
792        self.id
793    }
794
795    pub fn accessible_id(&self) -> Result<String> {
796        self.resolve(|node| {
797            if let Some(author_id) = node.author_id() {
798                Ok(author_id.to_string())
799            } else {
800                Ok(String::new())
801            }
802        })
803    }
804
805    pub fn child_at_index(&self, index: usize) -> Result<Option<NodeId>> {
806        self.resolve(|node| {
807            let child = node
808                .filtered_children(&filter)
809                .nth(index)
810                .map(|child| child.id());
811            Ok(child)
812        })
813    }
814
815    pub fn map_children<T, I>(&self, f: impl Fn(NodeId) -> I) -> Result<T>
816    where
817        T: FromIterator<I>,
818    {
819        self.resolve(|node| {
820            let children = node
821                .filtered_children(&filter)
822                .map(|child| child.id())
823                .map(f)
824                .collect();
825            Ok(children)
826        })
827    }
828
829    pub fn index_in_parent(&self) -> Result<i32> {
830        self.resolve(|node| {
831            i32::try_from(node.preceding_filtered_siblings(&filter).count())
832                .map_err(|_| Error::IndexOutOfRange)
833        })
834    }
835
836    pub fn relation_set<T>(
837        &self,
838        f: impl Fn(NodeId) -> T,
839    ) -> Result<HashMap<RelationType, Vec<T>>> {
840        self.resolve(|node| {
841            let mut relations = HashMap::new();
842            let controls: Vec<_> = node
843                .controls()
844                .filter(|controlled| filter(controlled) == FilterResult::Include)
845                .map(|controlled| controlled.id())
846                .map(f)
847                .collect();
848            if !controls.is_empty() {
849                relations.insert(RelationType::ControllerFor, controls);
850            }
851            Ok(relations)
852        })
853    }
854
855    pub fn role(&self) -> Result<AtspiRole> {
856        self.resolve(|node| {
857            let wrapper = NodeWrapper(&node);
858            Ok(wrapper.role())
859        })
860    }
861
862    pub fn localized_role_name(&self) -> Result<String> {
863        self.resolve(|node| Ok(node.role_description().unwrap_or_default().to_string()))
864    }
865
866    pub fn state(&self) -> StateSet {
867        self.resolve_with_context(|node, context| {
868            let wrapper = NodeWrapper(&node);
869            Ok(wrapper.state(context.read_tree().state().focus_id().is_some()))
870        })
871        .unwrap_or(State::Defunct.into())
872    }
873
874    pub fn attributes(&self) -> Result<HashMap<&'static str, String>> {
875        self.resolve(|node| {
876            let wrapper = NodeWrapper(&node);
877            Ok(wrapper.attributes())
878        })
879    }
880
881    pub fn supports_action(&self) -> Result<bool> {
882        self.resolve(|node| {
883            let wrapper = NodeWrapper(&node);
884            Ok(wrapper.supports_action())
885        })
886    }
887
888    pub fn supports_component(&self) -> Result<bool> {
889        self.resolve(|node| {
890            let wrapper = NodeWrapper(&node);
891            Ok(wrapper.supports_component())
892        })
893    }
894
895    pub fn supports_selection(&self) -> Result<bool> {
896        self.resolve(|node| {
897            let wrapper = NodeWrapper(&node);
898            Ok(wrapper.supports_selection())
899        })
900    }
901
902    pub fn supports_text(&self) -> Result<bool> {
903        self.resolve(|node| {
904            let wrapper = NodeWrapper(&node);
905            Ok(wrapper.supports_text())
906        })
907    }
908
909    pub fn supports_value(&self) -> Result<bool> {
910        self.resolve(|node| {
911            let wrapper = NodeWrapper(&node);
912            Ok(wrapper.supports_value())
913        })
914    }
915
916    pub fn interfaces(&self) -> Result<InterfaceSet> {
917        self.resolve(|node| {
918            let wrapper = NodeWrapper(&node);
919            Ok(wrapper.interfaces())
920        })
921    }
922
923    pub fn n_actions(&self) -> Result<i32> {
924        self.resolve(|node| {
925            let wrapper = NodeWrapper(&node);
926            Ok(wrapper.n_actions())
927        })
928    }
929
930    pub fn action_name(&self, index: i32) -> Result<String> {
931        self.resolve(|node| {
932            let wrapper = NodeWrapper(&node);
933            Ok(wrapper.get_action_name(index))
934        })
935    }
936
937    pub fn actions(&self) -> Result<Vec<AtspiAction>> {
938        self.resolve(|node| {
939            let wrapper = NodeWrapper(&node);
940            let n_actions = wrapper.n_actions() as usize;
941            let mut actions = Vec::with_capacity(n_actions);
942            for i in 0..n_actions {
943                actions.push(AtspiAction {
944                    localized_name: wrapper.get_action_name(i as i32),
945                    description: "".into(),
946                    key_binding: "".into(),
947                });
948            }
949            Ok(actions)
950        })
951    }
952
953    pub fn do_action(&self, index: i32) -> Result<bool> {
954        if index != 0 {
955            return Ok(false);
956        }
957        self.do_action_internal(|_, _| ActionRequest {
958            action: Action::Click,
959            target: self.id,
960            data: None,
961        })?;
962        Ok(true)
963    }
964
965    pub fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> Result<bool> {
966        self.resolve_with_context(|node, context| {
967            let window_bounds = context.read_root_window_bounds();
968            let wrapper = NodeWrapper(&node);
969            if let Some(extents) = wrapper.extents(&window_bounds, coord_type) {
970                Ok(extents.contains(Point::new(x.into(), y.into())))
971            } else {
972                Ok(false)
973            }
974        })
975    }
976
977    pub fn accessible_at_point(
978        &self,
979        x: i32,
980        y: i32,
981        coord_type: CoordType,
982    ) -> Result<Option<NodeId>> {
983        self.resolve_with_context(|node, context| {
984            let window_bounds = context.read_root_window_bounds();
985            let point = window_bounds.atspi_point_to_accesskit_point(
986                Point::new(x.into(), y.into()),
987                Some(node),
988                coord_type,
989            );
990            let point = node.transform().inverse() * point;
991            Ok(node.node_at_point(point, &filter).map(|node| node.id()))
992        })
993    }
994
995    pub fn extents(&self, coord_type: CoordType) -> Result<AtspiRect> {
996        self.resolve_with_context(|node, context| {
997            let window_bounds = context.read_root_window_bounds();
998            let wrapper = NodeWrapper(&node);
999            Ok(wrapper
1000                .extents(&window_bounds, coord_type)
1001                .map_or(AtspiRect::INVALID, AtspiRect::from))
1002        })
1003    }
1004
1005    pub fn layer(&self) -> Result<Layer> {
1006        self.resolve(|node| {
1007            let wrapper = NodeWrapper(&node);
1008            if wrapper.role() == AtspiRole::Window {
1009                Ok(Layer::Window)
1010            } else {
1011                Ok(Layer::Widget)
1012            }
1013        })
1014    }
1015
1016    pub fn grab_focus(&self) -> Result<bool> {
1017        self.do_action_internal(|_, _| ActionRequest {
1018            action: Action::Focus,
1019            target: self.id,
1020            data: None,
1021        })?;
1022        Ok(true)
1023    }
1024
1025    pub fn scroll_to(&self, scroll_type: ScrollType) -> Result<bool> {
1026        self.do_action_internal(|_, _| ActionRequest {
1027            action: Action::ScrollIntoView,
1028            target: self.id,
1029            data: atspi_scroll_type_to_scroll_hint(scroll_type).map(ActionData::ScrollHint),
1030        })?;
1031        Ok(true)
1032    }
1033
1034    pub fn scroll_to_point(&self, coord_type: CoordType, x: i32, y: i32) -> Result<bool> {
1035        self.resolve_with_context(|node, context| {
1036            let window_bounds = context.read_root_window_bounds();
1037            let point = window_bounds.atspi_point_to_accesskit_point(
1038                Point::new(x.into(), y.into()),
1039                node.filtered_parent(&filter),
1040                coord_type,
1041            );
1042            context.do_action(ActionRequest {
1043                action: Action::ScrollToPoint,
1044                target: self.id,
1045                data: Some(ActionData::ScrollToPoint(point)),
1046            });
1047            Ok(())
1048        })?;
1049        Ok(true)
1050    }
1051
1052    pub fn n_selected_children(&self) -> Result<i32> {
1053        self.resolve_for_selection(|node| {
1054            node.items(filter)
1055                .filter(|item| item.is_selected() == Some(true))
1056                .count()
1057                .try_into()
1058                .map_err(|_| Error::TooManyChildren)
1059        })
1060    }
1061
1062    pub fn selected_child(&self, selected_child_index: usize) -> Result<Option<NodeId>> {
1063        self.resolve_for_selection(|node| {
1064            Ok(node
1065                .items(filter)
1066                .filter(|item| item.is_selected() == Some(true))
1067                .nth(selected_child_index)
1068                .map(|node| node.id()))
1069        })
1070    }
1071
1072    pub fn select_child(&self, child_index: usize) -> Result<bool> {
1073        self.resolve_for_selection_with_context(|node, context| {
1074            if let Some(child) = node.filtered_children(filter).nth(child_index) {
1075                if let Some(true) = child.is_selected() {
1076                    Ok(true)
1077                } else if child.is_selectable() && child.is_clickable(&filter) {
1078                    context.do_action(ActionRequest {
1079                        action: Action::Click,
1080                        target: child.id(),
1081                        data: None,
1082                    });
1083                    Ok(true)
1084                } else {
1085                    Ok(false)
1086                }
1087            } else {
1088                Err(Error::Defunct)
1089            }
1090        })
1091    }
1092
1093    pub fn deselect_selected_child(&self, selected_child_index: usize) -> Result<bool> {
1094        self.resolve_for_selection_with_context(|node, context| {
1095            if let Some(child) = node
1096                .items(filter)
1097                .filter(|c| c.is_selected() == Some(true))
1098                .nth(selected_child_index)
1099            {
1100                if child.is_clickable(&filter) {
1101                    context.do_action(ActionRequest {
1102                        action: Action::Click,
1103                        target: child.id(),
1104                        data: None,
1105                    });
1106                    Ok(true)
1107                } else {
1108                    Ok(false)
1109                }
1110            } else {
1111                Err(Error::Defunct)
1112            }
1113        })
1114    }
1115
1116    pub fn is_child_selected(&self, child_index: usize) -> Result<bool> {
1117        self.resolve_for_selection(|node| {
1118            node.filtered_children(filter)
1119                .nth(child_index)
1120                .map(|child| child.is_item_like() && child.is_selected() == Some(true))
1121                .ok_or(Error::Defunct)
1122        })
1123    }
1124
1125    pub fn select_all(&self) -> Result<bool> {
1126        // We don't support selecting all children at once.
1127        Ok(false)
1128    }
1129
1130    pub fn clear_selection(&self) -> Result<bool> {
1131        // We don't support deselecting all children at once.
1132        Ok(false)
1133    }
1134
1135    pub fn deselect_child(&self, child_index: usize) -> Result<bool> {
1136        self.resolve_for_selection_with_context(|node, context| {
1137            if let Some(child) = node.filtered_children(filter).nth(child_index) {
1138                if let Some(false) = child.is_selected() {
1139                    Ok(true)
1140                } else if child.is_selectable() && child.is_clickable(&filter) {
1141                    context.do_action(ActionRequest {
1142                        action: Action::Click,
1143                        target: child.id(),
1144                        data: None,
1145                    });
1146                    Ok(true)
1147                } else {
1148                    Ok(false)
1149                }
1150            } else {
1151                Err(Error::Defunct)
1152            }
1153        })
1154    }
1155
1156    pub fn character_count(&self) -> Result<i32> {
1157        self.resolve_for_text(|node| {
1158            node.document_range()
1159                .end()
1160                .to_global_usv_index()
1161                .try_into()
1162                .map_err(|_| Error::TooManyCharacters)
1163        })
1164    }
1165
1166    pub fn caret_offset(&self) -> Result<i32> {
1167        self.resolve_for_text(|node| {
1168            node.text_selection_focus().map_or(Ok(-1), |focus| {
1169                focus
1170                    .to_global_usv_index()
1171                    .try_into()
1172                    .map_err(|_| Error::TooManyCharacters)
1173            })
1174        })
1175    }
1176
1177    pub fn string_at_offset(
1178        &self,
1179        offset: i32,
1180        granularity: Granularity,
1181    ) -> Result<(String, i32, i32)> {
1182        self.resolve_for_text(|node| {
1183            let range = text_range_from_offset(&node, offset, granularity)?;
1184            let text = range.text();
1185            let start = range
1186                .start()
1187                .to_global_usv_index()
1188                .try_into()
1189                .map_err(|_| Error::TooManyCharacters)?;
1190            let end = range
1191                .end()
1192                .to_global_usv_index()
1193                .try_into()
1194                .map_err(|_| Error::TooManyCharacters)?;
1195
1196            Ok((text, start, end))
1197        })
1198    }
1199
1200    pub fn text(&self, start_offset: i32, end_offset: i32) -> Result<String> {
1201        self.resolve_for_text(|node| {
1202            let range = text_range_from_offsets(&node, start_offset, end_offset)
1203                .ok_or(Error::IndexOutOfRange)?;
1204            Ok(range.text())
1205        })
1206    }
1207
1208    pub fn set_caret_offset(&self, offset: i32) -> Result<bool> {
1209        self.resolve_for_text_with_context(|node, context| {
1210            let offset = text_position_from_offset(&node, offset).ok_or(Error::IndexOutOfRange)?;
1211            context.do_action(ActionRequest {
1212                action: Action::SetTextSelection,
1213                target: node.id(),
1214                data: Some(ActionData::SetTextSelection(
1215                    offset.to_degenerate_range().to_text_selection(),
1216                )),
1217            });
1218            Ok(true)
1219        })
1220    }
1221
1222    pub fn text_attribute_value(&self, _offset: i32, _attribute_name: &str) -> Result<String> {
1223        // TODO: Implement rich text.
1224        Err(Error::UnsupportedInterface)
1225    }
1226
1227    pub fn text_attributes(&self, _offset: i32) -> Result<(HashMap<String, String>, i32, i32)> {
1228        // TODO: Implement rich text.
1229        Err(Error::UnsupportedInterface)
1230    }
1231
1232    pub fn default_text_attributes(&self) -> Result<HashMap<String, String>> {
1233        // TODO: Implement rich text.
1234        Err(Error::UnsupportedInterface)
1235    }
1236
1237    pub fn character_extents(&self, offset: i32, coord_type: CoordType) -> Result<AtspiRect> {
1238        self.resolve_for_text_with_context(|node, context| {
1239            let range = text_range_from_offset(&node, offset, Granularity::Char)?;
1240            if let Some(bounds) = range.bounding_boxes().first() {
1241                let window_bounds = context.read_root_window_bounds();
1242                let new_origin = window_bounds.accesskit_point_to_atspi_point(
1243                    bounds.origin(),
1244                    Some(node),
1245                    coord_type,
1246                );
1247                Ok(bounds.with_origin(new_origin).into())
1248            } else {
1249                Ok(AtspiRect::INVALID)
1250            }
1251        })
1252    }
1253
1254    pub fn offset_at_point(&self, x: i32, y: i32, coord_type: CoordType) -> Result<i32> {
1255        self.resolve_for_text_with_context(|node, context| {
1256            let window_bounds = context.read_root_window_bounds();
1257            let point = window_bounds.atspi_point_to_accesskit_point(
1258                Point::new(x.into(), y.into()),
1259                Some(node),
1260                coord_type,
1261            );
1262            let point = node.transform().inverse() * point;
1263            node.text_position_at_point(point)
1264                .to_global_usv_index()
1265                .try_into()
1266                .map_err(|_| Error::TooManyCharacters)
1267        })
1268    }
1269
1270    pub fn n_selections(&self) -> Result<i32> {
1271        self.resolve_for_text(|node| {
1272            match node.text_selection().filter(|range| !range.is_degenerate()) {
1273                Some(_) => Ok(1),
1274                None => Ok(0),
1275            }
1276        })
1277    }
1278
1279    pub fn selection(&self, selection_num: i32) -> Result<(i32, i32)> {
1280        if selection_num != 0 {
1281            return Ok((-1, -1));
1282        }
1283
1284        self.resolve_for_text(|node| {
1285            node.text_selection()
1286                .filter(|range| !range.is_degenerate())
1287                .map_or(Ok((-1, -1)), |range| {
1288                    let start = range
1289                        .start()
1290                        .to_global_usv_index()
1291                        .try_into()
1292                        .map_err(|_| Error::TooManyCharacters)?;
1293                    let end = range
1294                        .end()
1295                        .to_global_usv_index()
1296                        .try_into()
1297                        .map_err(|_| Error::TooManyCharacters)?;
1298
1299                    Ok((start, end))
1300                })
1301        })
1302    }
1303
1304    pub fn add_selection(&self, start_offset: i32, end_offset: i32) -> Result<bool> {
1305        // We only support one selection.
1306        self.set_selection(0, start_offset, end_offset)
1307    }
1308
1309    pub fn remove_selection(&self, selection_num: i32) -> Result<bool> {
1310        if selection_num != 0 {
1311            return Ok(false);
1312        }
1313
1314        self.resolve_for_text_with_context(|node, context| {
1315            // Simply collapse the selection to the position of the caret if a caret is
1316            // visible, otherwise set the selection to 0.
1317            let selection_end = node
1318                .text_selection_focus()
1319                .unwrap_or_else(|| node.document_range().start());
1320            context.do_action(ActionRequest {
1321                action: Action::SetTextSelection,
1322                target: node.id(),
1323                data: Some(ActionData::SetTextSelection(
1324                    selection_end.to_degenerate_range().to_text_selection(),
1325                )),
1326            });
1327            Ok(true)
1328        })
1329    }
1330
1331    pub fn set_selection(
1332        &self,
1333        selection_num: i32,
1334        start_offset: i32,
1335        end_offset: i32,
1336    ) -> Result<bool> {
1337        if selection_num != 0 {
1338            return Ok(false);
1339        }
1340
1341        self.resolve_for_text_with_context(|node, context| {
1342            let range = text_range_from_offsets(&node, start_offset, end_offset)
1343                .ok_or(Error::IndexOutOfRange)?;
1344            context.do_action(ActionRequest {
1345                action: Action::SetTextSelection,
1346                target: node.id(),
1347                data: Some(ActionData::SetTextSelection(range.to_text_selection())),
1348            });
1349            Ok(true)
1350        })
1351    }
1352
1353    pub fn range_extents(
1354        &self,
1355        start_offset: i32,
1356        end_offset: i32,
1357        coord_type: CoordType,
1358    ) -> Result<AtspiRect> {
1359        self.resolve_for_text_with_context(|node, context| {
1360            if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) {
1361                let window_bounds = context.read_root_window_bounds();
1362                let new_origin = window_bounds.accesskit_point_to_atspi_point(
1363                    rect.origin(),
1364                    Some(node),
1365                    coord_type,
1366                );
1367                Ok(rect.with_origin(new_origin).into())
1368            } else {
1369                Ok(AtspiRect::INVALID)
1370            }
1371        })
1372    }
1373
1374    pub fn text_attribute_run(
1375        &self,
1376        _offset: i32,
1377        _include_defaults: bool,
1378    ) -> Result<(HashMap<String, String>, i32, i32)> {
1379        // TODO: Implement rich text.
1380        // For now, just report a range spanning the entire text with no attributes,
1381        // this is required by Orca to announce selection content and caret movements.
1382        let character_count = self.character_count()?;
1383        Ok((HashMap::new(), 0, character_count))
1384    }
1385
1386    pub fn scroll_substring_to(
1387        &self,
1388        start_offset: i32,
1389        end_offset: i32,
1390        scroll_type: ScrollType,
1391    ) -> Result<bool> {
1392        self.resolve_for_text_with_context(|node, context| {
1393            if let Some(range) = text_range_from_offsets(&node, start_offset, end_offset) {
1394                let position = if matches!(
1395                    scroll_type,
1396                    ScrollType::BottomRight | ScrollType::BottomEdge | ScrollType::RightEdge
1397                ) {
1398                    range.end()
1399                } else {
1400                    range.start()
1401                };
1402                context.do_action(ActionRequest {
1403                    action: Action::ScrollIntoView,
1404                    target: position.inner_node().id(),
1405                    data: atspi_scroll_type_to_scroll_hint(scroll_type).map(ActionData::ScrollHint),
1406                });
1407                Ok(true)
1408            } else {
1409                Ok(false)
1410            }
1411        })
1412    }
1413
1414    pub fn scroll_substring_to_point(
1415        &self,
1416        start_offset: i32,
1417        end_offset: i32,
1418        coord_type: CoordType,
1419        x: i32,
1420        y: i32,
1421    ) -> Result<bool> {
1422        self.resolve_for_text_with_context(|node, context| {
1423            let window_bounds = context.read_root_window_bounds();
1424            let target_point = window_bounds.atspi_point_to_accesskit_point(
1425                Point::new(x.into(), y.into()),
1426                Some(node),
1427                coord_type,
1428            );
1429
1430            if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) {
1431                let point = Point::new(target_point.x - rect.x0, target_point.y - rect.y0);
1432                context.do_action(ActionRequest {
1433                    action: Action::ScrollToPoint,
1434                    target: node.id(),
1435                    data: Some(ActionData::ScrollToPoint(point)),
1436                });
1437                return Ok(true);
1438            }
1439            Ok(false)
1440        })
1441    }
1442
1443    pub fn minimum_value(&self) -> Result<f64> {
1444        self.resolve(|node| Ok(node.min_numeric_value().unwrap_or(f64::MIN)))
1445    }
1446
1447    pub fn maximum_value(&self) -> Result<f64> {
1448        self.resolve(|node| Ok(node.max_numeric_value().unwrap_or(f64::MAX)))
1449    }
1450
1451    pub fn minimum_increment(&self) -> Result<f64> {
1452        self.resolve(|node| Ok(node.numeric_value_step().unwrap_or(0.0)))
1453    }
1454
1455    pub fn current_value(&self) -> Result<f64> {
1456        self.resolve(|node| {
1457            let wrapper = NodeWrapper(&node);
1458            Ok(wrapper.current_value().unwrap_or(0.0))
1459        })
1460    }
1461
1462    pub fn set_current_value(&self, value: f64) -> Result<()> {
1463        self.do_action_internal(|_, _| ActionRequest {
1464            action: Action::SetValue,
1465            target: self.id,
1466            data: Some(ActionData::NumericValue(value)),
1467        })
1468    }
1469}
1470
1471impl PartialEq for PlatformNode {
1472    fn eq(&self, other: &Self) -> bool {
1473        self.adapter_id == other.adapter_id && self.id == other.id
1474    }
1475}
1476
1477impl Eq for PlatformNode {}
1478
1479impl Hash for PlatformNode {
1480    fn hash<H: Hasher>(&self, state: &mut H) {
1481        self.adapter_id.hash(state);
1482        self.id.hash(state);
1483    }
1484}
1485
1486#[derive(Clone)]
1487pub struct PlatformRoot {
1488    app_context: Weak<RwLock<AppContext>>,
1489}
1490
1491impl PlatformRoot {
1492    pub fn new(app_context: &Arc<RwLock<AppContext>>) -> Self {
1493        Self {
1494            app_context: Arc::downgrade(app_context),
1495        }
1496    }
1497
1498    fn resolve_app_context<F, T>(&self, f: F) -> Result<T>
1499    where
1500        for<'a> F: FnOnce(RwLockReadGuard<'a, AppContext>) -> Result<T>,
1501    {
1502        let app_context = match self.app_context.upgrade() {
1503            Some(context) => context,
1504            None => return Err(Error::Defunct),
1505        };
1506        let app_context = app_context.read().unwrap();
1507        f(app_context)
1508    }
1509
1510    pub fn name(&self) -> Result<String> {
1511        self.resolve_app_context(|context| Ok(context.name.clone().unwrap_or_default()))
1512    }
1513
1514    pub fn child_count(&self) -> Result<i32> {
1515        self.resolve_app_context(|context| {
1516            i32::try_from(context.adapters.len()).map_err(|_| Error::TooManyChildren)
1517        })
1518    }
1519
1520    pub fn child_at_index(&self, index: usize) -> Result<Option<PlatformNode>> {
1521        self.resolve_app_context(|context| {
1522            let child = context
1523                .adapters
1524                .get(index)
1525                .map(PlatformNode::from_adapter_root);
1526            Ok(child)
1527        })
1528    }
1529
1530    pub fn child_id_at_index(&self, index: usize) -> Result<Option<(usize, NodeId)>> {
1531        self.resolve_app_context(|context| {
1532            let child = context
1533                .adapters
1534                .get(index)
1535                .map(|(adapter_id, context)| (*adapter_id, context.read_tree().state().root_id()));
1536            Ok(child)
1537        })
1538    }
1539
1540    pub fn map_children<T, I>(&self, f: impl Fn(PlatformNode) -> I) -> Result<T>
1541    where
1542        T: FromIterator<I>,
1543    {
1544        self.resolve_app_context(|context| {
1545            let children = context
1546                .adapters
1547                .iter()
1548                .map(PlatformNode::from_adapter_root)
1549                .map(f)
1550                .collect();
1551            Ok(children)
1552        })
1553    }
1554
1555    pub fn map_child_ids<T, I>(&self, f: impl Fn((usize, NodeId)) -> I) -> Result<T>
1556    where
1557        T: FromIterator<I>,
1558    {
1559        self.resolve_app_context(|context| {
1560            let children = context
1561                .adapters
1562                .iter()
1563                .map(|(adapter_id, context)| (*adapter_id, context.read_tree().state().root_id()))
1564                .map(f)
1565                .collect();
1566            Ok(children)
1567        })
1568    }
1569
1570    pub fn toolkit_name(&self) -> Result<String> {
1571        self.resolve_app_context(|context| Ok(context.toolkit_name.clone().unwrap_or_default()))
1572    }
1573
1574    pub fn toolkit_version(&self) -> Result<String> {
1575        self.resolve_app_context(|context| Ok(context.toolkit_version.clone().unwrap_or_default()))
1576    }
1577
1578    pub fn id(&self) -> Result<i32> {
1579        self.resolve_app_context(|context| Ok(context.id.unwrap_or(-1)))
1580    }
1581
1582    pub fn set_id(&mut self, id: i32) -> Result<()> {
1583        let app_context = match self.app_context.upgrade() {
1584            Some(context) => context,
1585            None => return Err(Error::Defunct),
1586        };
1587        let mut app_context = app_context.write().unwrap();
1588        app_context.id = Some(id);
1589        Ok(())
1590    }
1591}
1592
1593impl PartialEq for PlatformRoot {
1594    fn eq(&self, other: &Self) -> bool {
1595        self.app_context.ptr_eq(&other.app_context)
1596    }
1597}
1598
1599impl Hash for PlatformRoot {
1600    fn hash<H: Hasher>(&self, state: &mut H) {
1601        self.app_context.as_ptr().hash(state);
1602    }
1603}
1604
1605#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1606pub enum NodeIdOrRoot {
1607    Node(NodeId),
1608    Root,
1609}