- add sources.
[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/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/focus/view_storage.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/widget.h"
24
25 using views::FocusManager;
26
27 AccessibilityEventRouterViews::AccessibilityEventRouterViews()
28     : most_recent_profile_(NULL) {
29   // Register for notification when profile is destroyed to ensure that all
30   // observers are detatched at that time.
31   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
32                  content::NotificationService::AllSources());
33 }
34
35 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
36 }
37
38 // static
39 AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
40   return Singleton<AccessibilityEventRouterViews>::get();
41 }
42
43 void AccessibilityEventRouterViews::HandleAccessibilityEvent(
44     views::View* view, ui::AccessibilityTypes::Event event_type) {
45   if (!ExtensionAccessibilityEventRouter::GetInstance()->
46       IsAccessibilityEnabled()) {
47     return;
48   }
49
50   if (event_type == ui::AccessibilityTypes::EVENT_TEXT_CHANGED ||
51       event_type == ui::AccessibilityTypes::EVENT_SELECTION_CHANGED) {
52     // These two events should only be sent for views that have focus. This
53     // enforces the invariant that we fire events triggered by user action and
54     // not by programmatic logic. For example, the location bar can be updated
55     // by javascript while the user focus is within some other part of the
56     // user interface. In contrast, the other supported events here do not
57     // depend on focus. For example, a menu within a menubar can open or close
58     // while focus is within the location bar or anywhere else as a result of
59     // user action. Note that the below logic can at some point be removed if
60     // we pass more information along to the listener such as focused state.
61     if (!view->GetFocusManager() ||
62         view->GetFocusManager()->GetFocusedView() != view)
63       return;
64   }
65
66   // Don't dispatch the accessibility event until the next time through the
67   // event loop, to handle cases where the view's state changes after
68   // the call to post the event. It's safe to use base::Unretained(this)
69   // because AccessibilityEventRouterViews is a singleton.
70   views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
71   int view_storage_id = view_storage->CreateStorageID();
72   view_storage->StoreView(view_storage_id, view);
73   base::MessageLoop::current()->PostTask(
74       FROM_HERE,
75       base::Bind(
76           &AccessibilityEventRouterViews::DispatchEventOnViewStorageId,
77           view_storage_id,
78           event_type));
79 }
80
81 void AccessibilityEventRouterViews::HandleMenuItemFocused(
82     const string16& menu_name,
83     const string16& menu_item_name,
84     int item_index,
85     int item_count,
86     bool has_submenu) {
87   if (!ExtensionAccessibilityEventRouter::GetInstance()->
88       IsAccessibilityEnabled()) {
89     return;
90   }
91
92   if (!most_recent_profile_)
93     return;
94
95   AccessibilityMenuItemInfo info(most_recent_profile_,
96                                  UTF16ToUTF8(menu_item_name),
97                                  UTF16ToUTF8(menu_name),
98                                  has_submenu,
99                                  item_index,
100                                  item_count);
101   SendControlAccessibilityNotification(
102       ui::AccessibilityTypes::EVENT_FOCUS, &info);
103 }
104
105 void AccessibilityEventRouterViews::Observe(
106     int type,
107     const content::NotificationSource& source,
108     const content::NotificationDetails& details) {
109   DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_DESTROYED);
110   Profile* profile = content::Source<Profile>(source).ptr();
111   if (profile == most_recent_profile_)
112     most_recent_profile_ = NULL;
113 }
114
115 //
116 // Private methods
117 //
118
119 void AccessibilityEventRouterViews::DispatchEventOnViewStorageId(
120     int view_storage_id,
121     ui::AccessibilityTypes::Event type) {
122   views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
123   views::View* view = view_storage->RetrieveView(view_storage_id);
124   view_storage->RemoveView(view_storage_id);
125   if (!view)
126     return;
127
128   AccessibilityEventRouterViews* instance =
129       AccessibilityEventRouterViews::GetInstance();
130   instance->DispatchAccessibilityEvent(view, type);
131 }
132
133 void AccessibilityEventRouterViews::DispatchAccessibilityEvent(
134     views::View* view, ui::AccessibilityTypes::Event type) {
135   // Get the profile associated with this view. If it's not found, use
136   // the most recent profile where accessibility events were sent, or
137   // the default profile.
138   Profile* profile = NULL;
139   views::Widget* widget = view->GetWidget();
140   if (widget) {
141     profile = reinterpret_cast<Profile*>(
142         widget->GetNativeWindowProperty(Profile::kProfileKey));
143   }
144   if (!profile)
145     profile = most_recent_profile_;
146   if (!profile) {
147     if (g_browser_process->profile_manager())
148       profile = g_browser_process->profile_manager()->GetLastUsedProfile();
149   }
150   if (!profile) {
151     LOG(WARNING) << "Accessibility notification but no profile";
152     return;
153   }
154
155   most_recent_profile_ = profile;
156
157   if (type == ui::AccessibilityTypes::EVENT_MENUSTART ||
158       type == ui::AccessibilityTypes::EVENT_MENUPOPUPSTART ||
159       type == ui::AccessibilityTypes::EVENT_MENUEND ||
160       type == ui::AccessibilityTypes::EVENT_MENUPOPUPEND) {
161     SendMenuNotification(view, type, profile);
162     return;
163   }
164
165   ui::AccessibleViewState state;
166   view->GetAccessibleState(&state);
167
168   switch (state.role) {
169   case ui::AccessibilityTypes::ROLE_ALERT:
170   case ui::AccessibilityTypes::ROLE_WINDOW:
171     SendWindowNotification(view, type, profile);
172     break;
173   case ui::AccessibilityTypes::ROLE_BUTTONMENU:
174   case ui::AccessibilityTypes::ROLE_MENUBAR:
175   case ui::AccessibilityTypes::ROLE_MENUPOPUP:
176     SendMenuNotification(view, type, profile);
177     break;
178   case ui::AccessibilityTypes::ROLE_BUTTONDROPDOWN:
179   case ui::AccessibilityTypes::ROLE_PUSHBUTTON:
180     SendButtonNotification(view, type, profile);
181     break;
182   case ui::AccessibilityTypes::ROLE_CHECKBUTTON:
183     SendCheckboxNotification(view, type, profile);
184     break;
185   case ui::AccessibilityTypes::ROLE_COMBOBOX:
186     SendComboboxNotification(view, type, profile);
187     break;
188   case ui::AccessibilityTypes::ROLE_LINK:
189     SendLinkNotification(view, type, profile);
190     break;
191   case ui::AccessibilityTypes::ROLE_LOCATION_BAR:
192   case ui::AccessibilityTypes::ROLE_TEXT:
193     SendTextfieldNotification(view, type, profile);
194     break;
195   case ui::AccessibilityTypes::ROLE_MENUITEM:
196     SendMenuItemNotification(view, type, profile);
197     break;
198   case ui::AccessibilityTypes::ROLE_RADIOBUTTON:
199     // Not used anymore?
200   case ui::AccessibilityTypes::ROLE_SLIDER:
201     SendSliderNotification(view, type, profile);
202     break;
203   default:
204     // If this is encountered, please file a bug with the role that wasn't
205     // caught so we can add accessibility extension API support.
206     NOTREACHED();
207   }
208 }
209
210 // static
211 void AccessibilityEventRouterViews::SendButtonNotification(
212     views::View* view,
213     ui::AccessibilityTypes::Event event,
214     Profile* profile) {
215   AccessibilityButtonInfo info(
216       profile, GetViewName(view), GetViewContext(view));
217   SendControlAccessibilityNotification(event, &info);
218 }
219
220 // static
221 void AccessibilityEventRouterViews::SendLinkNotification(
222     views::View* view,
223     ui::AccessibilityTypes::Event event,
224     Profile* profile) {
225   AccessibilityLinkInfo info(profile, GetViewName(view), GetViewContext(view));
226   SendControlAccessibilityNotification(event, &info);
227 }
228
229 // static
230 void AccessibilityEventRouterViews::SendMenuNotification(
231     views::View* view,
232     ui::AccessibilityTypes::Event event,
233     Profile* profile) {
234   AccessibilityMenuInfo info(profile, GetViewName(view));
235   SendMenuAccessibilityNotification(event, &info);
236 }
237
238 // static
239 void AccessibilityEventRouterViews::SendMenuItemNotification(
240     views::View* view,
241     ui::AccessibilityTypes::Event event,
242     Profile* profile) {
243   std::string name = GetViewName(view);
244   std::string context = GetViewContext(view);
245
246   bool has_submenu = false;
247   int index = -1;
248   int count = -1;
249
250   if (!strcmp(view->GetClassName(), views::MenuItemView::kViewClassName))
251     has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();
252
253   views::View* parent_menu = view->parent();
254   while (parent_menu != NULL && strcmp(parent_menu->GetClassName(),
255                                        views::SubmenuView::kViewClassName)) {
256     parent_menu = parent_menu->parent();
257   }
258   if (parent_menu) {
259     count = 0;
260     RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
261   }
262
263   AccessibilityMenuItemInfo info(
264       profile, name, context, has_submenu, index, count);
265   SendControlAccessibilityNotification(event, &info);
266 }
267
268 // static
269 void AccessibilityEventRouterViews::SendTextfieldNotification(
270     views::View* view,
271     ui::AccessibilityTypes::Event event,
272     Profile* profile) {
273   ui::AccessibleViewState state;
274   view->GetAccessibleState(&state);
275   std::string name = UTF16ToUTF8(state.name);
276   std::string context = GetViewContext(view);
277   bool password =
278       (state.state & ui::AccessibilityTypes::STATE_PROTECTED) != 0;
279   AccessibilityTextBoxInfo info(profile, name, context, password);
280   std::string value = UTF16ToUTF8(state.value);
281   info.SetValue(value, state.selection_start, state.selection_end);
282   SendControlAccessibilityNotification(event, &info);
283 }
284
285 // static
286 void AccessibilityEventRouterViews::SendComboboxNotification(
287     views::View* view,
288     ui::AccessibilityTypes::Event event,
289     Profile* profile) {
290   ui::AccessibleViewState state;
291   view->GetAccessibleState(&state);
292   std::string name = UTF16ToUTF8(state.name);
293   std::string value = UTF16ToUTF8(state.value);
294   std::string context = GetViewContext(view);
295   AccessibilityComboBoxInfo info(
296       profile, name, context, value, state.index, state.count);
297   SendControlAccessibilityNotification(event, &info);
298 }
299
300 // static
301 void AccessibilityEventRouterViews::SendCheckboxNotification(
302     views::View* view,
303     ui::AccessibilityTypes::Event event,
304     Profile* profile) {
305   ui::AccessibleViewState state;
306   view->GetAccessibleState(&state);
307   std::string name = UTF16ToUTF8(state.name);
308   std::string context = GetViewContext(view);
309   AccessibilityCheckboxInfo info(
310       profile,
311       name,
312       context,
313       state.state == ui::AccessibilityTypes::STATE_CHECKED);
314   SendControlAccessibilityNotification(event, &info);
315 }
316
317 // static
318 void AccessibilityEventRouterViews::SendWindowNotification(
319     views::View* view,
320     ui::AccessibilityTypes::Event event,
321     Profile* profile) {
322   ui::AccessibleViewState state;
323   view->GetAccessibleState(&state);
324   std::string window_text;
325
326   // If it's an alert, try to get the text from the contents of the
327   // static text, not the window title.
328   if (state.role == ui::AccessibilityTypes::ROLE_ALERT)
329     window_text = RecursiveGetStaticText(view);
330
331   // Otherwise get it from the window's accessible name.
332   if (window_text.empty())
333     window_text = UTF16ToUTF8(state.name);
334
335   AccessibilityWindowInfo info(profile, window_text);
336   SendWindowAccessibilityNotification(event, &info);
337 }
338
339 // static
340 void AccessibilityEventRouterViews::SendSliderNotification(
341     views::View* view,
342     ui::AccessibilityTypes::Event event,
343     Profile* profile) {
344   ui::AccessibleViewState state;
345   view->GetAccessibleState(&state);
346
347   std::string name = UTF16ToUTF8(state.name);
348   std::string value = UTF16ToUTF8(state.value);
349   std::string context = GetViewContext(view);
350   AccessibilitySliderInfo info(
351       profile,
352       name,
353       context,
354       value);
355   SendControlAccessibilityNotification(event, &info);
356 }
357
358 // static
359 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
360   ui::AccessibleViewState state;
361   view->GetAccessibleState(&state);
362   return UTF16ToUTF8(state.name);
363 }
364
365 // static
366 std::string AccessibilityEventRouterViews::GetViewContext(views::View* view) {
367   for (views::View* parent = view->parent();
368        parent;
369        parent = parent->parent()) {
370     ui::AccessibleViewState state;
371     parent->GetAccessibleState(&state);
372
373     // Two cases are handled right now. More could be added in the future
374     // depending on how the UI evolves.
375
376     // A control in a toolbar should use the toolbar's accessible name
377     // as the context.
378     if (state.role == ui::AccessibilityTypes::ROLE_TOOLBAR &&
379         !state.name.empty()) {
380       return UTF16ToUTF8(state.name);
381     }
382
383     // A control inside of an alert or dialog (including an infobar)
384     // should grab the first static text descendant as the context;
385     // that's the prompt.
386     if (state.role == ui::AccessibilityTypes::ROLE_ALERT ||
387         state.role == ui::AccessibilityTypes::ROLE_DIALOG) {
388       views::View* static_text_child = FindDescendantWithAccessibleRole(
389           parent, ui::AccessibilityTypes::ROLE_STATICTEXT);
390       if (static_text_child) {
391         ui::AccessibleViewState state;
392         static_text_child->GetAccessibleState(&state);
393         if (!state.name.empty())
394           return UTF16ToUTF8(state.name);
395       }
396       return std::string();
397     }
398   }
399
400   return std::string();
401 }
402
403 // static
404 views::View* AccessibilityEventRouterViews::FindDescendantWithAccessibleRole(
405     views::View* view, ui::AccessibilityTypes::Role role) {
406   ui::AccessibleViewState state;
407   view->GetAccessibleState(&state);
408   if (state.role == role)
409     return view;
410
411   for (int i = 0; i < view->child_count(); i++) {
412     views::View* child = view->child_at(i);
413     views::View* result = FindDescendantWithAccessibleRole(child, role);
414     if (result)
415       return result;
416   }
417
418   return NULL;
419 }
420
421 // static
422 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
423     views::View* menu,
424     views::View* item,
425     int* index,
426     int* count) {
427   for (int i = 0; i < menu->child_count(); ++i) {
428     views::View* child = menu->child_at(i);
429     int previous_count = *count;
430     RecursiveGetMenuItemIndexAndCount(child, item, index, count);
431     ui::AccessibleViewState state;
432     child->GetAccessibleState(&state);
433     if (state.role == ui::AccessibilityTypes::ROLE_MENUITEM &&
434         *count == previous_count) {
435       if (item == child)
436         *index = *count;
437       (*count)++;
438     } else if (state.role == ui::AccessibilityTypes::ROLE_PUSHBUTTON) {
439       if (item == child)
440         *index = *count;
441       (*count)++;
442     }
443   }
444 }
445
446 // static
447 std::string AccessibilityEventRouterViews::RecursiveGetStaticText(
448     views::View* view) {
449   ui::AccessibleViewState state;
450   view->GetAccessibleState(&state);
451   if (state.role == ui::AccessibilityTypes::ROLE_STATICTEXT)
452     return UTF16ToUTF8(state.name);
453
454   for (int i = 0; i < view->child_count(); ++i) {
455     views::View* child = view->child_at(i);
456     std::string result = RecursiveGetStaticText(child);
457     if (!result.empty())
458       return result;
459   }
460   return std::string();
461 }