summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-18 00:09:34 +0000
committerdmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-18 00:09:34 +0000
commitf0779b5f9025898b681ea7b33e61e4682f19c224 (patch)
tree4b906ef97ef16698ae60246ccb792f8776c89042
parent4e5592998c1a879c85c179543180c65e9dfe4855 (diff)
downloadchromium_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
-rw-r--r--content/browser/accessibility/browser_accessibility_manager.cc34
-rw-r--r--content/browser/accessibility/browser_accessibility_manager.h4
-rw-r--r--content/browser/accessibility/browser_accessibility_manager_android.cc77
-rw-r--r--content/browser/accessibility/browser_accessibility_manager_android.h8
-rw-r--r--content/browser/accessibility/browser_accessibility_manager_unittest.cc49
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java37
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);
}