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