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/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"
26 using views::FocusManager
;
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());
36 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
40 AccessibilityEventRouterViews
* AccessibilityEventRouterViews::GetInstance() {
41 return Singleton
<AccessibilityEventRouterViews
>::get();
44 void AccessibilityEventRouterViews::HandleAccessibilityEvent(
45 views::View
* view
, ui::AXEvent event_type
) {
46 if (!ExtensionAccessibilityEventRouter::GetInstance()->
47 IsAccessibilityEnabled()) {
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
)
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(
77 &AccessibilityEventRouterViews::DispatchEventOnViewStorageId
,
82 void AccessibilityEventRouterViews::HandleMenuItemFocused(
83 const base::string16
& menu_name
,
84 const base::string16
& menu_item_name
,
88 if (!ExtensionAccessibilityEventRouter::GetInstance()->
89 IsAccessibilityEnabled()) {
93 if (!most_recent_profile_
)
96 AccessibilityMenuItemInfo
info(most_recent_profile_
,
97 base::UTF16ToUTF8(menu_item_name
),
98 base::UTF16ToUTF8(menu_name
),
102 SendControlAccessibilityNotification(
103 ui::AX_EVENT_FOCUS
, &info
);
106 void AccessibilityEventRouterViews::Observe(
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
;
120 void AccessibilityEventRouterViews::DispatchEventOnViewStorageId(
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
);
129 AccessibilityEventRouterViews
* instance
=
130 AccessibilityEventRouterViews::GetInstance();
131 instance
->DispatchAccessibilityEvent(view
, 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();
142 profile
= reinterpret_cast<Profile
*>(
143 widget
->GetNativeWindowProperty(Profile::kProfileKey
));
146 profile
= most_recent_profile_
;
148 if (g_browser_process
->profile_manager())
149 profile
= g_browser_process
->profile_manager()->GetLastUsedProfile();
152 LOG(WARNING
) << "Accessibility notification but no profile";
156 most_recent_profile_
= profile
;
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
);
166 ui::AXViewState state
;
167 view
->GetAccessibleState(&state
);
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
);
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
);
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
);
187 case ui::AX_ROLE_BUTTON_DROP_DOWN
:
188 case ui::AX_ROLE_BUTTON
:
189 SendButtonNotification(view
, type
, profile
);
191 case ui::AX_ROLE_CHECK_BOX
:
192 SendCheckboxNotification(view
, type
, profile
);
194 case ui::AX_ROLE_COMBO_BOX
:
195 SendComboboxNotification(view
, type
, profile
);
197 case ui::AX_ROLE_LINK
:
198 SendLinkNotification(view
, type
, profile
);
200 case ui::AX_ROLE_LOCATION_BAR
:
201 case ui::AX_ROLE_TEXT_FIELD
:
202 SendTextfieldNotification(view
, type
, profile
);
204 case ui::AX_ROLE_MENU_ITEM
:
205 SendMenuItemNotification(view
, type
, profile
);
207 case ui::AX_ROLE_RADIO_BUTTON
:
209 case ui::AX_ROLE_SLIDER
:
210 SendSliderNotification(view
, type
, profile
);
212 case ui::AX_ROLE_TREE
:
213 SendTreeNotification(view
, type
, profile
);
215 case ui::AX_ROLE_TREE_ITEM
:
216 SendTreeItemNotification(view
, type
, profile
);
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.
226 void AccessibilityEventRouterViews::SendButtonNotification(
230 AccessibilityButtonInfo
info(
231 profile
, GetViewName(view
), GetViewContext(view
));
232 SendControlAccessibilityNotification(event
, &info
);
236 void AccessibilityEventRouterViews::SendLinkNotification(
240 AccessibilityLinkInfo
info(profile
, GetViewName(view
), GetViewContext(view
));
241 SendControlAccessibilityNotification(event
, &info
);
245 void AccessibilityEventRouterViews::SendMenuNotification(
249 AccessibilityMenuInfo
info(profile
, GetViewName(view
));
250 SendMenuAccessibilityNotification(event
, &info
);
254 void AccessibilityEventRouterViews::SendMenuItemNotification(
258 std::string name
= GetViewName(view
);
259 std::string context
= GetViewContext(view
);
261 bool has_submenu
= false;
265 if (!strcmp(view
->GetClassName(), views::MenuItemView::kViewClassName
))
266 has_submenu
= static_cast<views::MenuItemView
*>(view
)->HasSubmenu();
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();
275 RecursiveGetMenuItemIndexAndCount(parent_menu
, view
, &index
, &count
);
278 AccessibilityMenuItemInfo
info(
279 profile
, name
, context
, has_submenu
, index
, count
);
280 SendControlAccessibilityNotification(event
, &info
);
284 void AccessibilityEventRouterViews::SendTreeNotification(
288 AccessibilityTreeInfo
info(profile
, GetViewName(view
));
289 SendControlAccessibilityNotification(event
, &info
);
293 void AccessibilityEventRouterViews::SendTreeItemNotification(
297 std::string name
= GetViewName(view
);
298 std::string context
= GetViewContext(view
);
300 if (strcmp(view
->GetClassName(), views::TreeView::kViewClassName
) != 0) {
305 views::TreeView
* tree
= static_cast<views::TreeView
*>(view
);
306 ui::TreeModelNode
* selected_node
= tree
->GetSelectedNode();
307 ui::TreeModel
* model
= tree
->model();
309 int siblings_count
= model
->GetChildCount(model
->GetRoot());
310 int children_count
= -1;
313 bool is_expanded
= false;
316 children_count
= model
->GetChildCount(selected_node
);
317 is_expanded
= tree
->IsExpanded(selected_node
);
318 ui::TreeModelNode
* parent_node
= model
->GetParent(selected_node
);
320 index
= model
->GetIndexOf(parent_node
, selected_node
);
321 siblings_count
= model
->GetChildCount(parent_node
);
325 while (parent_node
) {
327 parent_node
= model
->GetParent(parent_node
);
331 AccessibilityTreeItemInfo
info(
332 profile
, name
, context
, depth
, index
, siblings_count
, children_count
,
334 SendControlAccessibilityNotification(event
, &info
);
338 void AccessibilityEventRouterViews::SendTextfieldNotification(
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
);
354 void AccessibilityEventRouterViews::SendComboboxNotification(
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
);
369 void AccessibilityEventRouterViews::SendCheckboxNotification(
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(
381 state
.HasStateFlag(ui::AX_STATE_CHECKED
));
382 SendControlAccessibilityNotification(event
, &info
);
386 void AccessibilityEventRouterViews::SendWindowNotification(
390 ui::AXViewState state
;
391 view
->GetAccessibleState(&state
);
392 std::string window_text
;
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
);
399 // Otherwise get it from the window's accessible name.
400 if (window_text
.empty())
401 window_text
= base::UTF16ToUTF8(state
.name
);
403 AccessibilityWindowInfo
info(profile
, window_text
);
404 SendWindowAccessibilityNotification(event
, &info
);
408 void AccessibilityEventRouterViews::SendSliderNotification(
412 ui::AXViewState state
;
413 view
->GetAccessibleState(&state
);
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(
423 SendControlAccessibilityNotification(event
, &info
);
427 void AccessibilityEventRouterViews::SendAlertControlNotification(
431 ui::AXViewState state
;
432 view
->GetAccessibleState(&state
);
434 std::string name
= base::UTF16ToUTF8(state
.name
);
435 AccessibilityAlertInfo
info(
438 SendControlAccessibilityNotification(event
, &info
);
442 std::string
AccessibilityEventRouterViews::GetViewName(views::View
* view
) {
443 ui::AXViewState state
;
444 view
->GetAccessibleState(&state
);
445 return base::UTF16ToUTF8(state
.name
);
449 std::string
AccessibilityEventRouterViews::GetViewContext(views::View
* view
) {
450 for (views::View
* parent
= view
->parent();
452 parent
= parent
->parent()) {
453 ui::AXViewState state
;
454 parent
->GetAccessibleState(&state
);
456 // Two cases are handled right now. More could be added in the future
457 // depending on how the UI evolves.
459 // A control inside of alert, toolbar or dialog should use that container's
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
);
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
);
481 return std::string();
485 return std::string();
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
)
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
);
507 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
512 for (int i
= 0; i
< menu
->child_count(); ++i
) {
513 views::View
* child
= menu
->child_at(i
);
514 if (!child
->visible())
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
) {
526 } else if (state
.role
== ui::AX_ROLE_BUTTON
) {
535 std::string
AccessibilityEventRouterViews::RecursiveGetStaticText(
537 ui::AXViewState state
;
538 view
->GetAccessibleState(&state
);
539 if (state
.role
== ui::AX_ROLE_STATIC_TEXT
)
540 return base::UTF16ToUTF8(state
.name
);
542 for (int i
= 0; i
< view
->child_count(); ++i
) {
543 views::View
* child
= view
->child_at(i
);
544 std::string result
= RecursiveGetStaticText(child
);
548 return std::string();