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() {
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()) {
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
)
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(
76 &AccessibilityEventRouterViews::DispatchEventOnViewStorageId
,
81 void AccessibilityEventRouterViews::HandleMenuItemFocused(
82 const string16
& menu_name
,
83 const string16
& menu_item_name
,
87 if (!ExtensionAccessibilityEventRouter::GetInstance()->
88 IsAccessibilityEnabled()) {
92 if (!most_recent_profile_
)
95 AccessibilityMenuItemInfo
info(most_recent_profile_
,
96 UTF16ToUTF8(menu_item_name
),
97 UTF16ToUTF8(menu_name
),
101 SendControlAccessibilityNotification(
102 ui::AccessibilityTypes::EVENT_FOCUS
, &info
);
105 void AccessibilityEventRouterViews::Observe(
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
;
119 void AccessibilityEventRouterViews::DispatchEventOnViewStorageId(
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
);
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();
141 profile
= reinterpret_cast<Profile
*>(
142 widget
->GetNativeWindowProperty(Profile::kProfileKey
));
145 profile
= most_recent_profile_
;
147 if (g_browser_process
->profile_manager())
148 profile
= g_browser_process
->profile_manager()->GetLastUsedProfile();
151 LOG(WARNING
) << "Accessibility notification but no profile";
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
);
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
);
173 case ui::AccessibilityTypes::ROLE_BUTTONMENU
:
174 case ui::AccessibilityTypes::ROLE_MENUBAR
:
175 case ui::AccessibilityTypes::ROLE_MENUPOPUP
:
176 SendMenuNotification(view
, type
, profile
);
178 case ui::AccessibilityTypes::ROLE_BUTTONDROPDOWN
:
179 case ui::AccessibilityTypes::ROLE_PUSHBUTTON
:
180 SendButtonNotification(view
, type
, profile
);
182 case ui::AccessibilityTypes::ROLE_CHECKBUTTON
:
183 SendCheckboxNotification(view
, type
, profile
);
185 case ui::AccessibilityTypes::ROLE_COMBOBOX
:
186 SendComboboxNotification(view
, type
, profile
);
188 case ui::AccessibilityTypes::ROLE_LINK
:
189 SendLinkNotification(view
, type
, profile
);
191 case ui::AccessibilityTypes::ROLE_LOCATION_BAR
:
192 case ui::AccessibilityTypes::ROLE_TEXT
:
193 SendTextfieldNotification(view
, type
, profile
);
195 case ui::AccessibilityTypes::ROLE_MENUITEM
:
196 SendMenuItemNotification(view
, type
, profile
);
198 case ui::AccessibilityTypes::ROLE_RADIOBUTTON
:
200 case ui::AccessibilityTypes::ROLE_SLIDER
:
201 SendSliderNotification(view
, type
, profile
);
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.
211 void AccessibilityEventRouterViews::SendButtonNotification(
213 ui::AccessibilityTypes::Event event
,
215 AccessibilityButtonInfo
info(
216 profile
, GetViewName(view
), GetViewContext(view
));
217 SendControlAccessibilityNotification(event
, &info
);
221 void AccessibilityEventRouterViews::SendLinkNotification(
223 ui::AccessibilityTypes::Event event
,
225 AccessibilityLinkInfo
info(profile
, GetViewName(view
), GetViewContext(view
));
226 SendControlAccessibilityNotification(event
, &info
);
230 void AccessibilityEventRouterViews::SendMenuNotification(
232 ui::AccessibilityTypes::Event event
,
234 AccessibilityMenuInfo
info(profile
, GetViewName(view
));
235 SendMenuAccessibilityNotification(event
, &info
);
239 void AccessibilityEventRouterViews::SendMenuItemNotification(
241 ui::AccessibilityTypes::Event event
,
243 std::string name
= GetViewName(view
);
244 std::string context
= GetViewContext(view
);
246 bool has_submenu
= false;
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();
260 RecursiveGetMenuItemIndexAndCount(parent_menu
, view
, &index
, &count
);
263 AccessibilityMenuItemInfo
info(
264 profile
, name
, context
, has_submenu
, index
, count
);
265 SendControlAccessibilityNotification(event
, &info
);
269 void AccessibilityEventRouterViews::SendTextfieldNotification(
271 ui::AccessibilityTypes::Event event
,
273 ui::AccessibleViewState state
;
274 view
->GetAccessibleState(&state
);
275 std::string name
= UTF16ToUTF8(state
.name
);
276 std::string context
= GetViewContext(view
);
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
);
286 void AccessibilityEventRouterViews::SendComboboxNotification(
288 ui::AccessibilityTypes::Event event
,
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
);
301 void AccessibilityEventRouterViews::SendCheckboxNotification(
303 ui::AccessibilityTypes::Event event
,
305 ui::AccessibleViewState state
;
306 view
->GetAccessibleState(&state
);
307 std::string name
= UTF16ToUTF8(state
.name
);
308 std::string context
= GetViewContext(view
);
309 AccessibilityCheckboxInfo
info(
313 state
.state
== ui::AccessibilityTypes::STATE_CHECKED
);
314 SendControlAccessibilityNotification(event
, &info
);
318 void AccessibilityEventRouterViews::SendWindowNotification(
320 ui::AccessibilityTypes::Event event
,
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
);
340 void AccessibilityEventRouterViews::SendSliderNotification(
342 ui::AccessibilityTypes::Event event
,
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(
355 SendControlAccessibilityNotification(event
, &info
);
359 std::string
AccessibilityEventRouterViews::GetViewName(views::View
* view
) {
360 ui::AccessibleViewState state
;
361 view
->GetAccessibleState(&state
);
362 return UTF16ToUTF8(state
.name
);
366 std::string
AccessibilityEventRouterViews::GetViewContext(views::View
* view
) {
367 for (views::View
* parent
= view
->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
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();
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
)
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
);
422 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
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
) {
438 } else if (state
.role
== ui::AccessibilityTypes::ROLE_PUSHBUTTON
) {
447 std::string
AccessibilityEventRouterViews::RecursiveGetStaticText(
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
);
460 return std::string();