Add a minor text member to ui::MenuModel.
[chromium-blink-merge.git] / chrome / browser / ui / views / accessibility / accessibility_event_router_views.cc
blob931cf0d61e9e63b6d4eb941e833b9ed5cd4e1295
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/focus/view_storage.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/widget.h"
25 using views::FocusManager;
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());
35 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
38 // static
39 AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
40 return Singleton<AccessibilityEventRouterViews>::get();
43 void AccessibilityEventRouterViews::HandleAccessibilityEvent(
44 views::View* view, ui::AccessibilityTypes::Event event_type) {
45 if (!ExtensionAccessibilityEventRouter::GetInstance()->
46 IsAccessibilityEnabled()) {
47 return;
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;
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));
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;
92 if (!most_recent_profile_)
93 return;
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);
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;
116 // Private methods
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;
128 AccessibilityEventRouterViews* instance =
129 AccessibilityEventRouterViews::GetInstance();
130 instance->DispatchAccessibilityEvent(view, type);
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));
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();
150 if (!profile) {
151 LOG(WARNING) << "Accessibility notification but no profile";
152 return;
155 most_recent_profile_ = profile;
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;
165 ui::AccessibleViewState state;
166 view->GetAccessibleState(&state);
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();
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);
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);
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);
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);
246 bool has_submenu = false;
247 int index = -1;
248 int count = -1;
250 if (!strcmp(view->GetClassName(), views::MenuItemView::kViewClassName))
251 has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();
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();
258 if (parent_menu) {
259 count = 0;
260 RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
263 AccessibilityMenuItemInfo info(
264 profile, name, context, has_submenu, index, count);
265 SendControlAccessibilityNotification(event, &info);
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);
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);
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);
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;
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);
331 // Otherwise get it from the window's accessible name.
332 if (window_text.empty())
333 window_text = UTF16ToUTF8(state.name);
335 AccessibilityWindowInfo info(profile, window_text);
336 SendWindowAccessibilityNotification(event, &info);
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);
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);
358 // static
359 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
360 ui::AccessibleViewState state;
361 view->GetAccessibleState(&state);
362 return UTF16ToUTF8(state.name);
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);
373 // Two cases are handled right now. More could be added in the future
374 // depending on how the UI evolves.
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);
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);
396 return std::string();
400 return std::string();
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;
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;
418 return NULL;
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)++;
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);
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;
460 return std::string();