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) 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 60 HWND hwnd() { 61 DCHECK(::IsWindow(m_hWnd)); 62 return m_hWnd; 63 } 64 65 IAccessible* window_accessible() { return window_accessible_; } 66 67 void OnManagerDeleted() { 68 manager_ = NULL; 69 } 70 71 protected: 72 virtual void OnFinalMessage(HWND hwnd) OVERRIDE { 73 if (manager_) 74 manager_->OnAccessibleHwndDeleted(); 75 delete this; 76 } 77 78 private: 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 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* 108 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() { 109 return static_cast<BrowserAccessibilityManagerWin*>(this); 110 } 111 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 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 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 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 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 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 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 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 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 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