native_view_accessibility_win.cc revision b2df76ea8fec9e32f6f3718986dba0d95315b29c
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/views/accessibility/native_view_accessibility_win.h"
6
7#include <UIAutomationClient.h>
8#include <oleacc.h>
9
10#include <set>
11#include <vector>
12
13#include "base/memory/singleton.h"
14#include "base/win/windows_version.h"
15#include "third_party/iaccessible2/ia2_api_all.h"
16#include "ui/base/accessibility/accessible_text_utils.h"
17#include "ui/base/accessibility/accessible_view_state.h"
18#include "ui/base/win/accessibility_ids_win.h"
19#include "ui/base/win/accessibility_misc_utils.h"
20#include "ui/base/win/atl_module.h"
21#include "ui/views/controls/button/custom_button.h"
22#include "ui/views/controls/webview/webview.h"
23#include "ui/views/focus/focus_manager.h"
24#include "ui/views/focus/view_storage.h"
25#include "ui/views/win/hwnd_util.h"
26
27using ui::AccessibilityTypes;
28
29namespace views {
30namespace {
31
32class AccessibleWebViewRegistry {
33 public:
34  static AccessibleWebViewRegistry* GetInstance();
35
36  void RegisterWebView(AccessibleWebView* web_view);
37
38  void UnregisterWebView(AccessibleWebView* web_view);
39
40  // Given the view that received the request for the accessible
41  // id in |top_view|, and the child id requested, return the native
42  // accessible object with that child id from one of the WebViews in
43  // |top_view|'s view hierarchy, if any.
44  IAccessible* GetAccessibleFromWebView(View* top_view, long child_id);
45
46 private:
47  friend struct DefaultSingletonTraits<AccessibleWebViewRegistry>;
48  AccessibleWebViewRegistry();
49  ~AccessibleWebViewRegistry() {}
50
51  // Set of all web views. We check whether each one is contained in a
52  // top view dynamically rather than keeping track of a map.
53  std::set<AccessibleWebView*> web_views_;
54
55  // The most recent top view used in a call to GetAccessibleFromWebView.
56  View* last_top_view_;
57
58  // The most recent web view where an accessible object was found,
59  // corresponding to |last_top_view_|.
60  AccessibleWebView* last_web_view_;
61
62  DISALLOW_COPY_AND_ASSIGN(AccessibleWebViewRegistry);
63};
64
65AccessibleWebViewRegistry::AccessibleWebViewRegistry()
66    : last_top_view_(NULL),
67      last_web_view_(NULL) {
68}
69
70AccessibleWebViewRegistry* AccessibleWebViewRegistry::GetInstance() {
71  return Singleton<AccessibleWebViewRegistry>::get();
72}
73
74void AccessibleWebViewRegistry::RegisterWebView(AccessibleWebView* web_view) {
75  DCHECK(web_views_.find(web_view) == web_views_.end());
76  web_views_.insert(web_view);
77}
78
79void AccessibleWebViewRegistry::UnregisterWebView(AccessibleWebView* web_view) {
80  DCHECK(web_views_.find(web_view) != web_views_.end());
81  web_views_.erase(web_view);
82  if (last_web_view_ == web_view) {
83    last_top_view_ = NULL;
84    last_web_view_ = NULL;
85  }
86}
87
88IAccessible* AccessibleWebViewRegistry::GetAccessibleFromWebView(
89    View* top_view, long child_id) {
90  // This function gets called frequently, so try to avoid searching all
91  // of the web views if the notification is on the same web view that
92  // sent the last one.
93  if (last_top_view_ == top_view) {
94    IAccessible* accessible =
95        last_web_view_->AccessibleObjectFromChildId(child_id);
96    if (accessible)
97      return accessible;
98  }
99
100  // Search all web views. For each one, first ensure it's a descendant
101  // of this view where the event was posted - and if so, see if it owns
102  // an accessible object with that child id. If so, save the view to speed
103  // up the next notification.
104  for (std::set<AccessibleWebView*>::iterator iter = web_views_.begin();
105       iter != web_views_.end(); ++iter) {
106    AccessibleWebView* web_view = *iter;
107    if (!top_view->Contains(web_view->AsView()))
108      continue;
109    IAccessible* accessible = web_view->AccessibleObjectFromChildId(child_id);
110    if (accessible) {
111      last_top_view_ = top_view;
112      last_web_view_ = web_view;
113      return accessible;
114    }
115  }
116  return NULL;
117}
118
119}  // anonymous namespace
120
121// static
122long NativeViewAccessibilityWin::next_unique_id_ = 1;
123int NativeViewAccessibilityWin::view_storage_ids_[kMaxViewStorageIds] = {0};
124int NativeViewAccessibilityWin::next_view_storage_id_index_ = 0;
125
126// static
127NativeViewAccessibility* NativeViewAccessibility::Create(View* view) {
128  // Make sure ATL is initialized in this module.
129  ui::win::CreateATLModuleIfNeeded();
130
131  CComObject<NativeViewAccessibilityWin>* instance = NULL;
132  HRESULT hr = CComObject<NativeViewAccessibilityWin>::CreateInstance(
133      &instance);
134  DCHECK(SUCCEEDED(hr));
135  instance->set_view(view);
136  instance->AddRef();
137  return instance;
138}
139
140NativeViewAccessibilityWin::NativeViewAccessibilityWin()
141    : view_(NULL),
142      unique_id_(next_unique_id_++) {
143}
144
145NativeViewAccessibilityWin::~NativeViewAccessibilityWin() {
146}
147
148void NativeViewAccessibilityWin::NotifyAccessibilityEvent(
149    ui::AccessibilityTypes::Event event_type) {
150  if (!view_)
151    return;
152
153  ViewStorage* view_storage = ViewStorage::GetInstance();
154  HWND hwnd = HWNDForView(view_);
155  int view_storage_id = view_storage_ids_[next_view_storage_id_index_];
156  if (view_storage_id == 0) {
157    view_storage_id = view_storage->CreateStorageID();
158    view_storage_ids_[next_view_storage_id_index_] = view_storage_id;
159  } else {
160    view_storage->RemoveView(view_storage_id);
161  }
162  view_storage->StoreView(view_storage_id, view_);
163
164  // Positive child ids are used for enumerating direct children,
165  // negative child ids can be used as unique ids to refer to a specific
166  // descendants.  Make index into view_storage_ids_ into a negative child id.
167  int child_id =
168      base::win::kFirstViewsAccessibilityId - next_view_storage_id_index_;
169  ::NotifyWinEvent(MSAAEvent(event_type), hwnd, OBJID_CLIENT, child_id);
170  next_view_storage_id_index_ =
171      (next_view_storage_id_index_ + 1) % kMaxViewStorageIds;
172}
173
174gfx::NativeViewAccessible NativeViewAccessibilityWin::GetNativeObject() {
175  return this;
176}
177
178void NativeViewAccessibilityWin::Destroy() {
179  view_ = NULL;
180  Release();
181}
182
183// TODO(ctguil): Handle case where child View is not contained by parent.
184STDMETHODIMP NativeViewAccessibilityWin::accHitTest(
185    LONG x_left, LONG y_top, VARIANT* child) {
186  if (!child)
187    return E_INVALIDARG;
188
189  if (!view_)
190    return E_FAIL;
191
192  gfx::Point point(x_left, y_top);
193  View::ConvertPointToTarget(NULL, view_, &point);
194
195  if (!view_->HitTestPoint(point)) {
196    // If containing parent is not hit, return with failure.
197    child->vt = VT_EMPTY;
198    return S_FALSE;
199  }
200
201  View* view = view_->GetEventHandlerForPoint(point);
202  if (view == view_) {
203    // No child hit, return parent id.
204    child->vt = VT_I4;
205    child->lVal = CHILDID_SELF;
206  } else {
207    child->vt = VT_DISPATCH;
208    child->pdispVal = view->GetNativeViewAccessible();
209    child->pdispVal->AddRef();
210  }
211  return S_OK;
212}
213
214HRESULT NativeViewAccessibilityWin::accDoDefaultAction(VARIANT var_id) {
215  if (!IsValidId(var_id))
216    return E_INVALIDARG;
217
218  // The object does not support the method. This value is returned for
219  // controls that do not perform actions, such as edit fields.
220  return DISP_E_MEMBERNOTFOUND;
221}
222
223STDMETHODIMP NativeViewAccessibilityWin::accLocation(
224    LONG* x_left, LONG* y_top, LONG* width, LONG* height, VARIANT var_id) {
225  if (!IsValidId(var_id) || !x_left || !y_top || !width || !height)
226    return E_INVALIDARG;
227
228  if (!view_)
229    return E_FAIL;
230
231  if (!view_->bounds().IsEmpty()) {
232    *width  = view_->width();
233    *height = view_->height();
234    gfx::Point topleft(view_->bounds().origin());
235    View::ConvertPointToScreen(
236        view_->parent() ? view_->parent() : view_, &topleft);
237    *x_left = topleft.x();
238    *y_top  = topleft.y();
239  } else {
240    return E_FAIL;
241  }
242  return S_OK;
243}
244
245STDMETHODIMP NativeViewAccessibilityWin::accNavigate(
246    LONG nav_dir, VARIANT start, VARIANT* end) {
247  if (start.vt != VT_I4 || !end)
248    return E_INVALIDARG;
249
250  if (!view_)
251    return E_FAIL;
252
253  switch (nav_dir) {
254    case NAVDIR_FIRSTCHILD:
255    case NAVDIR_LASTCHILD: {
256      if (start.lVal != CHILDID_SELF) {
257        // Start of navigation must be on the View itself.
258        return E_INVALIDARG;
259      } else if (!view_->has_children()) {
260        // No children found.
261        return S_FALSE;
262      }
263
264      // Set child_id based on first or last child.
265      int child_id = 0;
266      if (nav_dir == NAVDIR_LASTCHILD)
267        child_id = view_->child_count() - 1;
268
269      View* child = view_->child_at(child_id);
270      end->vt = VT_DISPATCH;
271      end->pdispVal = child->GetNativeViewAccessible();
272      end->pdispVal->AddRef();
273      return S_OK;
274    }
275    case NAVDIR_LEFT:
276    case NAVDIR_UP:
277    case NAVDIR_PREVIOUS:
278    case NAVDIR_RIGHT:
279    case NAVDIR_DOWN:
280    case NAVDIR_NEXT: {
281      // Retrieve parent to access view index and perform bounds checking.
282      View* parent = view_->parent();
283      if (!parent) {
284        return E_FAIL;
285      }
286
287      if (start.lVal == CHILDID_SELF) {
288        int view_index = parent->GetIndexOf(view_);
289        // Check navigation bounds, adjusting for View child indexing (MSAA
290        // child indexing starts with 1, whereas View indexing starts with 0).
291        if (!IsValidNav(nav_dir, view_index, -1,
292                        parent->child_count() - 1)) {
293          // Navigation attempted to go out-of-bounds.
294          end->vt = VT_EMPTY;
295          return S_FALSE;
296        } else {
297          if (IsNavDirNext(nav_dir)) {
298            view_index += 1;
299          } else {
300            view_index -=1;
301          }
302        }
303
304        View* child = parent->child_at(view_index);
305        end->pdispVal = child->GetNativeViewAccessible();
306        end->vt = VT_DISPATCH;
307        end->pdispVal->AddRef();
308        return S_OK;
309      } else {
310        // Check navigation bounds, adjusting for MSAA child indexing (MSAA
311        // child indexing starts with 1, whereas View indexing starts with 0).
312        if (!IsValidNav(nav_dir, start.lVal, 0, parent->child_count() + 1)) {
313            // Navigation attempted to go out-of-bounds.
314            end->vt = VT_EMPTY;
315            return S_FALSE;
316          } else {
317            if (IsNavDirNext(nav_dir)) {
318              start.lVal += 1;
319            } else {
320              start.lVal -= 1;
321            }
322        }
323
324        HRESULT result = this->get_accChild(start, &end->pdispVal);
325        if (result == S_FALSE) {
326          // Child is a leaf.
327          end->vt = VT_I4;
328          end->lVal = start.lVal;
329        } else if (result == E_INVALIDARG) {
330          return E_INVALIDARG;
331        } else {
332          // Child is not a leaf.
333          end->vt = VT_DISPATCH;
334        }
335      }
336      break;
337    }
338    default:
339      return E_INVALIDARG;
340  }
341  // Navigation performed correctly. Global return for this function, if no
342  // error triggered an escape earlier.
343  return S_OK;
344}
345
346STDMETHODIMP NativeViewAccessibilityWin::get_accChild(VARIANT var_child,
347                                                      IDispatch** disp_child) {
348  if (var_child.vt != VT_I4 || !disp_child)
349    return E_INVALIDARG;
350
351  if (!view_)
352    return E_FAIL;
353
354  LONG child_id = V_I4(&var_child);
355
356  if (child_id == CHILDID_SELF) {
357    // Remain with the same dispatch.
358    return S_OK;
359  }
360
361  View* child_view = NULL;
362  if (child_id > 0) {
363    // Positive child ids are a 1-based child index, used by clients
364    // that want to enumerate all immediate children.
365    int child_id_as_index = child_id - 1;
366    if (child_id_as_index < view_->child_count())
367      child_view = view_->child_at(child_id_as_index);
368  } else {
369    // Negative child ids can be used to map to any descendant;
370    // we map child ids to a view storage id that can refer to a
371    // specific view (if that view still exists).
372    int view_storage_id_index =
373        base::win::kFirstViewsAccessibilityId - child_id;
374    if (view_storage_id_index >= 0 &&
375        view_storage_id_index < kMaxViewStorageIds) {
376      int view_storage_id = view_storage_ids_[view_storage_id_index];
377      ViewStorage* view_storage = ViewStorage::GetInstance();
378      child_view = view_storage->RetrieveView(view_storage_id);
379    } else {
380      *disp_child = AccessibleWebViewRegistry::GetInstance()->
381          GetAccessibleFromWebView(view_, child_id);
382      if (*disp_child) {
383        (*disp_child)->AddRef();
384        return S_OK;
385      }
386    }
387  }
388
389  if (!child_view) {
390    // No child found.
391    *disp_child = NULL;
392    return E_FAIL;
393  }
394
395  *disp_child = child_view->GetNativeViewAccessible();
396  (*disp_child)->AddRef();
397  return S_OK;
398}
399
400STDMETHODIMP NativeViewAccessibilityWin::get_accChildCount(LONG* child_count) {
401  if (!child_count || !view_)
402    return E_INVALIDARG;
403
404  if (!view_)
405    return E_FAIL;
406
407  *child_count = view_->child_count();
408  return S_OK;
409}
410
411STDMETHODIMP NativeViewAccessibilityWin::get_accDefaultAction(
412    VARIANT var_id, BSTR* def_action) {
413  if (!IsValidId(var_id) || !def_action)
414    return E_INVALIDARG;
415
416  if (!view_)
417    return E_FAIL;
418
419  ui::AccessibleViewState state;
420  view_->GetAccessibleState(&state);
421  string16 temp_action = state.default_action;
422
423  if (!temp_action.empty()) {
424    *def_action = SysAllocString(temp_action.c_str());
425  } else {
426    return S_FALSE;
427  }
428
429  return S_OK;
430}
431
432STDMETHODIMP NativeViewAccessibilityWin::get_accDescription(
433    VARIANT var_id, BSTR* desc) {
434  if (!IsValidId(var_id) || !desc)
435    return E_INVALIDARG;
436
437  if (!view_)
438    return E_FAIL;
439
440  string16 temp_desc;
441
442  view_->GetTooltipText(gfx::Point(), &temp_desc);
443  if (!temp_desc.empty()) {
444    *desc = SysAllocString(temp_desc.c_str());
445  } else {
446    return S_FALSE;
447  }
448
449  return S_OK;
450}
451
452STDMETHODIMP NativeViewAccessibilityWin::get_accFocus(VARIANT* focus_child) {
453  if (!focus_child)
454    return E_INVALIDARG;
455
456  if (!view_)
457    return E_FAIL;
458
459  FocusManager* focus_manager = view_->GetFocusManager();
460  View* focus = focus_manager ? focus_manager->GetFocusedView() : NULL;
461  if (focus == view_) {
462    // This view has focus.
463    focus_child->vt = VT_I4;
464    focus_child->lVal = CHILDID_SELF;
465  } else if (focus && view_->Contains(focus)) {
466    // Return the child object that has the keyboard focus.
467    focus_child->vt = VT_DISPATCH;
468    focus_child->pdispVal = focus->GetNativeViewAccessible();
469    focus_child->pdispVal->AddRef();
470    return S_OK;
471  } else {
472    // Neither this object nor any of its children has the keyboard focus.
473    focus_child->vt = VT_EMPTY;
474  }
475  return S_OK;
476}
477
478STDMETHODIMP NativeViewAccessibilityWin::get_accKeyboardShortcut(
479    VARIANT var_id, BSTR* acc_key) {
480  if (!IsValidId(var_id) || !acc_key)
481    return E_INVALIDARG;
482
483  if (!view_)
484    return E_FAIL;
485
486  ui::AccessibleViewState state;
487  view_->GetAccessibleState(&state);
488  string16 temp_key = state.keyboard_shortcut;
489
490  if (!temp_key.empty()) {
491    *acc_key = SysAllocString(temp_key.c_str());
492  } else {
493    return S_FALSE;
494  }
495
496  return S_OK;
497}
498
499STDMETHODIMP NativeViewAccessibilityWin::get_accName(
500    VARIANT var_id, BSTR* name) {
501  if (!IsValidId(var_id) || !name)
502    return E_INVALIDARG;
503
504  if (!view_)
505    return E_FAIL;
506
507  // Retrieve the current view's name.
508  ui::AccessibleViewState state;
509  view_->GetAccessibleState(&state);
510  string16 temp_name = state.name;
511  if (!temp_name.empty()) {
512    // Return name retrieved.
513    *name = SysAllocString(temp_name.c_str());
514  } else {
515    // If view has no name, return S_FALSE.
516    return S_FALSE;
517  }
518
519  return S_OK;
520}
521
522STDMETHODIMP NativeViewAccessibilityWin::get_accParent(
523    IDispatch** disp_parent) {
524  if (!disp_parent)
525    return E_INVALIDARG;
526
527  if (!view_)
528    return E_FAIL;
529
530  *disp_parent = NULL;
531  View* parent_view = view_->parent();
532
533  if (!parent_view) {
534    HWND hwnd = HWNDForView(view_);
535    if (!hwnd)
536      return S_FALSE;
537
538    return ::AccessibleObjectFromWindow(
539        hwnd, OBJID_WINDOW, IID_IAccessible,
540        reinterpret_cast<void**>(disp_parent));
541  }
542
543  *disp_parent = parent_view->GetNativeViewAccessible();
544  (*disp_parent)->AddRef();
545  return S_OK;
546}
547
548STDMETHODIMP NativeViewAccessibilityWin::get_accRole(
549    VARIANT var_id, VARIANT* role) {
550  if (!IsValidId(var_id) || !role)
551    return E_INVALIDARG;
552
553  if (!view_)
554    return E_FAIL;
555
556  ui::AccessibleViewState state;
557  view_->GetAccessibleState(&state);
558  role->vt = VT_I4;
559  role->lVal = MSAARole(state.role);
560  return S_OK;
561}
562
563STDMETHODIMP NativeViewAccessibilityWin::get_accState(
564    VARIANT var_id, VARIANT* state) {
565  // This returns MSAA states. See also the IAccessible2 interface
566  // get_states().
567
568  if (!IsValidId(var_id) || !state)
569    return E_INVALIDARG;
570
571  if (!view_)
572    return E_FAIL;
573
574  state->vt = VT_I4;
575
576  // Retrieve all currently applicable states of the parent.
577  SetState(state, view_);
578
579  // Make sure that state is not empty, and has the proper type.
580  if (state->vt == VT_EMPTY)
581    return E_FAIL;
582
583  return S_OK;
584}
585
586STDMETHODIMP NativeViewAccessibilityWin::get_accValue(
587    VARIANT var_id, BSTR* value) {
588  if (!IsValidId(var_id) || !value)
589    return E_INVALIDARG;
590
591  if (!view_)
592    return E_FAIL;
593
594  // Retrieve the current view's value.
595  ui::AccessibleViewState state;
596  view_->GetAccessibleState(&state);
597  string16 temp_value = state.value;
598
599  if (!temp_value.empty()) {
600    // Return value retrieved.
601    *value = SysAllocString(temp_value.c_str());
602  } else {
603    // If view has no value, fall back into the default implementation.
604    *value = NULL;
605    return E_NOTIMPL;
606  }
607
608  return S_OK;
609}
610
611// IAccessible functions not supported.
612
613STDMETHODIMP NativeViewAccessibilityWin::get_accSelection(VARIANT* selected) {
614  if (selected)
615    selected->vt = VT_EMPTY;
616  return E_NOTIMPL;
617}
618
619STDMETHODIMP NativeViewAccessibilityWin::accSelect(
620    LONG flagsSelect, VARIANT var_id) {
621  return E_NOTIMPL;
622}
623
624STDMETHODIMP NativeViewAccessibilityWin::get_accHelp(
625    VARIANT var_id, BSTR* help) {
626  if (help)
627    *help = NULL;
628  return E_NOTIMPL;
629}
630
631STDMETHODIMP NativeViewAccessibilityWin::get_accHelpTopic(
632    BSTR* help_file, VARIANT var_id, LONG* topic_id) {
633  if (help_file) {
634    *help_file = NULL;
635  }
636  if (topic_id) {
637    *topic_id = static_cast<LONG>(-1);
638  }
639  return E_NOTIMPL;
640}
641
642STDMETHODIMP NativeViewAccessibilityWin::put_accName(
643    VARIANT var_id, BSTR put_name) {
644  // Deprecated.
645  return E_NOTIMPL;
646}
647
648STDMETHODIMP NativeViewAccessibilityWin::put_accValue(
649    VARIANT var_id, BSTR put_val) {
650  // Deprecated.
651  return E_NOTIMPL;
652}
653
654//
655// IAccessible2
656//
657
658STDMETHODIMP NativeViewAccessibilityWin::role(LONG* role) {
659  if (!view_)
660    return E_FAIL;
661
662  if (!role)
663    return E_INVALIDARG;
664
665  ui::AccessibleViewState state;
666  view_->GetAccessibleState(&state);
667  *role = MSAARole(state.role);
668  return S_OK;
669}
670
671STDMETHODIMP NativeViewAccessibilityWin::get_states(AccessibleStates* states) {
672  // This returns IAccessible2 states, which supplement MSAA states.
673  // See also the MSAA interface get_accState.
674
675  if (!view_)
676    return E_FAIL;
677
678  if (!states)
679    return E_INVALIDARG;
680
681  ui::AccessibleViewState state;
682  view_->GetAccessibleState(&state);
683
684  // There are only a couple of states we need to support
685  // in IAccessible2. If any more are added, we may want to
686  // add a helper function like MSAAState.
687  *states = IA2_STATE_OPAQUE;
688  if (state.state & AccessibilityTypes::STATE_EDITABLE)
689    *states |= IA2_STATE_EDITABLE;
690
691  return S_OK;
692}
693
694STDMETHODIMP NativeViewAccessibilityWin::get_uniqueID(LONG* unique_id) {
695  if (!view_)
696    return E_FAIL;
697
698  if (!unique_id)
699    return E_INVALIDARG;
700
701  *unique_id = unique_id_;
702  return S_OK;
703}
704
705STDMETHODIMP NativeViewAccessibilityWin::get_windowHandle(HWND* window_handle) {
706  if (!view_)
707    return E_FAIL;
708
709  if (!window_handle)
710    return E_INVALIDARG;
711
712  *window_handle = HWNDForView(view_);
713  return *window_handle ? S_OK : S_FALSE;
714}
715
716//
717// IAccessibleText
718//
719
720STDMETHODIMP NativeViewAccessibilityWin::get_nCharacters(LONG* n_characters) {
721  if (!view_)
722    return E_FAIL;
723
724  if (!n_characters)
725    return E_INVALIDARG;
726
727  string16 text = TextForIAccessibleText();
728  *n_characters = static_cast<LONG>(text.size());
729  return S_OK;
730}
731
732STDMETHODIMP NativeViewAccessibilityWin::get_caretOffset(LONG* offset) {
733  if (!view_)
734    return E_FAIL;
735
736  if (!offset)
737    return E_INVALIDARG;
738
739  ui::AccessibleViewState state;
740  view_->GetAccessibleState(&state);
741  *offset = static_cast<LONG>(state.selection_end);
742  return S_OK;
743}
744
745STDMETHODIMP NativeViewAccessibilityWin::get_nSelections(LONG* n_selections) {
746  if (!view_)
747    return E_FAIL;
748
749  if (!n_selections)
750    return E_INVALIDARG;
751
752  ui::AccessibleViewState state;
753  view_->GetAccessibleState(&state);
754  if (state.selection_start != state.selection_end)
755    *n_selections = 1;
756  else
757    *n_selections = 0;
758  return S_OK;
759}
760
761STDMETHODIMP NativeViewAccessibilityWin::get_selection(LONG selection_index,
762                                                      LONG* start_offset,
763                                                      LONG* end_offset) {
764  if (!view_)
765    return E_FAIL;
766
767  if (!start_offset || !end_offset || selection_index != 0)
768    return E_INVALIDARG;
769
770  ui::AccessibleViewState state;
771  view_->GetAccessibleState(&state);
772  *start_offset = static_cast<LONG>(state.selection_start);
773  *end_offset = static_cast<LONG>(state.selection_end);
774  return S_OK;
775}
776
777STDMETHODIMP NativeViewAccessibilityWin::get_text(LONG start_offset,
778                                                  LONG end_offset,
779                                                  BSTR* text) {
780  if (!view_)
781    return E_FAIL;
782
783  ui::AccessibleViewState state;
784  view_->GetAccessibleState(&state);
785  string16 text_str = TextForIAccessibleText();
786  LONG len = static_cast<LONG>(text_str.size());
787
788  if (start_offset == IA2_TEXT_OFFSET_LENGTH) {
789    start_offset = len;
790  } else if (start_offset == IA2_TEXT_OFFSET_CARET) {
791    start_offset = static_cast<LONG>(state.selection_end);
792  }
793  if (end_offset == IA2_TEXT_OFFSET_LENGTH) {
794    end_offset = static_cast<LONG>(text_str.size());
795  } else if (end_offset == IA2_TEXT_OFFSET_CARET) {
796    end_offset = static_cast<LONG>(state.selection_end);
797  }
798
799  // The spec allows the arguments to be reversed.
800  if (start_offset > end_offset) {
801    LONG tmp = start_offset;
802    start_offset = end_offset;
803    end_offset = tmp;
804  }
805
806  // The spec does not allow the start or end offsets to be out or range;
807  // we must return an error if so.
808  if (start_offset < 0)
809    return E_INVALIDARG;
810  if (end_offset > len)
811    return E_INVALIDARG;
812
813  string16 substr = text_str.substr(start_offset, end_offset - start_offset);
814  if (substr.empty())
815    return S_FALSE;
816
817  *text = SysAllocString(substr.c_str());
818  DCHECK(*text);
819  return S_OK;
820}
821
822STDMETHODIMP NativeViewAccessibilityWin::get_textAtOffset(
823    LONG offset,
824    enum IA2TextBoundaryType boundary_type,
825    LONG* start_offset, LONG* end_offset,
826    BSTR* text) {
827  if (!start_offset || !end_offset || !text)
828    return E_INVALIDARG;
829
830  // The IAccessible2 spec says we don't have to implement the "sentence"
831  // boundary type, we can just let the screenreader handle it.
832  if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) {
833    *start_offset = 0;
834    *end_offset = 0;
835    *text = NULL;
836    return S_FALSE;
837  }
838
839  const string16& text_str = TextForIAccessibleText();
840
841  *start_offset = FindBoundary(
842      text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION);
843  *end_offset = FindBoundary(
844      text_str, boundary_type, offset, ui::FORWARDS_DIRECTION);
845  return get_text(*start_offset, *end_offset, text);
846}
847
848STDMETHODIMP NativeViewAccessibilityWin::get_textBeforeOffset(
849    LONG offset,
850    enum IA2TextBoundaryType boundary_type,
851    LONG* start_offset, LONG* end_offset,
852    BSTR* text) {
853  if (!start_offset || !end_offset || !text)
854    return E_INVALIDARG;
855
856  // The IAccessible2 spec says we don't have to implement the "sentence"
857  // boundary type, we can just let the screenreader handle it.
858  if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) {
859    *start_offset = 0;
860    *end_offset = 0;
861    *text = NULL;
862    return S_FALSE;
863  }
864
865  const string16& text_str = TextForIAccessibleText();
866
867  *start_offset = FindBoundary(
868      text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION);
869  *end_offset = offset;
870  return get_text(*start_offset, *end_offset, text);
871}
872
873STDMETHODIMP NativeViewAccessibilityWin::get_textAfterOffset(
874    LONG offset,
875    enum IA2TextBoundaryType boundary_type,
876    LONG* start_offset, LONG* end_offset,
877    BSTR* text) {
878  if (!start_offset || !end_offset || !text)
879    return E_INVALIDARG;
880
881  // The IAccessible2 spec says we don't have to implement the "sentence"
882  // boundary type, we can just let the screenreader handle it.
883  if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) {
884    *start_offset = 0;
885    *end_offset = 0;
886    *text = NULL;
887    return S_FALSE;
888  }
889
890  const string16& text_str = TextForIAccessibleText();
891
892  *start_offset = offset;
893  *end_offset = FindBoundary(
894      text_str, boundary_type, offset, ui::FORWARDS_DIRECTION);
895  return get_text(*start_offset, *end_offset, text);
896}
897
898STDMETHODIMP NativeViewAccessibilityWin::get_offsetAtPoint(
899    LONG x, LONG y, enum IA2CoordinateType coord_type, LONG* offset) {
900  if (!view_)
901    return E_FAIL;
902
903  if (!offset)
904    return E_INVALIDARG;
905
906  // We don't support this method, but we have to return something
907  // rather than E_NOTIMPL or screen readers will complain.
908  *offset = 0;
909  return S_OK;
910}
911
912//
913// IServiceProvider methods.
914//
915
916STDMETHODIMP NativeViewAccessibilityWin::QueryService(
917    REFGUID guidService, REFIID riid, void** object) {
918  if (!view_)
919    return E_FAIL;
920
921  if (guidService == IID_IAccessible ||
922      guidService == IID_IAccessible2 ||
923      guidService == IID_IAccessibleText)  {
924    return QueryInterface(riid, object);
925  }
926
927  // We only support the IAccessibleEx interface on Windows 8 and above. This
928  // is needed for the On screen Keyboard to show up in metro mode, when the
929  // user taps an editable region in the window.
930  // All methods in the IAccessibleEx interface are unimplemented.
931  if (riid == IID_IAccessibleEx &&
932      base::win::GetVersion() >= base::win::VERSION_WIN8) {
933    return QueryInterface(riid, object);
934  }
935
936  *object = NULL;
937  return E_FAIL;
938}
939
940STDMETHODIMP NativeViewAccessibilityWin::GetPatternProvider(
941    PATTERNID id, IUnknown** provider) {
942  DVLOG(1) << "In Function: "
943           << __FUNCTION__
944           << " for pattern id: "
945           << id;
946  if (id == UIA_ValuePatternId || id == UIA_TextPatternId) {
947    ui::AccessibleViewState state;
948    view_->GetAccessibleState(&state);
949    long role = MSAARole(state.role);
950
951    if (role == ROLE_SYSTEM_TEXT) {
952      DVLOG(1) << "Returning UIA text provider";
953      base::win::UIATextProvider::CreateTextProvider(true, provider);
954      return S_OK;
955    }
956  }
957  return E_NOTIMPL;
958}
959
960STDMETHODIMP NativeViewAccessibilityWin::GetPropertyValue(PROPERTYID id,
961                                                          VARIANT* ret) {
962  DVLOG(1) << "In Function: "
963           << __FUNCTION__
964           << " for property id: "
965           << id;
966  if (id == UIA_ControlTypePropertyId) {
967    ui::AccessibleViewState state;
968    view_->GetAccessibleState(&state);
969    long role = MSAARole(state.role);
970    if (role == ROLE_SYSTEM_TEXT) {
971      V_VT(ret) = VT_I4;
972      ret->lVal = UIA_EditControlTypeId;
973      DVLOG(1) << "Returning Edit control type";
974    } else {
975      DVLOG(1) << "Returning empty control type";
976      V_VT(ret) = VT_EMPTY;
977    }
978  } else {
979    V_VT(ret) = VT_EMPTY;
980  }
981  return S_OK;
982}
983
984//
985// Static methods.
986//
987
988void NativeViewAccessibility::RegisterWebView(AccessibleWebView* web_view) {
989  AccessibleWebViewRegistry::GetInstance()->RegisterWebView(web_view);
990}
991
992void NativeViewAccessibility::UnregisterWebView(AccessibleWebView* web_view) {
993  AccessibleWebViewRegistry::GetInstance()->UnregisterWebView(web_view);
994}
995
996int32 NativeViewAccessibilityWin::MSAAEvent(AccessibilityTypes::Event event) {
997  switch (event) {
998    case AccessibilityTypes::EVENT_ALERT:
999      return EVENT_SYSTEM_ALERT;
1000    case AccessibilityTypes::EVENT_FOCUS:
1001      return EVENT_OBJECT_FOCUS;
1002    case AccessibilityTypes::EVENT_MENUSTART:
1003      return EVENT_SYSTEM_MENUSTART;
1004    case AccessibilityTypes::EVENT_MENUEND:
1005      return EVENT_SYSTEM_MENUEND;
1006    case AccessibilityTypes::EVENT_MENUPOPUPSTART:
1007      return EVENT_SYSTEM_MENUPOPUPSTART;
1008    case AccessibilityTypes::EVENT_MENUPOPUPEND:
1009      return EVENT_SYSTEM_MENUPOPUPEND;
1010    case AccessibilityTypes::EVENT_NAME_CHANGED:
1011      return EVENT_OBJECT_NAMECHANGE;
1012    case AccessibilityTypes::EVENT_TEXT_CHANGED:
1013      return EVENT_OBJECT_VALUECHANGE;
1014    case AccessibilityTypes::EVENT_SELECTION_CHANGED:
1015      return IA2_EVENT_TEXT_CARET_MOVED;
1016    case AccessibilityTypes::EVENT_VALUE_CHANGED:
1017      return EVENT_OBJECT_VALUECHANGE;
1018    default:
1019      // Not supported or invalid event.
1020      NOTREACHED();
1021      return -1;
1022  }
1023}
1024
1025int32 NativeViewAccessibilityWin::MSAARole(AccessibilityTypes::Role role) {
1026  switch (role) {
1027    case AccessibilityTypes::ROLE_ALERT:
1028      return ROLE_SYSTEM_ALERT;
1029    case AccessibilityTypes::ROLE_APPLICATION:
1030      return ROLE_SYSTEM_APPLICATION;
1031    case AccessibilityTypes::ROLE_BUTTONDROPDOWN:
1032      return ROLE_SYSTEM_BUTTONDROPDOWN;
1033    case AccessibilityTypes::ROLE_BUTTONMENU:
1034      return ROLE_SYSTEM_BUTTONMENU;
1035    case AccessibilityTypes::ROLE_CHECKBUTTON:
1036      return ROLE_SYSTEM_CHECKBUTTON;
1037    case AccessibilityTypes::ROLE_COMBOBOX:
1038      return ROLE_SYSTEM_COMBOBOX;
1039    case AccessibilityTypes::ROLE_DIALOG:
1040      return ROLE_SYSTEM_DIALOG;
1041    case AccessibilityTypes::ROLE_GRAPHIC:
1042      return ROLE_SYSTEM_GRAPHIC;
1043    case AccessibilityTypes::ROLE_GROUPING:
1044      return ROLE_SYSTEM_GROUPING;
1045    case AccessibilityTypes::ROLE_LINK:
1046      return ROLE_SYSTEM_LINK;
1047    case AccessibilityTypes::ROLE_LOCATION_BAR:
1048      return ROLE_SYSTEM_GROUPING;
1049    case AccessibilityTypes::ROLE_MENUBAR:
1050      return ROLE_SYSTEM_MENUBAR;
1051    case AccessibilityTypes::ROLE_MENUITEM:
1052      return ROLE_SYSTEM_MENUITEM;
1053    case AccessibilityTypes::ROLE_MENUPOPUP:
1054      return ROLE_SYSTEM_MENUPOPUP;
1055    case AccessibilityTypes::ROLE_OUTLINE:
1056      return ROLE_SYSTEM_OUTLINE;
1057    case AccessibilityTypes::ROLE_OUTLINEITEM:
1058      return ROLE_SYSTEM_OUTLINEITEM;
1059    case AccessibilityTypes::ROLE_PAGETAB:
1060      return ROLE_SYSTEM_PAGETAB;
1061    case AccessibilityTypes::ROLE_PAGETABLIST:
1062      return ROLE_SYSTEM_PAGETABLIST;
1063    case AccessibilityTypes::ROLE_PANE:
1064      return ROLE_SYSTEM_PANE;
1065    case AccessibilityTypes::ROLE_PROGRESSBAR:
1066      return ROLE_SYSTEM_PROGRESSBAR;
1067    case AccessibilityTypes::ROLE_PUSHBUTTON:
1068      return ROLE_SYSTEM_PUSHBUTTON;
1069    case AccessibilityTypes::ROLE_RADIOBUTTON:
1070      return ROLE_SYSTEM_RADIOBUTTON;
1071    case AccessibilityTypes::ROLE_SCROLLBAR:
1072      return ROLE_SYSTEM_SCROLLBAR;
1073    case AccessibilityTypes::ROLE_SEPARATOR:
1074      return ROLE_SYSTEM_SEPARATOR;
1075    case AccessibilityTypes::ROLE_SLIDER:
1076      return ROLE_SYSTEM_SLIDER;
1077    case AccessibilityTypes::ROLE_STATICTEXT:
1078      return ROLE_SYSTEM_STATICTEXT;
1079    case AccessibilityTypes::ROLE_TEXT:
1080      return ROLE_SYSTEM_TEXT;
1081    case AccessibilityTypes::ROLE_TITLEBAR:
1082      return ROLE_SYSTEM_TITLEBAR;
1083    case AccessibilityTypes::ROLE_TOOLBAR:
1084      return ROLE_SYSTEM_TOOLBAR;
1085    case AccessibilityTypes::ROLE_WINDOW:
1086      return ROLE_SYSTEM_WINDOW;
1087    case AccessibilityTypes::ROLE_CLIENT:
1088    default:
1089      // This is the default role for MSAA.
1090      return ROLE_SYSTEM_CLIENT;
1091  }
1092}
1093
1094int32 NativeViewAccessibilityWin::MSAAState(AccessibilityTypes::State state) {
1095  // This maps MSAA states for get_accState(). See also the IAccessible2
1096  // interface get_states().
1097
1098  int32 msaa_state = 0;
1099  if (state & AccessibilityTypes::STATE_CHECKED)
1100    msaa_state |= STATE_SYSTEM_CHECKED;
1101  if (state & AccessibilityTypes::STATE_COLLAPSED)
1102    msaa_state |= STATE_SYSTEM_COLLAPSED;
1103  if (state & AccessibilityTypes::STATE_DEFAULT)
1104    msaa_state |= STATE_SYSTEM_DEFAULT;
1105  if (state & AccessibilityTypes::STATE_EXPANDED)
1106    msaa_state |= STATE_SYSTEM_EXPANDED;
1107  if (state & AccessibilityTypes::STATE_HASPOPUP)
1108    msaa_state |= STATE_SYSTEM_HASPOPUP;
1109  if (state & AccessibilityTypes::STATE_HOTTRACKED)
1110    msaa_state |= STATE_SYSTEM_HOTTRACKED;
1111  if (state & AccessibilityTypes::STATE_INVISIBLE)
1112    msaa_state |= STATE_SYSTEM_INVISIBLE;
1113  if (state & AccessibilityTypes::STATE_LINKED)
1114    msaa_state |= STATE_SYSTEM_LINKED;
1115  if (state & AccessibilityTypes::STATE_OFFSCREEN)
1116    msaa_state |= STATE_SYSTEM_OFFSCREEN;
1117  if (state & AccessibilityTypes::STATE_PRESSED)
1118    msaa_state |= STATE_SYSTEM_PRESSED;
1119  if (state & AccessibilityTypes::STATE_PROTECTED)
1120    msaa_state |= STATE_SYSTEM_PROTECTED;
1121  if (state & AccessibilityTypes::STATE_READONLY)
1122    msaa_state |= STATE_SYSTEM_READONLY;
1123  if (state & AccessibilityTypes::STATE_SELECTED)
1124    msaa_state |= STATE_SYSTEM_SELECTED;
1125  if (state & AccessibilityTypes::STATE_FOCUSED)
1126    msaa_state |= STATE_SYSTEM_FOCUSED;
1127  if (state & AccessibilityTypes::STATE_UNAVAILABLE)
1128    msaa_state |= STATE_SYSTEM_UNAVAILABLE;
1129  return msaa_state;
1130}
1131
1132//
1133// Private methods.
1134//
1135
1136bool NativeViewAccessibilityWin::IsNavDirNext(int nav_dir) const {
1137  return (nav_dir == NAVDIR_RIGHT ||
1138          nav_dir == NAVDIR_DOWN ||
1139          nav_dir == NAVDIR_NEXT);
1140}
1141
1142bool NativeViewAccessibilityWin::IsValidNav(
1143    int nav_dir, int start_id, int lower_bound, int upper_bound) const {
1144  if (IsNavDirNext(nav_dir)) {
1145    if ((start_id + 1) > upper_bound) {
1146      return false;
1147    }
1148  } else {
1149    if ((start_id - 1) <= lower_bound) {
1150      return false;
1151    }
1152  }
1153  return true;
1154}
1155
1156bool NativeViewAccessibilityWin::IsValidId(const VARIANT& child) const {
1157  // View accessibility returns an IAccessible for each view so we only support
1158  // the CHILDID_SELF id.
1159  return (VT_I4 == child.vt) && (CHILDID_SELF == child.lVal);
1160}
1161
1162void NativeViewAccessibilityWin::SetState(
1163    VARIANT* msaa_state, View* view) {
1164  // Ensure the output param is initialized to zero.
1165  msaa_state->lVal = 0;
1166
1167  // Default state; all views can have accessibility focus.
1168  msaa_state->lVal |= STATE_SYSTEM_FOCUSABLE;
1169
1170  if (!view)
1171    return;
1172
1173  if (!view->enabled())
1174    msaa_state->lVal |= STATE_SYSTEM_UNAVAILABLE;
1175  if (!view->visible())
1176    msaa_state->lVal |= STATE_SYSTEM_INVISIBLE;
1177  if (!strcmp(view->GetClassName(), CustomButton::kViewClassName)) {
1178    CustomButton* button = static_cast<CustomButton*>(view);
1179    if (button->IsHotTracked())
1180      msaa_state->lVal |= STATE_SYSTEM_HOTTRACKED;
1181  }
1182  if (view->HasFocus())
1183    msaa_state->lVal |= STATE_SYSTEM_FOCUSED;
1184
1185  // Add on any view-specific states.
1186  ui::AccessibleViewState view_state;
1187  view->GetAccessibleState(&view_state);
1188  msaa_state->lVal |= MSAAState(view_state.state);
1189}
1190
1191string16 NativeViewAccessibilityWin::TextForIAccessibleText() {
1192  ui::AccessibleViewState state;
1193  view_->GetAccessibleState(&state);
1194  if (state.role == AccessibilityTypes::ROLE_TEXT)
1195    return state.value;
1196  else
1197    return state.name;
1198}
1199
1200void NativeViewAccessibilityWin::HandleSpecialTextOffset(
1201    const string16& text, LONG* offset) {
1202  if (*offset == IA2_TEXT_OFFSET_LENGTH) {
1203    *offset = static_cast<LONG>(text.size());
1204  } else if (*offset == IA2_TEXT_OFFSET_CARET) {
1205    get_caretOffset(offset);
1206  }
1207}
1208
1209ui::TextBoundaryType NativeViewAccessibilityWin::IA2TextBoundaryToTextBoundary(
1210    IA2TextBoundaryType ia2_boundary) {
1211  switch(ia2_boundary) {
1212    case IA2_TEXT_BOUNDARY_CHAR: return ui::CHAR_BOUNDARY;
1213    case IA2_TEXT_BOUNDARY_WORD: return ui::WORD_BOUNDARY;
1214    case IA2_TEXT_BOUNDARY_LINE: return ui::LINE_BOUNDARY;
1215    case IA2_TEXT_BOUNDARY_SENTENCE: return ui::SENTENCE_BOUNDARY;
1216    case IA2_TEXT_BOUNDARY_PARAGRAPH: return ui::PARAGRAPH_BOUNDARY;
1217    case IA2_TEXT_BOUNDARY_ALL: return ui::ALL_BOUNDARY;
1218    default:
1219      NOTREACHED();
1220      return ui::CHAR_BOUNDARY;
1221  }
1222}
1223
1224LONG NativeViewAccessibilityWin::FindBoundary(
1225    const string16& text,
1226    IA2TextBoundaryType ia2_boundary,
1227    LONG start_offset,
1228    ui::TextBoundaryDirection direction) {
1229  HandleSpecialTextOffset(text, &start_offset);
1230  ui::TextBoundaryType boundary = IA2TextBoundaryToTextBoundary(ia2_boundary);
1231  std::vector<int32> line_breaks;
1232  return ui::FindAccessibleTextBoundary(
1233      text, line_breaks, boundary, start_offset, direction);
1234}
1235
1236}  // namespace views
1237