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"
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"
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
;
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
);
66 HWND
BrowserAccessibilityManagerWin::GetParentHWND() {
69 return delegate_
->AccessibilityGetAcceleratedWidget();
72 IAccessible
* BrowserAccessibilityManagerWin::GetParentIAccessible() {
75 return delegate_
->AccessibilityGetNativeViewAccessible();
78 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(
79 DWORD event
, BrowserAccessibility
* node
) {
80 BrowserAccessibilityDelegate
* delegate
= GetDelegateFromRootManager();
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";
89 if (!node
->IsNative())
92 HWND hwnd
= delegate
->AccessibilityGetAcceleratedWidget();
94 LOG(WARNING
) << "Not firing AX event because of no hwnd";
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
)
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())
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())) {
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
&&
121 node
->PlatformChildCount() == 0 &&
122 !node
->HasState(ui::AX_STATE_BUSY
) &&
123 !node
->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED
)) {
127 // If a focus event is needed on the root, fire that first before
129 if (event
== EVENT_OBJECT_FOCUS
&& node
== GetRoot())
130 focus_event_on_root_needed_
= false;
131 else if (focus_event_on_root_needed_
)
134 LONG child_id
= node
->ToBrowserAccessibilityWin()->unique_id_win();
135 ::NotifyWinEvent(event
, hwnd
, OBJID_CLIENT
, child_id
);
138 void BrowserAccessibilityManagerWin::OnWindowFocused() {
139 // This is called either when this web frame gets focused, or when
140 // the root of the accessibility tree changes. In both cases, we need
141 // to fire a focus event on the root and then on the focused element
142 // within the page, if different.
144 // Set this flag so that we'll keep trying to fire these focus events
145 // if they're not successful this time.
146 focus_event_on_root_needed_
= true;
148 if (!delegate_
|| !delegate_
->AccessibilityViewHasFocus())
151 // Try to fire a focus event on the root first and then the focused node.
152 // This will clear focus_event_on_root_needed_ if successful.
153 if (focus_
!= tree_
->root())
154 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, GetRoot());
155 BrowserAccessibilityManager::OnWindowFocused();
158 void BrowserAccessibilityManagerWin::UserIsReloading() {
159 MaybeCallNotifyWinEvent(IA2_EVENT_DOCUMENT_RELOAD
, GetRoot());
162 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
163 ui::AXEvent event_type
,
164 BrowserAccessibility
* node
) {
165 BrowserAccessibilityDelegate
* root_delegate
= GetDelegateFromRootManager();
166 if (!root_delegate
|| !root_delegate
->AccessibilityGetAcceleratedWidget()) {
167 LOG(WARNING
) << "Not firing AX event because of no root_delegate or hwnd";
171 // Don't fire events when this document might be stale as the user has
172 // started navigating to a new document.
173 if (user_is_navigating_away_
)
176 // Inline text boxes are an internal implementation detail, we don't
177 // expose them to Windows.
178 if (node
->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX
)
181 // Don't fire focus, blur, or load complete notifications if the
182 // window isn't focused, because that can confuse screen readers into
183 // entering their "browse" mode.
184 if ((event_type
== ui::AX_EVENT_FOCUS
||
185 event_type
== ui::AX_EVENT_BLUR
||
186 event_type
== ui::AX_EVENT_LOAD_COMPLETE
) &&
187 !root_delegate
->AccessibilityViewHasFocus()) {
191 // NVDA gets confused if we focus the main document element when it hasn't
192 // finished loading and it has no children at all, so suppress that event.
193 if (event_type
== ui::AX_EVENT_FOCUS
&&
195 node
->PlatformChildCount() == 0 &&
196 !node
->HasState(ui::AX_STATE_BUSY
) &&
197 !node
->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED
)) {
201 // If a focus event is needed on the root, fire that first before
203 if (event_type
== ui::AX_EVENT_FOCUS
&& node
== GetRoot())
204 focus_event_on_root_needed_
= false;
205 else if (focus_event_on_root_needed_
)
208 LONG event_id
= EVENT_MIN
;
209 switch (event_type
) {
210 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED
:
211 event_id
= IA2_EVENT_ACTIVE_DESCENDANT_CHANGED
;
213 case ui::AX_EVENT_ALERT
:
214 event_id
= EVENT_SYSTEM_ALERT
;
216 case ui::AX_EVENT_AUTOCORRECTION_OCCURED
:
217 event_id
= IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED
;
219 case ui::AX_EVENT_BLUR
:
220 // Equivalent to focus on the root.
221 event_id
= EVENT_OBJECT_FOCUS
;
224 case ui::AX_EVENT_CHILDREN_CHANGED
:
225 event_id
= EVENT_OBJECT_REORDER
;
227 case ui::AX_EVENT_FOCUS
:
228 event_id
= EVENT_OBJECT_FOCUS
;
230 case ui::AX_EVENT_LIVE_REGION_CHANGED
:
231 if (node
->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY
))
233 event_id
= EVENT_OBJECT_LIVEREGIONCHANGED
;
235 case ui::AX_EVENT_LOAD_COMPLETE
:
236 event_id
= IA2_EVENT_DOCUMENT_LOAD_COMPLETE
;
238 case ui::AX_EVENT_SCROLL_POSITION_CHANGED
:
239 event_id
= EVENT_SYSTEM_SCROLLINGEND
;
241 case ui::AX_EVENT_SCROLLED_TO_ANCHOR
:
242 event_id
= EVENT_SYSTEM_SCROLLINGSTART
;
244 case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED
:
245 event_id
= EVENT_OBJECT_SELECTIONWITHIN
;
247 case ui::AX_EVENT_TEXT_SELECTION_CHANGED
:
248 event_id
= IA2_EVENT_TEXT_CARET_MOVED
;
251 // Not all WebKit accessibility events result in a Windows
252 // accessibility notification.
256 if (event_id
!= EVENT_MIN
) {
257 // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
258 // the AT client will then call get_accChild on the HWND's accessibility
259 // object and pass it that same id, which we can use to retrieve the
260 // IAccessible for this node.
261 MaybeCallNotifyWinEvent(event_id
, node
);
264 // If this is a layout complete notification (sent when a container scrolls)
265 // and there is a descendant tracked object, send a notification on it.
266 // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
267 if (event_type
== ui::AX_EVENT_LAYOUT_COMPLETE
&&
268 tracked_scroll_object_
&&
269 tracked_scroll_object_
->IsDescendantOf(node
)) {
270 MaybeCallNotifyWinEvent(
271 IA2_EVENT_VISIBLE_DATA_CHANGED
, tracked_scroll_object_
);
272 tracked_scroll_object_
->Release();
273 tracked_scroll_object_
= NULL
;
277 void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXTree
* tree
,
279 BrowserAccessibilityManager::OnNodeCreated(tree
, node
);
280 BrowserAccessibility
* obj
= GetFromAXNode(node
);
283 if (!obj
->IsNative())
285 LONG unique_id_win
= obj
->ToBrowserAccessibilityWin()->unique_id_win();
286 unique_id_to_ax_id_map_
[unique_id_win
] = obj
->GetId();
289 void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXTree
* tree
,
291 BrowserAccessibilityManager::OnNodeWillBeDeleted(tree
, node
);
292 BrowserAccessibility
* obj
= GetFromAXNode(node
);
295 if (!obj
->IsNative())
297 unique_id_to_ax_id_map_
.erase(
298 obj
->ToBrowserAccessibilityWin()->unique_id_win());
299 if (obj
== tracked_scroll_object_
) {
300 tracked_scroll_object_
->Release();
301 tracked_scroll_object_
= NULL
;
305 void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished(
308 const std::vector
<ui::AXTreeDelegate::Change
>& changes
) {
309 BrowserAccessibilityManager::OnAtomicUpdateFinished(
310 tree
, root_changed
, changes
);
313 // In order to make screen readers aware of the new accessibility root,
314 // we need to fire a focus event on it.
318 // Do a sequence of Windows-specific updates on each node. Each one is
319 // done in a single pass that must complete before the next step starts.
320 // The first step moves win_attributes_ to old_win_attributes_ and then
321 // recomputes all of win_attributes_ other than IAccessibleText.
322 for (size_t i
= 0; i
< changes
.size(); ++i
) {
323 BrowserAccessibility
* obj
= GetFromAXNode(changes
[i
].node
);
324 if (obj
&& obj
->IsNative() && !obj
->PlatformIsChildOfLeaf())
325 obj
->ToBrowserAccessibilityWin()->UpdateStep1ComputeWinAttributes();
328 // The next step updates the hypertext of each node, which is a
329 // concatenation of all of its child text nodes, so it can't run until
330 // the text of all of the nodes was computed in the previous step.
331 for (size_t i
= 0; i
< changes
.size(); ++i
) {
332 BrowserAccessibility
* obj
= GetFromAXNode(changes
[i
].node
);
333 if (obj
&& obj
->IsNative() && !obj
->PlatformIsChildOfLeaf())
334 obj
->ToBrowserAccessibilityWin()->UpdateStep2ComputeHypertext();
337 // The third step fires events on nodes based on what's changed - like
338 // if the name, value, or description changed, or if the hypertext had
339 // text inserted or removed. It's able to figure out exactly what changed
340 // because we still have old_win_attributes_ populated.
341 // This step has to run after the previous two steps complete because the
342 // client may walk the tree when it receives any of these events.
343 // At the end, it deletes old_win_attributes_ since they're not needed
345 for (size_t i
= 0; i
< changes
.size(); ++i
) {
346 BrowserAccessibility
* obj
= GetFromAXNode(changes
[i
].node
);
347 if (obj
&& obj
->IsNative() && !obj
->PlatformIsChildOfLeaf()) {
348 obj
->ToBrowserAccessibilityWin()->UpdateStep3FireEvents(
349 changes
[i
].type
== AXTreeDelegate::SUBTREE_CREATED
);
354 void BrowserAccessibilityManagerWin::TrackScrollingObject(
355 BrowserAccessibilityWin
* node
) {
356 if (tracked_scroll_object_
)
357 tracked_scroll_object_
->Release();
358 tracked_scroll_object_
= node
;
359 tracked_scroll_object_
->AddRef();
362 BrowserAccessibilityWin
* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
363 LONG unique_id_win
) {
364 base::hash_map
<LONG
, int32
>::iterator iter
=
365 unique_id_to_ax_id_map_
.find(unique_id_win
);
366 if (iter
!= unique_id_to_ax_id_map_
.end()) {
367 BrowserAccessibility
* result
= GetFromID(iter
->second
);
368 if (result
&& result
->IsNative())
369 return result
->ToBrowserAccessibilityWin();
372 // Also search all child frames, such as out-of-process iframes or
373 // guest browser plugins.
375 std::vector
<BrowserAccessibilityManager
*> child_frames
;
376 delegate()->AccessibilityGetAllChildFrames(&child_frames
);
377 for (size_t i
= 0; i
< child_frames
.size(); ++i
) {
378 BrowserAccessibilityManagerWin
* child_manager
=
379 child_frames
[i
]->ToBrowserAccessibilityManagerWin();
380 BrowserAccessibilityWin
* result
=
381 child_manager
->GetFromUniqueIdWin(unique_id_win
);
390 } // namespace content