• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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