diff options
author | aboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-13 22:57:01 +0000 |
---|---|---|
committer | aboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-13 22:57:01 +0000 |
commit | 7f2d9f6bf46e1f16b6ebb997f2b42c77a9070620 (patch) | |
tree | d352d7539ee4d4002131151bd8b9a8c9aeaae388 /content/browser/accessibility | |
parent | 4dbecc37e310947f2ff7c3ada1dc9b934158d2b7 (diff) | |
download | chromium_src-7f2d9f6bf46e1f16b6ebb997f2b42c77a9070620.zip chromium_src-7f2d9f6bf46e1f16b6ebb997f2b42c77a9070620.tar.gz chromium_src-7f2d9f6bf46e1f16b6ebb997f2b42c77a9070620.tar.bz2 |
Content side of changes to enable native accessibility on android.
This code has been written collaboratively by dmazzoni and aboxhall; see https://codereview.chromium.org/15741009/ for the first-pass review on all code changes.
BUG=242953
TBR=piman
Review URL: https://chromiumcodereview.appspot.com/16661006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@206193 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/accessibility')
11 files changed, 1139 insertions, 15 deletions
diff --git a/content/browser/accessibility/accessibility_tree_formatter.cc b/content/browser/accessibility/accessibility_tree_formatter.cc index 99a5aa5..4cf30ea 100644 --- a/content/browser/accessibility/accessibility_tree_formatter.cc +++ b/content/browser/accessibility/accessibility_tree_formatter.cc @@ -63,8 +63,12 @@ void AccessibilityTreeFormatter::FormatAccessibilityTree( void AccessibilityTreeFormatter::RecursiveBuildAccessibilityTree( const BrowserAccessibility& node, DictionaryValue* dict) { AddProperties(node, dict); + ListValue* children = new ListValue; dict->Set(kChildrenDictAttr, children); + if (!IncludeChildren(node)) + return; + for (size_t i = 0; i < node.children().size(); ++i) { BrowserAccessibility* child_node = node.children()[i]; DictionaryValue* child_dict = new DictionaryValue; @@ -89,7 +93,14 @@ void AccessibilityTreeFormatter::RecursiveFormatAccessibilityTree( } } -#if (!defined(OS_WIN) && !defined(OS_MACOSX)) +#if !defined(OS_ANDROID) +bool AccessibilityTreeFormatter::IncludeChildren( + const BrowserAccessibility& node) { + return true; +} +#endif + +#if (!defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_ANDROID)) void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node, DictionaryValue* dict) { dict->SetInteger("id", node.renderer_id()); diff --git a/content/browser/accessibility/accessibility_tree_formatter.h b/content/browser/accessibility/accessibility_tree_formatter.h index 7b334fe..5678b46 100644 --- a/content/browser/accessibility/accessibility_tree_formatter.h +++ b/content/browser/accessibility/accessibility_tree_formatter.h @@ -112,6 +112,11 @@ class CONTENT_EXPORT AccessibilityTreeFormatter { // into the given dict. void AddProperties(const BrowserAccessibility& node, DictionaryValue* dict); + // Returns true by default; can be overridden by the platform to + // prune some children from the tree when they wouldn't be exposed + // natively on that platform. + virtual bool IncludeChildren(const BrowserAccessibility& node); + string16 FormatCoordinates(const char* name, const char* x_name, const char* y_name, diff --git a/content/browser/accessibility/accessibility_tree_formatter_android.cc b/content/browser/accessibility/accessibility_tree_formatter_android.cc new file mode 100644 index 0000000..a5d6a33 --- /dev/null +++ b/content/browser/accessibility/accessibility_tree_formatter_android.cc @@ -0,0 +1,170 @@ +// Copyright 2013 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 "content/browser/accessibility/accessibility_tree_formatter.h" + +#include <string> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/json/json_writer.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_android.h" +#include "content/common/accessibility_node_data.h" + +using base::StringPrintf; + +namespace content { + +namespace { +const char* BOOL_ATTRIBUTES[] = { + "checkable", + "checked", + "clickable", + "disabled", + "editable_text", + "focusable", + "focused", + "invisible", + "password", + "scrollable", + "selected" +}; + +const char* STRING_ATTRIBUTES[] = { + "name" +}; + +const char* INT_ATTRIBUTES[] = { + "item_index", + "item_count" +}; +} + +void AccessibilityTreeFormatter::Initialize() { +} + +void AccessibilityTreeFormatter::AddProperties( + const BrowserAccessibility& node, DictionaryValue* dict) { + const BrowserAccessibilityAndroid* android_node = + static_cast<const BrowserAccessibilityAndroid*>(&node); + JNIEnv* env = base::android::AttachCurrentThread(); + + // Class name. + dict->SetString("class", base::android::ConvertJavaStringToUTF8( + android_node->GetClassNameJNI(env, NULL))); + + // Bool attributes. + dict->SetBoolean("focusable", + android_node->IsFocusableJNI(env, NULL)); + dict->SetBoolean("focused", + android_node->IsFocusedJNI(env, NULL)); + dict->SetBoolean("clickable", + android_node->GetClickableJNI(env, NULL)); + dict->SetBoolean("editable_text", + android_node->IsEditableTextJNI(env, NULL)); + dict->SetBoolean("checkable", + android_node->IsCheckableJNI(env, NULL)); + dict->SetBoolean("checked", + android_node->IsCheckedJNI(env, NULL)); + dict->SetBoolean("disabled", + !android_node->IsEnabledJNI(env, NULL)); + dict->SetBoolean("scrollable", + android_node->IsScrollableJNI(env, NULL)); + dict->SetBoolean("password", + android_node->IsPasswordJNI(env, NULL)); + dict->SetBoolean("selected", + android_node->IsSelectedJNI(env, NULL)); + dict->SetBoolean("invisible", + !android_node->IsVisibleJNI(env, NULL)); + + // String attributes. + dict->SetString("name", base::android::ConvertJavaStringToUTF8( + android_node->GetNameJNI(env, NULL))); + + // Int attributes. + dict->SetInteger("item_index", + android_node->GetItemIndexJNI(env, NULL)); + dict->SetInteger("item_count", + android_node->GetItemCountJNI(env, NULL)); +} + +bool AccessibilityTreeFormatter::IncludeChildren( + const BrowserAccessibility& node) { + const BrowserAccessibilityAndroid* android_node = + static_cast<const BrowserAccessibilityAndroid*>(&node); + JNIEnv* env = base::android::AttachCurrentThread(); + + return 0 != android_node->GetChildCountJNI(env, NULL); +} + +string16 AccessibilityTreeFormatter::ToString(const DictionaryValue& dict, + const string16& indent) { + string16 line; + + string16 class_value; + dict.GetString("class", &class_value); + WriteAttribute(true, UTF16ToUTF8(class_value), &line); + + for (unsigned i = 0; i < arraysize(BOOL_ATTRIBUTES); i++) { + const char* attribute_name = BOOL_ATTRIBUTES[i]; + bool value; + if (dict.GetBoolean(attribute_name, &value) && value) + WriteAttribute(true, attribute_name, &line); + } + + for (unsigned i = 0; i < arraysize(STRING_ATTRIBUTES); i++) { + const char* attribute_name = STRING_ATTRIBUTES[i]; + std::string value; + if (!dict.GetString(attribute_name, &value) || value.empty()) + continue; + WriteAttribute(true, + StringPrintf("%s='%s'", attribute_name, value.c_str()), + &line); + } + + for (unsigned i = 0; i < arraysize(INT_ATTRIBUTES); i++) { + const char* attribute_name = INT_ATTRIBUTES[i]; + int value; + if (!dict.GetInteger(attribute_name, &value) || value == 0) + continue; + WriteAttribute(true, + StringPrintf("%s=%d", attribute_name, value), + &line); + } + + return indent + line + ASCIIToUTF16("\n"); +} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetActualFileSuffix() { + return FILE_PATH_LITERAL("-actual-android.txt"); +} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetExpectedFileSuffix() { + return FILE_PATH_LITERAL("-expected-android.txt"); +} + +// static +const std::string AccessibilityTreeFormatter::GetAllowEmptyString() { + return "@ANDROID-ALLOW-EMPTY:"; +} + +// static +const std::string AccessibilityTreeFormatter::GetAllowString() { + return "@ANDROID-ALLOW:"; +} + +// static +const std::string AccessibilityTreeFormatter::GetDenyString() { + return "@ANDROID-DENY:"; +} + +} // namespace content diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc index f2f040b..ae7efe2 100644 --- a/content/browser/accessibility/browser_accessibility.cc +++ b/content/browser/accessibility/browser_accessibility.cc @@ -19,7 +19,8 @@ typedef AccessibilityNodeData::StringAttribute StringAttribute; #if !defined(OS_MACOSX) && \ !defined(OS_WIN) && \ - !defined(TOOLKIT_GTK) + !defined(TOOLKIT_GTK) && \ + !defined(OS_ANDROID) // We have subclassess of BrowserAccessibility on Mac, Linux/GTK, // and Win. For any other platform, instantiate the base class. // static @@ -112,7 +113,7 @@ bool BrowserAccessibility::IsDescendantOf( return false; } -BrowserAccessibility* BrowserAccessibility::GetChild(uint32 child_index) { +BrowserAccessibility* BrowserAccessibility::GetChild(uint32 child_index) const { DCHECK(child_index < children_.size()); return children_[child_index]; } @@ -134,7 +135,7 @@ BrowserAccessibility* BrowserAccessibility::GetNextSibling() { return NULL; } -gfx::Rect BrowserAccessibility::GetLocalBoundsRect() { +gfx::Rect BrowserAccessibility::GetLocalBoundsRect() const { gfx::Rect bounds = location_; // Walk up the parent chain. Every time we encounter a Web Area, offset @@ -151,6 +152,14 @@ gfx::Rect BrowserAccessibility::GetLocalBoundsRect() { bounds.Offset(parent->location().x(), parent->location().y()); need_to_offset_web_area = false; } + + // On some platforms, we don't want to take the root scroll offsets + // into account. + if (parent->role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA && + !manager()->UseRootScrollOffsetsWhenComputingBounds()) { + break; + } + if (parent->role() == AccessibilityNodeData::ROLE_WEB_AREA || parent->role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA) { int sx = 0; @@ -167,7 +176,7 @@ gfx::Rect BrowserAccessibility::GetLocalBoundsRect() { return bounds; } -gfx::Rect BrowserAccessibility::GetGlobalBoundsRect() { +gfx::Rect BrowserAccessibility::GetGlobalBoundsRect() const { gfx::Rect bounds = GetLocalBoundsRect(); // Adjust the bounds by the top left corner of the containing view's bounds @@ -303,6 +312,13 @@ bool BrowserAccessibility::HasState( } bool BrowserAccessibility::IsEditableText() const { + // These roles don't have readonly set, but they're not editable text. + if (role_ == AccessibilityNodeData::ROLE_SCROLLAREA || + role_ == AccessibilityNodeData::ROLE_COLUMN || + role_ == AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER) { + return false; + } + // Note: STATE_READONLY being false means it's either a text control, // or contenteditable. We also check for editable text roles to cover // another element that has role=textbox set on it. diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h index d70124a..8fda68f 100644 --- a/content/browser/accessibility/browser_accessibility.h +++ b/content/browser/accessibility/browser_accessibility.h @@ -88,13 +88,13 @@ class CONTENT_EXPORT BrowserAccessibility { bool IsDescendantOf(BrowserAccessibility* ancestor); // Returns the parent of this object, or NULL if it's the root. - BrowserAccessibility* parent() { return parent_; } + BrowserAccessibility* parent() const { return parent_; } // Returns the number of children of this object. uint32 child_count() const { return children_.size(); } // Return a pointer to the child with the given index. - BrowserAccessibility* GetChild(uint32 child_index); + BrowserAccessibility* GetChild(uint32 child_index) const; // Return the previous sibling of this object, or NULL if it's the first // child of its parent. @@ -106,10 +106,10 @@ class CONTENT_EXPORT BrowserAccessibility { // Returns the bounds of this object in coordinates relative to the // top-left corner of the overall web area. - gfx::Rect GetLocalBoundsRect(); + gfx::Rect GetLocalBoundsRect() const; // Returns the bounds of this object in screen coordinates. - gfx::Rect GetGlobalBoundsRect(); + gfx::Rect GetGlobalBoundsRect() const; // Returns the deepest descendant that contains the specified point // (in global screen coordinates). diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc new file mode 100644 index 0000000..511d67e --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_android.cc @@ -0,0 +1,534 @@ +// Copyright 2013 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 "content/browser/accessibility/browser_accessibility_android.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_manager_android.h" +#include "content/common/accessibility_messages.h" +#include "content/common/accessibility_node_data.h" + +using base::android::ScopedJavaLocalRef; + +namespace content { + +// static +BrowserAccessibility* BrowserAccessibility::Create() { + return new BrowserAccessibilityAndroid(); +} + +BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() { + first_time_ = true; +} + +bool BrowserAccessibilityAndroid::IsNative() const { + return true; +} + +// +// Actions, called from Java. +// + +void BrowserAccessibilityAndroid::FocusJNI(JNIEnv* env, jobject obj) { + manager_->SetFocus(this, true); +} + +void BrowserAccessibilityAndroid::ClickJNI(JNIEnv* env, jobject obj) { + manager_->DoDefaultAction(*this); +} + +// +// Const accessors, called from Java. +// + +ScopedJavaLocalRef<jstring> +BrowserAccessibilityAndroid::GetNameJNI(JNIEnv* env, jobject obj) const { + return base::android::ConvertUTF16ToJavaString(env, ComputeName()); +} + +ScopedJavaLocalRef<jobject> +BrowserAccessibilityAndroid::GetAbsoluteRectJNI( + JNIEnv* env, jobject obj) const { + gfx::Rect rect = GetLocalBoundsRect(); + + // TODO(aboxhall): replace with non-stub implementation + return ScopedJavaLocalRef<jobject>(env, NULL); +} + +ScopedJavaLocalRef<jobject> +BrowserAccessibilityAndroid::GetRectInParentJNI( + JNIEnv* env, jobject obj) const { + gfx::Rect rect = GetLocalBoundsRect(); + if (parent()) { + gfx::Rect parent_rect = parent()->GetLocalBoundsRect(); + rect.Offset(-parent_rect.OffsetFromOrigin()); + } + + // TODO(aboxhall): replace with non-stub implementation + return ScopedJavaLocalRef<jobject>(env, NULL); +} + +jboolean +BrowserAccessibilityAndroid::IsFocusableJNI(JNIEnv* env, jobject obj) const { + return static_cast<jboolean>(IsFocusable()); +} + +jboolean +BrowserAccessibilityAndroid::IsEditableTextJNI(JNIEnv* env, jobject obj) const { + return IsEditableText(); +} + +jint BrowserAccessibilityAndroid::GetParentJNI(JNIEnv* env, jobject obj) const { + return static_cast<jint>(parent()->renderer_id()); +} + +jint +BrowserAccessibilityAndroid::GetChildCountJNI(JNIEnv* env, jobject obj) const { + if (IsLeaf()) + return 0; + else + return static_cast<jint>(child_count()); +} + +jint BrowserAccessibilityAndroid::GetChildIdAtJNI(JNIEnv* env, + jobject obj, + jint child_index) const { + return static_cast<jint>(GetChild(child_index)->renderer_id()); +} + +jboolean +BrowserAccessibilityAndroid::IsCheckableJNI(JNIEnv* env, jobject obj) const { + bool checkable = false; + bool is_aria_pressed_defined; + bool is_mixed; + GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed); + if (role() == AccessibilityNodeData::ROLE_CHECKBOX || + role() == AccessibilityNodeData::ROLE_RADIO_BUTTON || + is_aria_pressed_defined) { + checkable = true; + } + if (HasState(AccessibilityNodeData::STATE_CHECKED)) + checkable = true; + return static_cast<jboolean>(checkable); +} + +jboolean +BrowserAccessibilityAndroid::IsCheckedJNI(JNIEnv* env, jobject obj) const { + return static_cast<jboolean>( + HasState(AccessibilityNodeData::STATE_CHECKED)); +} + +base::android::ScopedJavaLocalRef<jstring> +BrowserAccessibilityAndroid::GetClassNameJNI(JNIEnv* env, jobject obj) const { + const char* class_name = NULL; + + switch(role()) { + case AccessibilityNodeData::ROLE_EDITABLE_TEXT: + case AccessibilityNodeData::ROLE_SPIN_BUTTON: + case AccessibilityNodeData::ROLE_TEXTAREA: + case AccessibilityNodeData::ROLE_TEXT_FIELD: + class_name = "android.widget.EditText"; + break; + case AccessibilityNodeData::ROLE_SLIDER: + class_name = "android.widget.SeekBar"; + break; + case AccessibilityNodeData::ROLE_COMBO_BOX: + class_name = "android.widget.Spinner"; + break; + case AccessibilityNodeData::ROLE_BUTTON: + case AccessibilityNodeData::ROLE_MENU_BUTTON: + case AccessibilityNodeData::ROLE_POPUP_BUTTON: + class_name = "android.widget.Button"; + break; + case AccessibilityNodeData::ROLE_CHECKBOX: + class_name = "android.widget.CheckBox"; + break; + case AccessibilityNodeData::ROLE_RADIO_BUTTON: + class_name = "android.widget.RadioButton"; + break; + case AccessibilityNodeData::ROLE_TOGGLE_BUTTON: + class_name = "android.widget.ToggleButton"; + break; + case AccessibilityNodeData::ROLE_CANVAS: + case AccessibilityNodeData::ROLE_IMAGE: + class_name = "android.widget.Image"; + break; + case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: + class_name = "android.widget.ProgressBar"; + break; + case AccessibilityNodeData::ROLE_TAB_LIST: + class_name = "android.widget.TabWidget"; + break; + case AccessibilityNodeData::ROLE_GRID: + case AccessibilityNodeData::ROLE_TABLE: + class_name = "android.widget.GridView"; + break; + case AccessibilityNodeData::ROLE_LIST: + case AccessibilityNodeData::ROLE_LISTBOX: + class_name = "android.widget.ListView"; + break; + default: + class_name = "android.view.View"; + break; + } + + return base::android::ConvertUTF8ToJavaString(env, class_name); +} + +jboolean +BrowserAccessibilityAndroid::IsEnabledJNI(JNIEnv* env, jobject obj) const { + return static_cast<jboolean>( + !HasState(AccessibilityNodeData::STATE_UNAVAILABLE)); +} + +jboolean +BrowserAccessibilityAndroid::IsFocusedJNI(JNIEnv* env, jobject obj) const { + return manager()->GetFocus(manager()->GetRoot()) == this; +} + +jboolean +BrowserAccessibilityAndroid::IsPasswordJNI(JNIEnv* env, jobject obj) const { + return static_cast<jboolean>( + HasState(AccessibilityNodeData::STATE_PROTECTED)); +} + +jboolean +BrowserAccessibilityAndroid::IsScrollableJNI(JNIEnv* env, jobject obj) const { + int dummy; + bool scrollable = GetIntAttribute( + AccessibilityNodeData::ATTR_SCROLL_X_MAX, &dummy); + return static_cast<jboolean>(scrollable); +} + +jboolean +BrowserAccessibilityAndroid::IsSelectedJNI(JNIEnv* env, jobject obj) const { + return static_cast<jboolean>( + HasState(AccessibilityNodeData::STATE_SELECTED)); +} + +jboolean +BrowserAccessibilityAndroid::IsVisibleJNI(JNIEnv* env, jobject obj) const { + return static_cast<jboolean>( + !HasState(AccessibilityNodeData::STATE_INVISIBLE)); +} + +base::android::ScopedJavaLocalRef<jstring> +BrowserAccessibilityAndroid::GetAriaLiveJNI(JNIEnv* env, jobject obj) const { + return base::android::ConvertUTF16ToJavaString(env, GetAriaLive()); +} + +jint BrowserAccessibilityAndroid::GetItemIndexJNI( + JNIEnv* env, jobject obj) const { + int index = 0; + switch(role()) { + case AccessibilityNodeData::ROLE_LIST_ITEM: + case AccessibilityNodeData::ROLE_LISTBOX_OPTION: + index = index_in_parent(); + break; + case AccessibilityNodeData::ROLE_SLIDER: + case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: { + float value_for_range; + if (GetFloatAttribute( + AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &value_for_range)) { + index = static_cast<int>(value_for_range); + } + break; + } + } + return static_cast<jint>(index); +} + +jint +BrowserAccessibilityAndroid::GetItemCountJNI(JNIEnv* env, jobject obj) const { + int count = 0; + switch(role()) { + case AccessibilityNodeData::ROLE_LIST: + case AccessibilityNodeData::ROLE_LISTBOX: + count = child_count(); + break; + case AccessibilityNodeData::ROLE_SLIDER: + case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: { + float max_value_for_range; + if (GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE, + &max_value_for_range)) { + count = static_cast<int>(max_value_for_range); + } + break; + } + } + return static_cast<jint>(count); +} + +jint +BrowserAccessibilityAndroid::GetScrollXJNI(JNIEnv* env, jobject obj) const { + int value = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &value); + return static_cast<jint>(value); +} + +jint +BrowserAccessibilityAndroid::GetScrollYJNI(JNIEnv* env, jobject obj) const { + int value = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &value); + return static_cast<jint>(value); +} + +jint +BrowserAccessibilityAndroid::GetMaxScrollXJNI(JNIEnv* env, jobject obj) const { + int value = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &value); + return static_cast<jint>(value); +} + +jint +BrowserAccessibilityAndroid::GetMaxScrollYJNI(JNIEnv* env, jobject obj) const { + int value = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y_MAX, &value); + return static_cast<jint>(value); +} + +jboolean +BrowserAccessibilityAndroid::GetClickableJNI(JNIEnv* env, jobject obj) const { + return (IsLeaf() && !ComputeName().empty()); +} + +jint BrowserAccessibilityAndroid::GetSelectionStartJNI(JNIEnv* env, jobject obj) + const { + int sel_start = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start); + return sel_start; +} + +jint BrowserAccessibilityAndroid::GetSelectionEndJNI(JNIEnv* env, jobject obj) + const { + int sel_end = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end); + return sel_end; +} + +jint BrowserAccessibilityAndroid::GetEditableTextLengthJNI( + JNIEnv* env, jobject obj) const { + return value().length(); +} + +int BrowserAccessibilityAndroid::GetTextChangeFromIndexJNI( + JNIEnv* env, jobject obj) const { + size_t index = 0; + while (index < old_value_.length() && + index < new_value_.length() && + old_value_[index] == new_value_[index]) { + index++; + } + return index; +} + +jint BrowserAccessibilityAndroid::GetTextChangeAddedCountJNI( + JNIEnv* env, jobject obj) const { + size_t old_len = old_value_.length(); + size_t new_len = new_value_.length(); + size_t left = 0; + while (left < old_len && + left < new_len && + old_value_[left] == new_value_[left]) { + left++; + } + size_t right = 0; + while (right < old_len && + right < new_len && + old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) { + right++; + } + return (new_len - left - right); +} + +jint BrowserAccessibilityAndroid::GetTextChangeRemovedCountJNI( + JNIEnv* env, jobject obj) const { + size_t old_len = old_value_.length(); + size_t new_len = new_value_.length(); + size_t left = 0; + while (left < old_len && + left < new_len && + old_value_[left] == new_value_[left]) { + left++; + } + size_t right = 0; + while (right < old_len && + right < new_len && + old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) { + right++; + } + return (old_len - left - right); +} + +base::android::ScopedJavaLocalRef<jstring> +BrowserAccessibilityAndroid::GetTextChangeBeforeTextJNI( + JNIEnv* env, jobject obj) const { + return base::android::ConvertUTF16ToJavaString(env, old_value_); +} + +string16 BrowserAccessibilityAndroid::ComputeName() const { + if (IsIframe() || + role() == AccessibilityNodeData::ROLE_WEB_AREA) { + return string16(); + } + + string16 description; + GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION, + &description); + + string16 text; + if (!name().empty()) + text = name(); + else if (!description.empty()) + text = description; + else if (!value().empty()) + text = value(); + + if (text.empty() && HasOnlyStaticTextChildren()) { + for (uint32 i = 0; i < child_count(); i++) { + BrowserAccessibility* child = GetChild(i); + text += static_cast<BrowserAccessibilityAndroid*>(child)->ComputeName(); + } + } + + switch(role()) { + case AccessibilityNodeData::ROLE_IMAGE_MAP_LINK: + case AccessibilityNodeData::ROLE_LINK: + case AccessibilityNodeData::ROLE_WEBCORE_LINK: + if (!text.empty()) + text += ASCIIToUTF16(" "); + text += ASCIIToUTF16("Link"); + break; + case AccessibilityNodeData::ROLE_HEADING: + // Only append "heading" if this node already has text. + if (!text.empty()) + text += ASCIIToUTF16(" Heading"); + break; + } + + return text; +} + +string16 BrowserAccessibilityAndroid::GetAriaLive() const { + string16 aria_live; + if (GetStringAttribute(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS, + &aria_live)) { + return aria_live; + } + return string16(); +} + +bool BrowserAccessibilityAndroid::HasFocusableChild() const { + for (uint32 i = 0; i < child_count(); i++) { + BrowserAccessibility* child = GetChild(i); + if (child->HasState(AccessibilityNodeData::STATE_FOCUSABLE)) + return true; + if (static_cast<BrowserAccessibilityAndroid*>(child)->HasFocusableChild()) + return true; + } + return false; +} + +bool BrowserAccessibilityAndroid::HasOnlyStaticTextChildren() const { + for (uint32 i = 0; i < child_count(); i++) { + BrowserAccessibility* child = GetChild(i); + if (child->role() != AccessibilityNodeData::ROLE_STATIC_TEXT) + return false; + } + return true; +} + +bool BrowserAccessibilityAndroid::IsIframe() const { + string16 html_tag; + GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &html_tag); + return html_tag == ASCIIToUTF16("iframe"); +} + +bool BrowserAccessibilityAndroid::IsFocusable() const { + bool focusable = HasState(AccessibilityNodeData::STATE_FOCUSABLE); + if (IsIframe() || + role() == AccessibilityNodeData::ROLE_WEB_AREA) { + focusable = false; + } + return focusable; +} + +bool BrowserAccessibilityAndroid::IsLeaf() const { + if (child_count() == 0) + return true; + + // Iframes are always allowed to contain children. + if (IsIframe() || + role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA || + role() == AccessibilityNodeData::ROLE_WEB_AREA) { + return false; + } + + // If it has a focusable child, we definitely can't leave out children. + if (HasFocusableChild()) + return false; + + // Headings with text can drop their children. + string16 name = ComputeName(); + if (role() == AccessibilityNodeData::ROLE_HEADING && !name.empty()) + return true; + + // Focusable nodes with text can drop their children. + if (HasState(AccessibilityNodeData::STATE_FOCUSABLE) && !name.empty()) + return true; + + // Nodes with only static text as children can drop their children. + if (HasOnlyStaticTextChildren()) + return true; + + return false; +} + +void BrowserAccessibilityAndroid::PostInitialize() { + BrowserAccessibility::PostInitialize(); + + if (IsEditableText()) { + if (value_ != new_value_) { + old_value_ = new_value_; + new_value_ = value_; + } + } + + if (role_ == AccessibilityNodeData::ROLE_ALERT && first_time_) + manager_->NotifyAccessibilityEvent(AccessibilityNotificationAlert, this); + + string16 live; + if (GetStringAttribute(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS, + &live)) { + NotifyLiveRegionUpdate(live); + } + + first_time_ = false; +} + +void BrowserAccessibilityAndroid::NotifyLiveRegionUpdate(string16& aria_live) { + if (!EqualsASCII(aria_live, aria_strings::kAriaLivePolite) && + !EqualsASCII(aria_live, aria_strings::kAriaLiveAssertive)) + return; + + string16 text = ComputeName(); + if (cached_text_ != text) { + if (!text.empty()) { + manager_->NotifyAccessibilityEvent(AccessibilityNotificationObjectShow, + this); + } + cached_text_ = text; + } +} + +bool RegisterBrowserAccessibility(JNIEnv* env) { + // TODO(aboxhall): replace with non-stub implementation + return false; +} + +} // namespace content diff --git a/content/browser/accessibility/browser_accessibility_android.h b/content/browser/accessibility/browser_accessibility_android.h new file mode 100644 index 0000000..2de8c75 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_android.h @@ -0,0 +1,99 @@ +// Copyright 2013 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. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_ + +#include "base/android/scoped_java_ref.h" +#include "content/browser/accessibility/browser_accessibility.h" + +namespace content { + +class BrowserAccessibilityAndroid : public BrowserAccessibility { + public: + // Overrides from BrowserAccessibility. + virtual void PostInitialize() OVERRIDE; + virtual bool IsNative() const OVERRIDE; + + // -------------------------------------------------------------------------- + // Methods called from Java via JNI + // -------------------------------------------------------------------------- + + // Actions + void FocusJNI(JNIEnv* env, jobject obj); + void ClickJNI(JNIEnv* env, jobject obj); + + // Const accessors + jboolean GetClickableJNI(JNIEnv* env, jobject obj) const; + jboolean IsFocusedJNI(JNIEnv* env, jobject obj) const; + jboolean IsEditableTextJNI(JNIEnv* env, jobject obj) const; + base::android::ScopedJavaLocalRef<jstring> GetNameJNI( + JNIEnv* env, jobject obj) const; + base::android::ScopedJavaLocalRef<jobject> GetAbsoluteRectJNI( + JNIEnv* env, jobject obj) const; + base::android::ScopedJavaLocalRef<jobject> GetRectInParentJNI( + JNIEnv* env, jobject obj) const; + jboolean IsFocusableJNI(JNIEnv* env, jobject obj) const; + jint GetParentJNI(JNIEnv* env, jobject obj) const; + jint GetChildCountJNI(JNIEnv* env, jobject obj) const; + jint GetChildIdAtJNI( + JNIEnv* env, jobject obj, jint child_index) const; + jboolean IsCheckableJNI(JNIEnv* env, jobject obj) const; + jboolean IsCheckedJNI(JNIEnv* env, jobject obj) const; + base::android::ScopedJavaLocalRef<jstring> GetClassNameJNI( + JNIEnv* env, jobject obj) const; + jboolean IsEnabledJNI(JNIEnv* env, jobject obj) const; + jboolean IsPasswordJNI(JNIEnv* env, jobject obj) const; + jboolean IsScrollableJNI(JNIEnv* env, jobject obj) const; + jboolean IsSelectedJNI(JNIEnv* env, jobject obj) const; + jboolean IsVisibleJNI(JNIEnv* env, jobject obj) const; + jint GetItemIndexJNI(JNIEnv* env, jobject obj) const; + jint GetItemCountJNI(JNIEnv* env, jobject obj) const; + jint GetScrollXJNI(JNIEnv* env, jobject obj) const; + jint GetScrollYJNI(JNIEnv* env, jobject obj) const; + jint GetMaxScrollXJNI(JNIEnv* env, jobject obj) const; + jint GetMaxScrollYJNI(JNIEnv* env, jobject obj) const; + base::android::ScopedJavaLocalRef<jstring> GetAriaLiveJNI( + JNIEnv* env, jobject obj) const; + jint GetSelectionStartJNI(JNIEnv* env, jobject obj) const; + jint GetSelectionEndJNI(JNIEnv* env, jobject obj) const; + jint GetEditableTextLengthJNI(JNIEnv* env, jobject obj) const; + jint GetTextChangeFromIndexJNI(JNIEnv* env, jobject obj) const; + jint GetTextChangeAddedCountJNI(JNIEnv* env, jobject obj) const; + jint GetTextChangeRemovedCountJNI(JNIEnv* env, jobject obj) const; + base::android::ScopedJavaLocalRef<jstring> GetTextChangeBeforeTextJNI( + JNIEnv* env, jobject obj) const; + + private: + // This gives BrowserAccessibility::Create access to the class constructor. + friend class BrowserAccessibility; + + // Allow BrowserAccessibilityManagerAndroid to call these private methods. + friend class BrowserAccessibilityManagerAndroid; + + BrowserAccessibilityAndroid(); + + string16 ComputeName() const; + string16 GetAriaLive() const; + bool IsFocusable() const; + bool HasFocusableChild() const; + bool HasOnlyStaticTextChildren() const; + bool IsIframe() const; + bool IsLeaf() const; + + void NotifyLiveRegionUpdate(string16& aria_live); + + string16 cached_text_; + bool first_time_; + string16 old_value_; + string16 new_value_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityAndroid); +}; + +bool RegisterBrowserAccessibility(JNIEnv* env); + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_ diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc index 498d853..3856a20 100644 --- a/content/browser/accessibility/browser_accessibility_manager.cc +++ b/content/browser/accessibility/browser_accessibility_manager.cc @@ -6,7 +6,6 @@ #include "base/logging.h" #include "content/browser/accessibility/browser_accessibility.h" -#include "content/browser/accessibility/browser_accessibility_state_impl.h" #include "content/common/accessibility_messages.h" namespace content { @@ -17,7 +16,8 @@ BrowserAccessibility* BrowserAccessibilityFactory::Create() { #if !defined(OS_MACOSX) && \ !defined(OS_WIN) && \ - !defined(TOOLKIT_GTK) + !defined(TOOLKIT_GTK) && \ + !defined(OS_ANDROID) \ // We have subclassess of BrowserAccessibilityManager on Mac, Linux/GTK, // and Win. For any other platform, instantiate the base class. // static @@ -91,6 +91,10 @@ bool BrowserAccessibilityManager::IsOSKAllowed(const gfx::Rect& bounds) { return bounds.Contains(touch_point); } +bool BrowserAccessibilityManager::UseRootScrollOffsetsWhenComputingBounds() { + return true; +} + void BrowserAccessibilityManager::RemoveNode(BrowserAccessibility* node) { if (node == focus_) SetFocus(root_, false); @@ -158,6 +162,11 @@ void BrowserAccessibilityManager::SetFocus( delegate_->SetAccessibilityFocus(node->renderer_id()); } +void BrowserAccessibilityManager::SetRoot(BrowserAccessibility* node) { + root_ = node; + NotifyRootChanged(); +} + void BrowserAccessibilityManager::DoDefaultAction( const BrowserAccessibility& node) { if (delegate_) @@ -372,14 +381,16 @@ bool BrowserAccessibilityManager::UpdateNode(const AccessibilityNodeData& src) { if (root_) root_->Destroy(); if (focus_ == root_) - focus_ = instance; - root_ = instance; + SetFocus(instance, false); + SetRoot(instance); } // Keep track of what node is focused. - if ((src.state >> AccessibilityNodeData::STATE_FOCUSED) & 1) + if (src.role != AccessibilityNodeData::ROLE_ROOT_WEB_AREA && + src.role != AccessibilityNodeData::ROLE_WEB_AREA && + (src.state >> AccessibilityNodeData::STATE_FOCUSED & 1)) { SetFocus(instance, false); - + } return success; } diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h index 356f976..2b9761e 100644 --- a/content/browser/accessibility/browser_accessibility_manager.h +++ b/content/browser/accessibility/browser_accessibility_manager.h @@ -137,6 +137,10 @@ class CONTENT_EXPORT BrowserAccessibilityManager { // focus event on a text box? bool IsOSKAllowed(const gfx::Rect& bounds); + // True by default, but some platforms want to treat the root + // scroll offsets separately. + virtual bool UseRootScrollOffsetsWhenComputingBounds(); + // For testing only: update the given nodes as if they were // received from the renderer process in OnAccessibilityNotifications. // Takes up to 7 nodes at once so tests don't need to create a vector @@ -158,6 +162,8 @@ class CONTENT_EXPORT BrowserAccessibilityManager { virtual void AddNodeToMap(BrowserAccessibility* node); + virtual void NotifyRootChanged() {} + private: // The following states keep track of whether or not the // on-screen keyboard is allowed to be shown. @@ -189,6 +195,8 @@ class CONTENT_EXPORT BrowserAccessibilityManager { // process. Returns true on success, false on fatal error. bool UpdateNode(const AccessibilityNodeData& src); + void SetRoot(BrowserAccessibility* root); + BrowserAccessibility* CreateNode( BrowserAccessibility* parent, int32 renderer_id, diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc new file mode 100644 index 0000000..5b57b48 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_manager_android.cc @@ -0,0 +1,185 @@ +// Copyright 2013 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 "content/browser/accessibility/browser_accessibility_manager_android.h" + +#include <cmath> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "content/browser/accessibility/browser_accessibility_android.h" +#include "content/common/accessibility_messages.h" + +using base::android::AttachCurrentThread; +using base::android::ScopedJavaLocalRef; + +namespace { + +// Restricts |val| to the range [min, max]. +int Clamp(int val, int min, int max) { + return std::min(std::max(val, min), max); +} + +} // anonymous namespace + +namespace content { + +namespace aria_strings { + const char kAriaLivePolite[] = "polite"; + const char kAriaLiveAssertive[] = "assertive"; +} + +// static +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) { + return new BrowserAccessibilityManagerAndroid(ScopedJavaLocalRef<jobject>(), + src, delegate, factory); +} + +BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid( + ScopedJavaLocalRef<jobject> content_view_core, + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : BrowserAccessibilityManager(src, delegate, factory) { + if (content_view_core.is_null()) + return; + + // TODO(aboxhall): set up Java references +} + +BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + // TODO(aboxhall): tear down Java references +} + +// static +AccessibilityNodeData BrowserAccessibilityManagerAndroid::GetEmptyDocument() { + AccessibilityNodeData empty_document; + empty_document.id = 0; + empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + empty_document.state = 1 << AccessibilityNodeData::STATE_READONLY; + return empty_document; +} + +void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent( + int type, + BrowserAccessibility* node) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + // TODO(aboxhall): call into appropriate Java method for each type of + // notification +} + +jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) { + return static_cast<jint>(root_->renderer_id()); +} + +jint BrowserAccessibilityManagerAndroid::HitTest( + JNIEnv* env, jobject obj, jint x, jint y) { + BrowserAccessibilityAndroid* result = + static_cast<BrowserAccessibilityAndroid*>( + root_->BrowserAccessibilityForPoint(gfx::Point(x, y))); + + if (!result) + return root_->renderer_id(); + + if (result->IsFocusable()) + return result->renderer_id(); + + // Examine the children of |result| to find the nearest accessibility focus + // candidate + BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result); + if (nearest_node) + return nearest_node->renderer_id(); + + return root_->renderer_id(); +} + +BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest( + int x, int y, BrowserAccessibility* start_node) { + BrowserAccessibility* nearest_node = NULL; + int min_distance = INT_MAX; + FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance); + return nearest_node; +} + +// static +void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl( + int x, int y, BrowserAccessibility* start_node, + BrowserAccessibility** nearest_candidate, int* nearest_distance) { + BrowserAccessibilityAndroid* node = + static_cast<BrowserAccessibilityAndroid*>(start_node); + int distance = CalculateDistanceSquared(x, y, node); + + if (node->IsFocusable()) { + if (distance < *nearest_distance) { + *nearest_candidate = node; + *nearest_distance = distance; + } + // Don't examine any more children of focusable node + // TODO(aboxhall): what about focusable children? + return; + } + + if (!node->ComputeName().empty()) { + if (distance < *nearest_distance) { + *nearest_candidate = node; + *nearest_distance = distance; + } + return; + } + + if (!node->IsLeaf()) { + for (uint32 i = 0; i < node->child_count(); i++) { + BrowserAccessibility* child = node->GetChild(i); + FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance); + } + } +} + +// static +int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared( + int x, int y, BrowserAccessibility* node) { + gfx::Rect node_bounds = node->GetLocalBoundsRect(); + int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right()); + int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom()); + int dx = std::abs(x - nearest_x); + int dy = std::abs(y - nearest_y); + return dx * dx + dy * dy; +} + +jint BrowserAccessibilityManagerAndroid::GetNativeNodeById( + JNIEnv* env, jobject obj, jint id) { + return reinterpret_cast<jint>(GetFromRendererID(id)); +} + +void BrowserAccessibilityManagerAndroid::NotifyRootChanged() { + // TODO(aboxhall): non-stub implementation +} + +bool +BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() { + // The Java layer handles the root scroll offset. + return false; +} + +bool RegisterBrowserAccessibilityManager(JNIEnv* env) { + // TODO(aboxhall): non-stub implementation + return false; +} + +} // namespace content diff --git a/content/browser/accessibility/browser_accessibility_manager_android.h b/content/browser/accessibility/browser_accessibility_manager_android.h new file mode 100644 index 0000000..0ca04e5 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_manager_android.h @@ -0,0 +1,85 @@ +// Copyright 2013 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. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_ + +#include "base/android/scoped_java_ref.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/browser/android/content_view_core_impl.h" + +namespace content { + +namespace aria_strings { + extern const char kAriaLivePolite[]; + extern const char kAriaLiveAssertive[]; +} + +class CONTENT_EXPORT BrowserAccessibilityManagerAndroid + : public BrowserAccessibilityManager { + public: + BrowserAccessibilityManagerAndroid( + base::android::ScopedJavaLocalRef<jobject> content_view_core, + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory()); + + virtual ~BrowserAccessibilityManagerAndroid(); + + static AccessibilityNodeData GetEmptyDocument(); + + // Implementation of BrowserAccessibilityManager. + virtual void NotifyAccessibilityEvent(int type, + BrowserAccessibility* node) OVERRIDE; + + // -------------------------------------------------------------------------- + // Methods called from Java via JNI + // -------------------------------------------------------------------------- + + // Tree methods. + jint GetRootId(JNIEnv* env, jobject obj); + jint HitTest(JNIEnv* env, jobject obj, jint x, jint y); + + // Gets a temporary pointer to a specific node, only valid in this scope. + // May return 0 if that node id is no longer valid. + jint GetNativeNodeById(JNIEnv* env, jobject obj, jint id); + + protected: + virtual void NotifyRootChanged() OVERRIDE; + + virtual bool UseRootScrollOffsetsWhenComputingBounds() OVERRIDE; + + private: + // This gives BrowserAccessibilityManager::Create access to the class + // constructor. + friend class BrowserAccessibilityManager; + + // A weak reference to the Java BrowserAccessibilityManager object. + // This avoids adding another reference to BrowserAccessibilityManager and + // preventing garbage collection. + // Premature garbage collection is prevented by the long-lived reference in + // ContentViewCore. + JavaObjectWeakGlobalRef java_ref_; + + // Searches through the children of start_node to find the nearest + // accessibility focus candidate for a touch which has not landed directly on + // an accessibility focus candidate. + BrowserAccessibility* FuzzyHitTest( + int x, int y, BrowserAccessibility* start_node); + + static void FuzzyHitTestImpl(int x, int y, BrowserAccessibility* start_node, + BrowserAccessibility** nearest_candidate, int* min_distance); + + // Calculates the distance from the point (x, y) to the nearest point on the + // edge of |node|. + static int CalculateDistanceSquared(int x, int y, BrowserAccessibility* node); + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerAndroid); +}; + +bool RegisterBrowserAccessibilityManager(JNIEnv* env); + +} + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_ |