1// Copyright (c) 2011 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_event_router_views.h"
6
7#include "base/basictypes.h"
8#include "base/callback.h"
9#include "base/message_loop.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/extensions/extension_accessibility_api.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/profiles/profile_manager.h"
15#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
16#include "content/common/notification_type.h"
17#include "ui/base/models/combobox_model.h"
18#include "ui/base/accessibility/accessible_view_state.h"
19#include "views/controls/button/checkbox.h"
20#include "views/controls/button/custom_button.h"
21#include "views/controls/button/menu_button.h"
22#include "views/controls/button/native_button.h"
23#include "views/controls/combobox/combobox.h"
24#include "views/controls/link.h"
25#include "views/controls/menu/menu_item_view.h"
26#include "views/controls/menu/submenu_view.h"
27#include "views/controls/textfield/textfield.h"
28#include "views/view.h"
29#include "views/widget/native_widget.h"
30#include "views/widget/widget.h"
31#include "views/window/window.h"
32
33using views::FocusManager;
34
35AccessibilityEventRouterViews::AccessibilityEventRouterViews()
36    : most_recent_profile_(NULL),
37      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
38}
39
40AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
41}
42
43// static
44AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
45  return Singleton<AccessibilityEventRouterViews>::get();
46}
47
48void AccessibilityEventRouterViews::HandleAccessibilityEvent(
49    views::View* view, ui::AccessibilityTypes::Event event_type) {
50  if (!ExtensionAccessibilityEventRouter::GetInstance()->
51      IsAccessibilityEnabled()) {
52    return;
53  }
54
55  switch (event_type) {
56    case ui::AccessibilityTypes::EVENT_FOCUS:
57      DispatchAccessibilityNotification(
58          view, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED);
59      break;
60    case ui::AccessibilityTypes::EVENT_MENUSTART:
61    case ui::AccessibilityTypes::EVENT_MENUPOPUPSTART:
62      DispatchAccessibilityNotification(
63          view, NotificationType::ACCESSIBILITY_MENU_OPENED);
64      break;
65    case ui::AccessibilityTypes::EVENT_MENUEND:
66    case ui::AccessibilityTypes::EVENT_MENUPOPUPEND:
67      DispatchAccessibilityNotification(
68          view, NotificationType::ACCESSIBILITY_MENU_CLOSED);
69      break;
70    case ui::AccessibilityTypes::EVENT_TEXT_CHANGED:
71    case ui::AccessibilityTypes::EVENT_SELECTION_CHANGED:
72      DispatchAccessibilityNotification(
73          view, NotificationType::ACCESSIBILITY_TEXT_CHANGED);
74      break;
75    case ui::AccessibilityTypes::EVENT_VALUE_CHANGED:
76      DispatchAccessibilityNotification(
77          view, NotificationType::ACCESSIBILITY_CONTROL_ACTION);
78      break;
79    case ui::AccessibilityTypes::EVENT_ALERT:
80    case ui::AccessibilityTypes::EVENT_NAME_CHANGED:
81      // TODO(dmazzoni): re-evaluate this list later and see
82      // if supporting any of these would be useful feature requests or
83      // they'd just be superfluous.
84      NOTIMPLEMENTED();
85      break;
86  }
87}
88
89void AccessibilityEventRouterViews::HandleMenuItemFocused(
90    const std::wstring& menu_name,
91    const std::wstring& menu_item_name,
92    int item_index,
93    int item_count,
94    bool has_submenu) {
95  if (!ExtensionAccessibilityEventRouter::GetInstance()->
96      IsAccessibilityEnabled()) {
97    return;
98  }
99
100  if (!most_recent_profile_)
101    return;
102
103  AccessibilityMenuItemInfo info(
104      most_recent_profile_,
105      WideToUTF8(menu_item_name),
106      has_submenu,
107      item_index,
108      item_count);
109  SendAccessibilityNotification(
110      NotificationType::ACCESSIBILITY_CONTROL_FOCUSED, &info);
111}
112
113//
114// Private methods
115//
116
117std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
118  ui::AccessibleViewState state;
119  view->GetAccessibleState(&state);
120  return UTF16ToUTF8(state.name);
121}
122
123void AccessibilityEventRouterViews::DispatchAccessibilityNotification(
124    views::View* view, NotificationType type) {
125  // Get the profile associated with this view. If it's not found, use
126  // the most recent profile where accessibility events were sent, or
127  // the default profile.
128  Profile* profile = NULL;
129  views::Window* window = view->GetWindow();
130  if (window) {
131    profile = reinterpret_cast<Profile*>(
132        window->AsWidget()->native_widget()->GetNativeWindowProperty(
133            Profile::kProfileKey));
134  }
135  if (!profile)
136    profile = most_recent_profile_;
137  if (!profile)
138    profile = g_browser_process->profile_manager()->GetDefaultProfile();
139  if (!profile) {
140    NOTREACHED();
141    return;
142  }
143
144  most_recent_profile_ = profile;
145  std::string class_name = view->GetClassName();
146
147  if (class_name == views::Checkbox::kViewClassName) {
148    SendCheckboxNotification(view, type, profile);
149  } else if (class_name == views::MenuButton::kViewClassName ||
150      type == NotificationType::ACCESSIBILITY_MENU_OPENED ||
151      type == NotificationType::ACCESSIBILITY_MENU_CLOSED) {
152    SendMenuNotification(view, type, profile);
153  } else if (IsMenuEvent(view, type)) {
154    SendMenuItemNotification(view, type, profile);
155  } else if (class_name == views::CustomButton::kViewClassName ||
156             class_name == views::NativeButton::kViewClassName ||
157             class_name == views::TextButton::kViewClassName) {
158    SendButtonNotification(view, type, profile);
159  } else if (class_name == views::Link::kViewClassName) {
160    SendLinkNotification(view, type, profile);
161  } else if (class_name == LocationBarView::kViewClassName) {
162    SendLocationBarNotification(view, type, profile);
163  } else if (class_name == views::Textfield::kViewClassName) {
164    SendTextfieldNotification(view, type, profile);
165  } else if (class_name == views::Combobox::kViewClassName) {
166    SendComboboxNotification(view, type, profile);
167  }
168}
169
170void AccessibilityEventRouterViews::SendButtonNotification(
171    views::View* view, NotificationType type, Profile* profile) {
172  AccessibilityButtonInfo info(profile, GetViewName(view));
173  SendAccessibilityNotification(type, &info);
174}
175
176void AccessibilityEventRouterViews::SendLinkNotification(
177    views::View* view, NotificationType type, Profile* profile) {
178  AccessibilityLinkInfo info(profile, GetViewName(view));
179  SendAccessibilityNotification(type, &info);
180}
181
182void AccessibilityEventRouterViews::SendMenuNotification(
183    views::View* view, NotificationType type, Profile* profile) {
184  AccessibilityMenuInfo info(profile, GetViewName(view));
185  SendAccessibilityNotification(type, &info);
186}
187
188void AccessibilityEventRouterViews::SendMenuItemNotification(
189    views::View* view, NotificationType type, Profile* profile) {
190  std::string name = GetViewName(view);
191
192  bool has_submenu = false;
193  int index = -1;
194  int count = -1;
195
196  if (view->GetClassName() == views::MenuItemView::kViewClassName)
197    has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();
198
199  views::View* parent_menu = view->parent();
200  while (parent_menu != NULL && parent_menu->GetClassName() !=
201         views::SubmenuView::kViewClassName) {
202    parent_menu = parent_menu->parent();
203  }
204  if (parent_menu) {
205    count = 0;
206    RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
207  }
208
209  AccessibilityMenuItemInfo info(profile, name, has_submenu, index, count);
210  SendAccessibilityNotification(type, &info);
211}
212
213void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
214    views::View* menu, views::View* item, int* index, int* count) {
215  for (int i = 0; i < menu->child_count(); ++i) {
216    views::View* child = menu->GetChildViewAt(i);
217    int previous_count = *count;
218    RecursiveGetMenuItemIndexAndCount(child, item, index, count);
219    if (child->GetClassName() == views::MenuItemView::kViewClassName &&
220        *count == previous_count) {
221      if (item == child)
222        *index = *count;
223      (*count)++;
224    } else if (child->GetClassName() == views::TextButton::kViewClassName) {
225      if (item == child)
226        *index = *count;
227      (*count)++;
228    }
229  }
230}
231
232bool AccessibilityEventRouterViews::IsMenuEvent(
233    views::View* view, NotificationType type) {
234  if (type == NotificationType::ACCESSIBILITY_MENU_OPENED ||
235      type == NotificationType::ACCESSIBILITY_MENU_CLOSED)
236    return true;
237
238  while (view) {
239    ui::AccessibleViewState state;
240    view->GetAccessibleState(&state);
241    ui::AccessibilityTypes::Role role = state.role;
242    if (role == ui::AccessibilityTypes::ROLE_MENUITEM ||
243        role == ui::AccessibilityTypes::ROLE_MENUPOPUP) {
244      return true;
245    }
246    view = view->parent();
247  }
248
249  return false;
250}
251
252void AccessibilityEventRouterViews::SendLocationBarNotification(
253    views::View* view, NotificationType type, Profile* profile) {
254  ui::AccessibleViewState state;
255  view->GetAccessibleState(&state);
256  std::string name = UTF16ToUTF8(state.name);
257  AccessibilityTextBoxInfo info(profile, name, false);
258  std::string value = UTF16ToUTF8(state.value);
259  info.SetValue(value, state.selection_start, state.selection_end);
260  SendAccessibilityNotification(type, &info);
261}
262
263void AccessibilityEventRouterViews::SendTextfieldNotification(
264    views::View* view, NotificationType type, Profile* profile) {
265  ui::AccessibleViewState state;
266  view->GetAccessibleState(&state);
267  std::string name = UTF16ToUTF8(state.name);
268  views::Textfield* textfield = static_cast<views::Textfield*>(view);
269  bool password = textfield->IsPassword();
270  AccessibilityTextBoxInfo info(profile, name, password);
271  std::string value = UTF16ToUTF8(state.value);
272  info.SetValue(value, state.selection_start, state.selection_end);
273  SendAccessibilityNotification(type, &info);
274}
275
276void AccessibilityEventRouterViews::SendComboboxNotification(
277    views::View* view, NotificationType type, Profile* profile) {
278  ui::AccessibleViewState state;
279  view->GetAccessibleState(&state);
280  std::string name = UTF16ToUTF8(state.name);
281  std::string value = UTF16ToUTF8(state.value);
282  AccessibilityComboBoxInfo info(
283      profile, name, value, state.index, state.count);
284  SendAccessibilityNotification(type, &info);
285}
286
287void AccessibilityEventRouterViews::SendCheckboxNotification(
288    views::View* view, NotificationType type, Profile* profile) {
289  ui::AccessibleViewState state;
290  view->GetAccessibleState(&state);
291  std::string name = UTF16ToUTF8(state.name);
292  std::string value = UTF16ToUTF8(state.value);
293  AccessibilityCheckboxInfo info(
294      profile, name, state.state == ui::AccessibilityTypes::STATE_CHECKED);
295  SendAccessibilityNotification(type, &info);
296}
297
298