// 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 #include #include #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() {} ~AndroidGranularityMovementBrowserTest() override {} 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(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(node); BrowserAccessibilityManagerAndroid* manager = static_cast(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 (base::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 (base::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," "" "

One, two, three!

" "" ""); 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," "" "

One, two, three!

" "" ""); 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," "" "
One,%0dtwo,%0dthree!
" ""); 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