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
16 namespace content {
17
18 // static
Create(const ui::AXTreeUpdate & initial_tree,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)19 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
20 const ui::AXTreeUpdate& initial_tree,
21 BrowserAccessibilityDelegate* delegate,
22 BrowserAccessibilityFactory* factory) {
23 return new BrowserAccessibilityManagerWin(
24 content::LegacyRenderWidgetHostHWND::Create(GetDesktopWindow()).get(),
25 NULL, initial_tree, delegate, factory);
26 }
27
28 BrowserAccessibilityManagerWin*
ToBrowserAccessibilityManagerWin()29 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
30 return static_cast<BrowserAccessibilityManagerWin*>(this);
31 }
32
BrowserAccessibilityManagerWin(LegacyRenderWidgetHostHWND * accessible_hwnd,IAccessible * parent_iaccessible,const ui::AXTreeUpdate & initial_tree,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)33 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
34 LegacyRenderWidgetHostHWND* accessible_hwnd,
35 IAccessible* parent_iaccessible,
36 const ui::AXTreeUpdate& initial_tree,
37 BrowserAccessibilityDelegate* delegate,
38 BrowserAccessibilityFactory* factory)
39 : BrowserAccessibilityManager(initial_tree, delegate, factory),
40 parent_hwnd_(NULL),
41 parent_iaccessible_(parent_iaccessible),
42 tracked_scroll_object_(NULL),
43 accessible_hwnd_(accessible_hwnd),
44 focus_event_on_root_needed_(false) {
45 ui::win::CreateATLModuleIfNeeded();
46 if (accessible_hwnd_) {
47 accessible_hwnd_->set_browser_accessibility_manager(this);
48 parent_hwnd_ = accessible_hwnd_->GetParent();
49 }
50 }
51
~BrowserAccessibilityManagerWin()52 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
53 if (tracked_scroll_object_) {
54 tracked_scroll_object_->Release();
55 tracked_scroll_object_ = NULL;
56 }
57 if (accessible_hwnd_)
58 accessible_hwnd_->OnManagerDeleted();
59 }
60
61 // static
GetEmptyDocument()62 ui::AXTreeUpdate BrowserAccessibilityManagerWin::GetEmptyDocument() {
63 ui::AXNodeData empty_document;
64 empty_document.id = 0;
65 empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
66 empty_document.state =
67 (1 << ui::AX_STATE_ENABLED) |
68 (1 << ui::AX_STATE_READ_ONLY) |
69 (1 << ui::AX_STATE_BUSY);
70
71 ui::AXTreeUpdate update;
72 update.nodes.push_back(empty_document);
73 return update;
74 }
75
SetAccessibleHWND(LegacyRenderWidgetHostHWND * accessible_hwnd)76 void BrowserAccessibilityManagerWin::SetAccessibleHWND(
77 LegacyRenderWidgetHostHWND* accessible_hwnd) {
78 accessible_hwnd_ = accessible_hwnd;
79 if (accessible_hwnd_) {
80 accessible_hwnd_->set_browser_accessibility_manager(this);
81 parent_hwnd_ = accessible_hwnd_->GetParent();
82 }
83 }
84
MaybeCallNotifyWinEvent(DWORD event,LONG child_id)85 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event,
86 LONG child_id) {
87 // If on Win 7 and complete accessibility is enabled, use the fake child HWND
88 // to use as the root of the accessibility tree. See comments above
89 // LegacyRenderWidgetHostHWND for details.
90 if (accessible_hwnd_ &&
91 BrowserAccessibilityStateImpl::GetInstance()->IsAccessibleBrowser()) {
92 parent_hwnd_ = accessible_hwnd_->hwnd();
93 parent_iaccessible_ = accessible_hwnd_->window_accessible();
94 }
95
96 // Only fire events if this view is hooked up to its parent.
97 if (parent_iaccessible() && parent_hwnd())
98 ::NotifyWinEvent(event, parent_hwnd(), OBJID_CLIENT, child_id);
99 }
100
101
OnNodeCreated(ui::AXNode * node)102 void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXNode* node) {
103 BrowserAccessibilityManager::OnNodeCreated(node);
104 BrowserAccessibility* obj = GetFromAXNode(node);
105 LONG unique_id_win = obj->ToBrowserAccessibilityWin()->unique_id_win();
106 unique_id_to_ax_id_map_[unique_id_win] = obj->GetId();
107 }
108
OnNodeWillBeDeleted(ui::AXNode * node)109 void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXNode* node) {
110 BrowserAccessibilityManager::OnNodeWillBeDeleted(node);
111 BrowserAccessibility* obj = GetFromAXNode(node);
112 if (!obj)
113 return;
114 unique_id_to_ax_id_map_.erase(
115 obj->ToBrowserAccessibilityWin()->unique_id_win());
116 if (obj == tracked_scroll_object_) {
117 tracked_scroll_object_->Release();
118 tracked_scroll_object_ = NULL;
119 }
120 }
121
OnWindowFocused()122 void BrowserAccessibilityManagerWin::OnWindowFocused() {
123 // This is called either when this web frame gets focused, or when
124 // the root of the accessibility tree changes. In both cases, we need
125 // to fire a focus event on the root and then on the focused element
126 // within the page, if different.
127
128 // Set this flag so that we'll keep trying to fire these focus events
129 // if they're not successful this time.
130 focus_event_on_root_needed_ = true;
131
132 if (!delegate_ || !delegate_->AccessibilityViewHasFocus())
133 return;
134
135 // Try to fire a focus event on the root first and then the focused node.
136 // This will clear focus_event_on_root_needed_ if successful.
137 if (focus_ != tree_->GetRoot())
138 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetRoot());
139 BrowserAccessibilityManager::OnWindowFocused();
140 }
141
NotifyAccessibilityEvent(ui::AXEvent event_type,BrowserAccessibility * node)142 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
143 ui::AXEvent event_type,
144 BrowserAccessibility* node) {
145 if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
146 return;
147
148 // Don't fire focus, blur, or load complete notifications if the
149 // window isn't focused, because that can confuse screen readers into
150 // entering their "browse" mode.
151 if ((event_type == ui::AX_EVENT_FOCUS ||
152 event_type == ui::AX_EVENT_BLUR ||
153 event_type == ui::AX_EVENT_LOAD_COMPLETE) &&
154 (!delegate_ || !delegate_->AccessibilityViewHasFocus())) {
155 return;
156 }
157
158 // NVDA gets confused if we focus the main document element when it hasn't
159 // finished loading and it has no children at all, so suppress that event.
160 if (event_type == ui::AX_EVENT_FOCUS &&
161 node == GetRoot() &&
162 node->PlatformChildCount() == 0 &&
163 !node->HasState(ui::AX_STATE_BUSY) &&
164 !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) {
165 return;
166 }
167
168 // If a focus event is needed on the root, fire that first before
169 // this event.
170 if (event_type == ui::AX_EVENT_FOCUS && node == GetRoot())
171 focus_event_on_root_needed_ = false;
172 else if (focus_event_on_root_needed_)
173 OnWindowFocused();
174
175 LONG event_id = EVENT_MIN;
176 switch (event_type) {
177 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED:
178 event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
179 break;
180 case ui::AX_EVENT_ALERT:
181 event_id = EVENT_SYSTEM_ALERT;
182 break;
183 case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED:
184 event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
185 break;
186 case ui::AX_EVENT_AUTOCORRECTION_OCCURED:
187 event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
188 break;
189 case ui::AX_EVENT_BLUR:
190 // Equivalent to focus on the root.
191 event_id = EVENT_OBJECT_FOCUS;
192 node = GetRoot();
193 break;
194 case ui::AX_EVENT_CHECKED_STATE_CHANGED:
195 event_id = EVENT_OBJECT_STATECHANGE;
196 break;
197 case ui::AX_EVENT_CHILDREN_CHANGED:
198 event_id = EVENT_OBJECT_REORDER;
199 break;
200 case ui::AX_EVENT_FOCUS:
201 event_id = EVENT_OBJECT_FOCUS;
202 break;
203 case ui::AX_EVENT_INVALID_STATUS_CHANGED:
204 event_id = EVENT_OBJECT_STATECHANGE;
205 break;
206 case ui::AX_EVENT_LIVE_REGION_CHANGED:
207 if (node->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY))
208 return;
209 event_id = EVENT_OBJECT_LIVEREGIONCHANGED;
210 break;
211 case ui::AX_EVENT_LOAD_COMPLETE:
212 event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
213 break;
214 case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED:
215 event_id = EVENT_OBJECT_FOCUS;
216 break;
217 case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED:
218 event_id = EVENT_OBJECT_VALUECHANGE;
219 break;
220 case ui::AX_EVENT_HIDE:
221 event_id = EVENT_OBJECT_HIDE;
222 break;
223 case ui::AX_EVENT_SHOW:
224 event_id = EVENT_OBJECT_SHOW;
225 break;
226 case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
227 event_id = EVENT_SYSTEM_SCROLLINGEND;
228 break;
229 case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
230 event_id = EVENT_SYSTEM_SCROLLINGSTART;
231 break;
232 case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED:
233 event_id = EVENT_OBJECT_SELECTIONWITHIN;
234 break;
235 case ui::AX_EVENT_SELECTED_TEXT_CHANGED:
236 event_id = IA2_EVENT_TEXT_CARET_MOVED;
237 break;
238 case ui::AX_EVENT_TEXT_CHANGED:
239 event_id = EVENT_OBJECT_NAMECHANGE;
240 break;
241 case ui::AX_EVENT_TEXT_INSERTED:
242 event_id = IA2_EVENT_TEXT_INSERTED;
243 break;
244 case ui::AX_EVENT_TEXT_REMOVED:
245 event_id = IA2_EVENT_TEXT_REMOVED;
246 break;
247 case ui::AX_EVENT_VALUE_CHANGED:
248 event_id = EVENT_OBJECT_VALUECHANGE;
249 break;
250 default:
251 // Not all WebKit accessibility events result in a Windows
252 // accessibility notification.
253 break;
254 }
255
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 LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win();
262 MaybeCallNotifyWinEvent(event_id, child_id);
263 }
264
265 // If this is a layout complete notification (sent when a container scrolls)
266 // and there is a descendant tracked object, send a notification on it.
267 // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
268 if (event_type == ui::AX_EVENT_LAYOUT_COMPLETE &&
269 tracked_scroll_object_ &&
270 tracked_scroll_object_->IsDescendantOf(node)) {
271 MaybeCallNotifyWinEvent(
272 IA2_EVENT_VISIBLE_DATA_CHANGED,
273 tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win());
274 tracked_scroll_object_->Release();
275 tracked_scroll_object_ = NULL;
276 }
277 }
278
OnRootChanged(ui::AXNode * new_root)279 void BrowserAccessibilityManagerWin::OnRootChanged(ui::AXNode* new_root) {
280 // In order to make screen readers aware of the new accessibility root,
281 // we need to fire a focus event on it.
282 OnWindowFocused();
283 }
284
TrackScrollingObject(BrowserAccessibilityWin * node)285 void BrowserAccessibilityManagerWin::TrackScrollingObject(
286 BrowserAccessibilityWin* node) {
287 if (tracked_scroll_object_)
288 tracked_scroll_object_->Release();
289 tracked_scroll_object_ = node;
290 tracked_scroll_object_->AddRef();
291 }
292
GetFromUniqueIdWin(LONG unique_id_win)293 BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
294 LONG unique_id_win) {
295 base::hash_map<LONG, int32>::iterator iter =
296 unique_id_to_ax_id_map_.find(unique_id_win);
297 if (iter != unique_id_to_ax_id_map_.end()) {
298 BrowserAccessibility* result = GetFromID(iter->second);
299 if (result)
300 return result->ToBrowserAccessibilityWin();
301 }
302 return NULL;
303 }
304
OnAccessibleHwndDeleted()305 void BrowserAccessibilityManagerWin::OnAccessibleHwndDeleted() {
306 // If the AccessibleHWND is deleted, |parent_hwnd_| and
307 // |parent_iaccessible_| are no longer valid either, since they were
308 // derived from AccessibleHWND. We don't have to restore them to
309 // previous values, though, because this should only happen
310 // during the destruct sequence for this window.
311 accessible_hwnd_ = NULL;
312 parent_hwnd_ = NULL;
313 parent_iaccessible_ = NULL;
314 }
315
316 } // namespace content
317