summaryrefslogtreecommitdiff
path: root/chromium/ui/views/accessibility/native_view_accessibility_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/views/accessibility/native_view_accessibility_win.cc')
-rw-r--r--chromium/ui/views/accessibility/native_view_accessibility_win.cc229
1 files changed, 198 insertions, 31 deletions
diff --git a/chromium/ui/views/accessibility/native_view_accessibility_win.cc b/chromium/ui/views/accessibility/native_view_accessibility_win.cc
index 279c99d3d29..7aa92a2a13c 100644
--- a/chromium/ui/views/accessibility/native_view_accessibility_win.cc
+++ b/chromium/ui/views/accessibility/native_view_accessibility_win.cc
@@ -11,6 +11,8 @@
#include <vector>
#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/base/accessibility/accessible_text_utils.h"
@@ -21,6 +23,7 @@
#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"
using ui::AccessibilityTypes;
@@ -32,9 +35,9 @@ class AccessibleWebViewRegistry {
public:
static AccessibleWebViewRegistry* GetInstance();
- void RegisterWebView(AccessibleWebView* web_view);
+ void RegisterWebView(View* web_view);
- void UnregisterWebView(AccessibleWebView* 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
@@ -42,40 +45,62 @@ class AccessibleWebViewRegistry {
// |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();
~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<AccessibleWebView*> web_views_;
+ std::set<View*> 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_|.
- AccessibleWebView* last_web_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) {
+ last_web_view_(NULL),
+ iaccessible2_support_enabled_(false) {
}
AccessibleWebViewRegistry* AccessibleWebViewRegistry::GetInstance() {
return Singleton<AccessibleWebViewRegistry>::get();
}
-void AccessibleWebViewRegistry::RegisterWebView(AccessibleWebView* web_view) {
+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(AccessibleWebView* 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) {
@@ -91,7 +116,7 @@ IAccessible* AccessibleWebViewRegistry::GetAccessibleFromWebView(
// sent the last one.
if (last_top_view_ == top_view) {
IAccessible* accessible =
- last_web_view_->AccessibleObjectFromChildId(child_id);
+ AccessibleObjectFromChildId(last_web_view_, child_id);
if (accessible)
return accessible;
}
@@ -100,21 +125,65 @@ IAccessible* AccessibleWebViewRegistry::GetAccessibleFromWebView(
// 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<AccessibleWebView*>::iterator iter = web_views_.begin();
+ for (std::set<View*>::iterator iter = web_views_.begin();
iter != web_views_.end(); ++iter) {
- AccessibleWebView* web_view = *iter;
- if (!top_view->Contains(web_view->AsView()))
+ View* web_view = *iter;
+ if (top_view == web_view || !top_view->Contains(web_view))
continue;
- IAccessible* accessible = web_view->AccessibleObjectFromChildId(child_id);
+ 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<View*>::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<IDispatch**>(&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<IServiceProvider> service_provider;
+ if (S_OK != web_view_accessible->QueryInterface(service_provider.Receive()))
+ return;
+ base::win::ScopedComPtr<IAccessible2> iaccessible2;
+ service_provider->QueryService(
+ IID_IAccessible, IID_IAccessible2,
+ reinterpret_cast<void**>(iaccessible2.Receive()));
+}
+
} // anonymous namespace
// static
@@ -179,7 +248,6 @@ void NativeViewAccessibilityWin::Destroy() {
Release();
}
-// TODO(ctguil): Handle case where child View is not contained by parent.
STDMETHODIMP NativeViewAccessibilityWin::accHitTest(
LONG x_left, LONG y_top, VARIANT* child) {
if (!child)
@@ -188,25 +256,71 @@ STDMETHODIMP NativeViewAccessibilityWin::accHitTest(
if (!view_)
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<Widget*> 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::ConvertPointToTarget(NULL, view_, &point);
+ // If the point is not inside this view, return false.
if (!view_->HitTestPoint(point)) {
- // If containing parent is not hit, return with failure.
child->vt = VT_EMPTY;
return S_FALSE;
}
- View* view = view_->GetEventHandlerForPoint(point);
- if (view == view_) {
- // No child hit, return parent id.
- child->vt = VT_I4;
- child->lVal = CHILDID_SELF;
- } else {
- child->vt = VT_DISPATCH;
- child->pdispVal = view->GetNativeViewAccessible();
- child->pdispVal->AddRef();
+ // 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;
}
@@ -357,16 +471,37 @@ STDMETHODIMP NativeViewAccessibilityWin::get_accChild(VARIANT var_child,
return S_OK;
}
+ // If this is a root view, our widget might have child widgets. Include
+ std::vector<Widget*> child_widgets;
+ if (view_->GetWidget()->GetRootView() == view_)
+ PopulateChildWidgetVector(&child_widgets);
+ int child_widget_count = static_cast<int>(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())
+ 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;
- // we map child ids to a view storage id that can refer to a
+ // 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;
@@ -378,10 +513,8 @@ STDMETHODIMP NativeViewAccessibilityWin::get_accChild(VARIANT var_child,
} else {
*disp_child = AccessibleWebViewRegistry::GetInstance()->
GetAccessibleFromWebView(view_, child_id);
- if (*disp_child) {
- (*disp_child)->AddRef();
+ if (*disp_child)
return S_OK;
- }
}
}
@@ -404,6 +537,15 @@ STDMETHODIMP NativeViewAccessibilityWin::get_accChildCount(LONG* child_count) {
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<Widget*> child_widgets;
+ PopulateChildWidgetVector(&child_widgets);
+ *child_count += child_widgets.size();
+ }
+
return S_OK;
}
@@ -929,6 +1071,9 @@ STDMETHODIMP NativeViewAccessibilityWin::QueryService(
if (!view_)
return E_FAIL;
+ if (riid == IID_IAccessible2)
+ AccessibleWebViewRegistry::GetInstance()->EnableIAccessible2Support();
+
if (guidService == IID_IAccessible ||
guidService == IID_IAccessible2 ||
guidService == IID_IAccessibleText) {
@@ -996,11 +1141,11 @@ STDMETHODIMP NativeViewAccessibilityWin::GetPropertyValue(PROPERTYID id,
// Static methods.
//
-void NativeViewAccessibility::RegisterWebView(AccessibleWebView* web_view) {
+void NativeViewAccessibility::RegisterWebView(View* web_view) {
AccessibleWebViewRegistry::GetInstance()->RegisterWebView(web_view);
}
-void NativeViewAccessibility::UnregisterWebView(AccessibleWebView* web_view) {
+void NativeViewAccessibility::UnregisterWebView(View* web_view) {
AccessibleWebViewRegistry::GetInstance()->UnregisterWebView(web_view);
}
@@ -1244,4 +1389,26 @@ LONG NativeViewAccessibilityWin::FindBoundary(
text, line_breaks, boundary, start_offset, direction);
}
+void NativeViewAccessibilityWin::PopulateChildWidgetVector(
+ std::vector<Widget*>* result_child_widgets) {
+ const Widget* widget = view()->GetWidget();
+ std::set<Widget*> child_widgets;
+ Widget::GetAllChildWidgets(widget->GetNativeView(), &child_widgets);
+ Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets);
+ for (std::set<Widget*>::const_iterator iter = child_widgets.begin();
+ iter != child_widgets.end(); ++iter) {
+ Widget* child_widget = *iter;
+ if (child_widget == widget)
+ continue;
+
+ if (!child_widget->IsVisible())
+ continue;
+
+ if (widget->GetNativeWindowProperty(kWidgetNativeViewHostKey))
+ continue;
+
+ result_child_widgets->push_back(child_widget);
+ }
+}
+
} // namespace views