Refactor views app list services to allow more code sharing
[chromium-blink-merge.git] / chrome / browser / ui / views / accessibility / accessibility_event_router_views.cc
blob8d829153bfc6b99eb45d10328ec127eb99f01cf0
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() {
39 // static
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()) {
48 return;
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)
64 return;
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(
75 FROM_HERE,
76 base::Bind(
77 &AccessibilityEventRouterViews::DispatchEventOnViewStorageId,
78 view_storage_id,
79 event_type));
82 void AccessibilityEventRouterViews::HandleMenuItemFocused(
83 const base::string16& menu_name,
84 const base::string16& menu_item_name,
85 int item_index,
86 int item_count,
87 bool has_submenu) {
88 if (!ExtensionAccessibilityEventRouter::GetInstance()->
89 IsAccessibilityEnabled()) {
90 return;
93 if (!most_recent_profile_)
94 return;
96 AccessibilityMenuItemInfo info(most_recent_profile_,
97 base::UTF16ToUTF8(menu_item_name),
98 base::UTF16ToUTF8(menu_name),
99 has_submenu,
100 item_index,
101 item_count);
102 SendControlAccessibilityNotification(
103 ui::AX_EVENT_FOCUS, &info);
106 void AccessibilityEventRouterViews::Observe(
107 int type,
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;
117 // Private methods
120 void AccessibilityEventRouterViews::DispatchEventOnViewStorageId(
121 int view_storage_id,
122 ui::AXEvent type) {
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);
126 if (!view)
127 return;
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();
141 if (widget) {
142 profile = reinterpret_cast<Profile*>(
143 widget->GetNativeWindowProperty(Profile::kProfileKey));
145 if (!profile)
146 profile = most_recent_profile_;
147 if (!profile) {
148 if (g_browser_process->profile_manager())
149 profile = g_browser_process->profile_manager()->GetLastUsedProfile();
151 if (!profile) {
152 LOG(WARNING) << "Accessibility notification but no profile";
153 return;
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);
163 return;
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);
173 return;
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);
181 break;
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);
186 break;
187 case ui::AX_ROLE_BUTTON_DROP_DOWN:
188 case ui::AX_ROLE_BUTTON:
189 SendButtonNotification(view, type, profile);
190 break;
191 case ui::AX_ROLE_CHECK_BOX:
192 SendCheckboxNotification(view, type, profile);
193 break;
194 case ui::AX_ROLE_COMBO_BOX:
195 SendComboboxNotification(view, type, profile);
196 break;
197 case ui::AX_ROLE_LINK:
198 SendLinkNotification(view, type, profile);
199 break;
200 case ui::AX_ROLE_LOCATION_BAR:
201 case ui::AX_ROLE_TEXT_FIELD:
202 SendTextfieldNotification(view, type, profile);
203 break;
204 case ui::AX_ROLE_MENU_ITEM:
205 SendMenuItemNotification(view, type, profile);
206 break;
207 case ui::AX_ROLE_RADIO_BUTTON:
208 // Not used anymore?
209 case ui::AX_ROLE_SLIDER:
210 SendSliderNotification(view, type, profile);
211 break;
212 case ui::AX_ROLE_TREE:
213 SendTreeNotification(view, type, profile);
214 break;
215 case ui::AX_ROLE_TREE_ITEM:
216 SendTreeItemNotification(view, type, profile);
217 break;
218 default:
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.
221 NOTREACHED();
225 // static
226 void AccessibilityEventRouterViews::SendButtonNotification(
227 views::View* view,
228 ui::AXEvent event,
229 Profile* profile) {
230 AccessibilityButtonInfo info(
231 profile, GetViewName(view), GetViewContext(view));
232 SendControlAccessibilityNotification(event, &info);
235 // static
236 void AccessibilityEventRouterViews::SendLinkNotification(
237 views::View* view,
238 ui::AXEvent event,
239 Profile* profile) {
240 AccessibilityLinkInfo info(profile, GetViewName(view), GetViewContext(view));
241 SendControlAccessibilityNotification(event, &info);
244 // static
245 void AccessibilityEventRouterViews::SendMenuNotification(
246 views::View* view,
247 ui::AXEvent event,
248 Profile* profile) {
249 AccessibilityMenuInfo info(profile, GetViewName(view));
250 SendMenuAccessibilityNotification(event, &info);
253 // static
254 void AccessibilityEventRouterViews::SendMenuItemNotification(
255 views::View* view,
256 ui::AXEvent event,
257 Profile* profile) {
258 std::string name = GetViewName(view);
259 std::string context = GetViewContext(view);
261 bool has_submenu = false;
262 int index = -1;
263 int count = -1;
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();
273 if (parent_menu) {
274 count = 0;
275 RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
278 AccessibilityMenuItemInfo info(
279 profile, name, context, has_submenu, index, count);
280 SendControlAccessibilityNotification(event, &info);
283 // static
284 void AccessibilityEventRouterViews::SendTreeNotification(
285 views::View* view,
286 ui::AXEvent event,
287 Profile* profile) {
288 AccessibilityTreeInfo info(profile, GetViewName(view));
289 SendControlAccessibilityNotification(event, &info);
292 // static
293 void AccessibilityEventRouterViews::SendTreeItemNotification(
294 views::View* view,
295 ui::AXEvent event,
296 Profile* profile) {
297 std::string name = GetViewName(view);
298 std::string context = GetViewContext(view);
300 if (strcmp(view->GetClassName(), views::TreeView::kViewClassName) != 0) {
301 NOTREACHED();
302 return;
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;
311 int index = -1;
312 int depth = -1;
313 bool is_expanded = false;
315 if (selected_node) {
316 children_count = model->GetChildCount(selected_node);
317 is_expanded = tree->IsExpanded(selected_node);
318 ui::TreeModelNode* parent_node = model->GetParent(selected_node);
319 if (parent_node) {
320 index = model->GetIndexOf(parent_node, selected_node);
321 siblings_count = model->GetChildCount(parent_node);
323 // Get node depth.
324 depth = 0;
325 while (parent_node) {
326 depth++;
327 parent_node = model->GetParent(parent_node);
331 AccessibilityTreeItemInfo info(
332 profile, name, context, depth, index, siblings_count, children_count,
333 is_expanded);
334 SendControlAccessibilityNotification(event, &info);
337 // static
338 void AccessibilityEventRouterViews::SendTextfieldNotification(
339 views::View* view,
340 ui::AXEvent event,
341 Profile* profile) {
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);
353 // static
354 void AccessibilityEventRouterViews::SendComboboxNotification(
355 views::View* view,
356 ui::AXEvent event,
357 Profile* profile) {
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);
368 // static
369 void AccessibilityEventRouterViews::SendCheckboxNotification(
370 views::View* view,
371 ui::AXEvent event,
372 Profile* profile) {
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(
378 profile,
379 name,
380 context,
381 state.HasStateFlag(ui::AX_STATE_CHECKED));
382 SendControlAccessibilityNotification(event, &info);
385 // static
386 void AccessibilityEventRouterViews::SendWindowNotification(
387 views::View* view,
388 ui::AXEvent event,
389 Profile* profile) {
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);
407 // static
408 void AccessibilityEventRouterViews::SendSliderNotification(
409 views::View* view,
410 ui::AXEvent event,
411 Profile* profile) {
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(
419 profile,
420 name,
421 context,
422 value);
423 SendControlAccessibilityNotification(event, &info);
426 // static
427 void AccessibilityEventRouterViews::SendAlertControlNotification(
428 views::View* view,
429 ui::AXEvent event,
430 Profile* profile) {
431 ui::AXViewState state;
432 view->GetAccessibleState(&state);
434 std::string name = base::UTF16ToUTF8(state.name);
435 AccessibilityAlertInfo info(
436 profile,
437 name);
438 SendControlAccessibilityNotification(event, &info);
441 // static
442 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
443 ui::AXViewState state;
444 view->GetAccessibleState(&state);
445 return base::UTF16ToUTF8(state.name);
448 // static
449 std::string AccessibilityEventRouterViews::GetViewContext(views::View* view) {
450 for (views::View* parent = view->parent();
451 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
460 // accessible name.
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();
488 // static
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)
494 return view;
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);
499 if (result)
500 return result;
503 return NULL;
506 // static
507 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
508 views::View* menu,
509 views::View* item,
510 int* index,
511 int* count) {
512 for (int i = 0; i < menu->child_count(); ++i) {
513 views::View* child = menu->child_at(i);
514 if (!child->visible())
515 continue;
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) {
523 if (item == child)
524 *index = *count;
525 (*count)++;
526 } else if (state.role == ui::AX_ROLE_BUTTON) {
527 if (item == child)
528 *index = *count;
529 (*count)++;
534 // static
535 std::string AccessibilityEventRouterViews::RecursiveGetStaticText(
536 views::View* view) {
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);
545 if (!result.empty())
546 return result;
548 return std::string();