Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_manager_win.cc
blob750d1311ff9957682db9f1b37b38bca8d2c9a4e8
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 "content/browser/accessibility/browser_accessibility_manager_win.h"
7 #include <vector>
9 #include "base/command_line.h"
10 #include "base/win/scoped_comptr.h"
11 #include "base/win/windows_version.h"
12 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
13 #include "content/browser/accessibility/browser_accessibility_win.h"
14 #include "content/browser/renderer_host/legacy_render_widget_host_win.h"
15 #include "content/common/accessibility_messages.h"
16 #include "ui/base/win/atl_module.h"
18 namespace content {
20 // static
21 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
22 const ui::AXTreeUpdate& initial_tree,
23 BrowserAccessibilityDelegate* delegate,
24 BrowserAccessibilityFactory* factory) {
25 return new BrowserAccessibilityManagerWin(initial_tree, delegate, factory);
28 BrowserAccessibilityManagerWin*
29 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
30 return static_cast<BrowserAccessibilityManagerWin*>(this);
33 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
34 const ui::AXTreeUpdate& initial_tree,
35 BrowserAccessibilityDelegate* delegate,
36 BrowserAccessibilityFactory* factory)
37 : BrowserAccessibilityManager(delegate, factory),
38 tracked_scroll_object_(NULL),
39 focus_event_on_root_needed_(false) {
40 ui::win::CreateATLModuleIfNeeded();
41 Initialize(initial_tree);
44 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
45 if (tracked_scroll_object_) {
46 tracked_scroll_object_->Release();
47 tracked_scroll_object_ = NULL;
51 // static
52 ui::AXTreeUpdate BrowserAccessibilityManagerWin::GetEmptyDocument() {
53 ui::AXNodeData empty_document;
54 empty_document.id = 0;
55 empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
56 empty_document.state =
57 (1 << ui::AX_STATE_ENABLED) |
58 (1 << ui::AX_STATE_READ_ONLY) |
59 (1 << ui::AX_STATE_BUSY);
61 ui::AXTreeUpdate update;
62 update.nodes.push_back(empty_document);
63 return update;
66 HWND BrowserAccessibilityManagerWin::GetParentHWND() {
67 if (!delegate_)
68 return NULL;
69 return delegate_->AccessibilityGetAcceleratedWidget();
72 IAccessible* BrowserAccessibilityManagerWin::GetParentIAccessible() {
73 if (!delegate_)
74 return NULL;
75 return delegate_->AccessibilityGetNativeViewAccessible();
78 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(
79 DWORD event, BrowserAccessibility* node) {
80 BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
81 if (!delegate) {
82 // This line and other LOG(WARNING) lines are temporary, to debug
83 // flaky failures in DumpAccessibilityEvent* tests.
84 // http://crbug.com/440579
85 LOG(WARNING) << "Not firing AX event because of no delegate";
86 return;
89 if (!node->IsNative())
90 return;
92 HWND hwnd = delegate->AccessibilityGetAcceleratedWidget();
93 if (!hwnd) {
94 LOG(WARNING) << "Not firing AX event because of no hwnd";
95 return;
98 // Inline text boxes are an internal implementation detail, we don't
99 // expose them to Windows.
100 if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
101 return;
103 // It doesn't make sense to fire a REORDER event on a leaf node; that
104 // happens when the node has internal children line inline text boxes.
105 if (event == EVENT_OBJECT_REORDER && node->PlatformIsLeaf())
106 return;
108 // Don't fire focus, or load complete notifications if the
109 // window isn't focused, because that can confuse screen readers into
110 // entering their "browse" mode.
111 if ((event == EVENT_OBJECT_FOCUS ||
112 event == IA2_EVENT_DOCUMENT_LOAD_COMPLETE) &&
113 (!delegate_->AccessibilityViewHasFocus())) {
114 return;
117 // NVDA gets confused if we focus the main document element when it hasn't
118 // finished loading and it has no children at all, so suppress that event.
119 if (event == EVENT_OBJECT_FOCUS &&
120 node == GetRoot() &&
121 node->PlatformChildCount() == 0 &&
122 !node->HasState(ui::AX_STATE_BUSY) &&
123 !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) {
124 return;
127 // If a focus event is needed on the root, fire that first before
128 // this event.
129 if (event == EVENT_OBJECT_FOCUS && node == GetRoot())
130 focus_event_on_root_needed_ = false;
131 else if (focus_event_on_root_needed_)
132 OnWindowFocused();
134 LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win();
135 ::NotifyWinEvent(event, hwnd, OBJID_CLIENT, child_id);
138 void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXNode* node) {
139 BrowserAccessibilityManager::OnNodeCreated(node);
140 BrowserAccessibility* obj = GetFromAXNode(node);
141 if (!obj)
142 return;
143 if (!obj->IsNative())
144 return;
145 LONG unique_id_win = obj->ToBrowserAccessibilityWin()->unique_id_win();
146 unique_id_to_ax_id_map_[unique_id_win] = obj->GetId();
149 void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXNode* node) {
150 BrowserAccessibilityManager::OnNodeWillBeDeleted(node);
151 BrowserAccessibility* obj = GetFromAXNode(node);
152 if (!obj)
153 return;
154 if (!obj->IsNative())
155 return;
156 unique_id_to_ax_id_map_.erase(
157 obj->ToBrowserAccessibilityWin()->unique_id_win());
158 if (obj == tracked_scroll_object_) {
159 tracked_scroll_object_->Release();
160 tracked_scroll_object_ = NULL;
164 void BrowserAccessibilityManagerWin::OnWindowFocused() {
165 // This is called either when this web frame gets focused, or when
166 // the root of the accessibility tree changes. In both cases, we need
167 // to fire a focus event on the root and then on the focused element
168 // within the page, if different.
170 // Set this flag so that we'll keep trying to fire these focus events
171 // if they're not successful this time.
172 focus_event_on_root_needed_ = true;
174 if (!delegate_ || !delegate_->AccessibilityViewHasFocus())
175 return;
177 // Try to fire a focus event on the root first and then the focused node.
178 // This will clear focus_event_on_root_needed_ if successful.
179 if (focus_ != tree_->root())
180 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetRoot());
181 BrowserAccessibilityManager::OnWindowFocused();
184 void BrowserAccessibilityManagerWin::UserIsReloading() {
185 MaybeCallNotifyWinEvent(IA2_EVENT_DOCUMENT_RELOAD, GetRoot());
188 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
189 ui::AXEvent event_type,
190 BrowserAccessibility* node) {
191 BrowserAccessibilityDelegate* root_delegate = GetDelegateFromRootManager();
192 if (!root_delegate || !root_delegate->AccessibilityGetAcceleratedWidget()) {
193 LOG(WARNING) << "Not firing AX event because of no root_delegate or hwnd";
194 return;
197 // Don't fire events when this document might be stale as the user has
198 // started navigating to a new document.
199 if (user_is_navigating_away_)
200 return;
202 // Inline text boxes are an internal implementation detail, we don't
203 // expose them to Windows.
204 if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
205 return;
207 // Don't fire focus, blur, or load complete notifications if the
208 // window isn't focused, because that can confuse screen readers into
209 // entering their "browse" mode.
210 if ((event_type == ui::AX_EVENT_FOCUS ||
211 event_type == ui::AX_EVENT_BLUR ||
212 event_type == ui::AX_EVENT_LOAD_COMPLETE) &&
213 !root_delegate->AccessibilityViewHasFocus()) {
214 return;
217 // NVDA gets confused if we focus the main document element when it hasn't
218 // finished loading and it has no children at all, so suppress that event.
219 if (event_type == ui::AX_EVENT_FOCUS &&
220 node == GetRoot() &&
221 node->PlatformChildCount() == 0 &&
222 !node->HasState(ui::AX_STATE_BUSY) &&
223 !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) {
224 return;
227 // If a focus event is needed on the root, fire that first before
228 // this event.
229 if (event_type == ui::AX_EVENT_FOCUS && node == GetRoot())
230 focus_event_on_root_needed_ = false;
231 else if (focus_event_on_root_needed_)
232 OnWindowFocused();
234 LONG event_id = EVENT_MIN;
235 switch (event_type) {
236 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED:
237 event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
238 break;
239 case ui::AX_EVENT_ALERT:
240 event_id = EVENT_SYSTEM_ALERT;
241 break;
242 case ui::AX_EVENT_AUTOCORRECTION_OCCURED:
243 event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
244 break;
245 case ui::AX_EVENT_BLUR:
246 // Equivalent to focus on the root.
247 event_id = EVENT_OBJECT_FOCUS;
248 node = GetRoot();
249 break;
250 case ui::AX_EVENT_CHILDREN_CHANGED:
251 event_id = EVENT_OBJECT_REORDER;
252 break;
253 case ui::AX_EVENT_FOCUS:
254 event_id = EVENT_OBJECT_FOCUS;
255 break;
256 case ui::AX_EVENT_LIVE_REGION_CHANGED:
257 if (node->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY))
258 return;
259 event_id = EVENT_OBJECT_LIVEREGIONCHANGED;
260 break;
261 case ui::AX_EVENT_LOAD_COMPLETE:
262 event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
263 break;
264 case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED:
265 event_id = EVENT_OBJECT_FOCUS;
266 break;
267 case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED:
268 event_id = EVENT_OBJECT_VALUECHANGE;
269 break;
270 case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
271 event_id = EVENT_SYSTEM_SCROLLINGEND;
272 break;
273 case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
274 event_id = EVENT_SYSTEM_SCROLLINGSTART;
275 break;
276 case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED:
277 event_id = EVENT_OBJECT_SELECTIONWITHIN;
278 break;
279 case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
280 event_id = IA2_EVENT_TEXT_CARET_MOVED;
281 break;
282 default:
283 // Not all WebKit accessibility events result in a Windows
284 // accessibility notification.
285 break;
288 if (event_id != EVENT_MIN) {
289 // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
290 // the AT client will then call get_accChild on the HWND's accessibility
291 // object and pass it that same id, which we can use to retrieve the
292 // IAccessible for this node.
293 MaybeCallNotifyWinEvent(event_id, node);
296 // If this is a layout complete notification (sent when a container scrolls)
297 // and there is a descendant tracked object, send a notification on it.
298 // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
299 if (event_type == ui::AX_EVENT_LAYOUT_COMPLETE &&
300 tracked_scroll_object_ &&
301 tracked_scroll_object_->IsDescendantOf(node)) {
302 MaybeCallNotifyWinEvent(
303 IA2_EVENT_VISIBLE_DATA_CHANGED, tracked_scroll_object_);
304 tracked_scroll_object_->Release();
305 tracked_scroll_object_ = NULL;
309 void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished(
310 bool root_changed,
311 const std::vector<ui::AXTreeDelegate::Change>& changes) {
312 BrowserAccessibilityManager::OnAtomicUpdateFinished(root_changed, changes);
314 if (root_changed) {
315 // In order to make screen readers aware of the new accessibility root,
316 // we need to fire a focus event on it.
317 OnWindowFocused();
320 // Do a sequence of Windows-specific updates on each node. Each one is
321 // done in a single pass that must complete before the next step starts.
322 // The first step moves win_attributes_ to old_win_attributes_ and then
323 // recomputes all of win_attributes_ other than IAccessibleText.
324 for (size_t i = 0; i < changes.size(); ++i) {
325 BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
326 if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf())
327 obj->ToBrowserAccessibilityWin()->UpdateStep1ComputeWinAttributes();
330 // The next step updates the hypertext of each node, which is a
331 // concatenation of all of its child text nodes, so it can't run until
332 // the text of all of the nodes was computed in the previous step.
333 for (size_t i = 0; i < changes.size(); ++i) {
334 BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
335 if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf())
336 obj->ToBrowserAccessibilityWin()->UpdateStep2ComputeHypertext();
339 // The third step fires events on nodes based on what's changed - like
340 // if the name, value, or description changed, or if the hypertext had
341 // text inserted or removed. It's able to figure out exactly what changed
342 // because we still have old_win_attributes_ populated.
343 // This step has to run after the previous two steps complete because the
344 // client may walk the tree when it receives any of these events.
345 // At the end, it deletes old_win_attributes_ since they're not needed
346 // anymore.
347 for (size_t i = 0; i < changes.size(); ++i) {
348 BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
349 if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf()) {
350 obj->ToBrowserAccessibilityWin()->UpdateStep3FireEvents(
351 changes[i].type == AXTreeDelegate::SUBTREE_CREATED);
356 void BrowserAccessibilityManagerWin::TrackScrollingObject(
357 BrowserAccessibilityWin* node) {
358 if (tracked_scroll_object_)
359 tracked_scroll_object_->Release();
360 tracked_scroll_object_ = node;
361 tracked_scroll_object_->AddRef();
364 BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
365 LONG unique_id_win) {
366 base::hash_map<LONG, int32>::iterator iter =
367 unique_id_to_ax_id_map_.find(unique_id_win);
368 if (iter != unique_id_to_ax_id_map_.end()) {
369 BrowserAccessibility* result = GetFromID(iter->second);
370 if (result && result->IsNative())
371 return result->ToBrowserAccessibilityWin();
374 // Also search all child frames, such as out-of-process iframes or
375 // guest browser plugins.
376 if (delegate()) {
377 std::vector<BrowserAccessibilityManager*> child_frames;
378 delegate()->AccessibilityGetAllChildFrames(&child_frames);
379 for (size_t i = 0; i < child_frames.size(); ++i) {
380 BrowserAccessibilityManagerWin* child_manager =
381 child_frames[i]->ToBrowserAccessibilityManagerWin();
382 BrowserAccessibilityWin* result =
383 child_manager->GetFromUniqueIdWin(unique_id_win);
384 if (result)
385 return result;
389 return NULL;
392 } // namespace content