| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/browser/accessibility/browser_accessibility_android.h" |
| #include "content/browser/accessibility/browser_accessibility_manager_android.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/accessibility_browser_test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| |
| const int GRANULARITY_CHARACTER = |
| ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER; |
| const int GRANULARITY_WORD = |
| ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD; |
| const int GRANULARITY_LINE = |
| ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE; |
| |
| class AndroidGranularityMovementBrowserTest : public ContentBrowserTest { |
| public: |
| AndroidGranularityMovementBrowserTest() {} |
| virtual ~AndroidGranularityMovementBrowserTest() {} |
| |
| BrowserAccessibility* LoadUrlAndGetAccessibilityRoot(const GURL& url) { |
| NavigateToURL(shell(), GURL(url::kAboutBlankURL)); |
| |
| // Load the page. |
| AccessibilityNotificationWaiter waiter( |
| shell(), AccessibilityModeComplete, |
| ui::AX_EVENT_LOAD_COMPLETE); |
| NavigateToURL(shell(), url); |
| waiter.WaitForNotification(); |
| |
| // Get the BrowserAccessibilityManager. |
| WebContentsImpl* web_contents = |
| static_cast<WebContentsImpl*>(shell()->web_contents()); |
| return web_contents->GetRootBrowserAccessibilityManager()->GetRoot(); |
| } |
| |
| // First, set accessibility focus to a node and wait for the update that |
| // loads inline text boxes for that node. (We load inline text boxes |
| // asynchronously on Android since we only ever need them for the node |
| // with accessibility focus.) |
| // |
| // Then call NextAtGranularity repeatedly and return a string that |
| // concatenates all of the text of the returned text ranges. |
| // |
| // As an example, if the node's text is "cat dog" and you traverse by |
| // word, this returns "'cat', 'dog'". |
| // |
| // Also calls PreviousAtGranularity from the end back to the beginning |
| // and fails (by logging an error and returning the empty string) if |
| // the result when traversing backwards is not the same |
| // (but in reverse order). |
| base::string16 TraverseNodeAtGranularity( |
| BrowserAccessibility* node, |
| int granularity) { |
| AccessibilityNotificationWaiter waiter( |
| shell(), AccessibilityModeComplete, |
| ui::AX_EVENT_TREE_CHANGED); |
| node->manager()->delegate()->AccessibilitySetAccessibilityFocus( |
| node->GetId()); |
| waiter.WaitForNotification(); |
| |
| int start_index = -1; |
| int end_index = -1; |
| BrowserAccessibilityAndroid* android_node = |
| static_cast<BrowserAccessibilityAndroid*>(node); |
| BrowserAccessibilityManagerAndroid* manager = |
| static_cast<BrowserAccessibilityManagerAndroid*>(node->manager()); |
| base::string16 text = android_node->GetText(); |
| base::string16 concatenated; |
| int previous_end_index = -1; |
| while (manager->NextAtGranularity( |
| granularity, end_index, android_node, |
| &start_index, &end_index)) { |
| int len = (granularity == GRANULARITY_CHARACTER) ? |
| 1 : end_index - start_index; |
| base::string16 selection = text.substr(start_index, len); |
| if (EndsWith(selection, base::ASCIIToUTF16("\n"), false)) |
| selection.erase(selection.size() - 1); |
| if (!selection.empty()) { |
| if (!concatenated.empty()) |
| concatenated += base::ASCIIToUTF16(", "); |
| concatenated += base::ASCIIToUTF16("'") + selection + |
| base::ASCIIToUTF16("'"); |
| } |
| |
| // Prevent an endless loop. |
| if (end_index == previous_end_index) { |
| LOG(ERROR) << "Bailing from loop, NextAtGranularity didn't advance"; |
| break; |
| } |
| previous_end_index = end_index; |
| } |
| |
| base::string16 reverse; |
| previous_end_index = -1; |
| start_index = end_index; |
| while (manager->PreviousAtGranularity( |
| granularity, start_index, android_node, &start_index, &end_index)) { |
| int len = (granularity == GRANULARITY_CHARACTER) ? |
| 1 : end_index - start_index; |
| base::string16 selection = text.substr(start_index, len); |
| if (EndsWith(selection, base::ASCIIToUTF16("\n"), false)) |
| selection = selection.substr(0, selection.size() - 1); |
| if (!reverse.empty()) |
| reverse = base::ASCIIToUTF16(", ") + reverse; |
| reverse = base::ASCIIToUTF16("'") + selection + |
| base::ASCIIToUTF16("'") + reverse; |
| |
| // Prevent an endless loop. |
| if (end_index == previous_end_index) { |
| LOG(ERROR) << "Bailing from loop, PreviousAtGranularity didn't advance"; |
| break; |
| } |
| previous_end_index = end_index; |
| } |
| |
| if (concatenated != reverse) { |
| LOG(ERROR) << "Did not get the same sequence in the forwards and " |
| << "reverse directions!"; |
| LOG(ERROR) << "Forwards: " << concatenated; |
| LOG(ERROR) << "Backwards " << reverse; |
| return base::string16(); |
| } |
| |
| return concatenated; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AndroidGranularityMovementBrowserTest, |
| NavigateByCharacters) { |
| GURL url("data:text/html," |
| "<body>" |
| "<p>One, two, three!</p>" |
| "<button aria-label='Seven, eight, nine!'>Four, five, six!</button>" |
| "</body></html>"); |
| BrowserAccessibility* root = LoadUrlAndGetAccessibilityRoot(url); |
| ASSERT_EQ(2U, root->PlatformChildCount()); |
| BrowserAccessibility* para = root->PlatformGetChild(0); |
| ASSERT_EQ(0U, para->PlatformChildCount()); |
| BrowserAccessibility* button_container = root->PlatformGetChild(1); |
| ASSERT_EQ(1U, button_container->PlatformChildCount()); |
| BrowserAccessibility* button = button_container->PlatformGetChild(0); |
| ASSERT_EQ(0U, button->PlatformChildCount()); |
| |
| ASSERT_EQ( |
| base::ASCIIToUTF16("'O', 'n', 'e', ',', ' ', 't', 'w', 'o', " |
| "',', ' ', 't', 'h', 'r', 'e', 'e', '!'"), |
| TraverseNodeAtGranularity(para, GRANULARITY_CHARACTER)); |
| ASSERT_EQ( |
| base::ASCIIToUTF16("'S', 'e', 'v', 'e', 'n', ',', ' ', 'e', 'i', 'g', " |
| "'h', 't', ',', ' ', 'n', 'i', 'n', 'e', '!'"), |
| TraverseNodeAtGranularity(button, GRANULARITY_CHARACTER)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AndroidGranularityMovementBrowserTest, |
| NavigateByWords) { |
| GURL url("data:text/html," |
| "<body>" |
| "<p>One, two, three!</p>" |
| "<button aria-label='Seven, eight, nine!'>Four, five, six!</button>" |
| "</body></html>"); |
| BrowserAccessibility* root = LoadUrlAndGetAccessibilityRoot(url); |
| ASSERT_EQ(2U, root->PlatformChildCount()); |
| BrowserAccessibility* para = root->PlatformGetChild(0); |
| ASSERT_EQ(0U, para->PlatformChildCount()); |
| BrowserAccessibility* button_container = root->PlatformGetChild(1); |
| ASSERT_EQ(1U, button_container->PlatformChildCount()); |
| BrowserAccessibility* button = button_container->PlatformGetChild(0); |
| ASSERT_EQ(0U, button->PlatformChildCount()); |
| |
| ASSERT_EQ(base::ASCIIToUTF16("'One', 'two', 'three'"), |
| TraverseNodeAtGranularity(para, GRANULARITY_WORD)); |
| ASSERT_EQ(base::ASCIIToUTF16("'Seven', 'eight', 'nine'"), |
| TraverseNodeAtGranularity(button, GRANULARITY_WORD)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AndroidGranularityMovementBrowserTest, |
| NavigateByLine) { |
| GURL url("data:text/html," |
| "<body>" |
| "<pre>One,%0dtwo,%0dthree!</pre>" |
| "</body>"); |
| BrowserAccessibility* root = LoadUrlAndGetAccessibilityRoot(url); |
| ASSERT_EQ(1U, root->PlatformChildCount()); |
| BrowserAccessibility* pre = root->PlatformGetChild(0); |
| ASSERT_EQ(0U, pre->PlatformChildCount()); |
| |
| ASSERT_EQ(base::ASCIIToUTF16("'One,', 'two,', 'three!'"), |
| TraverseNodeAtGranularity(pre, GRANULARITY_LINE)); |
| } |
| |
| } // namespace content |