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 "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"
19 BrowserAccessibilityManager
* BrowserAccessibilityManager::Create(
20 const ui::AXTreeUpdate
& initial_tree
,
21 BrowserAccessibilityDelegate
* delegate
,
22 BrowserAccessibilityFactory
* factory
) {
23 return new BrowserAccessibilityManagerWin(initial_tree
, delegate
, factory
);
26 BrowserAccessibilityManagerWin
*
27 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
28 return static_cast<BrowserAccessibilityManagerWin
*>(this);
31 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
32 const ui::AXTreeUpdate
& initial_tree
,
33 BrowserAccessibilityDelegate
* delegate
,
34 BrowserAccessibilityFactory
* factory
)
35 : BrowserAccessibilityManager(delegate
, factory
),
36 tracked_scroll_object_(NULL
),
37 focus_event_on_root_needed_(false) {
38 ui::win::CreateATLModuleIfNeeded();
39 Initialize(initial_tree
);
42 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
43 if (tracked_scroll_object_
) {
44 tracked_scroll_object_
->Release();
45 tracked_scroll_object_
= NULL
;
50 ui::AXTreeUpdate
BrowserAccessibilityManagerWin::GetEmptyDocument() {
51 ui::AXNodeData empty_document
;
52 empty_document
.id
= 0;
53 empty_document
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
54 empty_document
.state
=
55 (1 << ui::AX_STATE_ENABLED
) |
56 (1 << ui::AX_STATE_READ_ONLY
) |
57 (1 << ui::AX_STATE_BUSY
);
59 ui::AXTreeUpdate update
;
60 update
.nodes
.push_back(empty_document
);
64 HWND
BrowserAccessibilityManagerWin::GetParentHWND() {
67 return delegate_
->AccessibilityGetAcceleratedWidget();
70 IAccessible
* BrowserAccessibilityManagerWin::GetParentIAccessible() {
73 return delegate_
->AccessibilityGetNativeViewAccessible();
76 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event
,
81 HWND hwnd
= delegate_
->AccessibilityGetAcceleratedWidget();
85 ::NotifyWinEvent(event
, hwnd
, OBJID_CLIENT
, child_id
);
89 void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXNode
* node
) {
90 BrowserAccessibilityManager::OnNodeCreated(node
);
91 BrowserAccessibility
* obj
= GetFromAXNode(node
);
92 LONG unique_id_win
= obj
->ToBrowserAccessibilityWin()->unique_id_win();
93 unique_id_to_ax_id_map_
[unique_id_win
] = obj
->GetId();
96 void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXNode
* node
) {
97 BrowserAccessibilityManager::OnNodeWillBeDeleted(node
);
98 BrowserAccessibility
* obj
= GetFromAXNode(node
);
101 unique_id_to_ax_id_map_
.erase(
102 obj
->ToBrowserAccessibilityWin()->unique_id_win());
103 if (obj
== tracked_scroll_object_
) {
104 tracked_scroll_object_
->Release();
105 tracked_scroll_object_
= NULL
;
109 void BrowserAccessibilityManagerWin::OnWindowFocused() {
110 // This is called either when this web frame gets focused, or when
111 // the root of the accessibility tree changes. In both cases, we need
112 // to fire a focus event on the root and then on the focused element
113 // within the page, if different.
115 // Set this flag so that we'll keep trying to fire these focus events
116 // if they're not successful this time.
117 focus_event_on_root_needed_
= true;
119 if (!delegate_
|| !delegate_
->AccessibilityViewHasFocus())
122 // Try to fire a focus event on the root first and then the focused node.
123 // This will clear focus_event_on_root_needed_ if successful.
124 if (focus_
!= tree_
->GetRoot())
125 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, GetRoot());
126 BrowserAccessibilityManager::OnWindowFocused();
129 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
130 ui::AXEvent event_type
,
131 BrowserAccessibility
* node
) {
132 if (!delegate_
|| !delegate_
->AccessibilityGetAcceleratedWidget())
135 // Inline text boxes are an internal implementation detail, we don't
136 // expose them to Windows.
137 if (node
->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX
)
140 // Don't fire focus, blur, or load complete notifications if the
141 // window isn't focused, because that can confuse screen readers into
142 // entering their "browse" mode.
143 if ((event_type
== ui::AX_EVENT_FOCUS
||
144 event_type
== ui::AX_EVENT_BLUR
||
145 event_type
== ui::AX_EVENT_LOAD_COMPLETE
) &&
146 (!delegate_
|| !delegate_
->AccessibilityViewHasFocus())) {
150 // NVDA gets confused if we focus the main document element when it hasn't
151 // finished loading and it has no children at all, so suppress that event.
152 if (event_type
== ui::AX_EVENT_FOCUS
&&
154 node
->PlatformChildCount() == 0 &&
155 !node
->HasState(ui::AX_STATE_BUSY
) &&
156 !node
->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED
)) {
160 // If a focus event is needed on the root, fire that first before
162 if (event_type
== ui::AX_EVENT_FOCUS
&& node
== GetRoot())
163 focus_event_on_root_needed_
= false;
164 else if (focus_event_on_root_needed_
)
167 LONG event_id
= EVENT_MIN
;
168 switch (event_type
) {
169 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED
:
170 event_id
= IA2_EVENT_ACTIVE_DESCENDANT_CHANGED
;
172 case ui::AX_EVENT_ALERT
:
173 event_id
= EVENT_SYSTEM_ALERT
;
175 case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED
:
176 event_id
= IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED
;
178 case ui::AX_EVENT_AUTOCORRECTION_OCCURED
:
179 event_id
= IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED
;
181 case ui::AX_EVENT_BLUR
:
182 // Equivalent to focus on the root.
183 event_id
= EVENT_OBJECT_FOCUS
;
186 case ui::AX_EVENT_CHECKED_STATE_CHANGED
:
187 event_id
= EVENT_OBJECT_STATECHANGE
;
189 case ui::AX_EVENT_CHILDREN_CHANGED
:
190 event_id
= EVENT_OBJECT_REORDER
;
192 case ui::AX_EVENT_FOCUS
:
193 event_id
= EVENT_OBJECT_FOCUS
;
195 case ui::AX_EVENT_INVALID_STATUS_CHANGED
:
196 event_id
= EVENT_OBJECT_STATECHANGE
;
198 case ui::AX_EVENT_LIVE_REGION_CHANGED
:
199 if (node
->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY
))
201 event_id
= EVENT_OBJECT_LIVEREGIONCHANGED
;
203 case ui::AX_EVENT_LOAD_COMPLETE
:
204 event_id
= IA2_EVENT_DOCUMENT_LOAD_COMPLETE
;
206 case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED
:
207 event_id
= EVENT_OBJECT_FOCUS
;
209 case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED
:
210 event_id
= EVENT_OBJECT_VALUECHANGE
;
212 case ui::AX_EVENT_HIDE
:
213 event_id
= EVENT_OBJECT_HIDE
;
215 case ui::AX_EVENT_SHOW
:
216 event_id
= EVENT_OBJECT_SHOW
;
218 case ui::AX_EVENT_SCROLL_POSITION_CHANGED
:
219 event_id
= EVENT_SYSTEM_SCROLLINGEND
;
221 case ui::AX_EVENT_SCROLLED_TO_ANCHOR
:
222 event_id
= EVENT_SYSTEM_SCROLLINGSTART
;
224 case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED
:
225 event_id
= EVENT_OBJECT_SELECTIONWITHIN
;
227 case ui::AX_EVENT_TEXT_CHANGED
:
228 event_id
= EVENT_OBJECT_NAMECHANGE
;
230 case ui::AX_EVENT_TEXT_SELECTION_CHANGED
:
231 event_id
= IA2_EVENT_TEXT_CARET_MOVED
;
233 case ui::AX_EVENT_VALUE_CHANGED
:
234 event_id
= EVENT_OBJECT_VALUECHANGE
;
237 // Not all WebKit accessibility events result in a Windows
238 // accessibility notification.
242 if (event_id
!= EVENT_MIN
) {
243 // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
244 // the AT client will then call get_accChild on the HWND's accessibility
245 // object and pass it that same id, which we can use to retrieve the
246 // IAccessible for this node.
247 LONG child_id
= node
->ToBrowserAccessibilityWin()->unique_id_win();
248 MaybeCallNotifyWinEvent(event_id
, child_id
);
251 // If this is a layout complete notification (sent when a container scrolls)
252 // and there is a descendant tracked object, send a notification on it.
253 // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
254 if (event_type
== ui::AX_EVENT_LAYOUT_COMPLETE
&&
255 tracked_scroll_object_
&&
256 tracked_scroll_object_
->IsDescendantOf(node
)) {
257 MaybeCallNotifyWinEvent(
258 IA2_EVENT_VISIBLE_DATA_CHANGED
,
259 tracked_scroll_object_
->ToBrowserAccessibilityWin()->unique_id_win());
260 tracked_scroll_object_
->Release();
261 tracked_scroll_object_
= NULL
;
265 void BrowserAccessibilityManagerWin::OnRootChanged(ui::AXNode
* new_root
) {
266 // In order to make screen readers aware of the new accessibility root,
267 // we need to fire a focus event on it.
271 void BrowserAccessibilityManagerWin::TrackScrollingObject(
272 BrowserAccessibilityWin
* node
) {
273 if (tracked_scroll_object_
)
274 tracked_scroll_object_
->Release();
275 tracked_scroll_object_
= node
;
276 tracked_scroll_object_
->AddRef();
279 BrowserAccessibilityWin
* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
280 LONG unique_id_win
) {
281 base::hash_map
<LONG
, int32
>::iterator iter
=
282 unique_id_to_ax_id_map_
.find(unique_id_win
);
283 if (iter
!= unique_id_to_ax_id_map_
.end()) {
284 BrowserAccessibility
* result
= GetFromID(iter
->second
);
286 return result
->ToBrowserAccessibilityWin();
291 } // namespace content