Commit 96e024f9 authored by Alison Maher's avatar Alison Maher Committed by Commit Bot

Implement ITextRangeProvider::GetChildren

GetChildren retrieves a collection of all embedded objects that fall
within a text range.

More info here:
https://docs.microsoft.com/en-us/windows/desktop/api/uiautomationcore/nf-uiautomationcore-itextrangeprovider-getchildren

Unit test added to AXPlatformNodeTextRangeProviderTest.

Support was added to AXPlatformNodeDelegate and BrowserAccessibility
to retrieve the descendant nodes that are exposed to the platform API.
Unit test added for this to BrowserAccessibilityTest.

Bug: 928948
Change-Id: Id10b9ada1a8acabfea685bb0d95a4420007dc7ec
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1529077
Commit-Queue: Alison Maher <almaher@microsoft.com>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#644389}
parent 9606835b
...@@ -1067,6 +1067,22 @@ BrowserAccessibility::FindTextBoundariesAtOffset( ...@@ -1067,6 +1067,22 @@ BrowserAccessibility::FindTextBoundariesAtOffset(
} }
} }
const std::vector<gfx::NativeViewAccessible>
BrowserAccessibility::GetDescendants() const {
std::vector<gfx::NativeViewAccessible> descendants;
if (PlatformChildCount() > 0) {
BrowserAccessibility* next_sibling_node = GetNextSibling();
BrowserAccessibility* next_descendant_node =
BrowserAccessibilityManager::NextInTreeOrder(this);
while (next_descendant_node && next_descendant_node != next_sibling_node) {
descendants.emplace_back(next_descendant_node->GetNativeViewAccessible());
next_descendant_node =
BrowserAccessibilityManager::NextInTreeOrder(next_descendant_node);
}
}
return descendants;
}
gfx::NativeViewAccessible BrowserAccessibility::GetNativeViewAccessible() { gfx::NativeViewAccessible BrowserAccessibility::GetNativeViewAccessible() {
// TODO(703369) On Windows, where we have started to migrate to an // TODO(703369) On Windows, where we have started to migrate to an
// AXPlatformNode implementation, the BrowserAccessibilityWin subclass has // AXPlatformNode implementation, the BrowserAccessibilityWin subclass has
......
...@@ -376,6 +376,8 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate { ...@@ -376,6 +376,8 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate {
int offset, int offset,
ax::mojom::TextAffinity affinity) const override; ax::mojom::TextAffinity affinity) const override;
const std::vector<gfx::NativeViewAccessible> GetDescendants() const override;
bool IsTable() const override; bool IsTable() const override;
int32_t GetTableColCount() const override; int32_t GetTableColCount() const override;
int32_t GetTableRowCount() const override; int32_t GetTableRowCount() const override;
......
...@@ -11,6 +11,24 @@ ...@@ -11,6 +11,24 @@
namespace content { namespace content {
#define EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, \
expected_descendants) \
{ \
size_t count = descendants.size(); \
EXPECT_EQ(count, expected_descendants.size()); \
for (size_t i = 0; i < count; ++i) { \
EXPECT_EQ(ui::AXPlatformNode::FromNativeViewAccessible(descendants[i]) \
->GetDelegate() \
->GetData() \
.ToString(), \
ui::AXPlatformNode::FromNativeViewAccessible( \
expected_descendants[i]) \
->GetDelegate() \
->GetData() \
.ToString()); \
} \
}
class BrowserAccessibilityTest : public testing::Test { class BrowserAccessibilityTest : public testing::Test {
public: public:
BrowserAccessibilityTest(); BrowserAccessibilityTest();
...@@ -65,4 +83,82 @@ TEST_F(BrowserAccessibilityTest, TestCanFireEvents) { ...@@ -65,4 +83,82 @@ TEST_F(BrowserAccessibilityTest, TestCanFireEvents) {
manager.reset(); manager.reset();
} }
#if defined(OS_WIN) || BUILDFLAG(USE_ATK)
TEST_F(BrowserAccessibilityTest, TestGetDescendants) {
// Set up ax tree with the following structure:
//
// root____________
// | |
// para1___ text3
// | |
// text1 text2
ui::AXNodeData text1;
text1.id = 111;
text1.role = ax::mojom::Role::kStaticText;
text1.SetName("One two three.");
ui::AXNodeData text2;
text2.id = 112;
text2.role = ax::mojom::Role::kStaticText;
text2.SetName("Two three four.");
ui::AXNodeData text3;
text3.id = 113;
text3.role = ax::mojom::Role::kStaticText;
text3.SetName("Three four five.");
ui::AXNodeData para1;
para1.id = 11;
para1.role = ax::mojom::Role::kParagraph;
para1.child_ids.push_back(text1.id);
para1.child_ids.push_back(text2.id);
ui::AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids.push_back(para1.id);
root.child_ids.push_back(text3.id);
std::unique_ptr<BrowserAccessibilityManager> manager(
BrowserAccessibilityManager::Create(
MakeAXTreeUpdate(root, para1, text1, text2, text3), nullptr,
new BrowserAccessibilityFactory()));
BrowserAccessibility* root_obj = manager->GetRoot();
BrowserAccessibility* para_obj = root_obj->PlatformGetChild(0);
BrowserAccessibility* text1_obj = manager->GetFromID(111);
BrowserAccessibility* text2_obj = manager->GetFromID(112);
BrowserAccessibility* text3_obj = manager->GetFromID(113);
// Leaf nodes should have no children.
std::vector<gfx::NativeViewAccessible> descendants =
text1_obj->GetDescendants();
std::vector<gfx::NativeViewAccessible> expected_descendants = {};
EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
descendants = text2_obj->GetDescendants();
EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
descendants = text3_obj->GetDescendants();
EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
// Verify that para1 has two children (text1 and tex2).
descendants = para_obj->GetDescendants();
expected_descendants = {text1_obj->GetNativeViewAccessible(),
text2_obj->GetNativeViewAccessible()};
EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
// Calling GetChildNodeIds on the root should encompass the entire
// right and left subtrees (para1, text1, text2, and text3).
descendants = root_obj->GetDescendants();
expected_descendants = {para_obj->GetNativeViewAccessible(),
text1_obj->GetNativeViewAccessible(),
text2_obj->GetNativeViewAccessible(),
text3_obj->GetNativeViewAccessible()};
EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
manager.reset();
}
#endif // defined(OS_WIN) || BUILDFLAG(USE_ATK)
} // namespace content } // namespace content
...@@ -167,6 +167,10 @@ class AX_EXPORT AXPlatformNodeDelegate { ...@@ -167,6 +167,10 @@ class AX_EXPORT AXPlatformNodeDelegate {
int offset, int offset,
ax::mojom::TextAffinity affinity) const = 0; ax::mojom::TextAffinity affinity) const = 0;
// Return a vector of all the descendants of this delegate's node.
virtual const std::vector<gfx::NativeViewAccessible> GetDescendants()
const = 0;
// //
// Tables. All of these should be called on a node that's a table-like // Tables. All of these should be called on a node that's a table-like
// role. // role.
......
...@@ -296,6 +296,11 @@ AXPlatformNodeDelegateBase::FindTextBoundariesAtOffset( ...@@ -296,6 +296,11 @@ AXPlatformNodeDelegateBase::FindTextBoundariesAtOffset(
return base::nullopt; return base::nullopt;
} }
const std::vector<gfx::NativeViewAccessible>
AXPlatformNodeDelegateBase::GetDescendants() const {
return {};
}
bool AXPlatformNodeDelegateBase::IsOrderedSetItem() const { bool AXPlatformNodeDelegateBase::IsOrderedSetItem() const {
return false; return false;
} }
......
...@@ -129,6 +129,8 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate { ...@@ -129,6 +129,8 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
int offset, int offset,
ax::mojom::TextAffinity affinity) const override; ax::mojom::TextAffinity affinity) const override;
const std::vector<gfx::NativeViewAccessible> GetDescendants() const override;
// //
// Tables. All of these should be called on a node that's a table-like // Tables. All of these should be called on a node that's a table-like
// role. // role.
......
...@@ -305,7 +305,43 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ScrollIntoView( ...@@ -305,7 +305,43 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ScrollIntoView(
STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetChildren( STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetChildren(
SAFEARRAY** children) { SAFEARRAY** children) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_GETCHILDREN); WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_GETCHILDREN);
return E_NOTIMPL; UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
std::vector<gfx::NativeViewAccessible> descendants;
AXPositionInstance common_ancestor =
start_->LowestCommonAncestor(*end_.get());
if (common_ancestor->GetAnchor()->child_count() > 0) {
descendants = owner()
->GetDelegate()
->GetFromNodeID(common_ancestor->anchor_id())
->GetDelegate()
->GetDescendants();
}
SAFEARRAY* safe_array =
SafeArrayCreateVector(VT_UNKNOWN, 0, descendants.size());
if (!safe_array)
return E_OUTOFMEMORY;
if (safe_array->rgsabound->cElements != descendants.size()) {
DCHECK(safe_array);
SafeArrayDestroy(safe_array);
return E_OUTOFMEMORY;
}
LONG i = 0;
for (const gfx::NativeViewAccessible& descendant : descendants) {
IRawElementProviderSimple* raw_provider;
descendant->QueryInterface(IID_PPV_ARGS(&raw_provider));
SafeArrayPutElement(safe_array, &i, raw_provider);
++i;
}
*children = safe_array;
return S_OK;
} }
base::string16 AXPlatformNodeTextRangeProviderWin::GetString() { base::string16 AXPlatformNodeTextRangeProviderWin::GetString() {
......
...@@ -38,7 +38,8 @@ namespace ui { ...@@ -38,7 +38,8 @@ namespace ui {
#define EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(safearray, expected_property_values) \ #define EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(safearray, expected_property_values) \
{ \ { \
EXPECT_EQ(8U, ::SafeArrayGetElemsize(safearray)); \ EXPECT_EQ(sizeof(V_R8(LPVARIANT(NULL))), \
::SafeArrayGetElemsize(safearray)); \
ASSERT_EQ(1u, SafeArrayGetDim(safearray)); \ ASSERT_EQ(1u, SafeArrayGetDim(safearray)); \
LONG array_lower_bound; \ LONG array_lower_bound; \
ASSERT_HRESULT_SUCCEEDED( \ ASSERT_HRESULT_SUCCEEDED( \
...@@ -57,6 +58,29 @@ namespace ui { ...@@ -57,6 +58,29 @@ namespace ui {
ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(safearray)); \ ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(safearray)); \
} }
#define EXPECT_UIA_VT_UNKNOWN_SAFEARRAY_EQ(safearray, \
expected_property_values) \
{ \
EXPECT_EQ(sizeof(V_UNKNOWN(LPVARIANT(NULL))), \
::SafeArrayGetElemsize(safearray)); \
EXPECT_EQ(1u, SafeArrayGetDim(safearray)); \
LONG array_lower_bound; \
EXPECT_HRESULT_SUCCEEDED( \
SafeArrayGetLBound(safearray, 1, &array_lower_bound)); \
LONG array_upper_bound; \
EXPECT_HRESULT_SUCCEEDED( \
SafeArrayGetUBound(safearray, 1, &array_upper_bound)); \
ComPtr<IRawElementProviderSimple>* array_data; \
EXPECT_HRESULT_SUCCEEDED(::SafeArrayAccessData( \
safearray, reinterpret_cast<void**>(&array_data))); \
size_t count = array_upper_bound - array_lower_bound + 1; \
EXPECT_EQ(expected_property_values.size(), count); \
for (size_t i = 0; i < count; ++i) { \
EXPECT_EQ(array_data[i].Get(), expected_property_values[i].Get()); \
} \
EXPECT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(safearray)); \
}
class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest { class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
public: public:
const AXNodePosition::AXPositionInstance& GetStart( const AXNodePosition::AXPositionInstance& GetStart(
...@@ -1055,4 +1079,131 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, ...@@ -1055,4 +1079,131 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_EQ(*GetEnd(text_range.Get()), *GetEnd(more_text_range.Get())); EXPECT_EQ(*GetEnd(text_range.Get()), *GetEnd(more_text_range.Get()));
} }
TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetChildren) {
// Set up ax tree with the following structure:
//
// root______________________
// | |
// text_node1___ text_node2
// | |
// text_node3 text_node4
ui::AXNodeData root_data;
root_data.id = 0;
root_data.role = ax::mojom::Role::kRootWebArea;
ui::AXNodeData text_node1;
text_node1.id = 1;
text_node1.role = ax::mojom::Role::kStaticText;
root_data.child_ids.push_back(1);
ui::AXNodeData text_node2;
text_node2.id = 2;
text_node2.role = ax::mojom::Role::kStaticText;
root_data.child_ids.push_back(2);
ui::AXNodeData text_node3;
text_node3.id = 3;
text_node3.role = ax::mojom::Role::kStaticText;
text_node1.child_ids.push_back(3);
ui::AXNodeData text_node4;
text_node4.id = 4;
text_node4.role = ax::mojom::Role::kStaticText;
text_node1.child_ids.push_back(4);
ui::AXTreeUpdate update;
ui::AXTreeData tree_data;
tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
update.tree_data = tree_data;
update.has_tree_data = true;
update.root_id = root_data.id;
update.nodes.push_back(root_data);
update.nodes.push_back(text_node1);
update.nodes.push_back(text_node2);
update.nodes.push_back(text_node3);
update.nodes.push_back(text_node4);
Init(update);
// Set up variables from the tree for testing.
AXNode* rootnode = GetRootNode();
AXNodePosition::SetTreeForTesting(tree_.get());
AXNode* node1 = rootnode->children()[0];
AXNode* node2 = rootnode->children()[1];
AXNode* node3 = node1->children()[0];
AXNode* node4 = node1->children()[1];
ComPtr<IRawElementProviderSimple> root_node_raw =
QueryInterfaceFromNode<IRawElementProviderSimple>(rootnode);
ComPtr<IRawElementProviderSimple> text_node_raw1 =
QueryInterfaceFromNode<IRawElementProviderSimple>(node1);
ComPtr<IRawElementProviderSimple> text_node_raw2 =
QueryInterfaceFromNode<IRawElementProviderSimple>(node2);
ComPtr<IRawElementProviderSimple> text_node_raw3 =
QueryInterfaceFromNode<IRawElementProviderSimple>(node3);
ComPtr<IRawElementProviderSimple> text_node_raw4 =
QueryInterfaceFromNode<IRawElementProviderSimple>(node4);
// Test text_node3 - leaf nodes should have no children.
ComPtr<ITextProvider> text_provider;
EXPECT_HRESULT_SUCCEEDED(
text_node_raw3->GetPatternProvider(UIA_TextPatternId, &text_provider));
ComPtr<ITextRangeProvider> text_range_provider;
EXPECT_HRESULT_SUCCEEDED(
text_provider->get_DocumentRange(&text_range_provider));
base::win::ScopedSafearray children;
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetChildren(children.Receive()));
std::vector<ComPtr<IRawElementProviderSimple>> expected_values = {};
EXPECT_UIA_VT_UNKNOWN_SAFEARRAY_EQ(children.Get(), expected_values);
// Test text_node2 - leaf nodes should have no children.
EXPECT_HRESULT_SUCCEEDED(
text_node_raw2->GetPatternProvider(UIA_TextPatternId, &text_provider));
EXPECT_HRESULT_SUCCEEDED(
text_provider->get_DocumentRange(&text_range_provider));
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetChildren(children.Receive()));
EXPECT_UIA_VT_UNKNOWN_SAFEARRAY_EQ(children.Get(), expected_values);
// Test text_node1 - children should include text_node3 and text_node4.
EXPECT_HRESULT_SUCCEEDED(
text_node_raw1->GetPatternProvider(UIA_TextPatternId, &text_provider));
EXPECT_HRESULT_SUCCEEDED(
text_provider->get_DocumentRange(&text_range_provider));
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetChildren(children.Receive()));
expected_values = {text_node_raw3, text_node_raw4};
EXPECT_UIA_VT_UNKNOWN_SAFEARRAY_EQ(children.Get(), expected_values);
// Test root_node - children should include the entire left subtree and
// the entire right subtree.
EXPECT_HRESULT_SUCCEEDED(
root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
EXPECT_HRESULT_SUCCEEDED(
text_provider->get_DocumentRange(&text_range_provider));
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetChildren(children.Receive()));
expected_values = {text_node_raw1, text_node_raw3, text_node_raw4,
text_node_raw2};
EXPECT_UIA_VT_UNKNOWN_SAFEARRAY_EQ(children.Get(), expected_values);
AXNodePosition::SetTreeForTesting(nullptr);
}
} // namespace ui } // namespace ui
...@@ -503,4 +503,26 @@ int32_t TestAXNodeWrapper::GetSetSize() const { ...@@ -503,4 +503,26 @@ int32_t TestAXNodeWrapper::GetSetSize() const {
return node_->GetSetSize(); return node_->GetSetSize();
} }
// Recursive helper function for GetDescendants. Aggregates all of the
// descendants for a given node within the descendants vector.
void TestAXNodeWrapper::Descendants(
const AXNode* node,
std::vector<gfx::NativeViewAccessible>& descendants) const {
std::vector<AXNode*> child_nodes = node->children();
for (AXNode* child : child_nodes) {
descendants.emplace_back(ax_platform_node()
->GetDelegate()
->GetFromNodeID(child->id())
->GetNativeViewAccessible());
Descendants(child, descendants);
}
}
const std::vector<gfx::NativeViewAccessible> TestAXNodeWrapper::GetDescendants()
const {
std::vector<gfx::NativeViewAccessible> descendants;
Descendants(node_, descendants);
return descendants;
}
} // namespace ui } // namespace ui
...@@ -104,6 +104,9 @@ class TestAXNodeWrapper : public AXPlatformNodeDelegateBase { ...@@ -104,6 +104,9 @@ class TestAXNodeWrapper : public AXPlatformNodeDelegateBase {
bool IsOrderedSet() const override; bool IsOrderedSet() const override;
int32_t GetPosInSet() const override; int32_t GetPosInSet() const override;
int32_t GetSetSize() const override; int32_t GetSetSize() const override;
const std::vector<gfx::NativeViewAccessible> GetDescendants() const override;
void Descendants(const AXNode* node,
std::vector<gfx::NativeViewAccessible>& descendants) const;
private: private:
TestAXNodeWrapper(AXTree* tree, AXNode* node); TestAXNodeWrapper(AXTree* tree, AXNode* node);
......
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