| OLD | NEW |
| (Empty) |
| 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 "chrome/browser/ui/views/accessibility_event_router_views.h" | |
| 6 | |
| 7 #include "base/basictypes.h" | |
| 8 #include "base/callback.h" | |
| 9 #include "base/memory/singleton.h" | |
| 10 #include "base/message_loop.h" | |
| 11 #include "base/utf_string_conversions.h" | |
| 12 #include "chrome/browser/accessibility/accessibility_extension_api.h" | |
| 13 #include "chrome/browser/browser_process.h" | |
| 14 #include "chrome/browser/profiles/profile.h" | |
| 15 #include "chrome/browser/profiles/profile_manager.h" | |
| 16 #include "chrome/common/chrome_notification_types.h" | |
| 17 #include "ui/base/accessibility/accessible_view_state.h" | |
| 18 #include "ui/views/controls/button/text_button.h" | |
| 19 #include "ui/views/controls/menu/menu_item_view.h" | |
| 20 #include "ui/views/controls/menu/submenu_view.h" | |
| 21 #include "ui/views/view.h" | |
| 22 #include "ui/views/widget/widget.h" | |
| 23 | |
| 24 using views::FocusManager; | |
| 25 | |
| 26 AccessibilityEventRouterViews::AccessibilityEventRouterViews() | |
| 27 : most_recent_profile_(NULL) { | |
| 28 } | |
| 29 | |
| 30 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() { | |
| 31 } | |
| 32 | |
| 33 // static | |
| 34 AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() { | |
| 35 return Singleton<AccessibilityEventRouterViews>::get(); | |
| 36 } | |
| 37 | |
| 38 void AccessibilityEventRouterViews::HandleAccessibilityEvent( | |
| 39 views::View* view, ui::AccessibilityTypes::Event event_type) { | |
| 40 if (!ExtensionAccessibilityEventRouter::GetInstance()-> | |
| 41 IsAccessibilityEnabled()) { | |
| 42 return; | |
| 43 } | |
| 44 | |
| 45 switch (event_type) { | |
| 46 case ui::AccessibilityTypes::EVENT_FOCUS: | |
| 47 DispatchAccessibilityNotification( | |
| 48 view, chrome::NOTIFICATION_ACCESSIBILITY_CONTROL_FOCUSED); | |
| 49 break; | |
| 50 case ui::AccessibilityTypes::EVENT_MENUSTART: | |
| 51 case ui::AccessibilityTypes::EVENT_MENUPOPUPSTART: | |
| 52 DispatchAccessibilityNotification( | |
| 53 view, chrome::NOTIFICATION_ACCESSIBILITY_MENU_OPENED); | |
| 54 break; | |
| 55 case ui::AccessibilityTypes::EVENT_MENUEND: | |
| 56 case ui::AccessibilityTypes::EVENT_MENUPOPUPEND: | |
| 57 DispatchAccessibilityNotification( | |
| 58 view, chrome::NOTIFICATION_ACCESSIBILITY_MENU_CLOSED); | |
| 59 break; | |
| 60 case ui::AccessibilityTypes::EVENT_TEXT_CHANGED: | |
| 61 case ui::AccessibilityTypes::EVENT_SELECTION_CHANGED: | |
| 62 DispatchAccessibilityNotification( | |
| 63 view, chrome::NOTIFICATION_ACCESSIBILITY_TEXT_CHANGED); | |
| 64 break; | |
| 65 case ui::AccessibilityTypes::EVENT_VALUE_CHANGED: | |
| 66 DispatchAccessibilityNotification( | |
| 67 view, chrome::NOTIFICATION_ACCESSIBILITY_CONTROL_ACTION); | |
| 68 break; | |
| 69 case ui::AccessibilityTypes::EVENT_ALERT: | |
| 70 DispatchAccessibilityNotification( | |
| 71 view, chrome::NOTIFICATION_ACCESSIBILITY_WINDOW_OPENED); | |
| 72 break; | |
| 73 case ui::AccessibilityTypes::EVENT_NAME_CHANGED: | |
| 74 NOTIMPLEMENTED(); | |
| 75 break; | |
| 76 } | |
| 77 } | |
| 78 | |
| 79 void AccessibilityEventRouterViews::HandleMenuItemFocused( | |
| 80 const string16& menu_name, | |
| 81 const string16& menu_item_name, | |
| 82 int item_index, | |
| 83 int item_count, | |
| 84 bool has_submenu) { | |
| 85 if (!ExtensionAccessibilityEventRouter::GetInstance()-> | |
| 86 IsAccessibilityEnabled()) { | |
| 87 return; | |
| 88 } | |
| 89 | |
| 90 if (!most_recent_profile_) | |
| 91 return; | |
| 92 | |
| 93 AccessibilityMenuItemInfo info(most_recent_profile_, | |
| 94 UTF16ToUTF8(menu_item_name), | |
| 95 UTF16ToUTF8(menu_name), | |
| 96 has_submenu, | |
| 97 item_index, | |
| 98 item_count); | |
| 99 SendAccessibilityNotification( | |
| 100 chrome::NOTIFICATION_ACCESSIBILITY_CONTROL_FOCUSED, &info); | |
| 101 } | |
| 102 | |
| 103 // | |
| 104 // Private methods | |
| 105 // | |
| 106 | |
| 107 void AccessibilityEventRouterViews::DispatchAccessibilityNotification( | |
| 108 views::View* view, int type) { | |
| 109 // Get the profile associated with this view. If it's not found, use | |
| 110 // the most recent profile where accessibility events were sent, or | |
| 111 // the default profile. | |
| 112 Profile* profile = NULL; | |
| 113 views::Widget* widget = view->GetWidget(); | |
| 114 if (widget) { | |
| 115 profile = reinterpret_cast<Profile*>( | |
| 116 widget->GetNativeWindowProperty(Profile::kProfileKey)); | |
| 117 } | |
| 118 if (!profile) | |
| 119 profile = most_recent_profile_; | |
| 120 if (!profile) | |
| 121 profile = g_browser_process->profile_manager()->GetLastUsedProfile(); | |
| 122 if (!profile) { | |
| 123 NOTREACHED(); | |
| 124 return; | |
| 125 } | |
| 126 | |
| 127 most_recent_profile_ = profile; | |
| 128 | |
| 129 if (type == chrome::NOTIFICATION_ACCESSIBILITY_MENU_OPENED || | |
| 130 type == chrome::NOTIFICATION_ACCESSIBILITY_MENU_CLOSED) { | |
| 131 SendMenuNotification(view, type, profile); | |
| 132 return; | |
| 133 } | |
| 134 | |
| 135 ui::AccessibleViewState state; | |
| 136 view->GetAccessibleState(&state); | |
| 137 switch (state.role) { | |
| 138 case ui::AccessibilityTypes::ROLE_ALERT: | |
| 139 case ui::AccessibilityTypes::ROLE_WINDOW: | |
| 140 SendWindowNotification(view, type, profile); | |
| 141 break; | |
| 142 case ui::AccessibilityTypes::ROLE_BUTTONMENU: | |
| 143 case ui::AccessibilityTypes::ROLE_MENUBAR: | |
| 144 case ui::AccessibilityTypes::ROLE_MENUPOPUP: | |
| 145 SendMenuNotification(view, type, profile); | |
| 146 break; | |
| 147 case ui::AccessibilityTypes::ROLE_BUTTONDROPDOWN: | |
| 148 case ui::AccessibilityTypes::ROLE_PUSHBUTTON: | |
| 149 SendButtonNotification(view, type, profile); | |
| 150 break; | |
| 151 case ui::AccessibilityTypes::ROLE_CHECKBUTTON: | |
| 152 SendCheckboxNotification(view, type, profile); | |
| 153 break; | |
| 154 case ui::AccessibilityTypes::ROLE_COMBOBOX: | |
| 155 SendComboboxNotification(view, type, profile); | |
| 156 break; | |
| 157 case ui::AccessibilityTypes::ROLE_LINK: | |
| 158 SendLinkNotification(view, type, profile); | |
| 159 break; | |
| 160 case ui::AccessibilityTypes::ROLE_LOCATION_BAR: | |
| 161 case ui::AccessibilityTypes::ROLE_TEXT: | |
| 162 SendTextfieldNotification(view, type, profile); | |
| 163 break; | |
| 164 case ui::AccessibilityTypes::ROLE_MENUITEM: | |
| 165 SendMenuItemNotification(view, type, profile); | |
| 166 break; | |
| 167 case ui::AccessibilityTypes::ROLE_RADIOBUTTON: | |
| 168 // Not used anymore? | |
| 169 case ui::AccessibilityTypes::ROLE_SLIDER: | |
| 170 SendSliderNotification(view, type, profile); | |
| 171 break; | |
| 172 default: | |
| 173 // If this is encountered, please file a bug with the role that wasn't | |
| 174 // caught so we can add accessibility extension API support. | |
| 175 NOTREACHED(); | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 // static | |
| 180 void AccessibilityEventRouterViews::SendButtonNotification( | |
| 181 views::View* view, | |
| 182 int type, | |
| 183 Profile* profile) { | |
| 184 AccessibilityButtonInfo info( | |
| 185 profile, GetViewName(view), GetViewContext(view)); | |
| 186 SendAccessibilityNotification(type, &info); | |
| 187 } | |
| 188 | |
| 189 // static | |
| 190 void AccessibilityEventRouterViews::SendLinkNotification( | |
| 191 views::View* view, | |
| 192 int type, | |
| 193 Profile* profile) { | |
| 194 AccessibilityLinkInfo info(profile, GetViewName(view), GetViewContext(view)); | |
| 195 SendAccessibilityNotification(type, &info); | |
| 196 } | |
| 197 | |
| 198 // static | |
| 199 void AccessibilityEventRouterViews::SendMenuNotification( | |
| 200 views::View* view, | |
| 201 int type, | |
| 202 Profile* profile) { | |
| 203 AccessibilityMenuInfo info(profile, GetViewName(view)); | |
| 204 SendAccessibilityNotification(type, &info); | |
| 205 } | |
| 206 | |
| 207 // static | |
| 208 void AccessibilityEventRouterViews::SendMenuItemNotification( | |
| 209 views::View* view, | |
| 210 int type, | |
| 211 Profile* profile) { | |
| 212 std::string name = GetViewName(view); | |
| 213 std::string context = GetViewContext(view); | |
| 214 | |
| 215 bool has_submenu = false; | |
| 216 int index = -1; | |
| 217 int count = -1; | |
| 218 | |
| 219 if (view->GetClassName() == views::MenuItemView::kViewClassName) | |
| 220 has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu(); | |
| 221 | |
| 222 views::View* parent_menu = view->parent(); | |
| 223 while (parent_menu != NULL && parent_menu->GetClassName() != | |
| 224 views::SubmenuView::kViewClassName) { | |
| 225 parent_menu = parent_menu->parent(); | |
| 226 } | |
| 227 if (parent_menu) { | |
| 228 count = 0; | |
| 229 RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count); | |
| 230 } | |
| 231 | |
| 232 AccessibilityMenuItemInfo info( | |
| 233 profile, name, context, has_submenu, index, count); | |
| 234 SendAccessibilityNotification(type, &info); | |
| 235 } | |
| 236 | |
| 237 // static | |
| 238 void AccessibilityEventRouterViews::SendTextfieldNotification( | |
| 239 views::View* view, | |
| 240 int type, | |
| 241 Profile* profile) { | |
| 242 ui::AccessibleViewState state; | |
| 243 view->GetAccessibleState(&state); | |
| 244 std::string name = UTF16ToUTF8(state.name); | |
| 245 std::string context = GetViewContext(view); | |
| 246 bool password = | |
| 247 (state.state & ui::AccessibilityTypes::STATE_PROTECTED) != 0; | |
| 248 AccessibilityTextBoxInfo info(profile, name, context, password); | |
| 249 std::string value = UTF16ToUTF8(state.value); | |
| 250 info.SetValue(value, state.selection_start, state.selection_end); | |
| 251 SendAccessibilityNotification(type, &info); | |
| 252 } | |
| 253 | |
| 254 // static | |
| 255 void AccessibilityEventRouterViews::SendComboboxNotification( | |
| 256 views::View* view, | |
| 257 int type, | |
| 258 Profile* profile) { | |
| 259 ui::AccessibleViewState state; | |
| 260 view->GetAccessibleState(&state); | |
| 261 std::string name = UTF16ToUTF8(state.name); | |
| 262 std::string value = UTF16ToUTF8(state.value); | |
| 263 std::string context = GetViewContext(view); | |
| 264 AccessibilityComboBoxInfo info( | |
| 265 profile, name, context, value, state.index, state.count); | |
| 266 SendAccessibilityNotification(type, &info); | |
| 267 } | |
| 268 | |
| 269 // static | |
| 270 void AccessibilityEventRouterViews::SendCheckboxNotification( | |
| 271 views::View* view, | |
| 272 int type, | |
| 273 Profile* profile) { | |
| 274 ui::AccessibleViewState state; | |
| 275 view->GetAccessibleState(&state); | |
| 276 std::string name = UTF16ToUTF8(state.name); | |
| 277 std::string value = UTF16ToUTF8(state.value); | |
| 278 std::string context = GetViewContext(view); | |
| 279 AccessibilityCheckboxInfo info( | |
| 280 profile, | |
| 281 name, | |
| 282 context, | |
| 283 state.state == ui::AccessibilityTypes::STATE_CHECKED); | |
| 284 SendAccessibilityNotification(type, &info); | |
| 285 } | |
| 286 | |
| 287 // static | |
| 288 void AccessibilityEventRouterViews::SendWindowNotification( | |
| 289 views::View* view, | |
| 290 int type, | |
| 291 Profile* profile) { | |
| 292 ui::AccessibleViewState state; | |
| 293 view->GetAccessibleState(&state); | |
| 294 std::string window_text; | |
| 295 | |
| 296 // If it's an alert, try to get the text from the contents of the | |
| 297 // static text, not the window title. | |
| 298 if (state.role == ui::AccessibilityTypes::ROLE_ALERT) | |
| 299 window_text = RecursiveGetStaticText(view); | |
| 300 | |
| 301 // Otherwise get it from the window's accessible name. | |
| 302 if (window_text.empty()) | |
| 303 window_text = UTF16ToUTF8(state.name); | |
| 304 | |
| 305 AccessibilityWindowInfo info(profile, window_text); | |
| 306 SendAccessibilityNotification(type, &info); | |
| 307 } | |
| 308 | |
| 309 // static | |
| 310 void AccessibilityEventRouterViews::SendSliderNotification( | |
| 311 views::View* view, | |
| 312 int type, | |
| 313 Profile* profile) { | |
| 314 ui::AccessibleViewState state; | |
| 315 view->GetAccessibleState(&state); | |
| 316 | |
| 317 std::string name = UTF16ToUTF8(state.name); | |
| 318 std::string value = UTF16ToUTF8(state.value); | |
| 319 std::string context = GetViewContext(view); | |
| 320 AccessibilitySliderInfo info( | |
| 321 profile, | |
| 322 name, | |
| 323 context, | |
| 324 value); | |
| 325 SendAccessibilityNotification(type, &info); | |
| 326 } | |
| 327 | |
| 328 // static | |
| 329 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) { | |
| 330 ui::AccessibleViewState state; | |
| 331 view->GetAccessibleState(&state); | |
| 332 return UTF16ToUTF8(state.name); | |
| 333 } | |
| 334 | |
| 335 // static | |
| 336 std::string AccessibilityEventRouterViews::GetViewContext(views::View* view) { | |
| 337 for (views::View* parent = view->parent(); | |
| 338 parent; | |
| 339 parent = parent->parent()) { | |
| 340 ui::AccessibleViewState state; | |
| 341 parent->GetAccessibleState(&state); | |
| 342 | |
| 343 // Two cases are handled right now. More could be added in the future | |
| 344 // depending on how the UI evolves. | |
| 345 | |
| 346 // A control in a toolbar should use the toolbar's accessible name | |
| 347 // as the context. | |
| 348 if (state.role == ui::AccessibilityTypes::ROLE_TOOLBAR && | |
| 349 !state.name.empty()) { | |
| 350 return UTF16ToUTF8(state.name); | |
| 351 } | |
| 352 | |
| 353 // A control inside of an alert (like an infobar) should grab the | |
| 354 // first static text descendant as the context; that's the prompt. | |
| 355 if (state.role == ui::AccessibilityTypes::ROLE_ALERT) { | |
| 356 views::View* static_text_child = FindDescendantWithAccessibleRole( | |
| 357 parent, ui::AccessibilityTypes::ROLE_STATICTEXT); | |
| 358 if (static_text_child) { | |
| 359 ui::AccessibleViewState state; | |
| 360 static_text_child->GetAccessibleState(&state); | |
| 361 if (!state.name.empty()) | |
| 362 return UTF16ToUTF8(state.name); | |
| 363 } | |
| 364 return std::string(); | |
| 365 } | |
| 366 } | |
| 367 | |
| 368 return std::string(); | |
| 369 } | |
| 370 | |
| 371 // static | |
| 372 views::View* AccessibilityEventRouterViews::FindDescendantWithAccessibleRole( | |
| 373 views::View* view, ui::AccessibilityTypes::Role role) { | |
| 374 ui::AccessibleViewState state; | |
| 375 view->GetAccessibleState(&state); | |
| 376 if (state.role == role) | |
| 377 return view; | |
| 378 | |
| 379 for (int i = 0; i < view->child_count(); i++) { | |
| 380 views::View* child = view->child_at(i); | |
| 381 views::View* result = FindDescendantWithAccessibleRole(child, role); | |
| 382 if (result) | |
| 383 return result; | |
| 384 } | |
| 385 | |
| 386 return NULL; | |
| 387 } | |
| 388 | |
| 389 // static | |
| 390 bool AccessibilityEventRouterViews::IsMenuEvent( | |
| 391 views::View* view, | |
| 392 int type) { | |
| 393 if (type == chrome::NOTIFICATION_ACCESSIBILITY_MENU_OPENED || | |
| 394 type == chrome::NOTIFICATION_ACCESSIBILITY_MENU_CLOSED) | |
| 395 return true; | |
| 396 | |
| 397 while (view) { | |
| 398 ui::AccessibleViewState state; | |
| 399 view->GetAccessibleState(&state); | |
| 400 ui::AccessibilityTypes::Role role = state.role; | |
| 401 if (role == ui::AccessibilityTypes::ROLE_MENUITEM || | |
| 402 role == ui::AccessibilityTypes::ROLE_MENUPOPUP) { | |
| 403 return true; | |
| 404 } | |
| 405 view = view->parent(); | |
| 406 } | |
| 407 | |
| 408 return false; | |
| 409 } | |
| 410 | |
| 411 // static | |
| 412 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount( | |
| 413 views::View* menu, | |
| 414 views::View* item, | |
| 415 int* index, | |
| 416 int* count) { | |
| 417 for (int i = 0; i < menu->child_count(); ++i) { | |
| 418 views::View* child = menu->child_at(i); | |
| 419 int previous_count = *count; | |
| 420 RecursiveGetMenuItemIndexAndCount(child, item, index, count); | |
| 421 if (child->GetClassName() == views::MenuItemView::kViewClassName && | |
| 422 *count == previous_count) { | |
| 423 if (item == child) | |
| 424 *index = *count; | |
| 425 (*count)++; | |
| 426 } else if (child->GetClassName() == views::TextButton::kViewClassName) { | |
| 427 if (item == child) | |
| 428 *index = *count; | |
| 429 (*count)++; | |
| 430 } | |
| 431 } | |
| 432 } | |
| 433 | |
| 434 // static | |
| 435 std::string AccessibilityEventRouterViews::RecursiveGetStaticText( | |
| 436 views::View* view) { | |
| 437 ui::AccessibleViewState state; | |
| 438 view->GetAccessibleState(&state); | |
| 439 if (state.role == ui::AccessibilityTypes::ROLE_STATICTEXT) | |
| 440 return UTF16ToUTF8(state.name); | |
| 441 | |
| 442 for (int i = 0; i < view->child_count(); ++i) { | |
| 443 views::View* child = view->child_at(i); | |
| 444 std::string result = RecursiveGetStaticText(child); | |
| 445 if (!result.empty()) | |
| 446 return result; | |
| 447 } | |
| 448 return std::string(); | |
| 449 } | |
| OLD | NEW |