diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/content/browser/accessibility | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/content/browser/accessibility')
43 files changed, 15005 insertions, 0 deletions
diff --git a/chromium/content/browser/accessibility/OWNERS b/chromium/content/browser/accessibility/OWNERS new file mode 100644 index 00000000000..11e8fd837ee --- /dev/null +++ b/chromium/content/browser/accessibility/OWNERS @@ -0,0 +1,2 @@ +dmazzoni@chromium.org +dtseng@chromium.org diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter.cc new file mode 100644 index 00000000000..4368cf13217 --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_tree_formatter.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2012 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 "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/port/browser/render_widget_host_view_port.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" + +namespace content { +namespace { +const int kIndentSpaces = 4; +const char* kSkipString = "@NO_DUMP"; +const char* kChildrenDictAttr = "children"; +} + +AccessibilityTreeFormatter::AccessibilityTreeFormatter( + BrowserAccessibility* root) + : root_(root) { + Initialize(); +} + +// static +AccessibilityTreeFormatter* AccessibilityTreeFormatter::Create( + RenderViewHost* rvh) { + RenderWidgetHostViewPort* host_view = static_cast<RenderWidgetHostViewPort*>( + WebContents::FromRenderViewHost(rvh)->GetRenderWidgetHostView()); + + BrowserAccessibilityManager* manager = + host_view->GetBrowserAccessibilityManager(); + if (!manager) + return NULL; + + BrowserAccessibility* root = manager->GetRoot(); + return new AccessibilityTreeFormatter(root); +} + + +AccessibilityTreeFormatter::~AccessibilityTreeFormatter() { +} + +scoped_ptr<base::DictionaryValue> +AccessibilityTreeFormatter::BuildAccessibilityTree() { + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue); + RecursiveBuildAccessibilityTree(*root_, dict.get()); + return dict.Pass(); +} + +void AccessibilityTreeFormatter::FormatAccessibilityTree( + string16* contents) { + scoped_ptr<base::DictionaryValue> dict = BuildAccessibilityTree(); + RecursiveFormatAccessibilityTree(*(dict.get()), contents); +} + +void AccessibilityTreeFormatter::RecursiveBuildAccessibilityTree( + const BrowserAccessibility& node, base::DictionaryValue* dict) { + AddProperties(node, dict); + + base::ListValue* children = new base::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]; + base::DictionaryValue* child_dict = new base::DictionaryValue; + children->Append(child_dict); + RecursiveBuildAccessibilityTree(*child_node, child_dict); + } +} + +void AccessibilityTreeFormatter::RecursiveFormatAccessibilityTree( + const base::DictionaryValue& dict, string16* contents, int depth) { + string16 line = ToString(dict, string16(depth * kIndentSpaces, ' ')); + if (line.find(ASCIIToUTF16(kSkipString)) != string16::npos) + return; + + *contents += line; + const base::ListValue* children; + dict.GetList(kChildrenDictAttr, &children); + const base::DictionaryValue* child_dict; + for (size_t i = 0; i < children->GetSize(); i++) { + children->GetDictionary(i, &child_dict); + RecursiveFormatAccessibilityTree(*child_dict, contents, depth + 1); + } +} + +#if !defined(OS_ANDROID) +bool AccessibilityTreeFormatter::IncludeChildren( + const BrowserAccessibility& node) { + return true; +} +#endif + +#if (!defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_ANDROID) && \ + !defined(TOOLKIT_GTK)) +void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node, + base::DictionaryValue* dict) { + dict->SetInteger("id", node.renderer_id()); +} + +string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& node, + const string16& indent) { + int id_value; + node.GetInteger("id", &id_value); + return indent + base::IntToString16(id_value) + + ASCIIToUTF16("\n"); +} + +void AccessibilityTreeFormatter::Initialize() {} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetActualFileSuffix() { + return base::FilePath::StringType(); +} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetExpectedFileSuffix() { + return base::FilePath::StringType(); +} + +// static +const std::string AccessibilityTreeFormatter::GetAllowEmptyString() { + return std::string(); +} + +// static +const std::string AccessibilityTreeFormatter::GetAllowString() { + return std::string(); +} + +// static +const std::string AccessibilityTreeFormatter::GetDenyString() { + return std::string(); +} +#endif + +void AccessibilityTreeFormatter::SetFilters( + const std::vector<Filter>& filters) { + filters_ = filters; +} + +bool AccessibilityTreeFormatter::MatchesFilters( + const string16& text, bool default_result) const { + std::vector<Filter>::const_iterator iter = filters_.begin(); + bool allow = default_result; + for (iter = filters_.begin(); iter != filters_.end(); ++iter) { + if (MatchPattern(text, iter->match_str)) { + if (iter->type == Filter::ALLOW_EMPTY) + allow = true; + else if (iter->type == Filter::ALLOW) + allow = (!MatchPattern(text, UTF8ToUTF16("*=''"))); + else + allow = false; + } + } + return allow; +} + +string16 AccessibilityTreeFormatter::FormatCoordinates( + const char* name, const char* x_name, const char* y_name, + const base::DictionaryValue& value) { + int x, y; + value.GetInteger(x_name, &x); + value.GetInteger(y_name, &y); + std::string xy_str(base::StringPrintf("%s=(%d, %d)", name, x, y)); + + return UTF8ToUTF16(xy_str); +} + +void AccessibilityTreeFormatter::WriteAttribute( + bool include_by_default, const std::string& attr, string16* line) { + WriteAttribute(include_by_default, UTF8ToUTF16(attr), line); +} + +void AccessibilityTreeFormatter::WriteAttribute( + bool include_by_default, const string16& attr, string16* line) { + if (attr.empty()) + return; + if (!MatchesFilters(attr, include_by_default)) + return; + if (!line->empty()) + *line += ASCIIToUTF16(" "); + *line += attr; +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter.h b/chromium/content/browser/accessibility/accessibility_tree_formatter.h new file mode 100644 index 00000000000..3c36b2f6e52 --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_tree_formatter.h @@ -0,0 +1,153 @@ +// Copyright (c) 2012 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_ACCESSIBILITY_TREE_FORMATTER_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_H_ + +#include <vector> + +#include "base/files/file_path.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "content/browser/accessibility/browser_accessibility.h" +#include "content/common/content_export.h" + +namespace content { + +class RenderViewHost; + +// A utility class for formatting platform-specific accessibility information, +// for use in testing, debugging, and developer tools. +// This is extended by a subclass for each platform where accessibility is +// implemented. +class CONTENT_EXPORT AccessibilityTreeFormatter { + public: + explicit AccessibilityTreeFormatter(BrowserAccessibility* root); + virtual ~AccessibilityTreeFormatter(); + + static AccessibilityTreeFormatter* Create(RenderViewHost* rvh); + + // Populates the given DictionaryValue with the accessibility tree. + // The dictionary contains a key/value pair for each attribute of the node, + // plus a "children" attribute containing a list of all child nodes. + // { + // "AXName": "node", /* actual attributes will vary by platform */ + // "position": { /* some attributes may be dictionaries */ + // "x": 0, + // "y": 0 + // }, + // /* ... more attributes of |node| */ + // "children": [ { /* list of children created recursively */ + // "AXName": "child node 1", + // /* ... more attributes */ + // "children": [ ] + // }, { + // "AXName": "child name 2", + // /* ... more attributes */ + // "children": [ ] + // } ] + // } + scoped_ptr<base::DictionaryValue> BuildAccessibilityTree(); + + // Dumps a BrowserAccessibility tree into a string. + void FormatAccessibilityTree(string16* contents); + + // A single filter specification. See GetAllowString() and GetDenyString() + // for more information. + struct Filter { + enum Type { + ALLOW, + ALLOW_EMPTY, + DENY + }; + string16 match_str; + Type type; + + Filter(string16 match_str, Type type) + : match_str(match_str), type(type) {} + }; + + // Set regular expression filters that apply to each component of every + // line before it's output. + void SetFilters(const std::vector<Filter>& filters); + + // Suffix of the expectation file corresponding to html file. + // Example: + // HTML test: test-file.html + // Expected: test-file-expected-mac.txt. + // Auto-generated: test-file-actual-mac.txt + static const base::FilePath::StringType GetActualFileSuffix(); + static const base::FilePath::StringType GetExpectedFileSuffix(); + + // A platform-specific string that indicates a given line in a file + // is an allow-empty, allow or deny filter. Example: + // Mac values: + // GetAllowEmptyString() -> "@MAC-ALLOW-EMPTY:" + // GetAllowString() -> "@MAC-ALLOW:" + // GetDenyString() -> "@MAC-DENY:" + // Example html: + // <!-- + // @MAC-ALLOW-EMPTY:description* + // @MAC-ALLOW:roleDescription* + // @MAC-DENY:subrole* + // --> + // <p>Text</p> + static const std::string GetAllowEmptyString(); + static const std::string GetAllowString(); + static const std::string GetDenyString(); + + protected: + void RecursiveFormatAccessibilityTree(const BrowserAccessibility& node, + string16* contents, + int indent); + void RecursiveBuildAccessibilityTree(const BrowserAccessibility& node, + base::DictionaryValue* tree_node); + void RecursiveFormatAccessibilityTree(const base::DictionaryValue& tree_node, + string16* contents, + int depth = 0); + + // Overridden by each platform to add the required attributes for each node + // into the given dict. + void AddProperties(const BrowserAccessibility& node, + base::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, + const base::DictionaryValue& value); + + // Returns a platform specific representation of a BrowserAccessibility. + // Should be zero or more complete lines, each with |prefix| prepended + // (to indent each line). + string16 ToString(const base::DictionaryValue& node, const string16& indent); + + void Initialize(); + + bool MatchesFilters(const string16& text, bool default_result) const; + + // Writes the given attribute string out to |line| if it matches the filters. + void WriteAttribute(bool include_by_default, + const string16& attr, + string16* line); + void WriteAttribute(bool include_by_default, + const std::string& attr, + string16* line); + + BrowserAccessibility* root_; + + // Filters used when formatting the accessibility tree as text. + std::vector<Filter> filters_; + + DISALLOW_COPY_AND_ASSIGN(AccessibilityTreeFormatter); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_H_ diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_android.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_android.cc new file mode 100644 index 00000000000..11f4be64627 --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_android.cc @@ -0,0 +1,152 @@ +// 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/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/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); + + // Class name. + dict->SetString("class", android_node->GetClassName()); + + // Bool attributes. + dict->SetBoolean("focusable", android_node->IsFocusable()); + dict->SetBoolean("focused", android_node->IsFocused()); + dict->SetBoolean("clickable", android_node->IsClickable()); + dict->SetBoolean("editable_text", android_node->IsEditableText()); + dict->SetBoolean("checkable", android_node->IsCheckable()); + dict->SetBoolean("checked", android_node->IsChecked()); + dict->SetBoolean("disabled", !android_node->IsEnabled()); + dict->SetBoolean("scrollable", android_node->IsScrollable()); + dict->SetBoolean("password", android_node->IsPassword()); + dict->SetBoolean("selected", android_node->IsSelected()); + dict->SetBoolean("invisible", !android_node->IsVisibleToUser()); + + // String attributes. + dict->SetString("name", android_node->GetText()); + + // Int attributes. + dict->SetInteger("item_index", android_node->GetItemIndex()); + dict->SetInteger("item_count", android_node->GetItemCount()); +} + +bool AccessibilityTreeFormatter::IncludeChildren( + const BrowserAccessibility& node) { + const BrowserAccessibilityAndroid* android_node = + static_cast<const BrowserAccessibilityAndroid*>(&node); + return !android_node->IsLeaf(); +} + +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/chromium/content/browser/accessibility/accessibility_tree_formatter_gtk.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_gtk.cc new file mode 100644 index 00000000000..8dd6b9328a7 --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_gtk.cc @@ -0,0 +1,108 @@ +// 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 <atk/atk.h> + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_gtk.h" + +namespace content { + +void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node, + base::DictionaryValue* dict) { + BrowserAccessibilityGtk* node_gtk = + const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityGtk(); + AtkObject* atk_object = node_gtk->GetAtkObject(); + AtkRole role = atk_object_get_role(atk_object); + if (role != ATK_ROLE_UNKNOWN) + dict->SetString("role", atk_role_get_name(role)); + dict->SetString("name", atk_object_get_name(atk_object)); + dict->SetString("description", atk_object_get_description(atk_object)); + AtkStateSet* state_set = + atk_object_ref_state_set(atk_object); + ListValue* states = new base::ListValue; + for (int i = ATK_STATE_INVALID; i < ATK_STATE_LAST_DEFINED; i++) { + AtkStateType state_type = static_cast<AtkStateType>(i); + if (atk_state_set_contains_state(state_set, state_type)) + states->AppendString(atk_state_type_get_name(state_type)); + } + dict->Set("states", states); + dict->SetInteger("id", node.renderer_id()); +} + +string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& node, + const string16& indent) { + string16 line; + std::string role_value; + node.GetString("role", &role_value); + if (!role_value.empty()) + WriteAttribute(true, base::StringPrintf("[%s]", role_value.c_str()), &line); + + std::string name_value; + node.GetString("name", &name_value); + WriteAttribute(true, base::StringPrintf("name='%s'", name_value.c_str()), + &line); + + std::string description_value; + node.GetString("description", &description_value); + WriteAttribute(false, + base::StringPrintf("description='%s'", + description_value.c_str()), + &line); + + const base::ListValue* states_value; + node.GetList("states", &states_value); + for (base::ListValue::const_iterator it = states_value->begin(); + it != states_value->end(); + ++it) { + std::string state_value; + if ((*it)->GetAsString(&state_value)) + WriteAttribute(true, state_value, &line); + } + + int id_value; + node.GetInteger("id", &id_value); + WriteAttribute(false, + base::StringPrintf("id=%d", id_value), + &line); + + return indent + line + ASCIIToUTF16("\n"); +} + +void AccessibilityTreeFormatter::Initialize() {} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetActualFileSuffix() { + return FILE_PATH_LITERAL("-actual-gtk.txt"); +} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetExpectedFileSuffix() { + return FILE_PATH_LITERAL("-expected-gtk.txt"); +} + +// static +const std::string AccessibilityTreeFormatter::GetAllowEmptyString() { + return "@GTK-ALLOW-EMPTY:"; +} + +// static +const std::string AccessibilityTreeFormatter::GetAllowString() { + return "@GTK-ALLOW:"; +} + +// static +const std::string AccessibilityTreeFormatter::GetDenyString() { + return "@GTK-DENY:"; +} + +} diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm new file mode 100644 index 00000000000..58881b4af4b --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_mac.mm @@ -0,0 +1,250 @@ +// Copyright (c) 2012 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" + +#import <Cocoa/Cocoa.h> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/json/json_writer.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_cocoa.h" +#include "content/browser/accessibility/browser_accessibility_mac.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" + +using base::StringPrintf; +using base::SysNSStringToUTF8; +using base::SysNSStringToUTF16; +using std::string; + +namespace content { + +namespace { + +const char* kPositionDictAttr = "position"; +const char* kXCoordDictAttr = "x"; +const char* kYCoordDictAttr = "y"; +const char* kSizeDictAttr = "size"; +const char* kWidthDictAttr = "width"; +const char* kHeightDictAttr = "height"; +const char* kRangeLocDictAttr = "loc"; +const char* kRangeLenDictAttr = "len"; + +scoped_ptr<base::DictionaryValue> PopulatePosition( + const BrowserAccessibility& node) { + scoped_ptr<base::DictionaryValue> position(new base::DictionaryValue); + // The NSAccessibility position of an object is in global coordinates and + // based on the lower-left corner of the object. To make this easier and less + // confusing, convert it to local window coordinates using the top-left + // corner when dumping the position. + BrowserAccessibility* root = node.manager()->GetRoot(); + BrowserAccessibilityCocoa* cocoa_root = root->ToBrowserAccessibilityCocoa(); + NSPoint root_position = [[cocoa_root position] pointValue]; + NSSize root_size = [[cocoa_root size] sizeValue]; + int root_top = -static_cast<int>(root_position.y + root_size.height); + int root_left = static_cast<int>(root_position.x); + + BrowserAccessibilityCocoa* cocoa_node = + const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa(); + NSPoint node_position = [[cocoa_node position] pointValue]; + NSSize node_size = [[cocoa_node size] sizeValue]; + + position->SetInteger(kXCoordDictAttr, + static_cast<int>(node_position.x - root_left)); + position->SetInteger(kYCoordDictAttr, + static_cast<int>(-node_position.y - node_size.height - root_top)); + return position.Pass(); +} + +scoped_ptr<base::DictionaryValue> +PopulateSize(const BrowserAccessibilityCocoa* cocoa_node) { + scoped_ptr<base::DictionaryValue> size(new base::DictionaryValue); + NSSize node_size = [[cocoa_node size] sizeValue]; + size->SetInteger(kHeightDictAttr, static_cast<int>(node_size.height)); + size->SetInteger(kWidthDictAttr, static_cast<int>(node_size.width)); + return size.Pass(); +} + +scoped_ptr<base::DictionaryValue> PopulateRange(NSRange range) { + scoped_ptr<base::DictionaryValue> rangeDict(new base::DictionaryValue); + rangeDict->SetInteger(kRangeLocDictAttr, static_cast<int>(range.location)); + rangeDict->SetInteger(kRangeLenDictAttr, static_cast<int>(range.length)); + return rangeDict.Pass(); +} + +// Returns true if |value| is an NSValue containing a NSRange. +bool IsRangeValue(id value) { + if (![value isKindOfClass:[NSValue class]]) + return false; + return 0 == strcmp([value objCType], @encode(NSRange)); +} + +NSArray* BuildAllAttributesArray() { + NSArray* array = [NSArray arrayWithObjects: + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilityTitleAttribute, + NSAccessibilityValueAttribute, + NSAccessibilityMinValueAttribute, + NSAccessibilityMaxValueAttribute, + NSAccessibilityValueDescriptionAttribute, + NSAccessibilityDescriptionAttribute, + NSAccessibilityHelpAttribute, + @"AXInvalid", + NSAccessibilityDisclosingAttribute, + NSAccessibilityDisclosureLevelAttribute, + @"AXAccessKey", + @"AXARIAAtomic", + @"AXARIABusy", + @"AXARIALive", + @"AXARIARelevant", + NSAccessibilityColumnIndexRangeAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilityFocusedAttribute, + NSAccessibilityIndexAttribute, + @"AXLoaded", + @"AXLoadingProcess", + NSAccessibilityNumberOfCharactersAttribute, + NSAccessibilityOrientationAttribute, + @"AXRequired", + NSAccessibilityRowIndexRangeAttribute, + NSAccessibilityURLAttribute, + NSAccessibilityVisibleCharacterRangeAttribute, + @"AXVisited", + nil]; + return [array retain]; +} + +} // namespace + +void AccessibilityTreeFormatter::Initialize() { +} + + +void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node, + base::DictionaryValue* dict) { + BrowserAccessibilityCocoa* cocoa_node = + const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa(); + NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames]; + + string role = SysNSStringToUTF8( + [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]); + dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role); + + NSString* subrole = + [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute]; + if (subrole != nil) { + dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute), + SysNSStringToUTF8(subrole)); + } + + CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray())); + for (NSString* requestedAttribute in all_attributes) { + if (![supportedAttributes containsObject:requestedAttribute]) { + continue; + } + id value = [cocoa_node accessibilityAttributeValue:requestedAttribute]; + if (IsRangeValue(value)) { + dict->Set( + SysNSStringToUTF8(requestedAttribute), + PopulateRange([value rangeValue]).release()); + } else if (value != nil) { + dict->SetString( + SysNSStringToUTF8(requestedAttribute), + SysNSStringToUTF16([NSString stringWithFormat:@"%@", value])); + } + } + dict->Set(kPositionDictAttr, PopulatePosition(node).release()); + dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release()); +} + +string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& dict, + const string16& indent) { + string16 line; + NSArray* defaultAttributes = + [NSArray arrayWithObjects:NSAccessibilityTitleAttribute, + NSAccessibilityValueAttribute, + nil]; + string s_value; + dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value); + WriteAttribute(true, UTF8ToUTF16(s_value), &line); + + string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute); + if (dict.GetString(subroleAttribute, &s_value)) { + WriteAttribute(false, + StringPrintf("%s=%s", + subroleAttribute.c_str(), s_value.c_str()), + &line); + } + + CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray())); + for (NSString* requestedAttribute in all_attributes) { + string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute); + const base::DictionaryValue* d_value; + if (dict.GetDictionary(requestedAttributeUTF8, &d_value)) { + std::string json_value; + base::JSONWriter::Write(d_value, &json_value); + WriteAttribute( + [defaultAttributes containsObject:requestedAttribute], + StringPrintf("%s=%s", + requestedAttributeUTF8.c_str(), + json_value.c_str()), + &line); + } + if (!dict.GetString(requestedAttributeUTF8, &s_value)) + continue; + WriteAttribute([defaultAttributes containsObject:requestedAttribute], + StringPrintf("%s='%s'", + requestedAttributeUTF8.c_str(), + s_value.c_str()), + &line); + } + const base::DictionaryValue* d_value = NULL; + if (dict.GetDictionary(kPositionDictAttr, &d_value)) { + WriteAttribute(false, + FormatCoordinates(kPositionDictAttr, + kXCoordDictAttr, kYCoordDictAttr, + *d_value), + &line); + } + if (dict.GetDictionary(kSizeDictAttr, &d_value)) { + WriteAttribute(false, + FormatCoordinates(kSizeDictAttr, + kWidthDictAttr, kHeightDictAttr, *d_value), + &line); + } + + return indent + line + ASCIIToUTF16("\n"); +} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetActualFileSuffix() { + return FILE_PATH_LITERAL("-actual-mac.txt"); +} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetExpectedFileSuffix() { + return FILE_PATH_LITERAL("-expected-mac.txt"); +} + +// static +const string AccessibilityTreeFormatter::GetAllowEmptyString() { + return "@MAC-ALLOW-EMPTY:"; +} + +// static +const string AccessibilityTreeFormatter::GetAllowString() { + return "@MAC-ALLOW:"; +} + +// static +const string AccessibilityTreeFormatter::GetDenyString() { + return "@MAC-DENY:"; +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.cc new file mode 100644 index 00000000000..053e39b9b09 --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2012 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_utils_win.h" + +#include <oleacc.h> + +#include <map> +#include <string> + +#include "base/memory/singleton.h" +#include "base/strings/string_util.h" +#include "third_party/iaccessible2/ia2_api_all.h" + +namespace content { +namespace { + +class AccessibilityRoleStateMap { + public: + static AccessibilityRoleStateMap* GetInstance(); + + std::map<int32, string16> ia_role_string_map; + std::map<int32, string16> ia2_role_string_map; + std::map<int32, string16> ia_state_string_map; + std::map<int32, string16> ia2_state_string_map; + + private: + AccessibilityRoleStateMap(); + virtual ~AccessibilityRoleStateMap() {} + + friend struct DefaultSingletonTraits<AccessibilityRoleStateMap>; + + DISALLOW_COPY_AND_ASSIGN(AccessibilityRoleStateMap); +}; + +// static +AccessibilityRoleStateMap* AccessibilityRoleStateMap::GetInstance() { + return Singleton<AccessibilityRoleStateMap, + LeakySingletonTraits<AccessibilityRoleStateMap> >::get(); +} + +AccessibilityRoleStateMap::AccessibilityRoleStateMap() { +// Convenience macros for generating readable strings. +#define IA_ROLE_MAP(x) ia_role_string_map[x] = L#x; \ + ia2_role_string_map[x] = L#x; +#define IA2_ROLE_MAP(x) ia2_role_string_map[x] = L#x; +#define IA_STATE_MAP(x) ia_state_string_map[STATE_SYSTEM_##x] = L#x; +#define IA2_STATE_MAP(x) ia2_state_string_map[x] = L#x; + + // MSAA / IAccessible roles. Each one of these is also a valid + // IAccessible2 role, the IA_ROLE_MAP macro adds it to both. + IA_ROLE_MAP(ROLE_SYSTEM_ALERT) + IA_ROLE_MAP(ROLE_SYSTEM_ANIMATION) + IA_ROLE_MAP(ROLE_SYSTEM_APPLICATION) + IA_ROLE_MAP(ROLE_SYSTEM_BORDER) + IA_ROLE_MAP(ROLE_SYSTEM_BUTTONDROPDOWN) + IA_ROLE_MAP(ROLE_SYSTEM_BUTTONDROPDOWNGRID) + IA_ROLE_MAP(ROLE_SYSTEM_BUTTONMENU) + IA_ROLE_MAP(ROLE_SYSTEM_CARET) + IA_ROLE_MAP(ROLE_SYSTEM_CELL) + IA_ROLE_MAP(ROLE_SYSTEM_CHARACTER) + IA_ROLE_MAP(ROLE_SYSTEM_CHART) + IA_ROLE_MAP(ROLE_SYSTEM_CHECKBUTTON) + IA_ROLE_MAP(ROLE_SYSTEM_CLIENT) + IA_ROLE_MAP(ROLE_SYSTEM_CLOCK) + IA_ROLE_MAP(ROLE_SYSTEM_COLUMN) + IA_ROLE_MAP(ROLE_SYSTEM_COLUMNHEADER) + IA_ROLE_MAP(ROLE_SYSTEM_COMBOBOX) + IA_ROLE_MAP(ROLE_SYSTEM_CURSOR) + IA_ROLE_MAP(ROLE_SYSTEM_DIAGRAM) + IA_ROLE_MAP(ROLE_SYSTEM_DIAL) + IA_ROLE_MAP(ROLE_SYSTEM_DIALOG) + IA_ROLE_MAP(ROLE_SYSTEM_DOCUMENT) + IA_ROLE_MAP(ROLE_SYSTEM_DROPLIST) + IA_ROLE_MAP(ROLE_SYSTEM_EQUATION) + IA_ROLE_MAP(ROLE_SYSTEM_GRAPHIC) + IA_ROLE_MAP(ROLE_SYSTEM_GRIP) + IA_ROLE_MAP(ROLE_SYSTEM_GROUPING) + IA_ROLE_MAP(ROLE_SYSTEM_HELPBALLOON) + IA_ROLE_MAP(ROLE_SYSTEM_HOTKEYFIELD) + IA_ROLE_MAP(ROLE_SYSTEM_INDICATOR) + IA_ROLE_MAP(ROLE_SYSTEM_IPADDRESS) + IA_ROLE_MAP(ROLE_SYSTEM_LINK) + IA_ROLE_MAP(ROLE_SYSTEM_LIST) + IA_ROLE_MAP(ROLE_SYSTEM_LISTITEM) + IA_ROLE_MAP(ROLE_SYSTEM_MENUBAR) + IA_ROLE_MAP(ROLE_SYSTEM_MENUITEM) + IA_ROLE_MAP(ROLE_SYSTEM_MENUPOPUP) + IA_ROLE_MAP(ROLE_SYSTEM_OUTLINE) + IA_ROLE_MAP(ROLE_SYSTEM_OUTLINEBUTTON) + IA_ROLE_MAP(ROLE_SYSTEM_OUTLINEITEM) + IA_ROLE_MAP(ROLE_SYSTEM_PAGETAB) + IA_ROLE_MAP(ROLE_SYSTEM_PAGETABLIST) + IA_ROLE_MAP(ROLE_SYSTEM_PANE) + IA_ROLE_MAP(ROLE_SYSTEM_PROGRESSBAR) + IA_ROLE_MAP(ROLE_SYSTEM_PROPERTYPAGE) + IA_ROLE_MAP(ROLE_SYSTEM_PUSHBUTTON) + IA_ROLE_MAP(ROLE_SYSTEM_RADIOBUTTON) + IA_ROLE_MAP(ROLE_SYSTEM_ROW) + IA_ROLE_MAP(ROLE_SYSTEM_ROWHEADER) + IA_ROLE_MAP(ROLE_SYSTEM_SCROLLBAR) + IA_ROLE_MAP(ROLE_SYSTEM_SEPARATOR) + IA_ROLE_MAP(ROLE_SYSTEM_SLIDER) + IA_ROLE_MAP(ROLE_SYSTEM_SOUND) + IA_ROLE_MAP(ROLE_SYSTEM_SPINBUTTON) + IA_ROLE_MAP(ROLE_SYSTEM_SPLITBUTTON) + IA_ROLE_MAP(ROLE_SYSTEM_STATICTEXT) + IA_ROLE_MAP(ROLE_SYSTEM_STATUSBAR) + IA_ROLE_MAP(ROLE_SYSTEM_TABLE) + IA_ROLE_MAP(ROLE_SYSTEM_TEXT) + IA_ROLE_MAP(ROLE_SYSTEM_TITLEBAR) + IA_ROLE_MAP(ROLE_SYSTEM_TOOLBAR) + IA_ROLE_MAP(ROLE_SYSTEM_TOOLTIP) + IA_ROLE_MAP(ROLE_SYSTEM_WHITESPACE) + IA_ROLE_MAP(ROLE_SYSTEM_WINDOW) + + // IAccessible2 roles. + IA2_ROLE_MAP(IA2_ROLE_CANVAS) + IA2_ROLE_MAP(IA2_ROLE_CAPTION) + IA2_ROLE_MAP(IA2_ROLE_CHECK_MENU_ITEM) + IA2_ROLE_MAP(IA2_ROLE_COLOR_CHOOSER) + IA2_ROLE_MAP(IA2_ROLE_DATE_EDITOR) + IA2_ROLE_MAP(IA2_ROLE_DESKTOP_ICON) + IA2_ROLE_MAP(IA2_ROLE_DESKTOP_PANE) + IA2_ROLE_MAP(IA2_ROLE_DIRECTORY_PANE) + IA2_ROLE_MAP(IA2_ROLE_EDITBAR) + IA2_ROLE_MAP(IA2_ROLE_EMBEDDED_OBJECT) + IA2_ROLE_MAP(IA2_ROLE_ENDNOTE) + IA2_ROLE_MAP(IA2_ROLE_FILE_CHOOSER) + IA2_ROLE_MAP(IA2_ROLE_FONT_CHOOSER) + IA2_ROLE_MAP(IA2_ROLE_FOOTER) + IA2_ROLE_MAP(IA2_ROLE_FOOTNOTE) + IA2_ROLE_MAP(IA2_ROLE_FORM) + IA2_ROLE_MAP(IA2_ROLE_FRAME) + IA2_ROLE_MAP(IA2_ROLE_GLASS_PANE) + IA2_ROLE_MAP(IA2_ROLE_HEADER) + IA2_ROLE_MAP(IA2_ROLE_HEADING) + IA2_ROLE_MAP(IA2_ROLE_ICON) + IA2_ROLE_MAP(IA2_ROLE_IMAGE_MAP) + IA2_ROLE_MAP(IA2_ROLE_INPUT_METHOD_WINDOW) + IA2_ROLE_MAP(IA2_ROLE_INTERNAL_FRAME) + IA2_ROLE_MAP(IA2_ROLE_LABEL) + IA2_ROLE_MAP(IA2_ROLE_LAYERED_PANE) + IA2_ROLE_MAP(IA2_ROLE_NOTE) + IA2_ROLE_MAP(IA2_ROLE_OPTION_PANE) + IA2_ROLE_MAP(IA2_ROLE_PAGE) + IA2_ROLE_MAP(IA2_ROLE_PARAGRAPH) + IA2_ROLE_MAP(IA2_ROLE_RADIO_MENU_ITEM) + IA2_ROLE_MAP(IA2_ROLE_REDUNDANT_OBJECT) + IA2_ROLE_MAP(IA2_ROLE_ROOT_PANE) + IA2_ROLE_MAP(IA2_ROLE_RULER) + IA2_ROLE_MAP(IA2_ROLE_SCROLL_PANE) + IA2_ROLE_MAP(IA2_ROLE_SECTION) + IA2_ROLE_MAP(IA2_ROLE_SHAPE) + IA2_ROLE_MAP(IA2_ROLE_SPLIT_PANE) + IA2_ROLE_MAP(IA2_ROLE_TEAR_OFF_MENU) + IA2_ROLE_MAP(IA2_ROLE_TERMINAL) + IA2_ROLE_MAP(IA2_ROLE_TEXT_FRAME) + IA2_ROLE_MAP(IA2_ROLE_TOGGLE_BUTTON) + IA2_ROLE_MAP(IA2_ROLE_UNKNOWN) + IA2_ROLE_MAP(IA2_ROLE_VIEW_PORT) + + // MSAA / IAccessible states. Unlike roles, these are not also IA2 states. + IA_STATE_MAP(ALERT_HIGH) + IA_STATE_MAP(ALERT_LOW) + IA_STATE_MAP(ALERT_MEDIUM) + IA_STATE_MAP(ANIMATED) + IA_STATE_MAP(BUSY) + IA_STATE_MAP(CHECKED) + IA_STATE_MAP(COLLAPSED) + IA_STATE_MAP(DEFAULT) + IA_STATE_MAP(EXPANDED) + IA_STATE_MAP(EXTSELECTABLE) + IA_STATE_MAP(FLOATING) + IA_STATE_MAP(FOCUSABLE) + IA_STATE_MAP(FOCUSED) + IA_STATE_MAP(HASPOPUP) + IA_STATE_MAP(HOTTRACKED) + IA_STATE_MAP(INVISIBLE) + IA_STATE_MAP(LINKED) + IA_STATE_MAP(MARQUEED) + IA_STATE_MAP(MIXED) + IA_STATE_MAP(MOVEABLE) + IA_STATE_MAP(MULTISELECTABLE) + IA_STATE_MAP(OFFSCREEN) + IA_STATE_MAP(PRESSED) + IA_STATE_MAP(PROTECTED) + IA_STATE_MAP(READONLY) + IA_STATE_MAP(SELECTABLE) + IA_STATE_MAP(SELECTED) + IA_STATE_MAP(SELFVOICING) + IA_STATE_MAP(SIZEABLE) + IA_STATE_MAP(TRAVERSED) + IA_STATE_MAP(UNAVAILABLE) + + // IAccessible2 states. + IA2_STATE_MAP(IA2_STATE_ACTIVE) + IA2_STATE_MAP(IA2_STATE_ARMED) + IA2_STATE_MAP(IA2_STATE_DEFUNCT) + IA2_STATE_MAP(IA2_STATE_EDITABLE) + IA2_STATE_MAP(IA2_STATE_ICONIFIED) + IA2_STATE_MAP(IA2_STATE_INVALID_ENTRY) + IA2_STATE_MAP(IA2_STATE_MANAGES_DESCENDANTS) + IA2_STATE_MAP(IA2_STATE_MODAL) + IA2_STATE_MAP(IA2_STATE_MULTI_LINE) + IA2_STATE_MAP(IA2_STATE_REQUIRED) + IA2_STATE_MAP(IA2_STATE_SELECTABLE_TEXT) + IA2_STATE_MAP(IA2_STATE_SINGLE_LINE) + IA2_STATE_MAP(IA2_STATE_STALE) + IA2_STATE_MAP(IA2_STATE_SUPPORTS_AUTOCOMPLETION) + IA2_STATE_MAP(IA2_STATE_TRANSIENT) + + // Untested states include those that would be repeated on nearly every node, + // or would vary based on window size. + // IA2_STATE_MAP(IA2_STATE_HORIZONTAL) // Untested. + // IA2_STATE_MAP(IA2_STATE_OPAQUE) // Untested. + // IA2_STATE_MAP(IA2_STATE_VERTICAL) // Untested. +} + +} // namespace. + +string16 IAccessibleRoleToString(int32 ia_role) { + return AccessibilityRoleStateMap::GetInstance()->ia_role_string_map[ia_role]; +} + +string16 IAccessible2RoleToString(int32 ia_role) { + return AccessibilityRoleStateMap::GetInstance()->ia2_role_string_map[ia_role]; +} + +void IAccessibleStateToStringVector(int32 ia_state, + std::vector<string16>* result) { + const std::map<int32, string16>& state_string_map = + AccessibilityRoleStateMap::GetInstance()->ia_state_string_map; + std::map<int32, string16>::const_iterator it; + for (it = state_string_map.begin(); it != state_string_map.end(); ++it) { + if (it->first & ia_state) + result->push_back(it->second); + } +} + +string16 IAccessibleStateToString(int32 ia_state) { + std::vector<string16> strings; + IAccessibleStateToStringVector(ia_state, &strings); + return JoinString(strings, ','); +} + +void IAccessible2StateToStringVector(int32 ia2_state, + std::vector<string16>* result) { + const std::map<int32, string16>& state_string_map = + AccessibilityRoleStateMap::GetInstance()->ia2_state_string_map; + std::map<int32, string16>::const_iterator it; + for (it = state_string_map.begin(); it != state_string_map.end(); ++it) { + if (it->first & ia2_state) + result->push_back(it->second); + } +} + +string16 IAccessible2StateToString(int32 ia2_state) { + std::vector<string16> strings; + IAccessible2StateToStringVector(ia2_state, &strings); + return JoinString(strings, ','); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.h b/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.h new file mode 100644 index 00000000000..1baf20d54a8 --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_utils_win.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012 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_ACCESSIBILITY_TREE_FORMATTER_UTILS_WIN_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_UTILS_WIN_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "content/common/content_export.h" + +namespace content { + +CONTENT_EXPORT string16 IAccessibleRoleToString(int32 ia_role); +CONTENT_EXPORT string16 IAccessible2RoleToString(int32 ia_role); +CONTENT_EXPORT string16 IAccessibleStateToString(int32 ia_state); +CONTENT_EXPORT void IAccessibleStateToStringVector( + int32 ia_state, std::vector<string16>* result); +CONTENT_EXPORT string16 IAccessible2StateToString(int32 ia2_state); +CONTENT_EXPORT void IAccessible2StateToStringVector( + int32 ia_state, std::vector<string16>* result); + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_ACCESSIBILITY_TREE_FORMATTER_UTILS_WIN_H_ diff --git a/chromium/content/browser/accessibility/accessibility_tree_formatter_win.cc b/chromium/content/browser/accessibility/accessibility_tree_formatter_win.cc new file mode 100644 index 00000000000..679843dc42e --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_tree_formatter_win.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2012 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 <oleacc.h> + +#include <string> + +#include "base/files/file_path.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/accessibility_tree_formatter_utils_win.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/browser/accessibility/browser_accessibility_win.h" +#include "content/common/accessibility_node_data.h" +#include "third_party/iaccessible2/ia2_api_all.h" +#include "ui/base/win/atl_module.h" + +using base::StringPrintf; + +namespace content { + +const char* ALL_ATTRIBUTES[] = { + "name", + "value", + "states", + "attributes", + "role_name", + "currentValue", + "minimumValue", + "maximumValue", + "description", + "default_action", + "keyboard_shortcut", + "location", + "size", + "index_in_parent", + "n_relations", + "group_level", + "similar_items_in_group", + "position_in_group", + "table_rows", + "table_columns", + "row_index", + "column_index", + "n_characters", + "caret_offset", + "n_selections", + "selection_start", + "selection_end" +}; + +void AccessibilityTreeFormatter::Initialize() { + ui::win::CreateATLModuleIfNeeded(); +} + +void AccessibilityTreeFormatter::AddProperties( + const BrowserAccessibility& node, base::DictionaryValue* dict) { + BrowserAccessibilityWin* acc_obj = + const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityWin(); + + VARIANT variant_self; + variant_self.vt = VT_I4; + variant_self.lVal = CHILDID_SELF; + + dict->SetString("role", IAccessible2RoleToString(acc_obj->ia2_role())); + + CComBSTR msaa_variant; + HRESULT hresult = acc_obj->get_accName(variant_self, &msaa_variant); + if (hresult == S_OK) + dict->SetString("name", msaa_variant.m_str); + hresult = acc_obj->get_accValue(variant_self, &msaa_variant); + if (hresult == S_OK) + dict->SetString("value", msaa_variant.m_str); + + std::vector<string16> state_strings; + int32 ia_state = acc_obj->ia_state(); + + // Avoid flakiness: these states depend on whether the window is focused + // and the position of the mouse cursor. + ia_state &= ~STATE_SYSTEM_HOTTRACKED; + ia_state &= ~STATE_SYSTEM_OFFSCREEN; + + IAccessibleStateToStringVector(ia_state, &state_strings); + IAccessible2StateToStringVector(acc_obj->ia2_state(), &state_strings); + base::ListValue* states = new base::ListValue; + for (std::vector<string16>::const_iterator it = state_strings.begin(); + it != state_strings.end(); + ++it) { + states->AppendString(UTF16ToUTF8(*it)); + } + dict->Set("states", states); + + const std::vector<string16>& ia2_attributes = acc_obj->ia2_attributes(); + base::ListValue* attributes = new base::ListValue; + for (std::vector<string16>::const_iterator it = ia2_attributes.begin(); + it != ia2_attributes.end(); + ++it) { + attributes->AppendString(UTF16ToUTF8(*it)); + } + dict->Set("attributes", attributes); + + dict->SetString("role_name", acc_obj->role_name()); + + VARIANT currentValue; + if (acc_obj->get_currentValue(¤tValue) == S_OK) + dict->SetDouble("currentValue", V_R8(¤tValue)); + + VARIANT minimumValue; + if (acc_obj->get_minimumValue(&minimumValue) == S_OK) + dict->SetDouble("minimumValue", V_R8(&minimumValue)); + + VARIANT maximumValue; + if (acc_obj->get_maximumValue(&maximumValue) == S_OK) + dict->SetDouble("maximumValue", V_R8(&maximumValue)); + + hresult = acc_obj->get_accDescription(variant_self, &msaa_variant); + if (hresult == S_OK) + dict->SetString("description", msaa_variant.m_str); + + hresult = acc_obj->get_accDefaultAction(variant_self, &msaa_variant); + if (hresult == S_OK) + dict->SetString("default_action", msaa_variant.m_str); + + hresult = acc_obj->get_accKeyboardShortcut(variant_self, &msaa_variant); + if (hresult == S_OK) + dict->SetString("keyboard_shortcut", msaa_variant.m_str); + + hresult = acc_obj->get_accHelp(variant_self, &msaa_variant); + if (S_OK == hresult) + dict->SetString("help", msaa_variant.m_str); + + BrowserAccessibility* root = node.manager()->GetRoot(); + LONG left, top, width, height; + LONG root_left, root_top, root_width, root_height; + if (acc_obj->accLocation(&left, &top, &width, &height, variant_self) + != S_FALSE + && root->ToBrowserAccessibilityWin()->accLocation( + &root_left, &root_top, &root_width, &root_height, variant_self) + != S_FALSE) { + base::DictionaryValue* location = new base::DictionaryValue; + location->SetInteger("x", left - root_left); + location->SetInteger("y", top - root_top); + dict->Set("location", location); + + base::DictionaryValue* size = new base::DictionaryValue; + size->SetInteger("width", width); + size->SetInteger("height", height); + dict->Set("size", size); + } + + LONG index_in_parent; + if (acc_obj->get_indexInParent(&index_in_parent) == S_OK) + dict->SetInteger("index_in_parent", index_in_parent); + + LONG n_relations; + if (acc_obj->get_nRelations(&n_relations) == S_OK) + dict->SetInteger("n_relations", n_relations); + + LONG group_level, similar_items_in_group, position_in_group; + if (acc_obj->get_groupPosition(&group_level, + &similar_items_in_group, + &position_in_group) == S_OK) { + dict->SetInteger("group_level", group_level); + dict->SetInteger("similar_items_in_group", similar_items_in_group); + dict->SetInteger("position_in_group", position_in_group); + } + LONG table_rows; + if (acc_obj->get_nRows(&table_rows) == S_OK) + dict->SetInteger("table_rows", table_rows); + LONG table_columns; + if (acc_obj->get_nRows(&table_columns) == S_OK) + dict->SetInteger("table_columns", table_columns); + LONG row_index; + if (acc_obj->get_rowIndex(&row_index) == S_OK) + dict->SetInteger("row_index", row_index); + LONG column_index; + if (acc_obj->get_columnIndex(&column_index) == S_OK) + dict->SetInteger("column_index", column_index); + LONG n_characters; + if (acc_obj->get_nCharacters(&n_characters) == S_OK) + dict->SetInteger("n_characters", n_characters); + LONG caret_offset; + if (acc_obj->get_caretOffset(&caret_offset) == S_OK) + dict->SetInteger("caret_offset", caret_offset); + LONG n_selections; + if (acc_obj->get_nSelections(&n_selections) == S_OK) { + dict->SetInteger("n_selections", n_selections); + if (n_selections > 0) { + LONG start, end; + if (acc_obj->get_selection(0, &start, &end) == S_OK) { + dict->SetInteger("selection_start", start); + dict->SetInteger("selection_end", end); + } + } + } +} + +string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& dict, + const string16& indent) { + string16 line; + + string16 role_value; + dict.GetString("role", &role_value); + WriteAttribute(true, UTF16ToUTF8(role_value), &line); + + string16 name_value; + dict.GetString("name", &name_value); + WriteAttribute(true, base::StringPrintf(L"name='%ls'", name_value.c_str()), + &line); + + for (int i = 0; i < arraysize(ALL_ATTRIBUTES); i++) { + const char* attribute_name = ALL_ATTRIBUTES[i]; + const base::Value* value; + if (!dict.Get(attribute_name, &value)) + continue; + + switch (value->GetType()) { + case base::Value::TYPE_STRING: { + string16 string_value; + value->GetAsString(&string_value); + WriteAttribute(false, + StringPrintf(L"%ls='%ls'", + UTF8ToUTF16(attribute_name).c_str(), + string_value.c_str()), + &line); + break; + } + case base::Value::TYPE_INTEGER: { + int int_value; + value->GetAsInteger(&int_value); + WriteAttribute(false, + base::StringPrintf(L"%ls=%d", + UTF8ToUTF16(attribute_name).c_str(), + int_value), + &line); + break; + } + case base::Value::TYPE_DOUBLE: { + double double_value; + value->GetAsDouble(&double_value); + WriteAttribute(false, + base::StringPrintf(L"%ls=%.2f", + UTF8ToUTF16(attribute_name).c_str(), + double_value), + &line); + break; + } + case base::Value::TYPE_LIST: { + // Currently all list values are string and are written without + // attribute names. + const base::ListValue* list_value; + value->GetAsList(&list_value); + for (base::ListValue::const_iterator it = list_value->begin(); + it != list_value->end(); + ++it) { + string16 string_value; + if ((*it)->GetAsString(&string_value)) + WriteAttribute(false, string_value, &line); + } + break; + } + case base::Value::TYPE_DICTIONARY: { + // Currently all dictionary values are coordinates. + // Revisit this if that changes. + const base::DictionaryValue* dict_value; + value->GetAsDictionary(&dict_value); + if (strcmp(attribute_name, "size") == 0) { + WriteAttribute(false, + FormatCoordinates("size", "width", "height", + *dict_value), + &line); + } else if (strcmp(attribute_name, "location") == 0) { + WriteAttribute(false, + FormatCoordinates("location", "x", "y", *dict_value), + &line); + } + break; + } + default: + NOTREACHED(); + break; + } + } + + return indent + line + ASCIIToUTF16("\n"); +} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetActualFileSuffix() { + return FILE_PATH_LITERAL("-actual-win.txt"); +} + +// static +const base::FilePath::StringType +AccessibilityTreeFormatter::GetExpectedFileSuffix() { + return FILE_PATH_LITERAL("-expected-win.txt"); +} + +// static +const std::string AccessibilityTreeFormatter::GetAllowEmptyString() { + return "@WIN-ALLOW-EMPTY:"; +} + +// static +const std::string AccessibilityTreeFormatter::GetAllowString() { + return "@WIN-ALLOW:"; +} + +// static +const std::string AccessibilityTreeFormatter::GetDenyString() { + return "@WIN-DENY:"; +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/accessibility_ui.cc b/chromium/content/browser/accessibility/accessibility_ui.cc new file mode 100644 index 00000000000..585be6806a1 --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_ui.cc @@ -0,0 +1,253 @@ +// Copyright (c) 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_ui.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/json/json_writer.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "content/browser/accessibility/accessibility_tree_formatter.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/browser/accessibility/browser_accessibility_state_impl.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/common/view_message_enums.h" +#include "content/port/browser/render_widget_host_view_port.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/favicon_status.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui_data_source.h" +#include "content/public/common/url_constants.h" +#include "grit/content_resources.h" +#include "net/base/escape.h" + +static const char kDataFile[] = "targets-data.json"; + +static const char kProcessIdField[] = "processId"; +static const char kRouteIdField[] = "routeId"; +static const char kUrlField[] = "url"; +static const char kNameField[] = "name"; +static const char kFaviconUrlField[] = "favicon_url"; +static const char kPidField[] = "pid"; +static const char kAccessibilityModeField[] = "a11y_mode"; + +namespace content { + +namespace { + +base::DictionaryValue* BuildTargetDescriptor( + const GURL& url, + const std::string& name, + const GURL& favicon_url, + int process_id, + int route_id, + AccessibilityMode accessibility_mode, + base::ProcessHandle handle = base::kNullProcessHandle) { + base::DictionaryValue* target_data = new base::DictionaryValue(); + target_data->SetInteger(kProcessIdField, process_id); + target_data->SetInteger(kRouteIdField, route_id); + target_data->SetString(kUrlField, url.spec()); + target_data->SetString(kNameField, net::EscapeForHTML(name)); + target_data->SetInteger(kPidField, base::GetProcId(handle)); + target_data->SetString(kFaviconUrlField, favicon_url.spec()); + target_data->SetInteger(kAccessibilityModeField, + accessibility_mode); + return target_data; +} + +base::DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh) { + WebContents* web_contents = WebContents::FromRenderViewHost(rvh); + std::string title; + RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rvh); + AccessibilityMode accessibility_mode = rwhi->accessibility_mode(); + + GURL url; + GURL favicon_url; + if (web_contents) { + url = web_contents->GetURL(); + title = UTF16ToUTF8(web_contents->GetTitle()); + NavigationController& controller = web_contents->GetController(); + NavigationEntry* entry = controller.GetActiveEntry(); + if (entry != NULL && entry->GetURL().is_valid()) + favicon_url = entry->GetFavicon().url; + } + + return BuildTargetDescriptor(url, + title, + favicon_url, + rvh->GetProcess()->GetID(), + rvh->GetRoutingID(), + accessibility_mode); +} + +void SendTargetsData( + const WebUIDataSource::GotDataCallback& callback) { + scoped_ptr<base::ListValue> rvh_list(new base::ListValue()); + + RenderWidgetHost::List widgets = RenderWidgetHost::GetRenderWidgetHosts(); + for (size_t i = 0; i < widgets.size(); ++i) { + // Ignore processes that don't have a connection, such as crashed tabs. + if (!widgets[i]->GetProcess()->HasConnection()) + continue; + if (!widgets[i]->IsRenderView()) + continue; + + RenderViewHost* rvh = RenderViewHost::From(widgets[i]); + rvh_list->Append(BuildTargetDescriptor(rvh)); + } + + scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); + data->Set("list", rvh_list.release()); + scoped_ptr<base::FundamentalValue> a11y_mode(new base::FundamentalValue( + BrowserAccessibilityStateImpl::GetInstance()->accessibility_mode())); + data->Set("global_a11y_mode", a11y_mode.release()); + + std::string json_string; + base::JSONWriter::Write(data.get(), &json_string); + + callback.Run(base::RefCountedString::TakeString(&json_string)); +} + +bool HandleRequestCallback( + const std::string& path, + const WebUIDataSource::GotDataCallback& callback) { + if (path != kDataFile) + return false; + + SendTargetsData(callback); + return true; +} + +} // namespace + +AccessibilityUI::AccessibilityUI(WebUI* web_ui) + : WebUIController(web_ui) { + // Set up the chrome://accessibility source. + WebUIDataSource* html_source = + WebUIDataSource::Create(kChromeUIAccessibilityHost); + html_source->SetUseJsonJSFormatV2(); + + web_ui->RegisterMessageCallback( + "toggleAccessibility", + base::Bind(&AccessibilityUI::ToggleAccessibility, + base::Unretained(this))); + web_ui->RegisterMessageCallback( + "toggleGlobalAccessibility", + base::Bind(&AccessibilityUI::ToggleGlobalAccessibility, + base::Unretained(this))); + web_ui->RegisterMessageCallback( + "requestAccessibilityTree", + base::Bind(&AccessibilityUI::RequestAccessibilityTree, + base::Unretained(this))); + + // Add required resources. + html_source->SetJsonPath("strings.js"); + html_source->AddResourcePath("accessibility.css", IDR_ACCESSIBILITY_CSS); + html_source->AddResourcePath("accessibility.js", IDR_ACCESSIBILITY_JS); + html_source->SetDefaultResource(IDR_ACCESSIBILITY_HTML); + html_source->SetRequestFilter(base::Bind(&HandleRequestCallback)); + + BrowserContext* browser_context = + web_ui->GetWebContents()->GetBrowserContext(); + WebUIDataSource::Add(browser_context, html_source); +} + +AccessibilityUI::~AccessibilityUI() { +} + +void AccessibilityUI::ToggleAccessibility(const base::ListValue* args) { + std::string process_id_str; + std::string route_id_str; + int process_id; + int route_id; + CHECK(args->GetSize() == 2); + CHECK(args->GetString(0, &process_id_str)); + CHECK(args->GetString(1, &route_id_str)); + CHECK(base::StringToInt(process_id_str, + &process_id)); + CHECK(base::StringToInt(route_id_str, &route_id)); + + RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id); + if (!rvh) + return; + RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rvh); + if (!rwhi) + return; + AccessibilityMode mode = rwhi->accessibility_mode(); + if (mode == AccessibilityModeOff) + rwhi->SetAccessibilityMode(AccessibilityModeComplete); + else + rwhi->SetAccessibilityMode(AccessibilityModeOff); +} + +void AccessibilityUI::ToggleGlobalAccessibility(const base::ListValue* args) { + BrowserAccessibilityStateImpl* state = + BrowserAccessibilityStateImpl::GetInstance(); + AccessibilityMode mode = state->accessibility_mode(); + AccessibilityMode new_mode = (mode == AccessibilityModeOff + ? AccessibilityModeComplete + : AccessibilityModeOff); + state->SetAccessibilityMode(new_mode); +} + +void AccessibilityUI::RequestAccessibilityTree(const base::ListValue* args) { + std::string process_id_str; + std::string route_id_str; + int process_id; + int route_id; + CHECK(args->GetSize() == 2); + CHECK(args->GetString(0, &process_id_str)); + CHECK(args->GetString(1, &route_id_str)); + CHECK(base::StringToInt(process_id_str, &process_id)); + CHECK(base::StringToInt(route_id_str, &route_id)); + + RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id); + if (!rvh) { + scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue()); + result->SetInteger(kProcessIdField, process_id); + result->SetInteger(kRouteIdField, route_id); + result->Set("error", new base::StringValue("Renderer no longer exists.")); + web_ui()->CallJavascriptFunction("accessibility.showTree", *(result.get())); + return; + } + + scoped_ptr<base::DictionaryValue> result(BuildTargetDescriptor(rvh)); + RenderWidgetHostViewPort* host_view = static_cast<RenderWidgetHostViewPort*>( + WebContents::FromRenderViewHost(rvh)->GetRenderWidgetHostView()); + if (!host_view) { + result->Set("error", + new base::StringValue("Could not get accessibility tree.")); + web_ui()->CallJavascriptFunction("accessibility.showTree", *(result.get())); + return; + } + scoped_ptr<AccessibilityTreeFormatter> formatter( + AccessibilityTreeFormatter::Create(rvh)); + string16 accessibility_contents_utf16; + BrowserAccessibilityManager* manager = + host_view->GetBrowserAccessibilityManager(); + if (!manager) { + result->Set("error", + new base::StringValue("Could not get accessibility tree.")); + web_ui()->CallJavascriptFunction("accessibility.showTree", *(result.get())); + return; + } + std::vector<AccessibilityTreeFormatter::Filter> filters; + filters.push_back(AccessibilityTreeFormatter::Filter( + ASCIIToUTF16("*"), + AccessibilityTreeFormatter::Filter::ALLOW)); + formatter->SetFilters(filters); + formatter->FormatAccessibilityTree(&accessibility_contents_utf16); + + result->Set("tree", + new base::StringValue(UTF16ToUTF8(accessibility_contents_utf16))); + web_ui()->CallJavascriptFunction("accessibility.showTree", *(result.get())); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/accessibility_ui.h b/chromium/content/browser/accessibility/accessibility_ui.h new file mode 100644 index 00000000000..1b239f8033b --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_ui.h @@ -0,0 +1,31 @@ +// Copyright (c) 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 CHROME_BROWSER_UI_WEBUI_ACCESSIBILITY_UI_H_ +#define CHROME_BROWSER_UI_WEBUI_ACCESSIBILITY_UI_H_ + +#include "content/public/browser/web_ui_controller.h" + +namespace base { + class ListValue; +} // namespace base + +namespace content { + +class AccessibilityUI : public WebUIController { + public: + explicit AccessibilityUI(WebUI* web_ui); + virtual ~AccessibilityUI(); + + private: + void ToggleAccessibility(const base::ListValue* args); + void ToggleGlobalAccessibility(const base::ListValue* args); + void RequestAccessibilityTree(const base::ListValue* args); + + DISALLOW_COPY_AND_ASSIGN(AccessibilityUI); +}; + +} // namespace content + +#endif // CHROME_BROWSER_UI_WEBUI_ACCESSIBILITY_UI_H_ diff --git a/chromium/content/browser/accessibility/accessibility_win_browsertest.cc b/chromium/content/browser/accessibility/accessibility_win_browsertest.cc new file mode 100644 index 00000000000..0be9ba5067d --- /dev/null +++ b/chromium/content/browser/accessibility/accessibility_win_browsertest.cc @@ -0,0 +1,883 @@ +// Copyright (c) 2012 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 <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/scoped_bstr.h" +#include "base/win/scoped_comptr.h" +#include "base/win/scoped_variant.h" +#include "content/browser/accessibility/accessibility_tree_formatter_utils_win.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/url_constants.h" +#include "content/shell/shell.h" +#include "content/test/accessibility_browser_test_utils.h" +#include "content/test/content_browser_test.h" +#include "content/test/content_browser_test_utils.h" +#include "third_party/iaccessible2/ia2_api_all.h" +#include "third_party/isimpledom/ISimpleDOMNode.h" + +// TODO(dmazzoni): Disabled accessibility tests on Win64. crbug.com/179717 +#if defined(ARCH_CPU_X86_64) +#define MAYBE(x) DISABLED_##x +#else +#define MAYBE(x) x +#endif + +namespace content { + +namespace { + + +// Helpers -------------------------------------------------------------------- + +base::win::ScopedComPtr<IAccessible> GetAccessibleFromResultVariant( + IAccessible* parent, + VARIANT* var) { + base::win::ScopedComPtr<IAccessible> ptr; + switch (V_VT(var)) { + case VT_DISPATCH: { + IDispatch* dispatch = V_DISPATCH(var); + if (dispatch) + ptr.QueryFrom(dispatch); + break; + } + + case VT_I4: { + base::win::ScopedComPtr<IDispatch> dispatch; + HRESULT hr = parent->get_accChild(*var, dispatch.Receive()); + EXPECT_TRUE(SUCCEEDED(hr)); + if (dispatch) + dispatch.QueryInterface(ptr.Receive()); + break; + } + } + return ptr; +} + +HRESULT QueryIAccessible2(IAccessible* accessible, IAccessible2** accessible2) { + // TODO(ctguil): For some reason querying the IAccessible2 interface from + // IAccessible fails. + base::win::ScopedComPtr<IServiceProvider> service_provider; + HRESULT hr = accessible->QueryInterface(service_provider.Receive()); + return SUCCEEDED(hr) ? + service_provider->QueryService(IID_IAccessible2, accessible2) : hr; +} + +// Recursively search through all of the descendants reachable from an +// IAccessible node and return true if we find one with the given role +// and name. +void RecursiveFindNodeInAccessibilityTree(IAccessible* node, + int32 expected_role, + const std::wstring& expected_name, + int32 depth, + bool* found) { + base::win::ScopedBstr name_bstr; + base::win::ScopedVariant childid_self(CHILDID_SELF); + node->get_accName(childid_self, name_bstr.Receive()); + std::wstring name(name_bstr, name_bstr.Length()); + base::win::ScopedVariant role; + node->get_accRole(childid_self, role.Receive()); + ASSERT_EQ(VT_I4, role.type()); + + // Print the accessibility tree as we go, because if this test fails + // on the bots, this is really helpful in figuring out why. + for (int i = 0; i < depth; i++) + printf(" "); + printf("role=%d name=%s\n", V_I4(&role), WideToUTF8(name).c_str()); + + if (expected_role == V_I4(&role) && expected_name == name) { + *found = true; + return; + } + + LONG child_count = 0; + HRESULT hr = node->get_accChildCount(&child_count); + ASSERT_EQ(S_OK, hr); + + scoped_ptr<VARIANT[]> child_array(new VARIANT[child_count]); + LONG obtained_count = 0; + hr = AccessibleChildren( + node, 0, child_count, child_array.get(), &obtained_count); + ASSERT_EQ(S_OK, hr); + ASSERT_EQ(child_count, obtained_count); + + for (int index = 0; index < obtained_count; index++) { + base::win::ScopedComPtr<IAccessible> child_accessible( + GetAccessibleFromResultVariant(node, &child_array.get()[index])); + if (child_accessible) { + RecursiveFindNodeInAccessibilityTree( + child_accessible.get(), expected_role, expected_name, depth + 1, + found); + if (*found) + return; + } + } +} + + +// AccessibilityWinBrowserTest ------------------------------------------------ + +class AccessibilityWinBrowserTest : public ContentBrowserTest { + public: + AccessibilityWinBrowserTest(); + virtual ~AccessibilityWinBrowserTest(); + + protected: + void LoadInitialAccessibilityTreeFromHtml(const std::string& html); + IAccessible* GetRendererAccessible(); + void ExecuteScript(const std::wstring& script); + + private: + DISALLOW_COPY_AND_ASSIGN(AccessibilityWinBrowserTest); +}; + +AccessibilityWinBrowserTest::AccessibilityWinBrowserTest() { +} + +AccessibilityWinBrowserTest::~AccessibilityWinBrowserTest() { +} + +void AccessibilityWinBrowserTest::LoadInitialAccessibilityTreeFromHtml( + const std::string& html) { + AccessibilityNotificationWaiter waiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationLoadComplete); + GURL html_data_url("data:text/html," + html); + NavigateToURL(shell(), html_data_url); + waiter.WaitForNotification(); +} + +// Retrieve the MSAA client accessibility object for the Render Widget Host View +// of the selected tab. +IAccessible* AccessibilityWinBrowserTest::GetRendererAccessible() { + HWND hwnd_render_widget_host_view = + shell()->web_contents()->GetRenderWidgetHostView()->GetNativeView(); + + // Invoke windows screen reader detection by sending the WM_GETOBJECT message + // with kIdCustom as the LPARAM. + const int32 kIdCustom = 1; + SendMessage( + hwnd_render_widget_host_view, WM_GETOBJECT, OBJID_CLIENT, kIdCustom); + + IAccessible* accessible; + HRESULT hr = AccessibleObjectFromWindow( + hwnd_render_widget_host_view, OBJID_CLIENT, + IID_IAccessible, reinterpret_cast<void**>(&accessible)); + + EXPECT_EQ(S_OK, hr); + EXPECT_NE(accessible, reinterpret_cast<IAccessible*>(NULL)); + + return accessible; +} + +void AccessibilityWinBrowserTest::ExecuteScript(const std::wstring& script) { + shell()->web_contents()->GetRenderViewHost()->ExecuteJavascriptInWebFrame( + std::wstring(), script); +} + + +// AccessibleChecker ---------------------------------------------------------- + +class AccessibleChecker { + public: + // This constructor can be used if the IA2 role will be the same as the MSAA + // role. + AccessibleChecker(const std::wstring& expected_name, + int32 expected_role, + const std::wstring& expected_value); + AccessibleChecker(const std::wstring& expected_name, + int32 expected_role, + int32 expected_ia2_role, + const std::wstring& expected_value); + AccessibleChecker(const std::wstring& expected_name, + const std::wstring& expected_role, + int32 expected_ia2_role, + const std::wstring& expected_value); + + // Append an AccessibleChecker that verifies accessibility information for + // a child IAccessible. Order is important. + void AppendExpectedChild(AccessibleChecker* expected_child); + + // Check that the name and role of the given IAccessible instance and its + // descendants match the expected names and roles that this object was + // initialized with. + void CheckAccessible(IAccessible* accessible); + + // Set the expected value for this AccessibleChecker. + void SetExpectedValue(const std::wstring& expected_value); + + // Set the expected state for this AccessibleChecker. + void SetExpectedState(LONG expected_state); + + private: + typedef std::vector<AccessibleChecker*> AccessibleCheckerVector; + + void CheckAccessibleName(IAccessible* accessible); + void CheckAccessibleRole(IAccessible* accessible); + void CheckIA2Role(IAccessible* accessible); + void CheckAccessibleValue(IAccessible* accessible); + void CheckAccessibleState(IAccessible* accessible); + void CheckAccessibleChildren(IAccessible* accessible); + string16 RoleVariantToString(const base::win::ScopedVariant& role); + + // Expected accessible name. Checked against IAccessible::get_accName. + std::wstring name_; + + // Expected accessible role. Checked against IAccessible::get_accRole. + base::win::ScopedVariant role_; + + // Expected IAccessible2 role. Checked against IAccessible2::role. + int32 ia2_role_; + + // Expected accessible value. Checked against IAccessible::get_accValue. + std::wstring value_; + + // Expected accessible state. Checked against IAccessible::get_accState. + LONG state_; + + // Expected accessible children. Checked using IAccessible::get_accChildCount + // and ::AccessibleChildren. + AccessibleCheckerVector children_; +}; + +AccessibleChecker::AccessibleChecker(const std::wstring& expected_name, + int32 expected_role, + const std::wstring& expected_value) + : name_(expected_name), + role_(expected_role), + ia2_role_(expected_role), + value_(expected_value), + state_(-1) { +} + +AccessibleChecker::AccessibleChecker(const std::wstring& expected_name, + int32 expected_role, + int32 expected_ia2_role, + const std::wstring& expected_value) + : name_(expected_name), + role_(expected_role), + ia2_role_(expected_ia2_role), + value_(expected_value), + state_(-1) { +} + +AccessibleChecker::AccessibleChecker(const std::wstring& expected_name, + const std::wstring& expected_role, + int32 expected_ia2_role, + const std::wstring& expected_value) + : name_(expected_name), + role_(expected_role.c_str()), + ia2_role_(expected_ia2_role), + value_(expected_value), + state_(-1) { +} + +void AccessibleChecker::AppendExpectedChild( + AccessibleChecker* expected_child) { + children_.push_back(expected_child); +} + +void AccessibleChecker::CheckAccessible(IAccessible* accessible) { + SCOPED_TRACE("while checking " + UTF16ToUTF8(RoleVariantToString(role_))); + CheckAccessibleName(accessible); + CheckAccessibleRole(accessible); + CheckIA2Role(accessible); + CheckAccessibleValue(accessible); + CheckAccessibleState(accessible); + CheckAccessibleChildren(accessible); +} + +void AccessibleChecker::SetExpectedValue(const std::wstring& expected_value) { + value_ = expected_value; +} + +void AccessibleChecker::SetExpectedState(LONG expected_state) { + state_ = expected_state; +} + +void AccessibleChecker::CheckAccessibleName(IAccessible* accessible) { + base::win::ScopedBstr name; + base::win::ScopedVariant childid_self(CHILDID_SELF); + HRESULT hr = accessible->get_accName(childid_self, name.Receive()); + + if (name_.empty()) { + // If the object doesn't have name S_FALSE should be returned. + EXPECT_EQ(S_FALSE, hr); + } else { + // Test that the correct string was returned. + EXPECT_EQ(S_OK, hr); + EXPECT_EQ(name_, std::wstring(name, name.Length())); + } +} + +void AccessibleChecker::CheckAccessibleRole(IAccessible* accessible) { + base::win::ScopedVariant role; + base::win::ScopedVariant childid_self(CHILDID_SELF); + HRESULT hr = accessible->get_accRole(childid_self, role.Receive()); + ASSERT_EQ(S_OK, hr); + EXPECT_EQ(0, role_.Compare(role)) + << "Expected role: " << RoleVariantToString(role_) + << "\nGot role: " << RoleVariantToString(role); +} + +void AccessibleChecker::CheckIA2Role(IAccessible* accessible) { + base::win::ScopedComPtr<IAccessible2> accessible2; + HRESULT hr = QueryIAccessible2(accessible, accessible2.Receive()); + ASSERT_EQ(S_OK, hr); + long ia2_role = 0; + hr = accessible2->role(&ia2_role); + ASSERT_EQ(S_OK, hr); + EXPECT_EQ(ia2_role_, ia2_role) + << "Expected ia2 role: " << IAccessible2RoleToString(ia2_role_) + << "\nGot ia2 role: " << IAccessible2RoleToString(ia2_role); +} + +void AccessibleChecker::CheckAccessibleValue(IAccessible* accessible) { + // Don't check the value if if's a DOCUMENT role, because the value + // is supposed to be the url (and we don't keep track of that in the + // test expectations). + base::win::ScopedVariant role; + base::win::ScopedVariant childid_self(CHILDID_SELF); + HRESULT hr = accessible->get_accRole(childid_self, role.Receive()); + ASSERT_EQ(S_OK, hr); + if (role.type() == VT_I4 && V_I4(&role) == ROLE_SYSTEM_DOCUMENT) + return; + + // Get the value. + base::win::ScopedBstr value; + hr = accessible->get_accValue(childid_self, value.Receive()); + EXPECT_EQ(S_OK, hr); + + // Test that the correct string was returned. + EXPECT_EQ(value_, std::wstring(value, value.Length())); +} + +void AccessibleChecker::CheckAccessibleState(IAccessible* accessible) { + if (state_ < 0) + return; + + base::win::ScopedVariant state; + base::win::ScopedVariant childid_self(CHILDID_SELF); + HRESULT hr = accessible->get_accState(childid_self, state.Receive()); + EXPECT_EQ(S_OK, hr); + ASSERT_EQ(VT_I4, state.type()); + LONG obj_state = V_I4(&state); + // Avoid flakiness. The "offscreen" state depends on whether the browser + // window is frontmost or not, and "hottracked" depends on whether the + // mouse cursor happens to be over the element. + obj_state &= ~(STATE_SYSTEM_OFFSCREEN | STATE_SYSTEM_HOTTRACKED); + EXPECT_EQ(state_, obj_state) + << "Expected state: " << IAccessibleStateToString(state_) + << "\nGot state: " << IAccessibleStateToString(obj_state); +} + +void AccessibleChecker::CheckAccessibleChildren(IAccessible* parent) { + LONG child_count = 0; + HRESULT hr = parent->get_accChildCount(&child_count); + EXPECT_EQ(S_OK, hr); + ASSERT_EQ(child_count, children_.size()); + + scoped_ptr<VARIANT[]> child_array(new VARIANT[child_count]); + LONG obtained_count = 0; + hr = AccessibleChildren(parent, 0, child_count, + child_array.get(), &obtained_count); + ASSERT_EQ(S_OK, hr); + ASSERT_EQ(child_count, obtained_count); + + VARIANT* child = child_array.get(); + for (AccessibleCheckerVector::iterator child_checker = children_.begin(); + child_checker != children_.end(); + ++child_checker, ++child) { + base::win::ScopedComPtr<IAccessible> child_accessible( + GetAccessibleFromResultVariant(parent, child)); + ASSERT_TRUE(child_accessible.get()); + (*child_checker)->CheckAccessible(child_accessible); + } +} + +string16 AccessibleChecker::RoleVariantToString( + const base::win::ScopedVariant& role) { + if (role.type() == VT_I4) + return IAccessibleRoleToString(V_I4(&role)); + if (role.type() == VT_BSTR) + return string16(V_BSTR(&role), SysStringLen(V_BSTR(&role))); + return string16(); +} + +} // namespace + + +// Tests ---------------------------------------------------------------------- + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + MAYBE(TestBusyAccessibilityTree)) { + NavigateToURL(shell(), GURL(kAboutBlankURL)); + + // The initial accessible returned should have state STATE_SYSTEM_BUSY while + // the accessibility tree is being requested from the renderer. + AccessibleChecker document1_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + document1_checker.SetExpectedState( + STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_FOCUSED | + STATE_SYSTEM_BUSY); + document1_checker.CheckAccessible(GetRendererAccessible()); +} + +// Flaky, http://crbug.com/167320 . +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + DISABLED_TestRendererAccessibilityTree) { + LoadInitialAccessibilityTreeFromHtml( + "<html><head><title>Accessibility Win Test</title></head>" + "<body><input type='button' value='push' /><input type='checkbox' />" + "</body></html>"); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker button_checker(L"push", ROLE_SYSTEM_PUSHBUTTON, + std::wstring()); + AccessibleChecker checkbox_checker(std::wstring(), ROLE_SYSTEM_CHECKBUTTON, + std::wstring()); + AccessibleChecker body_checker(std::wstring(), L"body", IA2_ROLE_SECTION, + std::wstring()); + AccessibleChecker document2_checker(L"Accessibility Win Test", + ROLE_SYSTEM_DOCUMENT, std::wstring()); + body_checker.AppendExpectedChild(&button_checker); + body_checker.AppendExpectedChild(&checkbox_checker); + document2_checker.AppendExpectedChild(&body_checker); + document2_checker.CheckAccessible(GetRendererAccessible()); + + // Check that document accessible has a parent accessible. + base::win::ScopedComPtr<IAccessible> document_accessible( + GetRendererAccessible()); + ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL)); + base::win::ScopedComPtr<IDispatch> parent_dispatch; + HRESULT hr = document_accessible->get_accParent(parent_dispatch.Receive()); + EXPECT_EQ(S_OK, hr); + EXPECT_NE(parent_dispatch, reinterpret_cast<IDispatch*>(NULL)); + + // Navigate to another page. + NavigateToURL(shell(), GURL(kAboutBlankURL)); + + // Verify that the IAccessible reference still points to a valid object and + // that calls to its methods fail since the tree is no longer valid after + // the page navagation. + base::win::ScopedBstr name; + base::win::ScopedVariant childid_self(CHILDID_SELF); + hr = document_accessible->get_accName(childid_self, name.Receive()); + ASSERT_EQ(E_FAIL, hr); +} + +// Periodically failing. See crbug.com/145537 +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + DISABLED_TestNotificationActiveDescendantChanged) { + LoadInitialAccessibilityTreeFromHtml( + "<ul tabindex='-1' role='radiogroup' aria-label='ul'>" + "<li id='li'>li</li></ul>"); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker list_marker_checker(L"\x2022", ROLE_SYSTEM_TEXT, + std::wstring()); + AccessibleChecker static_text_checker(L"li", ROLE_SYSTEM_TEXT, + std::wstring()); + AccessibleChecker list_item_checker(std::wstring(), ROLE_SYSTEM_LISTITEM, + std::wstring()); + list_item_checker.SetExpectedState(STATE_SYSTEM_READONLY); + AccessibleChecker radio_group_checker(L"ul", ROLE_SYSTEM_GROUPING, + IA2_ROLE_SECTION, std::wstring()); + radio_group_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE); + AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + list_item_checker.AppendExpectedChild(&list_marker_checker); + list_item_checker.AppendExpectedChild(&static_text_checker); + radio_group_checker.AppendExpectedChild(&list_item_checker); + document_checker.AppendExpectedChild(&radio_group_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Set focus to the radio group. + scoped_ptr<AccessibilityNotificationWaiter> waiter( + new AccessibilityNotificationWaiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationFocusChanged)); + ExecuteScript(L"document.body.children[0].focus()"); + waiter->WaitForNotification(); + + // Check that the accessibility tree of the browser has been updated. + radio_group_checker.SetExpectedState( + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_FOCUSED); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Set the active descendant of the radio group + waiter.reset(new AccessibilityNotificationWaiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationFocusChanged)); + ExecuteScript( + L"document.body.children[0].setAttribute('aria-activedescendant', 'li')"); + waiter->WaitForNotification(); + + // Check that the accessibility tree of the browser has been updated. + list_item_checker.SetExpectedState( + STATE_SYSTEM_READONLY | STATE_SYSTEM_FOCUSED); + radio_group_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + MAYBE(TestNotificationCheckedStateChanged)) { + LoadInitialAccessibilityTreeFromHtml( + "<body><input type='checkbox' /></body>"); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker checkbox_checker(std::wstring(), ROLE_SYSTEM_CHECKBUTTON, + std::wstring()); + checkbox_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE); + AccessibleChecker body_checker(std::wstring(), L"body", IA2_ROLE_SECTION, + std::wstring()); + AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + body_checker.AppendExpectedChild(&checkbox_checker); + document_checker.AppendExpectedChild(&body_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Check the checkbox. + scoped_ptr<AccessibilityNotificationWaiter> waiter( + new AccessibilityNotificationWaiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationCheckStateChanged)); + ExecuteScript(L"document.body.children[0].checked=true"); + waiter->WaitForNotification(); + + // Check that the accessibility tree of the browser has been updated. + checkbox_checker.SetExpectedState( + STATE_SYSTEM_CHECKED | STATE_SYSTEM_FOCUSABLE); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + MAYBE(TestNotificationChildrenChanged)) { + // The role attribute causes the node to be in the accessibility tree. + LoadInitialAccessibilityTreeFromHtml("<body role=group></body>"); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker group_checker(std::wstring(), ROLE_SYSTEM_GROUPING, + std::wstring()); + AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + document_checker.AppendExpectedChild(&group_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Change the children of the document body. + scoped_ptr<AccessibilityNotificationWaiter> waiter( + new AccessibilityNotificationWaiter( + shell(), + AccessibilityModeComplete, + AccessibilityNotificationChildrenChanged)); + ExecuteScript(L"document.body.innerHTML='<b>new text</b>'"); + waiter->WaitForNotification(); + + // Check that the accessibility tree of the browser has been updated. + AccessibleChecker text_checker(L"new text", ROLE_SYSTEM_TEXT, std::wstring()); + group_checker.AppendExpectedChild(&text_checker); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + MAYBE(TestNotificationChildrenChanged2)) { + // The role attribute causes the node to be in the accessibility tree. + LoadInitialAccessibilityTreeFromHtml( + "<div role=group style='visibility: hidden'>text</div>"); + + // Check the accessible tree of the browser. + AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Change the children of the document body. + scoped_ptr<AccessibilityNotificationWaiter> waiter( + new AccessibilityNotificationWaiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationChildrenChanged)); + ExecuteScript(L"document.body.children[0].style.visibility='visible'"); + waiter->WaitForNotification(); + + // Check that the accessibility tree of the browser has been updated. + AccessibleChecker static_text_checker(L"text", ROLE_SYSTEM_TEXT, + std::wstring()); + AccessibleChecker group_checker(std::wstring(), ROLE_SYSTEM_GROUPING, + std::wstring()); + document_checker.AppendExpectedChild(&group_checker); + group_checker.AppendExpectedChild(&static_text_checker); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + MAYBE(TestNotificationFocusChanged)) { + // The role attribute causes the node to be in the accessibility tree. + LoadInitialAccessibilityTreeFromHtml("<div role=group tabindex='-1'></div>"); + + // Check the browser's copy of the renderer accessibility tree. + SCOPED_TRACE("Check initial tree"); + AccessibleChecker group_checker(std::wstring(), ROLE_SYSTEM_GROUPING, + std::wstring()); + group_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE); + AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + document_checker.AppendExpectedChild(&group_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Focus the div in the document + scoped_ptr<AccessibilityNotificationWaiter> waiter( + new AccessibilityNotificationWaiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationFocusChanged)); + ExecuteScript(L"document.body.children[0].focus()"); + waiter->WaitForNotification(); + + // Check that the accessibility tree of the browser has been updated. + SCOPED_TRACE("Check updated tree after focusing div"); + group_checker.SetExpectedState( + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_FOCUSED); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Focus the document accessible. This will un-focus the current node. + waiter.reset( + new AccessibilityNotificationWaiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationBlur)); + base::win::ScopedComPtr<IAccessible> document_accessible( + GetRendererAccessible()); + ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL)); + base::win::ScopedVariant childid_self(CHILDID_SELF); + HRESULT hr = document_accessible->accSelect(SELFLAG_TAKEFOCUS, childid_self); + ASSERT_EQ(S_OK, hr); + waiter->WaitForNotification(); + + // Check that the accessibility tree of the browser has been updated. + SCOPED_TRACE("Check updated tree after focusing document again"); + group_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + MAYBE(TestNotificationValueChanged)) { + LoadInitialAccessibilityTreeFromHtml( + "<body><input type='text' value='old value'/></body>"); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker text_field_checker(std::wstring(), ROLE_SYSTEM_TEXT, + L"old value"); + text_field_checker.SetExpectedState(STATE_SYSTEM_FOCUSABLE); + AccessibleChecker body_checker(std::wstring(), L"body", IA2_ROLE_SECTION, + std::wstring()); + AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + body_checker.AppendExpectedChild(&text_field_checker); + document_checker.AppendExpectedChild(&body_checker); + document_checker.CheckAccessible(GetRendererAccessible()); + + // Set the value of the text control + scoped_ptr<AccessibilityNotificationWaiter> waiter( + new AccessibilityNotificationWaiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationValueChanged)); + ExecuteScript(L"document.body.children[0].value='new value'"); + waiter->WaitForNotification(); + + // Check that the accessibility tree of the browser has been updated. + text_field_checker.SetExpectedValue(L"new value"); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +// This test verifies that the web content's accessibility tree is a +// descendant of the main browser window's accessibility tree, so that +// tools like AccExplorer32 or AccProbe can be used to examine Chrome's +// accessibility support. +// +// If you made a change and this test now fails, check that the NativeViewHost +// that wraps the tab contents returns the IAccessible implementation +// provided by RenderWidgetHostViewWin in GetNativeViewAccessible(). +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + MAYBE(ContainsRendererAccessibilityTree)) { + LoadInitialAccessibilityTreeFromHtml( + "<html><head><title>MyDocument</title></head>" + "<body>Content</body></html>"); + + // Get the accessibility object for the browser window. + HWND browser_hwnd = shell()->window(); + base::win::ScopedComPtr<IAccessible> browser_accessible; + HRESULT hr = AccessibleObjectFromWindow( + browser_hwnd, + OBJID_WINDOW, + IID_IAccessible, + reinterpret_cast<void**>(browser_accessible.Receive())); + ASSERT_EQ(S_OK, hr); + + bool found = false; + RecursiveFindNodeInAccessibilityTree( + browser_accessible.get(), ROLE_SYSTEM_DOCUMENT, L"MyDocument", 0, &found); + ASSERT_EQ(found, true); +} + +// Disabled because of http://crbug.com/144390. +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + DISABLED_TestToggleButtonRoleAndStates) { + AccessibleChecker* button_checker; + std::string button_html("data:text/html,"); + AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + AccessibleChecker body_checker(std::wstring(), L"body", IA2_ROLE_SECTION, + std::wstring()); + document_checker.AppendExpectedChild(&body_checker); + +// Temporary macro +#define ADD_BUTTON(html, ia2_role, state) \ + button_html += html; \ + button_checker = new AccessibleChecker(L"x", ROLE_SYSTEM_PUSHBUTTON, \ + ia2_role, std::wstring()); \ + button_checker->SetExpectedState(state); \ + body_checker.AppendExpectedChild(button_checker) + + // If aria-pressed is 'undefined', empty or not present, use PUSHBUTTON + // Otherwise use TOGGLE_BUTTON, even if the value is invalid. + // The spec does this in an attempt future-proof in case new values are added. + ADD_BUTTON("<span role='button' aria-pressed='false'>x</span>", + IA2_ROLE_TOGGLE_BUTTON, 0); + ADD_BUTTON("<span role='button' aria-pressed='true'>x</span>", + IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_PRESSED); + ADD_BUTTON("<span role='button' aria-pressed='mixed'>x</span>", + IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_MIXED); + ADD_BUTTON("<span role='button' aria-pressed='xyz'>x</span>", + IA2_ROLE_TOGGLE_BUTTON, 0); + ADD_BUTTON("<span role='button' aria-pressed=''>x</span>", + ROLE_SYSTEM_PUSHBUTTON, 0); + ADD_BUTTON("<span role='button' aria-pressed>x</span>", + ROLE_SYSTEM_PUSHBUTTON, 0); + ADD_BUTTON("<span role='button' aria-pressed='undefined'>x</span>", + ROLE_SYSTEM_PUSHBUTTON, 0); + ADD_BUTTON("<span role='button'>x</span>", ROLE_SYSTEM_PUSHBUTTON, 0); + ADD_BUTTON("<input type='button' aria-pressed='true' value='x'/>", + IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_PRESSED); + ADD_BUTTON("<input type='button' aria-pressed='false' value='x'/>", + IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<input type='button' aria-pressed='mixed' value='x'>", + IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_MIXED); + ADD_BUTTON("<input type='button' aria-pressed='xyz' value='x'/>", + IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<input type='button' aria-pressed='' value='x'/>", + ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<input type='button' aria-pressed value='x'>", + ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<input type='button' aria-pressed='undefined' value='x'>", + ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<input type='button' value='x'>", + ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<button aria-pressed='true'>x</button>", + IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_PRESSED); + ADD_BUTTON("<button aria-pressed='false'>x</button>", + IA2_ROLE_TOGGLE_BUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<button aria-pressed='mixed'>x</button>", IA2_ROLE_TOGGLE_BUTTON, + STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_MIXED); + ADD_BUTTON("<button aria-pressed='xyz'>x</button>", IA2_ROLE_TOGGLE_BUTTON, + STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<button aria-pressed=''>x</button>", + ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<button aria-pressed>x</button>", + ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<button aria-pressed='undefined'>x</button>", + ROLE_SYSTEM_PUSHBUTTON, STATE_SYSTEM_FOCUSABLE); + ADD_BUTTON("<button>x</button>", ROLE_SYSTEM_PUSHBUTTON, + STATE_SYSTEM_FOCUSABLE); +#undef ADD_BUTTON // Temporary macro + + LoadInitialAccessibilityTreeFromHtml(button_html); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, + MAYBE(SupportsISimpleDOM)) { + LoadInitialAccessibilityTreeFromHtml( + "<body><input type='checkbox' /></body>"); + + // Get the IAccessible object for the document. + base::win::ScopedComPtr<IAccessible> document_accessible( + GetRendererAccessible()); + ASSERT_NE(document_accessible.get(), reinterpret_cast<IAccessible*>(NULL)); + + // Get the ISimpleDOM object for the document. + base::win::ScopedComPtr<IServiceProvider> service_provider; + HRESULT hr = static_cast<IAccessible*>(document_accessible)->QueryInterface( + service_provider.Receive()); + ASSERT_EQ(S_OK, hr); + const GUID refguid = {0x0c539790, 0x12e4, 0x11cf, + 0xb6, 0x61, 0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8}; + base::win::ScopedComPtr<ISimpleDOMNode> document_isimpledomnode; + hr = static_cast<IServiceProvider *>(service_provider)->QueryService( + refguid, IID_ISimpleDOMNode, + reinterpret_cast<void**>(document_isimpledomnode.Receive())); + ASSERT_EQ(S_OK, hr); + + base::win::ScopedBstr node_name; + short name_space_id; // NOLINT + base::win::ScopedBstr node_value; + unsigned int num_children; + unsigned int unique_id; + unsigned short node_type; // NOLINT + hr = document_isimpledomnode->get_nodeInfo( + node_name.Receive(), &name_space_id, node_value.Receive(), &num_children, + &unique_id, &node_type); + ASSERT_EQ(S_OK, hr); + EXPECT_EQ(NODETYPE_DOCUMENT, node_type); + EXPECT_EQ(1, num_children); + node_name.Reset(); + node_value.Reset(); + + base::win::ScopedComPtr<ISimpleDOMNode> body_isimpledomnode; + hr = document_isimpledomnode->get_firstChild( + body_isimpledomnode.Receive()); + ASSERT_EQ(S_OK, hr); + hr = body_isimpledomnode->get_nodeInfo( + node_name.Receive(), &name_space_id, node_value.Receive(), &num_children, + &unique_id, &node_type); + ASSERT_EQ(S_OK, hr); + EXPECT_EQ(L"body", std::wstring(node_name, node_name.Length())); + EXPECT_EQ(NODETYPE_ELEMENT, node_type); + EXPECT_EQ(1, num_children); + node_name.Reset(); + node_value.Reset(); + + base::win::ScopedComPtr<ISimpleDOMNode> checkbox_isimpledomnode; + hr = body_isimpledomnode->get_firstChild( + checkbox_isimpledomnode.Receive()); + ASSERT_EQ(S_OK, hr); + hr = checkbox_isimpledomnode->get_nodeInfo( + node_name.Receive(), &name_space_id, node_value.Receive(), &num_children, + &unique_id, &node_type); + ASSERT_EQ(S_OK, hr); + EXPECT_EQ(L"input", std::wstring(node_name, node_name.Length())); + EXPECT_EQ(NODETYPE_ELEMENT, node_type); + EXPECT_EQ(0, num_children); +} + +IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, MAYBE(TestRoleGroup)) { + LoadInitialAccessibilityTreeFromHtml( + "<fieldset></fieldset><div role=group></div>"); + + // Check the browser's copy of the renderer accessibility tree. + AccessibleChecker grouping1_checker(std::wstring(), ROLE_SYSTEM_GROUPING, + std::wstring()); + AccessibleChecker grouping2_checker(std::wstring(), ROLE_SYSTEM_GROUPING, + std::wstring()); + AccessibleChecker document_checker(std::wstring(), ROLE_SYSTEM_DOCUMENT, + std::wstring()); + document_checker.AppendExpectedChild(&grouping1_checker); + document_checker.AppendExpectedChild(&grouping2_checker); + document_checker.CheckAccessible(GetRendererAccessible()); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility.cc b/chromium/content/browser/accessibility/browser_accessibility.cc new file mode 100644 index 00000000000..ae7efe2f660 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility.cc @@ -0,0 +1,341 @@ +// Copyright (c) 2012 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.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/common/accessibility_messages.h" + +namespace content { + +typedef AccessibilityNodeData::BoolAttribute BoolAttribute; +typedef AccessibilityNodeData::FloatAttribute FloatAttribute; +typedef AccessibilityNodeData::IntAttribute IntAttribute; +typedef AccessibilityNodeData::StringAttribute StringAttribute; + +#if !defined(OS_MACOSX) && \ + !defined(OS_WIN) && \ + !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 +BrowserAccessibility* BrowserAccessibility::Create() { + return new BrowserAccessibility(); +} +#endif + +BrowserAccessibility::BrowserAccessibility() + : manager_(NULL), + parent_(NULL), + index_in_parent_(0), + renderer_id_(0), + role_(0), + state_(0), + instance_active_(false) { +} + +BrowserAccessibility::~BrowserAccessibility() { +} + +void BrowserAccessibility::DetachTree( + std::vector<BrowserAccessibility*>* nodes) { + nodes->push_back(this); + for (size_t i = 0; i < children_.size(); i++) + children_[i]->DetachTree(nodes); + children_.clear(); + parent_ = NULL; +} + +void BrowserAccessibility::InitializeTreeStructure( + BrowserAccessibilityManager* manager, + BrowserAccessibility* parent, + int32 renderer_id, + int32 index_in_parent) { + manager_ = manager; + parent_ = parent; + renderer_id_ = renderer_id; + index_in_parent_ = index_in_parent; +} + +void BrowserAccessibility::InitializeData(const AccessibilityNodeData& src) { + DCHECK_EQ(renderer_id_, src.id); + name_ = src.name; + value_ = src.value; + role_ = src.role; + state_ = src.state; + string_attributes_ = src.string_attributes; + int_attributes_ = src.int_attributes; + float_attributes_ = src.float_attributes; + bool_attributes_ = src.bool_attributes; + html_attributes_ = src.html_attributes; + location_ = src.location; + indirect_child_ids_ = src.indirect_child_ids; + line_breaks_ = src.line_breaks; + cell_ids_ = src.cell_ids; + unique_cell_ids_ = src.unique_cell_ids; + instance_active_ = true; + + PreInitialize(); +} + +bool BrowserAccessibility::IsNative() const { + return false; +} + +void BrowserAccessibility::SwapChildren( + std::vector<BrowserAccessibility*>& children) { + children.swap(children_); +} + +void BrowserAccessibility::UpdateParent(BrowserAccessibility* parent, + int index_in_parent) { + parent_ = parent; + index_in_parent_ = index_in_parent; +} + +void BrowserAccessibility::SetLocation(const gfx::Rect& new_location) { + location_ = new_location; +} + +bool BrowserAccessibility::IsDescendantOf( + BrowserAccessibility* ancestor) { + if (this == ancestor) { + return true; + } else if (parent_) { + return parent_->IsDescendantOf(ancestor); + } + + return false; +} + +BrowserAccessibility* BrowserAccessibility::GetChild(uint32 child_index) const { + DCHECK(child_index < children_.size()); + return children_[child_index]; +} + +BrowserAccessibility* BrowserAccessibility::GetPreviousSibling() { + if (parent_ && index_in_parent_ > 0) + return parent_->children_[index_in_parent_ - 1]; + + return NULL; +} + +BrowserAccessibility* BrowserAccessibility::GetNextSibling() { + if (parent_ && + index_in_parent_ >= 0 && + index_in_parent_ < static_cast<int>(parent_->children_.size() - 1)) { + return parent_->children_[index_in_parent_ + 1]; + } + + return NULL; +} + +gfx::Rect BrowserAccessibility::GetLocalBoundsRect() const { + gfx::Rect bounds = location_; + + // Walk up the parent chain. Every time we encounter a Web Area, offset + // based on the scroll bars and then offset based on the origin of that + // nested web area. + BrowserAccessibility* parent = parent_; + bool need_to_offset_web_area = + (role_ == AccessibilityNodeData::ROLE_WEB_AREA || + role_ == AccessibilityNodeData::ROLE_ROOT_WEB_AREA); + while (parent) { + if (need_to_offset_web_area && + parent->location().width() > 0 && + parent->location().height() > 0) { + 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; + int sy = 0; + if (parent->GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &sx) && + parent->GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &sy)) { + bounds.Offset(-sx, -sy); + } + need_to_offset_web_area = true; + } + parent = parent->parent(); + } + + return bounds; +} + +gfx::Rect BrowserAccessibility::GetGlobalBoundsRect() const { + gfx::Rect bounds = GetLocalBoundsRect(); + + // Adjust the bounds by the top left corner of the containing view's bounds + // in screen coordinates. + bounds.Offset(manager_->GetViewBounds().OffsetFromOrigin()); + + return bounds; +} + +BrowserAccessibility* BrowserAccessibility::BrowserAccessibilityForPoint( + const gfx::Point& point) { + // Walk the children recursively looking for the BrowserAccessibility that + // most tightly encloses the specified point. + for (int i = children_.size() - 1; i >= 0; --i) { + BrowserAccessibility* child = children_[i]; + if (child->GetGlobalBoundsRect().Contains(point)) + return child->BrowserAccessibilityForPoint(point); + } + return this; +} + +void BrowserAccessibility::Destroy() { + for (std::vector<BrowserAccessibility*>::iterator iter = children_.begin(); + iter != children_.end(); + ++iter) { + (*iter)->Destroy(); + } + children_.clear(); + + // Allow the object to fire a TextRemoved notification. + name_.clear(); + value_.clear(); + PostInitialize(); + + manager_->NotifyAccessibilityEvent( + AccessibilityNotificationObjectHide, this); + + instance_active_ = false; + manager_->RemoveNode(this); + NativeReleaseReference(); +} + +void BrowserAccessibility::NativeReleaseReference() { + delete this; +} + +bool BrowserAccessibility::GetBoolAttribute( + BoolAttribute attribute, bool* value) const { + BoolAttrMap::const_iterator iter = bool_attributes_.find(attribute); + if (iter != bool_attributes_.end()) { + *value = iter->second; + return true; + } + + return false; +} + +bool BrowserAccessibility::GetFloatAttribute( + FloatAttribute attribute, float* value) const { + FloatAttrMap::const_iterator iter = float_attributes_.find(attribute); + if (iter != float_attributes_.end()) { + *value = iter->second; + return true; + } + + return false; +} + +bool BrowserAccessibility::GetIntAttribute( + IntAttribute attribute, int* value) const { + IntAttrMap::const_iterator iter = int_attributes_.find(attribute); + if (iter != int_attributes_.end()) { + *value = iter->second; + return true; + } + + return false; +} + +bool BrowserAccessibility::GetStringAttribute( + StringAttribute attribute, + string16* value) const { + StringAttrMap::const_iterator iter = string_attributes_.find(attribute); + if (iter != string_attributes_.end()) { + *value = iter->second; + return true; + } + + return false; +} + +bool BrowserAccessibility::GetHtmlAttribute( + const char* html_attr, string16* value) const { + for (size_t i = 0; i < html_attributes_.size(); i++) { + const string16& attr = html_attributes_[i].first; + if (LowerCaseEqualsASCII(attr, html_attr)) { + *value = html_attributes_[i].second; + return true; + } + } + + return false; +} + +bool BrowserAccessibility::GetAriaTristate( + const char* html_attr, + bool* is_defined, + bool* is_mixed) const { + *is_defined = false; + *is_mixed = false; + + string16 value; + if (!GetHtmlAttribute(html_attr, &value) || + value.empty() || + EqualsASCII(value, "undefined")) { + return false; // Not set (and *is_defined is also false) + } + + *is_defined = true; + + if (EqualsASCII(value, "true")) + return true; + + if (EqualsASCII(value, "mixed")) + *is_mixed = true; + + return false; // Not set +} + +bool BrowserAccessibility::HasState( + AccessibilityNodeData::State state_enum) const { + return (state_ >> state_enum) & 1; +} + +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. + return (!HasState(AccessibilityNodeData::STATE_READONLY) || + role_ == AccessibilityNodeData::ROLE_TEXT_FIELD || + role_ == AccessibilityNodeData::ROLE_TEXTAREA); +} + +string16 BrowserAccessibility::GetTextRecursive() const { + if (!name_.empty()) { + return name_; + } + + string16 result; + for (size_t i = 0; i < children_.size(); ++i) + result += children_[i]->GetTextRecursive(); + return result; +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility.h b/chromium/content/browser/accessibility/browser_accessibility.h new file mode 100644 index 00000000000..8fda68fc079 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility.h @@ -0,0 +1,296 @@ +// Copyright (c) 2012 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_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_ + +#include <map> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "content/common/accessibility_node_data.h" +#include "content/common/content_export.h" + +#if defined(OS_MACOSX) && __OBJC__ +@class BrowserAccessibilityCocoa; +#endif + +namespace content { +class BrowserAccessibilityManager; +#if defined(OS_WIN) +class BrowserAccessibilityWin; +#elif defined(TOOLKIT_GTK) +class BrowserAccessibilityGtk; +#endif + +typedef std::map<AccessibilityNodeData::BoolAttribute, bool> BoolAttrMap; +typedef std::map<AccessibilityNodeData::FloatAttribute, float> FloatAttrMap; +typedef std::map<AccessibilityNodeData::IntAttribute, int> IntAttrMap; +typedef std::map<AccessibilityNodeData::StringAttribute, string16> + StringAttrMap; + +//////////////////////////////////////////////////////////////////////////////// +// +// BrowserAccessibility +// +// Class implementing the cross platform interface for the Browser-Renderer +// communication of accessibility information, providing accessibility +// to be used by screen readers and other assistive technology (AT). +// +// An implementation for each platform handles platform specific accessibility +// APIs. +// +//////////////////////////////////////////////////////////////////////////////// +class CONTENT_EXPORT BrowserAccessibility { + public: + // Creates a platform specific BrowserAccessibility. Ownership passes to the + // caller. + static BrowserAccessibility* Create(); + + virtual ~BrowserAccessibility(); + + // Detach all descendants of this subtree and push all of the node pointers, + // including this node, onto the end of |nodes|. + virtual void DetachTree(std::vector<BrowserAccessibility*>* nodes); + + // Perform platform-specific initialization. This can be called multiple times + // during the lifetime of this instance after the members of this base object + // have been reset with new values from the renderer process. + // Child dependent initialization can be done here. + virtual void PostInitialize() {} + + // Returns true if this is a native platform-specific object, vs a + // cross-platform generic object. + virtual bool IsNative() const; + + // Initialize the tree structure of this object. + void InitializeTreeStructure( + BrowserAccessibilityManager* manager, + BrowserAccessibility* parent, + int32 renderer_id, + int32 index_in_parent); + + // Initialize this object's data. + void InitializeData(const AccessibilityNodeData& src); + + virtual void SwapChildren(std::vector<BrowserAccessibility*>& children); + + // Update the parent and index in parent if this node has been moved. + void UpdateParent(BrowserAccessibility* parent, int index_in_parent); + + // Update this node's location, leaving everything else the same. + virtual void SetLocation(const gfx::Rect& new_location); + + // Return true if this object is equal to or a descendant of |ancestor|. + bool IsDescendantOf(BrowserAccessibility* ancestor); + + // Returns the parent of this object, or NULL if it's the root. + 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) const; + + // Return the previous sibling of this object, or NULL if it's the first + // child of its parent. + BrowserAccessibility* GetPreviousSibling(); + + // Return the next sibling of this object, or NULL if it's the last child + // of its parent. + BrowserAccessibility* GetNextSibling(); + + // Returns the bounds of this object in coordinates relative to the + // top-left corner of the overall web area. + gfx::Rect GetLocalBoundsRect() const; + + // Returns the bounds of this object in screen coordinates. + gfx::Rect GetGlobalBoundsRect() const; + + // Returns the deepest descendant that contains the specified point + // (in global screen coordinates). + BrowserAccessibility* BrowserAccessibilityForPoint(const gfx::Point& point); + + // Marks this object for deletion, releases our reference to it, and + // recursively calls Destroy() on its children. May not delete + // immediately due to reference counting. + // + // Reference counting is used on some platforms because the + // operating system may hold onto a reference to a BrowserAccessibility + // object even after we're through with it. When a BrowserAccessibility + // has had Destroy() called but its reference count is not yet zero, + // queries on this object return failure + virtual void Destroy(); + + // Subclasses should override this to support platform reference counting. + virtual void NativeAddReference() { } + + // Subclasses should override this to support platform reference counting. + virtual void NativeReleaseReference(); + + // + // Accessors + // + + const BoolAttrMap& bool_attributes() const { + return bool_attributes_; + } + + const FloatAttrMap& float_attributes() const { + return float_attributes_; + } + + const IntAttrMap& int_attributes() const { + return int_attributes_; + } + + const StringAttrMap& string_attributes() const { + return string_attributes_; + } + + const std::vector<BrowserAccessibility*>& children() const { + return children_; + } + const std::vector<std::pair<string16, string16> >& html_attributes() const { + return html_attributes_; + } + int32 index_in_parent() const { return index_in_parent_; } + const std::vector<int32>& indirect_child_ids() const { + return indirect_child_ids_; + } + const std::vector<int32>& line_breaks() const { + return line_breaks_; + } + const std::vector<int32>& cell_ids() const { + return cell_ids_; + } + const std::vector<int32>& unique_cell_ids() const { + return unique_cell_ids_; + } + gfx::Rect location() const { return location_; } + BrowserAccessibilityManager* manager() const { return manager_; } + const string16& name() const { return name_; } + int32 renderer_id() const { return renderer_id_; } + int32 role() const { return role_; } + const string16& role_name() const { return role_name_; } + int32 state() const { return state_; } + const string16& value() const { return value_; } + bool instance_active() const { return instance_active_; } + +#if defined(OS_MACOSX) && __OBJC__ + BrowserAccessibilityCocoa* ToBrowserAccessibilityCocoa(); +#elif defined(OS_WIN) + BrowserAccessibilityWin* ToBrowserAccessibilityWin(); +#elif defined(TOOLKIT_GTK) + BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(); +#endif + + // Retrieve the value of a bool attribute from the bool attribute + // map and returns true if found. + bool GetBoolAttribute( + AccessibilityNodeData::BoolAttribute attr, bool* value) const; + + // Retrieve the value of a float attribute from the float attribute + // map and returns true if found. + bool GetFloatAttribute(AccessibilityNodeData::FloatAttribute attr, + float* value) const; + + // Retrieve the value of an integer attribute from the integer attribute + // map and returns true if found. + bool GetIntAttribute(AccessibilityNodeData::IntAttribute attribute, + int* value) const; + + // Retrieve the value of a string attribute from the attribute map and + // returns true if found. + bool GetStringAttribute( + AccessibilityNodeData::StringAttribute attribute, string16* value) const; + + // Retrieve the value of a html attribute from the attribute map and + // returns true if found. + bool GetHtmlAttribute(const char* attr, string16* value) const; + + // Utility method to handle special cases for ARIA booleans, tristates and + // booleans which have a "mixed" state. + // + // Warning: the term "Tristate" is used loosely by the spec and here, + // as some attributes support a 4th state. + // + // The following attributes are appropriate to use with this method: + // aria-selected (selectable) + // aria-grabbed (grabbable) + // aria-expanded (expandable) + // aria-pressed (toggleable/pressable) -- supports 4th "mixed" state + // aria-checked (checkable) -- supports 4th "mixed state" + bool GetAriaTristate(const char* attr_name, + bool* is_defined, + bool* is_mixed) const; + + // Returns true if the bit corresponding to the given state enum is 1. + bool HasState(AccessibilityNodeData::State state_enum) const; + + // Returns true if this node is an editable text field of any kind. + bool IsEditableText() const; + + // Append the text from this node and its children. + string16 GetTextRecursive() const; + + protected: + // Perform platform specific initialization. This can be called multiple times + // during the lifetime of this instance after the members of this base object + // have been reset with new values from the renderer process. + // Perform child independent initialization in this method. + virtual void PreInitialize() {} + + BrowserAccessibility(); + + // The manager of this tree of accessibility objects; needed for + // global operations like focus tracking. + BrowserAccessibilityManager* manager_; + + // The parent of this object, may be NULL if we're the root object. + BrowserAccessibility* parent_; + + // The index of this within its parent object. + int32 index_in_parent_; + + // The ID of this object in the renderer process. + int32 renderer_id_; + + // The children of this object. + std::vector<BrowserAccessibility*> children_; + + // Accessibility metadata from the renderer + string16 name_; + string16 value_; + BoolAttrMap bool_attributes_; + IntAttrMap int_attributes_; + FloatAttrMap float_attributes_; + StringAttrMap string_attributes_; + std::vector<std::pair<string16, string16> > html_attributes_; + int32 role_; + int32 state_; + string16 role_name_; + gfx::Rect location_; + std::vector<int32> indirect_child_ids_; + std::vector<int32> line_breaks_; + std::vector<int32> cell_ids_; + std::vector<int32> unique_cell_ids_; + + // BrowserAccessibility objects are reference-counted on some platforms. + // When we're done with this object and it's removed from our accessibility + // tree, a client may still be holding onto a pointer to this object, so + // we mark it as inactive so that calls to any of this object's methods + // immediately return failure. + bool instance_active_; + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibility); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_android.cc b/chromium/content/browser/accessibility/browser_accessibility_android.cc new file mode 100644 index 00000000000..8ff37ec0c7d --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_android.cc @@ -0,0 +1,406 @@ +// 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/strings/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" + +namespace content { + +// static +BrowserAccessibility* BrowserAccessibility::Create() { + return new BrowserAccessibilityAndroid(); +} + +BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() { + first_time_ = true; +} + +bool BrowserAccessibilityAndroid::IsNative() const { + return true; +} + +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 = GetText(); + 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; +} + +bool BrowserAccessibilityAndroid::IsCheckable() 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 checkable; +} + +bool BrowserAccessibilityAndroid::IsChecked() const { + return HasState(AccessibilityNodeData::STATE_CHECKED); +} + +bool BrowserAccessibilityAndroid::IsClickable() const { + return (IsLeaf() && !GetText().empty()); +} + +bool BrowserAccessibilityAndroid::IsEnabled() const { + return !HasState(AccessibilityNodeData::STATE_UNAVAILABLE); +} + +bool BrowserAccessibilityAndroid::IsFocusable() const { + bool focusable = HasState(AccessibilityNodeData::STATE_FOCUSABLE); + if (IsIframe() || + role() == AccessibilityNodeData::ROLE_WEB_AREA) { + focusable = false; + } + return focusable; +} + +bool BrowserAccessibilityAndroid::IsFocused() const { + return manager()->GetFocus(manager()->GetRoot()) == this; +} + +bool BrowserAccessibilityAndroid::IsPassword() const { + return HasState(AccessibilityNodeData::STATE_PROTECTED); +} + +bool BrowserAccessibilityAndroid::IsScrollable() const { + int dummy; + return GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &dummy); +} + +bool BrowserAccessibilityAndroid::IsSelected() const { + return HasState(AccessibilityNodeData::STATE_SELECTED); +} + +bool BrowserAccessibilityAndroid::IsVisibleToUser() const { + return !HasState(AccessibilityNodeData::STATE_INVISIBLE); +} + +const char* BrowserAccessibilityAndroid::GetClassName() 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 class_name; +} + +string16 BrowserAccessibilityAndroid::GetText() 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)->GetText(); + } + } + + 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; +} + +int BrowserAccessibilityAndroid::GetItemIndex() 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 index; +} + +int BrowserAccessibilityAndroid::GetItemCount() 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 count; +} + +int BrowserAccessibilityAndroid::GetScrollX() const { + int value = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &value); + return value; +} + +int BrowserAccessibilityAndroid::GetScrollY() const { + int value = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &value); + return value; +} + +int BrowserAccessibilityAndroid::GetMaxScrollX() const { + int value = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &value); + return value; +} + +int BrowserAccessibilityAndroid::GetMaxScrollY() const { + int value = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y_MAX, &value); + return value; +} + +int BrowserAccessibilityAndroid::GetTextChangeFromIndex() const { + size_t index = 0; + while (index < old_value_.length() && + index < new_value_.length() && + old_value_[index] == new_value_[index]) { + index++; + } + return index; +} + +int BrowserAccessibilityAndroid::GetTextChangeAddedCount() 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); +} + +int BrowserAccessibilityAndroid::GetTextChangeRemovedCount() 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); +} + +string16 BrowserAccessibilityAndroid::GetTextChangeBeforeText() const { + return old_value_; +} + +int BrowserAccessibilityAndroid::GetSelectionStart() const { + int sel_start = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start); + return sel_start; +} + +int BrowserAccessibilityAndroid::GetSelectionEnd() const { + int sel_end = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end); + return sel_end; +} + +int BrowserAccessibilityAndroid::GetEditableTextLength() const { + return value().length(); +} + +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"); +} + +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 = GetText(); + if (cached_text_ != text) { + if (!text.empty()) { + manager_->NotifyAccessibilityEvent(AccessibilityNotificationObjectShow, + this); + } + cached_text_ = text; + } +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_android.h b/chromium/content/browser/accessibility/browser_accessibility_android.h new file mode 100644 index 00000000000..8b4ed84bc9e --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_android.h @@ -0,0 +1,74 @@ +// 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; + + bool IsLeaf() const; + + bool IsCheckable() const; + bool IsChecked() const; + bool IsClickable() const; + bool IsEnabled() const; + bool IsFocusable() const; + bool IsFocused() const; + bool IsPassword() const; + bool IsScrollable() const; + bool IsSelected() const; + bool IsVisibleToUser() const; + + const char* GetClassName() const; + string16 GetText() const; + + int GetItemIndex() const; + int GetItemCount() const; + + int GetScrollX() const; + int GetScrollY() const; + int GetMaxScrollX() const; + int GetMaxScrollY() const; + + int GetTextChangeFromIndex() const; + int GetTextChangeAddedCount() const; + int GetTextChangeRemovedCount() const; + string16 GetTextChangeBeforeText() const; + + int GetSelectionStart() const; + int GetSelectionEnd() const; + int GetEditableTextLength() const; + + private: + // This gives BrowserAccessibility::Create access to the class constructor. + friend class BrowserAccessibility; + + BrowserAccessibilityAndroid(); + + bool HasFocusableChild() const; + bool HasOnlyStaticTextChildren() const; + bool IsIframe() const; + + void NotifyLiveRegionUpdate(string16& aria_live); + + string16 cached_text_; + bool first_time_; + string16 old_value_; + string16 new_value_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityAndroid); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_cocoa.h b/chromium/content/browser/accessibility/browser_accessibility_cocoa.h new file mode 100644 index 00000000000..a394822961c --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_cocoa.h @@ -0,0 +1,112 @@ +// Copyright (c) 2012 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_COCOA_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_COCOA_H_ + +#import <Cocoa/Cocoa.h> + +#import "base/mac/scoped_nsobject.h" +#include "content/browser/accessibility/browser_accessibility.h" +#import "content/browser/accessibility/browser_accessibility_delegate_mac.h" +#include "content/common/accessibility_node_data.h" + +// BrowserAccessibilityCocoa is a cocoa wrapper around the BrowserAccessibility +// object. The renderer converts webkit's accessibility tree into a +// WebAccessibility tree and passes it to the browser process over IPC. +// This class converts it into a format Cocoa can query. +@interface BrowserAccessibilityCocoa : NSObject { + @private + content::BrowserAccessibility* browserAccessibility_; + base::scoped_nsobject<NSMutableArray> children_; + id<BrowserAccessibilityDelegateCocoa> delegate_; +} + +// This creates a cocoa browser accessibility object around +// the cross platform BrowserAccessibility object. The delegate is +// used to communicate with the host renderer. None of these +// parameters can be null. +- (id)initWithObject:(content::BrowserAccessibility*)accessibility + delegate:(id<BrowserAccessibilityDelegateCocoa>)delegate; + +// Clear this object's pointer to the wrapped BrowserAccessibility object +// because the wrapped object has been deleted, but this object may +// persist if the system still has references to it. +- (void)detach; + +// Invalidate children for a non-ignored ancestor (including self). +- (void)childrenChanged; + +// Convenience method to get the internal, cross-platform role +// from browserAccessibility_. +- (content::AccessibilityNodeData::Role)internalRole; + +// Return the method name for the given attribute. For testing only. +- (NSString*)methodNameForAttribute:(NSString*)attribute; + +// Internally-used method. +@property(nonatomic, readonly) NSPoint origin; + +// Children is an array of BrowserAccessibility objects, representing +// the accessibility children of this object. +@property(nonatomic, readonly) NSString* accessKey; +@property(nonatomic, readonly) NSNumber* ariaAtomic; +@property(nonatomic, readonly) NSNumber* ariaBusy; +@property(nonatomic, readonly) NSString* ariaLive; +@property(nonatomic, readonly) NSString* ariaRelevant; +@property(nonatomic, readonly) NSArray* children; +@property(nonatomic, readonly) NSArray* columns; +@property(nonatomic, readonly) NSArray* columnHeaders; +@property(nonatomic, readonly) NSValue* columnIndexRange; +@property(nonatomic, readonly) NSString* description; +@property(nonatomic, readonly) NSNumber* disclosing; +@property(nonatomic, readonly) id disclosedByRow; +@property(nonatomic, readonly) NSNumber* disclosureLevel; +@property(nonatomic, readonly) id disclosedRows; +@property(nonatomic, readonly) NSNumber* enabled; +@property(nonatomic, readonly) NSNumber* focused; +@property(nonatomic, readonly) NSString* help; +// isIgnored returns whether or not the accessibility object +// should be ignored by the accessibility hierarchy. +@property(nonatomic, readonly, getter=isIgnored) BOOL ignored; +// Index of a row, column, or tree item. +@property(nonatomic, readonly) NSNumber* index; +@property(nonatomic, readonly) NSString* invalid; +@property(nonatomic, readonly) NSNumber* loaded; +@property(nonatomic, readonly) NSNumber* loadingProgress; +@property(nonatomic, readonly) NSNumber* maxValue; +@property(nonatomic, readonly) NSNumber* minValue; +@property(nonatomic, readonly) NSNumber* numberOfCharacters; +@property(nonatomic, readonly) NSString* orientation; +@property(nonatomic, readonly) id parent; +@property(nonatomic, readonly) NSValue* position; +@property(nonatomic, readonly) NSNumber* required; +// A string indicating the role of this object as far as accessibility +// is concerned. +@property(nonatomic, readonly) NSString* role; +@property(nonatomic, readonly) NSString* roleDescription; +@property(nonatomic, readonly) NSArray* rowHeaders; +@property(nonatomic, readonly) NSValue* rowIndexRange; +@property(nonatomic, readonly) NSArray* rows; +// The size of this object. +@property(nonatomic, readonly) NSValue* size; +// A string indicating the subrole of this object as far as accessibility +// is concerned. +@property(nonatomic, readonly) NSString* subrole; +// The tabs owned by a tablist. +@property(nonatomic, readonly) NSArray* tabs; +@property(nonatomic, readonly) NSString* title; +@property(nonatomic, readonly) id titleUIElement; +@property(nonatomic, readonly) NSString* url; +@property(nonatomic, readonly) NSString* value; +@property(nonatomic, readonly) NSString* valueDescription; +@property(nonatomic, readonly) NSValue* visibleCharacterRange; +@property(nonatomic, readonly) NSArray* visibleCells; +@property(nonatomic, readonly) NSArray* visibleColumns; +@property(nonatomic, readonly) NSArray* visibleRows; +@property(nonatomic, readonly) NSNumber* visited; +@property(nonatomic, readonly) id window; +@end + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_COCOA_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm b/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm new file mode 100644 index 00000000000..9d49767d266 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_cocoa.mm @@ -0,0 +1,1505 @@ +// Copyright (c) 2012 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 <execinfo.h> + +#import "content/browser/accessibility/browser_accessibility_cocoa.h" + +#include <map> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/browser/accessibility/browser_accessibility_manager_mac.h" +#include "content/public/common/content_client.h" +#include "grit/webkit_strings.h" + +// See http://openradar.appspot.com/9896491. This SPI has been tested on 10.5, +// 10.6, and 10.7. It allows accessibility clients to observe events posted on +// this object. +extern "C" void NSAccessibilityUnregisterUniqueIdForUIElement(id element); + +using content::AccessibilityNodeData; +using content::BrowserAccessibility; +using content::BrowserAccessibilityManager; +using content::BrowserAccessibilityManagerMac; +using content::ContentClient; +typedef AccessibilityNodeData::StringAttribute StringAttribute; + +namespace { + +// Returns an autoreleased copy of the AccessibilityNodeData's attribute. +NSString* NSStringForStringAttribute( + const std::map<StringAttribute, string16>& attributes, + StringAttribute attribute) { + std::map<StringAttribute, string16>::const_iterator iter = + attributes.find(attribute); + NSString* returnValue = @""; + if (iter != attributes.end()) { + returnValue = base::SysUTF16ToNSString(iter->second); + } + return returnValue; +} + +struct MapEntry { + AccessibilityNodeData::Role webKitValue; + NSString* nativeValue; +}; + +typedef std::map<AccessibilityNodeData::Role, NSString*> RoleMap; + +// GetState checks the bitmask used in AccessibilityNodeData to check +// if the given state was set on the accessibility object. +bool GetState(BrowserAccessibility* accessibility, int state) { + return ((accessibility->state() >> state) & 1); +} + +RoleMap BuildRoleMap() { + const MapEntry roles[] = { + { AccessibilityNodeData::ROLE_ALERT, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_ALERT_DIALOG, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_ANNOTATION, NSAccessibilityUnknownRole }, + { AccessibilityNodeData::ROLE_APPLICATION, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_ARTICLE, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_BROWSER, NSAccessibilityBrowserRole }, + { AccessibilityNodeData::ROLE_BUSY_INDICATOR, + NSAccessibilityBusyIndicatorRole }, + { AccessibilityNodeData::ROLE_BUTTON, NSAccessibilityButtonRole }, + { AccessibilityNodeData::ROLE_CANVAS, NSAccessibilityImageRole }, + { AccessibilityNodeData::ROLE_CANVAS_WITH_FALLBACK_CONTENT, + NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_CELL, @"AXCell" }, + { AccessibilityNodeData::ROLE_CHECKBOX, NSAccessibilityCheckBoxRole }, + { AccessibilityNodeData::ROLE_COLOR_WELL, NSAccessibilityColorWellRole }, + { AccessibilityNodeData::ROLE_COMBO_BOX, NSAccessibilityComboBoxRole }, + { AccessibilityNodeData::ROLE_COLUMN, NSAccessibilityColumnRole }, + { AccessibilityNodeData::ROLE_COLUMN_HEADER, @"AXCell" }, + { AccessibilityNodeData::ROLE_DEFINITION, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_DESCRIPTION_LIST_DETAIL, + NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_DESCRIPTION_LIST_TERM, + NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_DIALOG, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_DIRECTORY, NSAccessibilityListRole }, + { AccessibilityNodeData::ROLE_DISCLOSURE_TRIANGLE, + NSAccessibilityDisclosureTriangleRole }, + { AccessibilityNodeData::ROLE_DIV, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_DOCUMENT, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_DRAWER, NSAccessibilityDrawerRole }, + { AccessibilityNodeData::ROLE_EDITABLE_TEXT, NSAccessibilityTextFieldRole }, + { AccessibilityNodeData::ROLE_FOOTER, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_FORM, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_GRID, NSAccessibilityGridRole }, + { AccessibilityNodeData::ROLE_GROUP, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_GROW_AREA, NSAccessibilityGrowAreaRole }, + { AccessibilityNodeData::ROLE_HEADING, @"AXHeading" }, + { AccessibilityNodeData::ROLE_HELP_TAG, NSAccessibilityHelpTagRole }, + { AccessibilityNodeData::ROLE_HORIZONTAL_RULE, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_IGNORED, NSAccessibilityUnknownRole }, + { AccessibilityNodeData::ROLE_IMAGE, NSAccessibilityImageRole }, + { AccessibilityNodeData::ROLE_IMAGE_MAP, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_IMAGE_MAP_LINK, NSAccessibilityLinkRole }, + { AccessibilityNodeData::ROLE_INCREMENTOR, NSAccessibilityIncrementorRole }, + { AccessibilityNodeData::ROLE_LABEL, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LANDMARK_APPLICATION, + NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LANDMARK_BANNER, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LANDMARK_COMPLEMENTARY, + NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LANDMARK_CONTENTINFO, + NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LANDMARK_MAIN, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LANDMARK_NAVIGATION, + NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LANDMARK_SEARCH, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LINK, NSAccessibilityLinkRole }, + { AccessibilityNodeData::ROLE_LIST, NSAccessibilityListRole }, + { AccessibilityNodeData::ROLE_LIST_ITEM, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_LIST_MARKER, @"AXListMarker" }, + { AccessibilityNodeData::ROLE_LISTBOX, NSAccessibilityListRole }, + { AccessibilityNodeData::ROLE_LISTBOX_OPTION, + NSAccessibilityStaticTextRole }, + { AccessibilityNodeData::ROLE_LOG, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_MARQUEE, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_MATH, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_MATTE, NSAccessibilityMatteRole }, + { AccessibilityNodeData::ROLE_MENU, NSAccessibilityMenuRole }, + { AccessibilityNodeData::ROLE_MENU_BAR, NSAccessibilityMenuBarRole }, + { AccessibilityNodeData::ROLE_MENU_ITEM, NSAccessibilityMenuItemRole }, + { AccessibilityNodeData::ROLE_MENU_BUTTON, NSAccessibilityButtonRole }, + { AccessibilityNodeData::ROLE_MENU_LIST_OPTION, + NSAccessibilityMenuItemRole }, + { AccessibilityNodeData::ROLE_MENU_LIST_POPUP, NSAccessibilityUnknownRole }, + { AccessibilityNodeData::ROLE_NOTE, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_OUTLINE, NSAccessibilityOutlineRole }, + { AccessibilityNodeData::ROLE_PARAGRAPH, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_POPUP_BUTTON, + NSAccessibilityPopUpButtonRole }, + { AccessibilityNodeData::ROLE_PRESENTATIONAL, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_PROGRESS_INDICATOR, + NSAccessibilityProgressIndicatorRole }, + { AccessibilityNodeData::ROLE_RADIO_BUTTON, + NSAccessibilityRadioButtonRole }, + { AccessibilityNodeData::ROLE_RADIO_GROUP, NSAccessibilityRadioGroupRole }, + { AccessibilityNodeData::ROLE_REGION, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_ROOT_WEB_AREA, @"AXWebArea" }, + { AccessibilityNodeData::ROLE_ROW, NSAccessibilityRowRole }, + { AccessibilityNodeData::ROLE_ROW_HEADER, @"AXCell" }, + { AccessibilityNodeData::ROLE_RULER, NSAccessibilityRulerRole }, + { AccessibilityNodeData::ROLE_RULER_MARKER, + NSAccessibilityRulerMarkerRole }, + // TODO(dtseng): we don't correctly support the attributes for these roles. + // { AccessibilityNodeData::ROLE_SCROLLAREA, + // NSAccessibilityScrollAreaRole }, + { AccessibilityNodeData::ROLE_SCROLLBAR, NSAccessibilityScrollBarRole }, + { AccessibilityNodeData::ROLE_SHEET, NSAccessibilitySheetRole }, + { AccessibilityNodeData::ROLE_SLIDER, NSAccessibilitySliderRole }, + { AccessibilityNodeData::ROLE_SLIDER_THUMB, + NSAccessibilityValueIndicatorRole }, + { AccessibilityNodeData::ROLE_SPIN_BUTTON, NSAccessibilitySliderRole }, + { AccessibilityNodeData::ROLE_SPLITTER, NSAccessibilitySplitterRole }, + { AccessibilityNodeData::ROLE_SPLIT_GROUP, NSAccessibilitySplitGroupRole }, + { AccessibilityNodeData::ROLE_STATIC_TEXT, NSAccessibilityStaticTextRole }, + { AccessibilityNodeData::ROLE_STATUS, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_SVG_ROOT, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_SYSTEM_WIDE, NSAccessibilityUnknownRole }, + { AccessibilityNodeData::ROLE_TAB, NSAccessibilityRadioButtonRole }, + { AccessibilityNodeData::ROLE_TAB_LIST, NSAccessibilityTabGroupRole }, + { AccessibilityNodeData::ROLE_TAB_PANEL, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_TABLE, NSAccessibilityTableRole }, + { AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER, + NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_TAB_GROUP_UNUSED, + NSAccessibilityTabGroupRole }, + { AccessibilityNodeData::ROLE_TEXTAREA, NSAccessibilityTextAreaRole }, + { AccessibilityNodeData::ROLE_TEXT_FIELD, NSAccessibilityTextFieldRole }, + { AccessibilityNodeData::ROLE_TIMER, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_TOGGLE_BUTTON, NSAccessibilityButtonRole }, + { AccessibilityNodeData::ROLE_TOOLBAR, NSAccessibilityToolbarRole }, + { AccessibilityNodeData::ROLE_TOOLTIP, NSAccessibilityGroupRole }, + { AccessibilityNodeData::ROLE_TREE, NSAccessibilityOutlineRole }, + { AccessibilityNodeData::ROLE_TREE_GRID, NSAccessibilityTableRole }, + { AccessibilityNodeData::ROLE_TREE_ITEM, NSAccessibilityRowRole }, + { AccessibilityNodeData::ROLE_VALUE_INDICATOR, + NSAccessibilityValueIndicatorRole }, + { AccessibilityNodeData::ROLE_WEBCORE_LINK, NSAccessibilityLinkRole }, + { AccessibilityNodeData::ROLE_WEB_AREA, @"AXWebArea" }, + { AccessibilityNodeData::ROLE_WINDOW, NSAccessibilityWindowRole }, + }; + + RoleMap role_map; + for (size_t i = 0; i < arraysize(roles); ++i) + role_map[roles[i].webKitValue] = roles[i].nativeValue; + return role_map; +} + +// A mapping of webkit roles to native roles. +NSString* NativeRoleFromAccessibilityNodeDataRole( + const AccessibilityNodeData::Role& role) { + CR_DEFINE_STATIC_LOCAL(RoleMap, web_accessibility_to_native_role, + (BuildRoleMap())); + RoleMap::iterator it = web_accessibility_to_native_role.find(role); + if (it != web_accessibility_to_native_role.end()) + return it->second; + else + return NSAccessibilityUnknownRole; +} + +RoleMap BuildSubroleMap() { + const MapEntry subroles[] = { + { AccessibilityNodeData::ROLE_ALERT, @"AXApplicationAlert" }, + { AccessibilityNodeData::ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog" }, + { AccessibilityNodeData::ROLE_ARTICLE, @"AXDocumentArticle" }, + { AccessibilityNodeData::ROLE_DEFINITION, @"AXDefinition" }, + { AccessibilityNodeData::ROLE_DESCRIPTION_LIST_DETAIL, @"AXDescription" }, + { AccessibilityNodeData::ROLE_DESCRIPTION_LIST_TERM, @"AXTerm" }, + { AccessibilityNodeData::ROLE_DIALOG, @"AXApplicationDialog" }, + { AccessibilityNodeData::ROLE_DOCUMENT, @"AXDocument" }, + { AccessibilityNodeData::ROLE_FOOTER, @"AXLandmarkContentInfo" }, + { AccessibilityNodeData::ROLE_LANDMARK_APPLICATION, + @"AXLandmarkApplication" }, + { AccessibilityNodeData::ROLE_LANDMARK_BANNER, @"AXLandmarkBanner" }, + { AccessibilityNodeData::ROLE_LANDMARK_COMPLEMENTARY, + @"AXLandmarkComplementary" }, + { AccessibilityNodeData::ROLE_LANDMARK_CONTENTINFO, + @"AXLandmarkContentInfo" }, + { AccessibilityNodeData::ROLE_LANDMARK_MAIN, @"AXLandmarkMain" }, + { AccessibilityNodeData::ROLE_LANDMARK_NAVIGATION, + @"AXLandmarkNavigation" }, + { AccessibilityNodeData::ROLE_LANDMARK_SEARCH, @"AXLandmarkSearch" }, + { AccessibilityNodeData::ROLE_LOG, @"AXApplicationLog" }, + { AccessibilityNodeData::ROLE_MARQUEE, @"AXApplicationMarquee" }, + { AccessibilityNodeData::ROLE_MATH, @"AXDocumentMath" }, + { AccessibilityNodeData::ROLE_NOTE, @"AXDocumentNote" }, + { AccessibilityNodeData::ROLE_REGION, @"AXDocumentRegion" }, + { AccessibilityNodeData::ROLE_STATUS, @"AXApplicationStatus" }, + { AccessibilityNodeData::ROLE_TAB_PANEL, @"AXTabPanel" }, + { AccessibilityNodeData::ROLE_TIMER, @"AXApplicationTimer" }, + { AccessibilityNodeData::ROLE_TOOLTIP, @"AXUserInterfaceTooltip" }, + { AccessibilityNodeData::ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole }, + }; + + RoleMap subrole_map; + for (size_t i = 0; i < arraysize(subroles); ++i) + subrole_map[subroles[i].webKitValue] = subroles[i].nativeValue; + return subrole_map; +} + +// A mapping of webkit roles to native subroles. +NSString* NativeSubroleFromAccessibilityNodeDataRole( + const AccessibilityNodeData::Role& role) { + CR_DEFINE_STATIC_LOCAL(RoleMap, web_accessibility_to_native_subrole, + (BuildSubroleMap())); + RoleMap::iterator it = web_accessibility_to_native_subrole.find(role); + if (it != web_accessibility_to_native_subrole.end()) + return it->second; + else + return nil; +} + +// A mapping from an accessibility attribute to its method name. +NSDictionary* attributeToMethodNameMap = nil; + +} // namespace + +@implementation BrowserAccessibilityCocoa + ++ (void)initialize { + const struct { + NSString* attribute; + NSString* methodName; + } attributeToMethodNameContainer[] = { + { NSAccessibilityChildrenAttribute, @"children" }, + { NSAccessibilityColumnsAttribute, @"columns" }, + { NSAccessibilityColumnHeaderUIElementsAttribute, @"columnHeaders" }, + { NSAccessibilityColumnIndexRangeAttribute, @"columnIndexRange" }, + { NSAccessibilityContentsAttribute, @"contents" }, + { NSAccessibilityDescriptionAttribute, @"description" }, + { NSAccessibilityDisclosingAttribute, @"disclosing" }, + { NSAccessibilityDisclosedByRowAttribute, @"disclosedByRow" }, + { NSAccessibilityDisclosureLevelAttribute, @"disclosureLevel" }, + { NSAccessibilityDisclosedRowsAttribute, @"disclosedRows" }, + { NSAccessibilityEnabledAttribute, @"enabled" }, + { NSAccessibilityFocusedAttribute, @"focused" }, + { NSAccessibilityHeaderAttribute, @"header" }, + { NSAccessibilityHelpAttribute, @"help" }, + { NSAccessibilityIndexAttribute, @"index" }, + { NSAccessibilityMaxValueAttribute, @"maxValue" }, + { NSAccessibilityMinValueAttribute, @"minValue" }, + { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" }, + { NSAccessibilityOrientationAttribute, @"orientation" }, + { NSAccessibilityParentAttribute, @"parent" }, + { NSAccessibilityPositionAttribute, @"position" }, + { NSAccessibilityRoleAttribute, @"role" }, + { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" }, + { NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders" }, + { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" }, + { NSAccessibilityRowsAttribute, @"rows" }, + { NSAccessibilitySizeAttribute, @"size" }, + { NSAccessibilitySubroleAttribute, @"subrole" }, + { NSAccessibilityTabsAttribute, @"tabs" }, + { NSAccessibilityTitleAttribute, @"title" }, + { NSAccessibilityTitleUIElementAttribute, @"titleUIElement" }, + { NSAccessibilityTopLevelUIElementAttribute, @"window" }, + { NSAccessibilityURLAttribute, @"url" }, + { NSAccessibilityValueAttribute, @"value" }, + { NSAccessibilityValueDescriptionAttribute, @"valueDescription" }, + { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" }, + { NSAccessibilityVisibleCellsAttribute, @"visibleCells" }, + { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" }, + { NSAccessibilityVisibleRowsAttribute, @"visibleRows" }, + { NSAccessibilityWindowAttribute, @"window" }, + { @"AXAccessKey", @"accessKey" }, + { @"AXARIAAtomic", @"ariaAtomic" }, + { @"AXARIABusy", @"ariaBusy" }, + { @"AXARIALive", @"ariaLive" }, + { @"AXARIARelevant", @"ariaRelevant" }, + { @"AXInvalid", @"invalid" }, + { @"AXLoaded", @"loaded" }, + { @"AXLoadingProgress", @"loadingProgress" }, + { @"AXRequired", @"required" }, + { @"AXVisited", @"visited" }, + }; + + NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; + const size_t numAttributes = sizeof(attributeToMethodNameContainer) / + sizeof(attributeToMethodNameContainer[0]); + for (size_t i = 0; i < numAttributes; ++i) { + [dict setObject:attributeToMethodNameContainer[i].methodName + forKey:attributeToMethodNameContainer[i].attribute]; + } + attributeToMethodNameMap = dict; + dict = nil; +} + +- (id)initWithObject:(BrowserAccessibility*)accessibility + delegate:(id<BrowserAccessibilityDelegateCocoa>)delegate { + if ((self = [super init])) { + browserAccessibility_ = accessibility; + delegate_ = delegate; + } + return self; +} + +- (void)detach { + if (browserAccessibility_) { + NSAccessibilityUnregisterUniqueIdForUIElement(self); + browserAccessibility_ = NULL; + } +} + +- (NSString*)accessKey { + return NSStringForStringAttribute( + browserAccessibility_->string_attributes(), + AccessibilityNodeData::ATTR_ACCESS_KEY); +} + +- (NSNumber*)ariaAtomic { + bool boolValue = false; + browserAccessibility_->GetBoolAttribute( + AccessibilityNodeData::ATTR_LIVE_ATOMIC, &boolValue); + return [NSNumber numberWithBool:boolValue]; +} + +- (NSNumber*)ariaBusy { + bool boolValue = false; + browserAccessibility_->GetBoolAttribute( + AccessibilityNodeData::ATTR_LIVE_BUSY, &boolValue); + return [NSNumber numberWithBool:boolValue]; +} + +- (NSString*)ariaLive { + return NSStringForStringAttribute( + browserAccessibility_->string_attributes(), + AccessibilityNodeData::ATTR_LIVE_STATUS); +} + +- (NSString*)ariaRelevant { + return NSStringForStringAttribute( + browserAccessibility_->string_attributes(), + AccessibilityNodeData::ATTR_LIVE_RELEVANT); +} + +// Returns an array of BrowserAccessibilityCocoa objects, representing the +// accessibility children of this object. +- (NSArray*)children { + if (!children_) { + children_.reset([[NSMutableArray alloc] + initWithCapacity:browserAccessibility_->child_count()] ); + for (uint32 index = 0; + index < browserAccessibility_->child_count(); + ++index) { + BrowserAccessibilityCocoa* child = + browserAccessibility_->GetChild(index)->ToBrowserAccessibilityCocoa(); + if ([child isIgnored]) + [children_ addObjectsFromArray:[child children]]; + else + [children_ addObject:child]; + } + + // Also, add indirect children (if any). + for (uint32 i = 0; + i < browserAccessibility_->indirect_child_ids().size(); + ++i) { + int32 child_id = browserAccessibility_->indirect_child_ids()[i]; + BrowserAccessibility* child = + browserAccessibility_->manager()->GetFromRendererID(child_id); + + // This only became necessary as a result of crbug.com/93095. It should be + // a DCHECK in the future. + if (child) { + BrowserAccessibilityCocoa* child_cocoa = + child->ToBrowserAccessibilityCocoa(); + [children_ addObject:child_cocoa]; + } + } + } + return children_; +} + +- (void)childrenChanged { + if (![self isIgnored]) { + children_.reset(); + } else { + [browserAccessibility_->parent()->ToBrowserAccessibilityCocoa() + childrenChanged]; + } +} + +- (NSArray*)columnHeaders { + if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE && + [self internalRole] != AccessibilityNodeData::ROLE_GRID) { + return nil; + } + + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + const std::vector<int32>& uniqueCellIds = + browserAccessibility_->unique_cell_ids(); + for (size_t i = 0; i < uniqueCellIds.size(); ++i) { + int id = uniqueCellIds[i]; + BrowserAccessibility* cell = + browserAccessibility_->manager()->GetFromRendererID(id); + if (cell && cell->role() == AccessibilityNodeData::ROLE_COLUMN_HEADER) + [ret addObject:cell->ToBrowserAccessibilityCocoa()]; + } + return ret; +} + +- (NSValue*)columnIndexRange { + if ([self internalRole] != AccessibilityNodeData::ROLE_CELL) + return nil; + + int column = -1; + int colspan = -1; + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column); + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan); + if (column >= 0 && colspan >= 1) + return [NSValue valueWithRange:NSMakeRange(column, colspan)]; + return nil; +} + +- (NSArray*)columns { + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + for (BrowserAccessibilityCocoa* child in [self children]) { + if ([[child role] isEqualToString:NSAccessibilityColumnRole]) + [ret addObject:child]; + } + return ret; +} + +- (NSString*)description { + const std::map<StringAttribute, string16>& attributes = + browserAccessibility_->string_attributes(); + std::map<StringAttribute, string16>::const_iterator iter = + attributes.find(AccessibilityNodeData::ATTR_DESCRIPTION); + if (iter != attributes.end()) + return base::SysUTF16ToNSString(iter->second); + + // If the role is anything other than an image, or if there's + // a title or title UI element, just return an empty string. + if (![[self role] isEqualToString:NSAccessibilityImageRole]) + return @""; + if (!browserAccessibility_->name().empty()) + return @""; + if ([self titleUIElement]) + return @""; + + // The remaining case is an image where there's no other title. + // Return the base part of the filename as the description. + iter = attributes.find(AccessibilityNodeData::ATTR_URL); + if (iter != attributes.end()) { + string16 filename = iter->second; + // Given a url like http://foo.com/bar/baz.png, just return the + // base name, e.g., "baz.png". + size_t leftIndex = filename.size(); + while (leftIndex > 0 && filename[leftIndex - 1] != '/') + leftIndex--; + string16 basename = filename.substr(leftIndex); + + return base::SysUTF16ToNSString(basename); + } + + return @""; +} + +- (NSNumber*)disclosing { + if ([self internalRole] == AccessibilityNodeData::ROLE_TREE_ITEM) { + return [NSNumber numberWithBool: + GetState(browserAccessibility_, AccessibilityNodeData::STATE_EXPANDED)]; + } else { + return nil; + } +} + +- (id)disclosedByRow { + // The row that contains this row. + // It should be the same as the first parent that is a treeitem. + return nil; +} + +- (NSNumber*)disclosureLevel { + AccessibilityNodeData::Role role = [self internalRole]; + if (role == AccessibilityNodeData::ROLE_ROW || + role == AccessibilityNodeData::ROLE_TREE_ITEM) { + int level = 0; + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_HIERARCHICAL_LEVEL, &level); + // Mac disclosureLevel is 0-based, but web levels are 1-based. + if (level > 0) + level--; + return [NSNumber numberWithInt:level]; + } else { + return nil; + } +} + +- (id)disclosedRows { + // The rows that are considered inside this row. + return nil; +} + +- (NSNumber*)enabled { + return [NSNumber numberWithBool: + !GetState(browserAccessibility_, + AccessibilityNodeData::STATE_UNAVAILABLE)]; +} + +- (NSNumber*)focused { + BrowserAccessibilityManager* manager = browserAccessibility_->manager(); + NSNumber* ret = [NSNumber numberWithBool: + manager->GetFocus(NULL) == browserAccessibility_]; + return ret; +} + +- (id)header { + int headerElementId = -1; + if ([self internalRole] == AccessibilityNodeData::ROLE_TABLE || + [self internalRole] == AccessibilityNodeData::ROLE_GRID) { + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_HEADER_ID, &headerElementId); + } else if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) { + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_HEADER_ID, &headerElementId); + } else if ([self internalRole] == AccessibilityNodeData::ROLE_ROW) { + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_HEADER_ID, &headerElementId); + } + + if (headerElementId > 0) { + BrowserAccessibility* headerObject = + browserAccessibility_->manager()->GetFromRendererID(headerElementId); + if (headerObject) + return headerObject->ToBrowserAccessibilityCocoa(); + } + return nil; +} + +- (NSString*)help { + return NSStringForStringAttribute( + browserAccessibility_->string_attributes(), + AccessibilityNodeData::ATTR_HELP); +} + +- (NSNumber*)index { + if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) { + int columnIndex; + if (browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_INDEX, &columnIndex)) { + return [NSNumber numberWithInt:columnIndex]; + } + } else if ([self internalRole] == AccessibilityNodeData::ROLE_ROW) { + int rowIndex; + if (browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_INDEX, &rowIndex)) { + return [NSNumber numberWithInt:rowIndex]; + } + } + + return nil; +} + +// Returns whether or not this node should be ignored in the +// accessibility tree. +- (BOOL)isIgnored { + return [[self role] isEqualToString:NSAccessibilityUnknownRole]; +} + +- (NSString*)invalid { + string16 invalidUTF; + if (!browserAccessibility_->GetHtmlAttribute("aria-invalid", &invalidUTF)) + return NULL; + NSString* invalid = base::SysUTF16ToNSString(invalidUTF); + if ([invalid isEqualToString:@"false"] || + [invalid isEqualToString:@""]) { + return @"false"; + } + return invalid; +} + +- (NSNumber*)loaded { + return [NSNumber numberWithBool:YES]; +} + +- (NSNumber*)loadingProgress { + float floatValue = 0.0; + browserAccessibility_->GetFloatAttribute( + AccessibilityNodeData::ATTR_DOC_LOADING_PROGRESS, &floatValue); + return [NSNumber numberWithFloat:floatValue]; +} + +- (NSNumber*)maxValue { + float floatValue = 0.0; + browserAccessibility_->GetFloatAttribute( + AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE, &floatValue); + return [NSNumber numberWithFloat:floatValue]; +} + +- (NSNumber*)minValue { + float floatValue = 0.0; + browserAccessibility_->GetFloatAttribute( + AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE, &floatValue); + return [NSNumber numberWithFloat:floatValue]; +} + +- (NSString*)orientation { + // We present a spin button as a vertical slider, with a role description + // of "spin button". + if ([self internalRole] == AccessibilityNodeData::ROLE_SPIN_BUTTON) + return NSAccessibilityVerticalOrientationValue; + + if (GetState(browserAccessibility_, AccessibilityNodeData::STATE_VERTICAL)) + return NSAccessibilityVerticalOrientationValue; + else + return NSAccessibilityHorizontalOrientationValue; +} + +- (NSNumber*)numberOfCharacters { + return [NSNumber numberWithInt:browserAccessibility_->value().length()]; +} + +// The origin of this accessibility object in the page's document. +// This is relative to webkit's top-left origin, not Cocoa's +// bottom-left origin. +- (NSPoint)origin { + gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect(); + return NSMakePoint(bounds.x(), bounds.y()); +} + +- (id)parent { + // A nil parent means we're the root. + if (browserAccessibility_->parent()) { + return NSAccessibilityUnignoredAncestor( + browserAccessibility_->parent()->ToBrowserAccessibilityCocoa()); + } else { + // Hook back up to RenderWidgetHostViewCocoa. + BrowserAccessibilityManagerMac* manager = + static_cast<BrowserAccessibilityManagerMac*>( + browserAccessibility_->manager()); + return manager->parent_view(); + } +} + +- (NSValue*)position { + return [NSValue valueWithPoint:[delegate_ accessibilityPointInScreen:self]]; +} + +- (NSNumber*)required { + return [NSNumber numberWithBool: + GetState(browserAccessibility_, AccessibilityNodeData::STATE_REQUIRED)]; +} + +// Returns an enum indicating the role from browserAccessibility_. +- (AccessibilityNodeData::Role)internalRole { + return static_cast<AccessibilityNodeData::Role>( + browserAccessibility_->role()); +} + +// Returns a string indicating the NSAccessibility role of this object. +- (NSString*)role { + return NativeRoleFromAccessibilityNodeDataRole([self internalRole]); +} + +// Returns a string indicating the role description of this object. +- (NSString*)roleDescription { + NSString* role = [self role]; + + ContentClient* content_client = content::GetContentClient(); + + // The following descriptions are specific to webkit. + if ([role isEqualToString:@"AXWebArea"]) { + return base::SysUTF16ToNSString(content_client->GetLocalizedString( + IDS_AX_ROLE_WEB_AREA)); + } + + if ([role isEqualToString:@"NSAccessibilityLinkRole"]) { + return base::SysUTF16ToNSString(content_client->GetLocalizedString( + IDS_AX_ROLE_LINK)); + } + + if ([role isEqualToString:@"AXHeading"]) { + return base::SysUTF16ToNSString(content_client->GetLocalizedString( + IDS_AX_ROLE_HEADING)); + } + + if ([role isEqualToString:NSAccessibilityGroupRole] || + [role isEqualToString:NSAccessibilityRadioButtonRole]) { + const std::vector<std::pair<string16, string16> >& htmlAttributes = + browserAccessibility_->html_attributes(); + AccessibilityNodeData::Role browserAccessibilityRole = [self internalRole]; + if ((browserAccessibilityRole != AccessibilityNodeData::ROLE_GROUP && + browserAccessibilityRole != AccessibilityNodeData::ROLE_LIST_ITEM) || + browserAccessibilityRole == AccessibilityNodeData::ROLE_TAB) { + for (size_t i = 0; i < htmlAttributes.size(); ++i) { + const std::pair<string16, string16>& htmlAttribute = htmlAttributes[i]; + if (htmlAttribute.first == ASCIIToUTF16("role")) { + // TODO(dtseng): This is not localized; see crbug/84814. + return base::SysUTF16ToNSString(htmlAttribute.second); + } + } + } + } + + switch([self internalRole]) { + case AccessibilityNodeData::ROLE_FOOTER: + return base::SysUTF16ToNSString(content_client->GetLocalizedString( + IDS_AX_ROLE_FOOTER)); + case AccessibilityNodeData::ROLE_SPIN_BUTTON: + // This control is similar to what VoiceOver calls a "stepper". + return base::SysUTF16ToNSString(content_client->GetLocalizedString( + IDS_AX_ROLE_STEPPER)); + default: + break; + } + + return NSAccessibilityRoleDescription(role, nil); +} + +- (NSArray*)rowHeaders { + if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE && + [self internalRole] != AccessibilityNodeData::ROLE_GRID) { + return nil; + } + + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + const std::vector<int32>& uniqueCellIds = + browserAccessibility_->unique_cell_ids(); + for (size_t i = 0; i < uniqueCellIds.size(); ++i) { + int id = uniqueCellIds[i]; + BrowserAccessibility* cell = + browserAccessibility_->manager()->GetFromRendererID(id); + if (cell && cell->role() == AccessibilityNodeData::ROLE_ROW_HEADER) + [ret addObject:cell->ToBrowserAccessibilityCocoa()]; + } + return ret; +} + +- (NSValue*)rowIndexRange { + if ([self internalRole] != AccessibilityNodeData::ROLE_CELL) + return nil; + + int row = -1; + int rowspan = -1; + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row); + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan); + if (row >= 0 && rowspan >= 1) + return [NSValue valueWithRange:NSMakeRange(row, rowspan)]; + return nil; +} + +- (NSArray*)rows { + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + + if ([self internalRole] == AccessibilityNodeData::ROLE_TABLE|| + [self internalRole] == AccessibilityNodeData::ROLE_GRID) { + for (BrowserAccessibilityCocoa* child in [self children]) { + if ([[child role] isEqualToString:NSAccessibilityRowRole]) + [ret addObject:child]; + } + } else if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) { + const std::vector<int32>& indirectChildIds = + browserAccessibility_->indirect_child_ids(); + for (uint32 i = 0; i < indirectChildIds.size(); ++i) { + int id = indirectChildIds[i]; + BrowserAccessibility* rowElement = + browserAccessibility_->manager()->GetFromRendererID(id); + if (rowElement) + [ret addObject:rowElement->ToBrowserAccessibilityCocoa()]; + } + } + + return ret; +} + +// Returns the size of this object. +- (NSValue*)size { + gfx::Rect bounds = browserAccessibility_->GetLocalBoundsRect(); + return [NSValue valueWithSize:NSMakeSize(bounds.width(), bounds.height())]; +} + +// Returns a subrole based upon the role. +- (NSString*) subrole { + AccessibilityNodeData::Role browserAccessibilityRole = [self internalRole]; + if (browserAccessibilityRole == AccessibilityNodeData::ROLE_TEXT_FIELD && + GetState(browserAccessibility_, AccessibilityNodeData::STATE_PROTECTED)) { + return @"AXSecureTextField"; + } + + NSString* htmlTag = NSStringForStringAttribute( + browserAccessibility_->string_attributes(), + AccessibilityNodeData::ATTR_HTML_TAG); + + if (browserAccessibilityRole == AccessibilityNodeData::ROLE_LIST) { + if ([htmlTag isEqualToString:@"ul"] || + [htmlTag isEqualToString:@"ol"]) { + return @"AXContentList"; + } else if ([htmlTag isEqualToString:@"dl"]) { + return @"AXDescriptionList"; + } + } + + return NativeSubroleFromAccessibilityNodeDataRole(browserAccessibilityRole); +} + +// Returns all tabs in this subtree. +- (NSArray*)tabs { + NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease]; + + if ([self internalRole] == AccessibilityNodeData::ROLE_TAB) + [tabSubtree addObject:self]; + + for (uint i=0; i < [[self children] count]; ++i) { + NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs]; + if ([tabChildren count] > 0) + [tabSubtree addObjectsFromArray:tabChildren]; + } + + return tabSubtree; +} + +- (NSString*)title { + return base::SysUTF16ToNSString(browserAccessibility_->name()); +} + +- (id)titleUIElement { + int titleElementId; + if (browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TITLE_UI_ELEMENT, &titleElementId)) { + BrowserAccessibility* titleElement = + browserAccessibility_->manager()->GetFromRendererID(titleElementId); + if (titleElement) + return titleElement->ToBrowserAccessibilityCocoa(); + } + return nil; +} + +- (NSString*)url { + StringAttribute urlAttribute = + [[self role] isEqualToString:@"AXWebArea"] ? + AccessibilityNodeData::ATTR_DOC_URL : + AccessibilityNodeData::ATTR_URL; + return NSStringForStringAttribute( + browserAccessibility_->string_attributes(), + urlAttribute); +} + +- (id)value { + // WebCore uses an attachmentView to get the below behavior. + // We do not have any native views backing this object, so need + // to approximate Cocoa ax behavior best as we can. + NSString* role = [self role]; + if ([role isEqualToString:@"AXHeading"]) { + int level; + if (browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_HIERARCHICAL_LEVEL, &level)) { + return [NSNumber numberWithInt:level]; + } + } else if ([role isEqualToString:NSAccessibilityButtonRole]) { + // AXValue does not make sense for pure buttons. + return @""; + } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] || + [role isEqualToString:NSAccessibilityRadioButtonRole]) { + int value = 0; + value = GetState( + browserAccessibility_, AccessibilityNodeData::STATE_CHECKED) ? 1 : 0; + value = GetState( + browserAccessibility_, AccessibilityNodeData::STATE_SELECTED) ? + 1 : + value; + + bool mixed = false; + browserAccessibility_->GetBoolAttribute( + AccessibilityNodeData::ATTR_BUTTON_MIXED, &mixed); + if (mixed) + value = 2; + return [NSNumber numberWithInt:value]; + } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] || + [role isEqualToString:NSAccessibilitySliderRole] || + [role isEqualToString:NSAccessibilityScrollBarRole]) { + float floatValue; + if (browserAccessibility_->GetFloatAttribute( + AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &floatValue)) { + return [NSNumber numberWithFloat:floatValue]; + } + } else if ([role isEqualToString:NSAccessibilityColorWellRole]) { + int r, g, b; + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_COLOR_VALUE_RED, &r); + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_COLOR_VALUE_GREEN, &g); + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_COLOR_VALUE_BLUE, &b); + // This string matches the one returned by a native Mac color well. + return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1", + r / 255., g / 255., b / 255.]; + } + + return base::SysUTF16ToNSString(browserAccessibility_->value()); +} + +- (NSString*)valueDescription { + if (!browserAccessibility_->value().empty()) + return base::SysUTF16ToNSString(browserAccessibility_->value()); + else + return nil; +} + +- (NSValue*)visibleCharacterRange { + return [NSValue valueWithRange: + NSMakeRange(0, browserAccessibility_->value().length())]; +} + +- (NSArray*)visibleCells { + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + const std::vector<int32>& uniqueCellIds = + browserAccessibility_->unique_cell_ids(); + for (size_t i = 0; i < uniqueCellIds.size(); ++i) { + int id = uniqueCellIds[i]; + BrowserAccessibility* cell = + browserAccessibility_->manager()->GetFromRendererID(id); + if (cell) + [ret addObject:cell->ToBrowserAccessibilityCocoa()]; + } + return ret; +} + +- (NSArray*)visibleColumns { + return [self columns]; +} + +- (NSArray*)visibleRows { + return [self rows]; +} + +- (NSNumber*)visited { + return [NSNumber numberWithBool: + GetState(browserAccessibility_, AccessibilityNodeData::STATE_TRAVERSED)]; +} + +- (id)window { + return [delegate_ window]; +} + +- (NSString*)methodNameForAttribute:(NSString*)attribute { + return [attributeToMethodNameMap objectForKey:attribute]; +} + +// Returns the accessibility value for the given attribute. If the value isn't +// supported this will return nil. +- (id)accessibilityAttributeValue:(NSString*)attribute { + if (!browserAccessibility_) + return nil; + + SEL selector = + NSSelectorFromString([self methodNameForAttribute:attribute]); + if (selector) + return [self performSelector:selector]; + + // TODO(dtseng): refactor remaining attributes. + int selStart, selEnd; + if (browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TEXT_SEL_START, &selStart) && + browserAccessibility_-> + GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &selEnd)) { + if (selStart > selEnd) + std::swap(selStart, selEnd); + int selLength = selEnd - selStart; + if ([attribute isEqualToString: + NSAccessibilityInsertionPointLineNumberAttribute]) { + const std::vector<int32>& line_breaks = + browserAccessibility_->line_breaks(); + for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) { + if (line_breaks[i] > selStart) + return [NSNumber numberWithInt:i]; + } + return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())]; + } + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { + return base::SysUTF16ToNSString(browserAccessibility_->value().substr( + selStart, selLength)); + } + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { + return [NSValue valueWithRange:NSMakeRange(selStart, selLength)]; + } + } + return nil; +} + +// Returns the accessibility value for the given attribute and parameter. If the +// value isn't supported this will return nil. +- (id)accessibilityAttributeValue:(NSString*)attribute + forParameter:(id)parameter { + if (!browserAccessibility_) + return nil; + + const std::vector<int32>& line_breaks = browserAccessibility_->line_breaks(); + int len = static_cast<int>(browserAccessibility_->value().size()); + + if ([attribute isEqualToString: + NSAccessibilityStringForRangeParameterizedAttribute]) { + NSRange range = [(NSValue*)parameter rangeValue]; + return base::SysUTF16ToNSString( + browserAccessibility_->value().substr(range.location, range.length)); + } + + if ([attribute isEqualToString: + NSAccessibilityLineForIndexParameterizedAttribute]) { + int index = [(NSNumber*)parameter intValue]; + for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) { + if (line_breaks[i] > index) + return [NSNumber numberWithInt:i]; + } + return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())]; + } + + if ([attribute isEqualToString: + NSAccessibilityRangeForLineParameterizedAttribute]) { + int line_index = [(NSNumber*)parameter intValue]; + int line_count = static_cast<int>(line_breaks.size()) + 1; + if (line_index < 0 || line_index >= line_count) + return nil; + int start = line_index > 0 ? line_breaks[line_index - 1] : 0; + int end = line_index < line_count - 1 ? line_breaks[line_index] : len; + return [NSValue valueWithRange: + NSMakeRange(start, end - start)]; + } + + if ([attribute isEqualToString: + NSAccessibilityCellForColumnAndRowParameterizedAttribute]) { + if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE && + [self internalRole] != AccessibilityNodeData::ROLE_GRID) { + return nil; + } + if (![parameter isKindOfClass:[NSArray self]]) + return nil; + NSArray* array = parameter; + int column = [[array objectAtIndex:0] intValue]; + int row = [[array objectAtIndex:1] intValue]; + int num_columns = 0; + int num_rows = 0; + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &num_columns); + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &num_rows); + if (column < 0 || column >= num_columns || + row < 0 || row >= num_rows) { + return nil; + } + for (size_t i = 0; + i < browserAccessibility_->child_count(); + ++i) { + BrowserAccessibility* child = browserAccessibility_->GetChild(i); + if (child->role() != AccessibilityNodeData::ROLE_ROW) + continue; + int rowIndex; + if (!child->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_INDEX, &rowIndex)) { + continue; + } + if (rowIndex < row) + continue; + if (rowIndex > row) + break; + for (size_t j = 0; + j < child->child_count(); + ++j) { + BrowserAccessibility* cell = child->GetChild(j); + if (cell->role() != AccessibilityNodeData::ROLE_CELL) + continue; + int colIndex; + if (!cell->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, + &colIndex)) { + continue; + } + if (colIndex == column) + return cell->ToBrowserAccessibilityCocoa(); + if (colIndex > column) + break; + } + } + return nil; + } + + // TODO(dtseng): support the following attributes. + if ([attribute isEqualTo: + NSAccessibilityRangeForPositionParameterizedAttribute] || + [attribute isEqualTo: + NSAccessibilityRangeForIndexParameterizedAttribute] || + [attribute isEqualTo: + NSAccessibilityBoundsForRangeParameterizedAttribute] || + [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] || + [attribute isEqualTo: + NSAccessibilityStyleRangeForIndexParameterizedAttribute]) { + return nil; + } + return nil; +} + +// Returns an array of parameterized attributes names that this object will +// respond to. +- (NSArray*)accessibilityParameterizedAttributeNames { + if (!browserAccessibility_) + return nil; + + if ([[self role] isEqualToString:NSAccessibilityTableRole] || + [[self role] isEqualToString:NSAccessibilityGridRole]) { + return [NSArray arrayWithObjects: + NSAccessibilityCellForColumnAndRowParameterizedAttribute, + nil]; + } + if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] || + [[self role] isEqualToString:NSAccessibilityTextAreaRole]) { + return [NSArray arrayWithObjects: + NSAccessibilityLineForIndexParameterizedAttribute, + NSAccessibilityRangeForLineParameterizedAttribute, + NSAccessibilityStringForRangeParameterizedAttribute, + NSAccessibilityRangeForPositionParameterizedAttribute, + NSAccessibilityRangeForIndexParameterizedAttribute, + NSAccessibilityBoundsForRangeParameterizedAttribute, + NSAccessibilityRTFForRangeParameterizedAttribute, + NSAccessibilityAttributedStringForRangeParameterizedAttribute, + NSAccessibilityStyleRangeForIndexParameterizedAttribute, + nil]; + } + return nil; +} + +// Returns an array of action names that this object will respond to. +- (NSArray*)accessibilityActionNames { + if (!browserAccessibility_) + return nil; + + NSMutableArray* ret = + [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction]; + NSString* role = [self role]; + // TODO(dtseng): this should only get set when there's a default action. + if (![role isEqualToString:NSAccessibilityStaticTextRole] && + ![role isEqualToString:NSAccessibilityTextAreaRole] && + ![role isEqualToString:NSAccessibilityTextFieldRole]) { + [ret addObject:NSAccessibilityPressAction]; + } + + return ret; +} + +// Returns a sub-array of values for the given attribute value, starting at +// index, with up to maxCount items. If the given index is out of bounds, +// or there are no values for the given attribute, it will return nil. +// This method is used for querying subsets of values, without having to +// return a large set of data, such as elements with a large number of +// children. +- (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute + index:(NSUInteger)index + maxCount:(NSUInteger)maxCount { + if (!browserAccessibility_) + return nil; + + NSArray* fullArray = [self accessibilityAttributeValue:attribute]; + if (!fullArray) + return nil; + NSUInteger arrayCount = [fullArray count]; + if (index >= arrayCount) + return nil; + NSRange subRange; + if ((index + maxCount) > arrayCount) { + subRange = NSMakeRange(index, arrayCount - index); + } else { + subRange = NSMakeRange(index, maxCount); + } + return [fullArray subarrayWithRange:subRange]; +} + +// Returns the count of the specified accessibility array attribute. +- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute { + if (!browserAccessibility_) + return nil; + + NSArray* fullArray = [self accessibilityAttributeValue:attribute]; + return [fullArray count]; +} + +// Returns the list of accessibility attributes that this object supports. +- (NSArray*)accessibilityAttributeNames { + if (!browserAccessibility_) + return nil; + + // General attributes. + NSMutableArray* ret = [NSMutableArray arrayWithObjects: + NSAccessibilityChildrenAttribute, + NSAccessibilityDescriptionAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilityFocusedAttribute, + NSAccessibilityHelpAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityPositionAttribute, + NSAccessibilityRoleAttribute, + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilitySizeAttribute, + NSAccessibilitySubroleAttribute, + NSAccessibilityTitleAttribute, + NSAccessibilityTopLevelUIElementAttribute, + NSAccessibilityValueAttribute, + NSAccessibilityWindowAttribute, + NSAccessibilityURLAttribute, + @"AXAccessKey", + @"AXInvalid", + @"AXRequired", + @"AXVisited", + nil]; + + // Specific role attributes. + NSString* role = [self role]; + NSString* subrole = [self subrole]; + if ([role isEqualToString:NSAccessibilityTableRole] || + [role isEqualToString:NSAccessibilityGridRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityColumnsAttribute, + NSAccessibilityVisibleColumnsAttribute, + NSAccessibilityRowsAttribute, + NSAccessibilityVisibleRowsAttribute, + NSAccessibilityVisibleCellsAttribute, + NSAccessibilityHeaderAttribute, + NSAccessibilityColumnHeaderUIElementsAttribute, + NSAccessibilityRowHeaderUIElementsAttribute, + nil]]; + } else if ([role isEqualToString:NSAccessibilityColumnRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityIndexAttribute, + NSAccessibilityHeaderAttribute, + NSAccessibilityRowsAttribute, + NSAccessibilityVisibleRowsAttribute, + nil]]; + } else if ([role isEqualToString:NSAccessibilityCellRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityColumnIndexRangeAttribute, + NSAccessibilityRowIndexRangeAttribute, + nil]]; + } else if ([role isEqualToString:@"AXWebArea"]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + @"AXLoaded", + @"AXLoadingProgress", + nil]]; + } else if ([role isEqualToString:NSAccessibilityTextFieldRole] || + [role isEqualToString:NSAccessibilityTextAreaRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityInsertionPointLineNumberAttribute, + NSAccessibilityNumberOfCharactersAttribute, + NSAccessibilitySelectedTextAttribute, + NSAccessibilitySelectedTextRangeAttribute, + NSAccessibilityVisibleCharacterRangeAttribute, + nil]]; + } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) { + [ret addObject:NSAccessibilityTabsAttribute]; + } else if ([role isEqualToString:NSAccessibilityProgressIndicatorRole] || + [role isEqualToString:NSAccessibilitySliderRole] || + [role isEqualToString:NSAccessibilityScrollBarRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityMaxValueAttribute, + NSAccessibilityMinValueAttribute, + NSAccessibilityOrientationAttribute, + NSAccessibilityValueDescriptionAttribute, + nil]]; + } else if ([subrole isEqualToString:NSAccessibilityOutlineRowSubrole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityDisclosingAttribute, + NSAccessibilityDisclosedByRowAttribute, + NSAccessibilityDisclosureLevelAttribute, + NSAccessibilityDisclosedRowsAttribute, + nil]]; + } else if ([role isEqualToString:NSAccessibilityRowRole]) { + if (browserAccessibility_->parent()) { + string16 parentRole; + browserAccessibility_->parent()->GetHtmlAttribute( + "role", &parentRole); + const string16 treegridRole(ASCIIToUTF16("treegrid")); + if (parentRole == treegridRole) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityDisclosingAttribute, + NSAccessibilityDisclosedByRowAttribute, + NSAccessibilityDisclosureLevelAttribute, + NSAccessibilityDisclosedRowsAttribute, + nil]]; + } else { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityIndexAttribute, + nil]]; + } + } + } + + // Live regions. + string16 s; + if (browserAccessibility_->GetStringAttribute( + AccessibilityNodeData::ATTR_LIVE_STATUS, &s)) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + @"AXARIALive", + @"AXARIARelevant", + nil]]; + } + if (browserAccessibility_->GetStringAttribute( + AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS, &s)) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + @"AXARIAAtomic", + @"AXARIABusy", + nil]]; + } + + // Title UI Element. + int i; + if (browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TITLE_UI_ELEMENT, &i)) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityTitleUIElementAttribute, + nil]]; + } + + return ret; +} + +// Returns the index of the child in this objects array of children. +- (NSUInteger)accessibilityGetIndexOf:(id)child { + if (!browserAccessibility_) + return nil; + + NSUInteger index = 0; + for (BrowserAccessibilityCocoa* childToCheck in [self children]) { + if ([child isEqual:childToCheck]) + return index; + ++index; + } + return NSNotFound; +} + +// Returns whether or not the specified attribute can be set by the +// accessibility API via |accessibilitySetValue:forAttribute:|. +- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { + if (!browserAccessibility_) + return nil; + + if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) + return GetState(browserAccessibility_, + AccessibilityNodeData::STATE_FOCUSABLE); + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { + bool canSetValue = false; + browserAccessibility_->GetBoolAttribute( + AccessibilityNodeData::ATTR_CAN_SET_VALUE, &canSetValue); + return canSetValue; + } + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] && + ([[self role] isEqualToString:NSAccessibilityTextFieldRole] || + [[self role] isEqualToString:NSAccessibilityTextAreaRole])) + return YES; + + return NO; +} + +// Returns whether or not this object should be ignored in the accessibilty +// tree. +- (BOOL)accessibilityIsIgnored { + if (!browserAccessibility_) + return true; + + return [self isIgnored]; +} + +// Performs the given accessibilty action on the webkit accessibility object +// that backs this object. +- (void)accessibilityPerformAction:(NSString*)action { + if (!browserAccessibility_) + return; + + // TODO(feldstein): Support more actions. + if ([action isEqualToString:NSAccessibilityPressAction]) + [delegate_ doDefaultAction:browserAccessibility_->renderer_id()]; + else if ([action isEqualToString:NSAccessibilityShowMenuAction]) + [delegate_ performShowMenuAction:self]; +} + +// Returns the description of the given action. +- (NSString*)accessibilityActionDescription:(NSString*)action { + if (!browserAccessibility_) + return nil; + + return NSAccessibilityActionDescription(action); +} + +// Sets an override value for a specific accessibility attribute. +// This class does not support this. +- (BOOL)accessibilitySetOverrideValue:(id)value + forAttribute:(NSString*)attribute { + return NO; +} + +// Sets the value for an accessibility attribute via the accessibility API. +- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute { + if (!browserAccessibility_) + return; + + if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { + NSNumber* focusedNumber = value; + BOOL focused = [focusedNumber intValue]; + [delegate_ setAccessibilityFocus:focused + accessibilityId:browserAccessibility_->renderer_id()]; + } + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { + NSRange range = [(NSValue*)value rangeValue]; + [delegate_ + accessibilitySetTextSelection:browserAccessibility_->renderer_id() + startOffset:range.location + endOffset:range.location + range.length]; + } +} + +// Returns the deepest accessibility child that should not be ignored. +// It is assumed that the hit test has been narrowed down to this object +// or one of its children, so this will never return nil unless this +// object is invalid. +- (id)accessibilityHitTest:(NSPoint)point { + if (!browserAccessibility_) + return nil; + + BrowserAccessibilityCocoa* hit = self; + for (BrowserAccessibilityCocoa* child in [self children]) { + NSPoint origin = [child origin]; + NSSize size = [[child size] sizeValue]; + NSRect rect; + rect.origin = origin; + rect.size = size; + if (NSPointInRect(point, rect)) { + hit = child; + id childResult = [child accessibilityHitTest:point]; + if (![childResult accessibilityIsIgnored]) { + hit = childResult; + break; + } + } + } + return NSAccessibilityUnignoredAncestor(hit); +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[BrowserAccessibilityCocoa class]]) + return NO; + return ([self hash] == [object hash]); +} + +- (NSUInteger)hash { + // Potentially called during dealloc. + if (!browserAccessibility_) + return [super hash]; + return browserAccessibility_->renderer_id(); +} + +- (BOOL)accessibilityShouldUseUniqueId { + return YES; +} + +@end + diff --git a/chromium/content/browser/accessibility/browser_accessibility_delegate_mac.h b/chromium/content/browser/accessibility/browser_accessibility_delegate_mac.h new file mode 100644 index 00000000000..8b9abff3a73 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_delegate_mac.h @@ -0,0 +1,26 @@ +// Copyright (c) 2012 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_DELEGATE_MAC_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_DELEGATE_MAC_H_ + +@class BrowserAccessibilityCocoa; +@class NSWindow; + +// This protocol is used by the BrowserAccessibility objects to pass messages +// to, or otherwise communicate with, their underlying WebAccessibility +// objects over the IPC boundary. +@protocol BrowserAccessibilityDelegateCocoa +- (NSPoint)accessibilityPointInScreen:(BrowserAccessibilityCocoa*)accessibility; +- (void)doDefaultAction:(int32)accessibilityObjectId; +- (void)accessibilitySetTextSelection:(int32)accId + startOffset:(int32)startOffset + endOffset:(int32)endOffset; +- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility; +- (void)setAccessibilityFocus:(BOOL)focus + accessibilityId:(int32)accessibilityObjectId; +- (NSWindow*)window; +@end + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_DELEGATE_MAC_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_gtk.cc b/chromium/content/browser/accessibility/browser_accessibility_gtk.cc new file mode 100644 index 00000000000..50364282b3d --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_gtk.cc @@ -0,0 +1,519 @@ +// Copyright (c) 2012 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_gtk.h" + +#include <gtk/gtk.h> + +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_manager_gtk.h" +#include "content/common/accessibility_messages.h" + +namespace content { + +static gpointer browser_accessibility_parent_class = NULL; + +static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk( + BrowserAccessibilityAtk* atk_object) { + if (!atk_object) + return NULL; + + return atk_object->m_object; +} + +// +// AtkComponent interface. +// + +static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk( + AtkComponent* atk_object) { + if (!IS_BROWSER_ACCESSIBILITY(atk_object)) + return NULL; + + return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object)); +} + +static AtkObject* browser_accessibility_accessible_at_point( + AtkComponent* component, gint x, gint y, AtkCoordType coord_type) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component); + if (!obj) + return NULL; + + gfx::Point point(x, y); + if (!obj->GetGlobalBoundsRect().Contains(point)) + return NULL; + + BrowserAccessibility* result = obj->BrowserAccessibilityForPoint(point); + if (!result) + return NULL; + + AtkObject* atk_result = result->ToBrowserAccessibilityGtk()->GetAtkObject(); + g_object_ref(atk_result); + return atk_result; +} + +static void browser_accessibility_get_extents( + AtkComponent* component, gint* x, gint* y, gint* width, gint* height, + AtkCoordType coord_type) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component); + if (!obj) + return; + + gfx::Rect bounds = obj->GetGlobalBoundsRect(); + *x = bounds.x(); + *y = bounds.y(); + *width = bounds.width(); + *height = bounds.height(); +} + +static gboolean browser_accessibility_grab_focus(AtkComponent* component) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component); + if (!obj) + return false; + + obj->manager()->SetFocus(obj, true); + return true; +} + +static void ComponentInterfaceInit(AtkComponentIface* iface) { + iface->ref_accessible_at_point = browser_accessibility_accessible_at_point; + iface->get_extents = browser_accessibility_get_extents; + iface->grab_focus = browser_accessibility_grab_focus; +} + +static const GInterfaceInfo ComponentInfo = { + reinterpret_cast<GInterfaceInitFunc>(ComponentInterfaceInit), 0, 0 +}; + +// +// AtkValue interface. +// + +static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk( + AtkValue* atk_object) { + if (!IS_BROWSER_ACCESSIBILITY(atk_object)) + return NULL; + + return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object)); +} + +static void browser_accessibility_get_current_value( + AtkValue* atk_object, GValue* value) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return; + + float float_val; + if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, + &float_val)) { + memset(value, 0, sizeof(*value)); + g_value_init(value, G_TYPE_FLOAT); + g_value_set_float(value, float_val); + } +} + +static void browser_accessibility_get_minimum_value( + AtkValue* atk_object, GValue* value) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return; + + float float_val; + if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE, + &float_val)) { + memset(value, 0, sizeof(*value)); + g_value_init(value, G_TYPE_FLOAT); + g_value_set_float(value, float_val); + } +} + +static void browser_accessibility_get_maximum_value( + AtkValue* atk_object, GValue* value) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return; + + float float_val; + if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE, + &float_val)) { + memset(value, 0, sizeof(*value)); + g_value_init(value, G_TYPE_FLOAT); + g_value_set_float(value, float_val); + } +} + +static void browser_accessibility_get_minimum_increment( + AtkValue* atk_object, GValue* value) { + // TODO(dmazzoni): get the correct value from an <input type=range>. + memset(value, 0, sizeof(*value)); + g_value_init(value, G_TYPE_FLOAT); + g_value_set_float(value, 1.0); +} + +static void ValueInterfaceInit(AtkValueIface* iface) { + iface->get_current_value = browser_accessibility_get_current_value; + iface->get_minimum_value = browser_accessibility_get_minimum_value; + iface->get_maximum_value = browser_accessibility_get_maximum_value; + iface->get_minimum_increment = browser_accessibility_get_minimum_increment; +} + +static const GInterfaceInfo ValueInfo = { + reinterpret_cast<GInterfaceInitFunc>(ValueInterfaceInit), 0, 0 +}; + +// +// AtkObject interface +// + +static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk( + AtkObject* atk_object) { + if (!IS_BROWSER_ACCESSIBILITY(atk_object)) + return NULL; + + return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object)); +} + +static const gchar* browser_accessibility_get_name(AtkObject* atk_object) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return NULL; + return obj->atk_acc_name().c_str(); +} + +static const gchar* browser_accessibility_get_description( + AtkObject* atk_object) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return NULL; + return obj->atk_acc_description().c_str(); +} + +static AtkObject* browser_accessibility_get_parent(AtkObject* atk_object) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return NULL; + if (obj->parent()) + return obj->parent()->ToBrowserAccessibilityGtk()->GetAtkObject(); + + BrowserAccessibilityManagerGtk* manager = + static_cast<BrowserAccessibilityManagerGtk*>(obj->manager()); + return gtk_widget_get_accessible(manager->parent_widget()); +} + +static gint browser_accessibility_get_n_children(AtkObject* atk_object) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return 0; + return obj->children().size(); +} + +static AtkObject* browser_accessibility_ref_child( + AtkObject* atk_object, gint index) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return NULL; + AtkObject* result = + obj->children()[index]->ToBrowserAccessibilityGtk()->GetAtkObject(); + g_object_ref(result); + return result; +} + +static gint browser_accessibility_get_index_in_parent(AtkObject* atk_object) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return 0; + return obj->index_in_parent(); +} + +static AtkAttributeSet* browser_accessibility_get_attributes( + AtkObject* atk_object) { + return NULL; +} + +static AtkRole browser_accessibility_get_role(AtkObject* atk_object) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return ATK_ROLE_INVALID; + return obj->atk_role(); +} + +static AtkStateSet* browser_accessibility_ref_state_set(AtkObject* atk_object) { + BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); + if (!obj) + return NULL; + AtkStateSet* state_set = + ATK_OBJECT_CLASS(browser_accessibility_parent_class)-> + ref_state_set(atk_object); + int32 state = obj->state(); + + if (state & (1 << AccessibilityNodeData::STATE_FOCUSABLE)) + atk_state_set_add_state(state_set, ATK_STATE_FOCUSABLE); + if (obj->manager()->GetFocus(NULL) == obj) + atk_state_set_add_state(state_set, ATK_STATE_FOCUSED); + if (!(state & (1 << AccessibilityNodeData::STATE_UNAVAILABLE))) + atk_state_set_add_state(state_set, ATK_STATE_ENABLED); + + return state_set; +} + +static AtkRelationSet* browser_accessibility_ref_relation_set( + AtkObject* atk_object) { + AtkRelationSet* relation_set = + ATK_OBJECT_CLASS(browser_accessibility_parent_class) + ->ref_relation_set(atk_object); + return relation_set; +} + +// +// The rest of the BrowserAccessibilityGtk code, not specific to one +// of the Atk* interfaces. +// + +static void browser_accessibility_init(AtkObject* atk_object, gpointer data) { + if (ATK_OBJECT_CLASS(browser_accessibility_parent_class)->initialize) { + ATK_OBJECT_CLASS(browser_accessibility_parent_class)->initialize( + atk_object, data); + } + + BROWSER_ACCESSIBILITY(atk_object)->m_object = + reinterpret_cast<BrowserAccessibilityGtk*>(data); +} + +static void browser_accessibility_finalize(GObject* atk_object) { + G_OBJECT_CLASS(browser_accessibility_parent_class)->finalize(atk_object); +} + +static void browser_accessibility_class_init(AtkObjectClass* klass) { + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + browser_accessibility_parent_class = g_type_class_peek_parent(klass); + + gobject_class->finalize = browser_accessibility_finalize; + klass->initialize = browser_accessibility_init; + klass->get_name = browser_accessibility_get_name; + klass->get_description = browser_accessibility_get_description; + klass->get_parent = browser_accessibility_get_parent; + klass->get_n_children = browser_accessibility_get_n_children; + klass->ref_child = browser_accessibility_ref_child; + klass->get_role = browser_accessibility_get_role; + klass->ref_state_set = browser_accessibility_ref_state_set; + klass->get_index_in_parent = browser_accessibility_get_index_in_parent; + klass->get_attributes = browser_accessibility_get_attributes; + klass->ref_relation_set = browser_accessibility_ref_relation_set; +} + +GType browser_accessibility_get_type() { + static volatile gsize type_volatile = 0; + + if (g_once_init_enter(&type_volatile)) { + static const GTypeInfo tinfo = { + sizeof(BrowserAccessibilityAtkClass), + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) browser_accessibility_class_init, + (GClassFinalizeFunc) 0, + 0, /* class data */ + sizeof(BrowserAccessibilityAtk), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) 0, + 0 /* value table */ + }; + + GType type = g_type_register_static( + ATK_TYPE_OBJECT, "BrowserAccessibility", &tinfo, GTypeFlags(0)); + g_once_init_leave(&type_volatile, type); + } + + return type_volatile; +} + +static const char* GetUniqueAccessibilityTypeName(int interface_mask) +{ + // 20 characters is enough for "Chrome%x" with any integer value. + static char name[20]; + snprintf(name, sizeof(name), "Chrome%x", interface_mask); + return name; +} + +enum AtkInterfaces { + ATK_ACTION_INTERFACE, + ATK_COMPONENT_INTERFACE, + ATK_DOCUMENT_INTERFACE, + ATK_EDITABLE_TEXT_INTERFACE, + ATK_HYPERLINK_INTERFACE, + ATK_HYPERTEXT_INTERFACE, + ATK_IMAGE_INTERFACE, + ATK_SELECTION_INTERFACE, + ATK_TABLE_INTERFACE, + ATK_TEXT_INTERFACE, + ATK_VALUE_INTERFACE, +}; + +static int GetInterfaceMaskFromObject(BrowserAccessibilityGtk* obj) { + int interface_mask = 0; + + // Component interface is always supported. + interface_mask |= 1 << ATK_COMPONENT_INTERFACE; + + int role = obj->role(); + if (role == AccessibilityNodeData::ROLE_PROGRESS_INDICATOR || + role == AccessibilityNodeData::ROLE_SCROLLBAR || + role == AccessibilityNodeData::ROLE_SLIDER) { + interface_mask |= 1 << ATK_VALUE_INTERFACE; + } + + return interface_mask; +} + +static GType GetAccessibilityTypeFromObject(BrowserAccessibilityGtk* obj) { + static const GTypeInfo type_info = { + sizeof(BrowserAccessibilityAtkClass), + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) 0, + (GClassFinalizeFunc) 0, + 0, /* class data */ + sizeof(BrowserAccessibilityAtk), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) 0, + 0 /* value table */ + }; + + int interface_mask = GetInterfaceMaskFromObject(obj); + const char* atk_type_name = GetUniqueAccessibilityTypeName(interface_mask); + GType type = g_type_from_name(atk_type_name); + if (type) + return type; + + type = g_type_register_static(BROWSER_ACCESSIBILITY_TYPE, + atk_type_name, + &type_info, + GTypeFlags(0)); + if (interface_mask & (1 << ATK_COMPONENT_INTERFACE)) + g_type_add_interface_static(type, ATK_TYPE_COMPONENT, &ComponentInfo); + if (interface_mask & (1 << ATK_VALUE_INTERFACE)) + g_type_add_interface_static(type, ATK_TYPE_VALUE, &ValueInfo); + + return type; +} + +BrowserAccessibilityAtk* browser_accessibility_new( + BrowserAccessibilityGtk* obj) { + GType type = GetAccessibilityTypeFromObject(obj); + AtkObject* atk_object = static_cast<AtkObject*>(g_object_new(type, 0)); + + atk_object_initialize(atk_object, obj); + + return BROWSER_ACCESSIBILITY(atk_object); +} + +void browser_accessibility_detach(BrowserAccessibilityAtk* atk_object) { + atk_object->m_object = NULL; +} + +// static +BrowserAccessibility* BrowserAccessibility::Create() { + return new BrowserAccessibilityGtk(); +} + +BrowserAccessibilityGtk* BrowserAccessibility::ToBrowserAccessibilityGtk() { + return static_cast<BrowserAccessibilityGtk*>(this); +} + +BrowserAccessibilityGtk::BrowserAccessibilityGtk() + : atk_object_(NULL) { +} + +BrowserAccessibilityGtk::~BrowserAccessibilityGtk() { + browser_accessibility_detach(BROWSER_ACCESSIBILITY(atk_object_)); + if (atk_object_) + g_object_unref(atk_object_); +} + +AtkObject* BrowserAccessibilityGtk::GetAtkObject() const { + if (!G_IS_OBJECT(atk_object_)) + return NULL; + return atk_object_; +} + +void BrowserAccessibilityGtk::PreInitialize() { + BrowserAccessibility::PreInitialize(); + InitRoleAndState(); + + if (atk_object_) { + // If the object's role changes and that causes its + // interface mask to change, we need to create a new + // AtkObject for it. + int interface_mask = GetInterfaceMaskFromObject(this); + if (interface_mask != interface_mask_) { + g_object_unref(atk_object_); + atk_object_ = NULL; + } + } + + if (!atk_object_) { + interface_mask_ = GetInterfaceMaskFromObject(this); + atk_object_ = ATK_OBJECT(browser_accessibility_new(this)); + if (this->parent()) { + atk_object_set_parent( + atk_object_, + this->parent()->ToBrowserAccessibilityGtk()->GetAtkObject()); + } + } +} + +bool BrowserAccessibilityGtk::IsNative() const { + return true; +} + +void BrowserAccessibilityGtk::InitRoleAndState() { + atk_acc_name_ = UTF16ToUTF8(name()); + + string16 description; + GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION, &description); + atk_acc_description_ = UTF16ToUTF8(description); + + switch(role_) { + case AccessibilityNodeData::ROLE_DOCUMENT: + case AccessibilityNodeData::ROLE_ROOT_WEB_AREA: + case AccessibilityNodeData::ROLE_WEB_AREA: + atk_role_ = ATK_ROLE_DOCUMENT_WEB; + break; + case AccessibilityNodeData::ROLE_GROUP: + case AccessibilityNodeData::ROLE_DIV: + atk_role_ = ATK_ROLE_SECTION; + break; + case AccessibilityNodeData::ROLE_BUTTON: + atk_role_ = ATK_ROLE_PUSH_BUTTON; + break; + case AccessibilityNodeData::ROLE_CHECKBOX: + atk_role_ = ATK_ROLE_CHECK_BOX; + break; + case AccessibilityNodeData::ROLE_COMBO_BOX: + atk_role_ = ATK_ROLE_COMBO_BOX; + break; + case AccessibilityNodeData::ROLE_LINK: + atk_role_ = ATK_ROLE_LINK; + break; + case AccessibilityNodeData::ROLE_RADIO_BUTTON: + atk_role_ = ATK_ROLE_RADIO_BUTTON; + break; + case AccessibilityNodeData::ROLE_STATIC_TEXT: + atk_role_ = ATK_ROLE_TEXT; + break; + case AccessibilityNodeData::ROLE_TEXTAREA: + atk_role_ = ATK_ROLE_ENTRY; + break; + case AccessibilityNodeData::ROLE_TEXT_FIELD: + atk_role_ = ATK_ROLE_ENTRY; + break; + case AccessibilityNodeData::ROLE_WEBCORE_LINK: + atk_role_ = ATK_ROLE_LINK; + break; + default: + atk_role_ = ATK_ROLE_UNKNOWN; + break; + } +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_gtk.h b/chromium/content/browser/accessibility/browser_accessibility_gtk.h new file mode 100644 index 00000000000..8f3a2e31e5b --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_gtk.h @@ -0,0 +1,96 @@ +// Copyright (c) 2012 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_GTK_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_GTK_H_ + +#include <atk/atk.h> + +#include "base/compiler_specific.h" +#include "content/browser/accessibility/browser_accessibility.h" + +namespace content { + +class BrowserAccessibilityGtk; +class BrowserAccessibilityManagerGtk; + +G_BEGIN_DECLS + +#define BROWSER_ACCESSIBILITY_TYPE (browser_accessibility_get_type()) +#define BROWSER_ACCESSIBILITY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST( \ + (obj), BROWSER_ACCESSIBILITY_TYPE, BrowserAccessibilityAtk)) +#define BROWSER_ACCESSIBILITY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST( \ + (klass), BROWSER_ACCESSIBILITY_TYPE, BrowserAccessibilityAtkClass)) +#define IS_BROWSER_ACCESSIBILITY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), BROWSER_ACCESSIBILITY_TYPE)) +#define IS_BROWSER_ACCESSIBILITY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), BROWSER_ACCESSIBILITY_TYPE)) +#define BROWSER_ACCESSIBILITY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS( \ + (obj), BROWSER_ACCESSIBILITY_TYPE, BrowserAccessibilityAtkClass)) + +typedef struct _BrowserAccessibilityAtk BrowserAccessibilityAtk; +typedef struct _BrowserAccessibilityAtkClass BrowserAccessibilityAtkClass; + +struct _BrowserAccessibilityAtk { + AtkObject parent; + BrowserAccessibilityGtk* m_object; +}; + +struct _BrowserAccessibilityAtkClass { + AtkObjectClass parent_class; +}; + +GType browser_accessibility_get_type (void) G_GNUC_CONST; + +BrowserAccessibilityAtk* browser_accessibility_new( + BrowserAccessibilityGtk* object); + +BrowserAccessibilityGtk* browser_accessibility_get_object( + BrowserAccessibilityAtk* atk_object); + +void browser_accessibility_detach (BrowserAccessibilityAtk* atk_object); + +AtkObject* browser_accessibility_get_focused_element( + BrowserAccessibilityAtk* atk_object); + +G_END_DECLS + +class BrowserAccessibilityGtk : public BrowserAccessibility { + public: + BrowserAccessibilityGtk(); + + virtual ~BrowserAccessibilityGtk(); + + AtkObject* GetAtkObject() const; + + AtkRole atk_role() { return atk_role_; } + const std::string& atk_acc_name() { return atk_acc_name_; } + const std::string& atk_acc_description() { return atk_acc_description_; } + + // BrowserAccessibility methods. + virtual void PreInitialize() OVERRIDE; + virtual bool IsNative() const OVERRIDE; + + private: + virtual void InitRoleAndState(); + + // Give BrowserAccessibility::Create access to our constructor. + friend class BrowserAccessibility; + + AtkObject* atk_object_; + AtkRole atk_role_; + std::string atk_acc_name_; + std::string atk_acc_description_; + int interface_mask_; + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityGtk); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_GTK_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_mac.h b/chromium/content/browser/accessibility/browser_accessibility_mac.h new file mode 100644 index 00000000000..63a9d007d3e --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_mac.h @@ -0,0 +1,51 @@ +// Copyright (c) 2012 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_MAC_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MAC_H_ + +#include <map> +#include <utility> +#include <vector> + +#include "content/browser/accessibility/browser_accessibility.h" + +@class BrowserAccessibilityCocoa; + +namespace content { + +class BrowserAccessibilityMac : public BrowserAccessibility { + public: + // Implementation of BrowserAccessibility. + virtual void PreInitialize() OVERRIDE; + virtual void NativeReleaseReference() OVERRIDE; + virtual bool IsNative() const OVERRIDE; + + // Overrides from BrowserAccessibility. + virtual void DetachTree(std::vector<BrowserAccessibility*>* nodes) OVERRIDE; + virtual void SwapChildren(std::vector<BrowserAccessibility*>& children) + OVERRIDE; + + // The BrowserAccessibilityCocoa associated with us. + BrowserAccessibilityCocoa* native_view() const { + return browser_accessibility_cocoa_; + } + + private: + // This gives BrowserAccessibility::Create access to the class constructor. + friend class BrowserAccessibility; + + BrowserAccessibilityMac(); + + // Allows access to the BrowserAccessibilityCocoa which wraps this. + // BrowserAccessibility. + // We own this object until our manager calls ReleaseReference; + // thereafter, the cocoa object owns us. + BrowserAccessibilityCocoa* browser_accessibility_cocoa_; + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityMac); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MAC_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_mac.mm b/chromium/content/browser/accessibility/browser_accessibility_mac.mm new file mode 100644 index 00000000000..11595a10e28 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_mac.mm @@ -0,0 +1,71 @@ +// Copyright (c) 2012 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. + +#import <Cocoa/Cocoa.h> + +#import "content/browser/accessibility/browser_accessibility_mac.h" + +#import "content/browser/accessibility/browser_accessibility_cocoa.h" +#import "content/browser/accessibility/browser_accessibility_delegate_mac.h" +#include "content/browser/accessibility/browser_accessibility_manager_mac.h" + +namespace content { + +// Static. +BrowserAccessibility* BrowserAccessibility::Create() { + return new BrowserAccessibilityMac(); +} + +BrowserAccessibilityMac::BrowserAccessibilityMac() + : browser_accessibility_cocoa_(NULL) { +} + +void BrowserAccessibilityMac::PreInitialize() { + BrowserAccessibility::PreInitialize(); + + if (browser_accessibility_cocoa_) + return; + + // We take ownership of the cocoa obj here. + BrowserAccessibilityManagerMac* manager = + static_cast<BrowserAccessibilityManagerMac*>(manager_); + browser_accessibility_cocoa_ = [[BrowserAccessibilityCocoa alloc] + initWithObject:this + delegate: + (id<BrowserAccessibilityDelegateCocoa>)manager->parent_view()]; +} + +void BrowserAccessibilityMac::NativeReleaseReference() { + // Detach this object from |browser_accessibility_cocoa_| so it + // no longer has a pointer to this object. + [browser_accessibility_cocoa_ detach]; + // Now, release it - but at this point, other processes may have a + // reference to the cocoa object. + [browser_accessibility_cocoa_ release]; + // Finally, it's safe to delete this since we've detached. + delete this; +} + +bool BrowserAccessibilityMac::IsNative() const { + return true; +} + +void BrowserAccessibilityMac::DetachTree( + std::vector<BrowserAccessibility*>* nodes) { + [browser_accessibility_cocoa_ childrenChanged]; + BrowserAccessibility::DetachTree(nodes); +} + +void BrowserAccessibilityMac::SwapChildren( + std::vector<BrowserAccessibility*>& children) { + [browser_accessibility_cocoa_ childrenChanged]; + BrowserAccessibility::SwapChildren(children); +} + +BrowserAccessibilityCocoa* BrowserAccessibility::ToBrowserAccessibilityCocoa() { + return static_cast<BrowserAccessibilityMac*>(this)-> + native_view(); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm b/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm new file mode 100644 index 00000000000..45137ec677a --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_mac_unittest.mm @@ -0,0 +1,165 @@ +// Copyright (c) 2012 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. + +#import <Cocoa/Cocoa.h> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_cocoa.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/browser/accessibility/browser_accessibility_manager_mac.h" +#include "testing/gtest/include/gtest/gtest.h" +#import "testing/gtest_mac.h" +#import "ui/base/test/ui_cocoa_test_helper.h" + +@interface MockAccessibilityDelegate : + NSView<BrowserAccessibilityDelegateCocoa> + +- (NSPoint)accessibilityPointInScreen:(BrowserAccessibilityCocoa*)accessibility; +- (void)doDefaultAction:(int32)accessibilityObjectId; +- (void)accessibilitySetTextSelection:(int32)accId + startOffset:(int32)startOffset + endOffset:(int32)endOffset; +- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility; +- (void)setAccessibilityFocus:(BOOL)focus + accessibilityId:(int32)accessibilityObjectId; +- (NSWindow*)window; + +@end + +@implementation MockAccessibilityDelegate + +- (NSPoint)accessibilityPointInScreen: + (BrowserAccessibilityCocoa*)accessibility { + return NSZeroPoint; +} +- (void)doDefaultAction:(int32)accessibilityObjectId { +} +- (void)accessibilitySetTextSelection:(int32)accId + startOffset:(int32)startOffset + endOffset:(int32)endOffset { +} +- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility { +} +- (void)setAccessibilityFocus:(BOOL)focus + accessibilityId:(int32)accessibilityObjectId { +} +- (NSWindow*)window { + return nil; +} + +@end + +namespace content { + +class BrowserAccessibilityTest : public ui::CocoaTest { + public: + virtual void SetUp() { + CocoaTest::SetUp(); + RebuildAccessibilityTree(); + } + + protected: + void RebuildAccessibilityTree() { + AccessibilityNodeData root; + root.id = 1000; + root.location.set_width(500); + root.location.set_height(100); + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.string_attributes[AccessibilityNodeData::ATTR_HELP] = + ASCIIToUTF16("HelpText"); + root.child_ids.push_back(1001); + root.child_ids.push_back(1002); + + AccessibilityNodeData child1; + child1.id = 1001; + child1.name = ASCIIToUTF16("Child1"); + child1.location.set_width(250); + child1.location.set_height(100); + child1.role = AccessibilityNodeData::ROLE_BUTTON; + + AccessibilityNodeData child2; + child2.id = 1002; + child2.location.set_x(250); + child2.location.set_width(250); + child2.location.set_height(100); + child2.role = AccessibilityNodeData::ROLE_HEADING; + + delegate_.reset([[MockAccessibilityDelegate alloc] init]); + manager_.reset( + new BrowserAccessibilityManagerMac(delegate_, root, NULL)); + manager_->UpdateNodesForTesting(child1, child2); + accessibility_.reset([manager_->GetRoot()->ToBrowserAccessibilityCocoa() + retain]); + } + + base::scoped_nsobject<MockAccessibilityDelegate> delegate_; + base::scoped_nsobject<BrowserAccessibilityCocoa> accessibility_; + scoped_ptr<BrowserAccessibilityManager> manager_; +}; + +// Standard hit test. +TEST_F(BrowserAccessibilityTest, HitTestTest) { + BrowserAccessibilityCocoa* firstChild = + [accessibility_ accessibilityHitTest:NSMakePoint(50, 50)]; + EXPECT_NSEQ(@"Child1", + [firstChild accessibilityAttributeValue:NSAccessibilityTitleAttribute]); +} + +// Test doing a hit test on the edge of a child. +TEST_F(BrowserAccessibilityTest, EdgeHitTest) { + BrowserAccessibilityCocoa* firstChild = + [accessibility_ accessibilityHitTest:NSZeroPoint]; + EXPECT_NSEQ(@"Child1", + [firstChild accessibilityAttributeValue:NSAccessibilityTitleAttribute]); +} + +// This will test a hit test with invalid coordinates. It is assumed that +// the hit test has been narrowed down to this object or one of its children +// so it should return itself since it has no better hit result. +TEST_F(BrowserAccessibilityTest, InvalidHitTestCoordsTest) { + BrowserAccessibilityCocoa* hitTestResult = + [accessibility_ accessibilityHitTest:NSMakePoint(-50, 50)]; + EXPECT_NSEQ(accessibility_, hitTestResult); +} + +// Test to ensure querying standard attributes works. +// http://crbug.com/173983 Test fails on Mac ASan bot +TEST_F(BrowserAccessibilityTest, DISABLED_BasicAttributeTest) { + NSString* helpText = [accessibility_ + accessibilityAttributeValue:NSAccessibilityHelpAttribute]; + EXPECT_NSEQ(@"HelpText", helpText); +} + +// Test querying for an invalid attribute to ensure it doesn't crash. +TEST_F(BrowserAccessibilityTest, InvalidAttributeTest) { + NSString* shouldBeNil = [accessibility_ + accessibilityAttributeValue:@"NSAnInvalidAttribute"]; + EXPECT_TRUE(shouldBeNil == nil); +} + +TEST_F(BrowserAccessibilityTest, RetainedDetachedObjectsReturnNil) { + // Get the first child. + BrowserAccessibilityCocoa* retainedFirstChild = + [accessibility_ accessibilityHitTest:NSMakePoint(50, 50)]; + EXPECT_NSEQ(@"Child1", [retainedFirstChild + accessibilityAttributeValue:NSAccessibilityTitleAttribute]); + + // Retain it. This simulates what the system might do with an + // accessibility object. + [retainedFirstChild retain]; + + // Rebuild the accessibility tree, which should detach |retainedFirstChild|. + RebuildAccessibilityTree(); + + // Now any attributes we query should return nil. + EXPECT_EQ(nil, [retainedFirstChild + accessibilityAttributeValue:NSAccessibilityTitleAttribute]); + + // Don't leak memory in the test. + [retainedFirstChild release]; +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager.cc b/chromium/content/browser/accessibility/browser_accessibility_manager.cc new file mode 100644 index 00000000000..7def57459f9 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager.cc @@ -0,0 +1,419 @@ +// Copyright (c) 2012 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.h" + +#include "base/logging.h" +#include "content/browser/accessibility/browser_accessibility.h" +#include "content/common/accessibility_messages.h" + +namespace content { + +BrowserAccessibility* BrowserAccessibilityFactory::Create() { + return BrowserAccessibility::Create(); +} + +#if !defined(OS_MACOSX) && \ + !defined(OS_WIN) && \ + !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 +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) { + return new BrowserAccessibilityManager(src, delegate, factory); +} +#endif + +BrowserAccessibilityManager::BrowserAccessibilityManager( + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : delegate_(delegate), + factory_(factory), + root_(NULL), + focus_(NULL), + osk_state_(OSK_ALLOWED) { +} + +BrowserAccessibilityManager::BrowserAccessibilityManager( + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : delegate_(delegate), + factory_(factory), + root_(NULL), + focus_(NULL), + osk_state_(OSK_ALLOWED) { + Initialize(src); +} + +BrowserAccessibilityManager::~BrowserAccessibilityManager() { + if (root_) + root_->Destroy(); +} + +void BrowserAccessibilityManager::Initialize(const AccessibilityNodeData src) { + std::vector<AccessibilityNodeData> nodes; + nodes.push_back(src); + if (!UpdateNodes(nodes)) + return; + if (!focus_) + SetFocus(root_, false); +} + +// static +AccessibilityNodeData BrowserAccessibilityManager::GetEmptyDocument() { + AccessibilityNodeData empty_document; + empty_document.id = 0; + empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + return empty_document; +} + +BrowserAccessibility* BrowserAccessibilityManager::GetRoot() { + return root_; +} + +BrowserAccessibility* BrowserAccessibilityManager::GetFromRendererID( + int32 renderer_id) { + base::hash_map<int32, BrowserAccessibility*>::iterator iter = + renderer_id_map_.find(renderer_id); + if (iter != renderer_id_map_.end()) + return iter->second; + return NULL; +} + +void BrowserAccessibilityManager::GotFocus(bool touch_event_context) { + if (!touch_event_context) + osk_state_ = OSK_DISALLOWED_BECAUSE_TAB_JUST_APPEARED; + + if (!focus_) + return; + + NotifyAccessibilityEvent(AccessibilityNotificationFocusChanged, focus_); +} + +void BrowserAccessibilityManager::WasHidden() { + osk_state_ = OSK_DISALLOWED_BECAUSE_TAB_HIDDEN; +} + +void BrowserAccessibilityManager::GotMouseDown() { + osk_state_ = OSK_ALLOWED_WITHIN_FOCUSED_OBJECT; + NotifyAccessibilityEvent(AccessibilityNotificationFocusChanged, focus_); +} + +bool BrowserAccessibilityManager::IsOSKAllowed(const gfx::Rect& bounds) { + if (!delegate_ || !delegate_->HasFocus()) + return false; + + gfx::Point touch_point = delegate_->GetLastTouchEventLocation(); + return bounds.Contains(touch_point); +} + +bool BrowserAccessibilityManager::UseRootScrollOffsetsWhenComputingBounds() { + return true; +} + +void BrowserAccessibilityManager::RemoveNode(BrowserAccessibility* node) { + if (node == focus_) + SetFocus(root_, false); + int renderer_id = node->renderer_id(); + renderer_id_map_.erase(renderer_id); +} + +void BrowserAccessibilityManager::OnAccessibilityNotifications( + const std::vector<AccessibilityHostMsg_NotificationParams>& params) { + for (uint32 index = 0; index < params.size(); index++) { + const AccessibilityHostMsg_NotificationParams& param = params[index]; + + // Update nodes that changed. + if (!UpdateNodes(param.nodes)) + return; + + // Find the node corresponding to the id that's the target of the + // notification (which may not be the root of the update tree). + BrowserAccessibility* node = GetFromRendererID(param.id); + if (!node) + continue; + + int notification_type = param.notification_type; + if (notification_type == AccessibilityNotificationFocusChanged || + notification_type == AccessibilityNotificationBlur) { + SetFocus(node, false); + + if (osk_state_ != OSK_DISALLOWED_BECAUSE_TAB_HIDDEN && + osk_state_ != OSK_DISALLOWED_BECAUSE_TAB_JUST_APPEARED) + osk_state_ = OSK_ALLOWED; + + // Don't send a native focus event if the window itself doesn't + // have focus. + if (delegate_ && !delegate_->HasFocus()) + continue; + } + + // Send the notification event to the operating system. + NotifyAccessibilityEvent(notification_type, node); + + // Set initial focus when a page is loaded. + if (notification_type == AccessibilityNotificationLoadComplete) { + if (!focus_) + SetFocus(root_, false); + if (!delegate_ || delegate_->HasFocus()) + NotifyAccessibilityEvent(AccessibilityNotificationFocusChanged, focus_); + } + } +} + +BrowserAccessibility* BrowserAccessibilityManager::GetFocus( + BrowserAccessibility* root) { + if (focus_ && (!root || focus_->IsDescendantOf(root))) + return focus_; + + return NULL; +} + +void BrowserAccessibilityManager::SetFocus( + BrowserAccessibility* node, bool notify) { + if (focus_ != node) + focus_ = node; + + if (notify && node && delegate_) + delegate_->SetAccessibilityFocus(node->renderer_id()); +} + +void BrowserAccessibilityManager::SetRoot(BrowserAccessibility* node) { + root_ = node; + NotifyRootChanged(); +} + +void BrowserAccessibilityManager::DoDefaultAction( + const BrowserAccessibility& node) { + if (delegate_) + delegate_->AccessibilityDoDefaultAction(node.renderer_id()); +} + +void BrowserAccessibilityManager::ScrollToMakeVisible( + const BrowserAccessibility& node, gfx::Rect subfocus) { + if (delegate_) { + delegate_->AccessibilityScrollToMakeVisible(node.renderer_id(), subfocus); + } +} + +void BrowserAccessibilityManager::ScrollToPoint( + const BrowserAccessibility& node, gfx::Point point) { + if (delegate_) { + delegate_->AccessibilityScrollToPoint(node.renderer_id(), point); + } +} + +void BrowserAccessibilityManager::SetTextSelection( + const BrowserAccessibility& node, int start_offset, int end_offset) { + if (delegate_) { + delegate_->AccessibilitySetTextSelection( + node.renderer_id(), start_offset, end_offset); + } +} + +gfx::Rect BrowserAccessibilityManager::GetViewBounds() { + if (delegate_) + return delegate_->GetViewBounds(); + return gfx::Rect(); +} + +void BrowserAccessibilityManager::UpdateNodesForTesting( + const AccessibilityNodeData& node1, + const AccessibilityNodeData& node2 /* = AccessibilityNodeData() */, + const AccessibilityNodeData& node3 /* = AccessibilityNodeData() */, + const AccessibilityNodeData& node4 /* = AccessibilityNodeData() */, + const AccessibilityNodeData& node5 /* = AccessibilityNodeData() */, + const AccessibilityNodeData& node6 /* = AccessibilityNodeData() */, + const AccessibilityNodeData& node7 /* = AccessibilityNodeData() */) { + std::vector<AccessibilityNodeData> nodes; + nodes.push_back(node1); + if (node2.id != AccessibilityNodeData().id) + nodes.push_back(node2); + if (node3.id != AccessibilityNodeData().id) + nodes.push_back(node3); + if (node4.id != AccessibilityNodeData().id) + nodes.push_back(node4); + if (node5.id != AccessibilityNodeData().id) + nodes.push_back(node5); + if (node6.id != AccessibilityNodeData().id) + nodes.push_back(node6); + if (node7.id != AccessibilityNodeData().id) + nodes.push_back(node7); + UpdateNodes(nodes); +} + +bool BrowserAccessibilityManager::UpdateNodes( + const std::vector<AccessibilityNodeData>& nodes) { + bool success = true; + + // First, update all of the nodes in the tree. + for (size_t i = 0; i < nodes.size() && success; i++) { + if (!UpdateNode(nodes[i])) + success = false; + } + + // In a second pass, call PostInitialize on each one - this must + // be called after all of each node's children are initialized too. + for (size_t i = 0; i < nodes.size() && success; i++) { + // Note: it's not a bug for nodes[i].id to not be found in the tree. + // Consider this example: + // Before: + // A + // B + // C + // D + // E + // F + // After: + // A + // B + // C + // F + // D + // In this example, F is being reparented. The renderer scans the tree + // in order. If can't update "C" to add "F" as a child, when "F" is still + // a child of "E". So it first updates "E", to remove "F" as a child. + // Later, it ends up deleting "E". So when we get here, "E" was updated as + // part of this sequence but it no longer exists in the final tree, so + // there's nothing to postinitialize. + BrowserAccessibility* instance = GetFromRendererID(nodes[i].id); + if (instance) + instance->PostInitialize(); + } + + if (!success) { + // A bad accessibility tree could lead to memory corruption. + // Ask the delegate to crash the renderer, or if not available, + // crash the browser. + if (delegate_) + delegate_->FatalAccessibilityTreeError(); + else + CHECK(false); + } + + return success; +} + +BrowserAccessibility* BrowserAccessibilityManager::CreateNode( + BrowserAccessibility* parent, + int32 renderer_id, + int32 index_in_parent) { + BrowserAccessibility* node = factory_->Create(); + node->InitializeTreeStructure( + this, parent, renderer_id, index_in_parent); + AddNodeToMap(node); + return node; +} + +void BrowserAccessibilityManager::AddNodeToMap(BrowserAccessibility* node) { + renderer_id_map_[node->renderer_id()] = node; +} + +bool BrowserAccessibilityManager::UpdateNode(const AccessibilityNodeData& src) { + // This method updates one node in the tree based on serialized data + // received from the renderer. + + // Create a set of child ids in |src| for fast lookup. If a duplicate id is + // found, exit now with a fatal error before changing anything else. + std::set<int32> new_child_ids; + for (size_t i = 0; i < src.child_ids.size(); ++i) { + if (new_child_ids.find(src.child_ids[i]) != new_child_ids.end()) + return false; + new_child_ids.insert(src.child_ids[i]); + } + + // Look up the node by id. If it's not found, then either the root + // of the tree is being swapped, or we're out of sync with the renderer + // and this is a serious error. + BrowserAccessibility* instance = GetFromRendererID(src.id); + if (!instance) { + if (src.role != AccessibilityNodeData::ROLE_ROOT_WEB_AREA) + return false; + instance = CreateNode(NULL, src.id, 0); + } + + if (src.bool_attributes.find( + AccessibilityNodeData::ATTR_UPDATE_LOCATION_ONLY) != + src.bool_attributes.end()) { + instance->SetLocation(src.location); + return true; + } + + // Update all of the node-specific data, like its role, state, name, etc. + instance->InitializeData(src); + + // + // Update the children in three steps: + // + // 1. Iterate over the old children and delete nodes that are no longer + // in the tree. + // 2. Build up a vector of new children, reusing children that haven't + // changed (but may have been reordered) and adding new empty + // objects for new children. + // 3. Swap in the new children vector for the old one. + + // Delete any previous children of this instance that are no longer + // children first. We make a deletion-only pass first to prevent a + // node that's being reparented from being the child of both its old + // parent and new parent, which could lead to a double-free. + // If a node is reparented, the renderer will always send us a fresh + // copy of the node. + const std::vector<BrowserAccessibility*>& old_children = instance->children(); + for (size_t i = 0; i < old_children.size(); ++i) { + int old_id = old_children[i]->renderer_id(); + if (new_child_ids.find(old_id) == new_child_ids.end()) + old_children[i]->Destroy(); + } + + // Now build a vector of new children, reusing objects that were already + // children of this node before. + std::vector<BrowserAccessibility*> new_children; + bool success = true; + for (size_t i = 0; i < src.child_ids.size(); i++) { + int32 child_renderer_id = src.child_ids[i]; + int32 index_in_parent = static_cast<int32>(i); + BrowserAccessibility* child = GetFromRendererID(child_renderer_id); + if (child) { + if (child->parent() != instance) { + // This is a serious error - nodes should never be reparented. + // If this case occurs, continue so this node isn't left in an + // inconsistent state, but return failure at the end. + success = false; + continue; + } + child->UpdateParent(instance, index_in_parent); + } else { + child = CreateNode(instance, child_renderer_id, index_in_parent); + } + new_children.push_back(child); + } + + // Finally, swap in the new children vector for the old. + instance->SwapChildren(new_children); + + // Handle the case where this node is the new root of the tree. + if (src.role == AccessibilityNodeData::ROLE_ROOT_WEB_AREA && + (!root_ || root_->renderer_id() != src.id)) { + if (root_) + root_->Destroy(); + if (focus_ == root_) + SetFocus(instance, false); + SetRoot(instance); + } + + // Keep track of what node is focused. + 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; +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager.h b/chromium/content/browser/accessibility/browser_accessibility_manager.h new file mode 100644 index 00000000000..5fac3e5e3f3 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager.h @@ -0,0 +1,236 @@ +// Copyright (c) 2012 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_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_ + +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "content/common/accessibility_node_data.h" +#include "content/common/content_export.h" +#include "ui/gfx/native_widget_types.h" + +struct AccessibilityHostMsg_NotificationParams; + +namespace content { +class BrowserAccessibility; +#if defined(OS_WIN) +class BrowserAccessibilityManagerWin; +#endif + +// Class that can perform actions on behalf of the BrowserAccessibilityManager. +class CONTENT_EXPORT BrowserAccessibilityDelegate { + public: + virtual ~BrowserAccessibilityDelegate() {} + virtual void SetAccessibilityFocus(int acc_obj_id) = 0; + virtual void AccessibilityDoDefaultAction(int acc_obj_id) = 0; + virtual void AccessibilityScrollToMakeVisible( + int acc_obj_id, gfx::Rect subfocus) = 0; + virtual void AccessibilityScrollToPoint( + int acc_obj_id, gfx::Point point) = 0; + virtual void AccessibilitySetTextSelection( + int acc_obj_id, int start_offset, int end_offset) = 0; + virtual bool HasFocus() const = 0; + virtual gfx::Rect GetViewBounds() const = 0; + virtual gfx::Point GetLastTouchEventLocation() const = 0; + virtual void FatalAccessibilityTreeError() = 0; +}; + +class CONTENT_EXPORT BrowserAccessibilityFactory { + public: + virtual ~BrowserAccessibilityFactory() {} + + // Create an instance of BrowserAccessibility and return a new + // reference to it. + virtual BrowserAccessibility* Create(); +}; + +// Manages a tree of BrowserAccessibility objects. +class CONTENT_EXPORT BrowserAccessibilityManager { + public: + // Creates the platform-specific BrowserAccessibilityManager, but + // with no parent window pointer. Only useful for unit tests. + static BrowserAccessibilityManager* Create( + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory()); + + virtual ~BrowserAccessibilityManager(); + + void Initialize(const AccessibilityNodeData src); + + static AccessibilityNodeData GetEmptyDocument(); + + // Type is enum AccessibilityNotification. + // We pass it as int so that we don't include the message declaration + // header here. + virtual void NotifyAccessibilityEvent( + int type, + BrowserAccessibility* node) { } + + // Return a pointer to the root of the tree, does not make a new reference. + BrowserAccessibility* GetRoot(); + + // Removes a node from the manager. + virtual void RemoveNode(BrowserAccessibility* node); + + // Return a pointer to the object corresponding to the given renderer_id, + // does not make a new reference. + BrowserAccessibility* GetFromRendererID(int32 renderer_id); + + // Called to notify the accessibility manager that its associated native + // view got focused. This implies that it is shown (opposite of WasHidden, + // below). + // The touch_event_context parameter indicates that we were called in the + // context of a touch event. + void GotFocus(bool touch_event_context); + + // Called to notify the accessibility manager that its associated native + // view was hidden. When it's no longer hidden, GotFocus will be called. + void WasHidden(); + + // Called to notify the accessibility manager that a mouse down event + // occurred in the tab. + void GotMouseDown(); + + // Update the focused node to |node|, which may be null. + // If |notify| is true, send a message to the renderer to set focus + // to this node. + void SetFocus(BrowserAccessibility* node, bool notify); + + // Tell the renderer to do the default action for this node. + void DoDefaultAction(const BrowserAccessibility& node); + + // Tell the renderer to scroll to make |node| visible. + // In addition, if it's not possible to make the entire object visible, + // scroll so that the |subfocus| rect is visible at least. The subfocus + // rect is in local coordinates of the object itself. + void ScrollToMakeVisible( + const BrowserAccessibility& node, gfx::Rect subfocus); + + // Tell the renderer to scroll such that |node| is at |point|, + // where |point| is in global coordinates of the WebContents. + void ScrollToPoint( + const BrowserAccessibility& node, gfx::Point point); + + // Tell the renderer to set the text selection on a node. + void SetTextSelection( + const BrowserAccessibility& node, int start_offset, int end_offset); + + // Retrieve the bounds of the parent View in screen coordinates. + gfx::Rect GetViewBounds(); + + // Called when the renderer process has notified us of about tree changes. + // Send a notification to MSAA clients of the change. + void OnAccessibilityNotifications( + const std::vector<AccessibilityHostMsg_NotificationParams>& params); + +#if defined(OS_WIN) + BrowserAccessibilityManagerWin* ToBrowserAccessibilityManagerWin(); +#endif + + // Return the object that has focus, if it's a descandant of the + // given root (inclusive). Does not make a new reference. + BrowserAccessibility* GetFocus(BrowserAccessibility* root); + + // Is the on-screen keyboard allowed to be shown, in response to a + // 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 + // each time. + void UpdateNodesForTesting( + const AccessibilityNodeData& node, + const AccessibilityNodeData& node2 = AccessibilityNodeData(), + const AccessibilityNodeData& node3 = AccessibilityNodeData(), + const AccessibilityNodeData& node4 = AccessibilityNodeData(), + const AccessibilityNodeData& node5 = AccessibilityNodeData(), + const AccessibilityNodeData& node6 = AccessibilityNodeData(), + const AccessibilityNodeData& node7 = AccessibilityNodeData()); + + protected: + BrowserAccessibilityManager( + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory); + + BrowserAccessibilityManager( + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory); + + 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. + enum OnScreenKeyboardState { + // Never show the on-screen keyboard because this tab is hidden. + OSK_DISALLOWED_BECAUSE_TAB_HIDDEN, + + // This tab was just shown, so don't pop-up the on-screen keyboard if a + // text field gets focus that wasn't the result of an explicit touch. + OSK_DISALLOWED_BECAUSE_TAB_JUST_APPEARED, + + // A touch event has occurred within the window, but focus has not + // explicitly changed. Allow the on-screen keyboard to be shown if the + // touch event was within the bounds of the currently focused object. + // Otherwise we'll just wait to see if focus changes. + OSK_ALLOWED_WITHIN_FOCUSED_OBJECT, + + // Focus has changed within a tab that's already visible. Allow the + // on-screen keyboard to show anytime that a touch event leads to an + // editable text control getting focus. + OSK_ALLOWED + }; + + // Update a set of nodes using data received from the renderer + // process. + bool UpdateNodes(const std::vector<AccessibilityNodeData>& nodes); + + // Update one node from the tree using data received from the renderer + // 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, + int32 index_in_parent); + + protected: + // The object that can perform actions on our behalf. + BrowserAccessibilityDelegate* delegate_; + + // Factory to create BrowserAccessibility objects (for dependency injection). + scoped_ptr<BrowserAccessibilityFactory> factory_; + + // The root of the tree of accessible objects and the element that + // currently has focus, if any. + BrowserAccessibility* root_; + BrowserAccessibility* focus_; + + // The on-screen keyboard state. + OnScreenKeyboardState osk_state_; + + // A mapping from renderer IDs to BrowserAccessibility objects. + base::hash_map<int32, BrowserAccessibility*> renderer_id_map_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManager); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc new file mode 100644 index 00000000000..e14aa0bb977 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_android.cc @@ -0,0 +1,366 @@ +// 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/strings/utf_string_conversions.h" +#include "base/values.h" +#include "content/browser/accessibility/browser_accessibility_android.h" +#include "content/common/accessibility_messages.h" +#include "jni/BrowserAccessibilityManager_jni.h" + +using base::android::AttachCurrentThread; +using base::android::ScopedJavaLocalRef; + +namespace { + +// These are enums from android.view.accessibility.AccessibilityEvent in Java: +enum { + ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED = 16, + ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192 +}; + +// 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; + + JNIEnv* env = AttachCurrentThread(); + java_ref_ = JavaObjectWeakGlobalRef( + env, Java_BrowserAccessibilityManager_create( + env, reinterpret_cast<jint>(this), content_view_core.obj()).obj()); +} + +BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj()); +} + +// 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 = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + switch (type) { + case AccessibilityNotificationLoadComplete: + Java_BrowserAccessibilityManager_handlePageLoaded( + env, obj.obj(), focus_->renderer_id()); + break; + case AccessibilityNotificationFocusChanged: + Java_BrowserAccessibilityManager_handleFocusChanged( + env, obj.obj(), node->renderer_id()); + break; + case AccessibilityNotificationCheckStateChanged: + Java_BrowserAccessibilityManager_handleCheckStateChanged( + env, obj.obj(), node->renderer_id()); + break; + case AccessibilityNotificationScrolledToAnchor: + Java_BrowserAccessibilityManager_handleScrolledToAnchor( + env, obj.obj(), node->renderer_id()); + break; + case AccessibilityNotificationAlert: + // An alert is a special case of live region. Fall through to the + // next case to handle it. + case AccessibilityNotificationObjectShow: { + // This event is fired when an object appears in a live region. + // Speak its text. + BrowserAccessibilityAndroid* android_node = + static_cast<BrowserAccessibilityAndroid*>(node); + Java_BrowserAccessibilityManager_announceLiveRegionText( + env, obj.obj(), + base::android::ConvertUTF16ToJavaString( + env, android_node->GetText()).obj()); + break; + } + case AccessibilityNotificationSelectedTextChanged: + Java_BrowserAccessibilityManager_handleTextSelectionChanged( + env, obj.obj(), node->renderer_id()); + break; + case AccessibilityNotificationChildrenChanged: + case AccessibilityNotificationTextChanged: + case AccessibilityNotificationValueChanged: + if (node->IsEditableText()) { + Java_BrowserAccessibilityManager_handleEditableTextChanged( + env, obj.obj(), node->renderer_id()); + } else { + Java_BrowserAccessibilityManager_handleContentChanged( + env, obj.obj(), node->renderer_id()); + } + break; + default: + // There are some notifications that aren't meaningful on Android. + // It's okay to skip them. + break; + } +} + +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(); +} + +jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo( + JNIEnv* env, jobject obj, jobject info, jint id) { + BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>( + GetFromRendererID(id)); + if (!node) + return false; + + if (node->parent()) { + Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent( + env, obj, info, node->parent()->renderer_id()); + } + if (!node->IsLeaf()) { + for (unsigned i = 0; i < node->child_count(); ++i) { + Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild( + env, obj, info, node->children()[i]->renderer_id()); + } + } + Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes( + env, obj, info, + id, + node->IsCheckable(), + node->IsChecked(), + node->IsClickable(), + node->IsEnabled(), + node->IsFocusable(), + node->IsFocused(), + node->IsPassword(), + node->IsScrollable(), + node->IsSelected(), + node->IsVisibleToUser()); + Java_BrowserAccessibilityManager_setAccessibilityNodeInfoStringAttributes( + env, obj, info, + base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj(), + base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj()); + + gfx::Rect absolute_rect = node->GetLocalBoundsRect(); + gfx::Rect parent_relative_rect = absolute_rect; + if (node->parent()) { + gfx::Rect parent_rect = node->parent()->GetLocalBoundsRect(); + parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin()); + } + bool is_root = node->parent() == NULL; + Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation( + env, obj, info, + absolute_rect.x(), absolute_rect.y(), + parent_relative_rect.x(), parent_relative_rect.y(), + absolute_rect.width(), absolute_rect.height(), + is_root); + + return true; +} + +jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent( + JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) { + BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>( + GetFromRendererID(id)); + if (!node) + return false; + + Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes( + env, obj, event, + node->IsChecked(), + node->IsEnabled(), + node->IsPassword(), + node->IsScrollable()); + Java_BrowserAccessibilityManager_setAccessibilityEventClassName( + env, obj, event, + base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj()); + Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes( + env, obj, event, + node->GetItemIndex(), + node->GetItemCount()); + Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes( + env, obj, event, + node->GetScrollX(), + node->GetScrollY(), + node->GetMaxScrollX(), + node->GetMaxScrollY()); + + switch (event_type) { + case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED: + Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs( + env, obj, event, + node->GetTextChangeFromIndex(), + node->GetTextChangeAddedCount(), + node->GetTextChangeRemovedCount(), + base::android::ConvertUTF16ToJavaString( + env, node->GetTextChangeBeforeText()).obj(), + base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj()); + break; + case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED: + Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs( + env, obj, event, + node->GetSelectionStart(), + node->GetSelectionEnd(), + node->GetEditableTextLength(), + base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj()); + break; + default: + break; + } + + return true; +} + +void BrowserAccessibilityManagerAndroid::Click( + JNIEnv* env, jobject obj, jint id) { + BrowserAccessibility* node = GetFromRendererID(id); + if (node) + DoDefaultAction(*node); +} + +void BrowserAccessibilityManagerAndroid::Focus( + JNIEnv* env, jobject obj, jint id) { + BrowserAccessibility* node = GetFromRendererID(id); + if (node) + SetFocus(node, true); +} + +void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) { + SetFocus(root_, true); +} + +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->GetText().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; +} + +void BrowserAccessibilityManagerAndroid::NotifyRootChanged() { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj()); +} + +bool +BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() { + // The Java layer handles the root scroll offset. + return false; +} + +bool RegisterBrowserAccessibilityManager(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_android.h b/chromium/content/browser/accessibility/browser_accessibility_manager_android.h new file mode 100644 index 00000000000..2a6c291bacb --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_android.h @@ -0,0 +1,92 @@ +// 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); + + // Populate Java accessibility data structures with info about a node. + jboolean PopulateAccessibilityNodeInfo( + JNIEnv* env, jobject obj, jobject info, jint id); + jboolean PopulateAccessibilityEvent( + JNIEnv* env, jobject obj, jobject event, jint id, jint event_type); + + // Perform actions. + void Click(JNIEnv* env, jobject obj, jint id); + void Focus(JNIEnv* env, jobject obj, jint id); + void Blur(JNIEnv* env, jobject obj); + + 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_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.cc new file mode 100644 index 00000000000..15191c6a35b --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2012 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_gtk.h" + +#include "content/browser/accessibility/browser_accessibility_gtk.h" +#include "content/common/accessibility_messages.h" + +namespace content { + +// static +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) { + return new BrowserAccessibilityManagerGtk( + NULL, + src, + delegate, + factory); +} + +BrowserAccessibilityManagerGtk::BrowserAccessibilityManagerGtk( + GtkWidget* parent_widget, + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : BrowserAccessibilityManager(delegate, factory), + parent_widget_(parent_widget) { + Initialize(src); +} + +BrowserAccessibilityManagerGtk::~BrowserAccessibilityManagerGtk() { +} + +// static +AccessibilityNodeData BrowserAccessibilityManagerGtk::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 BrowserAccessibilityManagerGtk::NotifyAccessibilityEvent( + int type, + BrowserAccessibility* node) { + if (!node->IsNative()) + return; + AtkObject* atk_object = node->ToBrowserAccessibilityGtk()->GetAtkObject(); + + switch (type) { + case AccessibilityNotificationChildrenChanged: + RecursivelySendChildrenChanged(GetRoot()->ToBrowserAccessibilityGtk()); + break; + case AccessibilityNotificationFocusChanged: + // Note: atk_focus_tracker_notify may be deprecated in the future; + // follow this bug for the replacement: + // https://bugzilla.gnome.org/show_bug.cgi?id=649575#c4 + g_signal_emit_by_name(atk_object, "focus-event", true); + atk_focus_tracker_notify(atk_object); + break; + default: + break; + } +} + +void BrowserAccessibilityManagerGtk::RecursivelySendChildrenChanged( + BrowserAccessibilityGtk* node) { + AtkObject* atkObject = node->ToBrowserAccessibilityGtk()->GetAtkObject(); + for (unsigned int i = 0; i < node->children().size(); ++i) { + BrowserAccessibilityGtk* child = + node->children()[i]->ToBrowserAccessibilityGtk(); + g_signal_emit_by_name(atkObject, + "children-changed::add", + i, + child->GetAtkObject()); + RecursivelySendChildrenChanged(child); + } +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.h b/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.h new file mode 100644 index 00000000000..eedae7ca121 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_gtk.h @@ -0,0 +1,48 @@ +// Copyright (c) 2012 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_GTK_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_GTK_H_ + +#include "content/browser/accessibility/browser_accessibility_manager.h" + +struct ViewHostMsg_AccessibilityNotification_Params; + +namespace content { +class BrowserAccessibilityGtk; + +// Manages a tree of BrowserAccessibilityGtk objects. +class CONTENT_EXPORT BrowserAccessibilityManagerGtk + : public BrowserAccessibilityManager { + public: + BrowserAccessibilityManagerGtk( + GtkWidget* parent_widget, + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory()); + + virtual ~BrowserAccessibilityManagerGtk(); + + static AccessibilityNodeData GetEmptyDocument(); + + // BrowserAccessibilityManager methods + virtual void NotifyAccessibilityEvent(int type, BrowserAccessibility* node) + OVERRIDE; + + GtkWidget* parent_widget() { return parent_widget_; } + + private: + void RecursivelySendChildrenChanged(BrowserAccessibilityGtk* node); + + GtkWidget* parent_widget_; + + // Give BrowserAccessibilityManager::Create access to our constructor. + friend class BrowserAccessibilityManager; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerGtk); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_GTK_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_mac.h b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.h new file mode 100644 index 00000000000..d94c2c73df8 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.h @@ -0,0 +1,43 @@ +// Copyright (c) 2012 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_MAC_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_MAC_H_ + +#import <Cocoa/Cocoa.h> + +#include "content/browser/accessibility/browser_accessibility_manager.h" + +namespace content { + +class CONTENT_EXPORT BrowserAccessibilityManagerMac + : public BrowserAccessibilityManager { + public: + BrowserAccessibilityManagerMac( + NSView* parent_view, + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory()); + + static AccessibilityNodeData GetEmptyDocument(); + + // Implementation of BrowserAccessibilityManager. + virtual void NotifyAccessibilityEvent(int type, + BrowserAccessibility* node) OVERRIDE; + + NSView* parent_view() { return parent_view_; } + + private: + // This gives BrowserAccessibilityManager::Create access to the class + // constructor. + friend class BrowserAccessibilityManager; + + NSView* parent_view_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerMac); +}; + +} + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_MAC_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm new file mode 100644 index 00000000000..e3280997a25 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_mac.mm @@ -0,0 +1,121 @@ +// Copyright (c) 2012 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_mac.h" + +#import "base/logging.h" +#import "content/browser/accessibility/browser_accessibility_cocoa.h" +#include "content/common/accessibility_messages.h" + +namespace content { + +// static +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) { + return new BrowserAccessibilityManagerMac(NULL, src, delegate, factory); +} + +BrowserAccessibilityManagerMac::BrowserAccessibilityManagerMac( + NSView* parent_view, + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : BrowserAccessibilityManager(src, delegate, factory), + parent_view_(parent_view) { +} + +// static +AccessibilityNodeData BrowserAccessibilityManagerMac::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 BrowserAccessibilityManagerMac::NotifyAccessibilityEvent( + int type, + BrowserAccessibility* node) { + if (!node->IsNative()) + return; + + // Refer to AXObjectCache.mm (webkit). + NSString* event_id = @""; + switch (type) { + case AccessibilityNotificationActiveDescendantChanged: + if (node->role() == AccessibilityNodeData::ROLE_TREE) + event_id = NSAccessibilitySelectedRowsChangedNotification; + else + event_id = NSAccessibilityFocusedUIElementChangedNotification; + break; + case AccessibilityNotificationAlert: + // Not used on Mac. + return; + case AccessibilityNotificationBlur: + // A no-op on Mac. + return; + case AccessibilityNotificationCheckStateChanged: + // Not used on Mac. + return; + case AccessibilityNotificationChildrenChanged: + // TODO(dtseng): no clear equivalent on Mac. + return; + case AccessibilityNotificationFocusChanged: + event_id = NSAccessibilityFocusedUIElementChangedNotification; + break; + case AccessibilityNotificationLayoutComplete: + event_id = @"AXLayoutComplete"; + break; + case AccessibilityNotificationLiveRegionChanged: + event_id = @"AXLiveRegionChanged"; + break; + case AccessibilityNotificationLoadComplete: + event_id = @"AXLoadComplete"; + break; + case AccessibilityNotificationMenuListValueChanged: + // Not used on Mac. + return; + case AccessibilityNotificationObjectShow: + // Not used on Mac. + return; + case AccessibilityNotificationObjectHide: + // Not used on Mac. + return; + case AccessibilityNotificationRowCountChanged: + event_id = NSAccessibilityRowCountChangedNotification; + break; + case AccessibilityNotificationRowCollapsed: + event_id = @"AXRowCollapsed"; + break; + case AccessibilityNotificationRowExpanded: + event_id = @"AXRowExpanded"; + break; + case AccessibilityNotificationScrolledToAnchor: + // Not used on Mac. + return; + case AccessibilityNotificationSelectedChildrenChanged: + event_id = NSAccessibilitySelectedChildrenChangedNotification; + break; + case AccessibilityNotificationSelectedTextChanged: + event_id = NSAccessibilitySelectedTextChangedNotification; + break; + case AccessibilityNotificationTextInserted: + // Not used on Mac. + return; + case AccessibilityNotificationTextRemoved: + // Not used on Mac. + return; + case AccessibilityNotificationValueChanged: + event_id = NSAccessibilityValueChangedNotification; + break; + } + BrowserAccessibilityCocoa* native_node = node->ToBrowserAccessibilityCocoa(); + DCHECK(native_node); + NSAccessibilityPostNotification(native_node, event_id); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc new file mode 100644 index 00000000000..5cb03cd25ce --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_unittest.cc @@ -0,0 +1,603 @@ +// Copyright (c) 2012 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 "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/common/accessibility_messages.h" +#include "content/common/accessibility_node_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { +namespace { + +// Subclass of BrowserAccessibility that counts the number of instances. +class CountedBrowserAccessibility : public BrowserAccessibility { + public: + CountedBrowserAccessibility() { + global_obj_count_++; + native_ref_count_ = 1; + } + virtual ~CountedBrowserAccessibility() { + global_obj_count_--; + } + + virtual void NativeAddReference() OVERRIDE { + native_ref_count_++; + } + + virtual void NativeReleaseReference() OVERRIDE { + native_ref_count_--; + if (native_ref_count_ == 0) + delete this; + } + + int native_ref_count_; + static int global_obj_count_; +}; + +int CountedBrowserAccessibility::global_obj_count_ = 0; + +// Factory that creates a CountedBrowserAccessibility. +class CountedBrowserAccessibilityFactory + : public BrowserAccessibilityFactory { + public: + virtual ~CountedBrowserAccessibilityFactory() {} + virtual BrowserAccessibility* Create() OVERRIDE { + return new CountedBrowserAccessibility(); + } +}; + +class TestBrowserAccessibilityDelegate + : public BrowserAccessibilityDelegate { + public: + TestBrowserAccessibilityDelegate() + : got_fatal_error_(false) {} + + virtual void SetAccessibilityFocus(int acc_obj_id) OVERRIDE {} + virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE {} + virtual void AccessibilityScrollToMakeVisible( + int acc_obj_id, gfx::Rect subfocus) OVERRIDE {} + virtual void AccessibilityScrollToPoint( + int acc_obj_id, gfx::Point point) OVERRIDE {} + virtual void AccessibilitySetTextSelection( + int acc_obj_id, int start_offset, int end_offset) OVERRIDE {} + virtual bool HasFocus() const OVERRIDE { + return false; + } + virtual gfx::Rect GetViewBounds() const OVERRIDE { + return gfx::Rect(); + } + virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE { + return gfx::Point(); + } + virtual void FatalAccessibilityTreeError() OVERRIDE { + got_fatal_error_ = true; + } + + bool got_fatal_error() const { return got_fatal_error_; } + void reset_got_fatal_error() { got_fatal_error_ = false; } + +private: + bool got_fatal_error_; +}; + +} // anonymous namespace + +TEST(BrowserAccessibilityManagerTest, TestNoLeaks) { + // Create AccessibilityNodeData objects for a simple document tree, + // representing the accessibility information used to initialize + // BrowserAccessibilityManager. + AccessibilityNodeData button; + button.id = 2; + button.name = UTF8ToUTF16("Button"); + button.role = AccessibilityNodeData::ROLE_BUTTON; + button.state = 0; + + AccessibilityNodeData checkbox; + checkbox.id = 3; + checkbox.name = UTF8ToUTF16("Checkbox"); + checkbox.role = AccessibilityNodeData::ROLE_CHECKBOX; + checkbox.state = 0; + + AccessibilityNodeData root; + root.id = 1; + root.name = UTF8ToUTF16("Document"); + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.state = 0; + root.child_ids.push_back(2); + root.child_ids.push_back(3); + + // Construct a BrowserAccessibilityManager with this + // AccessibilityNodeData tree and a factory for an instance-counting + // BrowserAccessibility, and ensure that exactly 3 instances were + // created. Note that the manager takes ownership of the factory. + CountedBrowserAccessibility::global_obj_count_ = 0; + BrowserAccessibilityManager* manager = + BrowserAccessibilityManager::Create( + root, + NULL, + new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(button, checkbox); + + ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); + + // Delete the manager and test that all 3 instances are deleted. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); + + // Construct a manager again, and this time save references to two of + // the three nodes in the tree. + manager = + BrowserAccessibilityManager::Create( + root, + NULL, + new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(button, checkbox); + ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); + + CountedBrowserAccessibility* root_accessible = + static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); + root_accessible->NativeAddReference(); + CountedBrowserAccessibility* child1_accessible = + static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(1)); + child1_accessible->NativeAddReference(); + + // Now delete the manager, and only one of the three nodes in the tree + // should be released. + delete manager; + ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_); + + // Release each of our references and make sure that each one results in + // the instance being deleted as its reference count hits zero. + root_accessible->NativeReleaseReference(); + ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_); + child1_accessible->NativeReleaseReference(); + ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); +} + +TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) { + // Make sure that changes to a subtree reuse as many objects as possible. + + // Tree 1: + // + // root + // child1 + // child2 + // child3 + + AccessibilityNodeData tree1_child1; + tree1_child1.id = 2; + tree1_child1.name = UTF8ToUTF16("Child1"); + tree1_child1.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_child1.state = 0; + + AccessibilityNodeData tree1_child2; + tree1_child2.id = 3; + tree1_child2.name = UTF8ToUTF16("Child2"); + tree1_child2.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_child2.state = 0; + + AccessibilityNodeData tree1_child3; + tree1_child3.id = 4; + tree1_child3.name = UTF8ToUTF16("Child3"); + tree1_child3.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_child3.state = 0; + + AccessibilityNodeData tree1_root; + tree1_root.id = 1; + tree1_root.name = UTF8ToUTF16("Document"); + tree1_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + tree1_root.state = 0; + tree1_root.child_ids.push_back(2); + tree1_root.child_ids.push_back(3); + tree1_root.child_ids.push_back(4); + + // Tree 2: + // + // root + // child0 <-- inserted + // child1 + // child2 + // <-- child3 deleted + + AccessibilityNodeData tree2_child0; + tree2_child0.id = 5; + tree2_child0.name = UTF8ToUTF16("Child0"); + tree2_child0.role = AccessibilityNodeData::ROLE_BUTTON; + tree2_child0.state = 0; + + AccessibilityNodeData tree2_root; + tree2_root.id = 1; + tree2_root.name = UTF8ToUTF16("DocumentChanged"); + tree2_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + tree2_root.state = 0; + tree2_root.child_ids.push_back(5); + tree2_root.child_ids.push_back(2); + tree2_root.child_ids.push_back(3); + + // Construct a BrowserAccessibilityManager with tree1. + CountedBrowserAccessibility::global_obj_count_ = 0; + BrowserAccessibilityManager* manager = + BrowserAccessibilityManager::Create( + tree1_root, + NULL, + new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(tree1_child1, tree1_child2, tree1_child3); + ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); + + // Save references to all of the objects. + CountedBrowserAccessibility* root_accessible = + static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); + root_accessible->NativeAddReference(); + CountedBrowserAccessibility* child1_accessible = + static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(0)); + child1_accessible->NativeAddReference(); + CountedBrowserAccessibility* child2_accessible = + static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(1)); + child2_accessible->NativeAddReference(); + CountedBrowserAccessibility* child3_accessible = + static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(2)); + child3_accessible->NativeAddReference(); + + // Check the index in parent. + EXPECT_EQ(0, child1_accessible->index_in_parent()); + EXPECT_EQ(1, child2_accessible->index_in_parent()); + EXPECT_EQ(2, child3_accessible->index_in_parent()); + + // Process a notification containing the changed subtree. + std::vector<AccessibilityHostMsg_NotificationParams> params; + params.push_back(AccessibilityHostMsg_NotificationParams()); + AccessibilityHostMsg_NotificationParams* msg = ¶ms[0]; + msg->notification_type = AccessibilityNotificationChildrenChanged; + msg->nodes.push_back(tree2_root); + msg->nodes.push_back(tree2_child0); + msg->id = tree2_root.id; + manager->OnAccessibilityNotifications(params); + + // There should be 5 objects now: the 4 from the new tree, plus the + // reference to child3 we kept. + EXPECT_EQ(5, CountedBrowserAccessibility::global_obj_count_); + + // Check that our references to the root, child1, and child2 are still valid, + // but that the reference to child3 is now invalid. + EXPECT_TRUE(root_accessible->instance_active()); + EXPECT_TRUE(child1_accessible->instance_active()); + EXPECT_TRUE(child2_accessible->instance_active()); + EXPECT_FALSE(child3_accessible->instance_active()); + + // Check that the index in parent has been updated. + EXPECT_EQ(1, child1_accessible->index_in_parent()); + EXPECT_EQ(2, child2_accessible->index_in_parent()); + + // Release our references. The object count should only decrease by 1 + // for child3. + root_accessible->NativeReleaseReference(); + child1_accessible->NativeReleaseReference(); + child2_accessible->NativeReleaseReference(); + child3_accessible->NativeReleaseReference(); + + EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_); + + // Delete the manager and make sure all memory is cleaned up. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); +} + +TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { + // Similar to the test above, but with a more complicated tree. + + // Tree 1: + // + // root + // container + // child1 + // grandchild1 + // child2 + // grandchild2 + // child3 + // grandchild3 + + AccessibilityNodeData tree1_grandchild1; + tree1_grandchild1.id = 4; + tree1_grandchild1.name = UTF8ToUTF16("GrandChild1"); + tree1_grandchild1.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_grandchild1.state = 0; + + AccessibilityNodeData tree1_child1; + tree1_child1.id = 3; + tree1_child1.name = UTF8ToUTF16("Child1"); + tree1_child1.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_child1.state = 0; + tree1_child1.child_ids.push_back(4); + + AccessibilityNodeData tree1_grandchild2; + tree1_grandchild2.id = 6; + tree1_grandchild2.name = UTF8ToUTF16("GrandChild1"); + tree1_grandchild2.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_grandchild2.state = 0; + + AccessibilityNodeData tree1_child2; + tree1_child2.id = 5; + tree1_child2.name = UTF8ToUTF16("Child2"); + tree1_child2.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_child2.state = 0; + tree1_child2.child_ids.push_back(6); + + AccessibilityNodeData tree1_grandchild3; + tree1_grandchild3.id = 8; + tree1_grandchild3.name = UTF8ToUTF16("GrandChild3"); + tree1_grandchild3.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_grandchild3.state = 0; + + AccessibilityNodeData tree1_child3; + tree1_child3.id = 7; + tree1_child3.name = UTF8ToUTF16("Child3"); + tree1_child3.role = AccessibilityNodeData::ROLE_BUTTON; + tree1_child3.state = 0; + tree1_child3.child_ids.push_back(8); + + AccessibilityNodeData tree1_container; + tree1_container.id = 2; + tree1_container.name = UTF8ToUTF16("Container"); + tree1_container.role = AccessibilityNodeData::ROLE_GROUP; + tree1_container.state = 0; + tree1_container.child_ids.push_back(3); + tree1_container.child_ids.push_back(5); + tree1_container.child_ids.push_back(7); + + AccessibilityNodeData tree1_root; + tree1_root.id = 1; + tree1_root.name = UTF8ToUTF16("Document"); + tree1_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + tree1_root.state = 0; + tree1_root.child_ids.push_back(2); + + // Tree 2: + // + // root + // container + // child0 <-- inserted + // grandchild0 <-- + // child1 + // grandchild1 + // child2 + // grandchild2 + // <-- child3 (and grandchild3) deleted + + AccessibilityNodeData tree2_grandchild0; + tree2_grandchild0.id = 9; + tree2_grandchild0.name = UTF8ToUTF16("GrandChild0"); + tree2_grandchild0.role = AccessibilityNodeData::ROLE_BUTTON; + tree2_grandchild0.state = 0; + + AccessibilityNodeData tree2_child0; + tree2_child0.id = 10; + tree2_child0.name = UTF8ToUTF16("Child0"); + tree2_child0.role = AccessibilityNodeData::ROLE_BUTTON; + tree2_child0.state = 0; + tree2_child0.child_ids.push_back(9); + + AccessibilityNodeData tree2_container; + tree2_container.id = 2; + tree2_container.name = UTF8ToUTF16("Container"); + tree2_container.role = AccessibilityNodeData::ROLE_GROUP; + tree2_container.state = 0; + tree2_container.child_ids.push_back(10); + tree2_container.child_ids.push_back(3); + tree2_container.child_ids.push_back(5); + + // Construct a BrowserAccessibilityManager with tree1. + CountedBrowserAccessibility::global_obj_count_ = 0; + BrowserAccessibilityManager* manager = + BrowserAccessibilityManager::Create( + tree1_root, + NULL, + new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(tree1_container, + tree1_child1, tree1_grandchild1, + tree1_child2, tree1_grandchild2, + tree1_child3, tree1_grandchild3); + ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_); + + // Save references to some objects. + CountedBrowserAccessibility* root_accessible = + static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); + root_accessible->NativeAddReference(); + CountedBrowserAccessibility* container_accessible = + static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(0)); + container_accessible->NativeAddReference(); + CountedBrowserAccessibility* child2_accessible = + static_cast<CountedBrowserAccessibility*>( + container_accessible->GetChild(1)); + child2_accessible->NativeAddReference(); + CountedBrowserAccessibility* child3_accessible = + static_cast<CountedBrowserAccessibility*>( + container_accessible->GetChild(2)); + child3_accessible->NativeAddReference(); + + // Check the index in parent. + EXPECT_EQ(1, child2_accessible->index_in_parent()); + EXPECT_EQ(2, child3_accessible->index_in_parent()); + + // Process a notification containing the changed subtree rooted at + // the container. + std::vector<AccessibilityHostMsg_NotificationParams> params; + params.push_back(AccessibilityHostMsg_NotificationParams()); + AccessibilityHostMsg_NotificationParams* msg = ¶ms[0]; + msg->notification_type = AccessibilityNotificationChildrenChanged; + msg->nodes.push_back(tree2_container); + msg->nodes.push_back(tree2_child0); + msg->nodes.push_back(tree2_grandchild0); + msg->id = tree2_container.id; + manager->OnAccessibilityNotifications(params); + + // There should be 9 objects now: the 8 from the new tree, plus the + // reference to child3 we kept. + EXPECT_EQ(9, CountedBrowserAccessibility::global_obj_count_); + + // Check that our references to the root and container and child2 are + // still valid, but that the reference to child3 is now invalid. + EXPECT_TRUE(root_accessible->instance_active()); + EXPECT_TRUE(container_accessible->instance_active()); + EXPECT_TRUE(child2_accessible->instance_active()); + EXPECT_FALSE(child3_accessible->instance_active()); + + // Ensure that we retain the parent of the detached subtree. + EXPECT_EQ(root_accessible, container_accessible->parent()); + EXPECT_EQ(0, container_accessible->index_in_parent()); + + // Check that the index in parent has been updated. + EXPECT_EQ(2, child2_accessible->index_in_parent()); + + // Release our references. The object count should only decrease by 1 + // for child3. + root_accessible->NativeReleaseReference(); + container_accessible->NativeReleaseReference(); + child2_accessible->NativeReleaseReference(); + child3_accessible->NativeReleaseReference(); + + EXPECT_EQ(8, CountedBrowserAccessibility::global_obj_count_); + + // Delete the manager and make sure all memory is cleaned up. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); +} + +TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) { + // Tree 1: + // + // 1 + // 2 + // 3 + // 4 + + AccessibilityNodeData tree1_4; + tree1_4.id = 4; + tree1_4.state = 0; + + AccessibilityNodeData tree1_3; + tree1_3.id = 3; + tree1_3.state = 0; + tree1_3.child_ids.push_back(4); + + AccessibilityNodeData tree1_2; + tree1_2.id = 2; + tree1_2.state = 0; + + AccessibilityNodeData tree1_1; + tree1_1.id = 1; + tree1_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + tree1_1.state = 0; + tree1_1.child_ids.push_back(2); + tree1_1.child_ids.push_back(3); + + // Tree 2: + // + // 1 + // 4 <-- moves up a level and gains child + // 6 <-- new + // 5 <-- new + + AccessibilityNodeData tree2_6; + tree2_6.id = 6; + tree2_6.state = 0; + + AccessibilityNodeData tree2_5; + tree2_5.id = 5; + tree2_5.state = 0; + + AccessibilityNodeData tree2_4; + tree2_4.id = 4; + tree2_4.state = 0; + tree2_4.child_ids.push_back(6); + + AccessibilityNodeData tree2_1; + tree2_1.id = 1; + tree2_1.state = 0; + tree2_1.child_ids.push_back(4); + tree2_1.child_ids.push_back(5); + + // Construct a BrowserAccessibilityManager with tree1. + CountedBrowserAccessibility::global_obj_count_ = 0; + BrowserAccessibilityManager* manager = + BrowserAccessibilityManager::Create( + tree1_1, + NULL, + new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(tree1_2, tree1_3, tree1_4); + ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); + + // Process a notification containing the changed subtree. + std::vector<AccessibilityHostMsg_NotificationParams> params; + params.push_back(AccessibilityHostMsg_NotificationParams()); + AccessibilityHostMsg_NotificationParams* msg = ¶ms[0]; + msg->notification_type = AccessibilityNotificationChildrenChanged; + msg->nodes.push_back(tree2_1); + msg->nodes.push_back(tree2_4); + msg->nodes.push_back(tree2_5); + msg->nodes.push_back(tree2_6); + msg->id = tree2_1.id; + manager->OnAccessibilityNotifications(params); + + // There should be 4 objects now. + EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_); + + // Delete the manager and make sure all memory is cleaned up. + delete manager; + ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); +} + +TEST(BrowserAccessibilityManagerTest, TestFatalError) { + // Test that BrowserAccessibilityManager raises a fatal error + // (which will crash the renderer) if the same id is used in + // two places in the tree. + + AccessibilityNodeData root; + root.id = 1; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.child_ids.push_back(2); + root.child_ids.push_back(2); + + CountedBrowserAccessibilityFactory* factory = + new CountedBrowserAccessibilityFactory(); + scoped_ptr<TestBrowserAccessibilityDelegate> delegate( + new TestBrowserAccessibilityDelegate()); + scoped_ptr<BrowserAccessibilityManager> manager; + ASSERT_FALSE(delegate->got_fatal_error()); + manager.reset(BrowserAccessibilityManager::Create( + root, + delegate.get(), + factory)); + ASSERT_TRUE(delegate->got_fatal_error()); + + AccessibilityNodeData root2; + root2.id = 1; + root2.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root2.child_ids.push_back(2); + root2.child_ids.push_back(3); + + AccessibilityNodeData child1; + child1.id = 2; + child1.child_ids.push_back(4); + child1.child_ids.push_back(5); + + AccessibilityNodeData child2; + child2.id = 3; + child2.child_ids.push_back(6); + child2.child_ids.push_back(5); // Duplicate + + delegate->reset_got_fatal_error(); + factory = new CountedBrowserAccessibilityFactory(); + manager.reset(BrowserAccessibilityManager::Create( + root2, + delegate.get(), + factory)); + ASSERT_FALSE(delegate->got_fatal_error()); + manager->UpdateNodesForTesting(child1, child2); + ASSERT_TRUE(delegate->got_fatal_error()); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc b/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc new file mode 100644 index 00000000000..b956d218d33 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_win.cc @@ -0,0 +1,203 @@ +// Copyright (c) 2012 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_win.h" + +#include "content/browser/accessibility/browser_accessibility_win.h" +#include "content/common/accessibility_messages.h" + +namespace content { + +// static +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) { + return new BrowserAccessibilityManagerWin( + GetDesktopWindow(), NULL, src, delegate, factory); +} + +BrowserAccessibilityManagerWin* +BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() { + return static_cast<BrowserAccessibilityManagerWin*>(this); +} + +BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin( + HWND parent_hwnd, + IAccessible* parent_iaccessible, + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : BrowserAccessibilityManager(src, delegate, factory), + parent_hwnd_(parent_hwnd), + parent_iaccessible_(parent_iaccessible), + tracked_scroll_object_(NULL) { +} + +BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() { + if (tracked_scroll_object_) { + tracked_scroll_object_->Release(); + tracked_scroll_object_ = NULL; + } +} + +// static +AccessibilityNodeData BrowserAccessibilityManagerWin::GetEmptyDocument() { + AccessibilityNodeData empty_document; + empty_document.id = 0; + empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + empty_document.state = + (1 << AccessibilityNodeData::STATE_READONLY) | + (1 << AccessibilityNodeData::STATE_BUSY); + return empty_document; +} + +void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event, + LONG child_id) { + if (parent_iaccessible()) + ::NotifyWinEvent(event, parent_hwnd(), OBJID_CLIENT, child_id); +} + +void BrowserAccessibilityManagerWin::AddNodeToMap(BrowserAccessibility* node) { + BrowserAccessibilityManager::AddNodeToMap(node); + LONG unique_id_win = node->ToBrowserAccessibilityWin()->unique_id_win(); + unique_id_to_renderer_id_map_[unique_id_win] = node->renderer_id(); +} + +void BrowserAccessibilityManagerWin::RemoveNode(BrowserAccessibility* node) { + unique_id_to_renderer_id_map_.erase( + node->ToBrowserAccessibilityWin()->unique_id_win()); + BrowserAccessibilityManager::RemoveNode(node); + if (node == tracked_scroll_object_) { + tracked_scroll_object_->Release(); + tracked_scroll_object_ = NULL; + } +} + +void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent( + int type, + BrowserAccessibility* node) { + LONG event_id = EVENT_MIN; + switch (type) { + case AccessibilityNotificationActiveDescendantChanged: + event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED; + break; + case AccessibilityNotificationAlert: + event_id = EVENT_SYSTEM_ALERT; + break; + case AccessibilityNotificationAriaAttributeChanged: + event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED; + break; + case AccessibilityNotificationAutocorrectionOccurred: + event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED; + break; + case AccessibilityNotificationBlur: + // Equivalent to focus on the root. + event_id = EVENT_OBJECT_FOCUS; + node = GetRoot(); + break; + case AccessibilityNotificationCheckStateChanged: + event_id = EVENT_OBJECT_STATECHANGE; + break; + case AccessibilityNotificationChildrenChanged: + event_id = EVENT_OBJECT_REORDER; + break; + case AccessibilityNotificationFocusChanged: + event_id = EVENT_OBJECT_FOCUS; + break; + case AccessibilityNotificationInvalidStatusChanged: + event_id = EVENT_OBJECT_STATECHANGE; + break; + case AccessibilityNotificationLiveRegionChanged: + // TODO: try not firing a native notification at all, since + // on Windows, each individual item in a live region that changes + // already gets its own notification. + event_id = EVENT_OBJECT_REORDER; + break; + case AccessibilityNotificationLoadComplete: + event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE; + break; + case AccessibilityNotificationMenuListItemSelected: + event_id = EVENT_OBJECT_FOCUS; + break; + case AccessibilityNotificationMenuListValueChanged: + event_id = EVENT_OBJECT_VALUECHANGE; + break; + case AccessibilityNotificationObjectHide: + event_id = EVENT_OBJECT_HIDE; + break; + case AccessibilityNotificationObjectShow: + event_id = EVENT_OBJECT_SHOW; + break; + case AccessibilityNotificationScrolledToAnchor: + event_id = EVENT_SYSTEM_SCROLLINGSTART; + break; + case AccessibilityNotificationSelectedChildrenChanged: + event_id = EVENT_OBJECT_SELECTIONWITHIN; + break; + case AccessibilityNotificationSelectedTextChanged: + event_id = IA2_EVENT_TEXT_CARET_MOVED; + break; + case AccessibilityNotificationTextChanged: + event_id = EVENT_OBJECT_NAMECHANGE; + break; + case AccessibilityNotificationTextInserted: + event_id = IA2_EVENT_TEXT_INSERTED; + break; + case AccessibilityNotificationTextRemoved: + event_id = IA2_EVENT_TEXT_REMOVED; + break; + case AccessibilityNotificationValueChanged: + event_id = EVENT_OBJECT_VALUECHANGE; + break; + default: + // Not all WebKit accessibility events result in a Windows + // accessibility notification. + break; + } + + if (event_id != EVENT_MIN) { + // Pass the node's unique id in the |child_id| argument to NotifyWinEvent; + // the AT client will then call get_accChild on the HWND's accessibility + // object and pass it that same id, which we can use to retrieve the + // IAccessible for this node. + LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win(); + MaybeCallNotifyWinEvent(event_id, child_id); + } + + // If this is a layout complete notification (sent when a container scrolls) + // and there is a descendant tracked object, send a notification on it. + // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed. + if (type == AccessibilityNotificationLayoutComplete && + tracked_scroll_object_ && + tracked_scroll_object_->IsDescendantOf(node)) { + MaybeCallNotifyWinEvent( + IA2_EVENT_VISIBLE_DATA_CHANGED, + tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win()); + tracked_scroll_object_->Release(); + tracked_scroll_object_ = NULL; + } +} + +void BrowserAccessibilityManagerWin::TrackScrollingObject( + BrowserAccessibilityWin* node) { + if (tracked_scroll_object_) + tracked_scroll_object_->Release(); + tracked_scroll_object_ = node; + tracked_scroll_object_->AddRef(); +} + +BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin( + LONG unique_id_win) { + base::hash_map<LONG, int32>::iterator iter = + unique_id_to_renderer_id_map_.find(unique_id_win); + if (iter != unique_id_to_renderer_id_map_.end()) { + BrowserAccessibility* result = GetFromRendererID(iter->second); + if (result) + return result->ToBrowserAccessibilityWin(); + } + return NULL; +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_manager_win.h b/chromium/content/browser/accessibility/browser_accessibility_manager_win.h new file mode 100644 index 00000000000..7aec2e1c5e9 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_manager_win.h @@ -0,0 +1,82 @@ +// Copyright (c) 2012 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_WIN_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_ + +#include <oleacc.h> + +#include "base/win/scoped_comptr.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" + +namespace content { +class BrowserAccessibilityWin; + +// Manages a tree of BrowserAccessibilityWin objects. +class CONTENT_EXPORT BrowserAccessibilityManagerWin + : public BrowserAccessibilityManager { + public: + BrowserAccessibilityManagerWin( + HWND parent_hwnd, + IAccessible* parent_iaccessible, + const AccessibilityNodeData& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory()); + + virtual ~BrowserAccessibilityManagerWin(); + + static AccessibilityNodeData GetEmptyDocument(); + + // Get the closest containing HWND. + HWND parent_hwnd() { return parent_hwnd_; } + + // The IAccessible for the parent window. + IAccessible* parent_iaccessible() { return parent_iaccessible_; } + void set_parent_iaccessible(IAccessible* parent_iaccessible) { + parent_iaccessible_ = parent_iaccessible; + } + + // Calls NotifyWinEvent if the parent window's IAccessible pointer is known. + void MaybeCallNotifyWinEvent(DWORD event, LONG child_id); + + // BrowserAccessibilityManager methods + virtual void AddNodeToMap(BrowserAccessibility* node); + virtual void RemoveNode(BrowserAccessibility* node) OVERRIDE; + virtual void NotifyAccessibilityEvent(int type, BrowserAccessibility* node) + OVERRIDE; + + // Track this object and post a VISIBLE_DATA_CHANGED notification when + // its container scrolls. + // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed. + void TrackScrollingObject(BrowserAccessibilityWin* node); + + // Return a pointer to the object corresponding to the given windows-specific + // unique id, does not make a new reference. + BrowserAccessibilityWin* GetFromUniqueIdWin(LONG unique_id_win); + + private: + // The closest ancestor HWND. + HWND parent_hwnd_; + + // The accessibility instance for the parent window. + IAccessible* parent_iaccessible_; + + // Give BrowserAccessibilityManager::Create access to our constructor. + friend class BrowserAccessibilityManager; + + // Track the most recent object that has been asked to scroll and + // post a notification directly on it when it reaches its destination. + // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed. + BrowserAccessibilityWin* tracked_scroll_object_; + + // A mapping from the Windows-specific unique IDs (unique within the + // browser process) to renderer ids within this page. + base::hash_map<long, int32> unique_id_to_renderer_id_map_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerWin); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc b/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc new file mode 100644 index 00000000000..befd717bc0c --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_state_impl.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2012 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_state_impl.h" + +#include "base/command_line.h" +#include "base/metrics/histogram.h" +#include "base/timer/timer.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/common/content_switches.h" +#include "ui/gfx/sys_color_change_listener.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace content { + +// Update the accessibility histogram 45 seconds after initialization. +static const int kAccessibilityHistogramDelaySecs = 45; + +// static +BrowserAccessibilityState* BrowserAccessibilityState::GetInstance() { + return BrowserAccessibilityStateImpl::GetInstance(); +} + +// static +BrowserAccessibilityStateImpl* BrowserAccessibilityStateImpl::GetInstance() { + return Singleton<BrowserAccessibilityStateImpl, + LeakySingletonTraits<BrowserAccessibilityStateImpl> >::get(); +} + +BrowserAccessibilityStateImpl::BrowserAccessibilityStateImpl() + : BrowserAccessibilityState(), + accessibility_mode_(AccessibilityModeOff) { +#if defined(OS_WIN) + // On Windows 8, always enable accessibility for editable text controls + // so we can show the virtual keyboard when one is enabled. + if (base::win::GetVersion() >= base::win::VERSION_WIN8 && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableRendererAccessibility)) { + accessibility_mode_ = AccessibilityModeEditableTextOnly; + } +#endif // defined(OS_WIN) + + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kForceRendererAccessibility)) { + accessibility_mode_ = AccessibilityModeComplete; + } + +#if defined(OS_WIN) + // On Windows, UpdateHistograms calls some system functions with unknown + // runtime, so call it on the file thread to ensure there's no jank. + // Everything in that method must be safe to call on another thread. + BrowserThread::ID update_histogram_thread = BrowserThread::FILE; +#else + // On all other platforms, UpdateHistograms should be called on the main + // thread. + BrowserThread::ID update_histogram_thread = BrowserThread::UI; +#endif + + // We need to AddRef() the leaky singleton so that Bind doesn't + // delete it prematurely. + AddRef(); + BrowserThread::PostDelayedTask( + update_histogram_thread, FROM_HERE, + base::Bind(&BrowserAccessibilityStateImpl::UpdateHistograms, this), + base::TimeDelta::FromSeconds(kAccessibilityHistogramDelaySecs)); +} + +BrowserAccessibilityStateImpl::~BrowserAccessibilityStateImpl() { +} + +void BrowserAccessibilityStateImpl::OnScreenReaderDetected() { + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableRendererAccessibility)) { + return; + } + SetAccessibilityMode(AccessibilityModeComplete); +} + +void BrowserAccessibilityStateImpl::EnableAccessibility() { + // We may want to do something different with this later. + SetAccessibilityMode(AccessibilityModeComplete); +} + +void BrowserAccessibilityStateImpl::DisableAccessibility() { + SetAccessibilityMode(AccessibilityModeOff); +} + +bool BrowserAccessibilityStateImpl::IsAccessibleBrowser() { + return (accessibility_mode_ == AccessibilityModeComplete); +} + +void BrowserAccessibilityStateImpl::AddHistogramCallback( + base::Closure callback) { + histogram_callbacks_.push_back(callback); +} + +void BrowserAccessibilityStateImpl::UpdateHistogramsForTesting() { + UpdateHistograms(); +} + +void BrowserAccessibilityStateImpl::UpdateHistograms() { + UpdatePlatformSpecificHistograms(); + + for (size_t i = 0; i < histogram_callbacks_.size(); ++i) + histogram_callbacks_[i].Run(); + + UMA_HISTOGRAM_BOOLEAN("Accessibility.State", IsAccessibleBrowser()); + UMA_HISTOGRAM_BOOLEAN("Accessibility.InvertedColors", + gfx::IsInvertedColorScheme()); + UMA_HISTOGRAM_BOOLEAN("Accessibility.ManuallyEnabled", + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kForceRendererAccessibility)); +} + +#if !defined(OS_WIN) +void BrowserAccessibilityStateImpl::UpdatePlatformSpecificHistograms() { +} +#endif + +void BrowserAccessibilityStateImpl::SetAccessibilityMode( + AccessibilityMode mode) { + if (accessibility_mode_ == mode) + return; + accessibility_mode_ = mode; + + // Iterate over all RenderWidgetHosts, even swapped out ones in case + // they become active again. + RenderWidgetHost::List widgets = + RenderWidgetHostImpl::GetAllRenderWidgetHosts(); + for (size_t i = 0; i < widgets.size(); ++i) { + // Ignore processes that don't have a connection, such as crashed tabs. + if (!widgets[i]->GetProcess()->HasConnection()) + continue; + if (!widgets[i]->IsRenderView()) + continue; + + RenderWidgetHostImpl::From(widgets[i])->SetAccessibilityMode(mode); + } +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_state_impl.h b/chromium/content/browser/accessibility/browser_accessibility_state_impl.h new file mode 100644 index 00000000000..3098341ef25 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_state_impl.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012 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_STATE_IMPL_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_IMPL_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/singleton.h" +#include "content/common/view_message_enums.h" +#include "content/public/browser/browser_accessibility_state.h" + +namespace content { + +// The BrowserAccessibilityState class is used to determine if Chrome should be +// customized for users with assistive technology, such as screen readers. We +// modify the behavior of certain user interfaces to provide a better experience +// for screen reader users. The way we detect a screen reader program is +// different for each platform. +// +// Screen Reader Detection +// (1) On windows many screen reader detection mechinisms will give false +// positives like relying on the SPI_GETSCREENREADER system parameter. In Chrome +// we attempt to dynamically detect a MSAA client screen reader by calling +// NotifiyWinEvent in NativeWidgetWin with a custom ID and wait to see if the ID +// is requested by a subsequent call to WM_GETOBJECT. +// (2) On mac we detect dynamically if VoiceOver is running. We rely upon the +// undocumented accessibility attribute @"AXEnhancedUserInterface" which is set +// when VoiceOver is launched and unset when VoiceOver is closed. This is an +// improvement over reading defaults preference values (which has no callback +// mechanism). +class CONTENT_EXPORT BrowserAccessibilityStateImpl + : public base::RefCountedThreadSafe<BrowserAccessibilityStateImpl>, + public BrowserAccessibilityState { + public: + BrowserAccessibilityStateImpl(); + + static BrowserAccessibilityStateImpl* GetInstance(); + + virtual void EnableAccessibility() OVERRIDE; + virtual void DisableAccessibility() OVERRIDE; + virtual void OnScreenReaderDetected() OVERRIDE; + virtual bool IsAccessibleBrowser() OVERRIDE; + virtual void AddHistogramCallback(base::Closure callback) OVERRIDE; + + virtual void UpdateHistogramsForTesting() OVERRIDE; + + AccessibilityMode accessibility_mode() const { return accessibility_mode_; }; + void SetAccessibilityMode(AccessibilityMode mode); + + private: + friend class base::RefCountedThreadSafe<BrowserAccessibilityStateImpl>; + friend struct DefaultSingletonTraits<BrowserAccessibilityStateImpl>; + + // Called a short while after startup to allow time for the accessibility + // state to be determined. Updates histograms with the current state. + void UpdateHistograms(); + + // Leaky singleton, destructor generally won't be called. + virtual ~BrowserAccessibilityStateImpl(); + + void UpdatePlatformSpecificHistograms(); + + AccessibilityMode accessibility_mode_; + + std::vector<base::Closure> histogram_callbacks_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityStateImpl); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_IMPL_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_state_impl_win.cc b/chromium/content/browser/accessibility/browser_accessibility_state_impl_win.cc new file mode 100644 index 00000000000..824055f6e5d --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_state_impl_win.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2012 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_state_impl.h" + +#include <windows.h> +#include <psapi.h> + +#include "base/files/file_path.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_util.h" + +namespace content { + +void BrowserAccessibilityStateImpl::UpdatePlatformSpecificHistograms() { + // NOTE: this method is run from the file thread to reduce jank, since + // there's no guarantee these system calls will return quickly. Be careful + // not to add any code that isn't safe to run from a non-main thread! + + AUDIODESCRIPTION audio_description = {0}; + audio_description.cbSize = sizeof(AUDIODESCRIPTION); + SystemParametersInfo(SPI_GETAUDIODESCRIPTION, 0, &audio_description, 0); + UMA_HISTOGRAM_BOOLEAN("Accessibility.WinAudioDescription", + !!audio_description.Enabled); + + BOOL win_screen_reader = FALSE; + SystemParametersInfo(SPI_GETSCREENREADER, 0, &win_screen_reader, 0); + UMA_HISTOGRAM_BOOLEAN("Accessibility.WinScreenReader", + !!win_screen_reader); + + STICKYKEYS sticky_keys = {0}; + sticky_keys.cbSize = sizeof(STICKYKEYS); + SystemParametersInfo(SPI_GETSTICKYKEYS, 0, &sticky_keys, 0); + UMA_HISTOGRAM_BOOLEAN("Accessibility.WinStickyKeys", + 0 != (sticky_keys.dwFlags & SKF_STICKYKEYSON)); + + // Get the file paths of all DLLs loaded. + HANDLE process = GetCurrentProcess(); + HMODULE* modules = NULL; + DWORD bytes_required; + if (!EnumProcessModules(process, modules, 0, &bytes_required)) + return; + + scoped_ptr<char[]> buffer(new char[bytes_required]); + modules = reinterpret_cast<HMODULE*>(buffer.get()); + DWORD ignore; + if (!EnumProcessModules(process, modules, bytes_required, &ignore)) + return; + + // Look for DLLs of assistive technology known to work with Chrome. + bool jaws = false; + bool nvda = false; + bool satogo = false; + bool zoomtext = false; + size_t module_count = bytes_required / sizeof(HMODULE); + for (size_t i = 0; i < module_count; i++) { + TCHAR filename[MAX_PATH]; + GetModuleFileName(modules[i], filename, sizeof(filename)); + string16 module_name(base::FilePath(filename).BaseName().value()); + if (LowerCaseEqualsASCII(module_name, "fsdomsrv.dll")) + jaws = true; + if (LowerCaseEqualsASCII(module_name, "vbufbackend_gecko_ia2.dll")) + nvda = true; + if (LowerCaseEqualsASCII(module_name, "stsaw32.dll")) + satogo = true; + if (LowerCaseEqualsASCII(module_name, "zslhook.dll")) + zoomtext = true; + } + + UMA_HISTOGRAM_BOOLEAN("Accessibility.WinJAWS", jaws); + UMA_HISTOGRAM_BOOLEAN("Accessibility.WinNVDA", nvda); + UMA_HISTOGRAM_BOOLEAN("Accessibility.WinSAToGo", satogo); + UMA_HISTOGRAM_BOOLEAN("Accessibility.WinZoomText", zoomtext); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_win.cc b/chromium/content/browser/accessibility/browser_accessibility_win.cc new file mode 100644 index 00000000000..792adc4440d --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_win.cc @@ -0,0 +1,3626 @@ +// Copyright (c) 2012 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_win.h" + +#include <UIAutomationClient.h> +#include <UIAutomationCoreApi.h> + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/enum_variant.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" +#include "content/browser/accessibility/browser_accessibility_manager_win.h" +#include "content/common/accessibility_messages.h" +#include "content/public/common/content_client.h" +#include "ui/base/accessibility/accessible_text_utils.h" +#include "ui/base/win/accessibility_ids_win.h" +#include "ui/base/win/accessibility_misc_utils.h" + +namespace content { + +// These nonstandard GUIDs are taken directly from the Mozilla sources +// (accessible/src/msaa/nsAccessNodeWrap.cpp); some documentation is here: +// http://developer.mozilla.org/en/Accessibility/AT-APIs/ImplementationFeatures/MSAA +const GUID GUID_ISimpleDOM = { + 0x0c539790, 0x12e4, 0x11cf, + 0xb6, 0x61, 0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8}; +const GUID GUID_IAccessibleContentDocument = { + 0xa5d8e1f3, 0x3571, 0x4d8f, + 0x95, 0x21, 0x07, 0xed, 0x28, 0xfb, 0x07, 0x2e}; + +const char16 BrowserAccessibilityWin::kEmbeddedCharacter[] = L"\xfffc"; + +// static +LONG BrowserAccessibilityWin::next_unique_id_win_ = + base::win::kFirstBrowserAccessibilityManagerAccessibilityId; + +// +// BrowserAccessibilityRelation +// +// A simple implementation of IAccessibleRelation, used to represent +// a relationship between two accessible nodes in the tree. +// + +class BrowserAccessibilityRelation + : public CComObjectRootEx<CComMultiThreadModel>, + public IAccessibleRelation { + BEGIN_COM_MAP(BrowserAccessibilityRelation) + COM_INTERFACE_ENTRY(IAccessibleRelation) + END_COM_MAP() + + CONTENT_EXPORT BrowserAccessibilityRelation() {} + CONTENT_EXPORT virtual ~BrowserAccessibilityRelation() {} + + CONTENT_EXPORT void Initialize(BrowserAccessibilityWin* owner, + const string16& type); + CONTENT_EXPORT void AddTarget(int target_id); + + // IAccessibleRelation methods. + CONTENT_EXPORT STDMETHODIMP get_relationType(BSTR* relation_type); + CONTENT_EXPORT STDMETHODIMP get_nTargets(long* n_targets); + CONTENT_EXPORT STDMETHODIMP get_target(long target_index, IUnknown** target); + CONTENT_EXPORT STDMETHODIMP get_targets(long max_targets, + IUnknown** targets, + long* n_targets); + + // IAccessibleRelation methods not implemented. + CONTENT_EXPORT STDMETHODIMP get_localizedRelationType(BSTR* relation_type) { + return E_NOTIMPL; + } + + private: + string16 type_; + base::win::ScopedComPtr<BrowserAccessibilityWin> owner_; + std::vector<int> target_ids_; +}; + +void BrowserAccessibilityRelation::Initialize(BrowserAccessibilityWin* owner, + const string16& type) { + owner_ = owner; + type_ = type; +} + +void BrowserAccessibilityRelation::AddTarget(int target_id) { + target_ids_.push_back(target_id); +} + +STDMETHODIMP BrowserAccessibilityRelation::get_relationType( + BSTR* relation_type) { + if (!relation_type) + return E_INVALIDARG; + + if (!owner_->instance_active()) + return E_FAIL; + + *relation_type = SysAllocString(type_.c_str()); + DCHECK(*relation_type); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityRelation::get_nTargets(long* n_targets) { + if (!n_targets) + return E_INVALIDARG; + + if (!owner_->instance_active()) + return E_FAIL; + + *n_targets = static_cast<long>(target_ids_.size()); + + BrowserAccessibilityManager* manager = owner_->manager(); + for (long i = *n_targets - 1; i >= 0; --i) { + BrowserAccessibility* result = manager->GetFromRendererID(target_ids_[i]); + if (!result || !result->instance_active()) { + *n_targets = 0; + break; + } + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityRelation::get_target(long target_index, + IUnknown** target) { + if (!target) + return E_INVALIDARG; + + if (!owner_->instance_active()) + return E_FAIL; + + if (target_index < 0 || + target_index >= static_cast<long>(target_ids_.size())) { + return E_INVALIDARG; + } + + BrowserAccessibilityManager* manager = owner_->manager(); + BrowserAccessibility* result = + manager->GetFromRendererID(target_ids_[target_index]); + if (!result || !result->instance_active()) + return E_FAIL; + + *target = static_cast<IAccessible*>( + result->ToBrowserAccessibilityWin()->NewReference()); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityRelation::get_targets(long max_targets, + IUnknown** targets, + long* n_targets) { + if (!targets || !n_targets) + return E_INVALIDARG; + + if (!owner_->instance_active()) + return E_FAIL; + + long count = static_cast<long>(target_ids_.size()); + if (count > max_targets) + count = max_targets; + + *n_targets = count; + if (count == 0) + return S_FALSE; + + for (long i = 0; i < count; ++i) { + HRESULT result = get_target(i, &targets[i]); + if (result != S_OK) + return result; + } + + return S_OK; +} + +// +// BrowserAccessibilityWin +// + +// static +BrowserAccessibility* BrowserAccessibility::Create() { + CComObject<BrowserAccessibilityWin>* instance; + HRESULT hr = CComObject<BrowserAccessibilityWin>::CreateInstance(&instance); + DCHECK(SUCCEEDED(hr)); + return instance->NewReference(); +} + +BrowserAccessibilityWin* BrowserAccessibility::ToBrowserAccessibilityWin() { + return static_cast<BrowserAccessibilityWin*>(this); +} + +BrowserAccessibilityWin::BrowserAccessibilityWin() + : ia_role_(0), + ia_state_(0), + ia2_role_(0), + ia2_state_(0), + first_time_(true), + old_ia_state_(0) { + // Start unique IDs at -1 and decrement each time, because get_accChild + // uses positive IDs to enumerate children, so we use negative IDs to + // clearly distinguish between indices and unique IDs. + unique_id_win_ = next_unique_id_win_; + if (next_unique_id_win_ == + base::win::kLastBrowserAccessibilityManagerAccessibilityId) { + next_unique_id_win_ = + base::win::kFirstBrowserAccessibilityManagerAccessibilityId; + } + next_unique_id_win_--; +} + +BrowserAccessibilityWin::~BrowserAccessibilityWin() { + for (size_t i = 0; i < relations_.size(); ++i) + relations_[i]->Release(); +} + +// +// IAccessible methods. +// +// Conventions: +// * Always test for instance_active_ first and return E_FAIL if it's false. +// * Always check for invalid arguments first, even if they're unused. +// * Return S_FALSE if the only output is a string argument and it's empty. +// + +HRESULT BrowserAccessibilityWin::accDoDefaultAction(VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + manager_->DoDefaultAction(*target); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accHitTest(LONG x_left, + LONG y_top, + VARIANT* child) { + if (!instance_active_) + return E_FAIL; + + if (!child) + return E_INVALIDARG; + + gfx::Point point(x_left, y_top); + if (!GetGlobalBoundsRect().Contains(point)) { + // Return S_FALSE and VT_EMPTY when the outside the object's boundaries. + child->vt = VT_EMPTY; + return S_FALSE; + } + + BrowserAccessibility* result = BrowserAccessibilityForPoint(point); + if (result == this) { + // Point is within this object. + child->vt = VT_I4; + child->lVal = CHILDID_SELF; + } else { + child->vt = VT_DISPATCH; + child->pdispVal = result->ToBrowserAccessibilityWin()->NewReference(); + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accLocation(LONG* x_left, + LONG* y_top, + LONG* width, + LONG* height, + VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + if (!x_left || !y_top || !width || !height) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + gfx::Rect bounds = target->GetGlobalBoundsRect(); + *x_left = bounds.x(); + *y_top = bounds.y(); + *width = bounds.width(); + *height = bounds.height(); + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accNavigate(LONG nav_dir, + VARIANT start, + VARIANT* end) { + BrowserAccessibilityWin* target = GetTargetFromChildID(start); + if (!target) + return E_INVALIDARG; + + if ((nav_dir == NAVDIR_LASTCHILD || nav_dir == NAVDIR_FIRSTCHILD) && + start.lVal != CHILDID_SELF) { + // MSAA states that navigating to first/last child can only be from self. + return E_INVALIDARG; + } + + BrowserAccessibility* result = NULL; + switch (nav_dir) { + case NAVDIR_DOWN: + case NAVDIR_UP: + case NAVDIR_LEFT: + case NAVDIR_RIGHT: + // These directions are not implemented, matching Mozilla and IE. + return E_NOTIMPL; + case NAVDIR_FIRSTCHILD: + if (!target->children_.empty()) + result = target->children_.front(); + break; + case NAVDIR_LASTCHILD: + if (!target->children_.empty()) + result = target->children_.back(); + break; + case NAVDIR_NEXT: + result = target->GetNextSibling(); + break; + case NAVDIR_PREVIOUS: + result = target->GetPreviousSibling(); + break; + } + + if (!result) { + end->vt = VT_EMPTY; + return S_FALSE; + } + + end->vt = VT_DISPATCH; + end->pdispVal = result->ToBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accChild(VARIANT var_child, + IDispatch** disp_child) { + if (!instance_active_) + return E_FAIL; + + if (!disp_child) + return E_INVALIDARG; + + *disp_child = NULL; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_child); + if (!target) + return E_INVALIDARG; + + (*disp_child) = target->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accChildCount(LONG* child_count) { + if (!instance_active_) + return E_FAIL; + + if (!child_count) + return E_INVALIDARG; + + *child_count = children_.size(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accDefaultAction(VARIANT var_id, + BSTR* def_action) { + if (!instance_active_) + return E_FAIL; + + if (!def_action) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_SHORTCUT, def_action); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accDescription(VARIANT var_id, + BSTR* desc) { + if (!instance_active_) + return E_FAIL; + + if (!desc) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_DESCRIPTION, desc); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accFocus(VARIANT* focus_child) { + if (!instance_active_) + return E_FAIL; + + if (!focus_child) + return E_INVALIDARG; + + BrowserAccessibilityWin* focus = static_cast<BrowserAccessibilityWin*>( + manager_->GetFocus(this)); + if (focus == this) { + focus_child->vt = VT_I4; + focus_child->lVal = CHILDID_SELF; + } else if (focus == NULL) { + focus_child->vt = VT_EMPTY; + } else { + focus_child->vt = VT_DISPATCH; + focus_child->pdispVal = focus->NewReference(); + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accHelp(VARIANT var_id, BSTR* help) { + if (!instance_active_) + return E_FAIL; + + if (!help) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_HELP, help); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accKeyboardShortcut(VARIANT var_id, + BSTR* acc_key) { + if (!instance_active_) + return E_FAIL; + + if (!acc_key) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_SHORTCUT, acc_key); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accName(VARIANT var_id, BSTR* name) { + if (!instance_active_) + return E_FAIL; + + if (!name) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + string16 name_str = target->name_; + + // If the name is empty, see if it's labeled by another element. + if (name_str.empty()) { + int title_elem_id; + if (target->GetIntAttribute(AccessibilityNodeData::ATTR_TITLE_UI_ELEMENT, + &title_elem_id)) { + BrowserAccessibility* title_elem = + manager_->GetFromRendererID(title_elem_id); + if (title_elem) + name_str = title_elem->GetTextRecursive(); + } + } + + if (name_str.empty()) + return S_FALSE; + + *name = SysAllocString(name_str.c_str()); + + DCHECK(*name); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accParent(IDispatch** disp_parent) { + if (!instance_active_) + return E_FAIL; + + if (!disp_parent) + return E_INVALIDARG; + + IAccessible* parent = parent_->ToBrowserAccessibilityWin(); + if (parent == NULL) { + // This happens if we're the root of the tree; + // return the IAccessible for the window. + parent = manager_->ToBrowserAccessibilityManagerWin()->parent_iaccessible(); + // |parent| can only be NULL if the manager was created before the parent + // IAccessible was known and it wasn't subsequently set before a client + // requested it. Crash hard if this happens so that we get crash reports. + CHECK(parent); + } + + parent->AddRef(); + *disp_parent = parent; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accRole(VARIANT var_id, + VARIANT* role) { + if (!instance_active_) + return E_FAIL; + + if (!role) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + if (!target->role_name_.empty()) { + role->vt = VT_BSTR; + role->bstrVal = SysAllocString(target->role_name_.c_str()); + } else { + role->vt = VT_I4; + role->lVal = target->ia_role_; + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accState(VARIANT var_id, + VARIANT* state) { + if (!instance_active_) + return E_FAIL; + + if (!state) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + state->vt = VT_I4; + state->lVal = target->ia_state_; + if (manager_->GetFocus(NULL) == this) + state->lVal |= STATE_SYSTEM_FOCUSED; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accValue(VARIANT var_id, + BSTR* value) { + if (!instance_active_) + return E_FAIL; + + if (!value) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + *value = SysAllocString(target->value_.c_str()); + + DCHECK(*value); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accHelpTopic(BSTR* help_file, + VARIANT var_id, + LONG* topic_id) { + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accSelection(VARIANT* selected) { + if (!instance_active_) + return E_FAIL; + + if (role_ != AccessibilityNodeData::ROLE_LISTBOX) + return E_NOTIMPL; + + unsigned long selected_count = 0; + for (size_t i = 0; i < children_.size(); ++i) { + if (children_[i]->HasState(AccessibilityNodeData::STATE_SELECTED)) + ++selected_count; + } + + if (selected_count == 0) { + selected->vt = VT_EMPTY; + return S_OK; + } + + if (selected_count == 1) { + for (size_t i = 0; i < children_.size(); ++i) { + if (children_[i]->HasState(AccessibilityNodeData::STATE_SELECTED)) { + selected->vt = VT_DISPATCH; + selected->pdispVal = + children_[i]->ToBrowserAccessibilityWin()->NewReference(); + return S_OK; + } + } + } + + // Multiple items are selected. + base::win::EnumVariant* enum_variant = + new base::win::EnumVariant(selected_count); + enum_variant->AddRef(); + unsigned long index = 0; + for (size_t i = 0; i < children_.size(); ++i) { + if (children_[i]->HasState(AccessibilityNodeData::STATE_SELECTED)) { + enum_variant->ItemAt(index)->vt = VT_DISPATCH; + enum_variant->ItemAt(index)->pdispVal = + children_[i]->ToBrowserAccessibilityWin()->NewReference(); + ++index; + } + } + selected->vt = VT_UNKNOWN; + selected->punkVal = static_cast<IUnknown*>( + static_cast<base::win::IUnknownImpl*>(enum_variant)); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accSelect( + LONG flags_sel, VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + if (flags_sel & SELFLAG_TAKEFOCUS) { + manager_->SetFocus(this, true); + return S_OK; + } + + return S_FALSE; +} + +// +// IAccessible2 methods. +// + +STDMETHODIMP BrowserAccessibilityWin::role(LONG* role) { + if (!instance_active_) + return E_FAIL; + + if (!role) + return E_INVALIDARG; + + *role = ia2_role_; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributes(BSTR* attributes) { + if (!instance_active_) + return E_FAIL; + + if (!attributes) + return E_INVALIDARG; + + // The iaccessible2 attributes are a set of key-value pairs + // separated by semicolons, with a colon between the key and the value. + string16 str; + for (unsigned int i = 0; i < ia2_attributes_.size(); ++i) { + if (i != 0) + str += L';'; + str += ia2_attributes_[i]; + } + + if (str.empty()) + return S_FALSE; + + *attributes = SysAllocString(str.c_str()); + DCHECK(*attributes); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_states(AccessibleStates* states) { + if (!instance_active_) + return E_FAIL; + + if (!states) + return E_INVALIDARG; + + *states = ia2_state_; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_uniqueID(LONG* unique_id) { + if (!instance_active_) + return E_FAIL; + + if (!unique_id) + return E_INVALIDARG; + + *unique_id = unique_id_win_; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_windowHandle(HWND* window_handle) { + if (!instance_active_) + return E_FAIL; + + if (!window_handle) + return E_INVALIDARG; + + *window_handle = manager_->ToBrowserAccessibilityManagerWin()->parent_hwnd(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_indexInParent(LONG* index_in_parent) { + if (!instance_active_) + return E_FAIL; + + if (!index_in_parent) + return E_INVALIDARG; + + *index_in_parent = index_in_parent_; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nRelations(LONG* n_relations) { + if (!instance_active_) + return E_FAIL; + + if (!n_relations) + return E_INVALIDARG; + + *n_relations = relations_.size(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_relation( + LONG relation_index, + IAccessibleRelation** relation) { + if (!instance_active_) + return E_FAIL; + + if (relation_index < 0 || + relation_index >= static_cast<long>(relations_.size())) { + return E_INVALIDARG; + } + + if (!relation) + return E_INVALIDARG; + + relations_[relation_index]->AddRef(); + *relation = relations_[relation_index]; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_relations( + LONG max_relations, + IAccessibleRelation** relations, + LONG* n_relations) { + if (!instance_active_) + return E_FAIL; + + if (!relations || !n_relations) + return E_INVALIDARG; + + long count = static_cast<long>(relations_.size()); + *n_relations = count; + if (count == 0) + return S_FALSE; + + for (long i = 0; i < count; ++i) { + relations_[i]->AddRef(); + relations[i] = relations_[i]; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::scrollTo(enum IA2ScrollType scroll_type) { + if (!instance_active_) + return E_FAIL; + + gfx::Rect r = location_; + switch(scroll_type) { + case IA2_SCROLL_TYPE_TOP_LEFT: + manager_->ScrollToMakeVisible(*this, gfx::Rect(r.x(), r.y(), 0, 0)); + break; + case IA2_SCROLL_TYPE_BOTTOM_RIGHT: + manager_->ScrollToMakeVisible( + *this, gfx::Rect(r.right(), r.bottom(), 0, 0)); + break; + case IA2_SCROLL_TYPE_TOP_EDGE: + manager_->ScrollToMakeVisible( + *this, gfx::Rect(r.x(), r.y(), r.width(), 0)); + break; + case IA2_SCROLL_TYPE_BOTTOM_EDGE: + manager_->ScrollToMakeVisible( + *this, gfx::Rect(r.x(), r.bottom(), r.width(), 0)); + break; + case IA2_SCROLL_TYPE_LEFT_EDGE: + manager_->ScrollToMakeVisible( + *this, gfx::Rect(r.x(), r.y(), 0, r.height())); + break; + case IA2_SCROLL_TYPE_RIGHT_EDGE: + manager_->ScrollToMakeVisible( + *this, gfx::Rect(r.right(), r.y(), 0, r.height())); + break; + case IA2_SCROLL_TYPE_ANYWHERE: + default: + manager_->ScrollToMakeVisible(*this, r); + break; + } + + static_cast<BrowserAccessibilityManagerWin*>(manager_) + ->TrackScrollingObject(this); + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::scrollToPoint( + enum IA2CoordinateType coordinate_type, + LONG x, + LONG y) { + if (!instance_active_) + return E_FAIL; + + gfx::Point scroll_to(x, y); + + if (coordinate_type == IA2_COORDTYPE_SCREEN_RELATIVE) { + scroll_to -= manager_->GetViewBounds().OffsetFromOrigin(); + } else if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) { + if (parent_) + scroll_to += parent_->location().OffsetFromOrigin(); + } else { + return E_INVALIDARG; + } + + manager_->ScrollToPoint(*this, scroll_to); + + static_cast<BrowserAccessibilityManagerWin*>(manager_) + ->TrackScrollingObject(this); + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_groupPosition( + LONG* group_level, + LONG* similar_items_in_group, + LONG* position_in_group) { + if (!instance_active_) + return E_FAIL; + + if (!group_level || !similar_items_in_group || !position_in_group) + return E_INVALIDARG; + + if (role_ == AccessibilityNodeData::ROLE_LISTBOX_OPTION && + parent_ && + parent_->role() == AccessibilityNodeData::ROLE_LISTBOX) { + *group_level = 0; + *similar_items_in_group = parent_->child_count(); + *position_in_group = index_in_parent_ + 1; + return S_OK; + } + + return E_NOTIMPL; +} + +// +// IAccessibleApplication methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_appName(BSTR* app_name) { + // No need to check |instance_active_| because this interface is + // global, and doesn't depend on any local state. + + if (!app_name) + return E_INVALIDARG; + + // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out + // the part before the "/". + std::vector<std::string> product_components; + base::SplitString(GetContentClient()->GetProduct(), '/', &product_components); + DCHECK_EQ(2U, product_components.size()); + if (product_components.size() != 2) + return E_FAIL; + *app_name = SysAllocString(UTF8ToUTF16(product_components[0]).c_str()); + DCHECK(*app_name); + return *app_name ? S_OK : E_FAIL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_appVersion(BSTR* app_version) { + // No need to check |instance_active_| because this interface is + // global, and doesn't depend on any local state. + + if (!app_version) + return E_INVALIDARG; + + // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out + // the part after the "/". + std::vector<std::string> product_components; + base::SplitString(GetContentClient()->GetProduct(), '/', &product_components); + DCHECK_EQ(2U, product_components.size()); + if (product_components.size() != 2) + return E_FAIL; + *app_version = SysAllocString(UTF8ToUTF16(product_components[1]).c_str()); + DCHECK(*app_version); + return *app_version ? S_OK : E_FAIL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_toolkitName(BSTR* toolkit_name) { + // No need to check |instance_active_| because this interface is + // global, and doesn't depend on any local state. + + if (!toolkit_name) + return E_INVALIDARG; + + // This is hard-coded; all products based on the Chromium engine + // will have the same toolkit name, so that assistive technology can + // detect any Chrome-based product. + *toolkit_name = SysAllocString(L"Chrome"); + DCHECK(*toolkit_name); + return *toolkit_name ? S_OK : E_FAIL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_toolkitVersion( + BSTR* toolkit_version) { + // No need to check |instance_active_| because this interface is + // global, and doesn't depend on any local state. + + if (!toolkit_version) + return E_INVALIDARG; + + std::string user_agent = GetContentClient()->GetUserAgent(); + *toolkit_version = SysAllocString(UTF8ToUTF16(user_agent).c_str()); + DCHECK(*toolkit_version); + return *toolkit_version ? S_OK : E_FAIL; +} + +// +// IAccessibleImage methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_description(BSTR* desc) { + if (!instance_active_) + return E_FAIL; + + if (!desc) + return E_INVALIDARG; + + return GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_DESCRIPTION, desc); +} + +STDMETHODIMP BrowserAccessibilityWin::get_imagePosition( + enum IA2CoordinateType coordinate_type, + LONG* x, + LONG* y) { + if (!instance_active_) + return E_FAIL; + + if (!x || !y) + return E_INVALIDARG; + + if (coordinate_type == IA2_COORDTYPE_SCREEN_RELATIVE) { + HWND parent_hwnd = + manager_->ToBrowserAccessibilityManagerWin()->parent_hwnd(); + POINT top_left = {0, 0}; + ::ClientToScreen(parent_hwnd, &top_left); + *x = location_.x() + top_left.x; + *y = location_.y() + top_left.y; + } else if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) { + *x = location_.x(); + *y = location_.y(); + if (parent_) { + *x -= parent_->location().x(); + *y -= parent_->location().y(); + } + } else { + return E_INVALIDARG; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_imageSize(LONG* height, LONG* width) { + if (!instance_active_) + return E_FAIL; + + if (!height || !width) + return E_INVALIDARG; + + *height = location_.height(); + *width = location_.width(); + return S_OK; +} + +// +// IAccessibleTable methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_accessibleAt( + long row, + long column, + IUnknown** accessible) { + if (!instance_active_) + return E_FAIL; + + if (!accessible) + return E_INVALIDARG; + + int columns; + int rows; + if (!GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) || + !GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) || + columns <= 0 || + rows <= 0) { + return S_FALSE; + } + + if (row < 0 || row >= rows || column < 0 || column >= columns) + return E_INVALIDARG; + + DCHECK_EQ(columns * rows, static_cast<int>(cell_ids_.size())); + + int cell_id = cell_ids_[row * columns + column]; + BrowserAccessibilityWin* cell = GetFromRendererID(cell_id); + if (cell) { + *accessible = static_cast<IAccessible*>(cell->NewReference()); + return S_OK; + } + + *accessible = NULL; + return E_INVALIDARG; +} + +STDMETHODIMP BrowserAccessibilityWin::get_caption(IUnknown** accessible) { + if (!instance_active_) + return E_FAIL; + + if (!accessible) + return E_INVALIDARG; + + // TODO(dmazzoni): implement + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_childIndex(long row, + long column, + long* cell_index) { + if (!instance_active_) + return E_FAIL; + + if (!cell_index) + return E_INVALIDARG; + + int columns; + int rows; + if (!GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) || + !GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) || + columns <= 0 || + rows <= 0) { + return S_FALSE; + } + + if (row < 0 || row >= rows || column < 0 || column >= columns) + return E_INVALIDARG; + + DCHECK_EQ(columns * rows, static_cast<int>(cell_ids_.size())); + int cell_id = cell_ids_[row * columns + column]; + for (size_t i = 0; i < unique_cell_ids_.size(); ++i) { + if (unique_cell_ids_[i] == cell_id) { + *cell_index = (long)i; + return S_OK; + } + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_columnDescription(long column, + BSTR* description) { + if (!instance_active_) + return E_FAIL; + + if (!description) + return E_INVALIDARG; + + int columns; + int rows; + if (!GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) || + !GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) || + columns <= 0 || + rows <= 0) { + return S_FALSE; + } + + if (column < 0 || column >= columns) + return E_INVALIDARG; + + for (int i = 0; i < rows; ++i) { + int cell_id = cell_ids_[i * columns + column]; + BrowserAccessibilityWin* cell = static_cast<BrowserAccessibilityWin*>( + manager_->GetFromRendererID(cell_id)); + if (cell && cell->role_ == AccessibilityNodeData::ROLE_COLUMN_HEADER) { + if (cell->name_.size() > 0) { + *description = SysAllocString(cell->name_.c_str()); + return S_OK; + } + + return cell->GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_DESCRIPTION, description); + } + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_columnExtentAt( + long row, + long column, + long* n_columns_spanned) { + if (!instance_active_) + return E_FAIL; + + if (!n_columns_spanned) + return E_INVALIDARG; + + int columns; + int rows; + if (!GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) || + !GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) || + columns <= 0 || + rows <= 0) { + return S_FALSE; + } + + if (row < 0 || row >= rows || column < 0 || column >= columns) + return E_INVALIDARG; + + int cell_id = cell_ids_[row * columns + column]; + BrowserAccessibilityWin* cell = static_cast<BrowserAccessibilityWin*>( + manager_->GetFromRendererID(cell_id)); + int colspan; + if (cell && + cell->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan) && + colspan >= 1) { + *n_columns_spanned = colspan; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_columnHeader( + IAccessibleTable** accessible_table, + long* starting_row_index) { + // TODO(dmazzoni): implement + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_columnIndex(long cell_index, + long* column_index) { + if (!instance_active_) + return E_FAIL; + + if (!column_index) + return E_INVALIDARG; + + int cell_id_count = static_cast<int>(unique_cell_ids_.size()); + if (cell_index < 0) + return E_INVALIDARG; + if (cell_index >= cell_id_count) + return S_FALSE; + + int cell_id = unique_cell_ids_[cell_index]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + int col_index; + if (cell && + cell->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &col_index)) { + *column_index = col_index; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nColumns(long* column_count) { + if (!instance_active_) + return E_FAIL; + + if (!column_count) + return E_INVALIDARG; + + int columns; + if (GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns)) { + *column_count = columns; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nRows(long* row_count) { + if (!instance_active_) + return E_FAIL; + + if (!row_count) + return E_INVALIDARG; + + int rows; + if (GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows)) { + *row_count = rows; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nSelectedChildren(long* cell_count) { + if (!instance_active_) + return E_FAIL; + + if (!cell_count) + return E_INVALIDARG; + + // TODO(dmazzoni): add support for selected cells/rows/columns in tables. + *cell_count = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nSelectedColumns(long* column_count) { + if (!instance_active_) + return E_FAIL; + + if (!column_count) + return E_INVALIDARG; + + *column_count = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nSelectedRows(long* row_count) { + if (!instance_active_) + return E_FAIL; + + if (!row_count) + return E_INVALIDARG; + + *row_count = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowDescription(long row, + BSTR* description) { + if (!instance_active_) + return E_FAIL; + + if (!description) + return E_INVALIDARG; + + int columns; + int rows; + if (!GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) || + !GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) || + columns <= 0 || + rows <= 0) { + return S_FALSE; + } + + if (row < 0 || row >= rows) + return E_INVALIDARG; + + for (int i = 0; i < columns; ++i) { + int cell_id = cell_ids_[row * columns + i]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + if (cell && cell->role_ == AccessibilityNodeData::ROLE_ROW_HEADER) { + if (cell->name_.size() > 0) { + *description = SysAllocString(cell->name_.c_str()); + return S_OK; + } + + return cell->GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_DESCRIPTION, description); + } + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowExtentAt(long row, + long column, + long* n_rows_spanned) { + if (!instance_active_) + return E_FAIL; + + if (!n_rows_spanned) + return E_INVALIDARG; + + int columns; + int rows; + if (!GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) || + !GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows) || + columns <= 0 || + rows <= 0) { + return S_FALSE; + } + + if (row < 0 || row >= rows || column < 0 || column >= columns) + return E_INVALIDARG; + + int cell_id = cell_ids_[row * columns + column]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + int rowspan; + if (cell && + cell->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan) && + rowspan >= 1) { + *n_rows_spanned = rowspan; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowHeader( + IAccessibleTable** accessible_table, + long* starting_column_index) { + // TODO(dmazzoni): implement + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowIndex(long cell_index, + long* row_index) { + if (!instance_active_) + return E_FAIL; + + if (!row_index) + return E_INVALIDARG; + + int cell_id_count = static_cast<int>(unique_cell_ids_.size()); + if (cell_index < 0) + return E_INVALIDARG; + if (cell_index >= cell_id_count) + return S_FALSE; + + int cell_id = unique_cell_ids_[cell_index]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + int cell_row_index; + if (cell && + cell->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &cell_row_index)) { + *row_index = cell_row_index; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_selectedChildren(long max_children, + long** children, + long* n_children) { + if (!instance_active_) + return E_FAIL; + + if (!children || !n_children) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *n_children = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_selectedColumns(long max_columns, + long** columns, + long* n_columns) { + if (!instance_active_) + return E_FAIL; + + if (!columns || !n_columns) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *n_columns = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_selectedRows(long max_rows, + long** rows, + long* n_rows) { + if (!instance_active_) + return E_FAIL; + + if (!rows || !n_rows) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *n_rows = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_summary(IUnknown** accessible) { + if (!instance_active_) + return E_FAIL; + + if (!accessible) + return E_INVALIDARG; + + // TODO(dmazzoni): implement + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_isColumnSelected( + long column, + boolean* is_selected) { + if (!instance_active_) + return E_FAIL; + + if (!is_selected) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *is_selected = false; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_isRowSelected(long row, + boolean* is_selected) { + if (!instance_active_) + return E_FAIL; + + if (!is_selected) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *is_selected = false; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_isSelected(long row, + long column, + boolean* is_selected) { + if (!instance_active_) + return E_FAIL; + + if (!is_selected) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *is_selected = false; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowColumnExtentsAtIndex( + long index, + long* row, + long* column, + long* row_extents, + long* column_extents, + boolean* is_selected) { + if (!instance_active_) + return E_FAIL; + + if (!row || !column || !row_extents || !column_extents || !is_selected) + return E_INVALIDARG; + + int cell_id_count = static_cast<int>(unique_cell_ids_.size()); + if (index < 0) + return E_INVALIDARG; + if (index >= cell_id_count) + return S_FALSE; + + int cell_id = unique_cell_ids_[index]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + int rowspan; + int colspan; + if (cell && + cell->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan) && + cell->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan) && + rowspan >= 1 && + colspan >= 1) { + *row_extents = rowspan; + *column_extents = colspan; + return S_OK; + } + + return S_FALSE; +} + +// +// IAccessibleTable2 methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_cellAt(long row, + long column, + IUnknown** cell) { + return get_accessibleAt(row, column, cell); +} + +STDMETHODIMP BrowserAccessibilityWin::get_nSelectedCells(long* cell_count) { + return get_nSelectedChildren(cell_count); +} + +STDMETHODIMP BrowserAccessibilityWin::get_selectedCells( + IUnknown*** cells, + long* n_selected_cells) { + if (!instance_active_) + return E_FAIL; + + if (!cells || !n_selected_cells) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *n_selected_cells = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_selectedColumns(long** columns, + long* n_columns) { + if (!instance_active_) + return E_FAIL; + + if (!columns || !n_columns) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *n_columns = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_selectedRows(long** rows, + long* n_rows) { + if (!instance_active_) + return E_FAIL; + + if (!rows || !n_rows) + return E_INVALIDARG; + + // TODO(dmazzoni): Implement this. + *n_rows = 0; + return S_OK; +} + + +// +// IAccessibleTableCell methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_columnExtent( + long* n_columns_spanned) { + if (!instance_active_) + return E_FAIL; + + if (!n_columns_spanned) + return E_INVALIDARG; + + int colspan; + if (GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan) && + colspan >= 1) { + *n_columns_spanned = colspan; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_columnHeaderCells( + IUnknown*** cell_accessibles, + long* n_column_header_cells) { + if (!instance_active_) + return E_FAIL; + + if (!cell_accessibles || !n_column_header_cells) + return E_INVALIDARG; + + *n_column_header_cells = 0; + + int column; + if (!GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column)) { + return S_FALSE; + } + + BrowserAccessibility* table = parent(); + while (table && table->role() != AccessibilityNodeData::ROLE_TABLE) + table = table->parent(); + if (!table) { + NOTREACHED(); + return S_FALSE; + } + + int columns; + int rows; + if (!table->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) || + !table->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows)) { + return S_FALSE; + } + if (columns <= 0 || rows <= 0 || column < 0 || column >= columns) + return S_FALSE; + + for (int i = 0; i < rows; ++i) { + int cell_id = table->cell_ids()[i * columns + column]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + if (cell && cell->role_ == AccessibilityNodeData::ROLE_COLUMN_HEADER) + (*n_column_header_cells)++; + } + + *cell_accessibles = static_cast<IUnknown**>(CoTaskMemAlloc( + (*n_column_header_cells) * sizeof(cell_accessibles[0]))); + int index = 0; + for (int i = 0; i < rows; ++i) { + int cell_id = table->cell_ids()[i * columns + column]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + if (cell && cell->role_ == AccessibilityNodeData::ROLE_COLUMN_HEADER) { + (*cell_accessibles)[index] = + static_cast<IAccessible*>(cell->NewReference()); + ++index; + } + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_columnIndex(long* column_index) { + if (!instance_active_) + return E_FAIL; + + if (!column_index) + return E_INVALIDARG; + + int column; + if (GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column)) { + *column_index = column; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowExtent(long* n_rows_spanned) { + if (!instance_active_) + return E_FAIL; + + if (!n_rows_spanned) + return E_INVALIDARG; + + int rowspan; + if (GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan) && + rowspan >= 1) { + *n_rows_spanned = rowspan; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowHeaderCells( + IUnknown*** cell_accessibles, + long* n_row_header_cells) { + if (!instance_active_) + return E_FAIL; + + if (!cell_accessibles || !n_row_header_cells) + return E_INVALIDARG; + + *n_row_header_cells = 0; + + int row; + if (!GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row)) { + return S_FALSE; + } + + BrowserAccessibility* table = parent(); + while (table && table->role() != AccessibilityNodeData::ROLE_TABLE) + table = table->parent(); + if (!table) { + NOTREACHED(); + return S_FALSE; + } + + int columns; + int rows; + if (!table->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &columns) || + !table->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &rows)) { + return S_FALSE; + } + if (columns <= 0 || rows <= 0 || row < 0 || row >= rows) + return S_FALSE; + + for (int i = 0; i < columns; ++i) { + int cell_id = table->cell_ids()[row * columns + i]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + if (cell && cell->role_ == AccessibilityNodeData::ROLE_ROW_HEADER) + (*n_row_header_cells)++; + } + + *cell_accessibles = static_cast<IUnknown**>(CoTaskMemAlloc( + (*n_row_header_cells) * sizeof(cell_accessibles[0]))); + int index = 0; + for (int i = 0; i < columns; ++i) { + int cell_id = table->cell_ids()[row * columns + i]; + BrowserAccessibilityWin* cell = + manager_->GetFromRendererID(cell_id)->ToBrowserAccessibilityWin(); + if (cell && cell->role_ == AccessibilityNodeData::ROLE_ROW_HEADER) { + (*cell_accessibles)[index] = + static_cast<IAccessible*>(cell->NewReference()); + ++index; + } + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowIndex(long* row_index) { + if (!instance_active_) + return E_FAIL; + + if (!row_index) + return E_INVALIDARG; + + int row; + if (GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row)) { + *row_index = row; + return S_OK; + } + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_isSelected(boolean* is_selected) { + if (!instance_active_) + return E_FAIL; + + if (!is_selected) + return E_INVALIDARG; + + *is_selected = false; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_rowColumnExtents( + long* row_index, + long* column_index, + long* row_extents, + long* column_extents, + boolean* is_selected) { + if (!instance_active_) + return E_FAIL; + + if (!row_index || + !column_index || + !row_extents || + !column_extents || + !is_selected) { + return E_INVALIDARG; + } + + int row; + int column; + int rowspan; + int colspan; + if (GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row) && + GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column) && + GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan) && + GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan)) { + *row_index = row; + *column_index = column; + *row_extents = rowspan; + *column_extents = colspan; + *is_selected = false; + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_table(IUnknown** table) { + if (!instance_active_) + return E_FAIL; + + if (!table) + return E_INVALIDARG; + + + int row; + int column; + GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row); + GetIntAttribute(AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column); + + BrowserAccessibility* find_table = parent(); + while (find_table && find_table->role() != AccessibilityNodeData::ROLE_TABLE) + find_table = find_table->parent(); + if (!find_table) { + NOTREACHED(); + return S_FALSE; + } + + *table = static_cast<IAccessibleTable*>( + find_table->ToBrowserAccessibilityWin()->NewReference()); + + return S_OK; +} + +// +// IAccessibleText methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_nCharacters(LONG* n_characters) { + if (!instance_active_) + return E_FAIL; + + if (!n_characters) + return E_INVALIDARG; + + *n_characters = TextForIAccessibleText().length(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_caretOffset(LONG* offset) { + if (!instance_active_) + return E_FAIL; + + if (!offset) + return E_INVALIDARG; + + *offset = 0; + if (role_ == AccessibilityNodeData::ROLE_TEXT_FIELD || + role_ == AccessibilityNodeData::ROLE_TEXTAREA) { + int sel_start = 0; + if (GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, + &sel_start)) + *offset = sel_start; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nSelections(LONG* n_selections) { + if (!instance_active_) + return E_FAIL; + + if (!n_selections) + return E_INVALIDARG; + + *n_selections = 0; + if (role_ == AccessibilityNodeData::ROLE_TEXT_FIELD || + role_ == AccessibilityNodeData::ROLE_TEXTAREA) { + int sel_start = 0; + int sel_end = 0; + if (GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, + &sel_start) && + GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end) && + sel_start != sel_end) + *n_selections = 1; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_selection(LONG selection_index, + LONG* start_offset, + LONG* end_offset) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || selection_index != 0) + return E_INVALIDARG; + + *start_offset = 0; + *end_offset = 0; + if (role_ == AccessibilityNodeData::ROLE_TEXT_FIELD || + role_ == AccessibilityNodeData::ROLE_TEXTAREA) { + int sel_start = 0; + int sel_end = 0; + if (GetIntAttribute( + AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start) && + GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end)) { + *start_offset = sel_start; + *end_offset = sel_end; + } + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_text(LONG start_offset, + LONG end_offset, + BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!text) + return E_INVALIDARG; + + const string16& text_str = TextForIAccessibleText(); + + // Handle special text offsets. + HandleSpecialTextOffset(text_str, &start_offset); + HandleSpecialTextOffset(text_str, &end_offset); + + // The spec allows the arguments to be reversed. + if (start_offset > end_offset) { + LONG tmp = start_offset; + start_offset = end_offset; + end_offset = tmp; + } + + // The spec does not allow the start or end offsets to be out or range; + // we must return an error if so. + LONG len = text_str.length(); + if (start_offset < 0) + return E_INVALIDARG; + if (end_offset > len) + return E_INVALIDARG; + + string16 substr = text_str.substr(start_offset, end_offset - start_offset); + if (substr.empty()) + return S_FALSE; + + *text = SysAllocString(substr.c_str()); + DCHECK(*text); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_textAtOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, + LONG* end_offset, + BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || !text) + return E_INVALIDARG; + + // The IAccessible2 spec says we don't have to implement the "sentence" + // boundary type, we can just let the screenreader handle it. + if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { + *start_offset = 0; + *end_offset = 0; + *text = NULL; + return S_FALSE; + } + + const string16& text_str = TextForIAccessibleText(); + + *start_offset = FindBoundary( + text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION); + *end_offset = FindBoundary( + text_str, boundary_type, offset, ui::FORWARDS_DIRECTION); + return get_text(*start_offset, *end_offset, text); +} + +STDMETHODIMP BrowserAccessibilityWin::get_textBeforeOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, + LONG* end_offset, + BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || !text) + return E_INVALIDARG; + + // The IAccessible2 spec says we don't have to implement the "sentence" + // boundary type, we can just let the screenreader handle it. + if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { + *start_offset = 0; + *end_offset = 0; + *text = NULL; + return S_FALSE; + } + + const string16& text_str = TextForIAccessibleText(); + + *start_offset = FindBoundary( + text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION); + *end_offset = offset; + return get_text(*start_offset, *end_offset, text); +} + +STDMETHODIMP BrowserAccessibilityWin::get_textAfterOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, + LONG* end_offset, + BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || !text) + return E_INVALIDARG; + + // The IAccessible2 spec says we don't have to implement the "sentence" + // boundary type, we can just let the screenreader handle it. + if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { + *start_offset = 0; + *end_offset = 0; + *text = NULL; + return S_FALSE; + } + + const string16& text_str = TextForIAccessibleText(); + + *start_offset = offset; + *end_offset = FindBoundary( + text_str, boundary_type, offset, ui::FORWARDS_DIRECTION); + return get_text(*start_offset, *end_offset, text); +} + +STDMETHODIMP BrowserAccessibilityWin::get_newText(IA2TextSegment* new_text) { + if (!instance_active_) + return E_FAIL; + + if (!new_text) + return E_INVALIDARG; + + string16 text = TextForIAccessibleText(); + + new_text->text = SysAllocString(text.c_str()); + new_text->start = 0; + new_text->end = static_cast<long>(text.size()); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_oldText(IA2TextSegment* old_text) { + if (!instance_active_) + return E_FAIL; + + if (!old_text) + return E_INVALIDARG; + + old_text->text = SysAllocString(old_text_.c_str()); + old_text->start = 0; + old_text->end = static_cast<long>(old_text_.size()); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_offsetAtPoint( + LONG x, + LONG y, + enum IA2CoordinateType coord_type, + LONG* offset) { + if (!instance_active_) + return E_FAIL; + + if (!offset) + return E_INVALIDARG; + + // TODO(dmazzoni): implement this. We're returning S_OK for now so that + // screen readers still return partially accurate results rather than + // completely failing. + *offset = 0; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::scrollSubstringTo( + LONG start_index, + LONG end_index, + enum IA2ScrollType scroll_type) { + // TODO(dmazzoni): adjust this for the start and end index, too. + return scrollTo(scroll_type); +} + +STDMETHODIMP BrowserAccessibilityWin::scrollSubstringToPoint( + LONG start_index, + LONG end_index, + enum IA2CoordinateType coordinate_type, + LONG x, LONG y) { + // TODO(dmazzoni): adjust this for the start and end index, too. + return scrollToPoint(coordinate_type, x, y); +} + +STDMETHODIMP BrowserAccessibilityWin::addSelection(LONG start_offset, + LONG end_offset) { + if (!instance_active_) + return E_FAIL; + + const string16& text_str = TextForIAccessibleText(); + HandleSpecialTextOffset(text_str, &start_offset); + HandleSpecialTextOffset(text_str, &end_offset); + + manager_->SetTextSelection(*this, start_offset, end_offset); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::removeSelection(LONG selection_index) { + if (!instance_active_) + return E_FAIL; + + if (selection_index != 0) + return E_INVALIDARG; + + manager_->SetTextSelection(*this, 0, 0); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::setCaretOffset(LONG offset) { + if (!instance_active_) + return E_FAIL; + + const string16& text_str = TextForIAccessibleText(); + HandleSpecialTextOffset(text_str, &offset); + manager_->SetTextSelection(*this, offset, offset); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::setSelection(LONG selection_index, + LONG start_offset, + LONG end_offset) { + if (!instance_active_) + return E_FAIL; + + if (selection_index != 0) + return E_INVALIDARG; + + const string16& text_str = TextForIAccessibleText(); + HandleSpecialTextOffset(text_str, &start_offset); + HandleSpecialTextOffset(text_str, &end_offset); + + manager_->SetTextSelection(*this, start_offset, end_offset); + return S_OK; +} + +// +// IAccessibleHypertext methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_nHyperlinks(long* hyperlink_count) { + if (!instance_active_) + return E_FAIL; + + if (!hyperlink_count) + return E_INVALIDARG; + + *hyperlink_count = hyperlink_offset_to_index_.size(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_hyperlink( + long index, + IAccessibleHyperlink** hyperlink) { + if (!instance_active_) + return E_FAIL; + + if (!hyperlink || + index < 0 || + index >= static_cast<long>(hyperlinks_.size())) { + return E_INVALIDARG; + } + + BrowserAccessibilityWin* child = + children_[hyperlinks_[index]]->ToBrowserAccessibilityWin(); + *hyperlink = static_cast<IAccessibleHyperlink*>(child->NewReference()); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_hyperlinkIndex( + long char_index, + long* hyperlink_index) { + if (!instance_active_) + return E_FAIL; + + if (!hyperlink_index) + return E_INVALIDARG; + + *hyperlink_index = -1; + + if (char_index < 0 || char_index >= static_cast<long>(hypertext_.size())) + return E_INVALIDARG; + + std::map<int32, int32>::iterator it = + hyperlink_offset_to_index_.find(char_index); + if (it == hyperlink_offset_to_index_.end()) + return E_FAIL; + + *hyperlink_index = it->second; + return S_OK; +} + +// +// IAccessibleValue methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_currentValue(VARIANT* value) { + if (!instance_active_) + return E_FAIL; + + if (!value) + return E_INVALIDARG; + + float float_val; + if (GetFloatAttribute( + AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &float_val)) { + value->vt = VT_R8; + value->dblVal = float_val; + return S_OK; + } + + value->vt = VT_EMPTY; + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_minimumValue(VARIANT* value) { + if (!instance_active_) + return E_FAIL; + + if (!value) + return E_INVALIDARG; + + float float_val; + if (GetFloatAttribute(AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE, + &float_val)) { + value->vt = VT_R8; + value->dblVal = float_val; + return S_OK; + } + + value->vt = VT_EMPTY; + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::get_maximumValue(VARIANT* value) { + if (!instance_active_) + return E_FAIL; + + if (!value) + return E_INVALIDARG; + + float float_val; + if (GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE, + &float_val)) { + value->vt = VT_R8; + value->dblVal = float_val; + return S_OK; + } + + value->vt = VT_EMPTY; + return S_FALSE; +} + +STDMETHODIMP BrowserAccessibilityWin::setCurrentValue(VARIANT new_value) { + // TODO(dmazzoni): Implement this. + return E_NOTIMPL; +} + +// +// ISimpleDOMDocument methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_URL(BSTR* url) { + if (!instance_active_) + return E_FAIL; + + if (!url) + return E_INVALIDARG; + + return GetStringAttributeAsBstr(AccessibilityNodeData::ATTR_DOC_URL, url); +} + +STDMETHODIMP BrowserAccessibilityWin::get_title(BSTR* title) { + if (!instance_active_) + return E_FAIL; + + if (!title) + return E_INVALIDARG; + + return GetStringAttributeAsBstr(AccessibilityNodeData::ATTR_DOC_TITLE, title); +} + +STDMETHODIMP BrowserAccessibilityWin::get_mimeType(BSTR* mime_type) { + if (!instance_active_) + return E_FAIL; + + if (!mime_type) + return E_INVALIDARG; + + return GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_DOC_MIMETYPE, mime_type); +} + +STDMETHODIMP BrowserAccessibilityWin::get_docType(BSTR* doc_type) { + if (!instance_active_) + return E_FAIL; + + if (!doc_type) + return E_INVALIDARG; + + return GetStringAttributeAsBstr( + AccessibilityNodeData::ATTR_DOC_DOCTYPE, doc_type); +} + +// +// ISimpleDOMNode methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_nodeInfo( + BSTR* node_name, + short* name_space_id, + BSTR* node_value, + unsigned int* num_children, + unsigned int* unique_id, + unsigned short* node_type) { + if (!instance_active_) + return E_FAIL; + + if (!node_name || !name_space_id || !node_value || !num_children || + !unique_id || !node_type) { + return E_INVALIDARG; + } + + string16 tag; + if (GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &tag)) + *node_name = SysAllocString(tag.c_str()); + else + *node_name = NULL; + + *name_space_id = 0; + *node_value = SysAllocString(value_.c_str()); + *num_children = children_.size(); + *unique_id = unique_id_win_; + + if (ia_role_ == ROLE_SYSTEM_DOCUMENT) { + *node_type = NODETYPE_DOCUMENT; + } else if (ia_role_ == ROLE_SYSTEM_TEXT && + ((ia2_state_ & IA2_STATE_EDITABLE) == 0)) { + *node_type = NODETYPE_TEXT; + } else { + *node_type = NODETYPE_ELEMENT; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributes( + unsigned short max_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values, + unsigned short* num_attribs) { + if (!instance_active_) + return E_FAIL; + + if (!attrib_names || !name_space_id || !attrib_values || !num_attribs) + return E_INVALIDARG; + + *num_attribs = max_attribs; + if (*num_attribs > html_attributes_.size()) + *num_attribs = html_attributes_.size(); + + for (unsigned short i = 0; i < *num_attribs; ++i) { + attrib_names[i] = SysAllocString(html_attributes_[i].first.c_str()); + name_space_id[i] = 0; + attrib_values[i] = SysAllocString(html_attributes_[i].second.c_str()); + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributesForNames( + unsigned short num_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values) { + if (!instance_active_) + return E_FAIL; + + if (!attrib_names || !name_space_id || !attrib_values) + return E_INVALIDARG; + + for (unsigned short i = 0; i < num_attribs; ++i) { + name_space_id[i] = 0; + bool found = false; + string16 name = (LPCWSTR)attrib_names[i]; + for (unsigned int j = 0; j < html_attributes_.size(); ++j) { + if (html_attributes_[j].first == name) { + attrib_values[i] = SysAllocString(html_attributes_[j].second.c_str()); + found = true; + break; + } + } + if (!found) { + attrib_values[i] = NULL; + } + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_computedStyle( + unsigned short max_style_properties, + boolean use_alternate_view, + BSTR* style_properties, + BSTR* style_values, + unsigned short *num_style_properties) { + if (!instance_active_) + return E_FAIL; + + if (!style_properties || !style_values) + return E_INVALIDARG; + + // We only cache a single style property for now: DISPLAY + + string16 display; + if (max_style_properties == 0 || + !GetStringAttribute(AccessibilityNodeData::ATTR_DISPLAY, &display)) { + *num_style_properties = 0; + return S_OK; + } + + *num_style_properties = 1; + style_properties[0] = SysAllocString(L"display"); + style_values[0] = SysAllocString(display.c_str()); + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_computedStyleForProperties( + unsigned short num_style_properties, + boolean use_alternate_view, + BSTR* style_properties, + BSTR* style_values) { + if (!instance_active_) + return E_FAIL; + + if (!style_properties || !style_values) + return E_INVALIDARG; + + // We only cache a single style property for now: DISPLAY + + for (unsigned short i = 0; i < num_style_properties; ++i) { + string16 name = (LPCWSTR)style_properties[i]; + StringToLowerASCII(&name); + if (name == L"display") { + string16 display; + GetStringAttribute(AccessibilityNodeData::ATTR_DISPLAY, &display); + style_values[i] = SysAllocString(display.c_str()); + } else { + style_values[i] = NULL; + } + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::scrollTo(boolean placeTopLeft) { + return scrollTo(placeTopLeft ? + IA2_SCROLL_TYPE_TOP_LEFT : IA2_SCROLL_TYPE_ANYWHERE); +} + +STDMETHODIMP BrowserAccessibilityWin::get_parentNode(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + *node = parent_->ToBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_firstChild(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (children_.empty()) { + *node = NULL; + return S_FALSE; + } + + *node = children_[0]->ToBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_lastChild(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (children_.empty()) { + *node = NULL; + return S_FALSE; + } + + *node = (*children_.rbegin())->ToBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_previousSibling( + ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (!parent_ || index_in_parent_ <= 0) { + *node = NULL; + return S_FALSE; + } + + *node = parent_->children()[index_in_parent_ - 1]-> + ToBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nextSibling(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (!parent_ || + index_in_parent_ < 0 || + index_in_parent_ >= static_cast<int>(parent_->children().size()) - 1) { + *node = NULL; + return S_FALSE; + } + + *node = parent_->children()[index_in_parent_ + 1]-> + ToBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_childAt( + unsigned int child_index, + ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (child_index < children_.size()) { + *node = NULL; + return S_FALSE; + } + + *node = children_[child_index]->ToBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +// +// ISimpleDOMText methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_domText(BSTR* dom_text) { + if (!instance_active_) + return E_FAIL; + + if (!dom_text) + return E_INVALIDARG; + + if (name_.empty()) + return S_FALSE; + + *dom_text = SysAllocString(name_.c_str()); + DCHECK(*dom_text); + return S_OK; +} + +// +// IServiceProvider methods. +// + +STDMETHODIMP BrowserAccessibilityWin::QueryService(REFGUID guidService, + REFIID riid, + void** object) { + if (!instance_active_) + return E_FAIL; + + if (guidService == GUID_IAccessibleContentDocument) { + // Special Mozilla extension: return the accessible for the root document. + // Screen readers use this to distinguish between a document loaded event + // on the root document vs on an iframe. + return manager_->GetRoot()->ToBrowserAccessibilityWin()->QueryInterface( + IID_IAccessible2, object); + } + + if (guidService == IID_IAccessible || + guidService == IID_IAccessible2 || + guidService == IID_IAccessibleAction || + guidService == IID_IAccessibleApplication || + guidService == IID_IAccessibleHyperlink || + guidService == IID_IAccessibleHypertext || + guidService == IID_IAccessibleImage || + guidService == IID_IAccessibleTable || + guidService == IID_IAccessibleTable2 || + guidService == IID_IAccessibleTableCell || + guidService == IID_IAccessibleText || + guidService == IID_IAccessibleValue || + guidService == IID_ISimpleDOMDocument || + guidService == IID_ISimpleDOMNode || + guidService == IID_ISimpleDOMText || + guidService == GUID_ISimpleDOM) { + return QueryInterface(riid, object); + } + + // We only support the IAccessibleEx interface on Windows 8 and above. This + // is needed for the on-screen Keyboard to show up in metro mode, when the + // user taps an editable portion on the page. + // All methods in the IAccessibleEx interface are unimplemented. + if (riid == IID_IAccessibleEx && + base::win::GetVersion() >= base::win::VERSION_WIN8) { + return QueryInterface(riid, object); + } + + *object = NULL; + return E_FAIL; +} + +STDMETHODIMP BrowserAccessibilityWin::GetPatternProvider(PATTERNID id, + IUnknown** provider) { + DVLOG(1) << "In Function: " + << __FUNCTION__ + << " for pattern id: " + << id; + if (id == UIA_ValuePatternId || id == UIA_TextPatternId) { + if (IsEditableText()) { + // The BrowserAccessibilityManager keeps track of instances when + // we don't want to show the on-screen keyboard. + if (!manager_->IsOSKAllowed(GetGlobalBoundsRect())) + return E_NOTIMPL; + + DVLOG(1) << "Returning UIA text provider"; + base::win::UIATextProvider::CreateTextProvider(true, provider); + return S_OK; + } + } + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::GetPropertyValue(PROPERTYID id, + VARIANT* ret) { + DVLOG(1) << "In Function: " + << __FUNCTION__ + << " for property id: " + << id; + V_VT(ret) = VT_EMPTY; + if (id == UIA_ControlTypePropertyId) { + if (IsEditableText()) { + V_VT(ret) = VT_I4; + ret->lVal = UIA_EditControlTypeId; + DVLOG(1) << "Returning Edit control type"; + } else { + DVLOG(1) << "Returning empty control type"; + } + } + return S_OK; +} + +// +// CComObjectRootEx methods. +// + +HRESULT WINAPI BrowserAccessibilityWin::InternalQueryInterface( + void* this_ptr, + const _ATL_INTMAP_ENTRY* entries, + REFIID iid, + void** object) { + if (iid == IID_IAccessibleImage) { + if (ia_role_ != ROLE_SYSTEM_GRAPHIC) { + *object = NULL; + return E_NOINTERFACE; + } + } else if (iid == IID_IAccessibleTable || iid == IID_IAccessibleTable2) { + if (ia_role_ != ROLE_SYSTEM_TABLE) { + *object = NULL; + return E_NOINTERFACE; + } + } else if (iid == IID_IAccessibleTableCell) { + if (ia_role_ != ROLE_SYSTEM_CELL) { + *object = NULL; + return E_NOINTERFACE; + } + } else if (iid == IID_IAccessibleValue) { + if (ia_role_ != ROLE_SYSTEM_PROGRESSBAR && + ia_role_ != ROLE_SYSTEM_SCROLLBAR && + ia_role_ != ROLE_SYSTEM_SLIDER) { + *object = NULL; + return E_NOINTERFACE; + } + } else if (iid == IID_ISimpleDOMDocument) { + if (ia_role_ != ROLE_SYSTEM_DOCUMENT) { + *object = NULL; + return E_NOINTERFACE; + } + } + + return CComObjectRootBase::InternalQueryInterface( + this_ptr, entries, iid, object); +} + +// +// Private methods. +// + +// Initialize this object and mark it as active. +void BrowserAccessibilityWin::PreInitialize() { + BrowserAccessibility::PreInitialize(); + + InitRoleAndState(); + + // Expose the "display" and "tag" attributes. + StringAttributeToIA2(AccessibilityNodeData::ATTR_DISPLAY, "display"); + StringAttributeToIA2(AccessibilityNodeData::ATTR_HTML_TAG, "tag"); + StringAttributeToIA2(AccessibilityNodeData::ATTR_ROLE, "xml-roles"); + + // Expose "level" attribute for headings, trees, etc. + IntAttributeToIA2(AccessibilityNodeData::ATTR_HIERARCHICAL_LEVEL, "level"); + + // Expose the set size and position in set for listbox options. + if (role_ == AccessibilityNodeData::ROLE_LISTBOX_OPTION && + parent_ && + parent_->role() == AccessibilityNodeData::ROLE_LISTBOX) { + ia2_attributes_.push_back( + L"setsize:" + base::IntToString16(parent_->child_count())); + ia2_attributes_.push_back( + L"setsize:" + base::IntToString16(index_in_parent_ + 1)); + } + + if (ia_role_ == ROLE_SYSTEM_CHECKBUTTON || + ia_role_ == ROLE_SYSTEM_RADIOBUTTON || + ia2_role_ == IA2_ROLE_TOGGLE_BUTTON) { + ia2_attributes_.push_back(L"checkable:true"); + } + + // Expose live region attributes. + StringAttributeToIA2(AccessibilityNodeData::ATTR_LIVE_STATUS, "live"); + StringAttributeToIA2(AccessibilityNodeData::ATTR_LIVE_RELEVANT, "relevant"); + BoolAttributeToIA2(AccessibilityNodeData::ATTR_LIVE_ATOMIC, "atomic"); + BoolAttributeToIA2(AccessibilityNodeData::ATTR_LIVE_BUSY, "busy"); + + // Expose container live region attributes. + StringAttributeToIA2(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS, + "container-live"); + StringAttributeToIA2(AccessibilityNodeData::ATTR_CONTAINER_LIVE_RELEVANT, + "container-relevant"); + BoolAttributeToIA2(AccessibilityNodeData::ATTR_CONTAINER_LIVE_ATOMIC, + "container-atomic"); + BoolAttributeToIA2(AccessibilityNodeData::ATTR_CONTAINER_LIVE_BUSY, + "container-busy"); + + // Expose slider value. + if (ia_role_ == ROLE_SYSTEM_PROGRESSBAR || + ia_role_ == ROLE_SYSTEM_SCROLLBAR || + ia_role_ == ROLE_SYSTEM_SLIDER) { + float fval; + if (value_.empty() && + GetFloatAttribute(AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &fval)) { + // TODO(dmazzoni): Use ICU to localize this? + value_ = UTF8ToUTF16(base::DoubleToString(fval)); + } + ia2_attributes_.push_back(L"valuetext:" + value_); + } + + // Expose color well value. + if (ia2_role_ == IA2_ROLE_COLOR_CHOOSER) { + int r, g, b; + GetIntAttribute(AccessibilityNodeData::ATTR_COLOR_VALUE_RED, &r); + GetIntAttribute(AccessibilityNodeData::ATTR_COLOR_VALUE_GREEN, &g); + GetIntAttribute(AccessibilityNodeData::ATTR_COLOR_VALUE_BLUE, &b); + value_ = base::IntToString16((r * 100) / 255) + L"% red " + + base::IntToString16((g * 100) / 255) + L"% green " + + base::IntToString16((b * 100) / 255) + L"% blue"; + } + + // Expose table cell index. + if (ia_role_ == ROLE_SYSTEM_CELL) { + BrowserAccessibility* table = parent(); + while (table && table->role() != AccessibilityNodeData::ROLE_TABLE) + table = table->parent(); + if (table) { + const std::vector<int32>& unique_cell_ids = table->unique_cell_ids(); + for (size_t i = 0; i < unique_cell_ids.size(); ++i) { + if (unique_cell_ids[i] == renderer_id_) { + ia2_attributes_.push_back( + string16(L"table-cell-index:") + base::IntToString16(i)); + } + } + } + } + + // The calculation of the accessible name of an element has been + // standardized in the HTML to Platform Accessibility APIs Implementation + // Guide (http://www.w3.org/TR/html-aapi/). In order to return the + // appropriate accessible name on Windows, we need to apply some logic + // to the fields we get from WebKit. + // + // TODO(dmazzoni): move most of this logic into WebKit. + // + // WebKit gives us: + // + // name: the default name, e.g. inner text + // title ui element: a reference to a <label> element on the same + // page that labels this node. + // description: accessible labels that override the default name: + // aria-label or aria-labelledby or aria-describedby + // help: the value of the "title" attribute + // + // On Windows, the logic we apply lets some fields take precedence and + // always returns the primary name in "name" and the secondary name, + // if any, in "description". + + string16 description, help, title_attr; + int title_elem_id = 0; + GetIntAttribute(AccessibilityNodeData::ATTR_TITLE_UI_ELEMENT, &title_elem_id); + GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION, &description); + GetStringAttribute(AccessibilityNodeData::ATTR_HELP, &help); + + // WebKit annoyingly puts the title in the description if there's no other + // description, which just confuses the rest of the logic. Put it back. + // Now "help" is always the value of the "title" attribute, if present. + if (GetHtmlAttribute("title", &title_attr) && + description == title_attr && + help.empty()) { + help = description; + description.clear(); + string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION].clear(); + string_attributes_[AccessibilityNodeData::ATTR_HELP] = help; + } + + // Now implement the main logic: the descripion should become the name if + // it's nonempty, and the help should become the description if + // there's no description - or the name if there's no name or description. + if (!description.empty()) { + name_ = description; + description.clear(); + string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION] = description; + } + if (!help.empty() && description.empty()) { + description = help; + string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION] = help; + string_attributes_[AccessibilityNodeData::ATTR_HELP].clear(); + } + if (!description.empty() && name_.empty() && !title_elem_id) { + name_ = description; + description.clear(); + string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION].clear(); + } + + // If it's a text field, also consider the placeholder. + string16 placeholder; + if (role_ == AccessibilityNodeData::ROLE_TEXT_FIELD && + HasState(AccessibilityNodeData::STATE_FOCUSABLE) && + GetHtmlAttribute("placeholder", &placeholder)) { + if (name_.empty() && !title_elem_id) { + name_ = placeholder; + } else if (description.empty()) { + description = placeholder; + string_attributes_[AccessibilityNodeData::ATTR_DESCRIPTION] = description; + } + } + + // On Windows, the value of a document should be its url. + if (role_ == AccessibilityNodeData::ROLE_ROOT_WEB_AREA || + role_ == AccessibilityNodeData::ROLE_WEB_AREA) { + GetStringAttribute(AccessibilityNodeData::ATTR_DOC_URL, &value_); + } + + // For certain roles (listbox option, static text, and list marker) + // WebKit stores the main accessible text in the "value" - swap it so + // that it's the "name". + if (name_.empty() && + (role_ == AccessibilityNodeData::ROLE_LISTBOX_OPTION || + role_ == AccessibilityNodeData::ROLE_STATIC_TEXT || + role_ == AccessibilityNodeData::ROLE_LIST_MARKER)) { + name_.swap(value_); + } + + // If this doesn't have a value and is linked then set its value to the url + // attribute. This allows screen readers to read an empty link's destination. + string16 url; + if (value_.empty() && (ia_state_ & STATE_SYSTEM_LINKED)) + GetStringAttribute(AccessibilityNodeData::ATTR_URL, &value_); + + // Clear any old relationships between this node and other nodes. + for (size_t i = 0; i < relations_.size(); ++i) + relations_[i]->Release(); + relations_.clear(); + + // Handle title UI element. + if (title_elem_id) { + // Add a labelled by relationship. + CComObject<BrowserAccessibilityRelation>* relation; + HRESULT hr = CComObject<BrowserAccessibilityRelation>::CreateInstance( + &relation); + DCHECK(SUCCEEDED(hr)); + relation->AddRef(); + relation->Initialize(this, IA2_RELATION_LABELLED_BY); + relation->AddTarget(title_elem_id); + relations_.push_back(relation); + } +} + +void BrowserAccessibilityWin::PostInitialize() { + BrowserAccessibility::PostInitialize(); + + // Construct the hypertext for this node. + hyperlink_offset_to_index_.clear(); + hyperlinks_.clear(); + hypertext_.clear(); + for (unsigned int i = 0; i < children().size(); ++i) { + BrowserAccessibility* child = children()[i]; + if (child->role() == AccessibilityNodeData::ROLE_STATIC_TEXT) { + hypertext_ += child->name(); + } else { + hyperlink_offset_to_index_[hypertext_.size()] = hyperlinks_.size(); + hypertext_ += kEmbeddedCharacter; + hyperlinks_.push_back(i); + } + } + DCHECK_EQ(hyperlink_offset_to_index_.size(), hyperlinks_.size()); + + // Fire an event when an alert first appears. + if (role_ == AccessibilityNodeData::ROLE_ALERT && first_time_) + manager_->NotifyAccessibilityEvent(AccessibilityNotificationAlert, this); + + // Fire events if text has changed. + string16 text = TextForIAccessibleText(); + if (previous_text_ != text) { + if (!previous_text_.empty() && !text.empty()) { + manager_->NotifyAccessibilityEvent( + AccessibilityNotificationObjectShow, this); + } + + // TODO(dmazzoni): Look into HIDE events, too. + + old_text_ = previous_text_; + previous_text_ = text; + } + + // Fire events if the state has changed. + if (!first_time_ && ia_state_ != old_ia_state_) { + BrowserAccessibilityManagerWin* manager = + manager_->ToBrowserAccessibilityManagerWin(); + + // Normally focus events are handled elsewhere, however + // focus for managed descendants is platform-specific. + // Fire a focus event if the focused descendant in a multi-select + // list box changes. + if (role_ == AccessibilityNodeData::ROLE_LISTBOX_OPTION && + (ia_state_ & STATE_SYSTEM_FOCUSABLE) && + (ia_state_ & STATE_SYSTEM_SELECTABLE) && + (ia_state_ & STATE_SYSTEM_FOCUSED) && + !(old_ia_state_ & STATE_SYSTEM_FOCUSED)) { + manager->MaybeCallNotifyWinEvent(EVENT_OBJECT_FOCUS, unique_id_win()); + } + + if ((ia_state_ & STATE_SYSTEM_SELECTED) && + !(old_ia_state_ & STATE_SYSTEM_SELECTED)) { + manager->MaybeCallNotifyWinEvent(EVENT_OBJECT_SELECTIONADD, + unique_id_win()); + } else if (!(ia_state_ & STATE_SYSTEM_SELECTED) && + (old_ia_state_ & STATE_SYSTEM_SELECTED)) { + manager->MaybeCallNotifyWinEvent(EVENT_OBJECT_SELECTIONREMOVE, + unique_id_win()); + } + + old_ia_state_ = ia_state_; + } + + first_time_ = false; +} + +void BrowserAccessibilityWin::NativeAddReference() { + AddRef(); +} + +void BrowserAccessibilityWin::NativeReleaseReference() { + Release(); +} + +bool BrowserAccessibilityWin::IsNative() const { + return true; +} + +void BrowserAccessibilityWin::SetLocation(const gfx::Rect& new_location) { + BrowserAccessibility::SetLocation(new_location); + manager_->ToBrowserAccessibilityManagerWin()->MaybeCallNotifyWinEvent( + EVENT_OBJECT_LOCATIONCHANGE, unique_id_win()); +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::NewReference() { + AddRef(); + return this; +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::GetTargetFromChildID( + const VARIANT& var_id) { + if (var_id.vt != VT_I4) + return NULL; + + LONG child_id = var_id.lVal; + if (child_id == CHILDID_SELF) + return this; + + if (child_id >= 1 && child_id <= static_cast<LONG>(children_.size())) + return children_[child_id - 1]->ToBrowserAccessibilityWin(); + + return manager_->ToBrowserAccessibilityManagerWin()-> + GetFromUniqueIdWin(child_id); +} + +HRESULT BrowserAccessibilityWin::GetStringAttributeAsBstr( + AccessibilityNodeData::StringAttribute attribute, + BSTR* value_bstr) { + string16 str; + + if (!GetStringAttribute(attribute, &str)) + return S_FALSE; + + if (str.empty()) + return S_FALSE; + + *value_bstr = SysAllocString(str.c_str()); + DCHECK(*value_bstr); + + return S_OK; +} + +void BrowserAccessibilityWin::StringAttributeToIA2( + AccessibilityNodeData::StringAttribute attribute, + const char* ia2_attr) { + string16 value; + if (GetStringAttribute(attribute, &value)) + ia2_attributes_.push_back(ASCIIToUTF16(ia2_attr) + L":" + value); +} + +void BrowserAccessibilityWin::BoolAttributeToIA2( + AccessibilityNodeData::BoolAttribute attribute, + const char* ia2_attr) { + bool value; + if (GetBoolAttribute(attribute, &value)) { + ia2_attributes_.push_back((ASCIIToUTF16(ia2_attr) + L":") + + (value ? L"true" : L"false")); + } +} + +void BrowserAccessibilityWin::IntAttributeToIA2( + AccessibilityNodeData::IntAttribute attribute, + const char* ia2_attr) { + int value; + if (GetIntAttribute(attribute, &value)) + ia2_attributes_.push_back(ASCIIToUTF16(ia2_attr) + L":" + + base::IntToString16(value)); +} + +const string16& BrowserAccessibilityWin::TextForIAccessibleText() { + if (IsEditableText()) + return value_; + return (role_ == AccessibilityNodeData::ROLE_STATIC_TEXT) ? + name_ : hypertext_; +} + +void BrowserAccessibilityWin::HandleSpecialTextOffset(const string16& text, + LONG* offset) { + if (*offset == IA2_TEXT_OFFSET_LENGTH) + *offset = static_cast<LONG>(text.size()); + else if (*offset == IA2_TEXT_OFFSET_CARET) + get_caretOffset(offset); +} + +ui::TextBoundaryType BrowserAccessibilityWin::IA2TextBoundaryToTextBoundary( + IA2TextBoundaryType ia2_boundary) { + switch(ia2_boundary) { + case IA2_TEXT_BOUNDARY_CHAR: return ui::CHAR_BOUNDARY; + case IA2_TEXT_BOUNDARY_WORD: return ui::WORD_BOUNDARY; + case IA2_TEXT_BOUNDARY_LINE: return ui::LINE_BOUNDARY; + case IA2_TEXT_BOUNDARY_SENTENCE: return ui::SENTENCE_BOUNDARY; + case IA2_TEXT_BOUNDARY_PARAGRAPH: return ui::PARAGRAPH_BOUNDARY; + case IA2_TEXT_BOUNDARY_ALL: return ui::ALL_BOUNDARY; + default: + NOTREACHED(); + return ui::CHAR_BOUNDARY; + } +} + +LONG BrowserAccessibilityWin::FindBoundary( + const string16& text, + IA2TextBoundaryType ia2_boundary, + LONG start_offset, + ui::TextBoundaryDirection direction) { + HandleSpecialTextOffset(text, &start_offset); + ui::TextBoundaryType boundary = IA2TextBoundaryToTextBoundary(ia2_boundary); + return ui::FindAccessibleTextBoundary( + text, line_breaks_, boundary, start_offset, direction); +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::GetFromRendererID( + int32 renderer_id) { + return manager_->GetFromRendererID(renderer_id)->ToBrowserAccessibilityWin(); +} + +void BrowserAccessibilityWin::InitRoleAndState() { + ia_state_ = 0; + ia2_state_ = IA2_STATE_OPAQUE; + ia2_attributes_.clear(); + + if (HasState(AccessibilityNodeData::STATE_BUSY)) + ia_state_ |= STATE_SYSTEM_BUSY; + if (HasState(AccessibilityNodeData::STATE_CHECKED)) + ia_state_ |= STATE_SYSTEM_CHECKED; + if (HasState(AccessibilityNodeData::STATE_COLLAPSED)) + ia_state_ |= STATE_SYSTEM_COLLAPSED; + if (HasState(AccessibilityNodeData::STATE_EXPANDED)) + ia_state_ |= STATE_SYSTEM_EXPANDED; + if (HasState(AccessibilityNodeData::STATE_FOCUSABLE)) + ia_state_ |= STATE_SYSTEM_FOCUSABLE; + if (HasState(AccessibilityNodeData::STATE_HASPOPUP)) + ia_state_ |= STATE_SYSTEM_HASPOPUP; + if (HasState(AccessibilityNodeData::STATE_HOTTRACKED)) + ia_state_ |= STATE_SYSTEM_HOTTRACKED; + if (HasState(AccessibilityNodeData::STATE_INDETERMINATE)) + ia_state_ |= STATE_SYSTEM_INDETERMINATE; + if (HasState(AccessibilityNodeData::STATE_INVISIBLE)) + ia_state_ |= STATE_SYSTEM_INVISIBLE; + if (HasState(AccessibilityNodeData::STATE_LINKED)) + ia_state_ |= STATE_SYSTEM_LINKED; + if (HasState(AccessibilityNodeData::STATE_MULTISELECTABLE)) { + ia_state_ |= STATE_SYSTEM_EXTSELECTABLE; + ia_state_ |= STATE_SYSTEM_MULTISELECTABLE; + } + // TODO(ctguil): Support STATE_SYSTEM_EXTSELECTABLE/accSelect. + if (HasState(AccessibilityNodeData::STATE_OFFSCREEN)) + ia_state_ |= STATE_SYSTEM_OFFSCREEN; + if (HasState(AccessibilityNodeData::STATE_PRESSED)) + ia_state_ |= STATE_SYSTEM_PRESSED; + if (HasState(AccessibilityNodeData::STATE_PROTECTED)) + ia_state_ |= STATE_SYSTEM_PROTECTED; + if (HasState(AccessibilityNodeData::STATE_REQUIRED)) + ia2_state_ |= IA2_STATE_REQUIRED; + if (HasState(AccessibilityNodeData::STATE_SELECTABLE)) + ia_state_ |= STATE_SYSTEM_SELECTABLE; + if (HasState(AccessibilityNodeData::STATE_SELECTED)) + ia_state_ |= STATE_SYSTEM_SELECTED; + if (HasState(AccessibilityNodeData::STATE_TRAVERSED)) + ia_state_ |= STATE_SYSTEM_TRAVERSED; + if (HasState(AccessibilityNodeData::STATE_UNAVAILABLE)) + ia_state_ |= STATE_SYSTEM_UNAVAILABLE; + if (HasState(AccessibilityNodeData::STATE_VERTICAL)) { + ia2_state_ |= IA2_STATE_VERTICAL; + } else { + ia2_state_ |= IA2_STATE_HORIZONTAL; + } + if (HasState(AccessibilityNodeData::STATE_VISITED)) + ia_state_ |= STATE_SYSTEM_TRAVERSED; + + // WebKit marks everything as readonly unless it's editable text, so if it's + // not readonly, mark it as editable now. The final computation of the + // READONLY state for MSAA is below, after the switch. + if (!HasState(AccessibilityNodeData::STATE_READONLY)) + ia2_state_ |= IA2_STATE_EDITABLE; + + string16 invalid; + if (GetHtmlAttribute("aria-invalid", &invalid)) + ia2_state_ |= IA2_STATE_INVALID_ENTRY; + + bool mixed = false; + GetBoolAttribute(AccessibilityNodeData::ATTR_BUTTON_MIXED, &mixed); + if (mixed) + ia_state_ |= STATE_SYSTEM_MIXED; + + bool editable = false; + GetBoolAttribute(AccessibilityNodeData::ATTR_CAN_SET_VALUE, &editable); + if (editable) + ia2_state_ |= IA2_STATE_EDITABLE; + + string16 html_tag; + GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &html_tag); + ia_role_ = 0; + ia2_role_ = 0; + switch (role_) { + case AccessibilityNodeData::ROLE_ALERT: + ia_role_ = ROLE_SYSTEM_ALERT; + break; + case AccessibilityNodeData::ROLE_ALERT_DIALOG: + ia_role_ = ROLE_SYSTEM_DIALOG; + break; + case AccessibilityNodeData::ROLE_APPLICATION: + ia_role_ = ROLE_SYSTEM_APPLICATION; + break; + case AccessibilityNodeData::ROLE_ARTICLE: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_BUSY_INDICATOR: + ia_role_ = ROLE_SYSTEM_ANIMATION; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_BUTTON: + ia_role_ = ROLE_SYSTEM_PUSHBUTTON; + bool is_aria_pressed_defined; + bool is_mixed; + if (GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed)) + ia_state_ |= STATE_SYSTEM_PRESSED; + if (is_aria_pressed_defined) + ia2_role_ = IA2_ROLE_TOGGLE_BUTTON; + if (is_mixed) + ia_state_ |= STATE_SYSTEM_MIXED; + break; + case AccessibilityNodeData::ROLE_CANVAS: + ia_role_ = ROLE_SYSTEM_GRAPHIC; + break; + case AccessibilityNodeData::ROLE_CANVAS_WITH_FALLBACK_CONTENT: + role_name_ = L"canvas"; + ia2_role_ = IA2_ROLE_CANVAS; + break; + case AccessibilityNodeData::ROLE_CELL: + ia_role_ = ROLE_SYSTEM_CELL; + break; + case AccessibilityNodeData::ROLE_CHECKBOX: + ia_role_ = ROLE_SYSTEM_CHECKBUTTON; + break; + case AccessibilityNodeData::ROLE_COLOR_WELL: + ia_role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_COLOR_CHOOSER; + break; + case AccessibilityNodeData::ROLE_COLUMN: + ia_role_ = ROLE_SYSTEM_COLUMN; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_COLUMN_HEADER: + ia_role_ = ROLE_SYSTEM_COLUMNHEADER; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_COMBO_BOX: + ia_role_ = ROLE_SYSTEM_COMBOBOX; + break; + case AccessibilityNodeData::ROLE_DIV: + role_name_ = L"div"; + ia2_role_ = IA2_ROLE_SECTION; + break; + case AccessibilityNodeData::ROLE_DEFINITION: + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_PARAGRAPH; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_DESCRIPTION_LIST_DETAIL: + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_PARAGRAPH; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_DESCRIPTION_LIST_TERM: + ia_role_ = ROLE_SYSTEM_LISTITEM; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_DIALOG: + ia_role_ = ROLE_SYSTEM_DIALOG; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_DISCLOSURE_TRIANGLE: + ia_role_ = ROLE_SYSTEM_OUTLINEBUTTON; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_DOCUMENT: + case AccessibilityNodeData::ROLE_ROOT_WEB_AREA: + case AccessibilityNodeData::ROLE_WEB_AREA: + ia_role_ = ROLE_SYSTEM_DOCUMENT; + ia_state_ |= STATE_SYSTEM_READONLY; + ia_state_ |= STATE_SYSTEM_FOCUSABLE; + break; + case AccessibilityNodeData::ROLE_EDITABLE_TEXT: + ia_role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_SINGLE_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + break; + case AccessibilityNodeData::ROLE_FORM: + role_name_ = L"form"; + ia2_role_ = IA2_ROLE_FORM; + break; + case AccessibilityNodeData::ROLE_FOOTER: + ia_role_ = IA2_ROLE_FOOTER; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_GRID: + ia_role_ = ROLE_SYSTEM_TABLE; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_GROUP: { + string16 aria_role; + GetStringAttribute(AccessibilityNodeData::ATTR_ROLE, &aria_role); + if (aria_role == L"group" || html_tag == L"fieldset") { + ia_role_ = ROLE_SYSTEM_GROUPING; + } else if (html_tag == L"li") { + ia_role_ = ROLE_SYSTEM_LISTITEM; + } else { + if (html_tag.empty()) + role_name_ = L"div"; + else + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_SECTION; + } + ia_state_ |= STATE_SYSTEM_READONLY; + break; + } + case AccessibilityNodeData::ROLE_GROW_AREA: + ia_role_ = ROLE_SYSTEM_GRIP; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_HEADING: + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_HEADING; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_HORIZONTAL_RULE: + ia_role_ = ROLE_SYSTEM_SEPARATOR; + break; + case AccessibilityNodeData::ROLE_IMAGE: + ia_role_ = ROLE_SYSTEM_GRAPHIC; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_IMAGE_MAP: + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_IMAGE_MAP; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_IMAGE_MAP_LINK: + ia_role_ = ROLE_SYSTEM_LINK; + ia_state_ |= STATE_SYSTEM_LINKED; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_LABEL: + ia_role_ = ROLE_SYSTEM_TEXT; + ia2_role_ = IA2_ROLE_LABEL; + break; + case AccessibilityNodeData::ROLE_LANDMARK_APPLICATION: + case AccessibilityNodeData::ROLE_LANDMARK_BANNER: + case AccessibilityNodeData::ROLE_LANDMARK_COMPLEMENTARY: + case AccessibilityNodeData::ROLE_LANDMARK_CONTENTINFO: + case AccessibilityNodeData::ROLE_LANDMARK_MAIN: + case AccessibilityNodeData::ROLE_LANDMARK_NAVIGATION: + case AccessibilityNodeData::ROLE_LANDMARK_SEARCH: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_LINK: + case AccessibilityNodeData::ROLE_WEBCORE_LINK: + ia_role_ = ROLE_SYSTEM_LINK; + ia_state_ |= STATE_SYSTEM_LINKED; + break; + case AccessibilityNodeData::ROLE_LIST: + ia_role_ = ROLE_SYSTEM_LIST; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_LISTBOX: + ia_role_ = ROLE_SYSTEM_LIST; + break; + case AccessibilityNodeData::ROLE_LISTBOX_OPTION: + ia_role_ = ROLE_SYSTEM_LISTITEM; + if (ia_state_ & STATE_SYSTEM_SELECTABLE) { + ia_state_ |= STATE_SYSTEM_FOCUSABLE; + if (HasState(AccessibilityNodeData::STATE_FOCUSED)) + ia_state_ |= STATE_SYSTEM_FOCUSED; + } + break; + case AccessibilityNodeData::ROLE_LIST_ITEM: + ia_role_ = ROLE_SYSTEM_LISTITEM; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_LIST_MARKER: + ia_role_ = ROLE_SYSTEM_TEXT; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_MATH: + ia_role_ = ROLE_SYSTEM_EQUATION; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_MENU: + case AccessibilityNodeData::ROLE_MENU_BUTTON: + ia_role_ = ROLE_SYSTEM_MENUPOPUP; + break; + case AccessibilityNodeData::ROLE_MENU_BAR: + ia_role_ = ROLE_SYSTEM_MENUBAR; + break; + case AccessibilityNodeData::ROLE_MENU_ITEM: + ia_role_ = ROLE_SYSTEM_MENUITEM; + break; + case AccessibilityNodeData::ROLE_MENU_LIST_POPUP: + ia_role_ = ROLE_SYSTEM_CLIENT; + break; + case AccessibilityNodeData::ROLE_MENU_LIST_OPTION: + ia_role_ = ROLE_SYSTEM_LISTITEM; + if (ia_state_ & STATE_SYSTEM_SELECTABLE) { + ia_state_ |= STATE_SYSTEM_FOCUSABLE; + if (HasState(AccessibilityNodeData::STATE_FOCUSED)) + ia_state_ |= STATE_SYSTEM_FOCUSED; + } + break; + case AccessibilityNodeData::ROLE_NOTE: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_NOTE; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_OUTLINE: + ia_role_ = ROLE_SYSTEM_OUTLINE; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_PARAGRAPH: + role_name_ = L"P"; + ia2_role_ = IA2_ROLE_PARAGRAPH; + break; + case AccessibilityNodeData::ROLE_POPUP_BUTTON: + if (html_tag == L"select") { + ia_role_ = ROLE_SYSTEM_COMBOBOX; + } else { + ia_role_ = ROLE_SYSTEM_BUTTONMENU; + } + break; + case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: + ia_role_ = ROLE_SYSTEM_PROGRESSBAR; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_RADIO_BUTTON: + ia_role_ = ROLE_SYSTEM_RADIOBUTTON; + break; + case AccessibilityNodeData::ROLE_RADIO_GROUP: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case AccessibilityNodeData::ROLE_REGION: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_ROW: + ia_role_ = ROLE_SYSTEM_ROW; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_ROW_HEADER: + ia_role_ = ROLE_SYSTEM_ROWHEADER; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_RULER: + ia_role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_RULER; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_SCROLLAREA: + ia_role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_SCROLL_PANE; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_SCROLLBAR: + ia_role_ = ROLE_SYSTEM_SCROLLBAR; + break; + case AccessibilityNodeData::ROLE_SLIDER: + ia_role_ = ROLE_SYSTEM_SLIDER; + break; + case AccessibilityNodeData::ROLE_SPIN_BUTTON: + ia_role_ = ROLE_SYSTEM_SPINBUTTON; + break; + case AccessibilityNodeData::ROLE_SPIN_BUTTON_PART: + ia_role_ = ROLE_SYSTEM_PUSHBUTTON; + break; + case AccessibilityNodeData::ROLE_SPLIT_GROUP: + ia_role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_SPLIT_PANE; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_ANNOTATION: + case AccessibilityNodeData::ROLE_STATIC_TEXT: + ia_role_ = ROLE_SYSTEM_TEXT; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_STATUS: + ia_role_ = ROLE_SYSTEM_STATUSBAR; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_SPLITTER: + ia_role_ = ROLE_SYSTEM_SEPARATOR; + break; + case AccessibilityNodeData::ROLE_SVG_ROOT: + ia_role_ = ROLE_SYSTEM_GRAPHIC; + break; + case AccessibilityNodeData::ROLE_TAB: + ia_role_ = ROLE_SYSTEM_PAGETAB; + break; + case AccessibilityNodeData::ROLE_TABLE: { + string16 aria_role; + GetStringAttribute(AccessibilityNodeData::ATTR_ROLE, &aria_role); + if (aria_role == L"treegrid") { + ia_role_ = ROLE_SYSTEM_OUTLINE; + } else { + ia_role_ = ROLE_SYSTEM_TABLE; + ia_state_ |= STATE_SYSTEM_READONLY; + } + break; + } + case AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_TAB_GROUP_UNUSED: + NOTREACHED(); + ia_role_ = ROLE_SYSTEM_PAGETABLIST; + break; + case AccessibilityNodeData::ROLE_TAB_LIST: + ia_role_ = ROLE_SYSTEM_PAGETABLIST; + break; + case AccessibilityNodeData::ROLE_TAB_PANEL: + ia_role_ = ROLE_SYSTEM_PROPERTYPAGE; + break; + case AccessibilityNodeData::ROLE_TOGGLE_BUTTON: + ia_role_ = ROLE_SYSTEM_PUSHBUTTON; + ia2_role_ = IA2_ROLE_TOGGLE_BUTTON; + break; + case AccessibilityNodeData::ROLE_TEXTAREA: + ia_role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_MULTI_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + ia2_state_ |= IA2_STATE_SELECTABLE_TEXT; + break; + case AccessibilityNodeData::ROLE_TEXT_FIELD: + ia_role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_SINGLE_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + ia2_state_ |= IA2_STATE_SELECTABLE_TEXT; + break; + case AccessibilityNodeData::ROLE_TIMER: + ia_role_ = ROLE_SYSTEM_CLOCK; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_TOOLBAR: + ia_role_ = ROLE_SYSTEM_TOOLBAR; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_TOOLTIP: + ia_role_ = ROLE_SYSTEM_TOOLTIP; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_TREE: + ia_role_ = ROLE_SYSTEM_OUTLINE; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_TREE_GRID: + ia_role_ = ROLE_SYSTEM_OUTLINE; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_TREE_ITEM: + ia_role_ = ROLE_SYSTEM_OUTLINEITEM; + ia_state_ |= STATE_SYSTEM_READONLY; + break; + case AccessibilityNodeData::ROLE_WINDOW: + ia_role_ = ROLE_SYSTEM_WINDOW; + break; + + // TODO(dmazzoni): figure out the proper MSAA role for all of these. + case AccessibilityNodeData::ROLE_BROWSER: + case AccessibilityNodeData::ROLE_DIRECTORY: + case AccessibilityNodeData::ROLE_DRAWER: + case AccessibilityNodeData::ROLE_HELP_TAG: + case AccessibilityNodeData::ROLE_IGNORED: + case AccessibilityNodeData::ROLE_INCREMENTOR: + case AccessibilityNodeData::ROLE_LOG: + case AccessibilityNodeData::ROLE_MARQUEE: + case AccessibilityNodeData::ROLE_MATTE: + case AccessibilityNodeData::ROLE_PRESENTATIONAL: + case AccessibilityNodeData::ROLE_RULER_MARKER: + case AccessibilityNodeData::ROLE_SHEET: + case AccessibilityNodeData::ROLE_SLIDER_THUMB: + case AccessibilityNodeData::ROLE_SYSTEM_WIDE: + case AccessibilityNodeData::ROLE_VALUE_INDICATOR: + default: + ia_role_ = ROLE_SYSTEM_CLIENT; + break; + } + + // Compute the final value of READONLY for MSAA. + // + // We always set the READONLY state for elements that have the + // aria-readonly attribute and for a few roles (in the switch above). + // We clear the READONLY state on focusable controls and on a document. + // Everything else, the majority of objects, do not have this state set. + if (HasState(AccessibilityNodeData::STATE_FOCUSABLE) && + ia_role_ != ROLE_SYSTEM_DOCUMENT) { + ia_state_ &= ~(STATE_SYSTEM_READONLY); + } + if (!HasState(AccessibilityNodeData::STATE_READONLY)) + ia_state_ &= ~(STATE_SYSTEM_READONLY); + bool aria_readonly = false; + GetBoolAttribute(AccessibilityNodeData::ATTR_ARIA_READONLY, &aria_readonly); + if (aria_readonly) + ia_state_ |= STATE_SYSTEM_READONLY; + + // The role should always be set. + DCHECK(!role_name_.empty() || ia_role_); + + // If we didn't explicitly set the IAccessible2 role, make it the same + // as the MSAA role. + if (!ia2_role_) + ia2_role_ = ia_role_; +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/browser_accessibility_win.h b/chromium/content/browser/accessibility/browser_accessibility_win.h new file mode 100644 index 00000000000..b6599325da8 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_win.h @@ -0,0 +1,899 @@ +// Copyright (c) 2012 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_WIN_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <oleacc.h> +#include <UIAutomationCore.h> + +#include <vector> + +#include "base/compiler_specific.h" +#include "content/browser/accessibility/browser_accessibility.h" +#include "content/common/content_export.h" +#include "third_party/iaccessible2/ia2_api_all.h" +#include "third_party/isimpledom/ISimpleDOMDocument.h" +#include "third_party/isimpledom/ISimpleDOMNode.h" +#include "third_party/isimpledom/ISimpleDOMText.h" + +namespace ui { +enum TextBoundaryDirection; +enum TextBoundaryType; +} + +namespace content { +class BrowserAccessibilityRelation; + +//////////////////////////////////////////////////////////////////////////////// +// +// BrowserAccessibilityWin +// +// Class implementing the windows accessible interface for the Browser-Renderer +// communication of accessibility information, providing accessibility +// to be used by screen readers and other assistive technology (AT). +// +//////////////////////////////////////////////////////////////////////////////// +class __declspec(uuid("562072fe-3390-43b1-9e2c-dd4118f5ac79")) +BrowserAccessibilityWin + : public BrowserAccessibility, + public CComObjectRootEx<CComMultiThreadModel>, + public IDispatchImpl<IAccessible2, &IID_IAccessible2, + &LIBID_IAccessible2Lib>, + public IAccessibleApplication, + public IAccessibleHyperlink, + public IAccessibleHypertext, + public IAccessibleImage, + public IAccessibleTable, + public IAccessibleTable2, + public IAccessibleTableCell, + public IAccessibleValue, + public IServiceProvider, + public ISimpleDOMDocument, + public ISimpleDOMNode, + public ISimpleDOMText, + public IAccessibleEx, + public IRawElementProviderSimple { + public: + BEGIN_COM_MAP(BrowserAccessibilityWin) + COM_INTERFACE_ENTRY2(IDispatch, IAccessible2) + COM_INTERFACE_ENTRY2(IAccessible, IAccessible2) + COM_INTERFACE_ENTRY2(IAccessibleText, IAccessibleHypertext) + COM_INTERFACE_ENTRY(IAccessible2) + COM_INTERFACE_ENTRY(IAccessibleApplication) + COM_INTERFACE_ENTRY(IAccessibleHyperlink) + COM_INTERFACE_ENTRY(IAccessibleHypertext) + COM_INTERFACE_ENTRY(IAccessibleImage) + COM_INTERFACE_ENTRY(IAccessibleTable) + COM_INTERFACE_ENTRY(IAccessibleTable2) + COM_INTERFACE_ENTRY(IAccessibleTableCell) + COM_INTERFACE_ENTRY(IAccessibleValue) + COM_INTERFACE_ENTRY(IServiceProvider) + COM_INTERFACE_ENTRY(ISimpleDOMDocument) + COM_INTERFACE_ENTRY(ISimpleDOMNode) + COM_INTERFACE_ENTRY(ISimpleDOMText) + COM_INTERFACE_ENTRY(IAccessibleEx) + COM_INTERFACE_ENTRY(IRawElementProviderSimple) + END_COM_MAP() + + // Represents a non-static text node in IAccessibleHypertext. This character + // is embedded in the response to IAccessibleText::get_text, indicating the + // position where a non-static text child object appears. + CONTENT_EXPORT static const char16 kEmbeddedCharacter[]; + + // Mappings from roles and states to human readable strings. Initialize + // with |InitializeStringMaps|. + static std::map<int32, string16> role_string_map; + static std::map<int32, string16> state_string_map; + + CONTENT_EXPORT BrowserAccessibilityWin(); + + CONTENT_EXPORT virtual ~BrowserAccessibilityWin(); + + // The Windows-specific unique ID, used as the child ID for MSAA methods + // like NotifyWinEvent, and as the unique ID for IAccessible2 and ISimpleDOM. + LONG unique_id_win() const { return unique_id_win_; } + + // + // BrowserAccessibility methods. + // + CONTENT_EXPORT virtual void PreInitialize() OVERRIDE; + CONTENT_EXPORT virtual void PostInitialize() OVERRIDE; + CONTENT_EXPORT virtual void NativeAddReference() OVERRIDE; + CONTENT_EXPORT virtual void NativeReleaseReference() OVERRIDE; + CONTENT_EXPORT virtual bool IsNative() const OVERRIDE; + CONTENT_EXPORT virtual void SetLocation(const gfx::Rect& new_location) + OVERRIDE; + + // + // IAccessible methods. + // + + // Performs the default action on a given object. + CONTENT_EXPORT STDMETHODIMP accDoDefaultAction(VARIANT var_id); + + // Retrieves the child element or child object at a given point on the screen. + CONTENT_EXPORT STDMETHODIMP accHitTest(LONG x_left, LONG y_top, + VARIANT* child); + + // Retrieves the specified object's current screen location. + CONTENT_EXPORT STDMETHODIMP accLocation(LONG* x_left, + LONG* y_top, + LONG* width, + LONG* height, + VARIANT var_id); + + // Traverses to another UI element and retrieves the object. + CONTENT_EXPORT STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, + VARIANT* end); + + // Retrieves an IDispatch interface pointer for the specified child. + CONTENT_EXPORT STDMETHODIMP get_accChild(VARIANT var_child, + IDispatch** disp_child); + + // Retrieves the number of accessible children. + CONTENT_EXPORT STDMETHODIMP get_accChildCount(LONG* child_count); + + // Retrieves a string that describes the object's default action. + CONTENT_EXPORT STDMETHODIMP get_accDefaultAction(VARIANT var_id, + BSTR* default_action); + + // Retrieves the object's description. + CONTENT_EXPORT STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc); + + // Retrieves the object that has the keyboard focus. + CONTENT_EXPORT STDMETHODIMP get_accFocus(VARIANT* focus_child); + + // Retrieves the help information associated with the object. + CONTENT_EXPORT STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* heflp); + + // Retrieves the specified object's shortcut. + CONTENT_EXPORT STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, + BSTR* access_key); + + // Retrieves the name of the specified object. + CONTENT_EXPORT STDMETHODIMP get_accName(VARIANT var_id, BSTR* name); + + // Retrieves the IDispatch interface of the object's parent. + CONTENT_EXPORT STDMETHODIMP get_accParent(IDispatch** disp_parent); + + // Retrieves information describing the role of the specified object. + CONTENT_EXPORT STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role); + + // Retrieves the current state of the specified object. + CONTENT_EXPORT STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state); + + // Returns the value associated with the object. + CONTENT_EXPORT STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value); + + // Make an object take focus or extend the selection. + CONTENT_EXPORT STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id); + + CONTENT_EXPORT STDMETHODIMP get_accHelpTopic(BSTR* help_file, + VARIANT var_id, + LONG* topic_id); + + CONTENT_EXPORT STDMETHODIMP get_accSelection(VARIANT* selected); + + // Deprecated methods, not implemented. + CONTENT_EXPORT STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP put_accValue(VARIANT var_id, BSTR put_val) { + return E_NOTIMPL; + } + + // + // IAccessible2 methods. + // + + // Returns role from a longer list of possible roles. + CONTENT_EXPORT STDMETHODIMP role(LONG* role); + + // Returns the state bitmask from a larger set of possible states. + CONTENT_EXPORT STDMETHODIMP get_states(AccessibleStates* states); + + // Returns the attributes specific to this IAccessible2 object, + // such as a cell's formula. + CONTENT_EXPORT STDMETHODIMP get_attributes(BSTR* attributes); + + // Get the unique ID of this object so that the client knows if it's + // been encountered previously. + CONTENT_EXPORT STDMETHODIMP get_uniqueID(LONG* unique_id); + + // Get the window handle of the enclosing window. + CONTENT_EXPORT STDMETHODIMP get_windowHandle(HWND* window_handle); + + // Get this object's index in its parent object. + CONTENT_EXPORT STDMETHODIMP get_indexInParent(LONG* index_in_parent); + + CONTENT_EXPORT STDMETHODIMP get_nRelations(LONG* n_relations); + + CONTENT_EXPORT STDMETHODIMP get_relation(LONG relation_index, + IAccessibleRelation** relation); + + CONTENT_EXPORT STDMETHODIMP get_relations(LONG max_relations, + IAccessibleRelation** relations, + LONG* n_relations); + + CONTENT_EXPORT STDMETHODIMP scrollTo(enum IA2ScrollType scroll_type); + + CONTENT_EXPORT STDMETHODIMP scrollToPoint( + enum IA2CoordinateType coordinate_type, + LONG x, + LONG y); + + CONTENT_EXPORT STDMETHODIMP get_groupPosition(LONG* group_level, + LONG* similar_items_in_group, + LONG* position_in_group); + + // + // IAccessibleEx methods not implemented. + // + CONTENT_EXPORT STDMETHODIMP get_extendedRole(BSTR* extended_role) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_localizedExtendedRole( + BSTR* localized_extended_role) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_nExtendedStates(LONG* n_extended_states) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_extendedStates(LONG max_extended_states, + BSTR** extended_states, + LONG* n_extended_states) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_localizedExtendedStates( + LONG max_localized_extended_states, + BSTR** localized_extended_states, + LONG* n_localized_extended_states) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_locale(IA2Locale* locale) { + return E_NOTIMPL; + } + + // + // IAccessibleApplication methods. + // + CONTENT_EXPORT STDMETHODIMP get_appName(BSTR* app_name); + + CONTENT_EXPORT STDMETHODIMP get_appVersion(BSTR* app_version); + + CONTENT_EXPORT STDMETHODIMP get_toolkitName(BSTR* toolkit_name); + + CONTENT_EXPORT STDMETHODIMP get_toolkitVersion(BSTR* toolkit_version); + + // + // IAccessibleImage methods. + // + CONTENT_EXPORT STDMETHODIMP get_description(BSTR* description); + + CONTENT_EXPORT STDMETHODIMP get_imagePosition( + enum IA2CoordinateType coordinate_type, + LONG* x, + LONG* y); + + CONTENT_EXPORT STDMETHODIMP get_imageSize(LONG* height, LONG* width); + + // + // IAccessibleTable methods. + // + + // get_description - also used by IAccessibleImage + + CONTENT_EXPORT STDMETHODIMP get_accessibleAt(long row, + long column, + IUnknown** accessible); + + CONTENT_EXPORT STDMETHODIMP get_caption(IUnknown** accessible); + + CONTENT_EXPORT STDMETHODIMP get_childIndex(long row_index, + long column_index, + long* cell_index); + + CONTENT_EXPORT STDMETHODIMP get_columnDescription(long column, + BSTR* description); + + CONTENT_EXPORT STDMETHODIMP get_columnExtentAt(long row, + long column, + long* n_columns_spanned); + + CONTENT_EXPORT STDMETHODIMP get_columnHeader( + IAccessibleTable** accessible_table, + long* starting_row_index); + + CONTENT_EXPORT STDMETHODIMP get_columnIndex(long cell_index, + long* column_index); + + CONTENT_EXPORT STDMETHODIMP get_nColumns(long* column_count); + + CONTENT_EXPORT STDMETHODIMP get_nRows(long* row_count); + + CONTENT_EXPORT STDMETHODIMP get_nSelectedChildren(long* cell_count); + + CONTENT_EXPORT STDMETHODIMP get_nSelectedColumns(long* column_count); + + CONTENT_EXPORT STDMETHODIMP get_nSelectedRows(long *row_count); + + CONTENT_EXPORT STDMETHODIMP get_rowDescription(long row, + BSTR* description); + + CONTENT_EXPORT STDMETHODIMP get_rowExtentAt(long row, + long column, + long* n_rows_spanned); + + CONTENT_EXPORT STDMETHODIMP get_rowHeader(IAccessibleTable** accessible_table, + long* starting_column_index); + + CONTENT_EXPORT STDMETHODIMP get_rowIndex(long cell_index, + long* row_index); + + CONTENT_EXPORT STDMETHODIMP get_selectedChildren(long max_children, + long** children, + long* n_children); + + CONTENT_EXPORT STDMETHODIMP get_selectedColumns(long max_columns, + long** columns, + long* n_columns); + + CONTENT_EXPORT STDMETHODIMP get_selectedRows(long max_rows, + long** rows, + long* n_rows); + + CONTENT_EXPORT STDMETHODIMP get_summary(IUnknown** accessible); + + CONTENT_EXPORT STDMETHODIMP get_isColumnSelected(long column, + boolean* is_selected); + + CONTENT_EXPORT STDMETHODIMP get_isRowSelected(long row, + boolean* is_selected); + + CONTENT_EXPORT STDMETHODIMP get_isSelected(long row, + long column, + boolean* is_selected); + + CONTENT_EXPORT STDMETHODIMP get_rowColumnExtentsAtIndex(long index, + long* row, + long* column, + long* row_extents, + long* column_extents, + boolean* is_selected); + + CONTENT_EXPORT STDMETHODIMP selectRow(long row) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP selectColumn(long column) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP unselectRow(long row) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP unselectColumn(long column) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP get_modelChange( + IA2TableModelChange* model_change) { + return E_NOTIMPL; + } + + // + // IAccessibleTable2 methods. + // + // (Most of these are duplicates of IAccessibleTable methods, only the + // unique ones are included here.) + // + + CONTENT_EXPORT STDMETHODIMP get_cellAt(long row, + long column, + IUnknown** cell); + + CONTENT_EXPORT STDMETHODIMP get_nSelectedCells(long* cell_count); + + CONTENT_EXPORT STDMETHODIMP get_selectedCells(IUnknown*** cells, + long* n_selected_cells); + + CONTENT_EXPORT STDMETHODIMP get_selectedColumns(long** columns, + long* n_columns); + + CONTENT_EXPORT STDMETHODIMP get_selectedRows(long** rows, + long* n_rows); + + // + // IAccessibleTableCell methods. + // + + CONTENT_EXPORT STDMETHODIMP get_columnExtent(long* n_columns_spanned); + + CONTENT_EXPORT STDMETHODIMP get_columnHeaderCells( + IUnknown*** cell_accessibles, + long* n_column_header_cells); + + CONTENT_EXPORT STDMETHODIMP get_columnIndex(long* column_index); + + CONTENT_EXPORT STDMETHODIMP get_rowExtent(long* n_rows_spanned); + + CONTENT_EXPORT STDMETHODIMP get_rowHeaderCells(IUnknown*** cell_accessibles, + long* n_row_header_cells); + + CONTENT_EXPORT STDMETHODIMP get_rowIndex(long* row_index); + + CONTENT_EXPORT STDMETHODIMP get_isSelected(boolean* is_selected); + + CONTENT_EXPORT STDMETHODIMP get_rowColumnExtents(long* row, + long* column, + long* row_extents, + long* column_extents, + boolean* is_selected); + + CONTENT_EXPORT STDMETHODIMP get_table(IUnknown** table); + + // + // IAccessibleText methods. + // + + CONTENT_EXPORT STDMETHODIMP get_nCharacters(LONG* n_characters); + + CONTENT_EXPORT STDMETHODIMP get_caretOffset(LONG* offset); + + CONTENT_EXPORT STDMETHODIMP get_nSelections(LONG* n_selections); + + CONTENT_EXPORT STDMETHODIMP get_selection(LONG selection_index, + LONG* start_offset, + LONG* end_offset); + + CONTENT_EXPORT STDMETHODIMP get_text(LONG start_offset, + LONG end_offset, + BSTR* text); + + CONTENT_EXPORT STDMETHODIMP get_textAtOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, + LONG* end_offset, + BSTR* text); + + CONTENT_EXPORT STDMETHODIMP get_textBeforeOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, + LONG* end_offset, + BSTR* text); + + CONTENT_EXPORT STDMETHODIMP get_textAfterOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, + LONG* end_offset, + BSTR* text); + + CONTENT_EXPORT STDMETHODIMP get_newText(IA2TextSegment* new_text); + + CONTENT_EXPORT STDMETHODIMP get_oldText(IA2TextSegment* old_text); + + CONTENT_EXPORT STDMETHODIMP get_offsetAtPoint( + LONG x, + LONG y, + enum IA2CoordinateType coord_type, + LONG* offset); + + CONTENT_EXPORT STDMETHODIMP scrollSubstringTo( + LONG start_index, + LONG end_index, + enum IA2ScrollType scroll_type); + + CONTENT_EXPORT STDMETHODIMP scrollSubstringToPoint( + LONG start_index, + LONG end_index, + enum IA2CoordinateType coordinate_type, + LONG x, LONG y); + + CONTENT_EXPORT STDMETHODIMP addSelection(LONG start_offset, LONG end_offset); + + CONTENT_EXPORT STDMETHODIMP removeSelection(LONG selection_index); + + CONTENT_EXPORT STDMETHODIMP setCaretOffset(LONG offset); + + CONTENT_EXPORT STDMETHODIMP setSelection(LONG selection_index, + LONG start_offset, + LONG end_offset); + + // IAccessibleText methods not implemented. + CONTENT_EXPORT STDMETHODIMP get_attributes(LONG offset, LONG* start_offset, + LONG* end_offset, + BSTR* text_attributes) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_characterExtents(LONG offset, + enum IA2CoordinateType coord_type, + LONG* x, + LONG* y, + LONG* width, + LONG* height) { + return E_NOTIMPL; + } + + // + // IAccessibleHypertext methods. + // + + CONTENT_EXPORT STDMETHODIMP get_nHyperlinks(long* hyperlink_count); + + CONTENT_EXPORT STDMETHODIMP get_hyperlink(long index, + IAccessibleHyperlink** hyperlink); + + CONTENT_EXPORT STDMETHODIMP get_hyperlinkIndex(long char_index, + long* hyperlink_index); + + // IAccessibleHyperlink not implemented. + CONTENT_EXPORT STDMETHODIMP get_anchor(long index, VARIANT* anchor) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_anchorTarget(long index, + VARIANT* anchor_target) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_startIndex( long* index) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_endIndex( long* index) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_valid(boolean* valid) { + return E_NOTIMPL; + } + + // IAccessibleAction not implemented. + CONTENT_EXPORT STDMETHODIMP nActions(long* n_actions) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP doAction(long action_index) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_description(long action_index, + BSTR* description) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_keyBinding(long action_index, + long n_max_bindings, + BSTR** key_bindings, + long* n_bindings) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_name(long action_index, BSTR* name) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP get_localizedName(long action_index, + BSTR* localized_name) { + return E_NOTIMPL; + } + + // + // IAccessibleValue methods. + // + + CONTENT_EXPORT STDMETHODIMP get_currentValue(VARIANT* value); + + CONTENT_EXPORT STDMETHODIMP get_minimumValue(VARIANT* value); + + CONTENT_EXPORT STDMETHODIMP get_maximumValue(VARIANT* value); + + CONTENT_EXPORT STDMETHODIMP setCurrentValue(VARIANT new_value); + + // + // ISimpleDOMDocument methods. + // + + CONTENT_EXPORT STDMETHODIMP get_URL(BSTR* url); + + CONTENT_EXPORT STDMETHODIMP get_title(BSTR* title); + + CONTENT_EXPORT STDMETHODIMP get_mimeType(BSTR* mime_type); + + CONTENT_EXPORT STDMETHODIMP get_docType(BSTR* doc_type); + + CONTENT_EXPORT STDMETHODIMP get_nameSpaceURIForID(short name_space_id, + BSTR* name_space_uri) { + return E_NOTIMPL; + } + CONTENT_EXPORT STDMETHODIMP put_alternateViewMediaTypes( + BSTR* comma_separated_media_types) { + return E_NOTIMPL; + } + + // + // ISimpleDOMNode methods. + // + + CONTENT_EXPORT STDMETHODIMP get_nodeInfo(BSTR* node_name, + short* name_space_id, + BSTR* node_value, + unsigned int* num_children, + unsigned int* unique_id, + unsigned short* node_type); + + CONTENT_EXPORT STDMETHODIMP get_attributes(unsigned short max_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values, + unsigned short* num_attribs); + + CONTENT_EXPORT STDMETHODIMP get_attributesForNames( + unsigned short num_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values); + + CONTENT_EXPORT STDMETHODIMP get_computedStyle( + unsigned short max_style_properties, + boolean use_alternate_view, + BSTR *style_properties, + BSTR *style_values, + unsigned short *num_style_properties); + + CONTENT_EXPORT STDMETHODIMP get_computedStyleForProperties( + unsigned short num_style_properties, + boolean use_alternate_view, + BSTR* style_properties, + BSTR* style_values); + + CONTENT_EXPORT STDMETHODIMP scrollTo(boolean placeTopLeft); + + CONTENT_EXPORT STDMETHODIMP get_parentNode(ISimpleDOMNode** node); + + CONTENT_EXPORT STDMETHODIMP get_firstChild(ISimpleDOMNode** node); + + CONTENT_EXPORT STDMETHODIMP get_lastChild(ISimpleDOMNode** node); + + CONTENT_EXPORT STDMETHODIMP get_previousSibling(ISimpleDOMNode** node); + + CONTENT_EXPORT STDMETHODIMP get_nextSibling(ISimpleDOMNode** node); + + CONTENT_EXPORT STDMETHODIMP get_childAt(unsigned int child_index, + ISimpleDOMNode** node); + + CONTENT_EXPORT STDMETHODIMP get_innerHTML(BSTR* innerHTML) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP get_localInterface(void** local_interface) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP get_language(BSTR* language) { + return E_NOTIMPL; + } + + // + // ISimpleDOMText methods. + // + + CONTENT_EXPORT STDMETHODIMP get_domText(BSTR* dom_text); + + CONTENT_EXPORT STDMETHODIMP get_clippedSubstringBounds( + unsigned int start_index, + unsigned int end_index, + int* x, + int* y, + int* width, + int* height) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP get_unclippedSubstringBounds( + unsigned int start_index, + unsigned int end_index, + int* x, + int* y, + int* width, + int* height) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP scrollToSubstring(unsigned int start_index, + unsigned int end_index) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP get_fontFamily(BSTR *font_family) { + return E_NOTIMPL; + } + + // + // IServiceProvider methods. + // + + CONTENT_EXPORT STDMETHODIMP QueryService(REFGUID guidService, + REFIID riid, + void** object); + + // IAccessibleEx methods not implemented. + CONTENT_EXPORT STDMETHODIMP GetObjectForChild(long child_id, + IAccessibleEx** ret) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP GetIAccessiblePair(IAccessible** acc, + long* child_id) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP GetRuntimeId(SAFEARRAY** runtime_id) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP ConvertReturnedElement( + IRawElementProviderSimple* element, + IAccessibleEx** acc) { + return E_NOTIMPL; + } + + // + // IRawElementProviderSimple methods. + // + // The GetPatternProvider/GetPropertyValue methods need to be implemented for + // the on-screen keyboard to show up in Windows 8 metro. + CONTENT_EXPORT STDMETHODIMP GetPatternProvider(PATTERNID id, + IUnknown** provider); + CONTENT_EXPORT STDMETHODIMP GetPropertyValue(PROPERTYID id, VARIANT* ret); + + // + // IRawElementProviderSimple methods not implemented + // + CONTENT_EXPORT STDMETHODIMP get_ProviderOptions(enum ProviderOptions* ret) { + return E_NOTIMPL; + } + + CONTENT_EXPORT STDMETHODIMP get_HostRawElementProvider( + IRawElementProviderSimple** provider) { + return E_NOTIMPL; + } + + // + // CComObjectRootEx methods. + // + + CONTENT_EXPORT HRESULT WINAPI InternalQueryInterface( + void* this_ptr, + const _ATL_INTMAP_ENTRY* entries, + REFIID iid, + void** object); + + // Accessors. + int32 ia_role() const { return ia_role_; } + int32 ia_state() const { return ia_state_; } + int32 ia2_role() const { return ia2_role_; } + int32 ia2_state() const { return ia2_state_; } + const std::vector<string16>& ia2_attributes() const { + return ia2_attributes_; + } + + private: + // Add one to the reference count and return the same object. Always + // use this method when returning a BrowserAccessibilityWin object as + // an output parameter to a COM interface, never use it otherwise. + BrowserAccessibilityWin* NewReference(); + + // Many MSAA methods take a var_id parameter indicating that the operation + // should be performed on a particular child ID, rather than this object. + // This method tries to figure out the target object from |var_id| and + // returns a pointer to the target object if it exists, otherwise NULL. + // Does not return a new reference. + BrowserAccessibilityWin* GetTargetFromChildID(const VARIANT& var_id); + + // Initialize the role and state metadata from the role enum and state + // bitmasks defined in AccessibilityNodeData. + void InitRoleAndState(); + + // Retrieve the value of an attribute from the string attribute map and + // if found and nonempty, allocate a new BSTR (with SysAllocString) + // and return S_OK. If not found or empty, return S_FALSE. + HRESULT GetStringAttributeAsBstr( + AccessibilityNodeData::StringAttribute attribute, + BSTR* value_bstr); + + // If the string attribute |attribute| is present, add its value as an + // IAccessible2 attribute with the name |ia2_attr|. + void StringAttributeToIA2(AccessibilityNodeData::StringAttribute attribute, + const char* ia2_attr); + + // If the bool attribute |attribute| is present, add its value as an + // IAccessible2 attribute with the name |ia2_attr|. + void BoolAttributeToIA2(AccessibilityNodeData::BoolAttribute attribute, + const char* ia2_attr); + + // If the int attribute |attribute| is present, add its value as an + // IAccessible2 attribute with the name |ia2_attr|. + void IntAttributeToIA2(AccessibilityNodeData::IntAttribute attribute, + const char* ia2_attr); + + // Get the text of this node for the purposes of IAccessibleText - it may + // be the name, it may be the value, etc. depending on the role. + const string16& TextForIAccessibleText(); + + // If offset is a member of IA2TextSpecialOffsets this function updates the + // value of offset and returns, otherwise offset remains unchanged. + void HandleSpecialTextOffset(const string16& text, LONG* offset); + + // Convert from a IA2TextBoundaryType to a ui::TextBoundaryType. + ui::TextBoundaryType IA2TextBoundaryToTextBoundary(IA2TextBoundaryType type); + + // Search forwards (direction == 1) or backwards (direction == -1) + // from the given offset until the given boundary is found, and + // return the offset of that boundary. + LONG FindBoundary(const string16& text, + IA2TextBoundaryType ia2_boundary, + LONG start_offset, + ui::TextBoundaryDirection direction); + + // Return a pointer to the object corresponding to the given renderer_id, + // does not make a new reference. + BrowserAccessibilityWin* GetFromRendererID(int32 renderer_id); + + // Windows-specific unique ID (unique within the browser process), + // used for get_accChild, NotifyWinEvent, and as the unique ID for + // IAccessible2 and ISimpleDOM. + LONG unique_id_win_; + + // IAccessible role and state. + int32 ia_role_; + int32 ia_state_; + + // IAccessible2 role and state. + int32 ia2_role_; + int32 ia2_state_; + + // IAccessible2 attributes. + std::vector<string16> ia2_attributes_; + + // True in Initialize when the object is first created, and false + // subsequent times. + bool first_time_; + + // The previous text, before the last update to this object. + string16 previous_text_; + + // The old text to return in IAccessibleText::get_oldText - this is like + // previous_text_ except that it's NOT updated when the object + // is initialized again but the text doesn't change. + string16 old_text_; + + // The previous state, used to see if there was a state change. + int32 old_ia_state_; + + // Relationships between this node and other nodes. + std::vector<BrowserAccessibilityRelation*> relations_; + + // The text of this node including embedded hyperlink characters. + string16 hypertext_; + + // Maps the |hypertext_| embedded character offset to an index in + // |hyperlinks_|. + std::map<int32, int32> hyperlink_offset_to_index_; + + // Collection of non-static text child indicies, each of which corresponds to + // a hyperlink. + std::vector<int32> hyperlinks_; + + // The next unique id to use. + static LONG next_unique_id_win_; + + // Give BrowserAccessibility::Create access to our constructor. + friend class BrowserAccessibility; + friend class BrowserAccessibilityRelation; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityWin); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_ diff --git a/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc b/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc new file mode 100644 index 00000000000..98a5d404a43 --- /dev/null +++ b/chromium/content/browser/accessibility/browser_accessibility_win_unittest.cc @@ -0,0 +1,660 @@ +// Copyright (c) 2012 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 "base/memory/scoped_ptr.h" +#include "base/win/scoped_bstr.h" +#include "base/win/scoped_comptr.h" +#include "base/win/scoped_variant.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/browser/accessibility/browser_accessibility_manager_win.h" +#include "content/browser/accessibility/browser_accessibility_win.h" +#include "content/common/accessibility_messages.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/win/atl_module.h" + +namespace content { +namespace { + + +// CountedBrowserAccessibility ------------------------------------------------ + +// Subclass of BrowserAccessibilityWin that counts the number of instances. +class CountedBrowserAccessibility : public BrowserAccessibilityWin { + public: + CountedBrowserAccessibility(); + virtual ~CountedBrowserAccessibility(); + + static void reset() { num_instances_ = 0; } + static int num_instances() { return num_instances_; } + + private: + static int num_instances_; + + DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibility); +}; + +// static +int CountedBrowserAccessibility::num_instances_ = 0; + +CountedBrowserAccessibility::CountedBrowserAccessibility() { + ++num_instances_; +} + +CountedBrowserAccessibility::~CountedBrowserAccessibility() { + --num_instances_; +} + + +// CountedBrowserAccessibilityFactory ----------------------------------------- + +// Factory that creates a CountedBrowserAccessibility. +class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory { + public: + CountedBrowserAccessibilityFactory(); + + private: + virtual ~CountedBrowserAccessibilityFactory(); + + virtual BrowserAccessibility* Create() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibilityFactory); +}; + +CountedBrowserAccessibilityFactory::CountedBrowserAccessibilityFactory() { +} + +CountedBrowserAccessibilityFactory::~CountedBrowserAccessibilityFactory() { +} + +BrowserAccessibility* CountedBrowserAccessibilityFactory::Create() { + CComObject<CountedBrowserAccessibility>* instance; + HRESULT hr = CComObject<CountedBrowserAccessibility>::CreateInstance( + &instance); + DCHECK(SUCCEEDED(hr)); + instance->AddRef(); + return instance; +} + +} // namespace + + +// BrowserAccessibilityTest --------------------------------------------------- + +class BrowserAccessibilityTest : public testing::Test { + public: + BrowserAccessibilityTest(); + virtual ~BrowserAccessibilityTest(); + + private: + virtual void SetUp() OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityTest); +}; + +BrowserAccessibilityTest::BrowserAccessibilityTest() { +} + +BrowserAccessibilityTest::~BrowserAccessibilityTest() { +} + +void BrowserAccessibilityTest::SetUp() { + ui::win::CreateATLModuleIfNeeded(); +} + + +// Actual tests --------------------------------------------------------------- + +// Test that BrowserAccessibilityManager correctly releases the tree of +// BrowserAccessibility instances upon delete. +TEST_F(BrowserAccessibilityTest, TestNoLeaks) { + // Create AccessibilityNodeData objects for a simple document tree, + // representing the accessibility information used to initialize + // BrowserAccessibilityManager. + AccessibilityNodeData button; + button.id = 2; + button.name = L"Button"; + button.role = AccessibilityNodeData::ROLE_BUTTON; + button.state = 0; + + AccessibilityNodeData checkbox; + checkbox.id = 3; + checkbox.name = L"Checkbox"; + checkbox.role = AccessibilityNodeData::ROLE_CHECKBOX; + checkbox.state = 0; + + AccessibilityNodeData root; + root.id = 1; + root.name = L"Document"; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.state = 0; + root.child_ids.push_back(2); + root.child_ids.push_back(3); + + // Construct a BrowserAccessibilityManager with this + // AccessibilityNodeData tree and a factory for an instance-counting + // BrowserAccessibility, and ensure that exactly 3 instances were + // created. Note that the manager takes ownership of the factory. + CountedBrowserAccessibility::reset(); + scoped_ptr<BrowserAccessibilityManager> manager( + BrowserAccessibilityManager::Create( + root, NULL, new CountedBrowserAccessibilityFactory())); + manager->UpdateNodesForTesting(button, checkbox); + ASSERT_EQ(3, CountedBrowserAccessibility::num_instances()); + + // Delete the manager and test that all 3 instances are deleted. + manager.reset(); + ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); + + // Construct a manager again, and this time use the IAccessible interface + // to get new references to two of the three nodes in the tree. + manager.reset(BrowserAccessibilityManager::Create( + root, NULL, new CountedBrowserAccessibilityFactory())); + manager->UpdateNodesForTesting(button, checkbox); + ASSERT_EQ(3, CountedBrowserAccessibility::num_instances()); + IAccessible* root_accessible = + manager->GetRoot()->ToBrowserAccessibilityWin(); + IDispatch* root_iaccessible = NULL; + IDispatch* child1_iaccessible = NULL; + base::win::ScopedVariant childid_self(CHILDID_SELF); + HRESULT hr = root_accessible->get_accChild(childid_self, &root_iaccessible); + ASSERT_EQ(S_OK, hr); + base::win::ScopedVariant one(1); + hr = root_accessible->get_accChild(one, &child1_iaccessible); + ASSERT_EQ(S_OK, hr); + + // Now delete the manager, and only one of the three nodes in the tree + // should be released. + manager.reset(); + ASSERT_EQ(2, CountedBrowserAccessibility::num_instances()); + + // Release each of our references and make sure that each one results in + // the instance being deleted as its reference count hits zero. + root_iaccessible->Release(); + ASSERT_EQ(1, CountedBrowserAccessibility::num_instances()); + child1_iaccessible->Release(); + ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); +} + +TEST_F(BrowserAccessibilityTest, TestChildrenChange) { + // Create AccessibilityNodeData objects for a simple document tree, + // representing the accessibility information used to initialize + // BrowserAccessibilityManager. + AccessibilityNodeData text; + text.id = 2; + text.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text.name = L"old text"; + text.state = 0; + + AccessibilityNodeData root; + root.id = 1; + root.name = L"Document"; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.state = 0; + root.child_ids.push_back(2); + + // Construct a BrowserAccessibilityManager with this + // AccessibilityNodeData tree and a factory for an instance-counting + // BrowserAccessibility. + CountedBrowserAccessibility::reset(); + scoped_ptr<BrowserAccessibilityManager> manager( + BrowserAccessibilityManager::Create( + root, NULL, new CountedBrowserAccessibilityFactory())); + manager->UpdateNodesForTesting(text); + + // Query for the text IAccessible and verify that it returns "old text" as its + // value. + base::win::ScopedVariant one(1); + base::win::ScopedComPtr<IDispatch> text_dispatch; + HRESULT hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild( + one, text_dispatch.Receive()); + ASSERT_EQ(S_OK, hr); + + base::win::ScopedComPtr<IAccessible> text_accessible; + hr = text_dispatch.QueryInterface(text_accessible.Receive()); + ASSERT_EQ(S_OK, hr); + + base::win::ScopedVariant childid_self(CHILDID_SELF); + base::win::ScopedBstr name; + hr = text_accessible->get_accName(childid_self, name.Receive()); + ASSERT_EQ(S_OK, hr); + EXPECT_EQ(L"old text", string16(name)); + name.Reset(); + + text_dispatch.Release(); + text_accessible.Release(); + + // Notify the BrowserAccessibilityManager that the text child has changed. + text.name = L"new text"; + AccessibilityHostMsg_NotificationParams param; + param.notification_type = AccessibilityNotificationChildrenChanged; + param.nodes.push_back(text); + param.id = text.id; + std::vector<AccessibilityHostMsg_NotificationParams> notifications; + notifications.push_back(param); + manager->OnAccessibilityNotifications(notifications); + + // Query for the text IAccessible and verify that it now returns "new text" + // as its value. + hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild( + one, text_dispatch.Receive()); + ASSERT_EQ(S_OK, hr); + + hr = text_dispatch.QueryInterface(text_accessible.Receive()); + ASSERT_EQ(S_OK, hr); + + hr = text_accessible->get_accName(childid_self, name.Receive()); + ASSERT_EQ(S_OK, hr); + EXPECT_EQ(L"new text", string16(name)); + + text_dispatch.Release(); + text_accessible.Release(); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + manager.reset(); + ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); +} + +TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) { + // Create AccessibilityNodeData objects for a simple document tree, + // representing the accessibility information used to initialize + // BrowserAccessibilityManager. + AccessibilityNodeData div; + div.id = 2; + div.role = AccessibilityNodeData::ROLE_GROUP; + div.state = 0; + + AccessibilityNodeData text3; + text3.id = 3; + text3.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text3.state = 0; + + AccessibilityNodeData text4; + text4.id = 4; + text4.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text4.state = 0; + + div.child_ids.push_back(3); + div.child_ids.push_back(4); + + AccessibilityNodeData root; + root.id = 1; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.state = 0; + root.child_ids.push_back(2); + + // Construct a BrowserAccessibilityManager with this + // AccessibilityNodeData tree and a factory for an instance-counting + // BrowserAccessibility and ensure that exactly 4 instances were + // created. Note that the manager takes ownership of the factory. + CountedBrowserAccessibility::reset(); + scoped_ptr<BrowserAccessibilityManager> manager( + BrowserAccessibilityManager::Create( + root, NULL, new CountedBrowserAccessibilityFactory())); + manager->UpdateNodesForTesting(div, text3, text4); + ASSERT_EQ(4, CountedBrowserAccessibility::num_instances()); + + // Notify the BrowserAccessibilityManager that the div node and its children + // were removed and ensure that only one BrowserAccessibility instance exists. + root.child_ids.clear(); + AccessibilityHostMsg_NotificationParams param; + param.notification_type = AccessibilityNotificationChildrenChanged; + param.nodes.push_back(root); + param.id = root.id; + std::vector<AccessibilityHostMsg_NotificationParams> notifications; + notifications.push_back(param); + manager->OnAccessibilityNotifications(notifications); + ASSERT_EQ(1, CountedBrowserAccessibility::num_instances()); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + manager.reset(); + ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); +} + +TEST_F(BrowserAccessibilityTest, TestTextBoundaries) { + AccessibilityNodeData text1; + text1.id = 11; + text1.role = AccessibilityNodeData::ROLE_TEXT_FIELD; + text1.state = 0; + text1.value = L"One two three.\nFour five six."; + text1.line_breaks.push_back(15); + + AccessibilityNodeData root; + root.id = 1; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.state = 0; + root.child_ids.push_back(11); + + CountedBrowserAccessibility::reset(); + scoped_ptr<BrowserAccessibilityManager> manager( + BrowserAccessibilityManager::Create( + root, NULL, new CountedBrowserAccessibilityFactory())); + manager->UpdateNodesForTesting(text1); + ASSERT_EQ(2, CountedBrowserAccessibility::num_instances()); + + BrowserAccessibilityWin* root_obj = + manager->GetRoot()->ToBrowserAccessibilityWin(); + BrowserAccessibilityWin* text1_obj = + root_obj->GetChild(0)->ToBrowserAccessibilityWin(); + + long text1_len; + ASSERT_EQ(S_OK, text1_obj->get_nCharacters(&text1_len)); + + base::win::ScopedBstr text; + ASSERT_EQ(S_OK, text1_obj->get_text(0, text1_len, text.Receive())); + ASSERT_EQ(text1.value, string16(text)); + text.Reset(); + + ASSERT_EQ(S_OK, text1_obj->get_text(0, 4, text.Receive())); + ASSERT_STREQ(L"One ", text); + text.Reset(); + + long start; + long end; + ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( + 1, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive())); + ASSERT_EQ(1, start); + ASSERT_EQ(2, end); + ASSERT_STREQ(L"n", text); + text.Reset(); + + ASSERT_EQ(S_FALSE, text1_obj->get_textAtOffset( + text1_len, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive())); + ASSERT_EQ(text1_len, start); + ASSERT_EQ(text1_len, end); + text.Reset(); + + ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( + 1, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive())); + ASSERT_EQ(0, start); + ASSERT_EQ(3, end); + ASSERT_STREQ(L"One", text); + text.Reset(); + + ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( + 6, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive())); + ASSERT_EQ(4, start); + ASSERT_EQ(7, end); + ASSERT_STREQ(L"two", text); + text.Reset(); + + ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( + text1_len, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive())); + ASSERT_EQ(25, start); + ASSERT_EQ(29, end); + ASSERT_STREQ(L"six.", text); + text.Reset(); + + ASSERT_EQ(S_OK, text1_obj->get_textAtOffset( + 1, IA2_TEXT_BOUNDARY_LINE, &start, &end, text.Receive())); + ASSERT_EQ(0, start); + ASSERT_EQ(15, end); + ASSERT_STREQ(L"One two three.\n", text); + text.Reset(); + + ASSERT_EQ(S_OK, + text1_obj->get_text(0, IA2_TEXT_OFFSET_LENGTH, text.Receive())); + ASSERT_STREQ(L"One two three.\nFour five six.", text); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + manager.reset(); + ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); +} + +TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) { + AccessibilityNodeData text1; + text1.id = 11; + text1.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text1.state = 1 << AccessibilityNodeData::STATE_READONLY; + text1.name = L"One two three."; + + AccessibilityNodeData text2; + text2.id = 12; + text2.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text2.state = 1 << AccessibilityNodeData::STATE_READONLY; + text2.name = L" Four five six."; + + AccessibilityNodeData root; + root.id = 1; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.state = 1 << AccessibilityNodeData::STATE_READONLY; + root.child_ids.push_back(11); + root.child_ids.push_back(12); + + CountedBrowserAccessibility::reset(); + scoped_ptr<BrowserAccessibilityManager> manager( + BrowserAccessibilityManager::Create( + root, NULL, new CountedBrowserAccessibilityFactory())); + manager->UpdateNodesForTesting(root, text1, text2); + ASSERT_EQ(3, CountedBrowserAccessibility::num_instances()); + + BrowserAccessibilityWin* root_obj = + manager->GetRoot()->ToBrowserAccessibilityWin(); + + long text_len; + ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len)); + + base::win::ScopedBstr text; + ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive())); + EXPECT_EQ(text1.name + text2.name, string16(text)); + + long hyperlink_count; + ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count)); + EXPECT_EQ(0, hyperlink_count); + + base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink; + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive())); + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(0, hyperlink.Receive())); + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive())); + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(29, hyperlink.Receive())); + + long hyperlink_index; + EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index)); + EXPECT_EQ(-1, hyperlink_index); + EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index)); + EXPECT_EQ(-1, hyperlink_index); + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(-1, &hyperlink_index)); + EXPECT_EQ(-1, hyperlink_index); + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(29, &hyperlink_index)); + EXPECT_EQ(-1, hyperlink_index); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + manager.reset(); + ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); +} + +TEST_F(BrowserAccessibilityTest, TestComplexHypertext) { + AccessibilityNodeData text1; + text1.id = 11; + text1.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text1.state = 1 << AccessibilityNodeData::STATE_READONLY; + text1.name = L"One two three."; + + AccessibilityNodeData text2; + text2.id = 12; + text2.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text2.state = 1 << AccessibilityNodeData::STATE_READONLY; + text2.name = L" Four five six."; + + AccessibilityNodeData button1, button1_text; + button1.id = 13; + button1_text.id = 15; + button1_text.name = L"red"; + button1.role = AccessibilityNodeData::ROLE_BUTTON; + button1_text.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + button1.state = 1 << AccessibilityNodeData::STATE_READONLY; + button1_text.state = 1 << AccessibilityNodeData::STATE_READONLY; + button1.child_ids.push_back(15); + + AccessibilityNodeData link1, link1_text; + link1.id = 14; + link1_text.id = 16; + link1_text.name = L"blue"; + link1.role = AccessibilityNodeData::ROLE_LINK; + link1_text.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + link1.state = 1 << AccessibilityNodeData::STATE_READONLY; + link1_text.state = 1 << AccessibilityNodeData::STATE_READONLY; + link1.child_ids.push_back(16); + + AccessibilityNodeData root; + root.id = 1; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.state = 1 << AccessibilityNodeData::STATE_READONLY; + root.child_ids.push_back(11); + root.child_ids.push_back(13); + root.child_ids.push_back(12); + root.child_ids.push_back(14); + + CountedBrowserAccessibility::reset(); + scoped_ptr<BrowserAccessibilityManager> manager( + BrowserAccessibilityManager::Create( + root, NULL, new CountedBrowserAccessibilityFactory())); + manager->UpdateNodesForTesting(root, + text1, button1, button1_text, + text2, link1, link1_text); + + ASSERT_EQ(7, CountedBrowserAccessibility::num_instances()); + + BrowserAccessibilityWin* root_obj = + manager->GetRoot()->ToBrowserAccessibilityWin(); + + long text_len; + ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len)); + + base::win::ScopedBstr text; + ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive())); + const string16 embed = BrowserAccessibilityWin::kEmbeddedCharacter; + EXPECT_EQ(text1.name + embed + text2.name + embed, string16(text)); + text.Reset(); + + long hyperlink_count; + ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count)); + EXPECT_EQ(2, hyperlink_count); + + base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink; + base::win::ScopedComPtr<IAccessibleText> hypertext; + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive())); + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(2, hyperlink.Receive())); + EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive())); + + EXPECT_EQ(S_OK, root_obj->get_hyperlink(0, hyperlink.Receive())); + EXPECT_EQ(S_OK, + hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive())); + EXPECT_EQ(S_OK, hypertext->get_text(0, 3, text.Receive())); + EXPECT_STREQ(L"red", text); + text.Reset(); + hyperlink.Release(); + hypertext.Release(); + + EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, hyperlink.Receive())); + EXPECT_EQ(S_OK, + hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive())); + EXPECT_EQ(S_OK, hypertext->get_text(0, 4, text.Receive())); + EXPECT_STREQ(L"blue", text); + text.Reset(); + hyperlink.Release(); + hypertext.Release(); + + long hyperlink_index; + EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index)); + EXPECT_EQ(-1, hyperlink_index); + EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index)); + EXPECT_EQ(-1, hyperlink_index); + EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(14, &hyperlink_index)); + EXPECT_EQ(0, hyperlink_index); + EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(30, &hyperlink_index)); + EXPECT_EQ(1, hyperlink_index); + + // Delete the manager and test that all BrowserAccessibility instances are + // deleted. + manager.reset(); + ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); +} + +TEST_F(BrowserAccessibilityTest, TestCreateEmptyDocument) { + // Try creating an empty document with busy state. Readonly is + // set automatically. + CountedBrowserAccessibility::reset(); + const int32 busy_state = 1 << AccessibilityNodeData::STATE_BUSY; + const int32 readonly_state = 1 << AccessibilityNodeData::STATE_READONLY; + scoped_ptr<BrowserAccessibilityManager> manager( + new BrowserAccessibilityManagerWin( + GetDesktopWindow(), + NULL, + BrowserAccessibilityManagerWin::GetEmptyDocument(), + NULL, + new CountedBrowserAccessibilityFactory())); + + // Verify the root is as we expect by default. + BrowserAccessibility* root = manager->GetRoot(); + EXPECT_EQ(0, root->renderer_id()); + EXPECT_EQ(AccessibilityNodeData::ROLE_ROOT_WEB_AREA, root->role()); + EXPECT_EQ(busy_state | readonly_state, root->state()); + + // Tree with a child textfield. + AccessibilityNodeData tree1_1; + tree1_1.id = 1; + tree1_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + tree1_1.child_ids.push_back(2); + + AccessibilityNodeData tree1_2; + tree1_2.id = 2; + tree1_2.role = AccessibilityNodeData::ROLE_TEXT_FIELD; + + // Process a load complete. + std::vector<AccessibilityHostMsg_NotificationParams> params; + params.push_back(AccessibilityHostMsg_NotificationParams()); + AccessibilityHostMsg_NotificationParams* msg = ¶ms[0]; + msg->notification_type = AccessibilityNotificationLoadComplete; + msg->nodes.push_back(tree1_1); + msg->nodes.push_back(tree1_2); + msg->id = tree1_1.id; + manager->OnAccessibilityNotifications(params); + + // Save for later comparison. + BrowserAccessibility* acc1_2 = manager->GetFromRendererID(2); + + // Verify the root has changed. + EXPECT_NE(root, manager->GetRoot()); + + // And the proper child remains. + EXPECT_EQ(AccessibilityNodeData::ROLE_TEXT_FIELD, acc1_2->role()); + EXPECT_EQ(2, acc1_2->renderer_id()); + + // Tree with a child button. + AccessibilityNodeData tree2_1; + tree2_1.id = 1; + tree2_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + tree2_1.child_ids.push_back(3); + + AccessibilityNodeData tree2_2; + tree2_2.id = 3; + tree2_2.role = AccessibilityNodeData::ROLE_BUTTON; + + msg->nodes.clear(); + msg->nodes.push_back(tree2_1); + msg->nodes.push_back(tree2_2); + msg->id = tree2_1.id; + + // Fire another load complete. + manager->OnAccessibilityNotifications(params); + + BrowserAccessibility* acc2_2 = manager->GetFromRendererID(3); + + // Verify the root has changed. + EXPECT_NE(root, manager->GetRoot()); + + // And the new child exists. + EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, acc2_2->role()); + EXPECT_EQ(3, acc2_2->renderer_id()); + + // Ensure we properly cleaned up. + manager.reset(); + ASSERT_EQ(0, CountedBrowserAccessibility::num_instances()); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/chromium/content/browser/accessibility/cross_platform_accessibility_browsertest.cc new file mode 100644 index 00000000000..5e3effe3c2a --- /dev/null +++ b/chromium/content/browser/accessibility/cross_platform_accessibility_browsertest.cc @@ -0,0 +1,465 @@ +// Copyright (c) 2012 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 <string> +#include <vector> + +#include "base/strings/utf_string_conversions.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/shell/shell.h" +#include "content/test/accessibility_browser_test_utils.h" +#include "content/test/content_browser_test.h" +#include "content/test/content_browser_test_utils.h" + +#if defined(OS_WIN) +#include <atlbase.h> +#include <atlcom.h> +#include "base/win/scoped_com_initializer.h" +#include "ui/base/win/atl_module.h" +#endif + +// TODO(dmazzoni): Disabled accessibility tests on Win64. crbug.com/179717 +#if defined(OS_WIN) && defined(ARCH_CPU_X86_64) +#define MAYBE_TableSpan DISABLED_TableSpan +#else +#define MAYBE_TableSpan TableSpan +#endif + +namespace content { + +class CrossPlatformAccessibilityBrowserTest : public ContentBrowserTest { + public: + CrossPlatformAccessibilityBrowserTest() {} + + // Tell the renderer to send an accessibility tree, then wait for the + // notification that it's been received. + const AccessibilityNodeDataTreeNode& GetAccessibilityNodeDataTree( + AccessibilityMode accessibility_mode = AccessibilityModeComplete) { + AccessibilityNotificationWaiter waiter( + shell(), accessibility_mode, AccessibilityNotificationLayoutComplete); + waiter.WaitForNotification(); + return waiter.GetAccessibilityNodeDataTree(); + } + + // Make sure each node in the tree has an unique id. + void RecursiveAssertUniqueIds( + const AccessibilityNodeDataTreeNode& node, base::hash_set<int>* ids) { + ASSERT_TRUE(ids->find(node.id) == ids->end()); + ids->insert(node.id); + for (size_t i = 0; i < node.children.size(); i++) + RecursiveAssertUniqueIds(node.children[i], ids); + } + + // ContentBrowserTest + virtual void SetUpInProcessBrowserTestFixture() OVERRIDE; + virtual void TearDownInProcessBrowserTestFixture() OVERRIDE; + + protected: + std::string GetAttr(const AccessibilityNodeData& node, + const AccessibilityNodeData::StringAttribute attr); + int GetIntAttr(const AccessibilityNodeData& node, + const AccessibilityNodeData::IntAttribute attr); + bool GetBoolAttr(const AccessibilityNodeData& node, + const AccessibilityNodeData::BoolAttribute attr); + + private: +#if defined(OS_WIN) + scoped_ptr<base::win::ScopedCOMInitializer> com_initializer_; +#endif + + DISALLOW_COPY_AND_ASSIGN(CrossPlatformAccessibilityBrowserTest); +}; + +void CrossPlatformAccessibilityBrowserTest::SetUpInProcessBrowserTestFixture() { +#if defined(OS_WIN) + ui::win::CreateATLModuleIfNeeded(); + com_initializer_.reset(new base::win::ScopedCOMInitializer()); +#endif +} + +void +CrossPlatformAccessibilityBrowserTest::TearDownInProcessBrowserTestFixture() { +#if defined(OS_WIN) + com_initializer_.reset(); +#endif +} + +// Convenience method to get the value of a particular AccessibilityNodeData +// node attribute as a UTF-8 const char*. +std::string CrossPlatformAccessibilityBrowserTest::GetAttr( + const AccessibilityNodeData& node, + const AccessibilityNodeData::StringAttribute attr) { + std::map<AccessibilityNodeData::StringAttribute, string16>::const_iterator + iter = node.string_attributes.find(attr); + if (iter != node.string_attributes.end()) + return UTF16ToUTF8(iter->second); + else + return std::string(); +} + +// Convenience method to get the value of a particular AccessibilityNodeData +// node integer attribute. +int CrossPlatformAccessibilityBrowserTest::GetIntAttr( + const AccessibilityNodeData& node, + const AccessibilityNodeData::IntAttribute attr) { + std::map<AccessibilityNodeData::IntAttribute, int32>::const_iterator iter = + node.int_attributes.find(attr); + if (iter != node.int_attributes.end()) + return iter->second; + else + return -1; +} + +// Convenience method to get the value of a particular AccessibilityNodeData +// node boolean attribute. +bool CrossPlatformAccessibilityBrowserTest::GetBoolAttr( + const AccessibilityNodeData& node, + const AccessibilityNodeData::BoolAttribute attr) { + std::map<AccessibilityNodeData::BoolAttribute, bool>::const_iterator iter = + node.bool_attributes.find(attr); + if (iter != node.bool_attributes.end()) + return iter->second; + else + return false; +} + +// Marked flaky per http://crbug.com/101984 +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + DISABLED_WebpageAccessibility) { + // Create a data url and load it. + const char url_str[] = + "data:text/html," + "<!doctype html>" + "<html><head><title>Accessibility Test</title></head>" + "<body><input type='button' value='push' /><input type='checkbox' />" + "</body></html>"; + GURL url(url_str); + NavigateToURL(shell(), url); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + + // Check properties of the root element of the tree. + EXPECT_STREQ(url_str, + GetAttr(tree, AccessibilityNodeData::ATTR_DOC_URL).c_str()); + EXPECT_STREQ( + "Accessibility Test", + GetAttr(tree, AccessibilityNodeData::ATTR_DOC_TITLE).c_str()); + EXPECT_STREQ( + "html", GetAttr(tree, AccessibilityNodeData::ATTR_DOC_DOCTYPE).c_str()); + EXPECT_STREQ( + "text/html", + GetAttr(tree, AccessibilityNodeData::ATTR_DOC_MIMETYPE).c_str()); + EXPECT_STREQ("Accessibility Test", UTF16ToUTF8(tree.name).c_str()); + EXPECT_EQ(AccessibilityNodeData::ROLE_ROOT_WEB_AREA, tree.role); + + // Check properites of the BODY element. + ASSERT_EQ(1U, tree.children.size()); + const AccessibilityNodeDataTreeNode& body = tree.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_GROUP, body.role); + EXPECT_STREQ("body", + GetAttr(body, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); + EXPECT_STREQ("block", + GetAttr(body, AccessibilityNodeData::ATTR_DISPLAY).c_str()); + + // Check properties of the two children of the BODY element. + ASSERT_EQ(2U, body.children.size()); + + const AccessibilityNodeDataTreeNode& button = body.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button.role); + EXPECT_STREQ( + "input", GetAttr(button, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); + EXPECT_STREQ("push", UTF16ToUTF8(button.name).c_str()); + EXPECT_STREQ( + "inline-block", + GetAttr(button, AccessibilityNodeData::ATTR_DISPLAY).c_str()); + ASSERT_EQ(2U, button.html_attributes.size()); + EXPECT_STREQ("type", UTF16ToUTF8(button.html_attributes[0].first).c_str()); + EXPECT_STREQ("button", UTF16ToUTF8(button.html_attributes[0].second).c_str()); + EXPECT_STREQ("value", UTF16ToUTF8(button.html_attributes[1].first).c_str()); + EXPECT_STREQ("push", UTF16ToUTF8(button.html_attributes[1].second).c_str()); + + const AccessibilityNodeDataTreeNode& checkbox = body.children[1]; + EXPECT_EQ(AccessibilityNodeData::ROLE_CHECKBOX, checkbox.role); + EXPECT_STREQ( + "input", GetAttr(checkbox, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); + EXPECT_STREQ( + "inline-block", + GetAttr(checkbox, AccessibilityNodeData::ATTR_DISPLAY).c_str()); + ASSERT_EQ(1U, checkbox.html_attributes.size()); + EXPECT_STREQ( + "type", UTF16ToUTF8(checkbox.html_attributes[0].first).c_str()); + EXPECT_STREQ( + "checkbox", UTF16ToUTF8(checkbox.html_attributes[0].second).c_str()); +} + +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + UnselectedEditableTextAccessibility) { + // Create a data url and load it. + const char url_str[] = + "data:text/html," + "<!doctype html>" + "<body>" + "<input value=\"Hello, world.\"/>" + "</body></html>"; + GURL url(url_str); + NavigateToURL(shell(), url); + + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + ASSERT_EQ(1U, tree.children.size()); + const AccessibilityNodeDataTreeNode& body = tree.children[0]; + ASSERT_EQ(1U, body.children.size()); + const AccessibilityNodeDataTreeNode& text = body.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_TEXT_FIELD, text.role); + EXPECT_STREQ( + "input", GetAttr(text, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); + EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_START)); + EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_END)); + EXPECT_STREQ("Hello, world.", UTF16ToUTF8(text.value).c_str()); + + // TODO(dmazzoni): as soon as more accessibility code is cross-platform, + // this code should test that the accessible info is dynamically updated + // if the selection or value changes. +} + +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + SelectedEditableTextAccessibility) { + // Create a data url and load it. + const char url_str[] = + "data:text/html," + "<!doctype html>" + "<body onload=\"document.body.children[0].select();\">" + "<input value=\"Hello, world.\"/>" + "</body></html>"; + GURL url(url_str); + NavigateToURL(shell(), url); + + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + ASSERT_EQ(1U, tree.children.size()); + const AccessibilityNodeDataTreeNode& body = tree.children[0]; + ASSERT_EQ(1U, body.children.size()); + const AccessibilityNodeDataTreeNode& text = body.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_TEXT_FIELD, text.role); + EXPECT_STREQ( + "input", GetAttr(text, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); + EXPECT_EQ(0, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_START)); + EXPECT_EQ(13, GetIntAttr(text, AccessibilityNodeData::ATTR_TEXT_SEL_END)); + EXPECT_STREQ("Hello, world.", UTF16ToUTF8(text.value).c_str()); +} + +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + MultipleInheritanceAccessibility) { + // In a WebKit accessibility render tree for a table, each cell is a + // child of both a row and a column, so it appears to use multiple + // inheritance. Make sure that the AccessibilityNodeDataObject tree only + // keeps one copy of each cell, and uses an indirect child id for the + // additional reference to it. + const char url_str[] = + "data:text/html," + "<!doctype html>" + "<table border=1><tr><td>1</td><td>2</td></tr></table>"; + GURL url(url_str); + NavigateToURL(shell(), url); + + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + ASSERT_EQ(1U, tree.children.size()); + const AccessibilityNodeDataTreeNode& table = tree.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_TABLE, table.role); + const AccessibilityNodeDataTreeNode& row = table.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_ROW, row.role); + const AccessibilityNodeDataTreeNode& cell1 = row.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_CELL, cell1.role); + const AccessibilityNodeDataTreeNode& cell2 = row.children[1]; + EXPECT_EQ(AccessibilityNodeData::ROLE_CELL, cell2.role); + const AccessibilityNodeDataTreeNode& column1 = table.children[1]; + EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, column1.role); + EXPECT_EQ(0U, column1.children.size()); + EXPECT_EQ(1U, column1.indirect_child_ids.size()); + EXPECT_EQ(cell1.id, column1.indirect_child_ids[0]); + const AccessibilityNodeDataTreeNode& column2 = table.children[2]; + EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, column2.role); + EXPECT_EQ(0U, column2.children.size()); + EXPECT_EQ(1U, column2.indirect_child_ids.size()); + EXPECT_EQ(cell2.id, column2.indirect_child_ids[0]); +} + +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + MultipleInheritanceAccessibility2) { + // Here's another html snippet where WebKit puts the same node as a child + // of two different parents. Instead of checking the exact output, just + // make sure that no id is reused in the resulting tree. + const char url_str[] = + "data:text/html," + "<!doctype html>" + "<script>\n" + " document.writeln('<q><section></section></q><q><li>');\n" + " setTimeout(function() {\n" + " document.close();\n" + " }, 1);\n" + "</script>"; + GURL url(url_str); + NavigateToURL(shell(), url); + + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + base::hash_set<int> ids; + RecursiveAssertUniqueIds(tree, &ids); +} + +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + IframeAccessibility) { + // Create a data url and load it. + const char url_str[] = + "data:text/html," + "<!doctype html><html><body>" + "<button>Button 1</button>" + "<iframe src='data:text/html," + "<!doctype html><html><body><button>Button 2</button></body></html>" + "'></iframe>" + "<button>Button 3</button>" + "</body></html>"; + GURL url(url_str); + NavigateToURL(shell(), url); + + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + ASSERT_EQ(1U, tree.children.size()); + const AccessibilityNodeDataTreeNode& body = tree.children[0]; + ASSERT_EQ(3U, body.children.size()); + + const AccessibilityNodeDataTreeNode& button1 = body.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button1.role); + EXPECT_STREQ("Button 1", UTF16ToUTF8(button1.name).c_str()); + + const AccessibilityNodeDataTreeNode& iframe = body.children[1]; + EXPECT_STREQ("iframe", + GetAttr(iframe, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); + ASSERT_EQ(1U, iframe.children.size()); + + const AccessibilityNodeDataTreeNode& scroll_area = iframe.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_SCROLLAREA, scroll_area.role); + ASSERT_EQ(1U, scroll_area.children.size()); + + const AccessibilityNodeDataTreeNode& sub_document = scroll_area.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_WEB_AREA, sub_document.role); + ASSERT_EQ(1U, sub_document.children.size()); + + const AccessibilityNodeDataTreeNode& sub_body = sub_document.children[0]; + ASSERT_EQ(1U, sub_body.children.size()); + + const AccessibilityNodeDataTreeNode& button2 = sub_body.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button2.role); + EXPECT_STREQ("Button 2", UTF16ToUTF8(button2.name).c_str()); + + const AccessibilityNodeDataTreeNode& button3 = body.children[2]; + EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button3.role); + EXPECT_STREQ("Button 3", UTF16ToUTF8(button3.name).c_str()); +} + +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + DuplicateChildrenAccessibility) { + // Here's another html snippet where WebKit has a parent node containing + // two duplicate child nodes. Instead of checking the exact output, just + // make sure that no id is reused in the resulting tree. + const char url_str[] = + "data:text/html," + "<!doctype html>" + "<em><code ><h4 ></em>"; + GURL url(url_str); + NavigateToURL(shell(), url); + + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + base::hash_set<int> ids; + RecursiveAssertUniqueIds(tree, &ids); +} + +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + MAYBE_TableSpan) { + // +---+---+---+ + // | 1 | 2 | + // +---+---+---+ + // | 3 | 4 | + // +---+---+---+ + + const char url_str[] = + "data:text/html," + "<!doctype html>" + "<table border=1>" + " <tr>" + " <td colspan=2>1</td><td>2</td>" + " </tr>" + " <tr>" + " <td>3</td><td colspan=2>4</td>" + " </tr>" + "</table>"; + GURL url(url_str); + NavigateToURL(shell(), url); + + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& table = tree.children[0]; + EXPECT_EQ(AccessibilityNodeData::ROLE_TABLE, table.role); + ASSERT_GE(table.children.size(), 5U); + EXPECT_EQ(AccessibilityNodeData::ROLE_ROW, table.children[0].role); + EXPECT_EQ(AccessibilityNodeData::ROLE_ROW, table.children[1].role); + EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, table.children[2].role); + EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, table.children[3].role); + EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, table.children[4].role); + EXPECT_EQ(3, + GetIntAttr(table, AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT)); + EXPECT_EQ(2, GetIntAttr(table, AccessibilityNodeData::ATTR_TABLE_ROW_COUNT)); + + const AccessibilityNodeDataTreeNode& cell1 = table.children[0].children[0]; + const AccessibilityNodeDataTreeNode& cell2 = table.children[0].children[1]; + const AccessibilityNodeDataTreeNode& cell3 = table.children[1].children[0]; + const AccessibilityNodeDataTreeNode& cell4 = table.children[1].children[1]; + + ASSERT_EQ(6U, table.cell_ids.size()); + EXPECT_EQ(cell1.id, table.cell_ids[0]); + EXPECT_EQ(cell1.id, table.cell_ids[1]); + EXPECT_EQ(cell2.id, table.cell_ids[2]); + EXPECT_EQ(cell3.id, table.cell_ids[3]); + EXPECT_EQ(cell4.id, table.cell_ids[4]); + EXPECT_EQ(cell4.id, table.cell_ids[5]); + + EXPECT_EQ(0, GetIntAttr(cell1, + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX)); + EXPECT_EQ(0, GetIntAttr(cell1, + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX)); + EXPECT_EQ(2, GetIntAttr(cell1, + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN)); + EXPECT_EQ(1, GetIntAttr(cell1, + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN)); + EXPECT_EQ(2, GetIntAttr(cell2, + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX)); + EXPECT_EQ(1, GetIntAttr(cell2, + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN)); + EXPECT_EQ(0, GetIntAttr(cell3, + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX)); + EXPECT_EQ(1, GetIntAttr(cell3, + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN)); + EXPECT_EQ(1, GetIntAttr(cell4, + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX)); + EXPECT_EQ(2, GetIntAttr(cell4, + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN)); +} + +IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, + WritableElement) { + const char url_str[] = + "data:text/html," + "<!doctype html>" + "<div role='textbox' tabindex=0>" + " Some text" + "</div>"; + GURL url(url_str); + NavigateToURL(shell(), url); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + + ASSERT_EQ(1U, tree.children.size()); + const AccessibilityNodeDataTreeNode& textbox = tree.children[0]; + + EXPECT_EQ( + true, GetBoolAttr(textbox, AccessibilityNodeData::ATTR_CAN_SET_VALUE)); +} + +} // namespace content diff --git a/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc new file mode 100644 index 00000000000..d0ff119fd24 --- /dev/null +++ b/chromium/content/browser/accessibility/dump_accessibility_tree_browsertest.cc @@ -0,0 +1,456 @@ +// Copyright (c) 2012 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 <set> +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/strings/string16.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/accessibility/accessibility_tree_formatter.h" +#include "content/browser/accessibility/browser_accessibility.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/port/browser/render_widget_host_view_port.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_paths.h" +#include "content/public/common/url_constants.h" +#include "content/shell/shell.h" +#include "content/test/accessibility_browser_test_utils.h" +#include "content/test/content_browser_test.h" +#include "content/test/content_browser_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +// TODO(dmazzoni): Disabled accessibility tests on Win64. crbug.com/179717 +#if defined(OS_WIN) && defined(ARCH_CPU_X86_64) +#define MAYBE(x) DISABLED_##x +#else +#define MAYBE(x) x +#endif + +namespace content { + +namespace { + +const char kCommentToken = '#'; +const char kMarkSkipFile[] = "#<skip"; +const char kMarkEndOfFile[] = "<-- End-of-file -->"; +const char kSignalDiff[] = "*"; + +} // namespace + +typedef AccessibilityTreeFormatter::Filter Filter; + +// This test takes a snapshot of the platform BrowserAccessibility tree and +// tests it against an expected baseline. +// +// The flow of the test is as outlined below. +// 1. Load an html file from chrome/test/data/accessibility. +// 2. Read the expectation. +// 3. Browse to the page and serialize the platform specific tree into a human +// readable string. +// 4. Perform a comparison between actual and expected and fail if they do not +// exactly match. +class DumpAccessibilityTreeTest : public ContentBrowserTest { + public: + // Utility helper that does a comment aware equality check. + // Returns array of lines from expected file which are different. + std::vector<int> DiffLines(const std::vector<std::string>& expected_lines, + const std::vector<std::string>& actual_lines) { + int actual_lines_count = actual_lines.size(); + int expected_lines_count = expected_lines.size(); + std::vector<int> diff_lines; + int i = 0, j = 0; + while (i < actual_lines_count && j < expected_lines_count) { + if (expected_lines[j].size() == 0 || + expected_lines[j][0] == kCommentToken) { + // Skip comment lines and blank lines in expected output. + ++j; + continue; + } + + if (actual_lines[i] != expected_lines[j]) + diff_lines.push_back(j); + ++i; + ++j; + } + + // Actual file has been fully checked. + return diff_lines; + } + + void AddDefaultFilters(std::vector<Filter>* filters) { + filters->push_back(Filter(ASCIIToUTF16("FOCUSABLE"), Filter::ALLOW)); + filters->push_back(Filter(ASCIIToUTF16("READONLY"), Filter::ALLOW)); + filters->push_back(Filter(ASCIIToUTF16("*=''"), Filter::DENY)); + } + + void ParseFilters(const std::string& test_html, + std::vector<Filter>* filters) { + std::vector<std::string> lines; + base::SplitString(test_html, '\n', &lines); + for (std::vector<std::string>::const_iterator iter = lines.begin(); + iter != lines.end(); + ++iter) { + const std::string& line = *iter; + const std::string& allow_empty_str = + AccessibilityTreeFormatter::GetAllowEmptyString(); + const std::string& allow_str = + AccessibilityTreeFormatter::GetAllowString(); + const std::string& deny_str = + AccessibilityTreeFormatter::GetDenyString(); + if (StartsWithASCII(line, allow_empty_str, true)) { + filters->push_back( + Filter(UTF8ToUTF16(line.substr(allow_empty_str.size())), + Filter::ALLOW_EMPTY)); + } else if (StartsWithASCII(line, allow_str, true)) { + filters->push_back(Filter(UTF8ToUTF16(line.substr(allow_str.size())), + Filter::ALLOW)); + } else if (StartsWithASCII(line, deny_str, true)) { + filters->push_back(Filter(UTF8ToUTF16(line.substr(deny_str.size())), + Filter::DENY)); + } + } + } + + void RunTest(const base::FilePath::CharType* file_path); +}; + +void DumpAccessibilityTreeTest::RunTest( + const base::FilePath::CharType* file_path) { + NavigateToURL(shell(), GURL(kAboutBlankURL)); + + // Setup test paths. + base::FilePath dir_test_data; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &dir_test_data)); + base::FilePath test_path( + dir_test_data.Append(FILE_PATH_LITERAL("accessibility"))); + ASSERT_TRUE(base::PathExists(test_path)) + << test_path.LossyDisplayName(); + + base::FilePath html_file = test_path.Append(base::FilePath(file_path)); + // Output the test path to help anyone who encounters a failure and needs + // to know where to look. + printf("Testing: %s\n", html_file.MaybeAsASCII().c_str()); + + std::string html_contents; + file_util::ReadFileToString(html_file, &html_contents); + + // Read the expected file. + std::string expected_contents_raw; + base::FilePath expected_file = + base::FilePath(html_file.RemoveExtension().value() + + AccessibilityTreeFormatter::GetExpectedFileSuffix()); + file_util::ReadFileToString(expected_file, &expected_contents_raw); + + // Tolerate Windows-style line endings (\r\n) in the expected file: + // normalize by deleting all \r from the file (if any) to leave only \n. + std::string expected_contents; + RemoveChars(expected_contents_raw, "\r", &expected_contents); + + if (!expected_contents.compare(0, strlen(kMarkSkipFile), kMarkSkipFile)) { + printf("Skipping this test on this platform.\n"); + return; + } + + // Load the page. + string16 html_contents16; + html_contents16 = UTF8ToUTF16(html_contents); + GURL url = GetTestUrl("accessibility", + html_file.BaseName().MaybeAsASCII().c_str()); + AccessibilityNotificationWaiter waiter( + shell(), AccessibilityModeComplete, + AccessibilityNotificationLoadComplete); + NavigateToURL(shell(), url); + waiter.WaitForNotification(); + + RenderWidgetHostViewPort* host_view = RenderWidgetHostViewPort::FromRWHV( + shell()->web_contents()->GetRenderWidgetHostView()); + AccessibilityTreeFormatter formatter( + host_view->GetBrowserAccessibilityManager()->GetRoot()); + + // Parse filters in the test file. + std::vector<Filter> filters; + AddDefaultFilters(&filters); + ParseFilters(html_contents, &filters); + formatter.SetFilters(filters); + + // Perform a diff (or write the initial baseline). + string16 actual_contents_utf16; + formatter.FormatAccessibilityTree(&actual_contents_utf16); + std::string actual_contents = UTF16ToUTF8(actual_contents_utf16); + std::vector<std::string> actual_lines, expected_lines; + Tokenize(actual_contents, "\n", &actual_lines); + Tokenize(expected_contents, "\n", &expected_lines); + // Marking the end of the file with a line of text ensures that + // file length differences are found. + expected_lines.push_back(kMarkEndOfFile); + actual_lines.push_back(kMarkEndOfFile); + + std::vector<int> diff_lines = DiffLines(expected_lines, actual_lines); + bool is_different = diff_lines.size() > 0; + EXPECT_FALSE(is_different); + if (is_different) { + // Mark the expected lines which did not match actual output with a *. + printf("* Line Expected\n"); + printf("- ---- --------\n"); + for (int line = 0, diff_index = 0; + line < static_cast<int>(expected_lines.size()); + ++line) { + bool is_diff = false; + if (diff_index < static_cast<int>(diff_lines.size()) && + diff_lines[diff_index] == line) { + is_diff = true; + ++diff_index; + } + printf("%1s %4d %s\n", is_diff? kSignalDiff : "", line + 1, + expected_lines[line].c_str()); + } + printf("\nActual\n"); + printf("------\n"); + printf("%s\n", actual_contents.c_str()); + } + + if (!base::PathExists(expected_file)) { + base::FilePath actual_file = + base::FilePath(html_file.RemoveExtension().value() + + AccessibilityTreeFormatter::GetActualFileSuffix()); + + EXPECT_TRUE(file_util::WriteFile( + actual_file, actual_contents.c_str(), actual_contents.size())); + + ADD_FAILURE() << "No expectation found. Create it by doing:\n" + << "mv " << actual_file.LossyDisplayName() << " " + << expected_file.LossyDisplayName(); + } +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityA) { + RunTest(FILE_PATH_LITERAL("a.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAddress) { + RunTest(FILE_PATH_LITERAL("address.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAName) { + RunTest(FILE_PATH_LITERAL("a-name.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAOnclick) { + RunTest(FILE_PATH_LITERAL("a-onclick.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaApplication) { + RunTest(FILE_PATH_LITERAL("aria-application.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaAutocomplete) { + RunTest(FILE_PATH_LITERAL("aria-autocomplete.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaCombobox) { + RunTest(FILE_PATH_LITERAL("aria-combobox.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaInvalid) { + RunTest(FILE_PATH_LITERAL("aria-invalid.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaLevel) { + RunTest(FILE_PATH_LITERAL("aria-level.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaMenu) { + RunTest(FILE_PATH_LITERAL("aria-menu.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaMenuitemradio) { + RunTest(FILE_PATH_LITERAL("aria-menuitemradio.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaPressed) { + RunTest(FILE_PATH_LITERAL("aria-pressed.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaProgressbar) { + RunTest(FILE_PATH_LITERAL("aria-progressbar.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaToolbar) { + RunTest(FILE_PATH_LITERAL("toolbar.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaValueMin) { + RunTest(FILE_PATH_LITERAL("aria-valuemin.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityAriaValueMax) { + RunTest(FILE_PATH_LITERAL("aria-valuemax.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityArticle) { + RunTest(FILE_PATH_LITERAL("article.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAWithImg) { + RunTest(FILE_PATH_LITERAL("a-with-img.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityBdo) { + RunTest(FILE_PATH_LITERAL("bdo.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityBR) { + RunTest(FILE_PATH_LITERAL("br.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityButtonNameCalc) { + RunTest(FILE_PATH_LITERAL("button-name-calc.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityCanvas) { + RunTest(FILE_PATH_LITERAL("canvas.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityCheckboxNameCalc) { + RunTest(FILE_PATH_LITERAL("checkbox-name-calc.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityDiv) { + RunTest(FILE_PATH_LITERAL("div.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityDl) { + RunTest(FILE_PATH_LITERAL("dl.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityContenteditableDescendants) { + RunTest(FILE_PATH_LITERAL("contenteditable-descendants.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityEm) { + RunTest(FILE_PATH_LITERAL("em.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityFooter) { + RunTest(FILE_PATH_LITERAL("footer.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityForm) { + RunTest(FILE_PATH_LITERAL("form.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityHeading) { + RunTest(FILE_PATH_LITERAL("heading.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityHR) { + RunTest(FILE_PATH_LITERAL("hr.html")); +} + +// crbug.com/179717 and crbug.com/224659 +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + DISABLED_AccessibilityIframeCoordinates) { + RunTest(FILE_PATH_LITERAL("iframe-coordinates.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputButton) { + RunTest(FILE_PATH_LITERAL("input-button.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityInputButtonInMenu) { + RunTest(FILE_PATH_LITERAL("input-button-in-menu.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputColor) { + RunTest(FILE_PATH_LITERAL("input-color.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityInputImageButtonInMenu) { + RunTest(FILE_PATH_LITERAL("input-image-button-in-menu.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputRange) { + RunTest(FILE_PATH_LITERAL("input-range.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityInputTextNameCalc) { + RunTest(FILE_PATH_LITERAL("input-text-name-calc.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityLabel) { + RunTest(FILE_PATH_LITERAL("label.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityListMarkers) { + RunTest(FILE_PATH_LITERAL("list-markers.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityP) { + RunTest(FILE_PATH_LITERAL("p.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilitySelect) { + RunTest(FILE_PATH_LITERAL("select.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilitySpan) { + RunTest(FILE_PATH_LITERAL("span.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilitySpinButton) { + RunTest(FILE_PATH_LITERAL("spinbutton.html")); +} + +// TODO(dmazzoni): Rebaseline this test after Blink rolls past r155083. +// See http://crbug.com/265619 +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, DISABLED_AccessibilitySvg) { + RunTest(FILE_PATH_LITERAL("svg.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTab) { + RunTest(FILE_PATH_LITERAL("tab.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTableSimple) { + RunTest(FILE_PATH_LITERAL("table-simple.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTableSpans) { + RunTest(FILE_PATH_LITERAL("table-spans.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, + AccessibilityToggleButton) { + RunTest(FILE_PATH_LITERAL("togglebutton.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityUl) { + RunTest(FILE_PATH_LITERAL("ul.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityWbr) { + RunTest(FILE_PATH_LITERAL("wbr.html")); +} + +} // namespace content |