Commit bcdfb909 authored by Nektarios Paisios's avatar Nektarios Paisios Committed by Commit Bot

Implemented tree traversal helper methods in AXObject

Will be used in AXPosition and AXRange to adjust AX selection and move AX positions to valid DOM objects.
See https://chromium-review.googlesource.com/c/chromium/src/+/954182 for the selection changes.
R=dmazzoni@chromium.org, aboxhall@chromium.org

Change-Id: I798e353262e6fea3166b74e20ef09e327ccfcd4d
Reviewed-on: https://chromium-review.googlesource.com/955728
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#542258}
parent e1aab717
...@@ -1819,6 +1819,10 @@ AXObject* AXObject::ElementAccessibilityHitTest(const IntPoint& point) const { ...@@ -1819,6 +1819,10 @@ AXObject* AXObject::ElementAccessibilityHitTest(const IntPoint& point) const {
return const_cast<AXObject*>(this); return const_cast<AXObject*>(this);
} }
int AXObject::ChildCount() const {
return static_cast<int>(Children().size());
}
const AXObject::AXObjectVector& AXObject::Children() const { const AXObject::AXObjectVector& AXObject::Children() const {
return const_cast<AXObject*>(this)->Children(); return const_cast<AXObject*>(this)->Children();
} }
...@@ -1829,6 +1833,36 @@ const AXObject::AXObjectVector& AXObject::Children() { ...@@ -1829,6 +1833,36 @@ const AXObject::AXObjectVector& AXObject::Children() {
return children_; return children_;
} }
AXObject* AXObject::FirstChild() const {
return ChildCount() ? *Children().begin() : nullptr;
}
AXObject* AXObject::LastChild() const {
return ChildCount() ? *(Children().end() - 1) : nullptr;
}
AXObject* AXObject::DeepestFirstChild() const {
if (!ChildCount())
return nullptr;
AXObject* deepest_child = FirstChild();
while (deepest_child->ChildCount())
deepest_child = deepest_child->FirstChild();
return deepest_child;
}
AXObject* AXObject::DeepestLastChild() const {
if (!ChildCount())
return nullptr;
AXObject* deepest_child = LastChild();
while (deepest_child->ChildCount())
deepest_child = deepest_child->LastChild();
return deepest_child;
}
bool AXObject::IsAncestorOf(const AXObject& descendant) const { bool AXObject::IsAncestorOf(const AXObject& descendant) const {
return descendant.IsDescendantOf(*this); return descendant.IsDescendantOf(*this);
} }
...@@ -1840,6 +1874,59 @@ bool AXObject::IsDescendantOf(const AXObject& ancestor) const { ...@@ -1840,6 +1874,59 @@ bool AXObject::IsDescendantOf(const AXObject& ancestor) const {
return !!parent; return !!parent;
} }
AXObject* AXObject::NextSibling() const {
AXObject* parent = ParentObjectUnignored();
if (!parent)
return nullptr;
if (IndexInParent() < parent->ChildCount() - 1)
return *(parent->Children().begin() + IndexInParent() + 1);
return nullptr;
}
AXObject* AXObject::PreviousSibling() const {
AXObject* parent = ParentObjectUnignored();
if (!parent)
return nullptr;
if (IndexInParent() > 0)
return *(parent->Children().begin() + IndexInParent() - 1);
return nullptr;
}
AXObject* AXObject::NextInTreeObject(bool can_wrap_to_first_element) const {
if (ChildCount())
return FirstChild();
if (NextSibling())
return NextSibling();
AXObject* current_object = const_cast<AXObject*>(this);
while (current_object->ParentObjectUnignored()) {
current_object = current_object->ParentObjectUnignored();
AXObject* sibling = current_object->NextSibling();
if (sibling)
return sibling;
}
return can_wrap_to_first_element ? current_object : nullptr;
}
AXObject* AXObject::PreviousInTreeObject(bool can_wrap_to_last_element) const {
AXObject* sibling = PreviousSibling();
if (!sibling) {
if (ParentObjectUnignored())
return ParentObjectUnignored();
return can_wrap_to_last_element ? DeepestLastChild() : nullptr;
}
if (sibling->ChildCount())
return sibling->DeepestLastChild();
return sibling;
}
AXObject* AXObject::ParentObject() const { AXObject* AXObject::ParentObject() const {
if (IsDetached()) if (IsDetached())
return nullptr; return nullptr;
......
...@@ -635,10 +635,21 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> { ...@@ -635,10 +635,21 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> {
// High-level accessibility tree access. Other modules should only use these // High-level accessibility tree access. Other modules should only use these
// functions. // functions.
int ChildCount() const;
const AXObjectVector& Children() const; const AXObjectVector& Children() const;
const AXObjectVector& Children(); const AXObjectVector& Children();
AXObject* FirstChild() const;
AXObject* LastChild() const;
AXObject* DeepestFirstChild() const;
AXObject* DeepestLastChild() const;
bool IsAncestorOf(const AXObject&) const; bool IsAncestorOf(const AXObject&) const;
bool IsDescendantOf(const AXObject&) const; bool IsDescendantOf(const AXObject&) const;
AXObject* NextSibling() const;
AXObject* PreviousSibling() const;
// Next object in tree using depth-first pre-order traversal.
AXObject* NextInTreeObject(bool can_wrap_to_first_element = false) const;
// Previous object in tree using depth-first pre-order traversal.
AXObject* PreviousInTreeObject(bool can_wrap_to_last_element = false) const;
AXObject* ParentObject() const; AXObject* ParentObject() const;
AXObject* ParentObjectIfExists() const; AXObject* ParentObjectIfExists() const;
virtual AXObject* ComputeParent() const = 0; virtual AXObject* ComputeParent() const = 0;
......
...@@ -36,4 +36,46 @@ TEST_F(AccessibilityTest, IsAncestorOf) { ...@@ -36,4 +36,46 @@ TEST_F(AccessibilityTest, IsAncestorOf) {
EXPECT_FALSE(button->IsAncestorOf(*root)); EXPECT_FALSE(button->IsAncestorOf(*root));
} }
TEST_F(AccessibilityTest, SimpleTreeNavigation) {
SetBodyInnerHTML(R"HTML(<input id='input' type='text' value='value'>"
R"<p id='paragraph'>hello<br id='br'>there</p>"
R"<button id='button'>button</button>)HTML");
const AXObject* root = GetAXRootObject();
ASSERT_NE(nullptr, root);
const AXObject* input = GetAXObjectByElementId("input");
ASSERT_NE(nullptr, input);
const AXObject* paragraph = GetAXObjectByElementId("paragraph");
ASSERT_NE(nullptr, paragraph);
const AXObject* br = GetAXObjectByElementId("br");
ASSERT_NE(nullptr, br);
const AXObject* button = GetAXObjectByElementId("button");
ASSERT_NE(nullptr, button);
EXPECT_EQ(input, root->FirstChild());
EXPECT_EQ(button, root->LastChild());
EXPECT_EQ(button, root->DeepestLastChild());
ASSERT_NE(nullptr, paragraph->FirstChild());
EXPECT_EQ(AccessibilityRole::kStaticTextRole,
paragraph->FirstChild()->RoleValue());
ASSERT_NE(nullptr, paragraph->LastChild());
EXPECT_EQ(AccessibilityRole::kStaticTextRole,
paragraph->LastChild()->RoleValue());
ASSERT_NE(nullptr, paragraph->DeepestFirstChild());
EXPECT_EQ(AccessibilityRole::kStaticTextRole,
paragraph->DeepestFirstChild()->RoleValue());
ASSERT_NE(nullptr, paragraph->DeepestLastChild());
EXPECT_EQ(AccessibilityRole::kStaticTextRole,
paragraph->DeepestLastChild()->RoleValue());
// There is a static text element in between the input and the paragraph.
EXPECT_EQ(paragraph->PreviousSibling(), input->NextSibling());
ASSERT_NE(nullptr, br->NextSibling());
EXPECT_EQ(AccessibilityRole::kStaticTextRole, br->NextSibling()->RoleValue());
ASSERT_NE(nullptr, br->PreviousSibling());
EXPECT_EQ(AccessibilityRole::kStaticTextRole,
br->PreviousSibling()->RoleValue());
}
} // namespace blink } // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment