From db321ffa7851b7a01a95c4644296dba2f1297dab Mon Sep 17 00:00:00 2001 From: Arnold Loubriat Date: Mon, 27 Sep 2021 19:24:10 +0200 Subject: [PATCH] Node methods to retrieve siblings and unignored children (#3) Node methods to retrieve siblings and unignored children --- consumer/src/iterators.rs | 751 ++++++++++++++++++++++++++++++++++++++ consumer/src/lib.rs | 104 ++++++ consumer/src/node.rs | 312 +++++++++++++++- 3 files changed, 1166 insertions(+), 1 deletion(-) create mode 100644 consumer/src/iterators.rs diff --git a/consumer/src/iterators.rs b/consumer/src/iterators.rs new file mode 100644 index 000000000..6f01203c7 --- /dev/null +++ b/consumer/src/iterators.rs @@ -0,0 +1,751 @@ +// Copyright 2021 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +// Derived from Chromium's accessibility abstraction. +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +use std::iter::FusedIterator; + +use accesskit_schema::NodeId; + +use crate::node::Node; +use crate::tree::Reader as TreeReader; + +/// An iterator that yields following siblings of a node. +/// +/// This struct is created by the [following_siblings](Node::following_siblings) method on [Node]. +pub struct FollowingSiblings<'a> { + back_position: usize, + done: bool, + front_position: usize, + parent: Option>, +} + +impl<'a> FollowingSiblings<'a> { + pub(crate) fn new(node: &'a Node<'a>) -> Self { + let parent_and_index = node.parent_and_index(); + let (back_position, front_position, done) = + if let Some((ref parent, index)) = parent_and_index { + let back_position = parent.data().children.len() - 1; + let front_position = index + 1; + ( + back_position, + front_position, + front_position > back_position, + ) + } else { + (0, 0, true) + }; + Self { + back_position, + done, + front_position, + parent: parent_and_index.map(|(parent, _)| parent), + } + } +} + +impl<'a> Iterator for FollowingSiblings<'a> { + type Item = NodeId; + + fn next(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.front_position == self.back_position; + let child = self + .parent + .as_ref()? + .data() + .children + .get(self.front_position)?; + self.front_position += 1; + Some(*child) + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = match self.done { + true => 0, + _ => self.back_position + 1 - self.front_position, + }; + (len, Some(len)) + } +} + +impl<'a> DoubleEndedIterator for FollowingSiblings<'a> { + fn next_back(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.back_position == self.front_position; + let child = self + .parent + .as_ref()? + .data() + .children + .get(self.back_position)?; + self.back_position -= 1; + Some(*child) + } + } +} + +impl<'a> ExactSizeIterator for FollowingSiblings<'a> {} + +impl<'a> FusedIterator for FollowingSiblings<'a> {} + +/// An iterator that yields preceding siblings of a node. +/// +/// This struct is created by the [preceding_siblings](Node::preceding_siblings) method on [Node]. +pub struct PrecedingSiblings<'a> { + back_position: usize, + done: bool, + front_position: usize, + parent: Option>, +} + +impl<'a> PrecedingSiblings<'a> { + pub(crate) fn new(node: &'a Node<'a>) -> Self { + let parent_and_index = node.parent_and_index(); + let (back_position, front_position, done) = if let Some((_, index)) = parent_and_index { + let front_position = index.saturating_sub(1); + (0, front_position, index == 0) + } else { + (0, 0, true) + }; + Self { + back_position, + done, + front_position, + parent: parent_and_index.map(|(parent, _)| parent), + } + } +} + +impl<'a> Iterator for PrecedingSiblings<'a> { + type Item = NodeId; + + fn next(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.front_position == self.back_position; + let child = self + .parent + .as_ref()? + .data() + .children + .get(self.front_position)?; + if !self.done { + self.front_position -= 1; + } + Some(*child) + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = match self.done { + true => 0, + _ => self.front_position + 1 - self.back_position, + }; + (len, Some(len)) + } +} + +impl<'a> DoubleEndedIterator for PrecedingSiblings<'a> { + fn next_back(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.back_position == self.front_position; + let child = self + .parent + .as_ref()? + .data() + .children + .get(self.back_position)?; + self.back_position += 1; + Some(*child) + } + } +} + +impl<'a> ExactSizeIterator for PrecedingSiblings<'a> {} + +impl<'a> FusedIterator for PrecedingSiblings<'a> {} + +fn next_unignored_sibling(node_id: Option, reader: &TreeReader) -> Option { + // Search for the next sibling of this node, skipping over any ignored nodes + // encountered. + // + // In our search: + // If we find an ignored sibling, we consider its children as our siblings. + // If we run out of siblings, we consider an ignored parent's siblings as our + // own siblings. + // + // Note: this behaviour of 'skipping over' an ignored node makes this subtly + // different to finding the next (direct) sibling which is unignored. + let mut next_id = node_id; + let mut consider_children = false; + while let Some(current_node) = next_id.and_then(|id| reader.node_by_id(id)) { + if let Some(Some(child)) = consider_children.then(|| current_node.children().next()) { + next_id = Some(child.id()); + if !child.is_ignored() { + return next_id; + } + } else if let Some(sibling) = current_node.following_siblings().next() { + next_id = Some(sibling.id()); + if !sibling.is_ignored() { + return next_id; + } + consider_children = true; + } else { + let parent = current_node.parent(); + next_id = parent.as_ref().map(|parent| parent.id()); + if let Some(parent) = parent { + if !parent.is_ignored() { + return None; + } + } else { + return None; + } + } + } + None +} + +fn previous_unignored_sibling(node_id: Option, reader: &TreeReader) -> Option { + // Search for the previous sibling of this node, skipping over any ignored nodes + // encountered. + // + // In our search for a sibling: + // If we find an ignored sibling, we may consider its children as siblings. + // If we run out of siblings, we may consider an ignored parent's siblings as + // our own. + let mut previous_id = node_id; + let mut consider_children = false; + while let Some(current_node) = previous_id.and_then(|id| reader.node_by_id(id)) { + if let Some(Some(child)) = consider_children.then(|| current_node.children().next_back()) { + previous_id = Some(child.id()); + if !child.is_ignored() { + return previous_id; + } + } else if let Some(sibling) = current_node.preceding_siblings().next() { + previous_id = Some(sibling.id()); + if !sibling.is_ignored() { + return previous_id; + } + consider_children = true; + } else { + let parent = current_node.parent(); + previous_id = parent.as_ref().map(|parent| parent.id()); + if let Some(parent) = parent { + if !parent.is_ignored() { + return None; + } + } else { + return None; + } + } + } + None +} + +/// An iterator that yields following unignored siblings of a node. +/// +/// This struct is created by the [following_unignored_siblings](Node::following_unignored_siblings) method on [Node]. +pub struct FollowingUnignoredSiblings<'a> { + back_id: Option, + done: bool, + front_id: Option, + reader: &'a TreeReader<'a>, +} + +impl<'a> FollowingUnignoredSiblings<'a> { + pub(crate) fn new(node: &'a Node<'a>) -> Self { + let front_id = next_unignored_sibling(Some(node.id()), node.tree_reader); + let back_id = node.parent().as_ref().and_then(Node::last_unignored_child); + Self { + back_id, + done: back_id.is_none() || front_id.is_none(), + front_id, + reader: node.tree_reader, + } + } +} + +impl<'a> Iterator for FollowingUnignoredSiblings<'a> { + type Item = NodeId; + + fn next(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.front_id == self.back_id; + let current_id = self.front_id; + self.front_id = next_unignored_sibling(self.front_id, self.reader); + current_id + } + } +} + +impl<'a> DoubleEndedIterator for FollowingUnignoredSiblings<'a> { + fn next_back(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.back_id == self.front_id; + let current_id = self.back_id; + self.back_id = previous_unignored_sibling(self.back_id, self.reader); + current_id + } + } +} + +impl<'a> FusedIterator for FollowingUnignoredSiblings<'a> {} + +/// An iterator that yields preceding unignored siblings of a node. +/// +/// This struct is created by the [preceding_unignored_siblings](Node::preceding_unignored_siblings) method on [Node]. +pub struct PrecedingUnignoredSiblings<'a> { + back_id: Option, + done: bool, + front_id: Option, + reader: &'a TreeReader<'a>, +} + +impl<'a> PrecedingUnignoredSiblings<'a> { + pub(crate) fn new(node: &'a Node<'a>) -> Self { + let front_id = previous_unignored_sibling(Some(node.id()), node.tree_reader); + let back_id = node.parent().as_ref().and_then(Node::first_unignored_child); + Self { + back_id, + done: back_id.is_none() || front_id.is_none(), + front_id, + reader: node.tree_reader, + } + } +} + +impl<'a> Iterator for PrecedingUnignoredSiblings<'a> { + type Item = NodeId; + + fn next(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.front_id == self.back_id; + let current_id = self.front_id; + self.front_id = previous_unignored_sibling(self.front_id, self.reader); + current_id + } + } +} + +impl<'a> DoubleEndedIterator for PrecedingUnignoredSiblings<'a> { + fn next_back(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.back_id == self.front_id; + let current_id = self.back_id; + self.back_id = next_unignored_sibling(self.back_id, self.reader); + current_id + } + } +} + +impl<'a> FusedIterator for PrecedingUnignoredSiblings<'a> {} + +/// An iterator that yields unignored children of a node. +/// +/// This struct is created by the [unignored_children](Node::unignored_children) method on [Node]. +pub struct UnignoredChildren<'a> { + back_id: Option, + done: bool, + front_id: Option, + reader: &'a TreeReader<'a>, +} + +impl<'a> UnignoredChildren<'a> { + pub(crate) fn new(node: &'a Node<'a>) -> Self { + let front_id = node.first_unignored_child(); + let back_id = node.last_unignored_child(); + Self { + back_id, + done: back_id.is_none() || front_id.is_none(), + front_id, + reader: node.tree_reader, + } + } +} + +impl<'a> Iterator for UnignoredChildren<'a> { + type Item = NodeId; + + fn next(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.front_id == self.back_id; + let current_id = self.front_id; + self.front_id = next_unignored_sibling(self.front_id, self.reader); + current_id + } + } +} + +impl<'a> DoubleEndedIterator for UnignoredChildren<'a> { + fn next_back(&mut self) -> Option { + if self.done { + None + } else { + self.done = self.back_id == self.front_id; + let current_id = self.back_id; + self.back_id = previous_unignored_sibling(self.back_id, self.reader); + current_id + } + } +} + +impl<'a> FusedIterator for UnignoredChildren<'a> {} + +#[cfg(test)] +mod tests { + use crate::tests::*; + use accesskit_schema::NodeId; + + #[test] + fn following_siblings() { + let tree = test_tree(); + assert!(tree.read().root().following_siblings().next().is_none()); + assert_eq!(0, tree.read().root().following_siblings().len()); + assert_eq!( + [ + PARAGRAPH_1_IGNORED_ID, + PARAGRAPH_2_ID, + PARAGRAPH_3_IGNORED_ID + ], + tree.read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .following_siblings() + .map(|node| node.id()) + .collect::>()[..] + ); + assert_eq!( + 3, + tree.read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .following_siblings() + .len() + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .following_siblings() + .next() + .is_none()); + assert_eq!( + 0, + tree.read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .following_siblings() + .len() + ); + } + + #[test] + fn following_siblings_reversed() { + let tree = test_tree(); + assert!(tree + .read() + .root() + .following_siblings() + .next_back() + .is_none()); + assert_eq!( + [ + PARAGRAPH_3_IGNORED_ID, + PARAGRAPH_2_ID, + PARAGRAPH_1_IGNORED_ID + ], + tree.read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .following_siblings() + .rev() + .map(|node| node.id()) + .collect::>()[..] + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .following_siblings() + .next_back() + .is_none()); + } + + #[test] + fn preceding_siblings() { + let tree = test_tree(); + assert!(tree.read().root().preceding_siblings().next().is_none()); + assert_eq!(0, tree.read().root().preceding_siblings().len()); + assert_eq!( + [PARAGRAPH_2_ID, PARAGRAPH_1_IGNORED_ID, PARAGRAPH_0_ID], + tree.read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .preceding_siblings() + .map(|node| node.id()) + .collect::>()[..] + ); + assert_eq!( + 3, + tree.read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .preceding_siblings() + .len() + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .preceding_siblings() + .next() + .is_none()); + assert_eq!( + 0, + tree.read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .preceding_siblings() + .len() + ); + } + + #[test] + fn preceding_siblings_reversed() { + let tree = test_tree(); + assert!(tree + .read() + .root() + .preceding_siblings() + .next_back() + .is_none()); + assert_eq!( + [PARAGRAPH_0_ID, PARAGRAPH_1_IGNORED_ID, PARAGRAPH_2_ID], + tree.read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .preceding_siblings() + .rev() + .map(|node| node.id()) + .collect::>()[..] + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .preceding_siblings() + .next_back() + .is_none()); + } + + #[test] + fn following_unignored_siblings() { + let tree = test_tree(); + assert!(tree + .read() + .root() + .following_unignored_siblings() + .next() + .is_none()); + assert_eq!( + [ + STATIC_TEXT_1_0_ID, + PARAGRAPH_2_ID, + STATIC_TEXT_3_0_0_ID, + BUTTON_3_1_ID + ], + tree.read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .following_unignored_siblings() + .map(|node| node.id()) + .collect::>()[..] + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .following_unignored_siblings() + .next() + .is_none()); + } + + #[test] + fn following_unignored_siblings_reversed() { + let tree = test_tree(); + assert!(tree + .read() + .root() + .following_unignored_siblings() + .next_back() + .is_none()); + assert_eq!( + [ + BUTTON_3_1_ID, + STATIC_TEXT_3_0_0_ID, + PARAGRAPH_2_ID, + STATIC_TEXT_1_0_ID + ], + tree.read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .following_unignored_siblings() + .rev() + .map(|node| node.id()) + .collect::>()[..] + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .following_unignored_siblings() + .next_back() + .is_none()); + } + + #[test] + fn preceding_unignored_siblings() { + let tree = test_tree(); + assert!(tree + .read() + .root() + .preceding_unignored_siblings() + .next() + .is_none()); + assert_eq!( + [PARAGRAPH_2_ID, STATIC_TEXT_1_0_ID, PARAGRAPH_0_ID], + tree.read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .preceding_unignored_siblings() + .map(|node| node.id()) + .collect::>()[..] + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .preceding_unignored_siblings() + .next() + .is_none()); + } + + #[test] + fn preceding_unignored_siblings_reversed() { + let tree = test_tree(); + assert!(tree + .read() + .root() + .preceding_unignored_siblings() + .next_back() + .is_none()); + assert_eq!( + [PARAGRAPH_0_ID, STATIC_TEXT_1_0_ID, PARAGRAPH_2_ID], + tree.read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .preceding_unignored_siblings() + .rev() + .map(|node| node.id()) + .collect::>()[..] + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .preceding_unignored_siblings() + .next_back() + .is_none()); + } + + #[test] + fn unignored_children() { + let tree = test_tree(); + assert_eq!( + [ + PARAGRAPH_0_ID, + STATIC_TEXT_1_0_ID, + PARAGRAPH_2_ID, + STATIC_TEXT_3_0_0_ID, + BUTTON_3_1_ID + ], + tree.read() + .root() + .unignored_children() + .map(|node| node.id()) + .collect::>()[..] + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .unignored_children() + .next() + .is_none()); + assert!(tree + .read() + .node_by_id(STATIC_TEXT_0_0_IGNORED_ID) + .unwrap() + .unignored_children() + .next() + .is_none()); + } + + #[test] + fn unignored_children_reversed() { + let tree = test_tree(); + assert_eq!( + [ + BUTTON_3_1_ID, + STATIC_TEXT_3_0_0_ID, + PARAGRAPH_2_ID, + STATIC_TEXT_1_0_ID, + PARAGRAPH_0_ID + ], + tree.read() + .root() + .unignored_children() + .rev() + .map(|node| node.id()) + .collect::>()[..] + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .unignored_children() + .next_back() + .is_none()); + assert!(tree + .read() + .node_by_id(STATIC_TEXT_0_0_IGNORED_ID) + .unwrap() + .unignored_children() + .next_back() + .is_none()); + } +} diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index a738501d9..f74c5713d 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -10,3 +10,107 @@ pub use tree::{Reader as TreeReader, Tree}; pub(crate) mod node; pub use node::{Node, WeakNode}; + +pub(crate) mod iterators; +pub use iterators::{ + FollowingSiblings, FollowingUnignoredSiblings, PrecedingSiblings, PrecedingUnignoredSiblings, + UnignoredChildren, +}; + +#[cfg(test)] +mod tests { + use accesskit_schema::{Node, NodeId, Role, StringEncoding, Tree, TreeId, TreeUpdate}; + use std::num::NonZeroU64; + use std::sync::Arc; + + pub const ROOT_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(1) }); + pub const PARAGRAPH_0_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(2) }); + pub const STATIC_TEXT_0_0_IGNORED_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(3) }); + pub const PARAGRAPH_1_IGNORED_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(4) }); + pub const STATIC_TEXT_1_0_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(5) }); + pub const PARAGRAPH_2_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(6) }); + pub const STATIC_TEXT_2_0_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(7) }); + pub const PARAGRAPH_3_IGNORED_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(8) }); + pub const LINK_3_0_IGNORED_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(9) }); + pub const STATIC_TEXT_3_0_0_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(10) }); + pub const BUTTON_3_1_ID: NodeId = NodeId(unsafe { NonZeroU64::new_unchecked(11) }); + + pub fn test_tree() -> Arc { + let root = Node { + children: vec![ + PARAGRAPH_0_ID, + PARAGRAPH_1_IGNORED_ID, + PARAGRAPH_2_ID, + PARAGRAPH_3_IGNORED_ID, + ], + ..Node::new(ROOT_ID, Role::RootWebArea) + }; + let paragraph_0 = Node { + children: vec![STATIC_TEXT_0_0_IGNORED_ID], + ..Node::new(PARAGRAPH_0_ID, Role::Paragraph) + }; + let static_text_0_0_ignored = Node { + ignored: true, + name: Some("static_text_0_0_ignored".to_string()), + ..Node::new(STATIC_TEXT_0_0_IGNORED_ID, Role::StaticText) + }; + let paragraph_1_ignored = Node { + children: vec![STATIC_TEXT_1_0_ID], + ignored: true, + ..Node::new(PARAGRAPH_1_IGNORED_ID, Role::Paragraph) + }; + let static_text_1_0 = Node { + name: Some("static_text_1_0".to_string()), + ..Node::new(STATIC_TEXT_1_0_ID, Role::StaticText) + }; + let paragraph_2 = Node { + children: vec![STATIC_TEXT_2_0_ID], + ..Node::new(PARAGRAPH_2_ID, Role::Paragraph) + }; + let static_text_2_0 = Node { + name: Some("static_text_2_0".to_string()), + ..Node::new(STATIC_TEXT_2_0_ID, Role::StaticText) + }; + let paragraph_3_ignored = Node { + children: vec![LINK_3_0_IGNORED_ID, BUTTON_3_1_ID], + ignored: true, + ..Node::new(PARAGRAPH_3_IGNORED_ID, Role::Paragraph) + }; + let link_3_0_ignored = Node { + children: vec![STATIC_TEXT_3_0_0_ID], + ignored: true, + linked: true, + ..Node::new(LINK_3_0_IGNORED_ID, Role::Link) + }; + let static_text_3_0_0 = Node { + name: Some("static_text_3_0_0".to_string()), + ..Node::new(STATIC_TEXT_3_0_0_ID, Role::StaticText) + }; + let button_3_1 = Node { + name: Some("button_3_1".to_string()), + ..Node::new(BUTTON_3_1_ID, Role::Button) + }; + let initial_update = TreeUpdate { + clear: None, + nodes: vec![ + root, + paragraph_0, + static_text_0_0_ignored, + paragraph_1_ignored, + static_text_1_0, + paragraph_2, + static_text_2_0, + paragraph_3_ignored, + link_3_0_ignored, + static_text_3_0_0, + button_3_1, + ], + tree: Some(Tree::new( + TreeId("test_tree".to_string()), + StringEncoding::Utf8, + )), + root: Some(ROOT_ID), + }; + crate::tree::Tree::new(initial_update) + } +} diff --git a/consumer/src/node.rs b/consumer/src/node.rs index ed60bb19c..db57acd5c 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -8,6 +8,10 @@ use std::sync::{Arc, Weak}; use accesskit_schema::{NodeId, Rect, Role}; +use crate::iterators::{ + FollowingSiblings, FollowingUnignoredSiblings, PrecedingSiblings, PrecedingUnignoredSiblings, + UnignoredChildren, +}; use crate::tree::{NodeState, ParentAndIndex, Reader as TreeReader, Tree}; use crate::NodeData; @@ -56,6 +60,15 @@ impl Node<'_> { } } + pub fn parent_and_index(&self) -> Option<(Node<'_>, usize)> { + self.state + .parent_and_index + .as_ref() + .map(|ParentAndIndex(parent, index)| { + (self.tree_reader.node_by_id(*parent).unwrap(), *index) + }) + } + pub fn children( &self, ) -> impl DoubleEndedIterator> @@ -67,7 +80,108 @@ impl Node<'_> { .map(move |id| self.tree_reader.node_by_id(*id).unwrap()) } - // TODO: get unignored children; see Chromium's ui/accessibility/ax_node.cc + pub fn unignored_children( + &self, + ) -> impl DoubleEndedIterator> + FusedIterator> { + UnignoredChildren::new(self).map(move |id| self.tree_reader.node_by_id(id).unwrap()) + } + + pub fn following_siblings( + &self, + ) -> impl DoubleEndedIterator> + + ExactSizeIterator> + + FusedIterator> { + FollowingSiblings::new(self).map(move |id| self.tree_reader.node_by_id(id).unwrap()) + } + + pub fn following_unignored_siblings( + &self, + ) -> impl DoubleEndedIterator> + FusedIterator> { + FollowingUnignoredSiblings::new(self) + .map(move |id| self.tree_reader.node_by_id(id).unwrap()) + } + + pub fn preceding_siblings( + &self, + ) -> impl DoubleEndedIterator> + + ExactSizeIterator> + + FusedIterator> { + PrecedingSiblings::new(self).map(move |id| self.tree_reader.node_by_id(id).unwrap()) + } + + pub fn preceding_unignored_siblings( + &self, + ) -> impl DoubleEndedIterator> + FusedIterator> { + PrecedingUnignoredSiblings::new(self) + .map(move |id| self.tree_reader.node_by_id(id).unwrap()) + } + + pub fn deepest_first_child(&self) -> Option> { + let mut deepest_child = *self.data().children.get(0)?; + while let Some(first_child) = self + .tree_reader + .node_by_id(deepest_child) + .unwrap() + .data() + .children + .get(0) + { + deepest_child = *first_child; + } + self.tree_reader.node_by_id(deepest_child) + } + + pub fn deepest_first_unignored_child(&self) -> Option> { + let mut deepest_child = self.first_unignored_child()?; + while let Some(first_child) = self + .tree_reader + .node_by_id(deepest_child) + .unwrap() + .first_unignored_child() + { + deepest_child = first_child; + } + self.tree_reader.node_by_id(deepest_child) + } + + pub fn deepest_last_child(&self) -> Option> { + let mut deepest_child = *self.data().children.iter().next_back()?; + while let Some(last_child) = self + .tree_reader + .node_by_id(deepest_child) + .unwrap() + .data() + .children + .iter() + .next_back() + { + deepest_child = *last_child; + } + self.tree_reader.node_by_id(deepest_child) + } + + pub fn deepest_last_unignored_child(&self) -> Option> { + let mut deepest_child = self.last_unignored_child()?; + while let Some(last_child) = self + .tree_reader + .node_by_id(deepest_child) + .unwrap() + .last_unignored_child() + { + deepest_child = last_child; + } + self.tree_reader.node_by_id(deepest_child) + } + + pub fn is_descendant_of(&self, ancestor: &Node) -> bool { + if self.id() == ancestor.id() { + return true; + } + if let Some(parent) = self.parent() { + return parent.is_descendant_of(ancestor); + } + false + } pub fn global_id(&self) -> String { format!("{}:{}", self.tree_reader.id().0, self.id().0) @@ -107,6 +221,30 @@ impl Node<'_> { None } } + + pub(crate) fn first_unignored_child(&self) -> Option { + for child in self.children() { + if !child.is_ignored() { + return Some(child.id()); + } + if let Some(descendant) = child.first_unignored_child() { + return Some(descendant); + } + } + None + } + + pub(crate) fn last_unignored_child(&self) -> Option { + for child in self.children().rev() { + if !child.is_ignored() { + return Some(child.id()); + } + if let Some(descendant) = child.last_unignored_child() { + return Some(descendant); + } + } + None + } } #[derive(Clone)] @@ -135,3 +273,175 @@ impl Node<'_> { } } } + +#[cfg(test)] +mod tests { + use crate::tests::*; + + #[test] + fn parent_and_index() { + let tree = test_tree(); + assert!(tree.read().root().parent_and_index().is_none()); + assert_eq!( + Some((ROOT_ID, 0)), + tree.read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .parent_and_index() + .map(|(parent, index)| (parent.id(), index)) + ); + assert_eq!( + Some((PARAGRAPH_0_ID, 0)), + tree.read() + .node_by_id(STATIC_TEXT_0_0_IGNORED_ID) + .unwrap() + .parent_and_index() + .map(|(parent, index)| (parent.id(), index)) + ); + assert_eq!( + Some((ROOT_ID, 1)), + tree.read() + .node_by_id(PARAGRAPH_1_IGNORED_ID) + .unwrap() + .parent_and_index() + .map(|(parent, index)| (parent.id(), index)) + ); + } + + #[test] + fn deepest_first_child() { + let tree = test_tree(); + assert_eq!( + STATIC_TEXT_0_0_IGNORED_ID, + tree.read().root().deepest_first_child().unwrap().id() + ); + assert_eq!( + STATIC_TEXT_0_0_IGNORED_ID, + tree.read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .deepest_first_child() + .unwrap() + .id() + ); + assert!(tree + .read() + .node_by_id(STATIC_TEXT_0_0_IGNORED_ID) + .unwrap() + .deepest_first_child() + .is_none()); + } + + #[test] + fn deepest_first_unignored_child() { + let tree = test_tree(); + assert_eq!( + PARAGRAPH_0_ID, + tree.read() + .root() + .deepest_first_unignored_child() + .unwrap() + .id() + ); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .deepest_first_unignored_child() + .is_none()); + assert!(tree + .read() + .node_by_id(STATIC_TEXT_0_0_IGNORED_ID) + .unwrap() + .deepest_first_unignored_child() + .is_none()); + } + + #[test] + fn deepest_last_child() { + let tree = test_tree(); + assert_eq!( + BUTTON_3_1_ID, + tree.read().root().deepest_last_child().unwrap().id() + ); + assert_eq!( + BUTTON_3_1_ID, + tree.read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .deepest_last_child() + .unwrap() + .id() + ); + assert!(tree + .read() + .node_by_id(BUTTON_3_1_ID) + .unwrap() + .deepest_last_child() + .is_none()); + } + + #[test] + fn deepest_last_unignored_child() { + let tree = test_tree(); + assert_eq!( + BUTTON_3_1_ID, + tree.read() + .root() + .deepest_last_unignored_child() + .unwrap() + .id() + ); + assert_eq!( + BUTTON_3_1_ID, + tree.read() + .node_by_id(PARAGRAPH_3_IGNORED_ID) + .unwrap() + .deepest_last_unignored_child() + .unwrap() + .id() + ); + assert!(tree + .read() + .node_by_id(BUTTON_3_1_ID) + .unwrap() + .deepest_last_unignored_child() + .is_none()); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .deepest_last_unignored_child() + .is_none()); + } + + #[test] + fn is_descendant_of() { + let tree = test_tree(); + assert!(tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .is_descendant_of(&tree.read().root())); + assert!(tree + .read() + .node_by_id(STATIC_TEXT_0_0_IGNORED_ID) + .unwrap() + .is_descendant_of(&tree.read().root())); + assert!(tree + .read() + .node_by_id(STATIC_TEXT_0_0_IGNORED_ID) + .unwrap() + .is_descendant_of(&tree.read().node_by_id(PARAGRAPH_0_ID).unwrap())); + assert!(!tree + .read() + .node_by_id(STATIC_TEXT_0_0_IGNORED_ID) + .unwrap() + .is_descendant_of(&tree.read().node_by_id(PARAGRAPH_2_ID).unwrap())); + assert!(!tree + .read() + .node_by_id(PARAGRAPH_0_ID) + .unwrap() + .is_descendant_of(&tree.read().node_by_id(PARAGRAPH_2_ID).unwrap())); + } +}