1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/accessibility/ax_tree_source_checker.h"
6 
7 #include "base/strings/string_util.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9 #include "ui/accessibility/ax_enums.mojom.h"
10 #include "ui/accessibility/ax_node_data.h"
11 #include "ui/accessibility/ax_tree_source.h"
12 
13 namespace ui {
14 namespace {
15 
16 struct FakeAXTreeData {};
17 
18 struct FakeAXNode {
19   int32_t id;
20   ax::mojom::Role role;
21   std::vector<int32_t> child_ids;
22   int32_t parent_id;
23 };
24 
25 // It's distracting to see an empty bounding box from every node, so do a
26 // search-and-replace to get rid of those strings.
CleanAXNodeDataString(std::string * error_str)27 void CleanAXNodeDataString(std::string* error_str) {
28   base::ReplaceSubstringsAfterOffset(error_str, 0, " (0, 0)-(0, 0)", "");
29 }
30 
31 // A simple implementation of AXTreeSource initialized from a simple static
32 // vector of node data, where both the child and parent connections are
33 // explicit. This allows us to test that AXTreeSourceChecker properly warns
34 // about errors in accessibility trees that have inconsistent parent/child
35 // links.
36 class FakeAXTreeSource
37     : public AXTreeSource<const FakeAXNode*, ui::AXNodeData, FakeAXTreeData> {
38  public:
FakeAXTreeSource(std::vector<FakeAXNode> nodes,int32_t root_id)39   FakeAXTreeSource(std::vector<FakeAXNode> nodes, int32_t root_id)
40       : nodes_(nodes), root_id_(root_id) {
41     for (size_t i = 0; i < nodes_.size(); ++i)
42       id_to_node_[nodes_[i].id] = &nodes_[i];
43   }
44 
45   // AXTreeSource overrides.
GetTreeData(FakeAXTreeData * data) const46   bool GetTreeData(FakeAXTreeData* data) const override { return true; }
47 
GetRoot() const48   const FakeAXNode* GetRoot() const override { return GetFromId(root_id_); }
49 
GetFromId(int32_t id) const50   const FakeAXNode* GetFromId(int32_t id) const override {
51     const auto& iter = id_to_node_.find(id);
52     if (iter != id_to_node_.end())
53       return iter->second;
54     return nullptr;
55   }
56 
GetId(const FakeAXNode * node) const57   int32_t GetId(const FakeAXNode* node) const override { return node->id; }
58 
GetChildren(const FakeAXNode * node,std::vector<const FakeAXNode * > * out_children) const59   void GetChildren(
60       const FakeAXNode* node,
61       std::vector<const FakeAXNode*>* out_children) const override {
62     for (size_t i = 0; i < node->child_ids.size(); ++i)
63       out_children->push_back(GetFromId(node->child_ids[i]));
64   }
65 
GetParent(const FakeAXNode * node) const66   const FakeAXNode* GetParent(const FakeAXNode* node) const override {
67     return GetFromId(node->parent_id);
68   }
69 
IsIgnored(const FakeAXNode * node) const70   bool IsIgnored(const FakeAXNode* node) const override { return false; }
71 
IsValid(const FakeAXNode * node) const72   bool IsValid(const FakeAXNode* node) const override {
73     return node != nullptr;
74   }
75 
IsEqual(const FakeAXNode * node1,const FakeAXNode * node2) const76   bool IsEqual(const FakeAXNode* node1,
77                const FakeAXNode* node2) const override {
78     return node1 == node2;
79   }
80 
GetNull() const81   const FakeAXNode* GetNull() const override { return nullptr; }
82 
SerializeNode(const FakeAXNode * node,AXNodeData * out_data) const83   void SerializeNode(const FakeAXNode* node,
84                      AXNodeData* out_data) const override {
85     out_data->id = node->id;
86     out_data->role = node->role;
87   }
88 
89  private:
90   std::vector<FakeAXNode> nodes_;
91   std::map<int32_t, FakeAXNode*> id_to_node_;
92   int32_t root_id_;
93 };
94 
95 }  // namespace
96 
97 using FakeAXTreeSourceChecker =
98     AXTreeSourceChecker<const FakeAXNode*, ui::AXNodeData, FakeAXTreeData>;
99 
TEST(AXTreeSourceCheckerTest,SimpleValidTree)100 TEST(AXTreeSourceCheckerTest, SimpleValidTree) {
101   std::vector<FakeAXNode> nodes = {
102       {1, ax::mojom::Role::kRootWebArea, {2}, -1},
103       {2, ax::mojom::Role::kRootWebArea, {}, 1},
104   };
105   FakeAXTreeSource node_source(nodes, 1);
106   FakeAXTreeSourceChecker checker(&node_source);
107   std::string error_string;
108   EXPECT_TRUE(checker.CheckAndGetErrorString(&error_string));
109 }
110 
TEST(AXTreeSourceCheckerTest,BadRoot)111 TEST(AXTreeSourceCheckerTest, BadRoot) {
112   std::vector<FakeAXNode> nodes = {
113       {1, ax::mojom::Role::kRootWebArea, {2}, -1},
114       {2, ax::mojom::Role::kRootWebArea, {}, 1},
115   };
116   FakeAXTreeSource node_source(nodes, 3);
117   FakeAXTreeSourceChecker checker(&node_source);
118   std::string error_string;
119   EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
120   CleanAXNodeDataString(&error_string);
121   EXPECT_EQ("Root is not valid.", error_string);
122 }
123 
TEST(AXTreeSourceCheckerTest,BadNodeIdOfRoot)124 TEST(AXTreeSourceCheckerTest, BadNodeIdOfRoot) {
125   std::vector<FakeAXNode> nodes = {
126       {0, ax::mojom::Role::kRootWebArea, {2}, -1},
127       {2, ax::mojom::Role::kRootWebArea, {}, 0},
128   };
129   FakeAXTreeSource node_source(nodes, 0);
130   FakeAXTreeSourceChecker checker(&node_source);
131   std::string error_string;
132   EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
133   CleanAXNodeDataString(&error_string);
134   EXPECT_EQ(
135       "Got a node with id 0, but all node IDs should be >= 1:\n"
136       "id=0 rootWebArea child_ids=2 parent_id=-1\n"
137       "id=0 rootWebArea child_ids=2 parent_id=-1",
138       error_string);
139 }
140 
TEST(AXTreeSourceCheckerTest,BadNodeIdOfChild)141 TEST(AXTreeSourceCheckerTest, BadNodeIdOfChild) {
142   std::vector<FakeAXNode> nodes = {
143       {1, ax::mojom::Role::kRootWebArea, {-5}, -1},
144       {-5, ax::mojom::Role::kRootWebArea, {}, 1},
145   };
146   FakeAXTreeSource node_source(nodes, -5);
147   FakeAXTreeSourceChecker checker(&node_source);
148   std::string error_string;
149   EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
150   CleanAXNodeDataString(&error_string);
151   EXPECT_EQ(
152       "Got a node with id -5, but all node IDs should be >= 1:\n"
153       "id=-5 rootWebArea (no children) parent_id=1\n"
154       "id=-5 rootWebArea (no children) parent_id=1",
155       error_string);
156 }
157 
TEST(AXTreeSourceCheckerTest,RootShouldNotBeNodeWithParent)158 TEST(AXTreeSourceCheckerTest, RootShouldNotBeNodeWithParent) {
159   std::vector<FakeAXNode> nodes = {
160       {1, ax::mojom::Role::kRootWebArea, {2}, -1},
161       {2, ax::mojom::Role::kRootWebArea, {}, 1},
162   };
163   FakeAXTreeSource node_source(nodes, 2);
164   FakeAXTreeSourceChecker checker(&node_source);
165   std::string error_string;
166   EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
167   CleanAXNodeDataString(&error_string);
168   EXPECT_EQ(
169       "Node 2 is the root, so its parent should be invalid, "
170       "but we got a node with id 1.\n"
171       "Node: id=2 rootWebArea (no children) parent_id=1\n"
172       "Parent: id=1 rootWebArea child_ids=2 parent_id=-1\n"
173       "id=2 rootWebArea (no children) parent_id=1",
174       error_string);
175 }
176 
TEST(AXTreeSourceCheckerTest,MissingParent)177 TEST(AXTreeSourceCheckerTest, MissingParent) {
178   std::vector<FakeAXNode> nodes = {
179       {1, ax::mojom::Role::kRootWebArea, {2}, -1},
180       {2, ax::mojom::Role::kRootWebArea, {}, -1},
181   };
182   FakeAXTreeSource node_source(nodes, 1);
183   FakeAXTreeSourceChecker checker(&node_source);
184   std::string error_string;
185   EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
186   CleanAXNodeDataString(&error_string);
187   EXPECT_EQ(
188       "Node 2 is not the root, but its parent was invalid:\n"
189       "id=2 rootWebArea (no children) parent_id=-1\n"
190       "id=1 rootWebArea child_ids=2 parent_id=-1\n"
191       "  id=2 rootWebArea (no children) parent_id=-1",
192       error_string);
193 }
194 
TEST(AXTreeSourceCheckerTest,InvalidParent)195 TEST(AXTreeSourceCheckerTest, InvalidParent) {
196   std::vector<FakeAXNode> nodes = {
197       {1, ax::mojom::Role::kRootWebArea, {2, 3}, -1},
198       {2, ax::mojom::Role::kButton, {}, 1},
199       {3, ax::mojom::Role::kParagraph, {}, 2},
200   };
201   FakeAXTreeSource node_source(nodes, 1);
202   FakeAXTreeSourceChecker checker(&node_source);
203   std::string error_string;
204   EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
205   CleanAXNodeDataString(&error_string);
206   EXPECT_EQ(
207       "Expected node 3 to have a parent of 1, but found a parent of 2.\n"
208       "Node: id=3 paragraph (no children) parent_id=2\n"
209       "Parent: id=2 button (no children) parent_id=1\n"
210       "Expected parent: id=1 rootWebArea child_ids=2,3 parent_id=-1\n"
211       "id=1 rootWebArea child_ids=2,3 parent_id=-1\n"
212       "  id=2 button (no children) parent_id=1\n"
213       "  id=3 paragraph (no children) parent_id=2",
214       error_string);
215 }
216 
217 }  // namespace ui
218