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_mac.h" 6 7 #import "base/logging.h" 8 #import "content/browser/accessibility/browser_accessibility_cocoa.h" 9 #import "content/browser/accessibility/browser_accessibility_mac.h" 10 #include "content/common/accessibility_messages.h" 11 12 namespace content { 13 14 // static 15 BrowserAccessibilityManager* BrowserAccessibilityManager::Create( 16 const ui::AXTreeUpdate& initial_tree, 17 BrowserAccessibilityDelegate* delegate, 18 BrowserAccessibilityFactory* factory) { 19 return new BrowserAccessibilityManagerMac( 20 NULL, initial_tree, delegate, factory); 21 } 22 23 BrowserAccessibilityManagerMac::BrowserAccessibilityManagerMac( 24 NSView* parent_view, 25 const ui::AXTreeUpdate& initial_tree, 26 BrowserAccessibilityDelegate* delegate, 27 BrowserAccessibilityFactory* factory) 28 : BrowserAccessibilityManager(initial_tree, delegate, factory), 29 parent_view_(parent_view), 30 created_live_region_(false) { 31 } 32 33 // static 34 ui::AXTreeUpdate BrowserAccessibilityManagerMac::GetEmptyDocument() { 35 ui::AXNodeData empty_document; 36 empty_document.id = 0; 37 empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA; 38 empty_document.state = 39 1 << ui::AX_STATE_READ_ONLY; 40 ui::AXTreeUpdate update; 41 update.nodes.push_back(empty_document); 42 return update; 43 } 44 45 BrowserAccessibility* BrowserAccessibilityManagerMac::GetFocus( 46 BrowserAccessibility* root) { 47 BrowserAccessibility* node = GetActiveDescendantFocus(root); 48 return node; 49 } 50 51 void BrowserAccessibilityManagerMac::NotifyAccessibilityEvent( 52 ui::AXEvent event_type, 53 BrowserAccessibility* node) { 54 if (!node->IsNative()) 55 return; 56 57 // Refer to AXObjectCache.mm (webkit). 58 NSString* event_id = @""; 59 switch (event_type) { 60 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED: 61 if (node->GetRole() == ui::AX_ROLE_TREE) { 62 event_id = NSAccessibilitySelectedRowsChangedNotification; 63 } else { 64 event_id = NSAccessibilityFocusedUIElementChangedNotification; 65 BrowserAccessibility* active_descendant_focus = 66 GetActiveDescendantFocus(GetRoot()); 67 if (active_descendant_focus) 68 node = active_descendant_focus; 69 } 70 71 break; 72 case ui::AX_EVENT_ALERT: 73 // Not used on Mac. 74 return; 75 case ui::AX_EVENT_BLUR: 76 // A no-op on Mac. 77 return; 78 case ui::AX_EVENT_CHECKED_STATE_CHANGED: 79 // Not used on Mac. 80 return; 81 case ui::AX_EVENT_CHILDREN_CHANGED: 82 // TODO(dtseng): no clear equivalent on Mac. 83 return; 84 case ui::AX_EVENT_FOCUS: 85 event_id = NSAccessibilityFocusedUIElementChangedNotification; 86 break; 87 case ui::AX_EVENT_LAYOUT_COMPLETE: 88 event_id = @"AXLayoutComplete"; 89 break; 90 case ui::AX_EVENT_LIVE_REGION_CHANGED: 91 event_id = @"AXLiveRegionChanged"; 92 break; 93 case ui::AX_EVENT_LOAD_COMPLETE: 94 event_id = @"AXLoadComplete"; 95 break; 96 case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED: 97 // Not used on Mac. 98 return; 99 case ui::AX_EVENT_SHOW: 100 // Not used on Mac. 101 return; 102 case ui::AX_EVENT_HIDE: 103 // Not used on Mac. 104 return; 105 case ui::AX_EVENT_ROW_COUNT_CHANGED: 106 event_id = NSAccessibilityRowCountChangedNotification; 107 break; 108 case ui::AX_EVENT_ROW_COLLAPSED: 109 event_id = @"AXRowCollapsed"; 110 break; 111 case ui::AX_EVENT_ROW_EXPANDED: 112 event_id = @"AXRowExpanded"; 113 break; 114 case ui::AX_EVENT_SCROLLED_TO_ANCHOR: 115 // Not used on Mac. 116 return; 117 case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED: 118 event_id = NSAccessibilitySelectedChildrenChangedNotification; 119 break; 120 case ui::AX_EVENT_SELECTED_TEXT_CHANGED: 121 event_id = NSAccessibilitySelectedTextChangedNotification; 122 break; 123 case ui::AX_EVENT_TEXT_INSERTED: 124 // Not used on Mac. 125 return; 126 case ui::AX_EVENT_TEXT_REMOVED: 127 // Not used on Mac. 128 return; 129 case ui::AX_EVENT_VALUE_CHANGED: 130 event_id = NSAccessibilityValueChangedNotification; 131 break; 132 case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED: 133 // Not used on Mac. 134 return; 135 case ui::AX_EVENT_AUTOCORRECTION_OCCURED: 136 // Not used on Mac. 137 return; 138 case ui::AX_EVENT_INVALID_STATUS_CHANGED: 139 // Not used on Mac. 140 return; 141 case ui::AX_EVENT_LOCATION_CHANGED: 142 // Not used on Mac. 143 return; 144 case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED: 145 // Not used on Mac. 146 return; 147 case ui::AX_EVENT_TEXT_CHANGED: 148 // Not used on Mac. 149 return; 150 default: 151 LOG(WARNING) << "Unknown accessibility event: " << event_type; 152 return; 153 } 154 BrowserAccessibilityCocoa* native_node = node->ToBrowserAccessibilityCocoa(); 155 DCHECK(native_node); 156 NSAccessibilityPostNotification(native_node, event_id); 157 } 158 159 void BrowserAccessibilityManagerMac::OnNodeCreationFinished(ui::AXNode* node) { 160 BrowserAccessibility* obj = GetFromAXNode(node); 161 if (obj && obj->HasStringAttribute(ui::AX_ATTR_LIVE_STATUS)) 162 created_live_region_ = true; 163 } 164 165 void BrowserAccessibilityManagerMac::OnTreeUpdateFinished() { 166 if (!created_live_region_) 167 return; 168 169 // This code is to work around a bug in VoiceOver, where a new live 170 // region that gets added is ignored. VoiceOver seems to only scan the 171 // page for live regions once. By recreating the NSAccessibility 172 // object for the root of the tree, we force VoiceOver to clear out its 173 // internal state and find newly-added live regions this time. 174 BrowserAccessibilityMac* root = 175 static_cast<BrowserAccessibilityMac*>(GetRoot()); 176 root->RecreateNativeObject(); 177 NotifyAccessibilityEvent(ui::AX_EVENT_CHILDREN_CHANGED, root); 178 179 created_live_region_ = false; 180 } 181 182 } // namespace content 183