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.
5 #include "chrome/browser/ui/views/accessibility/accessibility_event_router_views.h"
7 #include "base/basictypes.h"
8 #include "base/callback.h"
9 #include "base/memory/singleton.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/accessibility/accessibility_extension_api.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/profiles/profile_manager.h"
17 #include "content/public/browser/notification_service.h"
18 #include "ui/accessibility/ax_view_state.h"
19 #include "ui/views/controls/menu/menu_item_view.h"
20 #include "ui/views/controls/menu/submenu_view.h"
21 #include "ui/views/controls/tree/tree_view.h"
22 #include "ui/views/focus/view_storage.h"
23 #include "ui/views/view.h"
24 #include "ui/views/widget/widget.h"
26 using views::FocusManager;
28 AccessibilityEventRouterViews::AccessibilityEventRouterViews()
29 : most_recent_profile_(NULL) {
30 // Register for notification when profile is destroyed to ensure that all
31 // observers are detatched at that time.
32 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
33 content::NotificationService::AllSources());
36 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
40 AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
41 return Singleton<AccessibilityEventRouterViews>::get();
44 void AccessibilityEventRouterViews::HandleAccessibilityEvent(
45 views::View* view, ui::AXEvent event_type) {
46 if (!ExtensionAccessibilityEventRouter::GetInstance()->
47 IsAccessibilityEnabled()) {
51 if (event_type == ui::AX_EVENT_TEXT_CHANGED ||
52 event_type == ui::AX_EVENT_SELECTION_CHANGED) {
53 // These two events should only be sent for views that have focus. This
54 // enforces the invariant that we fire events triggered by user action and
55 // not by programmatic logic. For example, the location bar can be updated
56 // by javascript while the user focus is within some other part of the
57 // user interface. In contrast, the other supported events here do not
58 // depend on focus. For example, a menu within a menubar can open or close
59 // while focus is within the location bar or anywhere else as a result of
60 // user action. Note that the below logic can at some point be removed if
61 // we pass more information along to the listener such as focused state.
62 if (!view->GetFocusManager() ||
63 view->GetFocusManager()->GetFocusedView() != view)
67 // Don't dispatch the accessibility event until the next time through the
68 // event loop, to handle cases where the view's state changes after
69 // the call to post the event. It's safe to use base::Unretained(this)
70 // because AccessibilityEventRouterViews is a singleton.
71 views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
72 int view_storage_id = view_storage->CreateStorageID();
73 view_storage->StoreView(view_storage_id, view);
74 base::MessageLoop::current()->PostTask(
77 &AccessibilityEventRouterViews::DispatchEventOnViewStorageId,
82 void AccessibilityEventRouterViews::HandleMenuItemFocused(
83 const base::string16& menu_name,
84 const base::string16& menu_item_name,
88 if (!ExtensionAccessibilityEventRouter::GetInstance()->
89 IsAccessibilityEnabled()) {
93 if (!most_recent_profile_)
96 AccessibilityMenuItemInfo info(most_recent_profile_,
97 base::UTF16ToUTF8(menu_item_name),
98 base::UTF16ToUTF8(menu_name),
102 SendControlAccessibilityNotification(
103 ui::AX_EVENT_FOCUS, &info);
106 void AccessibilityEventRouterViews::Observe(
108 const content::NotificationSource& source,
109 const content::NotificationDetails& details) {
110 DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_DESTROYED);
111 Profile* profile = content::Source<Profile>(source).ptr();
112 if (profile == most_recent_profile_)
113 most_recent_profile_ = NULL;
120 void AccessibilityEventRouterViews::DispatchEventOnViewStorageId(
123 views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
124 views::View* view = view_storage->RetrieveView(view_storage_id);
125 view_storage->RemoveView(view_storage_id);
129 AccessibilityEventRouterViews* instance =
130 AccessibilityEventRouterViews::GetInstance();
131 instance->DispatchAccessibilityEvent(view, type);
134 void AccessibilityEventRouterViews::DispatchAccessibilityEvent(
135 views::View* view, ui::AXEvent type) {
136 // Get the profile associated with this view. If it's not found, use
137 // the most recent profile where accessibility events were sent, or
138 // the default profile.
139 Profile* profile = NULL;
140 views::Widget* widget = view->GetWidget();
142 profile = reinterpret_cast<Profile*>(
143 widget->GetNativeWindowProperty(Profile::kProfileKey));
146 profile = most_recent_profile_;
148 if (g_browser_process->profile_manager())
149 profile = g_browser_process->profile_manager()->GetLastUsedProfile();
152 LOG(WARNING) << "Accessibility notification but no profile";
156 most_recent_profile_ = profile;
158 if (type == ui::AX_EVENT_MENU_START ||
159 type == ui::AX_EVENT_MENU_POPUP_START ||
160 type == ui::AX_EVENT_MENU_END ||
161 type == ui::AX_EVENT_MENU_POPUP_END) {
162 SendMenuNotification(view, type, profile);
166 ui::AXViewState state;
167 view->GetAccessibleState(&state);
169 if (type == ui::AX_EVENT_ALERT &&
170 !(state.role == ui::AX_ROLE_ALERT ||
171 state.role == ui::AX_ROLE_WINDOW)) {
172 SendAlertControlNotification(view, type, profile);
176 switch (state.role) {
177 case ui::AX_ROLE_ALERT:
178 case ui::AX_ROLE_DIALOG:
179 case ui::AX_ROLE_WINDOW:
180 SendWindowNotification(view, type, profile);
182 case ui::AX_ROLE_POP_UP_BUTTON:
183 case ui::AX_ROLE_MENU_BAR:
184 case ui::AX_ROLE_MENU_LIST_POPUP:
185 SendMenuNotification(view, type, profile);
187 case ui::AX_ROLE_BUTTON_DROP_DOWN:
188 case ui::AX_ROLE_BUTTON:
189 SendButtonNotification(view, type, profile);
191 case ui::AX_ROLE_CHECK_BOX:
192 SendCheckboxNotification(view, type, profile);
194 case ui::AX_ROLE_COMBO_BOX:
195 SendComboboxNotification(view, type, profile);
197 case ui::AX_ROLE_LINK:
198 SendLinkNotification(view, type, profile);
200 case ui::AX_ROLE_LOCATION_BAR:
201 case ui::AX_ROLE_TEXT_FIELD:
202 SendTextfieldNotification(view, type, profile);
204 case ui::AX_ROLE_MENU_ITEM:
205 SendMenuItemNotification(view, type, profile);
207 case ui::AX_ROLE_RADIO_BUTTON:
209 case ui::AX_ROLE_SLIDER:
210 SendSliderNotification(view, type, profile);
212 case ui::AX_ROLE_TREE:
213 SendTreeNotification(view, type, profile);
215 case ui::AX_ROLE_TREE_ITEM:
216 SendTreeItemNotification(view, type, profile);
219 // If this is encountered, please file a bug with the role that wasn't
220 // caught so we can add accessibility extension API support.
226 void AccessibilityEventRouterViews::SendButtonNotification(
230 AccessibilityButtonInfo info(
231 profile, GetViewName(view), GetViewContext(view));
232 SendControlAccessibilityNotification(event, &info);
236 void AccessibilityEventRouterViews::SendLinkNotification(
240 AccessibilityLinkInfo info(profile, GetViewName(view), GetViewContext(view));
241 SendControlAccessibilityNotification(event, &info);
245 void AccessibilityEventRouterViews::SendMenuNotification(
249 AccessibilityMenuInfo info(profile, GetViewName(view));
250 SendMenuAccessibilityNotification(event, &info);
254 void AccessibilityEventRouterViews::SendMenuItemNotification(
258 std::string name = GetViewName(view);
259 std::string context = GetViewContext(view);
261 bool has_submenu = false;
265 if (!strcmp(view->GetClassName(), views::MenuItemView::kViewClassName))
266 has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();
268 views::View* parent_menu = view->parent();
269 while (parent_menu != NULL && strcmp(parent_menu->GetClassName(),
270 views::SubmenuView::kViewClassName)) {
271 parent_menu = parent_menu->parent();
275 RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
278 AccessibilityMenuItemInfo info(
279 profile, name, context, has_submenu, index, count);
280 SendControlAccessibilityNotification(event, &info);
284 void AccessibilityEventRouterViews::SendTreeNotification(
288 AccessibilityTreeInfo info(profile, GetViewName(view));
289 SendControlAccessibilityNotification(event, &info);
293 void AccessibilityEventRouterViews::SendTreeItemNotification(
297 std::string name = GetViewName(view);
298 std::string context = GetViewContext(view);
300 if (strcmp(view->GetClassName(), views::TreeView::kViewClassName) != 0) {
305 views::TreeView* tree = static_cast<views::TreeView*>(view);
306 ui::TreeModelNode* selected_node = tree->GetSelectedNode();
307 ui::TreeModel* model = tree->model();
309 int siblings_count = model->GetChildCount(model->GetRoot());
310 int children_count = -1;
313 bool is_expanded = false;
316 children_count = model->GetChildCount(selected_node);
317 is_expanded = tree->IsExpanded(selected_node);
318 ui::TreeModelNode* parent_node = model->GetParent(selected_node);
320 index = model->GetIndexOf(parent_node, selected_node);
321 siblings_count = model->GetChildCount(parent_node);
325 while (parent_node) {
327 parent_node = model->GetParent(parent_node);
331 AccessibilityTreeItemInfo info(
332 profile, name, context, depth, index, siblings_count, children_count,
334 SendControlAccessibilityNotification(event, &info);
338 void AccessibilityEventRouterViews::SendTextfieldNotification(
342 ui::AXViewState state;
343 view->GetAccessibleState(&state);
344 std::string name = base::UTF16ToUTF8(state.name);
345 std::string context = GetViewContext(view);
346 bool password = state.HasStateFlag(ui::AX_STATE_PROTECTED);
347 AccessibilityTextBoxInfo info(profile, name, context, password);
348 std::string value = base::UTF16ToUTF8(state.value);
349 info.SetValue(value, state.selection_start, state.selection_end);
350 SendControlAccessibilityNotification(event, &info);
354 void AccessibilityEventRouterViews::SendComboboxNotification(
358 ui::AXViewState state;
359 view->GetAccessibleState(&state);
360 std::string name = base::UTF16ToUTF8(state.name);
361 std::string value = base::UTF16ToUTF8(state.value);
362 std::string context = GetViewContext(view);
363 AccessibilityComboBoxInfo info(
364 profile, name, context, value, state.index, state.count);
365 SendControlAccessibilityNotification(event, &info);
369 void AccessibilityEventRouterViews::SendCheckboxNotification(
373 ui::AXViewState state;
374 view->GetAccessibleState(&state);
375 std::string name = base::UTF16ToUTF8(state.name);
376 std::string context = GetViewContext(view);
377 AccessibilityCheckboxInfo info(
381 state.HasStateFlag(ui::AX_STATE_CHECKED));
382 SendControlAccessibilityNotification(event, &info);
386 void AccessibilityEventRouterViews::SendWindowNotification(
390 ui::AXViewState state;
391 view->GetAccessibleState(&state);
392 std::string window_text;
394 // If it's an alert, try to get the text from the contents of the
395 // static text, not the window title.
396 if (state.role == ui::AX_ROLE_ALERT)
397 window_text = RecursiveGetStaticText(view);
399 // Otherwise get it from the window's accessible name.
400 if (window_text.empty())
401 window_text = base::UTF16ToUTF8(state.name);
403 AccessibilityWindowInfo info(profile, window_text);
404 SendWindowAccessibilityNotification(event, &info);
408 void AccessibilityEventRouterViews::SendSliderNotification(
412 ui::AXViewState state;
413 view->GetAccessibleState(&state);
415 std::string name = base::UTF16ToUTF8(state.name);
416 std::string value = base::UTF16ToUTF8(state.value);
417 std::string context = GetViewContext(view);
418 AccessibilitySliderInfo info(
423 SendControlAccessibilityNotification(event, &info);
427 void AccessibilityEventRouterViews::SendAlertControlNotification(
431 ui::AXViewState state;
432 view->GetAccessibleState(&state);
434 std::string name = base::UTF16ToUTF8(state.name);
435 AccessibilityAlertInfo info(
438 SendControlAccessibilityNotification(event, &info);
442 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
443 ui::AXViewState state;
444 view->GetAccessibleState(&state);
445 return base::UTF16ToUTF8(state.name);
449 std::string AccessibilityEventRouterViews::GetViewContext(views::View* view) {
450 for (views::View* parent = view->parent();
452 parent = parent->parent()) {
453 ui::AXViewState state;
454 parent->GetAccessibleState(&state);
456 // Two cases are handled right now. More could be added in the future
457 // depending on how the UI evolves.
459 // A control inside of alert, toolbar or dialog should use that container's
461 if ((state.role == ui::AX_ROLE_ALERT ||
462 state.role == ui::AX_ROLE_DIALOG ||
463 state.role == ui::AX_ROLE_TOOLBAR) &&
464 !state.name.empty()) {
465 return base::UTF16ToUTF8(state.name);
468 // A control inside of an alert or dialog (including an infobar)
469 // should grab the first static text descendant as the context;
470 // that's the prompt.
471 if (state.role == ui::AX_ROLE_ALERT ||
472 state.role == ui::AX_ROLE_DIALOG) {
473 views::View* static_text_child = FindDescendantWithAccessibleRole(
474 parent, ui::AX_ROLE_STATIC_TEXT);
475 if (static_text_child) {
476 ui::AXViewState state;
477 static_text_child->GetAccessibleState(&state);
478 if (!state.name.empty())
479 return base::UTF16ToUTF8(state.name);
481 return std::string();
485 return std::string();
489 views::View* AccessibilityEventRouterViews::FindDescendantWithAccessibleRole(
490 views::View* view, ui::AXRole role) {
491 ui::AXViewState state;
492 view->GetAccessibleState(&state);
493 if (state.role == role)
496 for (int i = 0; i < view->child_count(); i++) {
497 views::View* child = view->child_at(i);
498 views::View* result = FindDescendantWithAccessibleRole(child, role);
507 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
512 for (int i = 0; i < menu->child_count(); ++i) {
513 views::View* child = menu->child_at(i);
514 if (!child->visible())
517 int previous_count = *count;
518 RecursiveGetMenuItemIndexAndCount(child, item, index, count);
519 ui::AXViewState state;
520 child->GetAccessibleState(&state);
521 if (state.role == ui::AX_ROLE_MENU_ITEM &&
522 *count == previous_count) {
526 } else if (state.role == ui::AX_ROLE_BUTTON) {
535 std::string AccessibilityEventRouterViews::RecursiveGetStaticText(
537 ui::AXViewState state;
538 view->GetAccessibleState(&state);
539 if (state.role == ui::AX_ROLE_STATIC_TEXT)
540 return base::UTF16ToUTF8(state.name);
542 for (int i = 0; i < view->child_count(); ++i) {
543 views::View* child = view->child_at(i);
544 std::string result = RecursiveGetStaticText(child);
548 return std::string();