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/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::AccessibilityTypes::Event event_type
) {
46 if (!ExtensionAccessibilityEventRouter::GetInstance()->
47 IsAccessibilityEnabled()) {
51 if (event_type
== ui::AccessibilityTypes::EVENT_TEXT_CHANGED
||
52 event_type
== ui::AccessibilityTypes::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::AccessibilityTypes::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(
122 ui::AccessibilityTypes::Event 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
);
129 AccessibilityEventRouterViews
* instance
=
130 AccessibilityEventRouterViews::GetInstance();
131 instance
->DispatchAccessibilityEvent(view
, type
);
134 void AccessibilityEventRouterViews::DispatchAccessibilityEvent(
135 views::View
* view
, ui::AccessibilityTypes::Event 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::AccessibilityTypes::EVENT_MENUSTART
||
159 type
== ui::AccessibilityTypes::EVENT_MENUPOPUPSTART
||
160 type
== ui::AccessibilityTypes::EVENT_MENUEND
||
161 type
== ui::AccessibilityTypes::EVENT_MENUPOPUPEND
) {
162 SendMenuNotification(view
, type
, profile
);
166 ui::AccessibleViewState state
;
167 view
->GetAccessibleState(&state
);
169 if (type
== ui::AccessibilityTypes::EVENT_ALERT
&&
170 !(state
.role
== ui::AccessibilityTypes::ROLE_ALERT
||
171 state
.role
== ui::AccessibilityTypes::ROLE_WINDOW
)) {
172 SendAlertControlNotification(view
, type
, profile
);
176 switch (state
.role
) {
177 case ui::AccessibilityTypes::ROLE_ALERT
:
178 case ui::AccessibilityTypes::ROLE_DIALOG
:
179 case ui::AccessibilityTypes::ROLE_WINDOW
:
180 SendWindowNotification(view
, type
, profile
);
182 case ui::AccessibilityTypes::ROLE_BUTTONMENU
:
183 case ui::AccessibilityTypes::ROLE_MENUBAR
:
184 case ui::AccessibilityTypes::ROLE_MENUPOPUP
:
185 SendMenuNotification(view
, type
, profile
);
187 case ui::AccessibilityTypes::ROLE_BUTTONDROPDOWN
:
188 case ui::AccessibilityTypes::ROLE_PUSHBUTTON
:
189 SendButtonNotification(view
, type
, profile
);
191 case ui::AccessibilityTypes::ROLE_CHECKBUTTON
:
192 SendCheckboxNotification(view
, type
, profile
);
194 case ui::AccessibilityTypes::ROLE_COMBOBOX
:
195 SendComboboxNotification(view
, type
, profile
);
197 case ui::AccessibilityTypes::ROLE_LINK
:
198 SendLinkNotification(view
, type
, profile
);
200 case ui::AccessibilityTypes::ROLE_LOCATION_BAR
:
201 case ui::AccessibilityTypes::ROLE_TEXT
:
202 SendTextfieldNotification(view
, type
, profile
);
204 case ui::AccessibilityTypes::ROLE_MENUITEM
:
205 SendMenuItemNotification(view
, type
, profile
);
207 case ui::AccessibilityTypes::ROLE_RADIOBUTTON
:
209 case ui::AccessibilityTypes::ROLE_SLIDER
:
210 SendSliderNotification(view
, type
, profile
);
212 case ui::AccessibilityTypes::ROLE_OUTLINE
:
213 SendTreeNotification(view
, type
, profile
);
215 case ui::AccessibilityTypes::ROLE_OUTLINEITEM
:
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(
228 ui::AccessibilityTypes::Event event
,
230 AccessibilityButtonInfo
info(
231 profile
, GetViewName(view
), GetViewContext(view
));
232 SendControlAccessibilityNotification(event
, &info
);
236 void AccessibilityEventRouterViews::SendLinkNotification(
238 ui::AccessibilityTypes::Event event
,
240 AccessibilityLinkInfo
info(profile
, GetViewName(view
), GetViewContext(view
));
241 SendControlAccessibilityNotification(event
, &info
);
245 void AccessibilityEventRouterViews::SendMenuNotification(
247 ui::AccessibilityTypes::Event event
,
249 AccessibilityMenuInfo
info(profile
, GetViewName(view
));
250 SendMenuAccessibilityNotification(event
, &info
);
254 void AccessibilityEventRouterViews::SendMenuItemNotification(
256 ui::AccessibilityTypes::Event event
,
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(
286 ui::AccessibilityTypes::Event event
,
288 AccessibilityTreeInfo
info(profile
, GetViewName(view
));
289 SendControlAccessibilityNotification(event
, &info
);
293 void AccessibilityEventRouterViews::SendTreeItemNotification(
295 ui::AccessibilityTypes::Event event
,
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(
340 ui::AccessibilityTypes::Event event
,
342 ui::AccessibleViewState state
;
343 view
->GetAccessibleState(&state
);
344 std::string name
= base::UTF16ToUTF8(state
.name
);
345 std::string context
= GetViewContext(view
);
347 (state
.state
& ui::AccessibilityTypes::STATE_PROTECTED
) != 0;
348 AccessibilityTextBoxInfo
info(profile
, name
, context
, password
);
349 std::string value
= base::UTF16ToUTF8(state
.value
);
350 info
.SetValue(value
, state
.selection_start
, state
.selection_end
);
351 SendControlAccessibilityNotification(event
, &info
);
355 void AccessibilityEventRouterViews::SendComboboxNotification(
357 ui::AccessibilityTypes::Event event
,
359 ui::AccessibleViewState state
;
360 view
->GetAccessibleState(&state
);
361 std::string name
= base::UTF16ToUTF8(state
.name
);
362 std::string value
= base::UTF16ToUTF8(state
.value
);
363 std::string context
= GetViewContext(view
);
364 AccessibilityComboBoxInfo
info(
365 profile
, name
, context
, value
, state
.index
, state
.count
);
366 SendControlAccessibilityNotification(event
, &info
);
370 void AccessibilityEventRouterViews::SendCheckboxNotification(
372 ui::AccessibilityTypes::Event event
,
374 ui::AccessibleViewState state
;
375 view
->GetAccessibleState(&state
);
376 std::string name
= base::UTF16ToUTF8(state
.name
);
377 std::string context
= GetViewContext(view
);
378 AccessibilityCheckboxInfo
info(
382 state
.state
== ui::AccessibilityTypes::STATE_CHECKED
);
383 SendControlAccessibilityNotification(event
, &info
);
387 void AccessibilityEventRouterViews::SendWindowNotification(
389 ui::AccessibilityTypes::Event event
,
391 ui::AccessibleViewState state
;
392 view
->GetAccessibleState(&state
);
393 std::string window_text
;
395 // If it's an alert, try to get the text from the contents of the
396 // static text, not the window title.
397 if (state
.role
== ui::AccessibilityTypes::ROLE_ALERT
)
398 window_text
= RecursiveGetStaticText(view
);
400 // Otherwise get it from the window's accessible name.
401 if (window_text
.empty())
402 window_text
= base::UTF16ToUTF8(state
.name
);
404 AccessibilityWindowInfo
info(profile
, window_text
);
405 SendWindowAccessibilityNotification(event
, &info
);
409 void AccessibilityEventRouterViews::SendSliderNotification(
411 ui::AccessibilityTypes::Event event
,
413 ui::AccessibleViewState state
;
414 view
->GetAccessibleState(&state
);
416 std::string name
= base::UTF16ToUTF8(state
.name
);
417 std::string value
= base::UTF16ToUTF8(state
.value
);
418 std::string context
= GetViewContext(view
);
419 AccessibilitySliderInfo
info(
424 SendControlAccessibilityNotification(event
, &info
);
428 void AccessibilityEventRouterViews::SendAlertControlNotification(
430 ui::AccessibilityTypes::Event event
,
432 ui::AccessibleViewState state
;
433 view
->GetAccessibleState(&state
);
435 std::string name
= base::UTF16ToUTF8(state
.name
);
436 AccessibilityAlertInfo
info(
439 SendControlAccessibilityNotification(event
, &info
);
443 std::string
AccessibilityEventRouterViews::GetViewName(views::View
* view
) {
444 ui::AccessibleViewState state
;
445 view
->GetAccessibleState(&state
);
446 return base::UTF16ToUTF8(state
.name
);
450 std::string
AccessibilityEventRouterViews::GetViewContext(views::View
* view
) {
451 for (views::View
* parent
= view
->parent();
453 parent
= parent
->parent()) {
454 ui::AccessibleViewState state
;
455 parent
->GetAccessibleState(&state
);
457 // Two cases are handled right now. More could be added in the future
458 // depending on how the UI evolves.
460 // A control inside of alert, toolbar or dialog should use that container's
462 if ((state
.role
== ui::AccessibilityTypes::ROLE_ALERT
||
463 state
.role
== ui::AccessibilityTypes::ROLE_DIALOG
||
464 state
.role
== ui::AccessibilityTypes::ROLE_TOOLBAR
) &&
465 !state
.name
.empty()) {
466 return base::UTF16ToUTF8(state
.name
);
469 // A control inside of an alert or dialog (including an infobar)
470 // should grab the first static text descendant as the context;
471 // that's the prompt.
472 if (state
.role
== ui::AccessibilityTypes::ROLE_ALERT
||
473 state
.role
== ui::AccessibilityTypes::ROLE_DIALOG
) {
474 views::View
* static_text_child
= FindDescendantWithAccessibleRole(
475 parent
, ui::AccessibilityTypes::ROLE_STATICTEXT
);
476 if (static_text_child
) {
477 ui::AccessibleViewState state
;
478 static_text_child
->GetAccessibleState(&state
);
479 if (!state
.name
.empty())
480 return base::UTF16ToUTF8(state
.name
);
482 return std::string();
486 return std::string();
490 views::View
* AccessibilityEventRouterViews::FindDescendantWithAccessibleRole(
491 views::View
* view
, ui::AccessibilityTypes::Role role
) {
492 ui::AccessibleViewState state
;
493 view
->GetAccessibleState(&state
);
494 if (state
.role
== role
)
497 for (int i
= 0; i
< view
->child_count(); i
++) {
498 views::View
* child
= view
->child_at(i
);
499 views::View
* result
= FindDescendantWithAccessibleRole(child
, role
);
508 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
513 for (int i
= 0; i
< menu
->child_count(); ++i
) {
514 views::View
* child
= menu
->child_at(i
);
515 if (!child
->visible())
518 int previous_count
= *count
;
519 RecursiveGetMenuItemIndexAndCount(child
, item
, index
, count
);
520 ui::AccessibleViewState state
;
521 child
->GetAccessibleState(&state
);
522 if (state
.role
== ui::AccessibilityTypes::ROLE_MENUITEM
&&
523 *count
== previous_count
) {
527 } else if (state
.role
== ui::AccessibilityTypes::ROLE_PUSHBUTTON
) {
536 std::string
AccessibilityEventRouterViews::RecursiveGetStaticText(
538 ui::AccessibleViewState state
;
539 view
->GetAccessibleState(&state
);
540 if (state
.role
== ui::AccessibilityTypes::ROLE_STATICTEXT
)
541 return base::UTF16ToUTF8(state
.name
);
543 for (int i
= 0; i
< view
->child_count(); ++i
) {
544 views::View
* child
= view
->child_at(i
);
545 std::string result
= RecursiveGetStaticText(child
);
549 return std::string();