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"
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"
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.
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 // DesktopWindowTreeHostWin.
36 : public ATL::CWindowImpl
<AccessibleHWND
,
38 ATL::CWinTraits
<WS_CHILD
> > {
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);
44 BEGIN_MSG_MAP_EX(AccessibleHWND
)
45 MESSAGE_HANDLER_EX(WM_GETOBJECT
, OnGetObject
)
48 AccessibleHWND(HWND parent
, BrowserAccessibilityManagerWin
* manager
)
52 MoveWindow(0, 0, 0, 0);
54 HRESULT hr
= ::CreateStdAccessibleObject(
55 hwnd(), OBJID_WINDOW
, IID_IAccessible
,
56 reinterpret_cast<void **>(window_accessible_
.Receive()));
57 DCHECK(SUCCEEDED(hr
));
61 DCHECK(::IsWindow(m_hWnd
));
65 IAccessible
* window_accessible() { return window_accessible_
; }
67 void OnManagerDeleted() {
72 virtual void OnFinalMessage(HWND hwnd
) OVERRIDE
{
74 manager_
->OnAccessibleHwndDeleted();
79 LRESULT
OnGetObject(UINT message
,
82 if (OBJID_CLIENT
!= l_param
|| !manager_
)
83 return static_cast<LRESULT
>(0L);
85 base::win::ScopedComPtr
<IAccessible
> root(
86 manager_
->GetRoot()->ToBrowserAccessibilityWin());
87 return LresultFromObject(IID_IAccessible
, w_param
,
88 static_cast<IAccessible
*>(root
.Detach()));
91 BrowserAccessibilityManagerWin
* manager_
;
92 base::win::ScopedComPtr
<IAccessible
> window_accessible_
;
94 DISALLOW_COPY_AND_ASSIGN(AccessibleHWND
);
99 BrowserAccessibilityManager
* BrowserAccessibilityManager::Create(
100 const ui::AXNodeData
& src
,
101 BrowserAccessibilityDelegate
* delegate
,
102 BrowserAccessibilityFactory
* factory
) {
103 return new BrowserAccessibilityManagerWin(
104 GetDesktopWindow(), NULL
, src
, delegate
, factory
);
107 BrowserAccessibilityManagerWin
*
108 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
109 return static_cast<BrowserAccessibilityManagerWin
*>(this);
112 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
114 IAccessible
* parent_iaccessible
,
115 const ui::AXNodeData
& 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 accessible_hwnd_(NULL
) {
125 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
126 if (tracked_scroll_object_
) {
127 tracked_scroll_object_
->Release();
128 tracked_scroll_object_
= NULL
;
130 if (accessible_hwnd_
)
131 accessible_hwnd_
->OnManagerDeleted();
135 ui::AXNodeData
BrowserAccessibilityManagerWin::GetEmptyDocument() {
136 ui::AXNodeData empty_document
;
137 empty_document
.id
= 0;
138 empty_document
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
139 empty_document
.state
=
140 (1 << blink::WebAXStateEnabled
) |
141 (1 << ui::AX_STATE_READONLY
) |
142 (1 << ui::AX_STATE_BUSY
);
143 return empty_document
;
146 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event
,
148 // Don't fire events if this view isn't hooked up to its parent.
149 if (!parent_iaccessible())
152 #if defined(USE_AURA)
153 // If this is an Aura build on Win 7 and complete accessibility is
154 // enabled, create a fake child HWND to use as the root of the
155 // accessibility tree. See comments above AccessibleHWND for details.
156 if (BrowserAccessibilityStateImpl::GetInstance()->IsAccessibleBrowser() &&
158 accessible_hwnd_
= new AccessibleHWND(parent_hwnd_
, this);
159 parent_hwnd_
= accessible_hwnd_
->hwnd();
160 parent_iaccessible_
= accessible_hwnd_
->window_accessible();
164 ::NotifyWinEvent(event
, parent_hwnd(), OBJID_CLIENT
, child_id
);
167 void BrowserAccessibilityManagerWin::AddNodeToMap(BrowserAccessibility
* node
) {
168 BrowserAccessibilityManager::AddNodeToMap(node
);
169 LONG unique_id_win
= node
->ToBrowserAccessibilityWin()->unique_id_win();
170 unique_id_to_renderer_id_map_
[unique_id_win
] = node
->renderer_id();
173 void BrowserAccessibilityManagerWin::RemoveNode(BrowserAccessibility
* node
) {
174 unique_id_to_renderer_id_map_
.erase(
175 node
->ToBrowserAccessibilityWin()->unique_id_win());
176 BrowserAccessibilityManager::RemoveNode(node
);
177 if (node
== tracked_scroll_object_
) {
178 tracked_scroll_object_
->Release();
179 tracked_scroll_object_
= NULL
;
183 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
184 ui::AXEvent event_type
,
185 BrowserAccessibility
* node
) {
186 if (node
->role() == ui::AX_ROLE_INLINE_TEXT_BOX
)
189 LONG event_id
= EVENT_MIN
;
190 switch (event_type
) {
191 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED
:
192 event_id
= IA2_EVENT_ACTIVE_DESCENDANT_CHANGED
;
194 case ui::AX_EVENT_ALERT
:
195 event_id
= EVENT_SYSTEM_ALERT
;
197 case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED
:
198 event_id
= IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED
;
200 case ui::AX_EVENT_AUTOCORRECTION_OCCURED
:
201 event_id
= IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED
;
203 case ui::AX_EVENT_BLUR
:
204 // Equivalent to focus on the root.
205 event_id
= EVENT_OBJECT_FOCUS
;
208 case ui::AX_EVENT_CHECKED_STATE_CHANGED
:
209 event_id
= EVENT_OBJECT_STATECHANGE
;
211 case ui::AX_EVENT_CHILDREN_CHANGED
:
212 event_id
= EVENT_OBJECT_REORDER
;
214 case ui::AX_EVENT_FOCUS
:
215 event_id
= EVENT_OBJECT_FOCUS
;
217 case ui::AX_EVENT_INVALID_STATUS_CHANGED
:
218 event_id
= EVENT_OBJECT_STATECHANGE
;
220 case ui::AX_EVENT_LIVE_REGION_CHANGED
:
221 // TODO: try not firing a native notification at all, since
222 // on Windows, each individual item in a live region that changes
223 // already gets its own notification.
224 event_id
= EVENT_OBJECT_REORDER
;
226 case ui::AX_EVENT_LOAD_COMPLETE
:
227 event_id
= IA2_EVENT_DOCUMENT_LOAD_COMPLETE
;
229 case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED
:
230 event_id
= EVENT_OBJECT_FOCUS
;
232 case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED
:
233 event_id
= EVENT_OBJECT_VALUECHANGE
;
235 case ui::AX_EVENT_HIDE
:
236 event_id
= EVENT_OBJECT_HIDE
;
238 case ui::AX_EVENT_SHOW
:
239 event_id
= EVENT_OBJECT_SHOW
;
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_SELECTED_TEXT_CHANGED
:
248 event_id
= IA2_EVENT_TEXT_CARET_MOVED
;
250 case ui::AX_EVENT_TEXT_CHANGED
:
251 event_id
= EVENT_OBJECT_NAMECHANGE
;
253 case ui::AX_EVENT_TEXT_INSERTED
:
254 event_id
= IA2_EVENT_TEXT_INSERTED
;
256 case ui::AX_EVENT_TEXT_REMOVED
:
257 event_id
= IA2_EVENT_TEXT_REMOVED
;
259 case ui::AX_EVENT_VALUE_CHANGED
:
260 event_id
= EVENT_OBJECT_VALUECHANGE
;
263 // Not all WebKit accessibility events result in a Windows
264 // accessibility notification.
268 if (event_id
!= EVENT_MIN
) {
269 // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
270 // the AT client will then call get_accChild on the HWND's accessibility
271 // object and pass it that same id, which we can use to retrieve the
272 // IAccessible for this node.
273 LONG child_id
= node
->ToBrowserAccessibilityWin()->unique_id_win();
274 MaybeCallNotifyWinEvent(event_id
, child_id
);
277 // If this is a layout complete notification (sent when a container scrolls)
278 // and there is a descendant tracked object, send a notification on it.
279 // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
280 if (event_type
== ui::AX_EVENT_LAYOUT_COMPLETE
&&
281 tracked_scroll_object_
&&
282 tracked_scroll_object_
->IsDescendantOf(node
)) {
283 MaybeCallNotifyWinEvent(
284 IA2_EVENT_VISIBLE_DATA_CHANGED
,
285 tracked_scroll_object_
->ToBrowserAccessibilityWin()->unique_id_win());
286 tracked_scroll_object_
->Release();
287 tracked_scroll_object_
= NULL
;
291 void BrowserAccessibilityManagerWin::TrackScrollingObject(
292 BrowserAccessibilityWin
* node
) {
293 if (tracked_scroll_object_
)
294 tracked_scroll_object_
->Release();
295 tracked_scroll_object_
= node
;
296 tracked_scroll_object_
->AddRef();
299 BrowserAccessibilityWin
* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
300 LONG unique_id_win
) {
301 base::hash_map
<LONG
, int32
>::iterator iter
=
302 unique_id_to_renderer_id_map_
.find(unique_id_win
);
303 if (iter
!= unique_id_to_renderer_id_map_
.end()) {
304 BrowserAccessibility
* result
= GetFromRendererID(iter
->second
);
306 return result
->ToBrowserAccessibilityWin();
311 void BrowserAccessibilityManagerWin::OnAccessibleHwndDeleted() {
312 // If the AccessibleHWND is deleted, |parent_hwnd_| and
313 // |parent_iaccessible_| are no longer valid either, since they were
314 // derived from AccessibleHWND. We don't have to restore them to
315 // previous values, though, because this should only happen
316 // during the destruct sequence for this window.
317 accessible_hwnd_
= NULL
;
319 parent_iaccessible_
= NULL
;
322 } // namespace content