diff options
Diffstat (limited to 'chromium/ui/views/accessibility/native_view_accessibility_win.cc')
-rw-r--r-- | chromium/ui/views/accessibility/native_view_accessibility_win.cc | 229 |
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 |