1 // Copyright 2014 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.
9 #include "base/logging.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "content/browser/accessibility/browser_accessibility_android.h"
14 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
15 #include "content/browser/web_contents/web_contents_impl.h"
16 #include "content/public/browser/web_contents.h"
17 #include "content/public/common/url_constants.h"
18 #include "content/public/test/content_browser_test.h"
19 #include "content/public/test/content_browser_test_utils.h"
20 #include "content/shell/browser/shell.h"
21 #include "content/test/accessibility_browser_test_utils.h"
22 #include "testing/gtest/include/gtest/gtest.h"
26 const int GRANULARITY_CHARACTER
=
27 ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER
;
28 const int GRANULARITY_WORD
=
29 ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD
;
30 const int GRANULARITY_LINE
=
31 ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE
;
33 class AndroidGranularityMovementBrowserTest
: public ContentBrowserTest
{
35 AndroidGranularityMovementBrowserTest() {}
36 ~AndroidGranularityMovementBrowserTest() override
{}
38 BrowserAccessibility
* LoadUrlAndGetAccessibilityRoot(const GURL
& url
) {
39 NavigateToURL(shell(), GURL(url::kAboutBlankURL
));
42 AccessibilityNotificationWaiter
waiter(
43 shell(), AccessibilityModeComplete
,
44 ui::AX_EVENT_LOAD_COMPLETE
);
45 NavigateToURL(shell(), url
);
46 waiter
.WaitForNotification();
48 // Get the BrowserAccessibilityManager.
49 WebContentsImpl
* web_contents
=
50 static_cast<WebContentsImpl
*>(shell()->web_contents());
51 return web_contents
->GetRootBrowserAccessibilityManager()->GetRoot();
54 // First, set accessibility focus to a node and wait for the update that
55 // loads inline text boxes for that node. (We load inline text boxes
56 // asynchronously on Android since we only ever need them for the node
57 // with accessibility focus.)
59 // Then call NextAtGranularity repeatedly and return a string that
60 // concatenates all of the text of the returned text ranges.
62 // As an example, if the node's text is "cat dog" and you traverse by
63 // word, this returns "'cat', 'dog'".
65 // Also calls PreviousAtGranularity from the end back to the beginning
66 // and fails (by logging an error and returning the empty string) if
67 // the result when traversing backwards is not the same
68 // (but in reverse order).
69 base::string16
TraverseNodeAtGranularity(
70 BrowserAccessibility
* node
,
72 AccessibilityNotificationWaiter
waiter(
73 shell(), AccessibilityModeComplete
,
74 ui::AX_EVENT_TREE_CHANGED
);
75 node
->manager()->delegate()->AccessibilitySetAccessibilityFocus(
77 waiter
.WaitForNotification();
81 BrowserAccessibilityAndroid
* android_node
=
82 static_cast<BrowserAccessibilityAndroid
*>(node
);
83 BrowserAccessibilityManagerAndroid
* manager
=
84 static_cast<BrowserAccessibilityManagerAndroid
*>(node
->manager());
85 base::string16 text
= android_node
->GetText();
86 base::string16 concatenated
;
87 int previous_end_index
= -1;
88 while (manager
->NextAtGranularity(
89 granularity
, end_index
, android_node
,
90 &start_index
, &end_index
)) {
91 int len
= (granularity
== GRANULARITY_CHARACTER
) ?
92 1 : end_index
- start_index
;
93 base::string16 selection
= text
.substr(start_index
, len
);
94 if (EndsWith(selection
, base::ASCIIToUTF16("\n"), false))
95 selection
.erase(selection
.size() - 1);
96 if (!selection
.empty()) {
97 if (!concatenated
.empty())
98 concatenated
+= base::ASCIIToUTF16(", ");
99 concatenated
+= base::ASCIIToUTF16("'") + selection
+
100 base::ASCIIToUTF16("'");
103 // Prevent an endless loop.
104 if (end_index
== previous_end_index
) {
105 LOG(ERROR
) << "Bailing from loop, NextAtGranularity didn't advance";
108 previous_end_index
= end_index
;
111 base::string16 reverse
;
112 previous_end_index
= -1;
113 start_index
= end_index
;
114 while (manager
->PreviousAtGranularity(
115 granularity
, start_index
, android_node
, &start_index
, &end_index
)) {
116 int len
= (granularity
== GRANULARITY_CHARACTER
) ?
117 1 : end_index
- start_index
;
118 base::string16 selection
= text
.substr(start_index
, len
);
119 if (EndsWith(selection
, base::ASCIIToUTF16("\n"), false))
120 selection
= selection
.substr(0, selection
.size() - 1);
121 if (!reverse
.empty())
122 reverse
= base::ASCIIToUTF16(", ") + reverse
;
123 reverse
= base::ASCIIToUTF16("'") + selection
+
124 base::ASCIIToUTF16("'") + reverse
;
126 // Prevent an endless loop.
127 if (end_index
== previous_end_index
) {
128 LOG(ERROR
) << "Bailing from loop, PreviousAtGranularity didn't advance";
131 previous_end_index
= end_index
;
134 if (concatenated
!= reverse
) {
135 LOG(ERROR
) << "Did not get the same sequence in the forwards and "
136 << "reverse directions!";
137 LOG(ERROR
) << "Forwards: " << concatenated
;
138 LOG(ERROR
) << "Backwards " << reverse
;
139 return base::string16();
146 IN_PROC_BROWSER_TEST_F(AndroidGranularityMovementBrowserTest
,
147 NavigateByCharacters
) {
148 GURL
url("data:text/html,"
150 "<p>One, two, three!</p>"
151 "<button aria-label='Seven, eight, nine!'>Four, five, six!</button>"
153 BrowserAccessibility
* root
= LoadUrlAndGetAccessibilityRoot(url
);
154 ASSERT_EQ(2U, root
->PlatformChildCount());
155 BrowserAccessibility
* para
= root
->PlatformGetChild(0);
156 ASSERT_EQ(0U, para
->PlatformChildCount());
157 BrowserAccessibility
* button_container
= root
->PlatformGetChild(1);
158 ASSERT_EQ(1U, button_container
->PlatformChildCount());
159 BrowserAccessibility
* button
= button_container
->PlatformGetChild(0);
160 ASSERT_EQ(0U, button
->PlatformChildCount());
163 base::ASCIIToUTF16("'O', 'n', 'e', ',', ' ', 't', 'w', 'o', "
164 "',', ' ', 't', 'h', 'r', 'e', 'e', '!'"),
165 TraverseNodeAtGranularity(para
, GRANULARITY_CHARACTER
));
167 base::ASCIIToUTF16("'S', 'e', 'v', 'e', 'n', ',', ' ', 'e', 'i', 'g', "
168 "'h', 't', ',', ' ', 'n', 'i', 'n', 'e', '!'"),
169 TraverseNodeAtGranularity(button
, GRANULARITY_CHARACTER
));
172 IN_PROC_BROWSER_TEST_F(AndroidGranularityMovementBrowserTest
,
174 GURL
url("data:text/html,"
176 "<p>One, two, three!</p>"
177 "<button aria-label='Seven, eight, nine!'>Four, five, six!</button>"
179 BrowserAccessibility
* root
= LoadUrlAndGetAccessibilityRoot(url
);
180 ASSERT_EQ(2U, root
->PlatformChildCount());
181 BrowserAccessibility
* para
= root
->PlatformGetChild(0);
182 ASSERT_EQ(0U, para
->PlatformChildCount());
183 BrowserAccessibility
* button_container
= root
->PlatformGetChild(1);
184 ASSERT_EQ(1U, button_container
->PlatformChildCount());
185 BrowserAccessibility
* button
= button_container
->PlatformGetChild(0);
186 ASSERT_EQ(0U, button
->PlatformChildCount());
188 ASSERT_EQ(base::ASCIIToUTF16("'One', 'two', 'three'"),
189 TraverseNodeAtGranularity(para
, GRANULARITY_WORD
));
190 ASSERT_EQ(base::ASCIIToUTF16("'Seven', 'eight', 'nine'"),
191 TraverseNodeAtGranularity(button
, GRANULARITY_WORD
));
194 IN_PROC_BROWSER_TEST_F(AndroidGranularityMovementBrowserTest
,
196 GURL
url("data:text/html,"
198 "<pre>One,%0dtwo,%0dthree!</pre>"
200 BrowserAccessibility
* root
= LoadUrlAndGetAccessibilityRoot(url
);
201 ASSERT_EQ(1U, root
->PlatformChildCount());
202 BrowserAccessibility
* pre
= root
->PlatformGetChild(0);
203 ASSERT_EQ(0U, pre
->PlatformChildCount());
205 ASSERT_EQ(base::ASCIIToUTF16("'One,', 'two,', 'three!'"),
206 TraverseNodeAtGranularity(pre
, GRANULARITY_LINE
));
209 } // namespace content