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_mac.h"
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"
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);
23 BrowserAccessibilityManagerMac::BrowserAccessibilityManagerMac(
25 const ui::AXTreeUpdate& initial_tree,
26 BrowserAccessibilityDelegate* delegate,
27 BrowserAccessibilityFactory* factory)
28 : BrowserAccessibilityManager(delegate, factory),
29 parent_view_(parent_view) {
30 Initialize(initial_tree);
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);
45 BrowserAccessibility* BrowserAccessibilityManagerMac::GetFocus(
46 BrowserAccessibility* root) {
47 // On Mac, list boxes should always get focus on the whole list, otherwise
48 // information about the number of selected items will never be reported.
49 BrowserAccessibility* node = BrowserAccessibilityManager::GetFocus(root);
50 if (node && node->GetRole() == ui::AX_ROLE_LIST_BOX)
53 // For other roles, follow the active descendant.
54 return GetActiveDescendantFocus(root);
57 void BrowserAccessibilityManagerMac::NotifyAccessibilityEvent(
58 ui::AXEvent event_type,
59 BrowserAccessibility* node) {
60 if (!node->IsNative())
63 if (event_type == ui::AX_EVENT_FOCUS &&
64 node->GetRole() == ui::AX_ROLE_LIST_BOX_OPTION &&
65 node->HasState(ui::AX_STATE_SELECTED) &&
67 node->GetParent()->GetRole() == ui::AX_ROLE_LIST_BOX) {
68 node = node->GetParent();
69 SetFocus(node, false);
72 // Refer to AXObjectCache.mm (webkit).
73 NSString* event_id = @"";
75 case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED:
76 if (node->GetRole() == ui::AX_ROLE_TREE) {
77 event_id = NSAccessibilitySelectedRowsChangedNotification;
79 event_id = NSAccessibilityFocusedUIElementChangedNotification;
80 BrowserAccessibility* active_descendant_focus =
81 GetActiveDescendantFocus(GetRoot());
82 if (active_descendant_focus)
83 node = active_descendant_focus;
87 case ui::AX_EVENT_ALERT:
90 case ui::AX_EVENT_BLUR:
93 case ui::AX_EVENT_CHECKED_STATE_CHANGED:
96 case ui::AX_EVENT_CHILDREN_CHANGED:
97 // TODO(dtseng): no clear equivalent on Mac.
99 case ui::AX_EVENT_FOCUS:
100 event_id = NSAccessibilityFocusedUIElementChangedNotification;
102 case ui::AX_EVENT_LAYOUT_COMPLETE:
103 event_id = @"AXLayoutComplete";
105 case ui::AX_EVENT_LIVE_REGION_CHANGED:
106 event_id = @"AXLiveRegionChanged";
108 case ui::AX_EVENT_LOAD_COMPLETE:
109 event_id = @"AXLoadComplete";
111 case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED:
114 case ui::AX_EVENT_ROW_COUNT_CHANGED:
115 event_id = NSAccessibilityRowCountChangedNotification;
117 case ui::AX_EVENT_ROW_COLLAPSED:
118 event_id = @"AXRowCollapsed";
120 case ui::AX_EVENT_ROW_EXPANDED:
121 event_id = @"AXRowExpanded";
123 case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
126 case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED:
127 event_id = NSAccessibilitySelectedChildrenChangedNotification;
129 case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
130 event_id = NSAccessibilitySelectedTextChangedNotification;
132 case ui::AX_EVENT_VALUE_CHANGED:
133 event_id = NSAccessibilityValueChangedNotification;
135 case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED:
138 case ui::AX_EVENT_AUTOCORRECTION_OCCURED:
141 case ui::AX_EVENT_INVALID_STATUS_CHANGED:
144 case ui::AX_EVENT_LOCATION_CHANGED:
147 case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED:
150 case ui::AX_EVENT_TEXT_CHANGED:
154 LOG(WARNING) << "Unknown accessibility event: " << event_type;
158 BrowserAccessibilityCocoa* native_node = node->ToBrowserAccessibilityCocoa();
160 NSAccessibilityPostNotification(native_node, event_id);
163 void BrowserAccessibilityManagerMac::OnAtomicUpdateFinished(
166 const std::vector<ui::AXTreeDelegate::Change>& changes) {
167 BrowserAccessibilityManager::OnAtomicUpdateFinished(
168 tree, root_changed, changes);
170 bool created_live_region = false;
171 for (size_t i = 0; i < changes.size(); ++i) {
172 if (changes[i].type != NODE_CREATED && changes[i].type != SUBTREE_CREATED)
174 BrowserAccessibility* obj = GetFromAXNode(changes[i].node);
175 if (obj && obj->HasStringAttribute(ui::AX_ATTR_LIVE_STATUS)) {
176 created_live_region = true;
181 if (!created_live_region)
184 // This code is to work around a bug in VoiceOver, where a new live
185 // region that gets added is ignored. VoiceOver seems to only scan the
186 // page for live regions once. By recreating the NSAccessibility
187 // object for the root of the tree, we force VoiceOver to clear out its
188 // internal state and find newly-added live regions this time.
189 BrowserAccessibilityMac* root =
190 static_cast<BrowserAccessibilityMac*>(GetRoot());
191 root->RecreateNativeObject();
192 NotifyAccessibilityEvent(ui::AX_EVENT_CHILDREN_CHANGED, root);
195 } // namespace content