| // 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 <oleacc.h> |
| #include <UIAutomationClient.h> |
| |
| #include <set> |
| #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/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 { |
| |
| // A version of the OBJID_CLIENT constant that works in 64-bit mode too. |
| static const LPARAM kObjIdClient = static_cast<ULONG>(OBJID_CLIENT); |
| |
| 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(); |
| ~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<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_|. |
| 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<AccessibleWebViewRegistry>::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<View*>::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<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 |
| long NativeViewAccessibilityWin::next_unique_id_ = 1; |
| int NativeViewAccessibilityWin::view_storage_ids_[kMaxViewStorageIds] = {0}; |
| int NativeViewAccessibilityWin::next_view_storage_id_index_ = 0; |
| std::vector<int> NativeViewAccessibilityWin::alert_target_view_storage_ids_; |
| |
| // static |
| NativeViewAccessibility* NativeViewAccessibility::Create(View* view) { |
| // Make sure ATL is initialized in this module. |
| ui::win::CreateATLModuleIfNeeded(); |
| |
| CComObject<NativeViewAccessibilityWin>* instance = NULL; |
| HRESULT hr = CComObject<NativeViewAccessibilityWin>::CreateInstance( |
| &instance); |
| DCHECK(SUCCEEDED(hr)); |
| instance->set_view(view); |
| instance->AddRef(); |
| return instance; |
| } |
| |
| NativeViewAccessibilityWin::NativeViewAccessibilityWin() |
| : view_(NULL), |
| 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<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::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<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()) { |
| 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(); |
| (*disp_child)->AddRef(); |
| return S_OK; |
| } |
| |
| 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<Widget*> 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<void**>(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) { |
| 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<LONG>(-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<View*> 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<IUnknown**>( |
| CoTaskMemAlloc(count * sizeof(IUnknown*))); |
| for (long i = 0; i < count; ++i) { |
| (*targets)[i] = alert_views[i]->GetNativeViewAccessible(); |
| (*targets)[i]->AddRef(); |
| } |
| 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<LONG>(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<LONG>(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<LONG>(state.selection_start); |
| *end_offset = static_cast<LONG>(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<LONG>(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<LONG>(state.selection_end); |
| } |
| if (end_offset == IA2_TEXT_OFFSET_LENGTH) { |
| end_offset = static_cast<LONG>(text_str.size()); |
| } else if (end_offset == IA2_TEXT_OFFSET_CARET) { |
| end_offset = static_cast<LONG>(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) { |
| 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) { |
| 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_TEXT_CHANGED: |
| return EVENT_OBJECT_NAMECHANGE; |
| case ui::AX_EVENT_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_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_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<CustomButton*>(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<LONG>(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<int32> line_breaks; |
| return ui::FindAccessibleTextBoundary( |
| text, line_breaks, boundary, start_offset, direction); |
| } |
| |
| void NativeViewAccessibilityWin::PopulateChildWidgetVector( |
| std::vector<Widget*>* result_child_widgets) { |
| const Widget* widget = view()->GetWidget(); |
| if (!widget) |
| return; |
| |
| 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); |
| } |
| } |
| |
| 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 |