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