• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AccessibilityEventRouterViews()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 
~AccessibilityEventRouterViews()36 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
37 }
38 
39 // static
GetInstance()40 AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
41   return Singleton<AccessibilityEventRouterViews>::get();
42 }
43 
HandleAccessibilityEvent(views::View * view,ui::AXEvent event_type)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 
HandleMenuItemFocused(const base::string16 & menu_name,const base::string16 & menu_item_name,int item_index,int item_count,bool has_submenu)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 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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 
DispatchEventOnViewStorageId(int view_storage_id,ui::AXEvent type)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 
DispatchAccessibilityEvent(views::View * view,ui::AXEvent 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));
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     // Hover events can fire on literally any view, so it's safe to
220     // ignore ones we don't care about.
221     if (type == ui::AX_EVENT_HOVER)
222       break;
223 
224     // If this is encountered, please file a bug with the role that wasn't
225     // caught so we can add accessibility extension API support.
226     NOTREACHED();
227   }
228 }
229 
230 // static
SendButtonNotification(views::View * view,ui::AXEvent event,Profile * profile)231 void AccessibilityEventRouterViews::SendButtonNotification(
232     views::View* view,
233     ui::AXEvent event,
234     Profile* profile) {
235   AccessibilityButtonInfo info(
236       profile, GetViewName(view), GetViewContext(view));
237   SendControlAccessibilityNotification(event, &info);
238 }
239 
240 // static
SendLinkNotification(views::View * view,ui::AXEvent event,Profile * profile)241 void AccessibilityEventRouterViews::SendLinkNotification(
242     views::View* view,
243     ui::AXEvent event,
244     Profile* profile) {
245   AccessibilityLinkInfo info(profile, GetViewName(view), GetViewContext(view));
246   SendControlAccessibilityNotification(event, &info);
247 }
248 
249 // static
SendMenuNotification(views::View * view,ui::AXEvent event,Profile * profile)250 void AccessibilityEventRouterViews::SendMenuNotification(
251     views::View* view,
252     ui::AXEvent event,
253     Profile* profile) {
254   AccessibilityMenuInfo info(profile, GetViewName(view));
255   SendMenuAccessibilityNotification(event, &info);
256 }
257 
258 // static
SendMenuItemNotification(views::View * view,ui::AXEvent event,Profile * profile)259 void AccessibilityEventRouterViews::SendMenuItemNotification(
260     views::View* view,
261     ui::AXEvent event,
262     Profile* profile) {
263   std::string name = GetViewName(view);
264   std::string context = GetViewContext(view);
265 
266   bool has_submenu = false;
267   int index = -1;
268   int count = -1;
269 
270   if (!strcmp(view->GetClassName(), views::MenuItemView::kViewClassName))
271     has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();
272 
273   views::View* parent_menu = view->parent();
274   while (parent_menu != NULL && strcmp(parent_menu->GetClassName(),
275                                        views::SubmenuView::kViewClassName)) {
276     parent_menu = parent_menu->parent();
277   }
278   if (parent_menu) {
279     count = 0;
280     RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
281   }
282 
283   AccessibilityMenuItemInfo info(
284       profile, name, context, has_submenu, index, count);
285   SendControlAccessibilityNotification(event, &info);
286 }
287 
288 // static
SendTreeNotification(views::View * view,ui::AXEvent event,Profile * profile)289 void AccessibilityEventRouterViews::SendTreeNotification(
290     views::View* view,
291     ui::AXEvent event,
292     Profile* profile) {
293   AccessibilityTreeInfo info(profile, GetViewName(view));
294   SendControlAccessibilityNotification(event, &info);
295 }
296 
297 // static
SendTreeItemNotification(views::View * view,ui::AXEvent event,Profile * profile)298 void AccessibilityEventRouterViews::SendTreeItemNotification(
299     views::View* view,
300     ui::AXEvent event,
301     Profile* profile) {
302   std::string name = GetViewName(view);
303   std::string context = GetViewContext(view);
304 
305   if (strcmp(view->GetClassName(), views::TreeView::kViewClassName) != 0) {
306     NOTREACHED();
307     return;
308   }
309 
310   views::TreeView* tree = static_cast<views::TreeView*>(view);
311   ui::TreeModelNode* selected_node = tree->GetSelectedNode();
312   ui::TreeModel* model = tree->model();
313 
314   int siblings_count = model->GetChildCount(model->GetRoot());
315   int children_count = -1;
316   int index = -1;
317   int depth = -1;
318   bool is_expanded = false;
319 
320   if (selected_node) {
321     children_count = model->GetChildCount(selected_node);
322     is_expanded = tree->IsExpanded(selected_node);
323     ui::TreeModelNode* parent_node = model->GetParent(selected_node);
324     if (parent_node) {
325       index = model->GetIndexOf(parent_node, selected_node);
326       siblings_count = model->GetChildCount(parent_node);
327     }
328     // Get node depth.
329     depth = 0;
330     while (parent_node) {
331       depth++;
332       parent_node = model->GetParent(parent_node);
333     }
334   }
335 
336   AccessibilityTreeItemInfo info(
337       profile, name, context, depth, index, siblings_count, children_count,
338       is_expanded);
339   SendControlAccessibilityNotification(event, &info);
340 }
341 
342 // static
SendTextfieldNotification(views::View * view,ui::AXEvent event,Profile * profile)343 void AccessibilityEventRouterViews::SendTextfieldNotification(
344     views::View* view,
345     ui::AXEvent event,
346     Profile* profile) {
347   ui::AXViewState state;
348   view->GetAccessibleState(&state);
349   std::string name = base::UTF16ToUTF8(state.name);
350   std::string context = GetViewContext(view);
351   bool password = state.HasStateFlag(ui::AX_STATE_PROTECTED);
352   AccessibilityTextBoxInfo info(profile, name, context, password);
353   std::string value = base::UTF16ToUTF8(state.value);
354   info.SetValue(value, state.selection_start, state.selection_end);
355   SendControlAccessibilityNotification(event, &info);
356 }
357 
358 // static
SendComboboxNotification(views::View * view,ui::AXEvent event,Profile * profile)359 void AccessibilityEventRouterViews::SendComboboxNotification(
360     views::View* view,
361     ui::AXEvent event,
362     Profile* profile) {
363   ui::AXViewState state;
364   view->GetAccessibleState(&state);
365   std::string name = base::UTF16ToUTF8(state.name);
366   std::string value = base::UTF16ToUTF8(state.value);
367   std::string context = GetViewContext(view);
368   AccessibilityComboBoxInfo info(
369       profile, name, context, value, state.index, state.count);
370   SendControlAccessibilityNotification(event, &info);
371 }
372 
373 // static
SendCheckboxNotification(views::View * view,ui::AXEvent event,Profile * profile)374 void AccessibilityEventRouterViews::SendCheckboxNotification(
375     views::View* view,
376     ui::AXEvent event,
377     Profile* profile) {
378   ui::AXViewState state;
379   view->GetAccessibleState(&state);
380   std::string name = base::UTF16ToUTF8(state.name);
381   std::string context = GetViewContext(view);
382   AccessibilityCheckboxInfo info(
383       profile,
384       name,
385       context,
386       state.HasStateFlag(ui::AX_STATE_CHECKED));
387   SendControlAccessibilityNotification(event, &info);
388 }
389 
390 // static
SendWindowNotification(views::View * view,ui::AXEvent event,Profile * profile)391 void AccessibilityEventRouterViews::SendWindowNotification(
392     views::View* view,
393     ui::AXEvent event,
394     Profile* profile) {
395   ui::AXViewState state;
396   view->GetAccessibleState(&state);
397   std::string window_text;
398 
399   // If it's an alert, try to get the text from the contents of the
400   // static text, not the window title.
401   if (state.role == ui::AX_ROLE_ALERT)
402     window_text = RecursiveGetStaticText(view);
403 
404   // Otherwise get it from the window's accessible name.
405   if (window_text.empty())
406     window_text = base::UTF16ToUTF8(state.name);
407 
408   AccessibilityWindowInfo info(profile, window_text);
409   SendWindowAccessibilityNotification(event, &info);
410 }
411 
412 // static
SendSliderNotification(views::View * view,ui::AXEvent event,Profile * profile)413 void AccessibilityEventRouterViews::SendSliderNotification(
414     views::View* view,
415     ui::AXEvent event,
416     Profile* profile) {
417   ui::AXViewState state;
418   view->GetAccessibleState(&state);
419 
420   std::string name = base::UTF16ToUTF8(state.name);
421   std::string value = base::UTF16ToUTF8(state.value);
422   std::string context = GetViewContext(view);
423   AccessibilitySliderInfo info(
424       profile,
425       name,
426       context,
427       value);
428   SendControlAccessibilityNotification(event, &info);
429 }
430 
431 // static
SendAlertControlNotification(views::View * view,ui::AXEvent event,Profile * profile)432 void AccessibilityEventRouterViews::SendAlertControlNotification(
433     views::View* view,
434     ui::AXEvent event,
435     Profile* profile) {
436   ui::AXViewState state;
437   view->GetAccessibleState(&state);
438 
439   std::string name = base::UTF16ToUTF8(state.name);
440   AccessibilityAlertInfo info(
441       profile,
442       name);
443   SendControlAccessibilityNotification(event, &info);
444 }
445 
446 // static
GetViewName(views::View * view)447 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
448   ui::AXViewState state;
449   view->GetAccessibleState(&state);
450   return base::UTF16ToUTF8(state.name);
451 }
452 
453 // static
GetViewContext(views::View * view)454 std::string AccessibilityEventRouterViews::GetViewContext(views::View* view) {
455   for (views::View* parent = view->parent();
456        parent;
457        parent = parent->parent()) {
458     ui::AXViewState state;
459     parent->GetAccessibleState(&state);
460 
461     // Two cases are handled right now. More could be added in the future
462     // depending on how the UI evolves.
463 
464     // A control inside of alert, toolbar or dialog should use that container's
465     // accessible name.
466     if ((state.role == ui::AX_ROLE_ALERT ||
467          state.role == ui::AX_ROLE_DIALOG ||
468          state.role == ui::AX_ROLE_TOOLBAR) &&
469         !state.name.empty()) {
470       return base::UTF16ToUTF8(state.name);
471     }
472 
473     // A control inside of an alert or dialog (including an infobar)
474     // should grab the first static text descendant as the context;
475     // that's the prompt.
476     if (state.role == ui::AX_ROLE_ALERT ||
477         state.role == ui::AX_ROLE_DIALOG) {
478       views::View* static_text_child = FindDescendantWithAccessibleRole(
479           parent, ui::AX_ROLE_STATIC_TEXT);
480       if (static_text_child) {
481         ui::AXViewState state;
482         static_text_child->GetAccessibleState(&state);
483         if (!state.name.empty())
484           return base::UTF16ToUTF8(state.name);
485       }
486       return std::string();
487     }
488   }
489 
490   return std::string();
491 }
492 
493 // static
FindDescendantWithAccessibleRole(views::View * view,ui::AXRole role)494 views::View* AccessibilityEventRouterViews::FindDescendantWithAccessibleRole(
495     views::View* view, ui::AXRole role) {
496   ui::AXViewState state;
497   view->GetAccessibleState(&state);
498   if (state.role == role)
499     return view;
500 
501   for (int i = 0; i < view->child_count(); i++) {
502     views::View* child = view->child_at(i);
503     views::View* result = FindDescendantWithAccessibleRole(child, role);
504     if (result)
505       return result;
506   }
507 
508   return NULL;
509 }
510 
511 // static
RecursiveGetMenuItemIndexAndCount(views::View * menu,views::View * item,int * index,int * count)512 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
513     views::View* menu,
514     views::View* item,
515     int* index,
516     int* count) {
517   for (int i = 0; i < menu->child_count(); ++i) {
518     views::View* child = menu->child_at(i);
519     if (!child->visible())
520       continue;
521 
522     int previous_count = *count;
523     RecursiveGetMenuItemIndexAndCount(child, item, index, count);
524     ui::AXViewState state;
525     child->GetAccessibleState(&state);
526     if (state.role == ui::AX_ROLE_MENU_ITEM &&
527         *count == previous_count) {
528       if (item == child)
529         *index = *count;
530       (*count)++;
531     } else if (state.role == ui::AX_ROLE_BUTTON) {
532       if (item == child)
533         *index = *count;
534       (*count)++;
535     }
536   }
537 }
538 
539 // static
RecursiveGetStaticText(views::View * view)540 std::string AccessibilityEventRouterViews::RecursiveGetStaticText(
541     views::View* view) {
542   ui::AXViewState state;
543   view->GetAccessibleState(&state);
544   if (state.role == ui::AX_ROLE_STATIC_TEXT)
545     return base::UTF16ToUTF8(state.name);
546 
547   for (int i = 0; i < view->child_count(); ++i) {
548     views::View* child = view->child_at(i);
549     std::string result = RecursiveGetStaticText(child);
550     if (!result.empty())
551       return result;
552   }
553   return std::string();
554 }
555