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