1 // Copyright 2015 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 "content/browser/accessibility/one_shot_accessibility_tree_search.h"
6
7 #include <memory>
8
9 #include "base/macros.h"
10 #include "base/test/task_environment.h"
11 #include "content/browser/accessibility/browser_accessibility.h"
12 #include "content/browser/accessibility/browser_accessibility_manager.h"
13 #ifdef OS_ANDROID
14 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
15 #endif
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace content {
19
20 namespace {
21
22 #ifdef OS_ANDROID
23 class TestBrowserAccessibilityManager
24 : public BrowserAccessibilityManagerAndroid {
25 public:
TestBrowserAccessibilityManager(const ui::AXTreeUpdate & initial_tree)26 explicit TestBrowserAccessibilityManager(const ui::AXTreeUpdate& initial_tree)
27 : BrowserAccessibilityManagerAndroid(initial_tree, nullptr, nullptr) {}
28 };
29 #else
30 class TestBrowserAccessibilityManager : public BrowserAccessibilityManager {
31 public:
32 explicit TestBrowserAccessibilityManager(const ui::AXTreeUpdate& initial_tree)
33 : BrowserAccessibilityManager(initial_tree, nullptr) {}
34 };
35 #endif
36
37 } // namespace
38
39 class OneShotAccessibilityTreeSearchTest : public testing::TestWithParam<bool> {
40 public:
41 OneShotAccessibilityTreeSearchTest() = default;
42 ~OneShotAccessibilityTreeSearchTest() override = default;
43
44 protected:
45 void SetUp() override;
46
47 base::test::TaskEnvironment task_environment_;
48
49 std::unique_ptr<BrowserAccessibilityManager> tree_;
50
51 private:
52 DISALLOW_COPY_AND_ASSIGN(OneShotAccessibilityTreeSearchTest);
53 };
54
SetUp()55 void OneShotAccessibilityTreeSearchTest::SetUp() {
56 ui::AXNodeData root;
57 root.id = 1;
58 root.role = ax::mojom::Role::kRootWebArea;
59 root.SetName("Document");
60 root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
61 root.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren, true);
62
63 ui::AXNodeData heading;
64 heading.id = 2;
65 heading.role = ax::mojom::Role::kHeading;
66 heading.SetName("Heading");
67 heading.relative_bounds.bounds = gfx::RectF(0, 0, 800, 50);
68
69 ui::AXNodeData table;
70 table.id = 3;
71 table.role = ax::mojom::Role::kTable;
72 table.AddIntAttribute(ax::mojom::IntAttribute::kTableRowCount, 1);
73 table.AddIntAttribute(ax::mojom::IntAttribute::kTableColumnCount, 2);
74
75 ui::AXNodeData table_row;
76 table_row.id = 4;
77 table_row.role = ax::mojom::Role::kRow;
78 table_row.AddIntAttribute(ax::mojom::IntAttribute::kTableRowIndex, 0);
79
80 ui::AXNodeData table_column_header_1;
81 table_column_header_1.id = 5;
82 table_column_header_1.role = ax::mojom::Role::kColumnHeader;
83 table_column_header_1.SetName("Cell1");
84 table_column_header_1.AddIntAttribute(
85 ax::mojom::IntAttribute::kTableCellRowIndex, 0);
86 table_column_header_1.AddIntAttribute(
87 ax::mojom::IntAttribute::kTableCellColumnIndex, 0);
88
89 ui::AXNodeData table_column_header_2;
90 table_column_header_2.id = 6;
91 table_column_header_2.role = ax::mojom::Role::kColumnHeader;
92 table_column_header_2.SetName("Cell2");
93 table_column_header_2.AddIntAttribute(
94 ax::mojom::IntAttribute::kTableCellRowIndex, 0);
95 table_column_header_2.AddIntAttribute(
96 ax::mojom::IntAttribute::kTableCellColumnIndex, 1);
97
98 ui::AXNodeData list;
99 list.id = 7;
100 list.role = ax::mojom::Role::kList;
101 list.relative_bounds.bounds = gfx::RectF(0, 50, 500, 500);
102
103 ui::AXNodeData list_item_1;
104 list_item_1.id = 8;
105 list_item_1.role = ax::mojom::Role::kListItem;
106 list_item_1.SetName("Autobots");
107 list_item_1.relative_bounds.bounds = gfx::RectF(10, 10, 200, 30);
108
109 ui::AXNodeData list_item_2;
110 list_item_2.id = 9;
111 list_item_2.role = ax::mojom::Role::kListItem;
112 list_item_2.SetName("Decepticons");
113 list_item_2.relative_bounds.bounds = gfx::RectF(10, 40, 200, 60);
114
115 ui::AXNodeData footer;
116 footer.id = 10;
117 footer.role = ax::mojom::Role::kFooter;
118 footer.SetName("Footer");
119 footer.relative_bounds.bounds = gfx::RectF(0, 650, 100, 800);
120
121 table_row.child_ids = {table_column_header_1.id, table_column_header_2.id};
122 table.child_ids = {table_row.id};
123 list.child_ids = {list_item_1.id, list_item_2.id};
124 root.child_ids = {heading.id, table.id, list.id, footer.id};
125
126 tree_.reset(new TestBrowserAccessibilityManager(MakeAXTreeUpdate(
127 root, heading, table, table_row, table_column_header_1,
128 table_column_header_2, list, list_item_1, list_item_2, footer)));
129 }
130
TEST_F(OneShotAccessibilityTreeSearchTest,GetAll)131 TEST_F(OneShotAccessibilityTreeSearchTest, GetAll) {
132 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
133 ASSERT_EQ(10U, search.CountMatches());
134 }
135
TEST_F(OneShotAccessibilityTreeSearchTest,BackwardsWrapFromRoot)136 TEST_F(OneShotAccessibilityTreeSearchTest, BackwardsWrapFromRoot) {
137 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
138 search.SetDirection(OneShotAccessibilityTreeSearch::BACKWARDS);
139 search.SetResultLimit(100);
140 search.SetCanWrapToLastElement(true);
141 ASSERT_EQ(10U, search.CountMatches());
142 EXPECT_EQ(1, search.GetMatchAtIndex(0)->GetId());
143 EXPECT_EQ(10, search.GetMatchAtIndex(1)->GetId());
144 EXPECT_EQ(9, search.GetMatchAtIndex(2)->GetId());
145 EXPECT_EQ(8, search.GetMatchAtIndex(3)->GetId());
146 EXPECT_EQ(7, search.GetMatchAtIndex(4)->GetId());
147 EXPECT_EQ(6, search.GetMatchAtIndex(5)->GetId());
148 EXPECT_EQ(5, search.GetMatchAtIndex(6)->GetId());
149 EXPECT_EQ(4, search.GetMatchAtIndex(7)->GetId());
150 EXPECT_EQ(3, search.GetMatchAtIndex(8)->GetId());
151 EXPECT_EQ(2, search.GetMatchAtIndex(9)->GetId());
152 }
153
TEST_P(OneShotAccessibilityTreeSearchTest,NoCycle)154 TEST_P(OneShotAccessibilityTreeSearchTest, NoCycle) {
155 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
156 // If you set a result limit of 1, you won't get the root node back as
157 // the first match.
158 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
159 search.SetResultLimit(1);
160 ASSERT_EQ(1U, search.CountMatches());
161 EXPECT_NE(1, search.GetMatchAtIndex(0)->GetId());
162 }
163
TEST_P(OneShotAccessibilityTreeSearchTest,ForwardsWithStartNode)164 TEST_P(OneShotAccessibilityTreeSearchTest, ForwardsWithStartNode) {
165 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
166 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
167 search.SetStartNode(tree_->GetFromID(7));
168 ASSERT_EQ(3U, search.CountMatches());
169 EXPECT_EQ(8, search.GetMatchAtIndex(0)->GetId());
170 EXPECT_EQ(9, search.GetMatchAtIndex(1)->GetId());
171 EXPECT_EQ(10, search.GetMatchAtIndex(2)->GetId());
172 }
173
TEST_P(OneShotAccessibilityTreeSearchTest,BackwardsWithStartNode)174 TEST_P(OneShotAccessibilityTreeSearchTest, BackwardsWithStartNode) {
175 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
176 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
177 search.SetStartNode(tree_->GetFromID(4));
178 search.SetDirection(OneShotAccessibilityTreeSearch::BACKWARDS);
179 ASSERT_EQ(3U, search.CountMatches());
180 EXPECT_EQ(3, search.GetMatchAtIndex(0)->GetId());
181 EXPECT_EQ(2, search.GetMatchAtIndex(1)->GetId());
182 EXPECT_EQ(1, search.GetMatchAtIndex(2)->GetId());
183 }
184
TEST_P(OneShotAccessibilityTreeSearchTest,BackwardsWithStartNodeForAndroid)185 TEST_P(OneShotAccessibilityTreeSearchTest, BackwardsWithStartNodeForAndroid) {
186 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
187 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
188 search.SetStartNode(tree_->GetFromID(4));
189 search.SetDirection(OneShotAccessibilityTreeSearch::BACKWARDS);
190 search.SetResultLimit(3);
191 search.SetCanWrapToLastElement(true);
192 ASSERT_EQ(3U, search.CountMatches());
193 EXPECT_EQ(3, search.GetMatchAtIndex(0)->GetId());
194 EXPECT_EQ(2, search.GetMatchAtIndex(1)->GetId());
195 EXPECT_EQ(1, search.GetMatchAtIndex(2)->GetId());
196 }
197
TEST_P(OneShotAccessibilityTreeSearchTest,ForwardsWithStartNodeAndScope)198 TEST_P(OneShotAccessibilityTreeSearchTest, ForwardsWithStartNodeAndScope) {
199 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
200 OneShotAccessibilityTreeSearch search(tree_->GetFromID(7));
201 search.SetStartNode(tree_->GetFromID(8));
202 ASSERT_EQ(2U, search.CountMatches());
203 EXPECT_EQ(9, search.GetMatchAtIndex(0)->GetId());
204 EXPECT_EQ(10, search.GetMatchAtIndex(1)->GetId());
205 }
206
TEST_P(OneShotAccessibilityTreeSearchTest,ResultLimitZero)207 TEST_P(OneShotAccessibilityTreeSearchTest, ResultLimitZero) {
208 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
209 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
210 search.SetResultLimit(0);
211 ASSERT_EQ(0U, search.CountMatches());
212 }
213
TEST_P(OneShotAccessibilityTreeSearchTest,ResultLimitFive)214 TEST_P(OneShotAccessibilityTreeSearchTest, ResultLimitFive) {
215 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
216 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
217 search.SetResultLimit(5);
218 ASSERT_EQ(5U, search.CountMatches());
219 }
220
TEST_P(OneShotAccessibilityTreeSearchTest,DescendantsOnlyOfRoot)221 TEST_P(OneShotAccessibilityTreeSearchTest, DescendantsOnlyOfRoot) {
222 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
223 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
224 search.SetStartNode(tree_->GetFromID(1));
225 search.SetImmediateDescendantsOnly(true);
226 ASSERT_EQ(4U, search.CountMatches());
227 EXPECT_EQ(2, search.GetMatchAtIndex(0)->GetId());
228 EXPECT_EQ(3, search.GetMatchAtIndex(1)->GetId());
229 EXPECT_EQ(7, search.GetMatchAtIndex(2)->GetId());
230 EXPECT_EQ(10, search.GetMatchAtIndex(3)->GetId());
231 }
232
TEST_P(OneShotAccessibilityTreeSearchTest,DescendantsOnlyOfNode)233 TEST_P(OneShotAccessibilityTreeSearchTest, DescendantsOnlyOfNode) {
234 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
235 OneShotAccessibilityTreeSearch search(tree_->GetFromID(7));
236 search.SetImmediateDescendantsOnly(true);
237 ASSERT_EQ(2U, search.CountMatches());
238 EXPECT_EQ(8, search.GetMatchAtIndex(0)->GetId());
239 EXPECT_EQ(9, search.GetMatchAtIndex(1)->GetId());
240 }
241
TEST_P(OneShotAccessibilityTreeSearchTest,DescendantsOnlyOfNodeWithStartNode)242 TEST_P(OneShotAccessibilityTreeSearchTest, DescendantsOnlyOfNodeWithStartNode) {
243 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
244 OneShotAccessibilityTreeSearch search(tree_->GetFromID(7));
245 search.SetStartNode(tree_->GetFromID(8));
246 search.SetImmediateDescendantsOnly(true);
247 ASSERT_EQ(1U, search.CountMatches());
248 EXPECT_EQ(9, search.GetMatchAtIndex(0)->GetId());
249 }
250
TEST_P(OneShotAccessibilityTreeSearchTest,DescendantsOnlyOfNodeWithStartNodeBackwardsTableCell)251 TEST_P(OneShotAccessibilityTreeSearchTest,
252 DescendantsOnlyOfNodeWithStartNodeBackwardsTableCell) {
253 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
254 OneShotAccessibilityTreeSearch search(tree_->GetFromID(3));
255 search.SetStartNode(tree_->GetFromID(5));
256 search.SetDirection(OneShotAccessibilityTreeSearch::BACKWARDS);
257 search.SetImmediateDescendantsOnly(true);
258 ASSERT_EQ(0U, search.CountMatches());
259 }
260
TEST_P(OneShotAccessibilityTreeSearchTest,DescendantsOnlyOfNodeWithStartNodeBackwardsListItem)261 TEST_P(OneShotAccessibilityTreeSearchTest,
262 DescendantsOnlyOfNodeWithStartNodeBackwardsListItem) {
263 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
264 OneShotAccessibilityTreeSearch search(tree_->GetFromID(7));
265 search.SetStartNode(tree_->GetFromID(9));
266 search.SetImmediateDescendantsOnly(true);
267 search.SetDirection(OneShotAccessibilityTreeSearch::BACKWARDS);
268 ASSERT_EQ(1U, search.CountMatches());
269 EXPECT_EQ(8, search.GetMatchAtIndex(0)->GetId());
270 }
271
TEST_P(OneShotAccessibilityTreeSearchTest,OnscreenOnly)272 TEST_P(OneShotAccessibilityTreeSearchTest, OnscreenOnly) {
273 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
274 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
275 search.SetOnscreenOnly(true);
276 ASSERT_EQ(7U, search.CountMatches());
277 EXPECT_EQ(1, search.GetMatchAtIndex(0)->GetId());
278 EXPECT_EQ(2, search.GetMatchAtIndex(1)->GetId());
279 EXPECT_EQ(3, search.GetMatchAtIndex(2)->GetId());
280 EXPECT_EQ(4, search.GetMatchAtIndex(3)->GetId());
281 EXPECT_EQ(7, search.GetMatchAtIndex(4)->GetId());
282 EXPECT_EQ(8, search.GetMatchAtIndex(5)->GetId());
283 EXPECT_EQ(9, search.GetMatchAtIndex(6)->GetId());
284 }
285
TEST_P(OneShotAccessibilityTreeSearchTest,CaseInsensitiveStringMatch)286 TEST_P(OneShotAccessibilityTreeSearchTest, CaseInsensitiveStringMatch) {
287 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
288 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
289 search.SetSearchText("eCEptiCOn");
290 ASSERT_EQ(1U, search.CountMatches());
291 EXPECT_EQ(9, search.GetMatchAtIndex(0)->GetId());
292 }
293
TEST_P(OneShotAccessibilityTreeSearchTest,OnePredicateTableCell)294 TEST_P(OneShotAccessibilityTreeSearchTest, OnePredicateTableCell) {
295 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
296 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
297 search.AddPredicate(
298 [](BrowserAccessibility* start, BrowserAccessibility* current) {
299 return current->GetRole() == ax::mojom::Role::kColumnHeader;
300 });
301 ASSERT_EQ(2U, search.CountMatches());
302 EXPECT_EQ(5, search.GetMatchAtIndex(0)->GetId());
303 EXPECT_EQ(6, search.GetMatchAtIndex(1)->GetId());
304 }
305
TEST_P(OneShotAccessibilityTreeSearchTest,OnePredicateListItem)306 TEST_P(OneShotAccessibilityTreeSearchTest, OnePredicateListItem) {
307 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
308 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
309 search.AddPredicate(
310 [](BrowserAccessibility* start, BrowserAccessibility* current) {
311 return current->GetRole() == ax::mojom::Role::kListItem;
312 });
313 ASSERT_EQ(2U, search.CountMatches());
314 EXPECT_EQ(8, search.GetMatchAtIndex(0)->GetId());
315 EXPECT_EQ(9, search.GetMatchAtIndex(1)->GetId());
316 }
317
TEST_P(OneShotAccessibilityTreeSearchTest,TwoPredicatesTableRowAndCell)318 TEST_P(OneShotAccessibilityTreeSearchTest, TwoPredicatesTableRowAndCell) {
319 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
320 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
321 search.AddPredicate(
322 [](BrowserAccessibility* start, BrowserAccessibility* current) {
323 return (current->GetRole() == ax::mojom::Role::kRow ||
324 current->GetRole() == ax::mojom::Role::kColumnHeader);
325 });
326 search.AddPredicate(
327 [](BrowserAccessibility* start, BrowserAccessibility* current) {
328 return (current->GetId() % 2 == 0);
329 });
330 ASSERT_EQ(2U, search.CountMatches());
331 EXPECT_EQ(4, search.GetMatchAtIndex(0)->GetId());
332 EXPECT_EQ(6, search.GetMatchAtIndex(1)->GetId());
333 }
334
TEST_P(OneShotAccessibilityTreeSearchTest,TwoPredicatesListItem)335 TEST_P(OneShotAccessibilityTreeSearchTest, TwoPredicatesListItem) {
336 tree_->ax_tree()->SetEnableExtraMacNodes(GetParam());
337 OneShotAccessibilityTreeSearch search(tree_->GetRoot());
338 search.AddPredicate(
339 [](BrowserAccessibility* start, BrowserAccessibility* current) {
340 return (current->GetRole() == ax::mojom::Role::kList ||
341 current->GetRole() == ax::mojom::Role::kListItem);
342 });
343 search.AddPredicate(
344 [](BrowserAccessibility* start, BrowserAccessibility* current) {
345 return (current->GetId() % 2 == 1);
346 });
347 ASSERT_EQ(2U, search.CountMatches());
348 EXPECT_EQ(7, search.GetMatchAtIndex(0)->GetId());
349 EXPECT_EQ(9, search.GetMatchAtIndex(1)->GetId());
350 }
351
352 // These tests prevent other tests from being run. crbug.com/514632
353 #if defined(ANDROID) && defined(ADDRESS_SANITIZER)
354 #define MAYBE_EnableExtraMacNodes DISABLED_EnableExtraMacNodes
355 #define MAYBE_DisableExtraMacNodes DISABLED_DisableExtraMacNodes
356 #else
357 #define MAYBE_EnableExtraMacNodes EnableExtraMacNodes
358 #define MAYBE_DisableExtraMacNodes DisableExtraMacNodes
359 #endif
360
361 INSTANTIATE_TEST_SUITE_P(MAYBE_EnableExtraMacNodes,
362 OneShotAccessibilityTreeSearchTest,
363 testing::Values(true));
364
365 INSTANTIATE_TEST_SUITE_P(MAYBE_DisableExtraMacNodes,
366 OneShotAccessibilityTreeSearchTest,
367 testing::Values(false));
368
369 } // namespace content
370