Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / accessibility / accessibility_event_router_views.cc
blobb946bcfe5f3b49c3c643dd221db76a26960cf84e
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/base/accessibility/accessible_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::AccessibilityTypes::Event event_type) {
46 if (!ExtensionAccessibilityEventRouter::GetInstance()->
47 IsAccessibilityEnabled()) {
48 return;
51 if (event_type == ui::AccessibilityTypes::EVENT_TEXT_CHANGED ||
52 event_type == ui::AccessibilityTypes::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::AccessibilityTypes::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::AccessibilityTypes::Event 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::AccessibilityTypes::Event 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::AccessibilityTypes::EVENT_MENUSTART ||
159 type == ui::AccessibilityTypes::EVENT_MENUPOPUPSTART ||
160 type == ui::AccessibilityTypes::EVENT_MENUEND ||
161 type == ui::AccessibilityTypes::EVENT_MENUPOPUPEND) {
162 SendMenuNotification(view, type, profile);
163 return;
166 ui::AccessibleViewState state;
167 view->GetAccessibleState(&state);
169 if (type == ui::AccessibilityTypes::EVENT_ALERT &&
170 !(state.role == ui::AccessibilityTypes::ROLE_ALERT ||
171 state.role == ui::AccessibilityTypes::ROLE_WINDOW)) {
172 SendAlertControlNotification(view, type, profile);
173 return;
176 switch (state.role) {
177 case ui::AccessibilityTypes::ROLE_ALERT:
178 case ui::AccessibilityTypes::ROLE_DIALOG:
179 case ui::AccessibilityTypes::ROLE_WINDOW:
180 SendWindowNotification(view, type, profile);
181 break;
182 case ui::AccessibilityTypes::ROLE_BUTTONMENU:
183 case ui::AccessibilityTypes::ROLE_MENUBAR:
184 case ui::AccessibilityTypes::ROLE_MENUPOPUP:
185 SendMenuNotification(view, type, profile);
186 break;
187 case ui::AccessibilityTypes::ROLE_BUTTONDROPDOWN:
188 case ui::AccessibilityTypes::ROLE_PUSHBUTTON:
189 SendButtonNotification(view, type, profile);
190 break;
191 case ui::AccessibilityTypes::ROLE_CHECKBUTTON:
192 SendCheckboxNotification(view, type, profile);
193 break;
194 case ui::AccessibilityTypes::ROLE_COMBOBOX:
195 SendComboboxNotification(view, type, profile);
196 break;
197 case ui::AccessibilityTypes::ROLE_LINK:
198 SendLinkNotification(view, type, profile);
199 break;
200 case ui::AccessibilityTypes::ROLE_LOCATION_BAR:
201 case ui::AccessibilityTypes::ROLE_TEXT:
202 SendTextfieldNotification(view, type, profile);
203 break;
204 case ui::AccessibilityTypes::ROLE_MENUITEM:
205 SendMenuItemNotification(view, type, profile);
206 break;
207 case ui::AccessibilityTypes::ROLE_RADIOBUTTON:
208 // Not used anymore?
209 case ui::AccessibilityTypes::ROLE_SLIDER:
210 SendSliderNotification(view, type, profile);
211 break;
212 case ui::AccessibilityTypes::ROLE_OUTLINE:
213 SendTreeNotification(view, type, profile);
214 break;
215 case ui::AccessibilityTypes::ROLE_OUTLINEITEM:
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::AccessibilityTypes::Event 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::AccessibilityTypes::Event 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::AccessibilityTypes::Event 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::AccessibilityTypes::Event 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::AccessibilityTypes::Event 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::AccessibilityTypes::Event 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::AccessibilityTypes::Event event,
341 Profile* profile) {
342 ui::AccessibleViewState state;
343 view->GetAccessibleState(&state);
344 std::string name = base::UTF16ToUTF8(state.name);
345 std::string context = GetViewContext(view);
346 bool password =
347 (state.state & ui::AccessibilityTypes::STATE_PROTECTED) != 0;
348 AccessibilityTextBoxInfo info(profile, name, context, password);
349 std::string value = base::UTF16ToUTF8(state.value);
350 info.SetValue(value, state.selection_start, state.selection_end);
351 SendControlAccessibilityNotification(event, &info);
354 // static
355 void AccessibilityEventRouterViews::SendComboboxNotification(
356 views::View* view,
357 ui::AccessibilityTypes::Event event,
358 Profile* profile) {
359 ui::AccessibleViewState state;
360 view->GetAccessibleState(&state);
361 std::string name = base::UTF16ToUTF8(state.name);
362 std::string value = base::UTF16ToUTF8(state.value);
363 std::string context = GetViewContext(view);
364 AccessibilityComboBoxInfo info(
365 profile, name, context, value, state.index, state.count);
366 SendControlAccessibilityNotification(event, &info);
369 // static
370 void AccessibilityEventRouterViews::SendCheckboxNotification(
371 views::View* view,
372 ui::AccessibilityTypes::Event event,
373 Profile* profile) {
374 ui::AccessibleViewState state;
375 view->GetAccessibleState(&state);
376 std::string name = base::UTF16ToUTF8(state.name);
377 std::string context = GetViewContext(view);
378 AccessibilityCheckboxInfo info(
379 profile,
380 name,
381 context,
382 state.state == ui::AccessibilityTypes::STATE_CHECKED);
383 SendControlAccessibilityNotification(event, &info);
386 // static
387 void AccessibilityEventRouterViews::SendWindowNotification(
388 views::View* view,
389 ui::AccessibilityTypes::Event event,
390 Profile* profile) {
391 ui::AccessibleViewState state;
392 view->GetAccessibleState(&state);
393 std::string window_text;
395 // If it's an alert, try to get the text from the contents of the
396 // static text, not the window title.
397 if (state.role == ui::AccessibilityTypes::ROLE_ALERT)
398 window_text = RecursiveGetStaticText(view);
400 // Otherwise get it from the window's accessible name.
401 if (window_text.empty())
402 window_text = base::UTF16ToUTF8(state.name);
404 AccessibilityWindowInfo info(profile, window_text);
405 SendWindowAccessibilityNotification(event, &info);
408 // static
409 void AccessibilityEventRouterViews::SendSliderNotification(
410 views::View* view,
411 ui::AccessibilityTypes::Event event,
412 Profile* profile) {
413 ui::AccessibleViewState state;
414 view->GetAccessibleState(&state);
416 std::string name = base::UTF16ToUTF8(state.name);
417 std::string value = base::UTF16ToUTF8(state.value);
418 std::string context = GetViewContext(view);
419 AccessibilitySliderInfo info(
420 profile,
421 name,
422 context,
423 value);
424 SendControlAccessibilityNotification(event, &info);
427 // static
428 void AccessibilityEventRouterViews::SendAlertControlNotification(
429 views::View* view,
430 ui::AccessibilityTypes::Event event,
431 Profile* profile) {
432 ui::AccessibleViewState state;
433 view->GetAccessibleState(&state);
435 std::string name = base::UTF16ToUTF8(state.name);
436 AccessibilityAlertInfo info(
437 profile,
438 name);
439 SendControlAccessibilityNotification(event, &info);
442 // static
443 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
444 ui::AccessibleViewState state;
445 view->GetAccessibleState(&state);
446 return base::UTF16ToUTF8(state.name);
449 // static
450 std::string AccessibilityEventRouterViews::GetViewContext(views::View* view) {
451 for (views::View* parent = view->parent();
452 parent;
453 parent = parent->parent()) {
454 ui::AccessibleViewState state;
455 parent->GetAccessibleState(&state);
457 // Two cases are handled right now. More could be added in the future
458 // depending on how the UI evolves.
460 // A control inside of alert, toolbar or dialog should use that container's
461 // accessible name.
462 if ((state.role == ui::AccessibilityTypes::ROLE_ALERT ||
463 state.role == ui::AccessibilityTypes::ROLE_DIALOG ||
464 state.role == ui::AccessibilityTypes::ROLE_TOOLBAR) &&
465 !state.name.empty()) {
466 return base::UTF16ToUTF8(state.name);
469 // A control inside of an alert or dialog (including an infobar)
470 // should grab the first static text descendant as the context;
471 // that's the prompt.
472 if (state.role == ui::AccessibilityTypes::ROLE_ALERT ||
473 state.role == ui::AccessibilityTypes::ROLE_DIALOG) {
474 views::View* static_text_child = FindDescendantWithAccessibleRole(
475 parent, ui::AccessibilityTypes::ROLE_STATICTEXT);
476 if (static_text_child) {
477 ui::AccessibleViewState state;
478 static_text_child->GetAccessibleState(&state);
479 if (!state.name.empty())
480 return base::UTF16ToUTF8(state.name);
482 return std::string();
486 return std::string();
489 // static
490 views::View* AccessibilityEventRouterViews::FindDescendantWithAccessibleRole(
491 views::View* view, ui::AccessibilityTypes::Role role) {
492 ui::AccessibleViewState state;
493 view->GetAccessibleState(&state);
494 if (state.role == role)
495 return view;
497 for (int i = 0; i < view->child_count(); i++) {
498 views::View* child = view->child_at(i);
499 views::View* result = FindDescendantWithAccessibleRole(child, role);
500 if (result)
501 return result;
504 return NULL;
507 // static
508 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
509 views::View* menu,
510 views::View* item,
511 int* index,
512 int* count) {
513 for (int i = 0; i < menu->child_count(); ++i) {
514 views::View* child = menu->child_at(i);
515 if (!child->visible())
516 continue;
518 int previous_count = *count;
519 RecursiveGetMenuItemIndexAndCount(child, item, index, count);
520 ui::AccessibleViewState state;
521 child->GetAccessibleState(&state);
522 if (state.role == ui::AccessibilityTypes::ROLE_MENUITEM &&
523 *count == previous_count) {
524 if (item == child)
525 *index = *count;
526 (*count)++;
527 } else if (state.role == ui::AccessibilityTypes::ROLE_PUSHBUTTON) {
528 if (item == child)
529 *index = *count;
530 (*count)++;
535 // static
536 std::string AccessibilityEventRouterViews::RecursiveGetStaticText(
537 views::View* view) {
538 ui::AccessibleViewState state;
539 view->GetAccessibleState(&state);
540 if (state.role == ui::AccessibilityTypes::ROLE_STATICTEXT)
541 return base::UTF16ToUTF8(state.name);
543 for (int i = 0; i < view->child_count(); ++i) {
544 views::View* child = view->child_at(i);
545 std::string result = RecursiveGetStaticText(child);
546 if (!result.empty())
547 return result;
549 return std::string();