Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / accessibility / accessibility_event_router_views.cc
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/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/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"
25
26 using views::FocusManager;
27 using views::ViewStorage;
28
29 AccessibilityEventRouterViews::AccessibilityEventRouterViews()
30     : most_recent_profile_(NULL),
31       most_recent_view_id_(
32           ViewStorage::GetInstance()->CreateStorageID()) {
33   // Register for notification when profile is destroyed to ensure that all
34   // observers are detatched at that time.
35   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
36                  content::NotificationService::AllSources());
37 }
38
39 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
40 }
41
42 // static
43 AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
44   return Singleton<AccessibilityEventRouterViews>::get();
45 }
46
47 void AccessibilityEventRouterViews::HandleAccessibilityEvent(
48     views::View* view, ui::AXEvent event_type) {
49   if (!ExtensionAccessibilityEventRouter::GetInstance()->
50       IsAccessibilityEnabled()) {
51     return;
52   }
53
54   if (event_type == ui::AX_EVENT_TEXT_CHANGED ||
55       event_type == ui::AX_EVENT_TEXT_SELECTION_CHANGED) {
56     // These two events should only be sent for views that have focus. This
57     // enforces the invariant that we fire events triggered by user action and
58     // not by programmatic logic. For example, the location bar can be updated
59     // by javascript while the user focus is within some other part of the
60     // user interface. In contrast, the other supported events here do not
61     // depend on focus. For example, a menu within a menubar can open or close
62     // while focus is within the location bar or anywhere else as a result of
63     // user action. Note that the below logic can at some point be removed if
64     // we pass more information along to the listener such as focused state.
65     if (!view->GetFocusManager() ||
66         view->GetFocusManager()->GetFocusedView() != view)
67       return;
68   }
69
70   // Don't dispatch the accessibility event until the next time through the
71   // event loop, to handle cases where the view's state changes after
72   // the call to post the event. It's safe to use base::Unretained(this)
73   // because AccessibilityEventRouterViews is a singleton.
74   ViewStorage* view_storage = ViewStorage::GetInstance();
75   int view_storage_id = view_storage->CreateStorageID();
76   view_storage->StoreView(view_storage_id, view);
77   base::MessageLoop::current()->PostTask(
78       FROM_HERE,
79       base::Bind(
80           &AccessibilityEventRouterViews::DispatchEventOnViewStorageId,
81           view_storage_id,
82           event_type));
83 }
84
85 void AccessibilityEventRouterViews::HandleMenuItemFocused(
86     const base::string16& menu_name,
87     const base::string16& menu_item_name,
88     int item_index,
89     int item_count,
90     bool has_submenu) {
91   if (!ExtensionAccessibilityEventRouter::GetInstance()->
92       IsAccessibilityEnabled()) {
93     return;
94   }
95
96   if (!most_recent_profile_)
97     return;
98
99   AccessibilityMenuItemInfo info(most_recent_profile_,
100                                  base::UTF16ToUTF8(menu_item_name),
101                                  base::UTF16ToUTF8(menu_name),
102                                  has_submenu,
103                                  item_index,
104                                  item_count);
105   SendControlAccessibilityNotification(
106       ui::AX_EVENT_FOCUS, &info);
107 }
108
109 void AccessibilityEventRouterViews::Observe(
110     int type,
111     const content::NotificationSource& source,
112     const content::NotificationDetails& details) {
113   DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_DESTROYED);
114   Profile* profile = content::Source<Profile>(source).ptr();
115   if (profile == most_recent_profile_)
116     most_recent_profile_ = NULL;
117 }
118
119 //
120 // Private methods
121 //
122
123 void AccessibilityEventRouterViews::DispatchEventOnViewStorageId(
124     int view_storage_id,
125     ui::AXEvent type) {
126   ViewStorage* view_storage = ViewStorage::GetInstance();
127   views::View* view = view_storage->RetrieveView(view_storage_id);
128   view_storage->RemoveView(view_storage_id);
129   if (!view)
130     return;
131
132   AccessibilityEventRouterViews* instance =
133       AccessibilityEventRouterViews::GetInstance();
134   instance->DispatchAccessibilityEvent(view, type);
135 }
136
137 void AccessibilityEventRouterViews::DispatchAccessibilityEvent(
138     views::View* view, ui::AXEvent type) {
139   // Get the profile associated with this view. If it's not found, use
140   // the most recent profile where accessibility events were sent, or
141   // the default profile.
142   Profile* profile = NULL;
143   views::Widget* widget = view->GetWidget();
144   if (widget) {
145     profile = reinterpret_cast<Profile*>(
146         widget->GetNativeWindowProperty(Profile::kProfileKey));
147   }
148   if (!profile)
149     profile = most_recent_profile_;
150   if (!profile) {
151     if (g_browser_process->profile_manager())
152       profile = g_browser_process->profile_manager()->GetLastUsedProfile();
153   }
154   if (!profile) {
155     LOG(WARNING) << "Accessibility notification but no profile";
156     return;
157   }
158
159   most_recent_profile_ = profile;
160
161   if (type == ui::AX_EVENT_MENU_START ||
162       type == ui::AX_EVENT_MENU_POPUP_START ||
163       type == ui::AX_EVENT_MENU_END ||
164       type == ui::AX_EVENT_MENU_POPUP_END) {
165     SendMenuNotification(view, type, profile);
166     return;
167   }
168
169   view = FindFirstAccessibleAncestor(view);
170
171   // Since multiple items could share a highest focusable view, these items
172   // could all dispatch the same accessibility hover events, which isn't
173   // necessary.
174   if (type == ui::AX_EVENT_HOVER &&
175       ViewStorage::GetInstance()->RetrieveView(most_recent_view_id_) == view) {
176     return;
177   }
178   // If there was already a view stored here from before, it must be removed
179   // before storing a new view.
180   ViewStorage::GetInstance()->RemoveView(most_recent_view_id_);
181   ViewStorage::GetInstance()->StoreView(most_recent_view_id_, view);
182
183   ui::AXViewState state;
184   view->GetAccessibleState(&state);
185
186   if (type == ui::AX_EVENT_ALERT &&
187       !(state.role == ui::AX_ROLE_ALERT ||
188         state.role == ui::AX_ROLE_WINDOW)) {
189     SendAlertControlNotification(view, type, profile);
190     return;
191   }
192
193   switch (state.role) {
194   case ui::AX_ROLE_ALERT:
195   case ui::AX_ROLE_DIALOG:
196   case ui::AX_ROLE_WINDOW:
197     SendWindowNotification(view, type, profile);
198     break;
199   case ui::AX_ROLE_POP_UP_BUTTON:
200   case ui::AX_ROLE_MENU_BAR:
201   case ui::AX_ROLE_MENU_LIST_POPUP:
202     SendMenuNotification(view, type, profile);
203     break;
204   case ui::AX_ROLE_BUTTON_DROP_DOWN:
205   case ui::AX_ROLE_BUTTON:
206     SendButtonNotification(view, type, profile);
207     break;
208   case ui::AX_ROLE_CHECK_BOX:
209     SendCheckboxNotification(view, type, profile);
210     break;
211   case ui::AX_ROLE_COMBO_BOX:
212     SendComboboxNotification(view, type, profile);
213     break;
214   case ui::AX_ROLE_LINK:
215     SendLinkNotification(view, type, profile);
216     break;
217   case ui::AX_ROLE_LOCATION_BAR:
218   case ui::AX_ROLE_TEXT_FIELD:
219     SendTextfieldNotification(view, type, profile);
220     break;
221   case ui::AX_ROLE_MENU_ITEM:
222     SendMenuItemNotification(view, type, profile);
223     break;
224   case ui::AX_ROLE_RADIO_BUTTON:
225     // Not used anymore?
226   case ui::AX_ROLE_SLIDER:
227     SendSliderNotification(view, type, profile);
228     break;
229   case ui::AX_ROLE_STATIC_TEXT:
230     SendStaticTextNotification(view, type, profile);
231     break;
232   case ui::AX_ROLE_TREE:
233     SendTreeNotification(view, type, profile);
234     break;
235   case ui::AX_ROLE_TAB:
236     SendTabNotification(view, type, profile);
237     break;
238   case ui::AX_ROLE_TREE_ITEM:
239     SendTreeItemNotification(view, type, profile);
240     break;
241   default:
242     // Hover events can fire on literally any view, so it's safe to
243     // ignore ones we don't care about.
244     if (type == ui::AX_EVENT_HOVER)
245       break;
246
247     // If this is encountered, please file a bug with the role that wasn't
248     // caught so we can add accessibility extension API support.
249     NOTREACHED();
250   }
251 }
252
253 // static
254 void AccessibilityEventRouterViews::SendTabNotification(
255     views::View* view,
256     ui::AXEvent event,
257     Profile* profile) {
258   ui::AXViewState state;
259   view->GetAccessibleState(&state);
260   if (state.index == -1)
261     return;
262   std::string name = base::UTF16ToUTF8(state.name);
263   std::string context = GetViewContext(view);
264   AccessibilityTabInfo info(profile, name, context, state.index, state.count);
265   SendControlAccessibilityNotification(event, &info);
266 }
267
268 // static
269 void AccessibilityEventRouterViews::SendButtonNotification(
270     views::View* view,
271     ui::AXEvent event,
272     Profile* profile) {
273   AccessibilityButtonInfo info(
274       profile, GetViewName(view), GetViewContext(view));
275   SendControlAccessibilityNotification(event, &info);
276 }
277
278 // static
279 void AccessibilityEventRouterViews::SendStaticTextNotification(
280     views::View* view,
281     ui::AXEvent event,
282     Profile* profile) {
283   AccessibilityStaticTextInfo info(
284       profile, GetViewName(view), GetViewContext(view));
285   SendControlAccessibilityNotification(event, &info);
286 }
287
288 // static
289 void AccessibilityEventRouterViews::SendLinkNotification(
290     views::View* view,
291     ui::AXEvent event,
292     Profile* profile) {
293   AccessibilityLinkInfo info(profile, GetViewName(view), GetViewContext(view));
294   SendControlAccessibilityNotification(event, &info);
295 }
296
297 // static
298 void AccessibilityEventRouterViews::SendMenuNotification(
299     views::View* view,
300     ui::AXEvent event,
301     Profile* profile) {
302   AccessibilityMenuInfo info(profile, GetViewName(view));
303   SendMenuAccessibilityNotification(event, &info);
304 }
305
306 // static
307 void AccessibilityEventRouterViews::SendMenuItemNotification(
308     views::View* view,
309     ui::AXEvent event,
310     Profile* profile) {
311   std::string name = GetViewName(view);
312   std::string context = GetViewContext(view);
313
314   bool has_submenu = false;
315   int index = -1;
316   int count = -1;
317
318   if (!strcmp(view->GetClassName(), views::MenuItemView::kViewClassName))
319     has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();
320
321   views::View* parent_menu = view->parent();
322   while (parent_menu != NULL && strcmp(parent_menu->GetClassName(),
323                                        views::SubmenuView::kViewClassName)) {
324     parent_menu = parent_menu->parent();
325   }
326   if (parent_menu) {
327     count = 0;
328     RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
329   }
330
331   AccessibilityMenuItemInfo info(
332       profile, name, context, has_submenu, index, count);
333   SendControlAccessibilityNotification(event, &info);
334 }
335
336 // static
337 void AccessibilityEventRouterViews::SendTreeNotification(
338     views::View* view,
339     ui::AXEvent event,
340     Profile* profile) {
341   AccessibilityTreeInfo info(profile, GetViewName(view));
342   SendControlAccessibilityNotification(event, &info);
343 }
344
345 // static
346 void AccessibilityEventRouterViews::SendTreeItemNotification(
347     views::View* view,
348     ui::AXEvent event,
349     Profile* profile) {
350   std::string name = GetViewName(view);
351   std::string context = GetViewContext(view);
352
353   if (strcmp(view->GetClassName(), views::TreeView::kViewClassName) != 0) {
354     NOTREACHED();
355     return;
356   }
357
358   views::TreeView* tree = static_cast<views::TreeView*>(view);
359   ui::TreeModelNode* selected_node = tree->GetSelectedNode();
360   ui::TreeModel* model = tree->model();
361
362   int siblings_count = model->GetChildCount(model->GetRoot());
363   int children_count = -1;
364   int index = -1;
365   int depth = -1;
366   bool is_expanded = false;
367
368   if (selected_node) {
369     children_count = model->GetChildCount(selected_node);
370     is_expanded = tree->IsExpanded(selected_node);
371     ui::TreeModelNode* parent_node = model->GetParent(selected_node);
372     if (parent_node) {
373       index = model->GetIndexOf(parent_node, selected_node);
374       siblings_count = model->GetChildCount(parent_node);
375     }
376     // Get node depth.
377     depth = 0;
378     while (parent_node) {
379       depth++;
380       parent_node = model->GetParent(parent_node);
381     }
382   }
383
384   AccessibilityTreeItemInfo info(
385       profile, name, context, depth, index, siblings_count, children_count,
386       is_expanded);
387   SendControlAccessibilityNotification(event, &info);
388 }
389
390 // static
391 void AccessibilityEventRouterViews::SendTextfieldNotification(
392     views::View* view,
393     ui::AXEvent event,
394     Profile* profile) {
395   ui::AXViewState state;
396   view->GetAccessibleState(&state);
397   std::string name = base::UTF16ToUTF8(state.name);
398   std::string context = GetViewContext(view);
399   bool password = state.HasStateFlag(ui::AX_STATE_PROTECTED);
400   AccessibilityTextBoxInfo info(profile, name, context, password);
401   std::string value = base::UTF16ToUTF8(state.value);
402   info.SetValue(value, state.selection_start, state.selection_end);
403   SendControlAccessibilityNotification(event, &info);
404 }
405
406 // static
407 void AccessibilityEventRouterViews::SendComboboxNotification(
408     views::View* view,
409     ui::AXEvent event,
410     Profile* profile) {
411   ui::AXViewState state;
412   view->GetAccessibleState(&state);
413   std::string name = base::UTF16ToUTF8(state.name);
414   std::string value = base::UTF16ToUTF8(state.value);
415   std::string context = GetViewContext(view);
416   AccessibilityComboBoxInfo info(
417       profile, name, context, value, state.index, state.count);
418   SendControlAccessibilityNotification(event, &info);
419 }
420
421 // static
422 void AccessibilityEventRouterViews::SendCheckboxNotification(
423     views::View* view,
424     ui::AXEvent event,
425     Profile* profile) {
426   ui::AXViewState state;
427   view->GetAccessibleState(&state);
428   std::string name = base::UTF16ToUTF8(state.name);
429   std::string context = GetViewContext(view);
430   AccessibilityCheckboxInfo info(
431       profile,
432       name,
433       context,
434       state.HasStateFlag(ui::AX_STATE_CHECKED));
435   SendControlAccessibilityNotification(event, &info);
436 }
437
438 // static
439 void AccessibilityEventRouterViews::SendWindowNotification(
440     views::View* view,
441     ui::AXEvent event,
442     Profile* profile) {
443   ui::AXViewState state;
444   view->GetAccessibleState(&state);
445   std::string window_text;
446
447   // If it's an alert, try to get the text from the contents of the
448   // static text, not the window title.
449   if (state.role == ui::AX_ROLE_ALERT)
450     window_text = RecursiveGetStaticText(view);
451
452   // Otherwise get it from the window's accessible name.
453   if (window_text.empty())
454     window_text = base::UTF16ToUTF8(state.name);
455
456   AccessibilityWindowInfo info(profile, window_text);
457   SendWindowAccessibilityNotification(event, &info);
458 }
459
460 // static
461 void AccessibilityEventRouterViews::SendSliderNotification(
462     views::View* view,
463     ui::AXEvent event,
464     Profile* profile) {
465   ui::AXViewState state;
466   view->GetAccessibleState(&state);
467
468   std::string name = base::UTF16ToUTF8(state.name);
469   std::string value = base::UTF16ToUTF8(state.value);
470   std::string context = GetViewContext(view);
471   AccessibilitySliderInfo info(
472       profile,
473       name,
474       context,
475       value);
476   SendControlAccessibilityNotification(event, &info);
477 }
478
479 // static
480 void AccessibilityEventRouterViews::SendAlertControlNotification(
481     views::View* view,
482     ui::AXEvent event,
483     Profile* profile) {
484   ui::AXViewState state;
485   view->GetAccessibleState(&state);
486
487   std::string name = base::UTF16ToUTF8(state.name);
488   AccessibilityAlertInfo info(
489       profile,
490       name);
491   SendControlAccessibilityNotification(event, &info);
492 }
493
494 // static
495 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
496   ui::AXViewState state;
497   view->GetAccessibleState(&state);
498   return base::UTF16ToUTF8(state.name);
499 }
500
501 // static
502 std::string AccessibilityEventRouterViews::GetViewContext(views::View* view) {
503   for (views::View* parent = view->parent();
504        parent;
505        parent = parent->parent()) {
506     ui::AXViewState state;
507     parent->GetAccessibleState(&state);
508
509     // Two cases are handled right now. More could be added in the future
510     // depending on how the UI evolves.
511
512     // A control inside of alert, toolbar or dialog should use that container's
513     // accessible name.
514     if ((state.role == ui::AX_ROLE_ALERT ||
515          state.role == ui::AX_ROLE_DIALOG ||
516          state.role == ui::AX_ROLE_TOOLBAR) &&
517         !state.name.empty()) {
518       return base::UTF16ToUTF8(state.name);
519     }
520
521     // A control inside of an alert or dialog (including an infobar)
522     // should grab the first static text descendant as the context;
523     // that's the prompt.
524     if (state.role == ui::AX_ROLE_ALERT ||
525         state.role == ui::AX_ROLE_DIALOG) {
526       views::View* static_text_child = FindDescendantWithAccessibleRole(
527           parent, ui::AX_ROLE_STATIC_TEXT);
528       if (static_text_child) {
529         ui::AXViewState state;
530         static_text_child->GetAccessibleState(&state);
531         if (!state.name.empty())
532           return base::UTF16ToUTF8(state.name);
533       }
534       return std::string();
535     }
536   }
537
538   return std::string();
539 }
540
541 // static
542 views::View* AccessibilityEventRouterViews::FindDescendantWithAccessibleRole(
543     views::View* view, ui::AXRole role) {
544   ui::AXViewState state;
545   view->GetAccessibleState(&state);
546   if (state.role == role)
547     return view;
548
549   for (int i = 0; i < view->child_count(); i++) {
550     views::View* child = view->child_at(i);
551     views::View* result = FindDescendantWithAccessibleRole(child, role);
552     if (result)
553       return result;
554   }
555
556   return NULL;
557 }
558
559 // static
560 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
561     views::View* menu,
562     views::View* item,
563     int* index,
564     int* count) {
565   for (int i = 0; i < menu->child_count(); ++i) {
566     views::View* child = menu->child_at(i);
567     if (!child->visible())
568       continue;
569
570     int previous_count = *count;
571     RecursiveGetMenuItemIndexAndCount(child, item, index, count);
572     ui::AXViewState state;
573     child->GetAccessibleState(&state);
574     if (state.role == ui::AX_ROLE_MENU_ITEM &&
575         *count == previous_count) {
576       if (item == child)
577         *index = *count;
578       (*count)++;
579     } else if (state.role == ui::AX_ROLE_BUTTON) {
580       if (item == child)
581         *index = *count;
582       (*count)++;
583     }
584   }
585 }
586
587 // static
588 std::string AccessibilityEventRouterViews::RecursiveGetStaticText(
589     views::View* view) {
590   ui::AXViewState state;
591   view->GetAccessibleState(&state);
592   if (state.role == ui::AX_ROLE_STATIC_TEXT)
593     return base::UTF16ToUTF8(state.name);
594
595   for (int i = 0; i < view->child_count(); ++i) {
596     views::View* child = view->child_at(i);
597     std::string result = RecursiveGetStaticText(child);
598     if (!result.empty())
599       return result;
600   }
601   return std::string();
602 }
603
604 // static
605 views::View* AccessibilityEventRouterViews::FindFirstAccessibleAncestor(
606     views::View* view) {
607   views::View* temp_view = view;
608   while (temp_view->parent() && !temp_view->IsAccessibilityFocusable())
609     temp_view = temp_view->parent();
610   if (temp_view->IsAccessibilityFocusable())
611     return temp_view;
612   return view;
613 }