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 <atlbase.h>
8 #include <atlapp.h>
9 #include <atlcom.h>
10 #include <atlcrack.h>
11 #include <oleacc.h>
12
13 #include "base/command_line.h"
14 #include "base/win/scoped_comptr.h"
15 #include "base/win/windows_version.h"
16 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
17 #include "content/browser/accessibility/browser_accessibility_win.h"
18 #include "content/common/accessibility_messages.h"
19
20 namespace content {
21
22 // Some screen readers expect every tab / every unique web content container
23 // to be in its own HWND, like it was before Aura, but with Aura there's just
24 // one main HWND for a frame, or even for the whole desktop. So, we need a
25 // fake HWND as the root of the accessibility tree for each tab.
26 // We should get rid of this code when the latest two versions of all
27 // supported screen readers no longer make this assumption.
28 //
29 // This class implements a child HWND with zero size, that delegates its
30 // accessibility implementation to the root of the BrowserAccessibilityManager
31 // tree. This HWND is hooked up as the parent of the root object in the
32 // BrowserAccessibilityManager tree, so when any accessibility client
33 // calls ::WindowFromAccessibleObject, they get this HWND instead of the
34 // DesktopRootWindowHostWin.
35 class AccessibleHWND
36 : public ATL::CWindowImpl<AccessibleHWND,
37 ATL::CWindow,
38 ATL::CWinTraits<WS_CHILD> > {
39 public:
40 // Unfortunately, some screen readers look for this exact window class
41 // to enable certain features. It'd be great to remove this.
42 DECLARE_WND_CLASS_EX(L"Chrome_RenderWidgetHostHWND", CS_DBLCLKS, 0);
43
44 BEGIN_MSG_MAP_EX(AccessibleHWND)
MESSAGE_HANDLER_EX(WM_GETOBJECT,OnGetObject)45 MESSAGE_HANDLER_EX(WM_GETOBJECT, OnGetObject)
46 END_MSG_MAP()
47
48 AccessibleHWND(HWND parent, BrowserAccessibilityManagerWin* manager)
49 : manager_(manager) {
50 Create(parent);
51 ShowWindow(true);
52 MoveWindow(0, 0, 0, 0);
53
54 HRESULT hr = ::CreateStdAccessibleObject(
55 hwnd(), OBJID_WINDOW, IID_IAccessible,
56 reinterpret_cast<void **>(window_accessible_.Receive()));
57 DCHECK(SUCCEEDED(hr));
58 }
59
hwnd()60 HWND hwnd() {
61 DCHECK(::IsWindow(m_hWnd));
62 return m_hWnd;
63 }
64
window_accessible()65 IAccessible* window_accessible() { return window_accessible_; }
66
OnManagerDeleted()67 void OnManagerDeleted() {
68 manager_ = NULL;
69 }
70
71 protected:
OnFinalMessage(HWND hwnd)72 virtual void OnFinalMessage(HWND hwnd) OVERRIDE {
73 if (manager_)
74 manager_->OnAccessibleHwndDeleted();
75 delete this;
76 }
77
78 private:
OnGetObject(UINT message,WPARAM w_param,LPARAM l_param)79 LRESULT OnGetObject(UINT message,
80 WPARAM w_param,
81 LPARAM l_param) {
82 if (OBJID_CLIENT != l_param || !manager_)
83 return static_cast<LRESULT>(0L);
84
85 base::win::ScopedComPtr<IAccessible> root(
86 manager_->GetRoot()->ToBrowserAccessibilityWin());
87 return LresultFromObject(IID_IAccessible, w_param,
88 static_cast<IAccessible*>(root.Detach()));
89 }
90
91 BrowserAccessibilityManagerWin* manager_;
92 base::win::ScopedComPtr<IAccessible> window_accessible_;
93
94 DISALLOW_COPY_AND_ASSIGN(AccessibleHWND);
95 };
96
97
98 // static
Create(const AccessibilityNodeData & src,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)99 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
100 const AccessibilityNodeData& src,
101 BrowserAccessibilityDelegate* delegate,
102 BrowserAccessibilityFactory* factory) {
103 return new BrowserAccessibilityManagerWin(
104 GetDesktopWindow(), NULL, src, delegate, factory);
105 }
106
107 BrowserAccessibilityManagerWin*
ToBrowserAccessibilityManagerWin()108 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
109 return static_cast<BrowserAccessibilityManagerWin*>(this);
110 }
111
BrowserAccessibilityManagerWin(HWND parent_hwnd,IAccessible * parent_iaccessible,const AccessibilityNodeData & src,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)112 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
113 HWND parent_hwnd,
114 IAccessible* parent_iaccessible,
115 const AccessibilityNodeData& src,
116 BrowserAccessibilityDelegate* delegate,
117 BrowserAccessibilityFactory* factory)
118 : BrowserAccessibilityManager(src, delegate, factory),
119 parent_hwnd_(parent_hwnd),
120 parent_iaccessible_(parent_iaccessible),
121 tracked_scroll_object_(NULL),
122 is_chrome_frame_(
123 CommandLine::ForCurrentProcess()->HasSwitch("chrome-frame")),
124 accessible_hwnd_(NULL) {
125 }
126
~BrowserAccessibilityManagerWin()127 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
128 if (tracked_scroll_object_) {
129 tracked_scroll_object_->Release();
130 tracked_scroll_object_ = NULL;
131 }
132 if (accessible_hwnd_)
133 accessible_hwnd_->OnManagerDeleted();
134 }
135
136 // static
GetEmptyDocument()137 AccessibilityNodeData BrowserAccessibilityManagerWin::GetEmptyDocument() {
138 AccessibilityNodeData empty_document;
139 empty_document.id = 0;
140 empty_document.role = blink::WebAXRoleRootWebArea;
141 empty_document.state =
142 (1 << blink::WebAXStateEnabled) |
143 (1 << blink::WebAXStateReadonly) |
144 (1 << blink::WebAXStateBusy);
145 return empty_document;
146 }
147
MaybeCallNotifyWinEvent(DWORD event,LONG child_id)148 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event,
149 LONG child_id) {
150 // Don't fire events if this view isn't hooked up to its parent.
151 if (!parent_iaccessible())
152 return;
153
154 #if defined(USE_AURA)
155 // If this is an Aura build on Win 7 and complete accessibility is
156 // enabled, create a fake child HWND to use as the root of the
157 // accessibility tree. See comments above AccessibleHWND for details.
158 if (BrowserAccessibilityStateImpl::GetInstance()->IsAccessibleBrowser() &&
159 !is_chrome_frame_ &&
160 !accessible_hwnd_) {
161 accessible_hwnd_ = new AccessibleHWND(parent_hwnd_, this);
162 parent_hwnd_ = accessible_hwnd_->hwnd();
163 parent_iaccessible_ = accessible_hwnd_->window_accessible();
164 }
165 #endif
166
167 ::NotifyWinEvent(event, parent_hwnd(), OBJID_CLIENT, child_id);
168 }
169
AddNodeToMap(BrowserAccessibility * node)170 void BrowserAccessibilityManagerWin::AddNodeToMap(BrowserAccessibility* node) {
171 BrowserAccessibilityManager::AddNodeToMap(node);
172 LONG unique_id_win = node->ToBrowserAccessibilityWin()->unique_id_win();
173 unique_id_to_renderer_id_map_[unique_id_win] = node->renderer_id();
174 }
175
RemoveNode(BrowserAccessibility * node)176 void BrowserAccessibilityManagerWin::RemoveNode(BrowserAccessibility* node) {
177 unique_id_to_renderer_id_map_.erase(
178 node->ToBrowserAccessibilityWin()->unique_id_win());
179 BrowserAccessibilityManager::RemoveNode(node);
180 if (node == tracked_scroll_object_) {
181 tracked_scroll_object_->Release();
182 tracked_scroll_object_ = NULL;
183 }
184 }
185
NotifyAccessibilityEvent(blink::WebAXEvent event_type,BrowserAccessibility * node)186 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
187 blink::WebAXEvent event_type,
188 BrowserAccessibility* node) {
189 if (node->role() == blink::WebAXRoleInlineTextBox)
190 return;
191
192 LONG event_id = EVENT_MIN;
193 switch (event_type) {
194 case blink::WebAXEventActiveDescendantChanged:
195 event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
196 break;
197 case blink::WebAXEventAlert:
198 event_id = EVENT_SYSTEM_ALERT;
199 break;
200 case blink::WebAXEventAriaAttributeChanged:
201 event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
202 break;
203 case blink::WebAXEventAutocorrectionOccured:
204 event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
205 break;
206 case blink::WebAXEventBlur:
207 // Equivalent to focus on the root.
208 event_id = EVENT_OBJECT_FOCUS;
209 node = GetRoot();
210 break;
211 case blink::WebAXEventCheckedStateChanged:
212 event_id = EVENT_OBJECT_STATECHANGE;
213 break;
214 case blink::WebAXEventChildrenChanged:
215 event_id = EVENT_OBJECT_REORDER;
216 break;
217 case blink::WebAXEventFocus:
218 event_id = EVENT_OBJECT_FOCUS;
219 break;
220 case blink::WebAXEventInvalidStatusChanged:
221 event_id = EVENT_OBJECT_STATECHANGE;
222 break;
223 case blink::WebAXEventLiveRegionChanged:
224 // TODO: try not firing a native notification at all, since
225 // on Windows, each individual item in a live region that changes
226 // already gets its own notification.
227 event_id = EVENT_OBJECT_REORDER;
228 break;
229 case blink::WebAXEventLoadComplete:
230 event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
231 break;
232 case blink::WebAXEventMenuListItemSelected:
233 event_id = EVENT_OBJECT_FOCUS;
234 break;
235 case blink::WebAXEventMenuListValueChanged:
236 event_id = EVENT_OBJECT_VALUECHANGE;
237 break;
238 case blink::WebAXEventHide:
239 event_id = EVENT_OBJECT_HIDE;
240 break;
241 case blink::WebAXEventShow:
242 event_id = EVENT_OBJECT_SHOW;
243 break;
244 case blink::WebAXEventScrolledToAnchor:
245 event_id = EVENT_SYSTEM_SCROLLINGSTART;
246 break;
247 case blink::WebAXEventSelectedChildrenChanged:
248 event_id = EVENT_OBJECT_SELECTIONWITHIN;
249 break;
250 case blink::WebAXEventSelectedTextChanged:
251 event_id = IA2_EVENT_TEXT_CARET_MOVED;
252 break;
253 case blink::WebAXEventTextChanged:
254 event_id = EVENT_OBJECT_NAMECHANGE;
255 break;
256 case blink::WebAXEventTextInserted:
257 event_id = IA2_EVENT_TEXT_INSERTED;
258 break;
259 case blink::WebAXEventTextRemoved:
260 event_id = IA2_EVENT_TEXT_REMOVED;
261 break;
262 case blink::WebAXEventValueChanged:
263 event_id = EVENT_OBJECT_VALUECHANGE;
264 break;
265 default:
266 // Not all WebKit accessibility events result in a Windows
267 // accessibility notification.
268 break;
269 }
270
271 if (event_id != EVENT_MIN) {
272 // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
273 // the AT client will then call get_accChild on the HWND's accessibility
274 // object and pass it that same id, which we can use to retrieve the
275 // IAccessible for this node.
276 LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win();
277 MaybeCallNotifyWinEvent(event_id, child_id);
278 }
279
280 // If this is a layout complete notification (sent when a container scrolls)
281 // and there is a descendant tracked object, send a notification on it.
282 // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
283 if (event_type == blink::WebAXEventLayoutComplete &&
284 tracked_scroll_object_ &&
285 tracked_scroll_object_->IsDescendantOf(node)) {
286 MaybeCallNotifyWinEvent(
287 IA2_EVENT_VISIBLE_DATA_CHANGED,
288 tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win());
289 tracked_scroll_object_->Release();
290 tracked_scroll_object_ = NULL;
291 }
292 }
293
TrackScrollingObject(BrowserAccessibilityWin * node)294 void BrowserAccessibilityManagerWin::TrackScrollingObject(
295 BrowserAccessibilityWin* node) {
296 if (tracked_scroll_object_)
297 tracked_scroll_object_->Release();
298 tracked_scroll_object_ = node;
299 tracked_scroll_object_->AddRef();
300 }
301
GetFromUniqueIdWin(LONG unique_id_win)302 BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
303 LONG unique_id_win) {
304 base::hash_map<LONG, int32>::iterator iter =
305 unique_id_to_renderer_id_map_.find(unique_id_win);
306 if (iter != unique_id_to_renderer_id_map_.end()) {
307 BrowserAccessibility* result = GetFromRendererID(iter->second);
308 if (result)
309 return result->ToBrowserAccessibilityWin();
310 }
311 return NULL;
312 }
313
OnAccessibleHwndDeleted()314 void BrowserAccessibilityManagerWin::OnAccessibleHwndDeleted() {
315 // If the AccessibleHWND is deleted, |parent_hwnd_| and
316 // |parent_iaccessible_| are no longer valid either, since they were
317 // derived from AccessibleHWND. We don't have to restore them to
318 // previous values, though, because this should only happen
319 // during the destruct sequence for this window.
320 accessible_hwnd_ = NULL;
321 parent_hwnd_ = NULL;
322 parent_iaccessible_ = NULL;
323 }
324
325 } // namespace content
326