From f12318afda4d5bfd2f8a48eda4f0d7107e11f400 Mon Sep 17 00:00:00 2001 From: dmazzoni Date: Tue, 24 Feb 2015 12:35:17 -0800 Subject: [PATCH] Implement NativeViewAccessibilityWin using AXPlatformNodeWin. AXPlatformNodeWin is an implementation of Windows accessibility APIs that doesn't know anything about content/ or ui/views/ - it just exposes an abstract tree of AXNodeData nodes and uses a delegate interface for everything else. This patch completely removes the Windows-specific views accessibility code and implements it using AXPlatformNodeWin instead. A future step will be to do the same with BrowserAccessibilityWin in content/. This patch also includes a new unit test for AXPlatformNodeWin, since it's now possible to test it without any views or web content code. Hooray! BUG=457564 Review URL: https://codereview.chromium.org/909143003 Cr-Commit-Position: refs/heads/master@{#317878} --- ui/accessibility/DEPS | 2 + ui/accessibility/accessibility.gyp | 25 +- ui/accessibility/platform/ax_platform_node.cc | 15 +- ui/accessibility/platform/ax_platform_node.h | 24 +- ui/accessibility/platform/ax_platform_node_base.cc | 249 ++- ui/accessibility/platform/ax_platform_node_base.h | 41 +- .../platform/ax_platform_node_delegate.h | 54 +- ui/accessibility/platform/ax_platform_node_mac.h | 4 + ui/accessibility/platform/ax_platform_node_mac.mm | 14 +- ui/accessibility/platform/ax_platform_node_win.cc | 983 ++++++++++++ .../platform/ax_platform_node_win.h} | 251 +-- .../platform/ax_platform_node_win_unittest.cc | 372 +++++ ui/accessibility/platform/test_ax_node_wrapper.cc | 120 ++ ui/accessibility/platform/test_ax_node_wrapper.h | 53 + .../accessibility/native_view_accessibility.cc | 208 ++- ui/views/accessibility/native_view_accessibility.h | 43 +- .../native_view_accessibility_unittest.cc | 6 +- .../accessibility/native_view_accessibility_win.cc | 1623 +------------------- .../accessibility/native_view_accessibility_win.h | 475 +----- .../native_view_accessibility_win_unittest.cc | 65 +- ui/views/controls/webview/webview.cc | 3 - 21 files changed, 2342 insertions(+), 2288 deletions(-) create mode 100644 ui/accessibility/platform/ax_platform_node_win.cc copy ui/{views/accessibility/native_view_accessibility_win.h => accessibility/platform/ax_platform_node_win.h} (57%) create mode 100644 ui/accessibility/platform/ax_platform_node_win_unittest.cc create mode 100644 ui/accessibility/platform/test_ax_node_wrapper.cc create mode 100644 ui/accessibility/platform/test_ax_node_wrapper.h rewrite ui/views/accessibility/native_view_accessibility_win.cc (97%) rewrite ui/views/accessibility/native_view_accessibility_win.h (95%) diff --git a/ui/accessibility/DEPS b/ui/accessibility/DEPS index b273ae3319cc..fd3b8f052a50 100644 --- a/ui/accessibility/DEPS +++ b/ui/accessibility/DEPS @@ -1,3 +1,5 @@ include_rules = [ + "+third_party/iaccessible2", + "+ui/base/win", "+ui/gfx", ] diff --git a/ui/accessibility/accessibility.gyp b/ui/accessibility/accessibility.gyp index ca174fd74f57..88613e68505f 100644 --- a/ui/accessibility/accessibility.gyp +++ b/ui/accessibility/accessibility.gyp @@ -50,7 +50,16 @@ 'platform/ax_platform_node_delegate.h', 'platform/ax_platform_node_mac.h', 'platform/ax_platform_node_mac.mm', - ] + 'platform/ax_platform_node_win.h', + 'platform/ax_platform_node_win.cc', + ], + 'conditions': [ + ['OS=="win"', { + 'dependencies': [ + '../../third_party/iaccessible2/iaccessible2.gyp:iaccessible2' + ], + }], + ], }, { 'target_name': 'accessibility_test_support', @@ -60,8 +69,10 @@ 'accessibility' ], 'sources': [ + 'platform/test_ax_node_wrapper.cc', + 'platform/test_ax_node_wrapper.h', 'tree_generator.cc', - 'tree_generator.h' + 'tree_generator.h', ] }, { @@ -82,7 +93,15 @@ 'ax_text_utils_unittest.cc', 'ax_tree_serializer_unittest.cc', 'ax_tree_unittest.cc', - ] + 'platform/ax_platform_node_win_unittest.cc' + ], + 'conditions': [ + ['OS=="win"', { + 'dependencies': [ + '../../third_party/iaccessible2/iaccessible2.gyp:iaccessible2' + ], + }], + ], }, { 'target_name': 'ax_gen', diff --git a/ui/accessibility/platform/ax_platform_node.cc b/ui/accessibility/platform/ax_platform_node.cc index 37b8ef41922e..363b3ee3b7f2 100644 --- a/ui/accessibility/platform/ax_platform_node.cc +++ b/ui/accessibility/platform/ax_platform_node.cc @@ -9,10 +9,21 @@ namespace ui { -#if !defined(OS_MACOSX) +#if !defined(OS_MACOSX) && !defined(OS_WIN) // static AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) { - return NULL; + return nullptr; +} +#endif + +#if !defined(OS_WIN) +// This is the default implementation for platforms where native views +// accessibility is unsupported or unfinished. +// +// static +AXPlatformNode* AXPlatformNode::FromNativeViewAccessible( + gfx::NativeViewAccessible accessible) { + return nullptr; } #endif diff --git a/ui/accessibility/platform/ax_platform_node.h b/ui/accessibility/platform/ax_platform_node.h index 10ecba2064d0..8fa4286afaab 100644 --- a/ui/accessibility/platform/ax_platform_node.h +++ b/ui/accessibility/platform/ax_platform_node.h @@ -5,6 +5,7 @@ #ifndef UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_H_ #define UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_H_ +#include "ui/accessibility/ax_enums.h" #include "ui/accessibility/ax_export.h" #include "ui/gfx/native_widget_types.h" @@ -12,17 +13,38 @@ namespace ui { class AXPlatformNodeDelegate; +// AXPlatformNode is the abstract base class for an implementation of +// native accessibility APIs on supported platforms (e.g. Windows, Mac OS X). +// An object that wants to be accessible can derive from AXPlatformNodeDelegate +// and then call AXPlatformNode::Create. The delegate implementation should +// own the AXPlatformNode instance (or otherwise manage its lifecycle). class AX_EXPORT AXPlatformNode { public: - // Create a platform appropriate instance. + // Create an appropriate platform-specific instance. The delegate owns the + // AXPlatformNode instance (or manages its lifecycle in some other way). static AXPlatformNode* Create(AXPlatformNodeDelegate* delegate); + // Cast a gfx::NativeViewAccessible to an AXPlatformNode if it is one, + // or return NULL if it's not an instance of this class. + static AXPlatformNode* FromNativeViewAccessible( + gfx::NativeViewAccessible accessible); + // Call Destroy rather than deleting this, because the subclass may // use reference counting. virtual void Destroy() = 0; + // Get the platform-specific accessible object type for this instance. + // On some platforms this is just a type cast, on others it may be a + // wrapper object or handle. virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0; + // Fire a platform-specific notification that an event has occurred on + // this object. + virtual void NotifyAccessibilityEvent(ui::AXEvent event_type) = 0; + + // Return this object's delegate. + virtual AXPlatformNodeDelegate* GetDelegate() const = 0; + protected: AXPlatformNode(); virtual ~AXPlatformNode(); diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc index 3479af14ab5d..62cfe5aec10e 100644 --- a/ui/accessibility/platform/ax_platform_node_base.cc +++ b/ui/accessibility/platform/ax_platform_node_base.cc @@ -4,55 +4,276 @@ #include "ui/accessibility/platform/ax_platform_node_base.h" +#include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/platform/ax_platform_node_delegate.h" namespace ui { -AXPlatformNodeBase::AXPlatformNodeBase() { -} +namespace { -AXPlatformNodeBase::~AXPlatformNodeBase() { +// Predicate that returns true if the first value of a pair is |first|. +template +struct FirstIs { + FirstIs(FirstType first) + : first_(first) {} + bool operator()(std::pair const& p) { + return p.first == first_; + } + FirstType first_; +}; + +// Helper function that finds in a vector of pairs by matching on the +// first value, and returns an iterator. +template +typename std::vector>::const_iterator + FindInVectorOfPairs( + FirstType first, + const std::vector>& vector) { + return std::find_if(vector.begin(), + vector.end(), + FirstIs(first)); } +} // namespace + void AXPlatformNodeBase::Init(AXPlatformNodeDelegate* delegate) { delegate_ = delegate; } -AXRole AXPlatformNodeBase::GetRole() const { - return delegate_ ? delegate_->GetData()->role : AX_ROLE_UNKNOWN; +const AXNodeData& AXPlatformNodeBase::GetData() const { + CHECK(delegate_); + return delegate_->GetData(); } gfx::Rect AXPlatformNodeBase::GetBoundsInScreen() const { - if (!delegate_) - return gfx::Rect(); - gfx::Rect bounds = delegate_->GetData()->location; + CHECK(delegate_); + gfx::Rect bounds = GetData().location; bounds.Offset(delegate_->GetGlobalCoordinateOffset()); return bounds; } gfx::NativeViewAccessible AXPlatformNodeBase::GetParent() { - return delegate_ ? delegate_->GetParent() : NULL; + CHECK(delegate_); + return delegate_->GetParent(); } int AXPlatformNodeBase::GetChildCount() { - return delegate_ ? delegate_->GetChildCount() : 0; + CHECK(delegate_); + return delegate_->GetChildCount(); } gfx::NativeViewAccessible AXPlatformNodeBase::ChildAtIndex(int index) { - return delegate_ ? delegate_->ChildAtIndex(index) : NULL; + CHECK(delegate_); + return delegate_->ChildAtIndex(index); } -// AXPlatformNode +// AXPlatformNode overrides. void AXPlatformNodeBase::Destroy() { - delegate_ = NULL; + delegate_ = nullptr; delete this; } gfx::NativeViewAccessible AXPlatformNodeBase::GetNativeViewAccessible() { - return NULL; + return nullptr; +} + +AXPlatformNodeDelegate* AXPlatformNodeBase::GetDelegate() const { + return delegate_; +} + +// Helpers. + +AXPlatformNodeBase* AXPlatformNodeBase::GetPreviousSibling() { + CHECK(delegate_); + gfx::NativeViewAccessible parent_accessible = GetParent(); + AXPlatformNodeBase* parent = FromNativeViewAccessible(parent_accessible); + if (!parent) + return nullptr; + + int previous_index = GetIndexInParent() - 1; + if (previous_index >= 0 && + previous_index < parent->GetChildCount()) { + return FromNativeViewAccessible(parent->ChildAtIndex(previous_index)); + } + return nullptr; +} + +AXPlatformNodeBase* AXPlatformNodeBase::GetNextSibling() { + CHECK(delegate_); + gfx::NativeViewAccessible parent_accessible = GetParent(); + AXPlatformNodeBase* parent = FromNativeViewAccessible(parent_accessible); + if (!parent) + return nullptr; + + int next_index = GetIndexInParent() + 1; + if (next_index >= 0 && next_index < parent->GetChildCount()) + return FromNativeViewAccessible(parent->ChildAtIndex(next_index)); + return nullptr; } +bool AXPlatformNodeBase::IsDescendant(AXPlatformNodeBase* node) { + CHECK(delegate_); + if (!node) + return false; + if (node == this) + return true; + AXPlatformNodeBase* parent = FromNativeViewAccessible(node->GetParent()); + return IsDescendant(parent); +} + +bool AXPlatformNodeBase::HasBoolAttribute( + ui::AXBoolAttribute attribute) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + auto iter = FindInVectorOfPairs(attribute, data.bool_attributes); + return iter != data.bool_attributes.end(); +} + +bool AXPlatformNodeBase::GetBoolAttribute( + ui::AXBoolAttribute attribute) const { + CHECK(delegate_); + bool result; + if (GetBoolAttribute(attribute, &result)) + return result; + return false; +} + +bool AXPlatformNodeBase::GetBoolAttribute( + ui::AXBoolAttribute attribute, bool* value) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + auto iter = FindInVectorOfPairs(attribute, data.bool_attributes); + if (iter != data.bool_attributes.end()) { + *value = iter->second; + return true; + } + + return false; +} + +bool AXPlatformNodeBase::HasFloatAttribute( + ui::AXFloatAttribute attribute) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + auto iter = FindInVectorOfPairs(attribute, data.float_attributes); + return iter != data.float_attributes.end(); +} + +float AXPlatformNodeBase::GetFloatAttribute( + ui::AXFloatAttribute attribute) const { + CHECK(delegate_); + float result; + if (GetFloatAttribute(attribute, &result)) + return result; + return 0.0; +} + +bool AXPlatformNodeBase::GetFloatAttribute( + ui::AXFloatAttribute attribute, float* value) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + auto iter = FindInVectorOfPairs(attribute, data.float_attributes); + if (iter != data.float_attributes.end()) { + *value = iter->second; + return true; + } + + return false; +} + +bool AXPlatformNodeBase::HasIntAttribute( + ui::AXIntAttribute attribute) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + auto iter = FindInVectorOfPairs(attribute, data.int_attributes); + return iter != data.int_attributes.end(); +} + +int AXPlatformNodeBase::GetIntAttribute( + ui::AXIntAttribute attribute) const { + CHECK(delegate_); + int result; + if (GetIntAttribute(attribute, &result)) + return result; + return 0; +} + +bool AXPlatformNodeBase::GetIntAttribute( + ui::AXIntAttribute attribute, int* value) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + auto iter = FindInVectorOfPairs(attribute, data.int_attributes); + if (iter != data.int_attributes.end()) { + *value = iter->second; + return true; + } + + return false; +} + +bool AXPlatformNodeBase::HasStringAttribute( + ui::AXStringAttribute attribute) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + auto iter = FindInVectorOfPairs(attribute, data.string_attributes); + return iter != data.string_attributes.end(); +} + +const std::string& AXPlatformNodeBase::GetStringAttribute( + ui::AXStringAttribute attribute) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + CR_DEFINE_STATIC_LOCAL(std::string, empty_string, ()); + auto iter = FindInVectorOfPairs(attribute, data.string_attributes); + return iter != data.string_attributes.end() ? iter->second : empty_string; +} + +bool AXPlatformNodeBase::GetStringAttribute( + ui::AXStringAttribute attribute, std::string* value) const { + CHECK(delegate_); + const ui::AXNodeData& data = GetData(); + auto iter = FindInVectorOfPairs(attribute, data.string_attributes); + if (iter != data.string_attributes.end()) { + *value = iter->second; + return true; + } + + return false; +} + +base::string16 AXPlatformNodeBase::GetString16Attribute( + ui::AXStringAttribute attribute) const { + CHECK(delegate_); + std::string value_utf8; + if (!GetStringAttribute(attribute, &value_utf8)) + return base::string16(); + return base::UTF8ToUTF16(value_utf8); +} + +bool AXPlatformNodeBase::GetString16Attribute( + ui::AXStringAttribute attribute, + base::string16* value) const { + CHECK(delegate_); + std::string value_utf8; + if (!GetStringAttribute(attribute, &value_utf8)) + return false; + *value = base::UTF8ToUTF16(value_utf8); + return true; +} + +AXPlatformNodeBase::AXPlatformNodeBase() { +} + +AXPlatformNodeBase::~AXPlatformNodeBase() { +} + +// static +AXPlatformNodeBase* AXPlatformNodeBase::FromNativeViewAccessible( + gfx::NativeViewAccessible accessible) { + return static_cast( + AXPlatformNode::FromNativeViewAccessible(accessible)); +} } // namespace ui diff --git a/ui/accessibility/platform/ax_platform_node_base.h b/ui/accessibility/platform/ax_platform_node_base.h index fcc0f5c5918a..7555ec0fc800 100644 --- a/ui/accessibility/platform/ax_platform_node_base.h +++ b/ui/accessibility/platform/ax_platform_node_base.h @@ -12,6 +12,7 @@ namespace ui { +struct AXNodeData; class AXPlatformNodeDelegate; class AXPlatformNodeBase : public AXPlatformNode { @@ -19,20 +20,56 @@ class AXPlatformNodeBase : public AXPlatformNode { virtual void Init(AXPlatformNodeDelegate* delegate); // These are simple wrappers to our delegate. - AXRole GetRole() const; + const AXNodeData& GetData() const; gfx::Rect GetBoundsInScreen() const; gfx::NativeViewAccessible GetParent(); int GetChildCount(); gfx::NativeViewAccessible ChildAtIndex(int index); - // AXPlatformNode + // This needs to be implemented for each platform. + virtual int GetIndexInParent() = 0; + + // AXPlatformNode. void Destroy() override; gfx::NativeViewAccessible GetNativeViewAccessible() override; + AXPlatformNodeDelegate* GetDelegate() const override; + + // Helpers. + AXPlatformNodeBase* GetPreviousSibling(); + AXPlatformNodeBase* GetNextSibling(); + bool IsDescendant(AXPlatformNodeBase* descendant); + + bool HasBoolAttribute(ui::AXBoolAttribute attr) const; + bool GetBoolAttribute(ui::AXBoolAttribute attr) const; + bool GetBoolAttribute(ui::AXBoolAttribute attr, bool* value) const; + + bool HasFloatAttribute(ui::AXFloatAttribute attr) const; + float GetFloatAttribute(ui::AXFloatAttribute attr) const; + bool GetFloatAttribute(ui::AXFloatAttribute attr, float* value) const; + + bool HasIntAttribute(ui::AXIntAttribute attribute) const; + int GetIntAttribute(ui::AXIntAttribute attribute) const; + bool GetIntAttribute(ui::AXIntAttribute attribute, int* value) const; + + bool HasStringAttribute( + ui::AXStringAttribute attribute) const; + const std::string& GetStringAttribute(ui::AXStringAttribute attribute) const; + bool GetStringAttribute(ui::AXStringAttribute attribute, + std::string* value) const; + bool GetString16Attribute(ui::AXStringAttribute attribute, + base::string16* value) const; + base::string16 GetString16Attribute( + ui::AXStringAttribute attribute) const; protected: AXPlatformNodeBase(); ~AXPlatformNodeBase() override; + // Cast a gfx::NativeViewAccessible to an AXPlatformNodeBase if it is one, + // or return NULL if it's not an instance of this class. + static AXPlatformNodeBase* FromNativeViewAccessible( + gfx::NativeViewAccessible accessible); + AXPlatformNodeDelegate* delegate_; // Weak. Owns this. private: diff --git a/ui/accessibility/platform/ax_platform_node_delegate.h b/ui/accessibility/platform/ax_platform_node_delegate.h index 9af5c4d5df48..e6dd24d90413 100644 --- a/ui/accessibility/platform/ax_platform_node_delegate.h +++ b/ui/accessibility/platform/ax_platform_node_delegate.h @@ -15,28 +15,74 @@ namespace ui { struct AXNodeData; class AXPlatformNode; +// An object that wants to be accessible should derive from this class. +// AXPlatformNode subclasses use this interface to query all of the information +// about the object in order to implement native accessibility APIs. +// +// Note that AXPlatformNode has support for accessibility trees where some +// of the objects in the tree are not implemented using AXPlatformNode. +// For example, you may have a native window with platform-native widgets +// in it, but in that window you have custom controls that use AXPlatformNode +// to provide accessibility. That's why GetParent, ChildAtIndex, HitTestSync, +// and GetFocus all return a gfx::NativeViewAccessible - so you can return a +// native accessible if necessary, and AXPlatformNode::GetNativeViewAccessible +// otherwise. class AX_EXPORT AXPlatformNodeDelegate { public: // Get the accessibility data that should be exposed for this node. - virtual AXNodeData* GetData() = 0; + // Virtually all of the information is obtained from this structure + // (role, state, name, cursor position, etc.) - the rest of this interface + // is mostly to implement support for walking the accessibility tree. + virtual const AXNodeData& GetData() = 0; - // Get the parent of the node unless it's the root, then it returns NULL. + // Get the parent of the node, which may be an AXPlatformNode or it may + // be a native accessible object implemented by another class. virtual gfx::NativeViewAccessible GetParent() = 0; // Get the number of children of this node. virtual int GetChildCount() = 0; - // Get the child of a node from [0...GetChildCount() - 1] + // Get the child of a node given a 0-based index. virtual gfx::NativeViewAccessible ChildAtIndex(int index) = 0; // Get the offset to convert local coordinates to screen global coordinates. virtual gfx::Vector2d GetGlobalCoordinateOffset() = 0; + // Do a *synchronous* hit test of the given location in global screen + // coordinates, and the node within this node's subtree (inclusive) that's + // hit, if any. + // + // If the result is anything other than this object or NULL, it will be + // hit tested again recursively - that allows hit testing to work across + // implementation classes. It's okay to take advantage of this and return + // only an immediate child and not the deepest descendant. + // + // This function is mainly used by accessibility debugging software. + // Platforms with touch accessibility use a different asynchronous interface. + virtual gfx::NativeViewAccessible HitTestSync(int x, int y) = 0; + + // Return the node within this node's subtree (inclusive) that currently + // has focus. + virtual gfx::NativeViewAccessible GetFocus() = 0; + // // Events. // - virtual void NotifyAccessibilityEvent(ui::AXEvent event_type) = 0; + // Return the platform-native GUI object that should be used as a target + // for accessibility events. + virtual gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() = 0; + + // + // Actions. + // + + // Perform the default action, e.g. click a button, follow a link, or + // toggle a checkbox. + virtual void DoDefaultAction() = 0; + + // Change the value of a control, such as the text content of a text field. + virtual bool SetStringValue(const base::string16& new_value) = 0; }; } // namespace ui diff --git a/ui/accessibility/platform/ax_platform_node_mac.h b/ui/accessibility/platform/ax_platform_node_mac.h index f39a94d0cbb0..b579e61c3611 100644 --- a/ui/accessibility/platform/ax_platform_node_mac.h +++ b/ui/accessibility/platform/ax_platform_node_mac.h @@ -20,6 +20,10 @@ class AXPlatformNodeMac : public AXPlatformNodeBase { // AXPlatformNode. void Destroy() override; gfx::NativeViewAccessible GetNativeViewAccessible() override; + void NotifyAccessibilityEvent(ui::AXEvent event_type) override; + + // AXPlatformNodeBase. + int GetIndexInParent() override; private: ~AXPlatformNodeMac() override; diff --git a/ui/accessibility/platform/ax_platform_node_mac.mm b/ui/accessibility/platform/ax_platform_node_mac.mm index d2b27679b02c..89444bf0c6fc 100644 --- a/ui/accessibility/platform/ax_platform_node_mac.mm +++ b/ui/accessibility/platform/ax_platform_node_mac.mm @@ -7,6 +7,7 @@ #import #include "base/strings/sys_string_conversions.h" +#import "ui/accessibility/ax_node_data.h" #import "ui/accessibility/platform/ax_platform_node_delegate.h" #import "ui/gfx/mac/coordinate_conversion.h" @@ -236,7 +237,7 @@ RoleMap BuildSubroleMap() { - (NSString*)AXRole { if (!node_) return nil; - return [[self class] nativeRoleFromAXRole:node_->GetRole()]; + return [[self class] nativeRoleFromAXRole:node_->GetData().role]; } - (NSValue*)AXSize { @@ -302,7 +303,7 @@ AXPlatformNodeMac::~AXPlatformNodeMac() { void AXPlatformNodeMac::Destroy() { if (native_node_) [native_node_ detach]; - delegate_ = NULL; + delegate_ = nullptr; delete this; } @@ -312,4 +313,13 @@ gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() { return native_node_.get(); } +void AXPlatformNodeMac::NotifyAccessibilityEvent(ui::AXEvent event_type) { + // TODO(dmazzoni): implement this. http://crbug.com/396137 +} + +int AXPlatformNodeMac::GetIndexInParent() { + // TODO(dmazzoni): implement this. http://crbug.com/396137 + return -1; +} + } // namespace ui diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc new file mode 100644 index 000000000000..acc2a2b69f70 --- /dev/null +++ b/ui/accessibility/platform/ax_platform_node_win.cc @@ -0,0 +1,983 @@ +// Copyright 2015 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 +#include +#include + +#include "base/containers/hash_tables.h" +#include "base/lazy_instance.h" +#include "base/win/scoped_comptr.h" +#include "base/win/scoped_variant.h" +#include "third_party/iaccessible2/ia2_api_all.h" +#include "ui/accessibility/ax_node_data.h" +#include "ui/accessibility/ax_text_utils.h" +#include "ui/accessibility/platform/ax_platform_node_delegate.h" +#include "ui/accessibility/platform/ax_platform_node_win.h" +#include "ui/base/win/atl_module.h" + +// +// Macros to use at the top of any AXPlatformNodeWin function that implements +// a COM interface. Because COM objects are reference counted and clients +// are completely untrusted, it's important to always first check that our +// object is still valid, and then check that all pointer arguments are +// not NULL. +// + +#define COM_OBJECT_VALIDATE() \ + if (!delegate_) \ + return E_FAIL; +#define COM_OBJECT_VALIDATE_1_ARG(arg) \ + if (!delegate_) return E_FAIL; \ + if (!arg) return E_INVALIDARG +#define COM_OBJECT_VALIDATE_2_ARGS(arg1, arg2) \ + if (!delegate_) return E_FAIL; \ + if (!arg1) return E_INVALIDARG; \ + if (!arg2) return E_INVALIDARG +#define COM_OBJECT_VALIDATE_3_ARGS(arg1, arg2, arg3) \ + if (!delegate_) return E_FAIL; \ + if (!arg1) return E_INVALIDARG; \ + if (!arg2) return E_INVALIDARG; \ + if (!arg3) return E_INVALIDARG +#define COM_OBJECT_VALIDATE_4_ARGS(arg1, arg2, arg3, arg4) \ + if (!delegate_) return E_FAIL; \ + if (!arg1) return E_INVALIDARG; \ + if (!arg2) return E_INVALIDARG; \ + if (!arg3) return E_INVALIDARG; \ + if (!arg4) return E_INVALIDARG +#define COM_OBJECT_VALIDATE_VAR_ID(var_id) \ + if (!delegate_) return E_FAIL; \ + if (!IsValidId(var_id)) return E_INVALIDARG +#define COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, arg) \ + if (!delegate_) return E_FAIL; \ + if (!IsValidId(var_id)) return E_INVALIDARG; \ + if (!arg) return E_INVALIDARG +#define COM_OBJECT_VALIDATE_VAR_ID_2_ARGS(var_id, arg1, arg2) \ + if (!delegate_) return E_FAIL; \ + if (!IsValidId(var_id)) return E_INVALIDARG; \ + if (!arg1) return E_INVALIDARG; \ + if (!arg2) return E_INVALIDARG +#define COM_OBJECT_VALIDATE_VAR_ID_3_ARGS(var_id, arg1, arg2, arg3) \ + if (!delegate_) return E_FAIL; \ + if (!IsValidId(var_id)) return E_INVALIDARG; \ + if (!arg1) return E_INVALIDARG; \ + if (!arg2) return E_INVALIDARG; \ + if (!arg3) return E_INVALIDARG +#define COM_OBJECT_VALIDATE_VAR_ID_4_ARGS(var_id, arg1, arg2, arg3, arg4) \ + if (!delegate_) return E_FAIL; \ + if (!IsValidId(var_id)) return E_INVALIDARG; \ + if (!arg1) return E_INVALIDARG; \ + if (!arg2) return E_INVALIDARG; \ + if (!arg3) return E_INVALIDARG; \ + if (!arg4) return E_INVALIDARG + +namespace ui { + +namespace { + +typedef base::hash_map UniqueIdWinMap; +// Map from each AXPlatformNodeWin's unique id to its instance. +base::LazyInstance g_unique_id_win_map = + LAZY_INSTANCE_INITIALIZER; + +typedef base::hash_set AXPlatformNodeWinSet; +// Set of all AXPlatformNodeWin objects that were the target of an +// alert event. +base::LazyInstance g_alert_targets = + LAZY_INSTANCE_INITIALIZER; + +LONG GetNextNegativeUniqueIdForWinAccessibility(AXPlatformNodeWin* obj) { + static LONG next_unique_id = -1; + LONG unique_id = next_unique_id; + if (next_unique_id == LONG_MIN) + next_unique_id = -1; + else + next_unique_id--; + + g_unique_id_win_map.Get().insert(std::make_pair(unique_id, obj)); + + return unique_id; +} + +void UnregisterNegativeUniqueId(LONG unique_id) { + g_unique_id_win_map.Get().erase(unique_id); +} + +} // namespace + +// static +AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) { + // Make sure ATL is initialized in this module. + ui::win::CreateATLModuleIfNeeded(); + + CComObject* instance = nullptr; + HRESULT hr = CComObject::CreateInstance(&instance); + DCHECK(SUCCEEDED(hr)); + instance->Init(delegate); + instance->AddRef(); + return instance; +} + +// static +AXPlatformNode* AXPlatformNode::FromNativeViewAccessible( + gfx::NativeViewAccessible accessible) { + base::win::ScopedComPtr ax_platform_node; + accessible->QueryInterface(ax_platform_node.Receive()); + return ax_platform_node.get(); +} + +AXPlatformNodeWin::AXPlatformNodeWin() + : unique_id_win_(GetNextNegativeUniqueIdForWinAccessibility(this)) { +} + +AXPlatformNodeWin::~AXPlatformNodeWin() { + CHECK(!delegate_); +} + +// +// AXPlatformNode implementation. +// + +void AXPlatformNodeWin::Destroy() { + delegate_ = nullptr; + UnregisterNegativeUniqueId(unique_id_win_); + RemoveAlertTarget(); + Release(); +} + +gfx::NativeViewAccessible AXPlatformNodeWin::GetNativeViewAccessible() { + return this; +} + +void AXPlatformNodeWin::NotifyAccessibilityEvent(ui::AXEvent event_type) { + HWND hwnd = delegate_->GetTargetForNativeAccessibilityEvent(); + if (!hwnd) + return; + + int native_event = MSAAEvent(event_type); + if (native_event < EVENT_MIN) + return; + + ::NotifyWinEvent(native_event, hwnd, OBJID_CLIENT, unique_id_win_); + + // Keep track of objects that are a target of an alert event. + if (event_type == ui::AX_EVENT_ALERT) + AddAlertTarget(); +} + +int AXPlatformNodeWin::GetIndexInParent() { + base::win::ScopedComPtr parent_dispatch; + base::win::ScopedComPtr parent_accessible; + if (S_OK != get_accParent(parent_dispatch.Receive())) + return -1; + if (S_OK != parent_dispatch.QueryInterface(parent_accessible.Receive())) + return -1; + + LONG child_count = 0; + if (S_OK != parent_accessible->get_accChildCount(&child_count)) + return -1; + for (LONG index = 1; index <= child_count; ++index) { + base::win::ScopedVariant childid_index(index); + base::win::ScopedComPtr child_dispatch; + base::win::ScopedComPtr child_accessible; + if (S_OK == parent_accessible->get_accChild(childid_index, + child_dispatch.Receive()) && + S_OK == child_dispatch.QueryInterface(child_accessible.Receive())) { + if (child_accessible.get() == this) + return index - 1; + } + } + + return -1; +} + +// +// IAccessible implementation. +// + +STDMETHODIMP AXPlatformNodeWin::accHitTest( + LONG x_left, LONG y_top, VARIANT* child) { + COM_OBJECT_VALIDATE_1_ARG(child); + gfx::NativeViewAccessible hit_child = delegate_->HitTestSync(x_left, y_top); + if (!hit_child) { + child->vt = VT_EMPTY; + return S_FALSE; + } + + if (hit_child == this) { + // This object is the best match, so return CHILDID_SELF. It's tempting to + // simplify the logic and use VT_DISPATCH everywhere, but the Windows + // call AccessibleObjectFromPoint will keep calling accHitTest until some + // object returns CHILDID_SELF. + child->vt = VT_I4; + child->lVal = CHILDID_SELF; + return S_OK; + } + + // Call accHitTest recursively on the result, which may be a recursive call + // to this function or it may be overridden, for example in the case of a + // WebView. + HRESULT result = hit_child->accHitTest(x_left, y_top, child); + + // If the recursive call returned CHILDID_SELF, we have to convert that + // into a VT_DISPATCH for the return value to this call. + if (S_OK == result && child->vt == VT_I4 && child->lVal == CHILDID_SELF) { + child->vt = VT_DISPATCH; + child->pdispVal = hit_child; + // Always increment ref when returning a reference to a COM object. + child->pdispVal->AddRef(); + } + return result; +} + +HRESULT AXPlatformNodeWin::accDoDefaultAction(VARIANT var_id) { + COM_OBJECT_VALIDATE_VAR_ID(var_id); + delegate_->DoDefaultAction(); + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::accLocation( + LONG* x_left, LONG* y_top, LONG* width, LONG* height, VARIANT var_id) { + COM_OBJECT_VALIDATE_VAR_ID_4_ARGS(var_id, x_left, y_top, width, height); + gfx::Rect bounds = GetData().location; + bounds += delegate_->GetGlobalCoordinateOffset(); + *x_left = bounds.x(); + *y_top = bounds.y(); + *width = bounds.width(); + *height = bounds.height(); + + if (bounds.IsEmpty()) + return S_FALSE; + + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::accNavigate( + LONG nav_dir, VARIANT start, VARIANT* end) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(start, end); + IAccessible* result = nullptr; + + 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 (delegate_->GetChildCount() > 0) + result = delegate_->ChildAtIndex(0); + break; + + case NAVDIR_LASTCHILD: + if (delegate_->GetChildCount() > 0) + result = delegate_->ChildAtIndex(delegate_->GetChildCount() - 1); + break; + + case NAVDIR_NEXT: { + AXPlatformNodeBase* next = GetNextSibling(); + if (next) + result = next->GetNativeViewAccessible(); + break; + } + + case NAVDIR_PREVIOUS: { + AXPlatformNodeBase* previous = GetPreviousSibling(); + if (previous) + result = previous->GetNativeViewAccessible(); + break; + } + + default: + return E_INVALIDARG; + } + + if (!result) { + end->vt = VT_EMPTY; + return S_FALSE; + } + + end->vt = VT_DISPATCH; + end->pdispVal = result; + // Always increment ref when returning a reference to a COM object. + end->pdispVal->AddRef(); + + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_accChild(VARIANT var_child, + IDispatch** disp_child) { + COM_OBJECT_VALIDATE_1_ARG(disp_child); + LONG child_id = V_I4(&var_child); + if (child_id == CHILDID_SELF) { + *disp_child = this; + (*disp_child)->AddRef(); + return S_OK; + } + + if (child_id >= 1 && child_id <= delegate_->GetChildCount()) { + // Positive child ids are a 1-based child index, used by clients + // that want to enumerate all immediate children. + *disp_child = delegate_->ChildAtIndex(child_id - 1); + (*disp_child)->AddRef(); + return S_OK; + } + + if (child_id >= 0) + return E_FAIL; + + // Negative child ids can be used to map to any descendant. + UniqueIdWinMap* unique_ids = g_unique_id_win_map.Pointer(); + auto iter = unique_ids->find(child_id); + if (iter != unique_ids->end()) { + *disp_child = iter->second; + (*disp_child)->AddRef(); + return S_OK; + } + + *disp_child = nullptr; + return E_FAIL; +} + +STDMETHODIMP AXPlatformNodeWin::get_accChildCount(LONG* child_count) { + COM_OBJECT_VALIDATE_1_ARG(child_count); + *child_count = delegate_->GetChildCount(); + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_accDefaultAction( + VARIANT var_id, BSTR* def_action) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, def_action); + return GetStringAttributeAsBstr(ui::AX_ATTR_ACTION, def_action); +} + +STDMETHODIMP AXPlatformNodeWin::get_accDescription( + VARIANT var_id, BSTR* desc) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, desc); + return GetStringAttributeAsBstr(ui::AX_ATTR_DESCRIPTION, desc); +} + +STDMETHODIMP AXPlatformNodeWin::get_accFocus(VARIANT* focus_child) { + COM_OBJECT_VALIDATE_1_ARG(focus_child); + gfx::NativeViewAccessible focus_accessible = delegate_->GetFocus(); + if (focus_accessible == this) { + focus_child->vt = VT_I4; + focus_child->lVal = CHILDID_SELF; + } else if (focus_accessible) { + focus_child->vt = VT_DISPATCH; + focus_child->pdispVal = focus_accessible; + focus_child->pdispVal->AddRef(); + return S_OK; + } else { + focus_child->vt = VT_EMPTY; + } + + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_accKeyboardShortcut( + VARIANT var_id, BSTR* acc_key) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, acc_key); + return GetStringAttributeAsBstr(ui::AX_ATTR_SHORTCUT, acc_key); +} + +STDMETHODIMP AXPlatformNodeWin::get_accName( + VARIANT var_id, BSTR* name) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, name); + return GetStringAttributeAsBstr(ui::AX_ATTR_NAME, name); +} + +STDMETHODIMP AXPlatformNodeWin::get_accParent( + IDispatch** disp_parent) { + COM_OBJECT_VALIDATE_1_ARG(disp_parent); + *disp_parent = GetParent(); + if (*disp_parent) { + (*disp_parent)->AddRef(); + return S_OK; + } + + return S_FALSE; +} + +STDMETHODIMP AXPlatformNodeWin::get_accRole( + VARIANT var_id, VARIANT* role) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, role); + role->vt = VT_I4; + role->lVal = MSAARole(); + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_accState( + VARIANT var_id, VARIANT* state) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, state); + state->vt = VT_I4; + state->lVal = MSAAState(); + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_accHelp( + VARIANT var_id, BSTR* help) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, help); + return GetStringAttributeAsBstr(ui::AX_ATTR_HELP, help); +} + +STDMETHODIMP AXPlatformNodeWin::get_accValue(VARIANT var_id, BSTR* value) { + COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, value); + return GetStringAttributeAsBstr(ui::AX_ATTR_VALUE, value); +} + +STDMETHODIMP AXPlatformNodeWin::put_accValue(VARIANT var_id, + BSTR new_value) { + COM_OBJECT_VALIDATE_VAR_ID(var_id); + if (delegate_->SetStringValue(new_value)) + return S_OK; + return E_FAIL; +} + +// IAccessible functions not supported. + +STDMETHODIMP AXPlatformNodeWin::get_accSelection(VARIANT* selected) { + COM_OBJECT_VALIDATE_1_ARG(selected); + if (selected) + selected->vt = VT_EMPTY; + return E_NOTIMPL; +} + +STDMETHODIMP AXPlatformNodeWin::accSelect( + LONG flagsSelect, VARIANT var_id) { + COM_OBJECT_VALIDATE_VAR_ID(var_id); + return E_NOTIMPL; +} + +STDMETHODIMP AXPlatformNodeWin::get_accHelpTopic( + BSTR* help_file, VARIANT var_id, LONG* topic_id) { + COM_OBJECT_VALIDATE_VAR_ID_2_ARGS(var_id, help_file, topic_id); + if (help_file) { + *help_file = nullptr; + } + if (topic_id) { + *topic_id = static_cast(-1); + } + return E_NOTIMPL; +} + +STDMETHODIMP AXPlatformNodeWin::put_accName( + VARIANT var_id, BSTR put_name) { + COM_OBJECT_VALIDATE_VAR_ID(var_id); + // Deprecated. + return E_NOTIMPL; +} + +// +// IAccessible2 implementation. +// + +STDMETHODIMP AXPlatformNodeWin::role(LONG* role) { + COM_OBJECT_VALIDATE_1_ARG(role); + *role = MSAARole(); + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_states(AccessibleStates* states) { + COM_OBJECT_VALIDATE_1_ARG(states); + // There are only a couple of states we need to support + // in IAccessible2. If any more are added, we may want to + // add a helper function like MSAAState. + *states = IA2_STATE_OPAQUE; + if (GetData().state & (1 << ui::AX_STATE_EDITABLE)) + *states |= IA2_STATE_EDITABLE; + + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_uniqueID(LONG* unique_id) { + COM_OBJECT_VALIDATE_1_ARG(unique_id); + *unique_id = unique_id_win_; + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_windowHandle(HWND* window_handle) { + COM_OBJECT_VALIDATE_1_ARG(window_handle); + *window_handle = delegate_->GetTargetForNativeAccessibilityEvent(); + return *window_handle ? S_OK : S_FALSE; +} + +STDMETHODIMP AXPlatformNodeWin::get_relationTargetsOfType( + BSTR type_bstr, + long max_targets, + IUnknown ***targets, + long *n_targets) { + COM_OBJECT_VALIDATE_2_ARGS(targets, n_targets); + + *n_targets = 0; + *targets = nullptr; + + // Only respond to requests for relations of type "alerts". + base::string16 type(type_bstr); + if (type != L"alerts") + return S_FALSE; + + // Collect all of the objects that have had an alert fired on them that + // are a descendant of this object. + std::vector alert_targets; + for (auto iter = g_alert_targets.Get().begin(); + iter != g_alert_targets.Get().end(); + ++iter) { + AXPlatformNodeWin* target = *iter; + if (IsDescendant(target)) + alert_targets.push_back(target); + } + + long count = static_cast(alert_targets.size()); + if (count == 0) + return S_FALSE; + + // Don't return more targets than max_targets - but note that the caller + // is allowed to specify max_targets=0 to mean no limit. + if (max_targets > 0 && count > max_targets) + count = max_targets; + + // Return the number of targets. + *n_targets = count; + + // Allocate COM memory for the result array and populate it. + *targets = static_cast( + CoTaskMemAlloc(count * sizeof(IUnknown*))); + for (long i = 0; i < count; ++i) { + (*targets)[i] = static_cast(alert_targets[i]); + (*targets)[i]->AddRef(); + } + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_attributes(BSTR* attributes) { + COM_OBJECT_VALIDATE_1_ARG(attributes); + base::string16 attributes_str; + + // Text fields need to report the attribute "text-model:a1" to instruct + // screen readers to use IAccessible2 APIs to handle text editing in this + // object (as opposed to treating it like a native Windows text box). + // The text-model:a1 attribute is documented here: + // http://www.linuxfoundation.org/collaborate/workgroups/accessibility/ia2/ia2_implementation_guide + if (GetData().role == ui::AX_ROLE_TEXT_FIELD) { + attributes_str = L"text-model:a1;"; + } + + *attributes = SysAllocString(attributes_str.c_str()); + DCHECK(*attributes); + return S_OK; +} + +// +// IAccessibleText +// + +STDMETHODIMP AXPlatformNodeWin::get_nCharacters(LONG* n_characters) { + COM_OBJECT_VALIDATE_1_ARG(n_characters); + base::string16 text = TextForIAccessibleText(); + *n_characters = static_cast(text.size()); + + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_caretOffset(LONG* offset) { + COM_OBJECT_VALIDATE_1_ARG(offset); + *offset = static_cast(GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END)); + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_nSelections(LONG* n_selections) { + COM_OBJECT_VALIDATE_1_ARG(n_selections); + int sel_start = GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START); + int sel_end = GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END); + if (sel_start != sel_end) + *n_selections = 1; + else + *n_selections = 0; + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_selection(LONG selection_index, + LONG* start_offset, + LONG* end_offset) { + COM_OBJECT_VALIDATE_2_ARGS(start_offset, end_offset); + if (selection_index != 0) + return E_INVALIDARG; + + *start_offset = static_cast( + GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START)); + *end_offset = static_cast( + GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END)); + return S_OK; +} + +STDMETHODIMP AXPlatformNodeWin::get_text(LONG start_offset, + LONG end_offset, + BSTR* text) { + COM_OBJECT_VALIDATE_1_ARG(text); + int sel_end = GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START); + base::string16 text_str = TextForIAccessibleText(); + LONG len = static_cast(text_str.size()); + + if (start_offset == IA2_TEXT_OFFSET_LENGTH) { + start_offset = len; + } else if (start_offset == IA2_TEXT_OFFSET_CARET) { + start_offset = static_cast(sel_end); + } + if (end_offset == IA2_TEXT_OFFSET_LENGTH) { + end_offset = static_cast(text_str.size()); + } else if (end_offset == IA2_TEXT_OFFSET_CARET) { + end_offset = static_cast(sel_end); + } + + // 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. + if (start_offset < 0) + return E_INVALIDARG; + if (end_offset > len) + return E_INVALIDARG; + + base::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 AXPlatformNodeWin::get_textAtOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + COM_OBJECT_VALIDATE_3_ARGS(start_offset, end_offset, text); + // The IAccessible2 spec says we don't have to implement the "sentence" + // boundary type, we can just let the screen reader handle it. + if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { + *start_offset = 0; + *end_offset = 0; + *text = nullptr; + return S_FALSE; + } + + const base::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 AXPlatformNodeWin::get_textBeforeOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + 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 = nullptr; + return S_FALSE; + } + + const base::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 AXPlatformNodeWin::get_textAfterOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + 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 = nullptr; + return S_FALSE; + } + + const base::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 AXPlatformNodeWin::get_offsetAtPoint( + LONG x, LONG y, enum IA2CoordinateType coord_type, LONG* offset) { + COM_OBJECT_VALIDATE_1_ARG(offset); + // We don't support this method, but we have to return something + // rather than E_NOTIMPL or screen readers will complain. + *offset = 0; + return S_OK; +} + +// +// IServiceProvider implementation. +// + +STDMETHODIMP AXPlatformNodeWin::QueryService( + REFGUID guidService, REFIID riid, void** object) { + COM_OBJECT_VALIDATE_1_ARG(object); + if (guidService == IID_IAccessible || + guidService == IID_IAccessible2 || + guidService == IID_IAccessible2_2 || + guidService == IID_IAccessibleText) { + return QueryInterface(riid, object); + } + + *object = nullptr; + return E_FAIL; +} + +// +// Private member functions. +// + +bool AXPlatformNodeWin::IsValidId(const VARIANT& child) const { + // Since we have a separate IAccessible COM object for each node, we only + // support the CHILDID_SELF id. + return (VT_I4 == child.vt) && (CHILDID_SELF == child.lVal); +} + +int AXPlatformNodeWin::MSAARole() { + switch (GetData().role) { + case ui::AX_ROLE_ALERT: + return ROLE_SYSTEM_ALERT; + case ui::AX_ROLE_APPLICATION: + return ROLE_SYSTEM_APPLICATION; + case ui::AX_ROLE_BUTTON_DROP_DOWN: + return ROLE_SYSTEM_BUTTONDROPDOWN; + case ui::AX_ROLE_POP_UP_BUTTON: + return ROLE_SYSTEM_BUTTONMENU; + case ui::AX_ROLE_CHECK_BOX: + return ROLE_SYSTEM_CHECKBUTTON; + case ui::AX_ROLE_COMBO_BOX: + return ROLE_SYSTEM_COMBOBOX; + case ui::AX_ROLE_DIALOG: + return ROLE_SYSTEM_DIALOG; + case ui::AX_ROLE_GROUP: + return ROLE_SYSTEM_GROUPING; + case ui::AX_ROLE_IMAGE: + return ROLE_SYSTEM_GRAPHIC; + case ui::AX_ROLE_LINK: + return ROLE_SYSTEM_LINK; + case ui::AX_ROLE_LOCATION_BAR: + return ROLE_SYSTEM_GROUPING; + case ui::AX_ROLE_MENU_BAR: + return ROLE_SYSTEM_MENUBAR; + case ui::AX_ROLE_MENU_ITEM: + return ROLE_SYSTEM_MENUITEM; + case ui::AX_ROLE_MENU_LIST_POPUP: + return ROLE_SYSTEM_MENUPOPUP; + case ui::AX_ROLE_TREE: + return ROLE_SYSTEM_OUTLINE; + case ui::AX_ROLE_TREE_ITEM: + return ROLE_SYSTEM_OUTLINEITEM; + case ui::AX_ROLE_TAB: + return ROLE_SYSTEM_PAGETAB; + case ui::AX_ROLE_TAB_LIST: + return ROLE_SYSTEM_PAGETABLIST; + case ui::AX_ROLE_PANE: + return ROLE_SYSTEM_PANE; + case ui::AX_ROLE_PROGRESS_INDICATOR: + return ROLE_SYSTEM_PROGRESSBAR; + case ui::AX_ROLE_BUTTON: + return ROLE_SYSTEM_PUSHBUTTON; + case ui::AX_ROLE_RADIO_BUTTON: + return ROLE_SYSTEM_RADIOBUTTON; + case ui::AX_ROLE_SCROLL_BAR: + return ROLE_SYSTEM_SCROLLBAR; + case ui::AX_ROLE_SPLITTER: + return ROLE_SYSTEM_SEPARATOR; + case ui::AX_ROLE_SLIDER: + return ROLE_SYSTEM_SLIDER; + case ui::AX_ROLE_STATIC_TEXT: + return ROLE_SYSTEM_STATICTEXT; + case ui::AX_ROLE_TEXT_FIELD: + return ROLE_SYSTEM_TEXT; + case ui::AX_ROLE_TITLE_BAR: + return ROLE_SYSTEM_TITLEBAR; + case ui::AX_ROLE_TOOLBAR: + return ROLE_SYSTEM_TOOLBAR; + case ui::AX_ROLE_WEB_VIEW: + return ROLE_SYSTEM_GROUPING; + case ui::AX_ROLE_WINDOW: + return ROLE_SYSTEM_WINDOW; + case ui::AX_ROLE_CLIENT: + default: + // This is the default role for MSAA. + return ROLE_SYSTEM_CLIENT; + } +} + +int AXPlatformNodeWin::MSAAState() { + uint32 state = GetData().state; + + int msaa_state = 0; + if (state & (1 << ui::AX_STATE_CHECKED)) + msaa_state |= STATE_SYSTEM_CHECKED; + if (state & (1 << ui::AX_STATE_COLLAPSED)) + msaa_state |= STATE_SYSTEM_COLLAPSED; + if (state & (1 << ui::AX_STATE_DEFAULT)) + msaa_state |= STATE_SYSTEM_DEFAULT; + if (state & (1 << ui::AX_STATE_EXPANDED)) + msaa_state |= STATE_SYSTEM_EXPANDED; + if (state & (1 << ui::AX_STATE_FOCUSABLE)) + msaa_state |= STATE_SYSTEM_FOCUSABLE; + if (state & (1 << ui::AX_STATE_FOCUSED)) + msaa_state |= STATE_SYSTEM_FOCUSED; + if (state & (1 << ui::AX_STATE_HASPOPUP)) + msaa_state |= STATE_SYSTEM_HASPOPUP; + if (state & (1 << ui::AX_STATE_HOVERED)) + msaa_state |= STATE_SYSTEM_HOTTRACKED; + if (state & (1 << ui::AX_STATE_INVISIBLE)) + msaa_state |= STATE_SYSTEM_INVISIBLE; + if (state & (1 << ui::AX_STATE_LINKED)) + msaa_state |= STATE_SYSTEM_LINKED; + if (state & (1 << ui::AX_STATE_OFFSCREEN)) + msaa_state |= STATE_SYSTEM_OFFSCREEN; + if (state & (1 << ui::AX_STATE_PRESSED)) + msaa_state |= STATE_SYSTEM_PRESSED; + if (state & (1 << ui::AX_STATE_PROTECTED)) + msaa_state |= STATE_SYSTEM_PROTECTED; + if (state & (1 << ui::AX_STATE_READ_ONLY)) + msaa_state |= STATE_SYSTEM_READONLY; + if (state & (1 << ui::AX_STATE_SELECTABLE)) + msaa_state |= STATE_SYSTEM_SELECTABLE; + if (state & (1 << ui::AX_STATE_SELECTED)) + msaa_state |= STATE_SYSTEM_SELECTED; + if (state & (1 << ui::AX_STATE_DISABLED)) + msaa_state |= STATE_SYSTEM_UNAVAILABLE; + + return msaa_state; +} + +int AXPlatformNodeWin::MSAAEvent(ui::AXEvent event) { + switch (event) { + case ui::AX_EVENT_ALERT: + return EVENT_SYSTEM_ALERT; + case ui::AX_EVENT_FOCUS: + return EVENT_OBJECT_FOCUS; + case ui::AX_EVENT_MENU_START: + return EVENT_SYSTEM_MENUSTART; + case ui::AX_EVENT_MENU_END: + return EVENT_SYSTEM_MENUEND; + case ui::AX_EVENT_MENU_POPUP_START: + return EVENT_SYSTEM_MENUPOPUPSTART; + case ui::AX_EVENT_MENU_POPUP_END: + return EVENT_SYSTEM_MENUPOPUPEND; + case ui::AX_EVENT_SELECTION: + return EVENT_OBJECT_SELECTION; + case ui::AX_EVENT_SELECTION_ADD: + return EVENT_OBJECT_SELECTIONADD; + case ui::AX_EVENT_SELECTION_REMOVE: + return EVENT_OBJECT_SELECTIONREMOVE; + case ui::AX_EVENT_TEXT_CHANGED: + return EVENT_OBJECT_NAMECHANGE; + case ui::AX_EVENT_TEXT_SELECTION_CHANGED: + return IA2_EVENT_TEXT_CARET_MOVED; + case ui::AX_EVENT_VALUE_CHANGED: + return EVENT_OBJECT_VALUECHANGE; + default: + return -1; + } +} + +HRESULT AXPlatformNodeWin::GetStringAttributeAsBstr( + ui::AXStringAttribute attribute, + BSTR* value_bstr) const { + base::string16 str; + + if (!GetString16Attribute(attribute, &str)) + return S_FALSE; + + if (str.empty()) + return S_FALSE; + + *value_bstr = SysAllocString(str.c_str()); + DCHECK(*value_bstr); + + return S_OK; +} + +void AXPlatformNodeWin::AddAlertTarget() { + g_alert_targets.Get().insert(this); +} + +void AXPlatformNodeWin::RemoveAlertTarget() { + if (g_alert_targets.Get().find(this) != g_alert_targets.Get().end()) + g_alert_targets.Get().erase(this); +} + +base::string16 AXPlatformNodeWin::TextForIAccessibleText() { + if (GetData().role == ui::AX_ROLE_TEXT_FIELD) + return GetString16Attribute(ui::AX_ATTR_VALUE); + else + return GetString16Attribute(ui::AX_ATTR_NAME); +} + +void AXPlatformNodeWin::HandleSpecialTextOffset( + const base::string16& text, LONG* offset) { + if (*offset == IA2_TEXT_OFFSET_LENGTH) { + *offset = static_cast(text.size()); + } else if (*offset == IA2_TEXT_OFFSET_CARET) { + get_caretOffset(offset); + } +} + +ui::TextBoundaryType AXPlatformNodeWin::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 AXPlatformNodeWin::FindBoundary( + const base::string16& text, + IA2TextBoundaryType ia2_boundary, + LONG start_offset, + ui::TextBoundaryDirection direction) { + HandleSpecialTextOffset(text, &start_offset); + ui::TextBoundaryType boundary = IA2TextBoundaryToTextBoundary(ia2_boundary); + std::vector line_breaks; + return static_cast(ui::FindAccessibleTextBoundary( + text, line_breaks, boundary, start_offset, direction)); +} + +} // namespace ui diff --git a/ui/views/accessibility/native_view_accessibility_win.h b/ui/accessibility/platform/ax_platform_node_win.h similarity index 57% copy from ui/views/accessibility/native_view_accessibility_win.h copy to ui/accessibility/platform/ax_platform_node_win.h index e34bdc33c310..36217904b49a 100644 --- a/ui/views/accessibility/native_view_accessibility_win.h +++ b/ui/accessibility/platform/ax_platform_node_win.h @@ -1,141 +1,117 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2015 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 UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ -#define UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ +#ifndef UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_WIN_H_ +#define UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_WIN_H_ #include #include #include -#include - -#include -#include - #include "third_party/iaccessible2/ia2_api_all.h" -#include "ui/accessibility/ax_view_state.h" -#include "ui/views/accessibility/native_view_accessibility.h" -#include "ui/views/controls/native/native_view_host.h" -#include "ui/views/view.h" +#include "ui/accessibility/platform/ax_platform_node_base.h" namespace ui { -enum TextBoundaryDirection; -enum TextBoundaryType; -} - -namespace views { - -//////////////////////////////////////////////////////////////////////////////// -// -// NativeViewAccessibilityWin -// -// Class implementing the MSAA IAccessible COM interface for a generic View, -// providing accessibility to be used by screen readers and other assistive -// technology (AT). -// -//////////////////////////////////////////////////////////////////////////////// + class __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) -NativeViewAccessibilityWin - : public CComObjectRootEx, - public IDispatchImpl, +AXPlatformNodeWin +: public CComObjectRootEx, + public IDispatchImpl, public IAccessibleText, public IServiceProvider, - public IAccessibleEx, - public IRawElementProviderSimple, - public NativeViewAccessibility { + public AXPlatformNodeBase { public: - BEGIN_COM_MAP(NativeViewAccessibilityWin) + BEGIN_COM_MAP(AXPlatformNodeWin) COM_INTERFACE_ENTRY2(IDispatch, IAccessible2_2) + COM_INTERFACE_ENTRY(AXPlatformNodeWin) COM_INTERFACE_ENTRY(IAccessible) COM_INTERFACE_ENTRY(IAccessible2) COM_INTERFACE_ENTRY(IAccessible2_2) - COM_INTERFACE_ENTRY(IAccessibleEx) COM_INTERFACE_ENTRY(IAccessibleText) - COM_INTERFACE_ENTRY(IRawElementProviderSimple) COM_INTERFACE_ENTRY(IServiceProvider) END_COM_MAP() - virtual ~NativeViewAccessibilityWin(); + virtual ~AXPlatformNodeWin(); - // NativeViewAccessibility. - void NotifyAccessibilityEvent(ui::AXEvent event_type) override; - gfx::NativeViewAccessible GetNativeObject() override; + // AXPlatformNode overrides. void Destroy() override; + gfx::NativeViewAccessible GetNativeViewAccessible() override; + void NotifyAccessibilityEvent(ui::AXEvent event_type) override; + + // AXPlatformNodeBase overrides. + int GetIndexInParent() override; - // Supported IAccessible methods. + // + // IAccessible methods. + // // Retrieves the child element or child object at a given point on the screen. - STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child) override; + virtual STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child); // Performs the object's default action. - STDMETHODIMP accDoDefaultAction(VARIANT var_id) override; + STDMETHODIMP accDoDefaultAction(VARIANT var_id); // Retrieves the specified object's current screen location. STDMETHODIMP accLocation(LONG* x_left, LONG* y_top, LONG* width, LONG* height, - VARIANT var_id) override; + VARIANT var_id); // Traverses to another UI element and retrieves the object. - STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end) override; + STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end); // Retrieves an IDispatch interface pointer for the specified child. - STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child) override; + virtual STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child); // Retrieves the number of accessible children. - STDMETHODIMP get_accChildCount(LONG* child_count) override; + virtual STDMETHODIMP get_accChildCount(LONG* child_count); // Retrieves a string that describes the object's default action. - STDMETHODIMP get_accDefaultAction(VARIANT var_id, - BSTR* default_action) override; + STDMETHODIMP get_accDefaultAction(VARIANT var_id, BSTR* default_action); // Retrieves the tooltip description. - STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc) override; + STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc); // Retrieves the object that has the keyboard focus. - STDMETHODIMP get_accFocus(VARIANT* focus_child) override; + STDMETHODIMP get_accFocus(VARIANT* focus_child); // Retrieves the specified object's shortcut. - STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, - BSTR* access_key) override; + STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, BSTR* access_key); // Retrieves the name of the specified object. - STDMETHODIMP get_accName(VARIANT var_id, BSTR* name) override; + STDMETHODIMP get_accName(VARIANT var_id, BSTR* name); // Retrieves the IDispatch interface of the object's parent. - STDMETHODIMP get_accParent(IDispatch** disp_parent) override; + STDMETHODIMP get_accParent(IDispatch** disp_parent); // Retrieves information describing the role of the specified object. - STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role) override; + STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role); // Retrieves the current state of the specified object. - STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state) override; + STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state); + + // Gets the help string for the specified object. + STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help); // Retrieve or set the string value associated with the specified object. // Setting the value is not typically used by screen readers, but it's // used frequently by automation software. - STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value) override; - STDMETHODIMP put_accValue(VARIANT var_id, BSTR new_value) override; + STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value); + STDMETHODIMP put_accValue(VARIANT var_id, BSTR new_value); - // Selections not applicable to views. - STDMETHODIMP get_accSelection(VARIANT* selected) override; - STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id) override; - - // Help functions not supported. - STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help) override; + // IAccessible methods not implemented. + STDMETHODIMP get_accSelection(VARIANT* selected); + STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id); STDMETHODIMP get_accHelpTopic(BSTR* help_file, VARIANT var_id, - LONG* topic_id) override; - - // Deprecated functions, not implemented here. - STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) override; + LONG* topic_id); + STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name); // - // IAccessible2 + // IAccessible2 methods. // STDMETHODIMP role(LONG* role) override; @@ -307,85 +283,23 @@ NativeViewAccessibilityWin // IServiceProvider methods. // - STDMETHODIMP QueryService(REFGUID guidService, - REFIID riid, - void** object) override; - - // - // IAccessibleEx methods not implemented. - // - STDMETHODIMP GetObjectForChild(long child_id, IAccessibleEx** ret) override { - return E_NOTIMPL; - } - - STDMETHODIMP GetIAccessiblePair(IAccessible** acc, long* child_id) override { - return E_NOTIMPL; - } - - STDMETHODIMP GetRuntimeId(SAFEARRAY** runtime_id) override { - return E_NOTIMPL; - } - - STDMETHODIMP ConvertReturnedElement(IRawElementProviderSimple* element, - IAccessibleEx** acc) override { - 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. - STDMETHODIMP GetPatternProvider(PATTERNID id, IUnknown** provider) override; - STDMETHODIMP GetPropertyValue(PROPERTYID id, VARIANT* ret) override; - - // - // IRawElementProviderSimple methods not implemented. - // - STDMETHODIMP get_ProviderOptions(enum ProviderOptions* ret) override { - return E_NOTIMPL; - } - - STDMETHODIMP get_HostRawElementProvider( - IRawElementProviderSimple** provider) override { - return E_NOTIMPL; - } - - // Static methods - - // Returns a conversion from the event (as defined in ax_enums.idl) - // to an MSAA event. - static int32 MSAAEvent(ui::AXEvent event); - - // Returns a conversion from the Role (as defined in ax_enums.idl) - // to an MSAA role. - static int32 MSAARole(ui::AXRole role); - - // Returns a conversion from the State (as defined in ax_enums.idl) - // to MSAA states set. - static int32 MSAAState(const ui::AXViewState& state); + STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void** object); protected: - NativeViewAccessibilityWin(); + AXPlatformNodeWin(); private: - // Determines navigation direction for accNavigate, based on left, up and - // previous being mapped all to previous and right, down, next being mapped - // to next. Returns true if navigation direction is next, false otherwise. - bool IsNavDirNext(int nav_dir) const; - - // Determines if the navigation target is within the allowed bounds. Returns - // true if it is, false otherwise. - bool IsValidNav(int nav_dir, - int start_id, - int lower_bound, - int upper_bound) const; - - // Determines if the child id variant is valid. bool IsValidId(const VARIANT& child) const; + int MSAARole(); + int MSAAState(); + int MSAAEvent(ui::AXEvent event); - // Helper function which sets applicable states of view. - void SetState(VARIANT* msaa_state, View* view); + HRESULT GetStringAttributeAsBstr( + ui::AXStringAttribute attribute, + BSTR* value_bstr) const; + + void AddAlertTarget(); + void RemoveAlertTarget(); // Return the text to use for IAccessibleText. base::string16 TextForIAccessibleText(); @@ -405,44 +319,17 @@ NativeViewAccessibilityWin LONG start_offset, ui::TextBoundaryDirection direction); - // Populates the given vector with all widgets that are either a child - // or are owned by this view's widget, and who are not contained in a - // NativeViewHost. - void PopulateChildWidgetVector(std::vector* child_widgets); - - // Adds this view to alert_target_view_storage_ids_. - void AddAlertTarget(); - - // Removes this view from alert_target_view_storage_ids_. - void RemoveAlertTarget(); - - // Give CComObject access to the class constructor. - template friend class CComObject; - - // A unique id for each object, needed for IAccessible2. - long unique_id_; - - // Next unique id to assign. - static long next_unique_id_; - - // Circular queue size. - static const int kMaxViewStorageIds = 20; - - // Circular queue of view storage ids corresponding to child ids - // used to post notifications using NotifyWinEvent. - static int view_storage_ids_[kMaxViewStorageIds]; - - // Next index into |view_storage_ids_| to use. - static int next_view_storage_id_index_; - - // A vector of view storage ids of views that have been the target of - // an alert event, in order to provide an api to quickly identify all - // open alerts. - static std::vector alert_target_view_storage_ids_; - - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityWin); + // A windows-specific unique ID for this object. It's returned in + // IAccessible2::get_uniqueID, but more importantly it's used for + // firing events. On Windows, we fire events on the nearest parent HWND + // and pass the unique ID as the child id parameter. When the client + // wants to retrieve the object the event was fired on, it calls + // get_accChild and passes the child ID. We use negative IDs for the unique + // ID so we can distinguish a request for an arbitrary child from a request + // for an immediate child of an object by its 0-based index. + LONG unique_id_win_; }; -} // namespace views +} // namespace ui -#endif // UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ +#endif // UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_WIN_H_ diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc new file mode 100644 index 000000000000..e6476816dfe1 --- /dev/null +++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc @@ -0,0 +1,372 @@ +// Copyright 2015 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 +#include +#include + +#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 "testing/gtest/include/gtest/gtest.h" +#include "third_party/iaccessible2/ia2_api_all.h" +#include "ui/accessibility/ax_node_data.h" +#include "ui/accessibility/platform/ax_platform_node.h" +#include "ui/accessibility/platform/test_ax_node_wrapper.h" +#include "ui/base/win/atl_module.h" + +using base::win::ScopedBstr; +using base::win::ScopedComPtr; +using base::win::ScopedVariant; + +namespace ui { + +namespace { + +// Most IAccessible functions require a VARIANT set to CHILDID_SELF as +// the first argument. +ScopedVariant SELF(CHILDID_SELF); + +} // namespace + +class AXPlatformNodeWinTest : public testing::Test { + public: + AXPlatformNodeWinTest() {} + virtual ~AXPlatformNodeWinTest() {} + + void SetUp() override { + win::CreateATLModuleIfNeeded(); + } + + // Initialize given an AXTreeUpdate. + void Init(const AXTreeUpdate& initial_state) { + tree_.reset(new AXTree(initial_state)); + } + + // Convenience functions to initialize directly from a few AXNodeDatas. + void Init(const AXNodeData& node1) { + AXTreeUpdate update; + update.nodes.push_back(node1); + Init(update); + } + + void Init(const AXNodeData& node1, + const AXNodeData& node2) { + AXTreeUpdate update; + update.nodes.push_back(node1); + update.nodes.push_back(node2); + Init(update); + } + + void Init(const AXNodeData& node1, + const AXNodeData& node2, + const AXNodeData& node3) { + AXTreeUpdate update; + update.nodes.push_back(node1); + update.nodes.push_back(node2); + update.nodes.push_back(node3); + Init(update); + } + + protected: + AXNode* GetRootNode() { + return tree_->root(); + } + + ScopedComPtr IAccessibleFromNode(AXNode* node) { + TestAXNodeWrapper* wrapper = + TestAXNodeWrapper::GetOrCreate(tree_.get(), node); + AXPlatformNode* ax_platform_node = wrapper->ax_platform_node(); + IAccessible* iaccessible = ax_platform_node->GetNativeViewAccessible(); + iaccessible->AddRef(); + return ScopedComPtr(iaccessible); + } + + ScopedComPtr GetRootIAccessible() { + return IAccessibleFromNode(GetRootNode()); + } + + ScopedComPtr ToIAccessible2( + ScopedComPtr accessible) { + ScopedComPtr service_provider; + service_provider.QueryFrom(accessible.get()); + ScopedComPtr result; + CHECK(SUCCEEDED( + service_provider->QueryService(IID_IAccessible2, result.Receive()))); + return result; + } + + scoped_ptr tree_; +}; + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleDetachedObject) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.AddStringAttribute(AX_ATTR_NAME, "Name"); + Init(root); + + ScopedComPtr root_obj(GetRootIAccessible()); + ScopedBstr name; + ASSERT_EQ(S_OK, root_obj->get_accName(SELF, name.Receive())); + EXPECT_EQ(L"Name", base::string16(name)); + + tree_.reset(new AXTree()); + ScopedBstr name2; + ASSERT_EQ(E_FAIL, root_obj->get_accName(SELF, name2.Receive())); +} + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleName) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.AddStringAttribute(AX_ATTR_NAME, "Name"); + Init(root); + + ScopedComPtr root_obj(GetRootIAccessible()); + ScopedBstr name; + ASSERT_EQ(S_OK, root_obj->get_accName(SELF, name.Receive())); + EXPECT_EQ(L"Name", base::string16(name)); + + ASSERT_EQ(E_INVALIDARG, root_obj->get_accName(SELF, nullptr)); + ScopedVariant bad_id(999); + ScopedBstr name2; + ASSERT_EQ(E_INVALIDARG, root_obj->get_accName(bad_id, name2.Receive())); +} + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleDescription) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.AddStringAttribute(AX_ATTR_DESCRIPTION, "Description"); + Init(root); + + ScopedComPtr root_obj(GetRootIAccessible()); + ScopedBstr description; + ASSERT_EQ(S_OK, root_obj->get_accDescription(SELF, description.Receive())); + EXPECT_EQ(L"Description", base::string16(description)); + + ASSERT_EQ(E_INVALIDARG, root_obj->get_accDescription(SELF, nullptr)); + ScopedVariant bad_id(999); + ScopedBstr d2; + ASSERT_EQ(E_INVALIDARG, root_obj->get_accDescription(bad_id, d2.Receive())); +} + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleHelp) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.AddStringAttribute(AX_ATTR_HELP, "Help"); + Init(root); + + ScopedComPtr root_obj(GetRootIAccessible()); + ScopedBstr help; + ASSERT_EQ(S_OK, root_obj->get_accHelp(SELF, help.Receive())); + EXPECT_EQ(L"Help", base::string16(help)); + + ASSERT_EQ(E_INVALIDARG, root_obj->get_accHelp(SELF, nullptr)); + ScopedVariant bad_id(999); + ScopedBstr h2; + ASSERT_EQ(E_INVALIDARG, root_obj->get_accHelp(bad_id, h2.Receive())); +} + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleValue) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.AddStringAttribute(AX_ATTR_VALUE, "Value"); + Init(root); + + ScopedComPtr root_obj(GetRootIAccessible()); + ScopedBstr value; + ASSERT_EQ(S_OK, root_obj->get_accValue(SELF, value.Receive())); + EXPECT_EQ(L"Value", base::string16(value)); + + ASSERT_EQ(E_INVALIDARG, root_obj->get_accValue(SELF, nullptr)); + ScopedVariant bad_id(999); + ScopedBstr v2; + ASSERT_EQ(E_INVALIDARG, root_obj->get_accValue(bad_id, v2.Receive())); +} + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleShortcut) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.AddStringAttribute(AX_ATTR_SHORTCUT, "Shortcut"); + Init(root); + + ScopedComPtr root_obj(GetRootIAccessible()); + ScopedBstr shortcut; + ASSERT_EQ(S_OK, root_obj->get_accKeyboardShortcut( + SELF, shortcut.Receive())); + EXPECT_EQ(L"Shortcut", base::string16(shortcut)); + + ASSERT_EQ(E_INVALIDARG, root_obj->get_accKeyboardShortcut(SELF, nullptr)); + ScopedVariant bad_id(999); + ScopedBstr k2; + ASSERT_EQ(E_INVALIDARG, root_obj->get_accKeyboardShortcut( + bad_id, k2.Receive())); +} + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleRole) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.child_ids.push_back(2); + + AXNodeData child; + child.id = 2; + + Init(root, child); + AXNode* child_node = GetRootNode()->children()[0]; + ScopedComPtr child_iaccessible( + IAccessibleFromNode(child_node)); + + ScopedVariant role; + + child.role = AX_ROLE_ALERT; + child_node->SetData(child); + ASSERT_EQ(S_OK, child_iaccessible->get_accRole(SELF, role.Receive())); + EXPECT_EQ(ROLE_SYSTEM_ALERT, V_I4(&role)); + + child.role = AX_ROLE_BUTTON; + child_node->SetData(child); + ASSERT_EQ(S_OK, child_iaccessible->get_accRole(SELF, role.Receive())); + EXPECT_EQ(ROLE_SYSTEM_PUSHBUTTON, V_I4(&role)); + + child.role = AX_ROLE_POP_UP_BUTTON; + child_node->SetData(child); + ASSERT_EQ(S_OK, child_iaccessible->get_accRole(SELF, role.Receive())); + EXPECT_EQ(ROLE_SYSTEM_BUTTONMENU, V_I4(&role)); + + ASSERT_EQ(E_INVALIDARG, child_iaccessible->get_accRole(SELF, nullptr)); + ScopedVariant bad_id(999); + ASSERT_EQ(E_INVALIDARG, child_iaccessible->get_accRole( + bad_id, role.Receive())); +} + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleLocation) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.location = gfx::Rect(10, 40, 800, 600); + Init(root); + + TestAXNodeWrapper::SetGlobalCoordinateOffset(gfx::Vector2d(100, 200)); + + LONG x_left, y_top, width, height; + ASSERT_EQ(S_OK, GetRootIAccessible()->accLocation( + &x_left, &y_top, &width, &height, SELF)); + EXPECT_EQ(110, x_left); + EXPECT_EQ(240, y_top); + EXPECT_EQ(800, width); + EXPECT_EQ(600, height); + + ASSERT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation( + nullptr, &y_top, &width, &height, SELF)); + ASSERT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation( + &x_left, nullptr, &width, &height, SELF)); + ASSERT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation( + &x_left, &y_top, nullptr, &height, SELF)); + ASSERT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation( + &x_left, &y_top, &width, nullptr, SELF)); + ScopedVariant bad_id(999); + ASSERT_EQ(E_INVALIDARG, GetRootIAccessible()->accLocation( + &x_left, &y_top, &width, &height, bad_id)); +} + +TEST_F(AXPlatformNodeWinTest, TestIAccessibleChildAndParent) { + AXNodeData root; + root.id = 1; + root.role = AX_ROLE_ROOT_WEB_AREA; + root.child_ids.push_back(2); + root.child_ids.push_back(3); + + AXNodeData button; + button.role = AX_ROLE_BUTTON; + button.id = 2; + + AXNodeData checkbox; + checkbox.role = AX_ROLE_CHECK_BOX; + checkbox.id = 3; + + Init(root, button, checkbox); + AXNode* button_node = GetRootNode()->children()[0]; + AXNode* checkbox_node = GetRootNode()->children()[1]; + ScopedComPtr root_iaccessible(GetRootIAccessible()); + ScopedComPtr button_iaccessible( + IAccessibleFromNode(button_node)); + ScopedComPtr checkbox_iaccessible( + IAccessibleFromNode(checkbox_node)); + + LONG child_count; + ASSERT_EQ(S_OK, root_iaccessible->get_accChildCount(&child_count)); + ASSERT_EQ(2L, child_count); + ASSERT_EQ(S_OK, button_iaccessible->get_accChildCount(&child_count)); + ASSERT_EQ(0L, child_count); + ASSERT_EQ(S_OK, checkbox_iaccessible->get_accChildCount(&child_count)); + ASSERT_EQ(0L, child_count); + + { + ScopedComPtr result; + ASSERT_EQ(S_OK, root_iaccessible->get_accChild(SELF, result.Receive())); + ASSERT_EQ(result.get(), root_iaccessible); + } + + { + ScopedComPtr result; + ScopedVariant child1(1); + ASSERT_EQ(S_OK, root_iaccessible->get_accChild(child1, result.Receive())); + ASSERT_EQ(result.get(), button_iaccessible); + } + + { + ScopedComPtr result; + ScopedVariant child2(2); + ASSERT_EQ(S_OK, root_iaccessible->get_accChild(child2, result.Receive())); + ASSERT_EQ(result.get(), checkbox_iaccessible); + } + + { + // Asking for child id 3 should fail. + ScopedComPtr result; + ScopedVariant child3(3); + ASSERT_EQ(E_FAIL, root_iaccessible->get_accChild(child3, result.Receive())); + } + + // We should be able to ask for the button by its unique id too. + LONG button_unique_id; + ScopedComPtr button_iaccessible2 = + ToIAccessible2(button_iaccessible); + button_iaccessible2->get_uniqueID(&button_unique_id); + ASSERT_LT(button_unique_id, 0); + { + ScopedComPtr result; + ScopedVariant button_id_variant(button_unique_id); + ASSERT_EQ(S_OK, root_iaccessible->get_accChild(button_id_variant, + result.Receive())); + ASSERT_EQ(result.get(), button_iaccessible); + } + + // Now check parents. + { + ScopedComPtr result; + ASSERT_EQ(S_OK, button_iaccessible->get_accParent(result.Receive())); + ASSERT_EQ(result.get(), root_iaccessible); + } + + { + ScopedComPtr result; + ASSERT_EQ(S_OK, checkbox_iaccessible->get_accParent(result.Receive())); + ASSERT_EQ(result.get(), root_iaccessible); + } + + { + ScopedComPtr result; + ASSERT_EQ(S_FALSE, root_iaccessible->get_accParent(result.Receive())); + } +} + +} // namespace ui diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc new file mode 100644 index 000000000000..056ad85caba6 --- /dev/null +++ b/ui/accessibility/platform/test_ax_node_wrapper.cc @@ -0,0 +1,120 @@ +// Copyright 2015 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/containers/hash_tables.h" +#include "ui/accessibility/platform/test_ax_node_wrapper.h" + +namespace ui { + +namespace { + +// A global map from AXNodes to TestAXNodeWrappers. +base::hash_map g_node_to_wrapper_map; + +// A global coordinate offset. +gfx::Vector2d g_offset; + +// A simple implementation of AXTreeDelegate to catch when AXNodes are +// deleted so we can delete their wrappers. +class TestAXTreeDelegate : public AXTreeDelegate { + void OnNodeWillBeDeleted(AXNode* node) override { + auto iter = g_node_to_wrapper_map.find(node); + if (iter != g_node_to_wrapper_map.end()) { + TestAXNodeWrapper* wrapper = iter->second; + delete wrapper; + g_node_to_wrapper_map.erase(iter->first); + } + } + void OnSubtreeWillBeDeleted(AXNode* node) override {} + void OnNodeCreated(AXNode* node) override {} + void OnNodeChanged(AXNode* node) override {} + void OnAtomicUpdateFinished(bool root_changed, + const std::vector& changes) override {} +}; + +TestAXTreeDelegate g_ax_tree_delegate; + +} // namespace + +// static +TestAXNodeWrapper* TestAXNodeWrapper::GetOrCreate(AXTree* tree, AXNode* node) { + // Just return NULL if |node| is NULL; this makes test code simpler because + // now we don't have to null-check AXNode* every time we call GetOrCreate. + if (!node) + return nullptr; + + tree->SetDelegate(&g_ax_tree_delegate); + auto iter = g_node_to_wrapper_map.find(node); + if (iter != g_node_to_wrapper_map.end()) + return iter->second; + TestAXNodeWrapper* wrapper = new TestAXNodeWrapper(tree, node); + g_node_to_wrapper_map[node] = wrapper; + return wrapper; +} + +// static +void TestAXNodeWrapper::SetGlobalCoordinateOffset(const gfx::Vector2d& offset) { + g_offset = offset; +} + +TestAXNodeWrapper::~TestAXNodeWrapper() { + platform_node_->Destroy(); +} + +const AXNodeData& TestAXNodeWrapper::GetData() { + return node_->data(); +} + +gfx::NativeViewAccessible TestAXNodeWrapper::GetParent() { + TestAXNodeWrapper* parent_wrapper = GetOrCreate(tree_, node_->parent()); + return parent_wrapper ? + parent_wrapper->ax_platform_node()->GetNativeViewAccessible() : + nullptr; +} + +int TestAXNodeWrapper::GetChildCount() { + return node_->child_count(); +} + +gfx::NativeViewAccessible TestAXNodeWrapper::ChildAtIndex(int index) { + CHECK_GE(index, 0); + CHECK_LT(index, GetChildCount()); + TestAXNodeWrapper* child_wrapper = + GetOrCreate(tree_, node_->children()[index]); + return child_wrapper ? + child_wrapper->ax_platform_node()->GetNativeViewAccessible() : + nullptr; +} + +gfx::Vector2d TestAXNodeWrapper::GetGlobalCoordinateOffset() { + return g_offset; +} + +gfx::NativeViewAccessible TestAXNodeWrapper::HitTestSync(int x, int y) { + return nullptr; +} + +gfx::NativeViewAccessible TestAXNodeWrapper::GetFocus() { + return nullptr; +} + +gfx::AcceleratedWidget +TestAXNodeWrapper::GetTargetForNativeAccessibilityEvent() { + return gfx::kNullAcceleratedWidget; +} + +void TestAXNodeWrapper::DoDefaultAction() { +} + +bool TestAXNodeWrapper::SetStringValue(const base::string16& new_value) { + return false; +} + +TestAXNodeWrapper::TestAXNodeWrapper(AXTree* tree, AXNode* node) + : tree_(tree), + node_(node), + platform_node_(AXPlatformNode::Create(this)) { +} + +} // namespace ui diff --git a/ui/accessibility/platform/test_ax_node_wrapper.h b/ui/accessibility/platform/test_ax_node_wrapper.h new file mode 100644 index 000000000000..918b4e3aca3c --- /dev/null +++ b/ui/accessibility/platform/test_ax_node_wrapper.h @@ -0,0 +1,53 @@ +// Copyright 2015 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 UI_ACCESSIBILITY_PLATFORM_TEST_AX_NODE_WRAPPER_H_ +#define UI_ACCESSIBILITY_PLATFORM_TEST_AX_NODE_WRAPPER_H_ + +#include "ui/accessibility/ax_node.h" +#include "ui/accessibility/ax_tree.h" +#include "ui/accessibility/platform/ax_platform_node.h" +#include "ui/accessibility/platform/ax_platform_node_delegate.h" + +namespace ui { + +// For testing, a TestAXNodeWrapper wraps an AXNode, implements +// AXPlatformNodeDelegate, and owns an AXPlatformNode. +class TestAXNodeWrapper : public AXPlatformNodeDelegate { + public: + // Create TestAXNodeWrapper instances on-demand from an AXTree and AXNode. + // Note that this sets the AXTreeDelegate, you can't use this class if + // you also want to implement AXTreeDelegate. + static TestAXNodeWrapper* GetOrCreate(AXTree* tree, AXNode* node); + + // Set a global coordinate offset for testing. + static void SetGlobalCoordinateOffset(const gfx::Vector2d& offset); + + virtual ~TestAXNodeWrapper(); + + AXPlatformNode* ax_platform_node() { return platform_node_; } + + // AXPlatformNodeDelegate. + const AXNodeData& GetData() override; + gfx::NativeViewAccessible GetParent() override; + int GetChildCount() override; + gfx::NativeViewAccessible ChildAtIndex(int index) override; + gfx::Vector2d GetGlobalCoordinateOffset() override; + gfx::NativeViewAccessible HitTestSync(int x, int y) override; + gfx::NativeViewAccessible GetFocus() override; + gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override; + void DoDefaultAction() override; + bool SetStringValue(const base::string16& new_value) override; + + private: + TestAXNodeWrapper(AXTree* tree, AXNode* node); + + AXTree* tree_; + AXNode* node_; + AXPlatformNode* platform_node_; +}; + +} // namespace ui + +#endif // UI_ACCESSIBILITY_PLATFORM_TEST_AX_NODE_WRAPPER_H_ diff --git a/ui/views/accessibility/native_view_accessibility.cc b/ui/views/accessibility/native_view_accessibility.cc index 6ca7676e6344..aeb2c7071a2b 100644 --- a/ui/views/accessibility/native_view_accessibility.cc +++ b/ui/views/accessibility/native_view_accessibility.cc @@ -4,7 +4,10 @@ #include "ui/views/accessibility/native_view_accessibility.h" +#include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_view_state.h" +#include "ui/events/event_utils.h" +#include "ui/views/controls/native/native_view_host.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" @@ -13,77 +16,236 @@ namespace views { #if !defined(OS_WIN) // static NativeViewAccessibility* NativeViewAccessibility::Create(View* view) { - DCHECK(view); - NativeViewAccessibility* instance = new NativeViewAccessibility(); - instance->set_view(view); - return instance; + return new NativeViewAccessibility(view); } #endif -NativeViewAccessibility::NativeViewAccessibility() - : view_(NULL), ax_node_(ui::AXPlatformNode::Create(this)) { +NativeViewAccessibility::NativeViewAccessibility(View* view) + : view_(view), + parent_widget_(nullptr), + ax_node_(ui::AXPlatformNode::Create(this)) { } NativeViewAccessibility::~NativeViewAccessibility() { if (ax_node_) ax_node_->Destroy(); + if (parent_widget_) + parent_widget_->RemoveObserver(this); } gfx::NativeViewAccessible NativeViewAccessibility::GetNativeObject() { - return ax_node_ ? ax_node_->GetNativeViewAccessible() : NULL; + return ax_node_ ? ax_node_->GetNativeViewAccessible() : nullptr; } void NativeViewAccessibility::Destroy() { delete this; } -#if !defined(OS_WIN) -// static -void NativeViewAccessibility::RegisterWebView(View* web_view) { -} - -// static -void NativeViewAccessibility::UnregisterWebView(View* web_view) { +void NativeViewAccessibility::NotifyAccessibilityEvent(ui::AXEvent event_type) { + if (ax_node_) + ax_node_->NotifyAccessibilityEvent(event_type); } -#endif // ui::AXPlatformNodeDelegate -ui::AXNodeData* NativeViewAccessibility::GetData() { +const ui::AXNodeData& NativeViewAccessibility::GetData() { ui::AXViewState state; view_->GetAccessibleState(&state); + data_ = ui::AXNodeData(); data_.role = state.role; + data_.state = state.state(); data_.location = view_->GetBoundsInScreen(); - return &data_; + data_.AddStringAttribute(ui::AX_ATTR_NAME, base::UTF16ToUTF8(state.name)); + data_.AddStringAttribute(ui::AX_ATTR_VALUE, base::UTF16ToUTF8(state.value)); + data_.AddStringAttribute(ui::AX_ATTR_ACTION, + base::UTF16ToUTF8(state.default_action)); + data_.AddStringAttribute(ui::AX_ATTR_SHORTCUT, + base::UTF16ToUTF8(state.keyboard_shortcut)); + data_.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, state.selection_start); + data_.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, state.selection_end); + + data_.state |= (1 << ui::AX_STATE_FOCUSABLE); + + if (!view_->enabled()) + data_.state |= (1 << ui::AX_STATE_DISABLED); + + if (!view_->visible()) + data_.state |= (1 << ui::AX_STATE_INVISIBLE); + + if (view_->HasFocus()) + data_.state |= (1 << ui::AX_STATE_FOCUSED); + + return data_; } int NativeViewAccessibility::GetChildCount() { - return view_->child_count(); + int child_count = view_->child_count(); + + std::vector child_widgets; + PopulateChildWidgetVector(&child_widgets); + child_count += child_widgets.size(); + + return child_count; } gfx::NativeViewAccessible NativeViewAccessibility::ChildAtIndex(int index) { - if (index < 0 || index >= view_->child_count()) - return NULL; - return view_->child_at(index)->GetNativeViewAccessible(); + // If this is a root view, our widget might have child widgets. Include + std::vector child_widgets; + PopulateChildWidgetVector(&child_widgets); + int child_widget_count = static_cast(child_widgets.size()); + + if (index < view_->child_count()) { + return view_->child_at(index)->GetNativeViewAccessible(); + } else if (index < view_->child_count() + child_widget_count) { + Widget* child_widget = child_widgets[index - view_->child_count()]; + return child_widget->GetRootView()->GetNativeViewAccessible(); + } + + return nullptr; } gfx::NativeViewAccessible NativeViewAccessibility::GetParent() { if (view_->parent()) return view_->parent()->GetNativeViewAccessible(); + // TODO: move this to NativeViewAccessibilityMac. #if defined(OS_MACOSX) if (view_->GetWidget()) return view_->GetWidget()->GetNativeView(); #endif - return NULL; + if (parent_widget_) + return parent_widget_->GetRootView()->GetNativeViewAccessible(); + + return nullptr; } gfx::Vector2d NativeViewAccessibility::GetGlobalCoordinateOffset() { return gfx::Vector2d(0, 0); // location is already in screen coordinates. } -void NativeViewAccessibility::NotifyAccessibilityEvent(ui::AXEvent event_type) { +gfx::NativeViewAccessible NativeViewAccessibility::HitTestSync(int x, int y) { + if (!view_ || !view_->GetWidget()) + return nullptr; + + // Search child widgets first, since they're on top in the z-order. + std::vector child_widgets; + PopulateChildWidgetVector(&child_widgets); + for (Widget* child_widget : child_widgets) { + View* child_root_view = child_widget->GetRootView(); + gfx::Point point(x, y); + View::ConvertPointFromScreen(child_root_view, &point); + if (child_root_view->HitTestPoint(point)) + return child_root_view->GetNativeViewAccessible(); + } + + gfx::Point point(x, y); + View::ConvertPointFromScreen(view_, &point); + if (!view_->HitTestPoint(point)) + return nullptr; + + // Check if the point is within any of the immediate children of this + // view. We don't have to search further because AXPlatformNode will + // do a recursive hit test if we return anything other than |this| or NULL. + for (int i = view_->child_count() - 1; i >= 0; --i) { + View* child_view = view_->child_at(i); + if (!child_view->visible()) + continue; + + gfx::Point point_in_child_coords(point); + view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords); + if (child_view->HitTestPoint(point_in_child_coords)) + return child_view->GetNativeViewAccessible(); + } + + // If it's not inside any of our children, it's inside this view. + return GetNativeObject(); +} + +gfx::NativeViewAccessible NativeViewAccessibility::GetFocus() { + FocusManager* focus_manager = view_->GetFocusManager(); + View* focused_view = + focus_manager ? focus_manager->GetFocusedView() : nullptr; + return focused_view ? focused_view->GetNativeViewAccessible() : nullptr; +} + +gfx::AcceleratedWidget +NativeViewAccessibility::GetTargetForNativeAccessibilityEvent() { + return gfx::kNullAcceleratedWidget; +} + +void NativeViewAccessibility::DoDefaultAction() { + gfx::Point center = view_->GetLocalBounds().CenterPoint(); + view_->OnMousePressed(ui::MouseEvent(ui::ET_MOUSE_PRESSED, + center, + center, + ui::EventTimeForNow(), + ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON)); + view_->OnMouseReleased(ui::MouseEvent(ui::ET_MOUSE_RELEASED, + center, + center, + ui::EventTimeForNow(), + ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON)); +} + +bool NativeViewAccessibility::SetStringValue(const base::string16& new_value) { + // Return an error if the view can't set the value. + ui::AXViewState state; + view_->GetAccessibleState(&state); + if (state.set_value_callback.is_null()) + return false; + + state.set_value_callback.Run(new_value); + return true; +} + +void NativeViewAccessibility::OnWidgetDestroying(Widget* widget) { + if (parent_widget_ == widget) + parent_widget_ = nullptr; +} + +void NativeViewAccessibility::SetParentWidget(Widget* parent_widget) { + if (parent_widget_) + parent_widget_->RemoveObserver(this); + parent_widget_ = parent_widget; + parent_widget_->AddObserver(this); +} + +void NativeViewAccessibility::PopulateChildWidgetVector( + std::vector* result_child_widgets) { + // Only attach child widgets to the root view. + Widget* widget = view_->GetWidget(); + if (!widget || widget->GetRootView() != view_) + return; + + std::set child_widgets; + Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets); + for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) { + Widget* child_widget = *iter; + DCHECK_NE(widget, child_widget); + + if (!child_widget->IsVisible()) + continue; + + if (widget->GetNativeWindowProperty(kWidgetNativeViewHostKey)) + continue; + + gfx::NativeViewAccessible child_widget_accessible = + child_widget->GetRootView()->GetNativeViewAccessible(); + ui::AXPlatformNode* child_widget_platform_node = + ui::AXPlatformNode::FromNativeViewAccessible(child_widget_accessible); + if (child_widget_platform_node) { + NativeViewAccessibility* child_widget_view_accessibility = + static_cast( + child_widget_platform_node->GetDelegate()); + if (child_widget_view_accessibility->parent_widget() != widget) + child_widget_view_accessibility->SetParentWidget(widget); + } + + result_child_widgets->push_back(child_widget); + } } } // namespace views diff --git a/ui/views/accessibility/native_view_accessibility.h b/ui/views/accessibility/native_view_accessibility.h index 9a94d3a14d1e..5c5c135c9b62 100644 --- a/ui/views/accessibility/native_view_accessibility.h +++ b/ui/views/accessibility/native_view_accessibility.h @@ -10,45 +10,60 @@ #include "ui/accessibility/platform/ax_platform_node_delegate.h" #include "ui/gfx/native_widget_types.h" #include "ui/views/views_export.h" +#include "ui/views/widget/widget_observer.h" namespace views { class View; +class Widget; -class VIEWS_EXPORT NativeViewAccessibility : public ui::AXPlatformNodeDelegate { +class VIEWS_EXPORT NativeViewAccessibility + : public ui::AXPlatformNodeDelegate, + public WidgetObserver { public: static NativeViewAccessibility* Create(View* view); - virtual gfx::NativeViewAccessible GetNativeObject(); + gfx::NativeViewAccessible GetNativeObject(); // Call Destroy rather than deleting this, because the subclass may // use reference counting. virtual void Destroy(); - // WebViews need to be registered because they implement their own - // tree of accessibility objects, and we need to check them when - // mapping a child id to a NativeViewAccessible. - static void RegisterWebView(View* web_view); - static void UnregisterWebView(View* web_view); + void NotifyAccessibilityEvent(ui::AXEvent event_type); // ui::AXPlatformNodeDelegate - ui::AXNodeData* GetData() override; + const ui::AXNodeData& GetData() override; int GetChildCount() override; gfx::NativeViewAccessible ChildAtIndex(int index) override; gfx::NativeViewAccessible GetParent() override; gfx::Vector2d GetGlobalCoordinateOffset() override; - void NotifyAccessibilityEvent(ui::AXEvent event_type) override; + gfx::NativeViewAccessible HitTestSync(int x, int y) override; + gfx::NativeViewAccessible GetFocus() override; + gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override; + void DoDefaultAction() override; + bool SetStringValue(const base::string16& new_value) override; + + // WidgetObserver + void OnWidgetDestroying(Widget* widget) override; + + Widget* parent_widget() const { return parent_widget_; } + void SetParentWidget(Widget* parent_widget); protected: - NativeViewAccessibility(); - virtual ~NativeViewAccessibility(); + NativeViewAccessibility(View* view); + ~NativeViewAccessibility() override; - void set_view(views::View* view) { view_ = view; } - const View* view() const { return view_; } + // Weak. Owns this. + View* view_; - View* view_; // Weak. Owns this. + // Weak. Uses WidgetObserver to clear. This is set on the root view for + // a widget that's owned by another widget, so we can walk back up the + // tree. + Widget* parent_widget_; private: + void PopulateChildWidgetVector(std::vector* result_child_widgets); + // We own this, but it is reference-counted on some platforms so we can't use // a scoped_ptr. It is dereferenced in the destructor. ui::AXPlatformNode* ax_node_; diff --git a/ui/views/accessibility/native_view_accessibility_unittest.cc b/ui/views/accessibility/native_view_accessibility_unittest.cc index bdd449518473..7fff8436f997 100644 --- a/ui/views/accessibility/native_view_accessibility_unittest.cc +++ b/ui/views/accessibility/native_view_accessibility_unittest.cc @@ -55,12 +55,12 @@ class NativeViewAccessibilityTest : public ViewsTestBase { }; TEST_F(NativeViewAccessibilityTest, RoleShouldMatch) { - EXPECT_EQ(ui::AX_ROLE_BUTTON, button_accessibility_->GetData()->role); - EXPECT_EQ(ui::AX_ROLE_STATIC_TEXT, label_accessibility_->GetData()->role); + EXPECT_EQ(ui::AX_ROLE_BUTTON, button_accessibility_->GetData().role); + EXPECT_EQ(ui::AX_ROLE_STATIC_TEXT, label_accessibility_->GetData().role); } TEST_F(NativeViewAccessibilityTest, BoundsShouldMatch) { - gfx::Rect bounds = button_accessibility_->GetData()->location; + gfx::Rect bounds = button_accessibility_->GetData().location; bounds.Offset(button_accessibility_->GetGlobalCoordinateOffset()); EXPECT_EQ(button_->GetBoundsInScreen(), bounds); } diff --git a/ui/views/accessibility/native_view_accessibility_win.cc b/ui/views/accessibility/native_view_accessibility_win.cc dissimilarity index 97% index 1255f1006853..fd9edaa3e29d 100644 --- a/ui/views/accessibility/native_view_accessibility_win.cc +++ b/ui/views/accessibility/native_view_accessibility_win.cc @@ -1,1558 +1,65 @@ -// 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 "ui/views/accessibility/native_view_accessibility_win.h" - -#include -#include - -#include -#include - -#include "base/memory/singleton.h" -#include "base/strings/utf_string_conversions.h" -#include "base/win/scoped_comptr.h" -#include "base/win/windows_version.h" -#include "third_party/iaccessible2/ia2_api_all.h" -#include "ui/accessibility/ax_enums.h" -#include "ui/accessibility/ax_text_utils.h" -#include "ui/accessibility/ax_view_state.h" -#include "ui/base/win/accessibility_ids_win.h" -#include "ui/base/win/accessibility_misc_utils.h" -#include "ui/base/win/atl_module.h" -#include "ui/views/controls/button/custom_button.h" -#include "ui/views/focus/focus_manager.h" -#include "ui/views/focus/view_storage.h" -#include "ui/views/widget/widget.h" -#include "ui/views/win/hwnd_util.h" - -namespace views { -namespace { - -class AccessibleWebViewRegistry { - public: - static AccessibleWebViewRegistry* GetInstance(); - - void RegisterWebView(View* web_view); - - void UnregisterWebView(View* web_view); - - // Given the view that received the request for the accessible - // id in |top_view|, and the child id requested, return the native - // accessible object with that child id from one of the WebViews in - // |top_view|'s view hierarchy, if any. - IAccessible* GetAccessibleFromWebView(View* top_view, long child_id); - - // The system uses IAccessible APIs for many purposes, but only - // assistive technology like screen readers uses IAccessible2. - // Call this method to note that the IAccessible2 interface was queried and - // that WebViews should be proactively notified that this interface will be - // used. If this is enabled for the first time, this will explicitly call - // QueryService with an argument of IAccessible2 on all WebViews, otherwise - // it will just do it from now on. - void EnableIAccessible2Support(); - - private: - friend struct DefaultSingletonTraits; - AccessibleWebViewRegistry(); - ~AccessibleWebViewRegistry() {} - - IAccessible* AccessibleObjectFromChildId(View* web_view, long child_id); - - void QueryIAccessible2Interface(View* web_view); - - // Set of all web views. We check whether each one is contained in a - // top view dynamically rather than keeping track of a map. - std::set web_views_; - - // The most recent top view used in a call to GetAccessibleFromWebView. - View* last_top_view_; - - // The most recent web view where an accessible object was found, - // corresponding to |last_top_view_|. - View* last_web_view_; - - // If IAccessible2 support is enabled, we query the IAccessible2 interface - // of WebViews proactively when they're registered, so that they are - // aware that they need to support this interface. - bool iaccessible2_support_enabled_; - - DISALLOW_COPY_AND_ASSIGN(AccessibleWebViewRegistry); -}; - -AccessibleWebViewRegistry::AccessibleWebViewRegistry() - : last_top_view_(NULL), - last_web_view_(NULL), - iaccessible2_support_enabled_(false) { -} - -AccessibleWebViewRegistry* AccessibleWebViewRegistry::GetInstance() { - return Singleton::get(); -} - -void AccessibleWebViewRegistry::RegisterWebView(View* web_view) { - DCHECK(web_views_.find(web_view) == web_views_.end()); - web_views_.insert(web_view); - - if (iaccessible2_support_enabled_) - QueryIAccessible2Interface(web_view); -} - -void AccessibleWebViewRegistry::UnregisterWebView(View* web_view) { - DCHECK(web_views_.find(web_view) != web_views_.end()); - web_views_.erase(web_view); - if (last_web_view_ == web_view) { - last_top_view_ = NULL; - last_web_view_ = NULL; - } -} - -IAccessible* AccessibleWebViewRegistry::GetAccessibleFromWebView( - View* top_view, long child_id) { - // This function gets called frequently, so try to avoid searching all - // of the web views if the notification is on the same web view that - // sent the last one. - if (last_top_view_ == top_view) { - IAccessible* accessible = - AccessibleObjectFromChildId(last_web_view_, child_id); - if (accessible) - return accessible; - } - - // Search all web views. For each one, first ensure it's a descendant - // of this view where the event was posted - and if so, see if it owns - // an accessible object with that child id. If so, save the view to speed - // up the next notification. - for (std::set::iterator iter = web_views_.begin(); - iter != web_views_.end(); ++iter) { - View* web_view = *iter; - if (top_view == web_view || !top_view->Contains(web_view)) - continue; - IAccessible* accessible = AccessibleObjectFromChildId(web_view, child_id); - if (accessible) { - last_top_view_ = top_view; - last_web_view_ = web_view; - return accessible; - } - } - - return NULL; -} - -void AccessibleWebViewRegistry::EnableIAccessible2Support() { - if (iaccessible2_support_enabled_) - return; - iaccessible2_support_enabled_ = true; - for (std::set::iterator iter = web_views_.begin(); - iter != web_views_.end(); ++iter) { - QueryIAccessible2Interface(*iter); - } -} - -IAccessible* AccessibleWebViewRegistry::AccessibleObjectFromChildId( - View* web_view, - long child_id) { - IAccessible* web_view_accessible = web_view->GetNativeViewAccessible(); - if (web_view_accessible == NULL) - return NULL; - - VARIANT var_child; - var_child.vt = VT_I4; - var_child.lVal = child_id; - IAccessible* result = NULL; - if (S_OK == web_view_accessible->get_accChild( - var_child, reinterpret_cast(&result))) { - return result; - } - - return NULL; -} - -void AccessibleWebViewRegistry::QueryIAccessible2Interface(View* web_view) { - IAccessible* web_view_accessible = web_view->GetNativeViewAccessible(); - if (!web_view_accessible) - return; - - base::win::ScopedComPtr service_provider; - if (S_OK != web_view_accessible->QueryInterface(service_provider.Receive())) - return; - base::win::ScopedComPtr iaccessible2; - service_provider->QueryService( - IID_IAccessible, IID_IAccessible2, - reinterpret_cast(iaccessible2.Receive())); -} - -} // anonymous namespace - -// static -long NativeViewAccessibilityWin::next_unique_id_ = 1; -int NativeViewAccessibilityWin::view_storage_ids_[kMaxViewStorageIds] = {0}; -int NativeViewAccessibilityWin::next_view_storage_id_index_ = 0; -std::vector NativeViewAccessibilityWin::alert_target_view_storage_ids_; - -// static -NativeViewAccessibility* NativeViewAccessibility::Create(View* view) { - // Make sure ATL is initialized in this module. - ui::win::CreateATLModuleIfNeeded(); - - CComObject* instance = NULL; - HRESULT hr = CComObject::CreateInstance( - &instance); - DCHECK(SUCCEEDED(hr)); - instance->set_view(view); - instance->AddRef(); - return instance; -} - -NativeViewAccessibilityWin::NativeViewAccessibilityWin() - : unique_id_(next_unique_id_++) { -} - -NativeViewAccessibilityWin::~NativeViewAccessibilityWin() { - RemoveAlertTarget(); -} - -void NativeViewAccessibilityWin::NotifyAccessibilityEvent( - ui::AXEvent event_type) { - if (!view_) - return; - - ViewStorage* view_storage = ViewStorage::GetInstance(); - HWND hwnd = HWNDForView(view_); - int view_storage_id = view_storage_ids_[next_view_storage_id_index_]; - if (view_storage_id == 0) { - view_storage_id = view_storage->CreateStorageID(); - view_storage_ids_[next_view_storage_id_index_] = view_storage_id; - } else { - view_storage->RemoveView(view_storage_id); - } - view_storage->StoreView(view_storage_id, view_); - - // Positive child ids are used for enumerating direct children, - // negative child ids can be used as unique ids to refer to a specific - // descendants. Make index into view_storage_ids_ into a negative child id. - int child_id = - base::win::kFirstViewsAccessibilityId - next_view_storage_id_index_; - ::NotifyWinEvent(MSAAEvent(event_type), hwnd, OBJID_CLIENT, child_id); - next_view_storage_id_index_ = - (next_view_storage_id_index_ + 1) % kMaxViewStorageIds; - - // Keep track of views that are a target of an alert event. - if (event_type == ui::AX_EVENT_ALERT) - AddAlertTarget(); -} - -gfx::NativeViewAccessible NativeViewAccessibilityWin::GetNativeObject() { - return this; -} - -void NativeViewAccessibilityWin::Destroy() { - view_ = NULL; - Release(); -} - -STDMETHODIMP NativeViewAccessibilityWin::accHitTest( - LONG x_left, LONG y_top, VARIANT* child) { - if (!child) - return E_INVALIDARG; - - if (!view_ || !view_->GetWidget()) - return E_FAIL; - - // If this is a root view, our widget might have child widgets. - // Search child widgets first, since they're on top in the z-order. - if (view_->GetWidget()->GetRootView() == view_) { - std::vector child_widgets; - PopulateChildWidgetVector(&child_widgets); - for (size_t i = 0; i < child_widgets.size(); ++i) { - Widget* child_widget = child_widgets[i]; - IAccessible* child_accessible = - child_widget->GetRootView()->GetNativeViewAccessible(); - HRESULT result = child_accessible->accHitTest(x_left, y_top, child); - if (result == S_OK) - return result; - } - } - - gfx::Point point(x_left, y_top); - View::ConvertPointFromScreen(view_, &point); - - // If the point is not inside this view, return false. - if (!view_->HitTestPoint(point)) { - child->vt = VT_EMPTY; - return S_FALSE; - } - - // Check if the point is within any of the immediate children of this - // view. - View* hit_child_view = NULL; - for (int i = view_->child_count() - 1; i >= 0; --i) { - View* child_view = view_->child_at(i); - if (!child_view->visible()) - continue; - - gfx::Point point_in_child_coords(point); - view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords); - if (child_view->HitTestPoint(point_in_child_coords)) { - hit_child_view = child_view; - break; - } - } - - // If the point was within one of this view's immediate children, - // call accHitTest recursively on that child's native view accessible - - // which may be a recursive call to this function or it may be overridden, - // for example in the case of a WebView. - if (hit_child_view) { - HRESULT result = hit_child_view->GetNativeViewAccessible()->accHitTest( - x_left, y_top, child); - - // If the recursive call returned CHILDID_SELF, we have to convert that - // into a VT_DISPATCH for the return value to this call. - if (S_OK == result && child->vt == VT_I4 && child->lVal == CHILDID_SELF) { - child->vt = VT_DISPATCH; - child->pdispVal = hit_child_view->GetNativeViewAccessible(); - // Always increment ref when returning a reference to a COM object. - child->pdispVal->AddRef(); - } - return result; - } - - // This object is the best match, so return CHILDID_SELF. It's tempting to - // simplify the logic and use VT_DISPATCH everywhere, but the Windows - // call AccessibleObjectFromPoint will keep calling accHitTest until some - // object returns CHILDID_SELF. - child->vt = VT_I4; - child->lVal = CHILDID_SELF; - return S_OK; -} - -HRESULT NativeViewAccessibilityWin::accDoDefaultAction(VARIANT var_id) { - if (!IsValidId(var_id)) - return E_INVALIDARG; - - // The object does not support the method. This value is returned for - // controls that do not perform actions, such as edit fields. - return DISP_E_MEMBERNOTFOUND; -} - -STDMETHODIMP NativeViewAccessibilityWin::accLocation( - LONG* x_left, LONG* y_top, LONG* width, LONG* height, VARIANT var_id) { - if (!IsValidId(var_id) || !x_left || !y_top || !width || !height) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - if (!view_->bounds().IsEmpty()) { - *width = view_->width(); - *height = view_->height(); - gfx::Point topleft(view_->bounds().origin()); - View::ConvertPointToScreen( - view_->parent() ? view_->parent() : view_, &topleft); - *x_left = topleft.x(); - *y_top = topleft.y(); - } else { - return E_FAIL; - } - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::accNavigate( - LONG nav_dir, VARIANT start, VARIANT* end) { - if (start.vt != VT_I4 || !end) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - switch (nav_dir) { - case NAVDIR_FIRSTCHILD: - case NAVDIR_LASTCHILD: { - if (start.lVal != CHILDID_SELF) { - // Start of navigation must be on the View itself. - return E_INVALIDARG; - } else if (!view_->has_children()) { - // No children found. - return S_FALSE; - } - - // Set child_id based on first or last child. - int child_id = 0; - if (nav_dir == NAVDIR_LASTCHILD) - child_id = view_->child_count() - 1; - - View* child = view_->child_at(child_id); - end->vt = VT_DISPATCH; - end->pdispVal = child->GetNativeViewAccessible(); - end->pdispVal->AddRef(); - return S_OK; - } - case NAVDIR_LEFT: - case NAVDIR_UP: - case NAVDIR_PREVIOUS: - case NAVDIR_RIGHT: - case NAVDIR_DOWN: - case NAVDIR_NEXT: { - // Retrieve parent to access view index and perform bounds checking. - View* parent = view_->parent(); - if (!parent) { - return E_FAIL; - } - - if (start.lVal == CHILDID_SELF) { - int view_index = parent->GetIndexOf(view_); - // Check navigation bounds, adjusting for View child indexing (MSAA - // child indexing starts with 1, whereas View indexing starts with 0). - if (!IsValidNav(nav_dir, view_index, -1, - parent->child_count() - 1)) { - // Navigation attempted to go out-of-bounds. - end->vt = VT_EMPTY; - return S_FALSE; - } else { - if (IsNavDirNext(nav_dir)) { - view_index += 1; - } else { - view_index -=1; - } - } - - View* child = parent->child_at(view_index); - end->pdispVal = child->GetNativeViewAccessible(); - end->vt = VT_DISPATCH; - end->pdispVal->AddRef(); - return S_OK; - } else { - // Check navigation bounds, adjusting for MSAA child indexing (MSAA - // child indexing starts with 1, whereas View indexing starts with 0). - if (!IsValidNav(nav_dir, start.lVal, 0, parent->child_count() + 1)) { - // Navigation attempted to go out-of-bounds. - end->vt = VT_EMPTY; - return S_FALSE; - } else { - if (IsNavDirNext(nav_dir)) { - start.lVal += 1; - } else { - start.lVal -= 1; - } - } - - HRESULT result = this->get_accChild(start, &end->pdispVal); - if (result == S_FALSE) { - // Child is a leaf. - end->vt = VT_I4; - end->lVal = start.lVal; - } else if (result == E_INVALIDARG) { - return E_INVALIDARG; - } else { - // Child is not a leaf. - end->vt = VT_DISPATCH; - } - } - break; - } - default: - return E_INVALIDARG; - } - // Navigation performed correctly. Global return for this function, if no - // error triggered an escape earlier. - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accChild(VARIANT var_child, - IDispatch** disp_child) { - if (var_child.vt != VT_I4 || !disp_child) - return E_INVALIDARG; - - if (!view_ || !view_->GetWidget()) - return E_FAIL; - - LONG child_id = V_I4(&var_child); - - if (child_id == CHILDID_SELF) { - // Remain with the same dispatch. - return S_OK; - } - - // If this is a root view, our widget might have child widgets. Include - std::vector child_widgets; - if (view_->GetWidget()->GetRootView() == view_) - PopulateChildWidgetVector(&child_widgets); - int child_widget_count = static_cast(child_widgets.size()); - - View* child_view = NULL; - if (child_id > 0) { - // Positive child ids are a 1-based child index, used by clients - // that want to enumerate all immediate children. - int child_id_as_index = child_id - 1; - if (child_id_as_index < view_->child_count()) { - child_view = view_->child_at(child_id_as_index); - } else if (child_id_as_index < view_->child_count() + child_widget_count) { - Widget* child_widget = - child_widgets[child_id_as_index - view_->child_count()]; - child_view = child_widget->GetRootView(); - } - } else { - // Negative child ids can be used to map to any descendant. - // Check child widget first. - for (int i = 0; i < child_widget_count; i++) { - Widget* child_widget = child_widgets[i]; - IAccessible* child_accessible = - child_widget->GetRootView()->GetNativeViewAccessible(); - HRESULT result = child_accessible->get_accChild(var_child, disp_child); - if (result == S_OK) - return result; - } - - // We map child ids to a view storage id that can refer to a - // specific view (if that view still exists). - int view_storage_id_index = - base::win::kFirstViewsAccessibilityId - child_id; - if (view_storage_id_index >= 0 && - view_storage_id_index < kMaxViewStorageIds) { - int view_storage_id = view_storage_ids_[view_storage_id_index]; - ViewStorage* view_storage = ViewStorage::GetInstance(); - child_view = view_storage->RetrieveView(view_storage_id); - } else { - *disp_child = AccessibleWebViewRegistry::GetInstance()-> - GetAccessibleFromWebView(view_, child_id); - if (*disp_child) - return S_OK; - } - } - - if (!child_view) { - // No child found. - *disp_child = NULL; - return E_FAIL; - } - - *disp_child = child_view->GetNativeViewAccessible(); - if (*disp_child) { - (*disp_child)->AddRef(); - return S_OK; - } - - return E_FAIL; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accChildCount(LONG* child_count) { - if (!child_count) - return E_INVALIDARG; - - if (!view_ || !view_->GetWidget()) - return E_FAIL; - - *child_count = view_->child_count(); - - // If this is a root view, our widget might have child widgets. Include - // them, too. - if (view_->GetWidget()->GetRootView() == view_) { - std::vector child_widgets; - PopulateChildWidgetVector(&child_widgets); - *child_count += child_widgets.size(); - } - - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accDefaultAction( - VARIANT var_id, BSTR* def_action) { - if (!IsValidId(var_id) || !def_action) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - base::string16 temp_action = state.default_action; - - if (!temp_action.empty()) { - *def_action = SysAllocString(temp_action.c_str()); - } else { - return S_FALSE; - } - - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accDescription( - VARIANT var_id, BSTR* desc) { - if (!IsValidId(var_id) || !desc) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - base::string16 temp_desc; - - view_->GetTooltipText(gfx::Point(), &temp_desc); - if (!temp_desc.empty()) { - *desc = SysAllocString(temp_desc.c_str()); - } else { - return S_FALSE; - } - - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accFocus(VARIANT* focus_child) { - if (!focus_child) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - FocusManager* focus_manager = view_->GetFocusManager(); - View* focus = focus_manager ? focus_manager->GetFocusedView() : NULL; - if (focus == view_) { - // This view has focus. - focus_child->vt = VT_I4; - focus_child->lVal = CHILDID_SELF; - } else if (focus && view_->Contains(focus)) { - // Return the child object that has the keyboard focus. - focus_child->vt = VT_DISPATCH; - focus_child->pdispVal = focus->GetNativeViewAccessible(); - focus_child->pdispVal->AddRef(); - return S_OK; - } else { - // Neither this object nor any of its children has the keyboard focus. - focus_child->vt = VT_EMPTY; - } - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accKeyboardShortcut( - VARIANT var_id, BSTR* acc_key) { - if (!IsValidId(var_id) || !acc_key) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - base::string16 temp_key = state.keyboard_shortcut; - - if (!temp_key.empty()) { - *acc_key = SysAllocString(temp_key.c_str()); - } else { - return S_FALSE; - } - - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accName( - VARIANT var_id, BSTR* name) { - if (!IsValidId(var_id) || !name) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - // Retrieve the current view's name. - ui::AXViewState state; - view_->GetAccessibleState(&state); - base::string16 temp_name = state.name; - if (!temp_name.empty()) { - // Return name retrieved. - *name = SysAllocString(temp_name.c_str()); - } else { - // If view has no name, return S_FALSE. - return S_FALSE; - } - - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accParent( - IDispatch** disp_parent) { - if (!disp_parent) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - *disp_parent = NULL; - View* parent_view = view_->parent(); - - if (!parent_view) { - HWND hwnd = HWNDForView(view_); - if (!hwnd) - return S_FALSE; - - return ::AccessibleObjectFromWindow( - hwnd, OBJID_WINDOW, IID_IAccessible, - reinterpret_cast(disp_parent)); - } - - *disp_parent = parent_view->GetNativeViewAccessible(); - (*disp_parent)->AddRef(); - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accRole( - VARIANT var_id, VARIANT* role) { - if (!IsValidId(var_id) || !role) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - role->vt = VT_I4; - role->lVal = MSAARole(state.role); - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accState( - VARIANT var_id, VARIANT* state) { - // This returns MSAA states. See also the IAccessible2 interface - // get_states(). - - if (!IsValidId(var_id) || !state) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - state->vt = VT_I4; - - // Retrieve all currently applicable states of the parent. - SetState(state, view_); - - // Make sure that state is not empty, and has the proper type. - if (state->vt == VT_EMPTY) - return E_FAIL; - - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accValue(VARIANT var_id, - BSTR* value) { - if (!IsValidId(var_id) || !value) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - // Retrieve the current view's value. - ui::AXViewState state; - view_->GetAccessibleState(&state); - base::string16 temp_value = state.value; - - if (!temp_value.empty()) { - // Return value retrieved. - *value = SysAllocString(temp_value.c_str()); - } else { - // If view has no value, fall back into the default implementation. - *value = NULL; - return E_NOTIMPL; - } - - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::put_accValue(VARIANT var_id, - BSTR new_value) { - if (!IsValidId(var_id) || !new_value) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - // Return an error if the view can't set the value. - ui::AXViewState state; - view_->GetAccessibleState(&state); - if (state.set_value_callback.is_null()) - return E_FAIL; - - state.set_value_callback.Run(new_value); - return S_OK; -} - -// IAccessible functions not supported. - -STDMETHODIMP NativeViewAccessibilityWin::get_accSelection(VARIANT* selected) { - if (selected) - selected->vt = VT_EMPTY; - return E_NOTIMPL; -} - -STDMETHODIMP NativeViewAccessibilityWin::accSelect( - LONG flagsSelect, VARIANT var_id) { - return E_NOTIMPL; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accHelp( - VARIANT var_id, BSTR* help) { - if (!IsValidId(var_id) || !help) - return E_INVALIDARG; - - if (!view_) - return E_FAIL; - - base::string16 temp = base::UTF8ToUTF16(view_->GetClassName()); - *help = SysAllocString(temp.c_str()); - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_accHelpTopic( - BSTR* help_file, VARIANT var_id, LONG* topic_id) { - if (help_file) { - *help_file = NULL; - } - if (topic_id) { - *topic_id = static_cast(-1); - } - return E_NOTIMPL; -} - -STDMETHODIMP NativeViewAccessibilityWin::put_accName( - VARIANT var_id, BSTR put_name) { - // Deprecated. - return E_NOTIMPL; -} - -// -// IAccessible2 -// - -STDMETHODIMP NativeViewAccessibilityWin::role(LONG* role) { - if (!view_) - return E_FAIL; - - if (!role) - return E_INVALIDARG; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - *role = MSAARole(state.role); - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_states(AccessibleStates* states) { - // This returns IAccessible2 states, which supplement MSAA states. - // See also the MSAA interface get_accState. - - if (!view_) - return E_FAIL; - - if (!states) - return E_INVALIDARG; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - - // There are only a couple of states we need to support - // in IAccessible2. If any more are added, we may want to - // add a helper function like MSAAState. - *states = IA2_STATE_OPAQUE; - if (state.HasStateFlag(ui::AX_STATE_EDITABLE)) - *states |= IA2_STATE_EDITABLE; - - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_uniqueID(LONG* unique_id) { - if (!view_) - return E_FAIL; - - if (!unique_id) - return E_INVALIDARG; - - *unique_id = unique_id_; - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_windowHandle(HWND* window_handle) { - if (!view_) - return E_FAIL; - - if (!window_handle) - return E_INVALIDARG; - - *window_handle = HWNDForView(view_); - return *window_handle ? S_OK : S_FALSE; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_relationTargetsOfType( - BSTR type_bstr, - long max_targets, - IUnknown ***targets, - long *n_targets) { - if (!view_) - return E_FAIL; - - if (!targets || !n_targets) - return E_INVALIDARG; - - *n_targets = 0; - *targets = NULL; - - // Only respond to requests for relations of type "alerts" on the - // root view. - base::string16 type(type_bstr); - if (type != L"alerts" || view_->parent()) - return S_FALSE; - - // Collect all of the alert views that are still valid. - std::vector alert_views; - ViewStorage* view_storage = ViewStorage::GetInstance(); - for (size_t i = 0; i < alert_target_view_storage_ids_.size(); ++i) { - int view_storage_id = alert_target_view_storage_ids_[i]; - View* view = view_storage->RetrieveView(view_storage_id); - if (!view || !view_->Contains(view)) - continue; - alert_views.push_back(view); - } - - long count = alert_views.size(); - if (count == 0) - return S_FALSE; - - // Don't return more targets than max_targets - but note that the caller - // is allowed to specify max_targets=0 to mean no limit. - if (max_targets > 0 && count > max_targets) - count = max_targets; - - // Return the number of targets. - *n_targets = count; - - // Allocate COM memory for the result array and populate it. - *targets = static_cast( - CoTaskMemAlloc(count * sizeof(IUnknown*))); - for (long i = 0; i < count; ++i) { - (*targets)[i] = alert_views[i]->GetNativeViewAccessible(); - (*targets)[i]->AddRef(); - } - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_attributes(BSTR* attributes) { - if (!view_) - return E_FAIL; - - if (!attributes) - return E_INVALIDARG; - - base::string16 attributes_str; - - // Text fields need to report the attribute "text-model:a1" to instruct - // screen readers to use IAccessible2 APIs to handle text editing in this - // object (as opposed to treating it like a native Windows text box). - // The text-model:a1 attribute is documented here: - // http://www.linuxfoundation.org/collaborate/workgroups/accessibility/ia2/ia2_implementation_guide - ui::AXViewState state; - view_->GetAccessibleState(&state); - if (state.role == ui::AX_ROLE_TEXT_FIELD) { - attributes_str = L"text-model:a1;"; - } - - *attributes = SysAllocString(attributes_str.c_str()); - DCHECK(*attributes); - return S_OK; -} - -// -// IAccessibleText -// - -STDMETHODIMP NativeViewAccessibilityWin::get_nCharacters(LONG* n_characters) { - if (!view_) - return E_FAIL; - - if (!n_characters) - return E_INVALIDARG; - - base::string16 text = TextForIAccessibleText(); - *n_characters = static_cast(text.size()); - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_caretOffset(LONG* offset) { - if (!view_) - return E_FAIL; - - if (!offset) - return E_INVALIDARG; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - *offset = static_cast(state.selection_end); - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_nSelections(LONG* n_selections) { - if (!view_) - return E_FAIL; - - if (!n_selections) - return E_INVALIDARG; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - if (state.selection_start != state.selection_end) - *n_selections = 1; - else - *n_selections = 0; - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_selection(LONG selection_index, - LONG* start_offset, - LONG* end_offset) { - if (!view_) - return E_FAIL; - - if (!start_offset || !end_offset || selection_index != 0) - return E_INVALIDARG; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - *start_offset = static_cast(state.selection_start); - *end_offset = static_cast(state.selection_end); - return S_OK; -} - -STDMETHODIMP NativeViewAccessibilityWin::get_text(LONG start_offset, - LONG end_offset, - BSTR* text) { - if (!view_) - return E_FAIL; - - ui::AXViewState state; - view_->GetAccessibleState(&state); - base::string16 text_str = TextForIAccessibleText(); - LONG len = static_cast(text_str.size()); - - if (start_offset == IA2_TEXT_OFFSET_LENGTH) { - start_offset = len; - } else if (start_offset == IA2_TEXT_OFFSET_CARET) { - start_offset = static_cast(state.selection_end); - } - if (end_offset == IA2_TEXT_OFFSET_LENGTH) { - end_offset = static_cast(text_str.size()); - } else if (end_offset == IA2_TEXT_OFFSET_CARET) { - end_offset = static_cast(state.selection_end); - } - - // 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. - if (start_offset < 0) - return E_INVALIDARG; - if (end_offset > len) - return E_INVALIDARG; - - base::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 NativeViewAccessibilityWin::get_textAtOffset( - LONG offset, - enum IA2TextBoundaryType boundary_type, - LONG* start_offset, LONG* end_offset, - BSTR* text) { - 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 base::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 NativeViewAccessibilityWin::get_textBeforeOffset( - LONG offset, - enum IA2TextBoundaryType boundary_type, - LONG* start_offset, LONG* end_offset, - BSTR* text) { - 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 base::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 NativeViewAccessibilityWin::get_textAfterOffset( - LONG offset, - enum IA2TextBoundaryType boundary_type, - LONG* start_offset, LONG* end_offset, - BSTR* text) { - 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 base::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 NativeViewAccessibilityWin::get_offsetAtPoint( - LONG x, LONG y, enum IA2CoordinateType coord_type, LONG* offset) { - if (!view_) - return E_FAIL; - - if (!offset) - return E_INVALIDARG; - - // We don't support this method, but we have to return something - // rather than E_NOTIMPL or screen readers will complain. - *offset = 0; - return S_OK; -} - -// -// IServiceProvider methods. -// - -STDMETHODIMP NativeViewAccessibilityWin::QueryService( - REFGUID guidService, REFIID riid, void** object) { - if (!view_) - return E_FAIL; - - if (riid == IID_IAccessible2 || riid == IID_IAccessible2_2) - AccessibleWebViewRegistry::GetInstance()->EnableIAccessible2Support(); - - if (guidService == IID_IAccessible || - guidService == IID_IAccessible2 || - guidService == IID_IAccessible2_2 || - guidService == IID_IAccessibleText) { - 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 region in the window. - // 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 NativeViewAccessibilityWin::GetPatternProvider( - PATTERNID id, IUnknown** provider) { - if (!view_) - return E_FAIL; - - if (!provider) - return E_INVALIDARG; - - DVLOG(1) << "In Function: " - << __FUNCTION__ - << " for pattern id: " - << id; - if (id == UIA_ValuePatternId || id == UIA_TextPatternId) { - ui::AXViewState state; - view_->GetAccessibleState(&state); - long role = MSAARole(state.role); - - if (role == ROLE_SYSTEM_TEXT) { - DVLOG(1) << "Returning UIA text provider"; - base::win::UIATextProvider::CreateTextProvider( - state.value, true, provider); - return S_OK; - } - } - return E_NOTIMPL; -} - -STDMETHODIMP NativeViewAccessibilityWin::GetPropertyValue(PROPERTYID id, - VARIANT* ret) { - if (!view_) - return E_FAIL; - - if (!ret) - return E_INVALIDARG; - - DVLOG(1) << "In Function: " - << __FUNCTION__ - << " for property id: " - << id; - if (id == UIA_ControlTypePropertyId) { - ui::AXViewState state; - view_->GetAccessibleState(&state); - long role = MSAARole(state.role); - if (role == ROLE_SYSTEM_TEXT) { - V_VT(ret) = VT_I4; - ret->lVal = UIA_EditControlTypeId; - DVLOG(1) << "Returning Edit control type"; - } else { - DVLOG(1) << "Returning empty control type"; - V_VT(ret) = VT_EMPTY; - } - } else { - V_VT(ret) = VT_EMPTY; - } - return S_OK; -} - -// -// Static methods. -// - -void NativeViewAccessibility::RegisterWebView(View* web_view) { - AccessibleWebViewRegistry::GetInstance()->RegisterWebView(web_view); -} - -void NativeViewAccessibility::UnregisterWebView(View* web_view) { - AccessibleWebViewRegistry::GetInstance()->UnregisterWebView(web_view); -} - -int32 NativeViewAccessibilityWin::MSAAEvent(ui::AXEvent event) { - switch (event) { - case ui::AX_EVENT_ALERT: - return EVENT_SYSTEM_ALERT; - case ui::AX_EVENT_FOCUS: - return EVENT_OBJECT_FOCUS; - case ui::AX_EVENT_MENU_START: - return EVENT_SYSTEM_MENUSTART; - case ui::AX_EVENT_MENU_END: - return EVENT_SYSTEM_MENUEND; - case ui::AX_EVENT_MENU_POPUP_START: - return EVENT_SYSTEM_MENUPOPUPSTART; - case ui::AX_EVENT_MENU_POPUP_END: - return EVENT_SYSTEM_MENUPOPUPEND; - case ui::AX_EVENT_SELECTION: - return EVENT_OBJECT_SELECTION; - case ui::AX_EVENT_SELECTION_ADD: - return EVENT_OBJECT_SELECTIONADD; - case ui::AX_EVENT_SELECTION_REMOVE: - return EVENT_OBJECT_SELECTIONREMOVE; - case ui::AX_EVENT_TEXT_CHANGED: - return EVENT_OBJECT_NAMECHANGE; - case ui::AX_EVENT_TEXT_SELECTION_CHANGED: - return IA2_EVENT_TEXT_CARET_MOVED; - case ui::AX_EVENT_VALUE_CHANGED: - return EVENT_OBJECT_VALUECHANGE; - default: - // Not supported or invalid event. - NOTREACHED(); - return -1; - } -} - -int32 NativeViewAccessibilityWin::MSAARole(ui::AXRole role) { - switch (role) { - case ui::AX_ROLE_ALERT: - return ROLE_SYSTEM_ALERT; - case ui::AX_ROLE_APPLICATION: - return ROLE_SYSTEM_APPLICATION; - case ui::AX_ROLE_BUTTON_DROP_DOWN: - return ROLE_SYSTEM_BUTTONDROPDOWN; - case ui::AX_ROLE_POP_UP_BUTTON: - return ROLE_SYSTEM_BUTTONMENU; - case ui::AX_ROLE_CHECK_BOX: - return ROLE_SYSTEM_CHECKBUTTON; - case ui::AX_ROLE_COMBO_BOX: - return ROLE_SYSTEM_COMBOBOX; - case ui::AX_ROLE_DIALOG: - return ROLE_SYSTEM_DIALOG; - case ui::AX_ROLE_GROUP: - return ROLE_SYSTEM_GROUPING; - case ui::AX_ROLE_IMAGE: - return ROLE_SYSTEM_GRAPHIC; - case ui::AX_ROLE_LINK: - return ROLE_SYSTEM_LINK; - case ui::AX_ROLE_LOCATION_BAR: - return ROLE_SYSTEM_GROUPING; - case ui::AX_ROLE_MENU_BAR: - return ROLE_SYSTEM_MENUBAR; - case ui::AX_ROLE_MENU_ITEM: - return ROLE_SYSTEM_MENUITEM; - case ui::AX_ROLE_MENU_LIST_POPUP: - return ROLE_SYSTEM_MENUPOPUP; - case ui::AX_ROLE_TREE: - return ROLE_SYSTEM_OUTLINE; - case ui::AX_ROLE_TREE_ITEM: - return ROLE_SYSTEM_OUTLINEITEM; - case ui::AX_ROLE_TAB: - return ROLE_SYSTEM_PAGETAB; - case ui::AX_ROLE_TAB_LIST: - return ROLE_SYSTEM_PAGETABLIST; - case ui::AX_ROLE_PANE: - return ROLE_SYSTEM_PANE; - case ui::AX_ROLE_PROGRESS_INDICATOR: - return ROLE_SYSTEM_PROGRESSBAR; - case ui::AX_ROLE_BUTTON: - return ROLE_SYSTEM_PUSHBUTTON; - case ui::AX_ROLE_RADIO_BUTTON: - return ROLE_SYSTEM_RADIOBUTTON; - case ui::AX_ROLE_SCROLL_BAR: - return ROLE_SYSTEM_SCROLLBAR; - case ui::AX_ROLE_SPLITTER: - return ROLE_SYSTEM_SEPARATOR; - case ui::AX_ROLE_SLIDER: - return ROLE_SYSTEM_SLIDER; - case ui::AX_ROLE_STATIC_TEXT: - return ROLE_SYSTEM_STATICTEXT; - case ui::AX_ROLE_TEXT_FIELD: - return ROLE_SYSTEM_TEXT; - case ui::AX_ROLE_TITLE_BAR: - return ROLE_SYSTEM_TITLEBAR; - case ui::AX_ROLE_TOOLBAR: - return ROLE_SYSTEM_TOOLBAR; - case ui::AX_ROLE_WEB_VIEW: - return ROLE_SYSTEM_GROUPING; - case ui::AX_ROLE_WINDOW: - return ROLE_SYSTEM_WINDOW; - case ui::AX_ROLE_CLIENT: - default: - // This is the default role for MSAA. - return ROLE_SYSTEM_CLIENT; - } -} - -int32 NativeViewAccessibilityWin::MSAAState(const ui::AXViewState& state) { - // This maps MSAA states for get_accState(). See also the IAccessible2 - // interface get_states(). - - int32 msaa_state = 0; - if (state.HasStateFlag(ui::AX_STATE_CHECKED)) - msaa_state |= STATE_SYSTEM_CHECKED; - if (state.HasStateFlag(ui::AX_STATE_COLLAPSED)) - msaa_state |= STATE_SYSTEM_COLLAPSED; - if (state.HasStateFlag(ui::AX_STATE_DEFAULT)) - msaa_state |= STATE_SYSTEM_DEFAULT; - if (state.HasStateFlag(ui::AX_STATE_EXPANDED)) - msaa_state |= STATE_SYSTEM_EXPANDED; - if (state.HasStateFlag(ui::AX_STATE_HASPOPUP)) - msaa_state |= STATE_SYSTEM_HASPOPUP; - if (state.HasStateFlag(ui::AX_STATE_HOVERED)) - msaa_state |= STATE_SYSTEM_HOTTRACKED; - if (state.HasStateFlag(ui::AX_STATE_INVISIBLE)) - msaa_state |= STATE_SYSTEM_INVISIBLE; - if (state.HasStateFlag(ui::AX_STATE_LINKED)) - msaa_state |= STATE_SYSTEM_LINKED; - if (state.HasStateFlag(ui::AX_STATE_OFFSCREEN)) - msaa_state |= STATE_SYSTEM_OFFSCREEN; - if (state.HasStateFlag(ui::AX_STATE_PRESSED)) - msaa_state |= STATE_SYSTEM_PRESSED; - if (state.HasStateFlag(ui::AX_STATE_PROTECTED)) - msaa_state |= STATE_SYSTEM_PROTECTED; - if (state.HasStateFlag(ui::AX_STATE_READ_ONLY)) - msaa_state |= STATE_SYSTEM_READONLY; - if (state.HasStateFlag(ui::AX_STATE_SELECTABLE)) - msaa_state |= STATE_SYSTEM_SELECTABLE; - if (state.HasStateFlag(ui::AX_STATE_SELECTED)) - msaa_state |= STATE_SYSTEM_SELECTED; - if (state.HasStateFlag(ui::AX_STATE_FOCUSED)) - msaa_state |= STATE_SYSTEM_FOCUSED; - if (state.HasStateFlag(ui::AX_STATE_DISABLED)) - msaa_state |= STATE_SYSTEM_UNAVAILABLE; - return msaa_state; -} - -// -// Private methods. -// - -bool NativeViewAccessibilityWin::IsNavDirNext(int nav_dir) const { - return (nav_dir == NAVDIR_RIGHT || - nav_dir == NAVDIR_DOWN || - nav_dir == NAVDIR_NEXT); -} - -bool NativeViewAccessibilityWin::IsValidNav( - int nav_dir, int start_id, int lower_bound, int upper_bound) const { - if (IsNavDirNext(nav_dir)) { - if ((start_id + 1) > upper_bound) { - return false; - } - } else { - if ((start_id - 1) <= lower_bound) { - return false; - } - } - return true; -} - -bool NativeViewAccessibilityWin::IsValidId(const VARIANT& child) const { - // View accessibility returns an IAccessible for each view so we only support - // the CHILDID_SELF id. - return (VT_I4 == child.vt) && (CHILDID_SELF == child.lVal); -} - -void NativeViewAccessibilityWin::SetState( - VARIANT* msaa_state, View* view) { - // Ensure the output param is initialized to zero. - msaa_state->lVal = 0; - - // Default state; all views can have accessibility focus. - msaa_state->lVal |= STATE_SYSTEM_FOCUSABLE; - - if (!view) - return; - - if (!view->enabled()) - msaa_state->lVal |= STATE_SYSTEM_UNAVAILABLE; - if (!view->visible()) - msaa_state->lVal |= STATE_SYSTEM_INVISIBLE; - if (!strcmp(view->GetClassName(), CustomButton::kViewClassName)) { - CustomButton* button = static_cast(view); - if (button->IsHotTracked()) - msaa_state->lVal |= STATE_SYSTEM_HOTTRACKED; - } - if (view->HasFocus()) - msaa_state->lVal |= STATE_SYSTEM_FOCUSED; - - // Add on any view-specific states. - ui::AXViewState view_state; - view->GetAccessibleState(&view_state); - msaa_state->lVal |= MSAAState(view_state); -} - -base::string16 NativeViewAccessibilityWin::TextForIAccessibleText() { - ui::AXViewState state; - view_->GetAccessibleState(&state); - if (state.role == ui::AX_ROLE_TEXT_FIELD) - return state.value; - else - return state.name; -} - -void NativeViewAccessibilityWin::HandleSpecialTextOffset( - const base::string16& text, LONG* offset) { - if (*offset == IA2_TEXT_OFFSET_LENGTH) { - *offset = static_cast(text.size()); - } else if (*offset == IA2_TEXT_OFFSET_CARET) { - get_caretOffset(offset); - } -} - -ui::TextBoundaryType NativeViewAccessibilityWin::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 NativeViewAccessibilityWin::FindBoundary( - const base::string16& text, - IA2TextBoundaryType ia2_boundary, - LONG start_offset, - ui::TextBoundaryDirection direction) { - HandleSpecialTextOffset(text, &start_offset); - ui::TextBoundaryType boundary = IA2TextBoundaryToTextBoundary(ia2_boundary); - std::vector line_breaks; - return ui::FindAccessibleTextBoundary( - text, line_breaks, boundary, start_offset, direction); -} - -void NativeViewAccessibilityWin::PopulateChildWidgetVector( - std::vector* result_child_widgets) { - const Widget* widget = view()->GetWidget(); - if (!widget) - return; - - std::set child_widgets; - Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets); - for (std::set::const_iterator iter = child_widgets.begin(); - iter != child_widgets.end(); ++iter) { - Widget* child_widget = *iter; - DCHECK_NE(widget, child_widget); - - if (!child_widget->IsVisible()) - continue; - - if (widget->GetNativeWindowProperty(kWidgetNativeViewHostKey)) - continue; - - result_child_widgets->push_back(child_widget); - } -} - -void NativeViewAccessibilityWin::AddAlertTarget() { - ViewStorage* view_storage = ViewStorage::GetInstance(); - for (size_t i = 0; i < alert_target_view_storage_ids_.size(); ++i) { - int view_storage_id = alert_target_view_storage_ids_[i]; - View* view = view_storage->RetrieveView(view_storage_id); - if (view == view_) - return; - } - int view_storage_id = view_storage->CreateStorageID(); - view_storage->StoreView(view_storage_id, view_); - alert_target_view_storage_ids_.push_back(view_storage_id); -} - -void NativeViewAccessibilityWin::RemoveAlertTarget() { - ViewStorage* view_storage = ViewStorage::GetInstance(); - size_t i = 0; - while (i < alert_target_view_storage_ids_.size()) { - int view_storage_id = alert_target_view_storage_ids_[i]; - View* view = view_storage->RetrieveView(view_storage_id); - if (view == NULL || view == view_) { - alert_target_view_storage_ids_.erase( - alert_target_view_storage_ids_.begin() + i); - } else { - ++i; - } - } -} - -} // namespace views +// 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 "ui/views/accessibility/native_view_accessibility_win.h" + +#include + +#include +#include + +#include "base/memory/singleton.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/scoped_comptr.h" +#include "base/win/windows_version.h" +#include "third_party/iaccessible2/ia2_api_all.h" +#include "ui/accessibility/ax_enums.h" +#include "ui/accessibility/ax_text_utils.h" +#include "ui/accessibility/ax_view_state.h" +#include "ui/base/win/accessibility_misc_utils.h" +#include "ui/base/win/atl_module.h" +#include "ui/views/controls/button/custom_button.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/focus/view_storage.h" +#include "ui/views/widget/widget.h" +#include "ui/views/win/hwnd_util.h" + +namespace views { + +// static +NativeViewAccessibility* NativeViewAccessibility::Create(View* view) { + return new NativeViewAccessibilityWin(view); +} + +NativeViewAccessibilityWin::NativeViewAccessibilityWin(View* view) + : NativeViewAccessibility(view) { +} + +NativeViewAccessibilityWin::~NativeViewAccessibilityWin() { +} + +gfx::NativeViewAccessible NativeViewAccessibilityWin::GetParent() { + IAccessible* parent = NativeViewAccessibility::GetParent(); + if (parent) + return parent; + + HWND hwnd = HWNDForView(view_); + if (!hwnd) + return NULL; + + HRESULT hr = ::AccessibleObjectFromWindow( + hwnd, OBJID_WINDOW, IID_IAccessible, + reinterpret_cast(&parent)); + if (SUCCEEDED(hr)) + return parent; + + return NULL; +} + +gfx::AcceleratedWidget +NativeViewAccessibilityWin::GetTargetForNativeAccessibilityEvent() { + return HWNDForView(view_); +} + +} // namespace views diff --git a/ui/views/accessibility/native_view_accessibility_win.h b/ui/views/accessibility/native_view_accessibility_win.h dissimilarity index 95% index e34bdc33c310..087d50e7d914 100644 --- a/ui/views/accessibility/native_view_accessibility_win.h +++ b/ui/views/accessibility/native_view_accessibility_win.h @@ -1,448 +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 UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ -#define UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ - -#include -#include -#include - -#include - -#include -#include - -#include "third_party/iaccessible2/ia2_api_all.h" -#include "ui/accessibility/ax_view_state.h" -#include "ui/views/accessibility/native_view_accessibility.h" -#include "ui/views/controls/native/native_view_host.h" -#include "ui/views/view.h" - -namespace ui { -enum TextBoundaryDirection; -enum TextBoundaryType; -} - -namespace views { - -//////////////////////////////////////////////////////////////////////////////// -// -// NativeViewAccessibilityWin -// -// Class implementing the MSAA IAccessible COM interface for a generic View, -// providing accessibility to be used by screen readers and other assistive -// technology (AT). -// -//////////////////////////////////////////////////////////////////////////////// -class __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) -NativeViewAccessibilityWin - : public CComObjectRootEx, - public IDispatchImpl, - public IAccessibleText, - public IServiceProvider, - public IAccessibleEx, - public IRawElementProviderSimple, - public NativeViewAccessibility { - public: - BEGIN_COM_MAP(NativeViewAccessibilityWin) - COM_INTERFACE_ENTRY2(IDispatch, IAccessible2_2) - COM_INTERFACE_ENTRY(IAccessible) - COM_INTERFACE_ENTRY(IAccessible2) - COM_INTERFACE_ENTRY(IAccessible2_2) - COM_INTERFACE_ENTRY(IAccessibleEx) - COM_INTERFACE_ENTRY(IAccessibleText) - COM_INTERFACE_ENTRY(IRawElementProviderSimple) - COM_INTERFACE_ENTRY(IServiceProvider) - END_COM_MAP() - - virtual ~NativeViewAccessibilityWin(); - - // NativeViewAccessibility. - void NotifyAccessibilityEvent(ui::AXEvent event_type) override; - gfx::NativeViewAccessible GetNativeObject() override; - void Destroy() override; - - // Supported IAccessible methods. - - // Retrieves the child element or child object at a given point on the screen. - STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child) override; - - // Performs the object's default action. - STDMETHODIMP accDoDefaultAction(VARIANT var_id) override; - - // Retrieves the specified object's current screen location. - STDMETHODIMP accLocation(LONG* x_left, - LONG* y_top, - LONG* width, - LONG* height, - VARIANT var_id) override; - - // Traverses to another UI element and retrieves the object. - STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end) override; - - // Retrieves an IDispatch interface pointer for the specified child. - STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child) override; - - // Retrieves the number of accessible children. - STDMETHODIMP get_accChildCount(LONG* child_count) override; - - // Retrieves a string that describes the object's default action. - STDMETHODIMP get_accDefaultAction(VARIANT var_id, - BSTR* default_action) override; - - // Retrieves the tooltip description. - STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc) override; - - // Retrieves the object that has the keyboard focus. - STDMETHODIMP get_accFocus(VARIANT* focus_child) override; - - // Retrieves the specified object's shortcut. - STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, - BSTR* access_key) override; - - // Retrieves the name of the specified object. - STDMETHODIMP get_accName(VARIANT var_id, BSTR* name) override; - - // Retrieves the IDispatch interface of the object's parent. - STDMETHODIMP get_accParent(IDispatch** disp_parent) override; - - // Retrieves information describing the role of the specified object. - STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role) override; - - // Retrieves the current state of the specified object. - STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state) override; - - // Retrieve or set the string value associated with the specified object. - // Setting the value is not typically used by screen readers, but it's - // used frequently by automation software. - STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value) override; - STDMETHODIMP put_accValue(VARIANT var_id, BSTR new_value) override; - - // Selections not applicable to views. - STDMETHODIMP get_accSelection(VARIANT* selected) override; - STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id) override; - - // Help functions not supported. - STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help) override; - STDMETHODIMP get_accHelpTopic(BSTR* help_file, - VARIANT var_id, - LONG* topic_id) override; - - // Deprecated functions, not implemented here. - STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) override; - - // - // IAccessible2 - // - - STDMETHODIMP role(LONG* role) override; - - STDMETHODIMP get_states(AccessibleStates* states) override; - - STDMETHODIMP get_uniqueID(LONG* unique_id) override; - - STDMETHODIMP get_windowHandle(HWND* window_handle) override; - - STDMETHODIMP get_relationTargetsOfType(BSTR type, - long max_targets, - IUnknown*** targets, - long* n_targets) override; - - STDMETHODIMP get_attributes(BSTR* attributes) override; - - // - // IAccessible2 methods not implemented. - // - - STDMETHODIMP get_attribute(BSTR name, VARIANT* attribute) override { - return E_NOTIMPL; - } - STDMETHODIMP get_indexInParent(LONG* index_in_parent) override { - return E_NOTIMPL; - } - STDMETHODIMP get_extendedRole(BSTR* extended_role) override { - return E_NOTIMPL; - } - STDMETHODIMP get_nRelations(LONG* n_relations) override { return E_NOTIMPL; } - STDMETHODIMP get_relation(LONG relation_index, - IAccessibleRelation** relation) override { - return E_NOTIMPL; - } - STDMETHODIMP get_relations(LONG max_relations, - IAccessibleRelation** relations, - LONG* n_relations) override { - return E_NOTIMPL; - } - STDMETHODIMP scrollTo(enum IA2ScrollType scroll_type) override { - return E_NOTIMPL; - } - STDMETHODIMP scrollToPoint(enum IA2CoordinateType coordinate_type, - LONG x, - LONG y) override { - return E_NOTIMPL; - } - STDMETHODIMP get_groupPosition(LONG* group_level, - LONG* similar_items_in_group, - LONG* position_in_group) override { - return E_NOTIMPL; - } - STDMETHODIMP get_localizedExtendedRole( - BSTR* localized_extended_role) override { - return E_NOTIMPL; - } - STDMETHODIMP get_nExtendedStates(LONG* n_extended_states) override { - return E_NOTIMPL; - } - STDMETHODIMP get_extendedStates(LONG max_extended_states, - BSTR** extended_states, - LONG* n_extended_states) override { - return E_NOTIMPL; - } - STDMETHODIMP get_localizedExtendedStates( - LONG max_localized_extended_states, - BSTR** localized_extended_states, - LONG* n_localized_extended_states) override { - return E_NOTIMPL; - } - STDMETHODIMP get_locale(IA2Locale* locale) override { return E_NOTIMPL; } - STDMETHODIMP get_accessibleWithCaret(IUnknown** accessible, - long* caret_offset) override { - return E_NOTIMPL; - } - - // - // IAccessibleText methods. - // - - STDMETHODIMP get_nCharacters(LONG* n_characters) override; - - STDMETHODIMP get_caretOffset(LONG* offset) override; - - STDMETHODIMP get_nSelections(LONG* n_selections) override; - - STDMETHODIMP get_selection(LONG selection_index, - LONG* start_offset, - LONG* end_offset) override; - - STDMETHODIMP get_text(LONG start_offset, - LONG end_offset, - BSTR* text) override; - - STDMETHODIMP get_textAtOffset(LONG offset, - enum IA2TextBoundaryType boundary_type, - LONG* start_offset, - LONG* end_offset, - BSTR* text) override; - - STDMETHODIMP get_textBeforeOffset(LONG offset, - enum IA2TextBoundaryType boundary_type, - LONG* start_offset, - LONG* end_offset, - BSTR* text) override; - - STDMETHODIMP get_textAfterOffset(LONG offset, - enum IA2TextBoundaryType boundary_type, - LONG* start_offset, - LONG* end_offset, - BSTR* text) override; - - STDMETHODIMP get_offsetAtPoint(LONG x, - LONG y, - enum IA2CoordinateType coord_type, - LONG* offset) override; - - // - // IAccessibleText methods not implemented. - // - - STDMETHODIMP get_newText(IA2TextSegment* new_text) override { - return E_NOTIMPL; - } - STDMETHODIMP get_oldText(IA2TextSegment* old_text) override { - return E_NOTIMPL; - } - STDMETHODIMP addSelection(LONG start_offset, LONG end_offset) override { - return E_NOTIMPL; - } - STDMETHODIMP get_attributes(LONG offset, - LONG* start_offset, - LONG* end_offset, - BSTR* text_attributes) override { - return E_NOTIMPL; - } - STDMETHODIMP get_characterExtents(LONG offset, - enum IA2CoordinateType coord_type, - LONG* x, - LONG* y, - LONG* width, - LONG* height) override { - return E_NOTIMPL; - } - STDMETHODIMP removeSelection(LONG selection_index) override { - return E_NOTIMPL; - } - STDMETHODIMP setCaretOffset(LONG offset) override { return E_NOTIMPL; } - STDMETHODIMP setSelection(LONG selection_index, - LONG start_offset, - LONG end_offset) override { - return E_NOTIMPL; - } - STDMETHODIMP scrollSubstringTo(LONG start_index, - LONG end_index, - enum IA2ScrollType scroll_type) override { - return E_NOTIMPL; - } - STDMETHODIMP scrollSubstringToPoint(LONG start_index, - LONG end_index, - enum IA2CoordinateType coordinate_type, - LONG x, - LONG y) override { - return E_NOTIMPL; - } - - // - // IServiceProvider methods. - // - - STDMETHODIMP QueryService(REFGUID guidService, - REFIID riid, - void** object) override; - - // - // IAccessibleEx methods not implemented. - // - STDMETHODIMP GetObjectForChild(long child_id, IAccessibleEx** ret) override { - return E_NOTIMPL; - } - - STDMETHODIMP GetIAccessiblePair(IAccessible** acc, long* child_id) override { - return E_NOTIMPL; - } - - STDMETHODIMP GetRuntimeId(SAFEARRAY** runtime_id) override { - return E_NOTIMPL; - } - - STDMETHODIMP ConvertReturnedElement(IRawElementProviderSimple* element, - IAccessibleEx** acc) override { - 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. - STDMETHODIMP GetPatternProvider(PATTERNID id, IUnknown** provider) override; - STDMETHODIMP GetPropertyValue(PROPERTYID id, VARIANT* ret) override; - - // - // IRawElementProviderSimple methods not implemented. - // - STDMETHODIMP get_ProviderOptions(enum ProviderOptions* ret) override { - return E_NOTIMPL; - } - - STDMETHODIMP get_HostRawElementProvider( - IRawElementProviderSimple** provider) override { - return E_NOTIMPL; - } - - // Static methods - - // Returns a conversion from the event (as defined in ax_enums.idl) - // to an MSAA event. - static int32 MSAAEvent(ui::AXEvent event); - - // Returns a conversion from the Role (as defined in ax_enums.idl) - // to an MSAA role. - static int32 MSAARole(ui::AXRole role); - - // Returns a conversion from the State (as defined in ax_enums.idl) - // to MSAA states set. - static int32 MSAAState(const ui::AXViewState& state); - - protected: - NativeViewAccessibilityWin(); - - private: - // Determines navigation direction for accNavigate, based on left, up and - // previous being mapped all to previous and right, down, next being mapped - // to next. Returns true if navigation direction is next, false otherwise. - bool IsNavDirNext(int nav_dir) const; - - // Determines if the navigation target is within the allowed bounds. Returns - // true if it is, false otherwise. - bool IsValidNav(int nav_dir, - int start_id, - int lower_bound, - int upper_bound) const; - - // Determines if the child id variant is valid. - bool IsValidId(const VARIANT& child) const; - - // Helper function which sets applicable states of view. - void SetState(VARIANT* msaa_state, View* view); - - // Return the text to use for IAccessibleText. - base::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 base::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 base::string16& text, - IA2TextBoundaryType ia2_boundary, - LONG start_offset, - ui::TextBoundaryDirection direction); - - // Populates the given vector with all widgets that are either a child - // or are owned by this view's widget, and who are not contained in a - // NativeViewHost. - void PopulateChildWidgetVector(std::vector* child_widgets); - - // Adds this view to alert_target_view_storage_ids_. - void AddAlertTarget(); - - // Removes this view from alert_target_view_storage_ids_. - void RemoveAlertTarget(); - - // Give CComObject access to the class constructor. - template friend class CComObject; - - // A unique id for each object, needed for IAccessible2. - long unique_id_; - - // Next unique id to assign. - static long next_unique_id_; - - // Circular queue size. - static const int kMaxViewStorageIds = 20; - - // Circular queue of view storage ids corresponding to child ids - // used to post notifications using NotifyWinEvent. - static int view_storage_ids_[kMaxViewStorageIds]; - - // Next index into |view_storage_ids_| to use. - static int next_view_storage_id_index_; - - // A vector of view storage ids of views that have been the target of - // an alert event, in order to provide an api to quickly identify all - // open alerts. - static std::vector alert_target_view_storage_ids_; - - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityWin); -}; - -} // namespace views - -#endif // UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ +// 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 UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ +#define UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ + +#include "ui/views/accessibility/native_view_accessibility.h" +#include "ui/views/view.h" + +namespace views { + +class NativeViewAccessibilityWin : public NativeViewAccessibility { + public: + NativeViewAccessibilityWin(View* view); + virtual ~NativeViewAccessibilityWin(); + + // NativeViewAccessibility. + gfx::NativeViewAccessible GetParent() override; + gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override; + + DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityWin); +}; + +} // namespace views + +#endif // UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ diff --git a/ui/views/accessibility/native_view_accessibility_win_unittest.cc b/ui/views/accessibility/native_view_accessibility_win_unittest.cc index 3edab8e258e9..e30a15c56c73 100644 --- a/ui/views/accessibility/native_view_accessibility_win_unittest.cc +++ b/ui/views/accessibility/native_view_accessibility_win_unittest.cc @@ -81,35 +81,6 @@ TEST_F(NativeViewAcccessibilityWinTest, TextfieldAccessibility) { ASSERT_STREQ(L"New value", textfield->text().c_str()); } -TEST_F(NativeViewAcccessibilityWinTest, UnattachedWebView) { - // This is a regression test. Calling get_accChild on the native accessible - // object for a WebView with no attached WebContents was causing an - // infinite loop and crash. This test simulates that with an ordinary - // View that registers itself as a web view with NativeViewAcccessibility. - - Widget widget; - Widget::InitParams init_params = - CreateParams(Widget::InitParams::TYPE_POPUP); - init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - widget.Init(init_params); - - View* content = new View; - widget.SetContentsView(content); - - View* web_view = new View; - content->AddChildView(web_view); - NativeViewAccessibility::RegisterWebView(web_view); - - ScopedComPtr web_view_accessible( - web_view->GetNativeViewAccessible()); - ScopedComPtr result_dispatch; - ScopedVariant child_index(-999); - ASSERT_EQ(E_FAIL, web_view_accessible->get_accChild( - child_index, result_dispatch.Receive())); - - NativeViewAccessibility::UnregisterWebView(web_view); -} - TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) { Widget widget; Widget::InitParams init_params = @@ -124,6 +95,14 @@ TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) { ASSERT_EQ(S_OK, root_view_accessible->get_accChildCount(&child_count)); ASSERT_EQ(1L, child_count); + ScopedComPtr child_view_dispatch; + ScopedComPtr child_view_accessible; + ScopedVariant child_index_1(1); + ASSERT_EQ(S_OK, root_view_accessible->get_accChild( + child_index_1, child_view_dispatch.Receive())); + ASSERT_EQ(S_OK, child_view_dispatch.QueryInterface( + child_view_accessible.Receive())); + Widget owned_widget; Widget::InitParams owned_init_params = CreateParams(Widget::InitParams::TYPE_POPUP); @@ -134,6 +113,34 @@ TEST_F(NativeViewAcccessibilityWinTest, AuraOwnedWidgets) { ASSERT_EQ(S_OK, root_view_accessible->get_accChildCount(&child_count)); ASSERT_EQ(2L, child_count); + + ScopedComPtr child_widget_dispatch; + ScopedComPtr child_widget_accessible; + ScopedVariant child_index_2(2); + ASSERT_EQ(S_OK, root_view_accessible->get_accChild( + child_index_2, child_widget_dispatch.Receive())); + ASSERT_EQ(S_OK, child_widget_dispatch.QueryInterface( + child_widget_accessible.Receive())); + + ScopedComPtr child_widget_sibling_dispatch; + ScopedComPtr child_widget_sibling_accessible; + ScopedVariant childid_self(CHILDID_SELF); + ScopedVariant result; + ASSERT_EQ(S_OK, child_widget_accessible->accNavigate( + NAVDIR_PREVIOUS, childid_self, result.Receive())); + ASSERT_EQ(VT_DISPATCH, V_VT(&result)); + child_widget_sibling_dispatch = V_DISPATCH(&result); + ASSERT_EQ(S_OK, child_widget_sibling_dispatch.QueryInterface( + child_widget_sibling_accessible.Receive())); + ASSERT_EQ(child_view_accessible.get(), child_widget_sibling_accessible.get()); + + ScopedComPtr child_widget_parent_dispatch; + ScopedComPtr child_widget_parent_accessible; + ASSERT_EQ(S_OK, child_widget_accessible->get_accParent( + child_widget_parent_dispatch.Receive())); + ASSERT_EQ(S_OK, child_widget_parent_dispatch.QueryInterface( + child_widget_parent_accessible.Receive())); + ASSERT_EQ(root_view_accessible.get(), child_widget_parent_accessible.get()); } TEST_F(NativeViewAcccessibilityWinTest, RetrieveAllAlerts) { diff --git a/ui/views/controls/webview/webview.cc b/ui/views/controls/webview/webview.cc index e872a58b22e1..be33f1d58df3 100644 --- a/ui/views/controls/webview/webview.cc +++ b/ui/views/controls/webview/webview.cc @@ -16,7 +16,6 @@ #include "ui/accessibility/ax_view_state.h" #include "ui/base/ui_base_switches_util.h" #include "ui/events/event.h" -#include "ui/views/accessibility/native_view_accessibility.h" #include "ui/views/controls/native/native_view_host.h" #include "ui/views/focus/focus_manager.h" #include "ui/views/views_delegate.h" @@ -37,12 +36,10 @@ WebView::WebView(content::BrowserContext* browser_context) browser_context_(browser_context), allow_accelerators_(false) { AddChildView(holder_); // Takes ownership of |holder_|. - NativeViewAccessibility::RegisterWebView(this); } WebView::~WebView() { SetWebContents(NULL); // Make sure all necessary tear-down takes place. - NativeViewAccessibility::UnregisterWebView(this); } content::WebContents* WebView::GetWebContents() { -- 2.11.4.GIT