Upstream version 6.35.121.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
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());
34 }
35
36 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
37 }
38
39 // static
40 AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
41   return Singleton<AccessibilityEventRouterViews>::get();
42 }
43
44 void AccessibilityEventRouterViews::HandleAccessibilityEvent(
45     views::View* view, ui::AXEvent event_type) {
46   if (!ExtensionAccessibilityEventRouter::GetInstance()->
47       IsAccessibilityEnabled()) {
48     return;
49   }
50
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;
65   }
66
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));
80 }
81
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;
91   }
92
93   if (!most_recent_profile_)
94     return;
95
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);
104 }
105
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;
114 }
115
116 //
117 // Private methods
118 //
119
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;
128
129   AccessibilityEventRouterViews* instance =
130       AccessibilityEventRouterViews::GetInstance();
131   instance->DispatchAccessibilityEvent(view, type);
132 }
133
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));
144   }
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();
150   }
151   if (!profile) {
152     LOG(WARNING) << "Accessibility notification but no profile";
153     return;
154   }
155
156   most_recent_profile_ = profile;
157
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;
164   }
165
166   ui::AXViewState state;
167   view->GetAccessibleState(&state);
168
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;
174   }
175
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();
222   }
223 }
224
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);
233 }
234
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);
242 }
243
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);
251 }
252
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);
260
261   bool has_submenu = false;
262   int index = -1;
263   int count = -1;
264
265   if (!strcmp(view->GetClassName(), views::MenuItemView::kViewClassName))
266     has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();
267
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();
272   }
273   if (parent_menu) {
274     count = 0;
275     RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
276   }
277
278   AccessibilityMenuItemInfo info(
279       profile, name, context, has_submenu, index, count);
280   SendControlAccessibilityNotification(event, &info);
281 }
282
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);
290 }
291
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);
299
300   if (strcmp(view->GetClassName(), views::TreeView::kViewClassName) != 0) {
301     NOTREACHED();
302     return;
303   }
304
305   views::TreeView* tree = static_cast<views::TreeView*>(view);
306   ui::TreeModelNode* selected_node = tree->GetSelectedNode();
307   ui::TreeModel* model = tree->model();
308
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;
314
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);
322     }
323     // Get node depth.
324     depth = 0;
325     while (parent_node) {
326       depth++;
327       parent_node = model->GetParent(parent_node);
328     }
329   }
330
331   AccessibilityTreeItemInfo info(
332       profile, name, context, depth, index, siblings_count, children_count,
333       is_expanded);
334   SendControlAccessibilityNotification(event, &info);
335 }
336
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);
351 }
352
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);
366 }
367
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);
383 }
384
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;
393
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);
398
399   // Otherwise get it from the window's accessible name.
400   if (window_text.empty())
401     window_text = base::UTF16ToUTF8(state.name);
402
403   AccessibilityWindowInfo info(profile, window_text);
404   SendWindowAccessibilityNotification(event, &info);
405 }
406
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);
414
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);
424 }
425
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);
433
434   std::string name = base::UTF16ToUTF8(state.name);
435   AccessibilityAlertInfo info(
436       profile,
437       name);
438   SendControlAccessibilityNotification(event, &info);
439 }
440
441 // static
442 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
443   ui::AXViewState state;
444   view->GetAccessibleState(&state);
445   return base::UTF16ToUTF8(state.name);
446 }
447
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);
455
456     // Two cases are handled right now. More could be added in the future
457     // depending on how the UI evolves.
458
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);
466     }
467
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);
480       }
481       return std::string();
482     }
483   }
484
485   return std::string();
486 }
487
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;
495
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;
501   }
502
503   return NULL;
504 }
505
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;
516
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)++;
530     }
531   }
532 }
533
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);
541
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;
547   }
548   return std::string();
549 }