From f0779b5f9025898b681ea7b33e61e4682f19c224 Mon Sep 17 00:00:00 2001 From: "dmazzoni@chromium.org" Date: Tue, 18 Mar 2014 00:09:34 +0000 Subject: 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 --- .../accessibility/browser_accessibility_manager.cc | 34 ++++++++++ .../accessibility/browser_accessibility_manager.h | 4 ++ .../browser_accessibility_manager_android.cc | 77 ++++++++++++++++++++++ .../browser_accessibility_manager_android.h | 8 +++ .../browser_accessibility_manager_unittest.cc | 49 ++++++++++++++ 5 files changed, 172 insertions(+) (limited to 'content/browser/accessibility') 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(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(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(node)->IsClickable()) + return node->renderer_id(); + break; + } + + node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node); + } + + return 0; +} + void BrowserAccessibilityManagerAndroid::NotifyRootChanged() { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef 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 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 -- cgit v1.1