diff options
author | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-18 00:09:34 +0000 |
---|---|---|
committer | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-18 00:09:34 +0000 |
commit | f0779b5f9025898b681ea7b33e61e4682f19c224 (patch) | |
tree | 4b906ef97ef16698ae60246ccb792f8776c89042 | |
parent | 4e5592998c1a879c85c179543180c65e9dfe4855 (diff) | |
download | chromium_src-f0779b5f9025898b681ea7b33e61e4682f19c224.zip chromium_src-f0779b5f9025898b681ea7b33e61e4682f19c224.tar.gz chromium_src-f0779b5f9025898b681ea7b33e61e4682f19c224.tar.bz2 |
Support TalkBack commands to move to next/prev element.
BUG=353051
Review URL: https://codereview.chromium.org/200323006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@257542 0039d316-1c4b-4281-b951-d872f2087c98
6 files changed, 209 insertions, 0 deletions
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc index c667377..8481b07 100644 --- a/content/browser/accessibility/browser_accessibility_manager.cc +++ b/content/browser/accessibility/browser_accessibility_manager.cc @@ -244,6 +244,40 @@ gfx::Rect BrowserAccessibilityManager::GetViewBounds() { return gfx::Rect(); } +BrowserAccessibility* BrowserAccessibilityManager::NextInTreeOrder( + BrowserAccessibility* node) { + if (!node) + return NULL; + + if (node->PlatformChildCount() > 0) + return node->PlatformGetChild(0); + while (node) { + if (node->parent() && + node->index_in_parent() < + static_cast<int>(node->parent()->PlatformChildCount()) - 1) { + return node->parent()->PlatformGetChild(node->index_in_parent() + 1); + } + node = node->parent(); + } + + return NULL; +} + +BrowserAccessibility* BrowserAccessibilityManager::PreviousInTreeOrder( + BrowserAccessibility* node) { + if (!node) + return NULL; + + if (node->parent() && node->index_in_parent() > 0) { + node = node->parent()->PlatformGetChild(node->index_in_parent() - 1); + while (node->PlatformChildCount() > 0) + node = node->PlatformGetChild(node->PlatformChildCount() - 1); + return node; + } + + return node->parent(); +} + void BrowserAccessibilityManager::UpdateNodesForTesting( const ui::AXNodeData& node1, const ui::AXNodeData& node2 /* = ui::AXNodeData() */, diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h index bd01f39..ffd754b 100644 --- a/content/browser/accessibility/browser_accessibility_manager.h +++ b/content/browser/accessibility/browser_accessibility_manager.h @@ -154,6 +154,10 @@ class CONTENT_EXPORT BrowserAccessibilityManager { // scroll offsets separately. virtual bool UseRootScrollOffsetsWhenComputingBounds(); + // Walk the tree. + BrowserAccessibility* NextInTreeOrder(BrowserAccessibility* node); + BrowserAccessibility* PreviousInTreeOrder(BrowserAccessibility* node); + // For testing only: update the given nodes as if they were // received from the renderer process in OnAccessibilityEvents. // Takes up to 7 nodes at once so tests don't need to create a vector diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc index 9516efe..c079825 100644 --- a/content/browser/accessibility/browser_accessibility_manager_android.cc +++ b/content/browser/accessibility/browser_accessibility_manager_android.cc @@ -26,11 +26,31 @@ enum { ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192 }; +enum AndroidHtmlElementType { + HTML_ELEMENT_TYPE_SECTION, + HTML_ELEMENT_TYPE_LIST, + HTML_ELEMENT_TYPE_CONTROL, + HTML_ELEMENT_TYPE_ANY +}; + // Restricts |val| to the range [min, max]. int Clamp(int val, int min, int max) { return std::min(std::max(val, min), max); } +// These are special unofficial strings sent from TalkBack/BrailleBack +// to jump to certain categories of web elements. +AndroidHtmlElementType HtmlElementTypeFromString(base::string16 element_type) { + if (element_type == base::ASCIIToUTF16("SECTION")) + return HTML_ELEMENT_TYPE_SECTION; + else if (element_type == base::ASCIIToUTF16("LIST")) + return HTML_ELEMENT_TYPE_LIST; + else if (element_type == base::ASCIIToUTF16("CONTROL")) + return HTML_ELEMENT_TYPE_CONTROL; + else + return HTML_ELEMENT_TYPE_ANY; +} + } // anonymous namespace namespace content { @@ -444,6 +464,63 @@ int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared( return dx * dx + dy * dy; } +jint BrowserAccessibilityManagerAndroid::FindElementType( + JNIEnv* env, jobject obj, jint start_id, jstring element_type_str, + jboolean forwards) { + BrowserAccessibility* node = GetFromRendererID(start_id); + if (!node) + return 0; + + AndroidHtmlElementType element_type = HtmlElementTypeFromString( + base::android::ConvertJavaStringToUTF16(env, element_type_str)); + + node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node); + while (node) { + switch(element_type) { + case HTML_ELEMENT_TYPE_SECTION: + if (node->role() == ui::AX_ROLE_ARTICLE || + node->role() == ui::AX_ROLE_APPLICATION || + node->role() == ui::AX_ROLE_BANNER || + node->role() == ui::AX_ROLE_COMPLEMENTARY || + node->role() == ui::AX_ROLE_CONTENT_INFO || + node->role() == ui::AX_ROLE_HEADING || + node->role() == ui::AX_ROLE_MAIN || + node->role() == ui::AX_ROLE_NAVIGATION || + node->role() == ui::AX_ROLE_SEARCH || + node->role() == ui::AX_ROLE_REGION) { + return node->renderer_id(); + } + break; + case HTML_ELEMENT_TYPE_LIST: + if (node->role() == ui::AX_ROLE_LIST || + node->role() == ui::AX_ROLE_GRID || + node->role() == ui::AX_ROLE_TABLE || + node->role() == ui::AX_ROLE_TREE) { + return node->renderer_id(); + } + break; + case HTML_ELEMENT_TYPE_CONTROL: + if (static_cast<BrowserAccessibilityAndroid*>(node)->IsFocusable()) + return node->renderer_id(); + break; + case HTML_ELEMENT_TYPE_ANY: + // In theory, the API says that an accessibility service could + // jump to an element by element name, like 'H1' or 'P'. This isn't + // currently used by any accessibility service, and we think it's + // better to keep them high-level like 'SECTION' or 'CONTROL', so we + // just fall back on linear navigation when we don't recognize the + // element type. + if (static_cast<BrowserAccessibilityAndroid*>(node)->IsClickable()) + return node->renderer_id(); + break; + } + + node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node); + } + + return 0; +} + void BrowserAccessibilityManagerAndroid::NotifyRootChanged() { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); diff --git a/content/browser/accessibility/browser_accessibility_manager_android.h b/content/browser/accessibility/browser_accessibility_manager_android.h index 53cb617..e587eda 100644 --- a/content/browser/accessibility/browser_accessibility_manager_android.h +++ b/content/browser/accessibility/browser_accessibility_manager_android.h @@ -57,6 +57,14 @@ class CONTENT_EXPORT BrowserAccessibilityManagerAndroid void Blur(JNIEnv* env, jobject obj); void ScrollToMakeNodeVisible(JNIEnv* env, jobject obj, int id); + // Return the id of the next node in tree order in the direction given by + // |forwards|, starting with |start_id|, that matches |element_type|, + // where |element_type| is a special uppercase string from TalkBack or + // BrailleBack indicating general categories of web content like + // "SECTION" or "CONTROL". Return 0 if not found. + jint FindElementType(JNIEnv* env, jobject obj, jint start_id, + jstring element_type, jboolean forwards); + protected: virtual void NotifyRootChanged() OVERRIDE; diff --git a/content/browser/accessibility/browser_accessibility_manager_unittest.cc b/content/browser/accessibility/browser_accessibility_manager_unittest.cc index 36e5d23..22d877b 100644 --- a/content/browser/accessibility/browser_accessibility_manager_unittest.cc +++ b/content/browser/accessibility/browser_accessibility_manager_unittest.cc @@ -886,4 +886,53 @@ TEST(BrowserAccessibilityManagerTest, MAYBE_BoundsForRangeOnParentElement) { root_accessible->GetLocalBoundsForRange(0, 4).ToString()); } +TEST(BrowserAccessibilityManagerTest, NextPreviousInTreeOrder) { + ui::AXNodeData root; + root.id = 1; + root.role = ui::AX_ROLE_ROOT_WEB_AREA; + + ui::AXNodeData node2; + node2.id = 2; + root.child_ids.push_back(2); + + ui::AXNodeData node3; + node3.id = 3; + root.child_ids.push_back(3); + + ui::AXNodeData node4; + node4.id = 4; + node3.child_ids.push_back(4); + + ui::AXNodeData node5; + node5.id = 5; + root.child_ids.push_back(5); + + scoped_ptr<BrowserAccessibilityManager> manager( + BrowserAccessibilityManager::Create( + root, + NULL, + new CountedBrowserAccessibilityFactory())); + manager->UpdateNodesForTesting(node2, node3, node4, node5); + + BrowserAccessibility* root_accessible = manager->GetRoot(); + BrowserAccessibility* node2_accessible = root_accessible->PlatformGetChild(0); + BrowserAccessibility* node3_accessible = root_accessible->PlatformGetChild(1); + BrowserAccessibility* node4_accessible = + node3_accessible->PlatformGetChild(0); + BrowserAccessibility* node5_accessible = root_accessible->PlatformGetChild(2); + + ASSERT_EQ(NULL, manager->NextInTreeOrder(NULL)); + ASSERT_EQ(node2_accessible, manager->NextInTreeOrder(root_accessible)); + ASSERT_EQ(node3_accessible, manager->NextInTreeOrder(node2_accessible)); + ASSERT_EQ(node4_accessible, manager->NextInTreeOrder(node3_accessible)); + ASSERT_EQ(node5_accessible, manager->NextInTreeOrder(node4_accessible)); + ASSERT_EQ(NULL, manager->NextInTreeOrder(node5_accessible)); + + ASSERT_EQ(NULL, manager->PreviousInTreeOrder(NULL)); + ASSERT_EQ(node4_accessible, manager->PreviousInTreeOrder(node5_accessible)); + ASSERT_EQ(node3_accessible, manager->PreviousInTreeOrder(node4_accessible)); + ASSERT_EQ(node2_accessible, manager->PreviousInTreeOrder(node3_accessible)); + ASSERT_EQ(root_accessible, manager->PreviousInTreeOrder(node2_accessible)); +} + } // namespace content diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java index 47209bb..d6a80ea 100644 --- a/content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java +++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java @@ -194,6 +194,28 @@ public class BrowserAccessibilityManager { case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: nativeBlur(mNativeObj); return true; + + case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT: { + if (arguments == null) + return false; + String elementType = arguments.getString( + AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING); + if (elementType == null) + return false; + elementType = elementType.toUpperCase(); + return jumpToElementType(elementType, true); + } + case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: { + if (arguments == null) + return false; + String elementType = arguments.getString( + AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING); + if (elementType == null) + return false; + elementType = elementType.toUpperCase(); + return jumpToElementType(elementType, false); + } + default: break; } @@ -261,6 +283,16 @@ public class BrowserAccessibilityManager { } } + private boolean jumpToElementType(String elementType, boolean forwards) { + int id = nativeFindElementType(mNativeObj, mAccessibilityFocusId, elementType, forwards); + if (id == 0) + return false; + + mAccessibilityFocusId = id; + sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); + return true; + } + private void sendAccessibilityEvent(int virtualViewId, int eventType) { // If mFrameInfoInitialized is false, then the virtual hierarchy // doesn't exist in the view of the Android framework, so should @@ -430,6 +462,9 @@ public class BrowserAccessibilityManager { node.setSelected(selected); node.setVisibleToUser(visibleToUser); + node.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT); + node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT); + if (focusable) { if (focused) { node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS); @@ -649,4 +684,6 @@ public class BrowserAccessibilityManager { private native void nativeBlur(long nativeBrowserAccessibilityManagerAndroid); private native void nativeScrollToMakeNodeVisible( long nativeBrowserAccessibilityManagerAndroid, int id); + private native int nativeFindElementType(long nativeBrowserAccessibilityManagerAndroid, + int startId, String elementType, boolean forwards); } |