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(initial_tree, delegate, factory); 24 } 25 26 BrowserAccessibilityManagerWin* 27 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() { 28 return static_cast<BrowserAccessibilityManagerWin*>(this); 29 } 30 31 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin( 32 const ui::AXTreeUpdate& initial_tree, 33 BrowserAccessibilityDelegate* delegate, 34 BrowserAccessibilityFactory* factory) 35 : BrowserAccessibilityManager(initial_tree, delegate, factory), 36 tracked_scroll_object_(NULL), 37 focus_event_on_root_needed_(false) { 38 ui::win::CreateATLModuleIfNeeded(); 39 } 40 41 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() { 42 if (tracked_scroll_object_) { 43 tracked_scroll_object_->Release(); 44 tracked_scroll_object_ = NULL; 45 } 46 } 47 48 // static 49 ui::AXTreeUpdate BrowserAccessibilityManagerWin::GetEmptyDocument() { 50 ui::AXNodeData empty_document; 51 empty_document.id = 0; 52 empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA; 53 empty_document.state = 54 (1 << ui::AX_STATE_ENABLED) | 55 (1 << ui::AX_STATE_READ_ONLY) | 56 (1 << ui::AX_STATE_BUSY); 57 58 ui::AXTreeUpdate update; 59 update.nodes.push_back(empty_document); 60 return update; 61 } 62 63 HWND BrowserAccessibilityManagerWin::GetParentHWND() { 64 if (!delegate_) 65 return NULL; 66 return delegate_->AccessibilityGetAcceleratedWidget(); 67 } 68 69 IAccessible* BrowserAccessibilityManagerWin::GetParentIAccessible() { 70 if (!delegate_) 71 return NULL; 72 return delegate_->AccessibilityGetNativeViewAccessible(); 73 } 74 75 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event, 76 LONG child_id) { 77 if (!delegate_) 78 return; 79 80 HWND hwnd = delegate_->AccessibilityGetAcceleratedWidget(); 81 if (!hwnd) 82 return; 83 84 ::NotifyWinEvent(event, hwnd, OBJID_CLIENT, child_id); 85 } 86 87 88 void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXNode* node) { 89 BrowserAccessibilityManager::OnNodeCreated(node); 90 BrowserAccessibility* obj = GetFromAXNode(node); 91 LONG unique_id_win = obj->ToBrowserAccessibilityWin()->unique_id_win(); 92 unique_id_to_ax_id_map_[unique_id_win] = obj->GetId(); 93 } 94 95 void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXNode* node) { 96 BrowserAccessibilityManager::OnNodeWillBeDeleted(node); 97 BrowserAccessibility* obj = GetFromAXNode(node); 98 if (!obj) 99 return; 100 unique_id_to_ax_id_map_.erase( 101 obj->ToBrowserAccessibilityWin()->unique_id_win()); 102 if (obj == tracked_scroll_object_) { 103 tracked_scroll_object_->Release(); 104 tracked_scroll_object_ = NULL; 105 } 106 } 107 108 void BrowserAccessibilityManagerWin::OnWindowFocused() { 109 // This is called either when this web frame gets focused, or when 110 // the root of the accessibility tree changes. In both cases, we need 111 // to fire a focus event on the root and then on the focused element 112 // within the page, if different. 113 114 // Set this flag so that we'll keep trying to fire these focus events 115 // if they're not successful this time. 116 focus_event_on_root_needed_ = true; 117 118 if (!delegate_ || !delegate_->AccessibilityViewHasFocus()) 119 return; 120 121 // Try to fire a focus event on the root first and then the focused node. 122 // This will clear focus_event_on_root_needed_ if successful. 123 if (focus_ != tree_->GetRoot()) 124 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetRoot()); 125 BrowserAccessibilityManager::OnWindowFocused(); 126 } 127 128 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent( 129 ui::AXEvent event_type, 130 BrowserAccessibility* node) { 131 if (!delegate_ || !delegate_->AccessibilityGetAcceleratedWidget()) 132 return; 133 134 // Inline text boxes are an internal implementation detail, we don't 135 // expose them to Windows. 136 if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX) 137 return; 138 139 // Don't fire focus, blur, or load complete notifications if the 140 // window isn't focused, because that can confuse screen readers into 141 // entering their "browse" mode. 142 if ((event_type == ui::AX_EVENT_FOCUS || 143 event_type == ui::AX_EVENT_BLUR || 144 event_type == ui::AX_EVENT_LOAD_COMPLETE) && 145 (!delegate_ || !delegate_->AccessibilityViewHasFocus())) { 146 return; 147 } 148 149 // NVDA gets confused if we focus the main document element when it hasn't 150 // finished loading and it has no children at all, so suppress that event. 151 if (event_type == ui::AX_EVENT_FOCUS && 152 node == GetRoot() && 153 node->PlatformChildCount() == 0 && 154 !node->HasState(ui::AX_STATE_BUSY) && 155 !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) { 156 return; 157 } 158 159 // If a focus event is needed on the root, fire that first before 160 // this event. 161 if (event_type == ui::AX_EVENT_FOCUS && node == GetRoot()) 162 focus_event_on_root_needed_ = false; 163 else if (focus_event_on_root_needed_) 164 OnWindowFocused(); 165 166 LONG event_id = EVENT_MIN; 167 switch (event_type) { 168 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED: 169 event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED; 170 break; 171 case ui::AX_EVENT_ALERT: 172 event_id = EVENT_SYSTEM_ALERT; 173 break; 174 case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED: 175 event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED; 176 break; 177 case ui::AX_EVENT_AUTOCORRECTION_OCCURED: 178 event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED; 179 break; 180 case ui::AX_EVENT_BLUR: 181 // Equivalent to focus on the root. 182 event_id = EVENT_OBJECT_FOCUS; 183 node = GetRoot(); 184 break; 185 case ui::AX_EVENT_CHECKED_STATE_CHANGED: 186 event_id = EVENT_OBJECT_STATECHANGE; 187 break; 188 case ui::AX_EVENT_CHILDREN_CHANGED: 189 event_id = EVENT_OBJECT_REORDER; 190 break; 191 case ui::AX_EVENT_FOCUS: 192 event_id = EVENT_OBJECT_FOCUS; 193 break; 194 case ui::AX_EVENT_INVALID_STATUS_CHANGED: 195 event_id = EVENT_OBJECT_STATECHANGE; 196 break; 197 case ui::AX_EVENT_LIVE_REGION_CHANGED: 198 if (node->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY)) 199 return; 200 event_id = EVENT_OBJECT_LIVEREGIONCHANGED; 201 break; 202 case ui::AX_EVENT_LOAD_COMPLETE: 203 event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE; 204 break; 205 case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED: 206 event_id = EVENT_OBJECT_FOCUS; 207 break; 208 case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED: 209 event_id = EVENT_OBJECT_VALUECHANGE; 210 break; 211 case ui::AX_EVENT_HIDE: 212 event_id = EVENT_OBJECT_HIDE; 213 break; 214 case ui::AX_EVENT_SHOW: 215 event_id = EVENT_OBJECT_SHOW; 216 break; 217 case ui::AX_EVENT_SCROLL_POSITION_CHANGED: 218 event_id = EVENT_SYSTEM_SCROLLINGEND; 219 break; 220 case ui::AX_EVENT_SCROLLED_TO_ANCHOR: 221 event_id = EVENT_SYSTEM_SCROLLINGSTART; 222 break; 223 case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED: 224 event_id = EVENT_OBJECT_SELECTIONWITHIN; 225 break; 226 case ui::AX_EVENT_TEXT_CHANGED: 227 event_id = EVENT_OBJECT_NAMECHANGE; 228 break; 229 case ui::AX_EVENT_TEXT_SELECTION_CHANGED: 230 event_id = IA2_EVENT_TEXT_CARET_MOVED; 231 break; 232 case ui::AX_EVENT_VALUE_CHANGED: 233 event_id = EVENT_OBJECT_VALUECHANGE; 234 break; 235 default: 236 // Not all WebKit accessibility events result in a Windows 237 // accessibility notification. 238 break; 239 } 240 241 if (event_id != EVENT_MIN) { 242 // Pass the node's unique id in the |child_id| argument to NotifyWinEvent; 243 // the AT client will then call get_accChild on the HWND's accessibility 244 // object and pass it that same id, which we can use to retrieve the 245 // IAccessible for this node. 246 LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win(); 247 MaybeCallNotifyWinEvent(event_id, child_id); 248 } 249 250 // If this is a layout complete notification (sent when a container scrolls) 251 // and there is a descendant tracked object, send a notification on it. 252 // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed. 253 if (event_type == ui::AX_EVENT_LAYOUT_COMPLETE && 254 tracked_scroll_object_ && 255 tracked_scroll_object_->IsDescendantOf(node)) { 256 MaybeCallNotifyWinEvent( 257 IA2_EVENT_VISIBLE_DATA_CHANGED, 258 tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win()); 259 tracked_scroll_object_->Release(); 260 tracked_scroll_object_ = NULL; 261 } 262 } 263 264 void BrowserAccessibilityManagerWin::OnRootChanged(ui::AXNode* new_root) { 265 // In order to make screen readers aware of the new accessibility root, 266 // we need to fire a focus event on it. 267 OnWindowFocused(); 268 } 269 270 void BrowserAccessibilityManagerWin::TrackScrollingObject( 271 BrowserAccessibilityWin* node) { 272 if (tracked_scroll_object_) 273 tracked_scroll_object_->Release(); 274 tracked_scroll_object_ = node; 275 tracked_scroll_object_->AddRef(); 276 } 277 278 BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin( 279 LONG unique_id_win) { 280 base::hash_map<LONG, int32>::iterator iter = 281 unique_id_to_ax_id_map_.find(unique_id_win); 282 if (iter != unique_id_to_ax_id_map_.end()) { 283 BrowserAccessibility* result = GetFromID(iter->second); 284 if (result) 285 return result->ToBrowserAccessibilityWin(); 286 } 287 return NULL; 288 } 289 290 } // namespace content 291