Add ICU message format support
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_manager_win.cc
blobd28423b70e457664356ebf081509d53a2c7f565a
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 SimpleAXTreeUpdate& 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 SimpleAXTreeUpdate& 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 inside_on_window_focused_(false) {
41 ui::win::CreateATLModuleIfNeeded();
42 Initialize(initial_tree);
45 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
46 if (tracked_scroll_object_) {
47 tracked_scroll_object_->Release();
48 tracked_scroll_object_ = NULL;
52 // static
53 SimpleAXTreeUpdate
54 BrowserAccessibilityManagerWin::GetEmptyDocument() {
55 ui::AXNodeData empty_document;
56 empty_document.id = 0;
57 empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
58 empty_document.state =
59 (1 << ui::AX_STATE_ENABLED) |
60 (1 << ui::AX_STATE_READ_ONLY) |
61 (1 << ui::AX_STATE_BUSY);
63 SimpleAXTreeUpdate update;
64 update.nodes.push_back(empty_document);
65 return update;
68 HWND BrowserAccessibilityManagerWin::GetParentHWND() {
69 if (!delegate_)
70 return NULL;
71 return delegate_->AccessibilityGetAcceleratedWidget();
74 IAccessible* BrowserAccessibilityManagerWin::GetParentIAccessible() {
75 if (!delegate_)
76 return NULL;
77 return delegate_->AccessibilityGetNativeViewAccessible();
80 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(
81 DWORD event, BrowserAccessibility* node) {
82 BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
83 if (!delegate) {
84 // This line and other LOG(WARNING) lines are temporary, to debug
85 // flaky failures in DumpAccessibilityEvent* tests.
86 // http://crbug.com/440579
87 LOG(WARNING) << "Not firing AX event because of no delegate";
88 return;
91 if (!node->IsNative())
92 return;
94 HWND hwnd = delegate->AccessibilityGetAcceleratedWidget();
95 if (!hwnd) {
96 LOG(WARNING) << "Not firing AX event because of no hwnd";
97 return;
100 // Inline text boxes are an internal implementation detail, we don't
101 // expose them to Windows.
102 if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
103 return;
105 // It doesn't make sense to fire a REORDER event on a leaf node; that
106 // happens when the node has internal children line inline text boxes.
107 if (event == EVENT_OBJECT_REORDER && node->PlatformIsLeaf())
108 return;
110 // Don't fire focus, or load complete notifications if the
111 // window isn't focused, because that can confuse screen readers into
112 // entering their "browse" mode.
113 if ((event == EVENT_OBJECT_FOCUS ||
114 event == IA2_EVENT_DOCUMENT_LOAD_COMPLETE) &&
115 (!delegate_->AccessibilityViewHasFocus())) {
116 return;
119 // NVDA gets confused if we focus the main document element when it hasn't
120 // finished loading and it has no children at all, so suppress that event.
121 if (event == EVENT_OBJECT_FOCUS &&
122 node == GetRoot() &&
123 node->PlatformChildCount() == 0 &&
124 !node->HasState(ui::AX_STATE_BUSY) &&
125 !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) {
126 return;
129 // If a focus event is needed on the root, fire that first before
130 // this event.
131 if (event == EVENT_OBJECT_FOCUS && node == GetRoot())
132 focus_event_on_root_needed_ = false;
133 else if (focus_event_on_root_needed_)
134 OnWindowFocused();
136 LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win();
137 ::NotifyWinEvent(event, hwnd, OBJID_CLIENT, child_id);
140 void BrowserAccessibilityManagerWin::OnWindowFocused() {
141 // Make sure we don't call this recursively.
142 if (inside_on_window_focused_)
143 return;
144 inside_on_window_focused_ = true;
146 // This is called either when this web frame gets focused, or when
147 // the root of the accessibility tree changes. In both cases, we need
148 // to fire a focus event on the root and then on the focused element
149 // within the page, if different.
151 // Set this flag so that we'll keep trying to fire these focus events
152 // if they're not successful this time.
153 focus_event_on_root_needed_ = true;
155 if (!delegate_ || !delegate_->AccessibilityViewHasFocus()) {
156 inside_on_window_focused_ = false;
157 return;
160 // Try to fire a focus event on the root first and then the focused node.
161 // This will clear focus_event_on_root_needed_ if successful.
162 if (focus_ != tree_->root() && GetRoot())
163 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetRoot());
164 BrowserAccessibilityManager::OnWindowFocused();
165 inside_on_window_focused_ = false;
168 void BrowserAccessibilityManagerWin::UserIsReloading() {
169 if (GetRoot())
170 MaybeCallNotifyWinEvent(IA2_EVENT_DOCUMENT_RELOAD, GetRoot());
173 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
174 ui::AXEvent event_type,
175 BrowserAccessibility* node) {
176 BrowserAccessibilityDelegate* root_delegate = GetDelegateFromRootManager();
177 if (!root_delegate || !root_delegate->AccessibilityGetAcceleratedWidget()) {
178 LOG(WARNING) << "Not firing AX event because of no root_delegate or hwnd";
179 return;
182 // Don't fire events when this document might be stale as the user has
183 // started navigating to a new document.
184 if (user_is_navigating_away_)
185 return;
187 // Inline text boxes are an internal implementation detail, we don't
188 // expose them to Windows.
189 if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
190 return;
192 // Don't fire focus, blur, or load complete notifications if the
193 // window isn't focused, because that can confuse screen readers into
194 // entering their "browse" mode.
195 if ((event_type == ui::AX_EVENT_FOCUS ||
196 event_type == ui::AX_EVENT_BLUR ||
197 event_type == ui::AX_EVENT_LOAD_COMPLETE) &&
198 !root_delegate->AccessibilityViewHasFocus()) {
199 return;
202 // NVDA gets confused if we focus the main document element when it hasn't
203 // finished loading and it has no children at all, so suppress that event.
204 if (event_type == ui::AX_EVENT_FOCUS &&
205 node == GetRoot() &&
206 node->PlatformChildCount() == 0 &&
207 !node->HasState(ui::AX_STATE_BUSY) &&
208 !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) {
209 return;
212 // If a focus event is needed on the root, fire that first before
213 // this event.
214 if (event_type == ui::AX_EVENT_FOCUS && node == GetRoot())
215 focus_event_on_root_needed_ = false;
216 else if (focus_event_on_root_needed_)
217 OnWindowFocused();
219 LONG event_id = EVENT_MIN;
220 switch (event_type) {
221 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED:
222 event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
223 break;
224 case ui::AX_EVENT_ALERT:
225 event_id = EVENT_SYSTEM_ALERT;
226 break;
227 case ui::AX_EVENT_AUTOCORRECTION_OCCURED:
228 event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
229 break;
230 case ui::AX_EVENT_BLUR:
231 // Equivalent to focus on the root.
232 event_id = EVENT_OBJECT_FOCUS;
233 node = GetRoot();
234 break;
235 case ui::AX_EVENT_CHILDREN_CHANGED:
236 event_id = EVENT_OBJECT_REORDER;
237 break;
238 case ui::AX_EVENT_FOCUS:
239 event_id = EVENT_OBJECT_FOCUS;
240 break;
241 case ui::AX_EVENT_LIVE_REGION_CHANGED:
242 if (node->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY))
243 return;
244 event_id = EVENT_OBJECT_LIVEREGIONCHANGED;
245 break;
246 case ui::AX_EVENT_LOAD_COMPLETE:
247 event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
248 break;
249 case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
250 event_id = EVENT_SYSTEM_SCROLLINGEND;
251 break;
252 case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
253 event_id = EVENT_SYSTEM_SCROLLINGSTART;
254 break;
255 case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED:
256 event_id = EVENT_OBJECT_SELECTIONWITHIN;
257 break;
258 case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
259 event_id = IA2_EVENT_TEXT_CARET_MOVED;
260 break;
261 default:
262 // Not all WebKit accessibility events result in a Windows
263 // accessibility notification.
264 break;
267 if (!node)
268 return;
270 if (event_id != EVENT_MIN) {
271 // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
272 // the AT client will then call get_accChild on the HWND's accessibility
273 // object and pass it that same id, which we can use to retrieve the
274 // IAccessible for this node.
275 MaybeCallNotifyWinEvent(event_id, node);
278 // If this is a layout complete notification (sent when a container scrolls)
279 // and there is a descendant tracked object, send a notification on it.
280 // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
281 if (event_type == ui::AX_EVENT_LAYOUT_COMPLETE &&
282 tracked_scroll_object_ &&
283 tracked_scroll_object_->IsDescendantOf(node)) {
284 MaybeCallNotifyWinEvent(
285 IA2_EVENT_VISIBLE_DATA_CHANGED, tracked_scroll_object_);
286 tracked_scroll_object_->Release();
287 tracked_scroll_object_ = NULL;
291 void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXTree* tree,
292 ui::AXNode* node) {
293 BrowserAccessibilityManager::OnNodeCreated(tree, node);
294 BrowserAccessibility* obj = GetFromAXNode(node);
295 if (!obj)
296 return;
297 if (!obj->IsNative())
298 return;
299 LONG unique_id_win = obj->ToBrowserAccessibilityWin()->unique_id_win();
300 unique_id_to_ax_id_map_[unique_id_win] = obj->GetId();
301 unique_id_to_ax_tree_id_map_[unique_id_win] = ax_tree_id_;
304 void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXTree* tree,
305 ui::AXNode* node) {
306 BrowserAccessibilityManager::OnNodeWillBeDeleted(tree, node);
307 BrowserAccessibility* obj = GetFromAXNode(node);
308 if (!obj)
309 return;
310 if (!obj->IsNative())
311 return;
312 unique_id_to_ax_id_map_.erase(
313 obj->ToBrowserAccessibilityWin()->unique_id_win());
314 unique_id_to_ax_tree_id_map_.erase(
315 obj->ToBrowserAccessibilityWin()->unique_id_win());
316 if (obj == tracked_scroll_object_) {
317 tracked_scroll_object_->Release();
318 tracked_scroll_object_ = NULL;
322 void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished(
323 ui::AXTree* tree,
324 bool root_changed,
325 const std::vector<ui::AXTreeDelegate::Change>& changes) {
326 BrowserAccessibilityManager::OnAtomicUpdateFinished(
327 tree, root_changed, changes);
329 if (root_changed) {
330 // In order to make screen readers aware of the new accessibility root,
331 // we need to fire a focus event on it.
332 OnWindowFocused();
335 // Do a sequence of Windows-specific updates on each node. Each one is
336 // done in a single pass that must complete before the next step starts.
337 // The first step moves win_attributes_ to old_win_attributes_ and then
338 // recomputes all of win_attributes_ other than IAccessibleText.
339 for (size_t i = 0; i < changes.size(); ++i) {
340 BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
341 if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf())
342 obj->ToBrowserAccessibilityWin()->UpdateStep1ComputeWinAttributes();
345 // The next step updates the hypertext of each node, which is a
346 // concatenation of all of its child text nodes, so it can't run until
347 // the text of all of the nodes was computed in the previous step.
348 for (size_t i = 0; i < changes.size(); ++i) {
349 BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
350 if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf())
351 obj->ToBrowserAccessibilityWin()->UpdateStep2ComputeHypertext();
354 // The third step fires events on nodes based on what's changed - like
355 // if the name, value, or description changed, or if the hypertext had
356 // text inserted or removed. It's able to figure out exactly what changed
357 // because we still have old_win_attributes_ populated.
358 // This step has to run after the previous two steps complete because the
359 // client may walk the tree when it receives any of these events.
360 // At the end, it deletes old_win_attributes_ since they're not needed
361 // anymore.
362 for (size_t i = 0; i < changes.size(); ++i) {
363 BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
364 if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf()) {
365 obj->ToBrowserAccessibilityWin()->UpdateStep3FireEvents(
366 changes[i].type == AXTreeDelegate::SUBTREE_CREATED);
371 void BrowserAccessibilityManagerWin::TrackScrollingObject(
372 BrowserAccessibilityWin* node) {
373 if (tracked_scroll_object_)
374 tracked_scroll_object_->Release();
375 tracked_scroll_object_ = node;
376 tracked_scroll_object_->AddRef();
379 BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
380 LONG unique_id_win) {
381 auto tree_iter = unique_id_to_ax_tree_id_map_.find(unique_id_win);
382 if (tree_iter == unique_id_to_ax_tree_id_map_.end())
383 return nullptr;
385 int tree_id = tree_iter->second;
386 if (tree_id != ax_tree_id_) {
387 BrowserAccessibilityManagerWin* manager =
388 BrowserAccessibilityManager::FromID(tree_id)
389 ->ToBrowserAccessibilityManagerWin();
390 if (!manager)
391 return nullptr;
392 if (manager != this)
393 return manager->GetFromUniqueIdWin(unique_id_win);
394 return nullptr;
397 auto iter = unique_id_to_ax_id_map_.find(unique_id_win);
398 if (iter == unique_id_to_ax_id_map_.end())
399 return nullptr;
401 BrowserAccessibility* result = GetFromID(iter->second);
402 if (result && result->IsNative())
403 return result->ToBrowserAccessibilityWin();
405 return nullptr;
408 } // namespace content