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.
4
5#include "content/browser/accessibility/browser_accessibility_manager_win.h"
6
7#include "base/command_line.h"
8#include "base/win/scoped_comptr.h"
9#include "base/win/windows_version.h"
10#include "content/browser/accessibility/browser_accessibility_state_impl.h"
11#include "content/browser/accessibility/browser_accessibility_win.h"
12#include "content/browser/renderer_host/legacy_render_widget_host_win.h"
13#include "content/common/accessibility_messages.h"
14#include "ui/base/win/atl_module.h"
15
16namespace content {
17
18// static
19BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
20    const ui::AXTreeUpdate& initial_tree,
21    BrowserAccessibilityDelegate* delegate,
22    BrowserAccessibilityFactory* factory) {
23  return new BrowserAccessibilityManagerWin(initial_tree, delegate, factory);
24}
25
26BrowserAccessibilityManagerWin*
27BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
28  return static_cast<BrowserAccessibilityManagerWin*>(this);
29}
30
31BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
32    const ui::AXTreeUpdate& initial_tree,
33    BrowserAccessibilityDelegate* delegate,
34    BrowserAccessibilityFactory* factory)
35    : BrowserAccessibilityManager(initial_tree, delegate, factory),
36      tracked_scroll_object_(NULL),
37      focus_event_on_root_needed_(false) {
38  ui::win::CreateATLModuleIfNeeded();
39}
40
41BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
42  if (tracked_scroll_object_) {
43    tracked_scroll_object_->Release();
44    tracked_scroll_object_ = NULL;
45  }
46}
47
48// static
49ui::AXTreeUpdate BrowserAccessibilityManagerWin::GetEmptyDocument() {
50  ui::AXNodeData empty_document;
51  empty_document.id = 0;
52  empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
53  empty_document.state =
54      (1 << ui::AX_STATE_ENABLED) |
55      (1 << ui::AX_STATE_READ_ONLY) |
56      (1 << ui::AX_STATE_BUSY);
57
58  ui::AXTreeUpdate update;
59  update.nodes.push_back(empty_document);
60  return update;
61}
62
63HWND BrowserAccessibilityManagerWin::GetParentHWND() {
64  if (!delegate_)
65    return NULL;
66  return delegate_->AccessibilityGetAcceleratedWidget();
67}
68
69IAccessible* BrowserAccessibilityManagerWin::GetParentIAccessible() {
70  if (!delegate_)
71    return NULL;
72  return delegate_->AccessibilityGetNativeViewAccessible();
73}
74
75void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event,
76                                                             LONG child_id) {
77  if (!delegate_)
78    return;
79
80  HWND hwnd = delegate_->AccessibilityGetAcceleratedWidget();
81  if (!hwnd)
82    return;
83
84  ::NotifyWinEvent(event, hwnd, OBJID_CLIENT, child_id);
85}
86
87
88void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXNode* node) {
89  BrowserAccessibilityManager::OnNodeCreated(node);
90  BrowserAccessibility* obj = GetFromAXNode(node);
91  LONG unique_id_win = obj->ToBrowserAccessibilityWin()->unique_id_win();
92  unique_id_to_ax_id_map_[unique_id_win] = obj->GetId();
93}
94
95void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXNode* node) {
96  BrowserAccessibilityManager::OnNodeWillBeDeleted(node);
97  BrowserAccessibility* obj = GetFromAXNode(node);
98  if (!obj)
99    return;
100  unique_id_to_ax_id_map_.erase(
101      obj->ToBrowserAccessibilityWin()->unique_id_win());
102  if (obj == tracked_scroll_object_) {
103    tracked_scroll_object_->Release();
104    tracked_scroll_object_ = NULL;
105  }
106}
107
108void BrowserAccessibilityManagerWin::OnWindowFocused() {
109  // This is called either when this web frame gets focused, or when
110  // the root of the accessibility tree changes. In both cases, we need
111  // to fire a focus event on the root and then on the focused element
112  // within the page, if different.
113
114  // Set this flag so that we'll keep trying to fire these focus events
115  // if they're not successful this time.
116  focus_event_on_root_needed_ = true;
117
118  if (!delegate_ || !delegate_->AccessibilityViewHasFocus())
119    return;
120
121  // Try to fire a focus event on the root first and then the focused node.
122  // This will clear focus_event_on_root_needed_ if successful.
123  if (focus_ != tree_->GetRoot())
124    NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetRoot());
125  BrowserAccessibilityManager::OnWindowFocused();
126}
127
128void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
129    ui::AXEvent event_type,
130    BrowserAccessibility* node) {
131  if (!delegate_ || !delegate_->AccessibilityGetAcceleratedWidget())
132    return;
133
134  // Inline text boxes are an internal implementation detail, we don't
135  // expose them to Windows.
136  if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
137    return;
138
139  // Don't fire focus, blur, or load complete notifications if the
140  // window isn't focused, because that can confuse screen readers into
141  // entering their "browse" mode.
142  if ((event_type == ui::AX_EVENT_FOCUS ||
143       event_type == ui::AX_EVENT_BLUR ||
144       event_type == ui::AX_EVENT_LOAD_COMPLETE) &&
145      (!delegate_ || !delegate_->AccessibilityViewHasFocus())) {
146    return;
147  }
148
149  // NVDA gets confused if we focus the main document element when it hasn't
150  // finished loading and it has no children at all, so suppress that event.
151  if (event_type == ui::AX_EVENT_FOCUS &&
152      node == GetRoot() &&
153      node->PlatformChildCount() == 0 &&
154      !node->HasState(ui::AX_STATE_BUSY) &&
155      !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) {
156    return;
157  }
158
159  // If a focus event is needed on the root, fire that first before
160  // this event.
161  if (event_type == ui::AX_EVENT_FOCUS && node == GetRoot())
162    focus_event_on_root_needed_ = false;
163  else if (focus_event_on_root_needed_)
164    OnWindowFocused();
165
166  LONG event_id = EVENT_MIN;
167  switch (event_type) {
168    case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED:
169      event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
170      break;
171    case ui::AX_EVENT_ALERT:
172      event_id = EVENT_SYSTEM_ALERT;
173      break;
174    case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED:
175      event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
176      break;
177    case ui::AX_EVENT_AUTOCORRECTION_OCCURED:
178      event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
179      break;
180    case ui::AX_EVENT_BLUR:
181      // Equivalent to focus on the root.
182      event_id = EVENT_OBJECT_FOCUS;
183      node = GetRoot();
184      break;
185    case ui::AX_EVENT_CHECKED_STATE_CHANGED:
186      event_id = EVENT_OBJECT_STATECHANGE;
187      break;
188    case ui::AX_EVENT_CHILDREN_CHANGED:
189      event_id = EVENT_OBJECT_REORDER;
190      break;
191    case ui::AX_EVENT_FOCUS:
192      event_id = EVENT_OBJECT_FOCUS;
193      break;
194    case ui::AX_EVENT_INVALID_STATUS_CHANGED:
195      event_id = EVENT_OBJECT_STATECHANGE;
196      break;
197    case ui::AX_EVENT_LIVE_REGION_CHANGED:
198      if (node->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY))
199        return;
200      event_id = EVENT_OBJECT_LIVEREGIONCHANGED;
201      break;
202    case ui::AX_EVENT_LOAD_COMPLETE:
203      event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
204      break;
205    case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED:
206      event_id = EVENT_OBJECT_FOCUS;
207      break;
208    case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED:
209      event_id = EVENT_OBJECT_VALUECHANGE;
210      break;
211    case ui::AX_EVENT_HIDE:
212      event_id = EVENT_OBJECT_HIDE;
213      break;
214    case ui::AX_EVENT_SHOW:
215      event_id = EVENT_OBJECT_SHOW;
216      break;
217    case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
218      event_id = EVENT_SYSTEM_SCROLLINGEND;
219      break;
220    case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
221      event_id = EVENT_SYSTEM_SCROLLINGSTART;
222      break;
223    case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED:
224      event_id = EVENT_OBJECT_SELECTIONWITHIN;
225      break;
226    case ui::AX_EVENT_TEXT_CHANGED:
227      event_id = EVENT_OBJECT_NAMECHANGE;
228      break;
229    case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
230      event_id = IA2_EVENT_TEXT_CARET_MOVED;
231      break;
232    case ui::AX_EVENT_VALUE_CHANGED:
233      event_id = EVENT_OBJECT_VALUECHANGE;
234      break;
235    default:
236      // Not all WebKit accessibility events result in a Windows
237      // accessibility notification.
238      break;
239  }
240
241  if (event_id != EVENT_MIN) {
242    // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
243    // the AT client will then call get_accChild on the HWND's accessibility
244    // object and pass it that same id, which we can use to retrieve the
245    // IAccessible for this node.
246    LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win();
247    MaybeCallNotifyWinEvent(event_id, child_id);
248  }
249
250  // If this is a layout complete notification (sent when a container scrolls)
251  // and there is a descendant tracked object, send a notification on it.
252  // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
253  if (event_type == ui::AX_EVENT_LAYOUT_COMPLETE &&
254      tracked_scroll_object_ &&
255      tracked_scroll_object_->IsDescendantOf(node)) {
256    MaybeCallNotifyWinEvent(
257        IA2_EVENT_VISIBLE_DATA_CHANGED,
258        tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win());
259    tracked_scroll_object_->Release();
260    tracked_scroll_object_ = NULL;
261  }
262}
263
264void BrowserAccessibilityManagerWin::OnRootChanged(ui::AXNode* new_root) {
265  // In order to make screen readers aware of the new accessibility root,
266  // we need to fire a focus event on it.
267  OnWindowFocused();
268}
269
270void BrowserAccessibilityManagerWin::TrackScrollingObject(
271    BrowserAccessibilityWin* node) {
272  if (tracked_scroll_object_)
273    tracked_scroll_object_->Release();
274  tracked_scroll_object_ = node;
275  tracked_scroll_object_->AddRef();
276}
277
278BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
279    LONG unique_id_win) {
280  base::hash_map<LONG, int32>::iterator iter =
281      unique_id_to_ax_id_map_.find(unique_id_win);
282  if (iter != unique_id_to_ax_id_map_.end()) {
283    BrowserAccessibility* result = GetFromID(iter->second);
284    if (result)
285      return result->ToBrowserAccessibilityWin();
286  }
287  return NULL;
288}
289
290}  // namespace content
291