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 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* 29 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() { 30 return static_cast<BrowserAccessibilityManagerWin*>(this); 31 } 32 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 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 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 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 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 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 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 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 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 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 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 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 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