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
12namespace content {
13
14// static
15BrowserAccessibilityManager* 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
23BrowserAccessibilityManagerMac::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
34ui::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
45BrowserAccessibility* BrowserAccessibilityManagerMac::GetFocus(
46    BrowserAccessibility* root) {
47  BrowserAccessibility* node = GetActiveDescendantFocus(root);
48  return node;
49}
50
51void 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_ROW_COUNT_CHANGED:
100      event_id = NSAccessibilityRowCountChangedNotification;
101      break;
102    case ui::AX_EVENT_ROW_COLLAPSED:
103      event_id = @"AXRowCollapsed";
104      break;
105    case ui::AX_EVENT_ROW_EXPANDED:
106      event_id = @"AXRowExpanded";
107      break;
108    case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
109      // Not used on Mac.
110      return;
111    case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED:
112      event_id = NSAccessibilitySelectedChildrenChangedNotification;
113      break;
114    case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
115      event_id = NSAccessibilitySelectedTextChangedNotification;
116      break;
117    case ui::AX_EVENT_VALUE_CHANGED:
118      event_id = NSAccessibilityValueChangedNotification;
119      break;
120    case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED:
121      // Not used on Mac.
122      return;
123    case ui::AX_EVENT_AUTOCORRECTION_OCCURED:
124      // Not used on Mac.
125      return;
126    case ui::AX_EVENT_INVALID_STATUS_CHANGED:
127      // Not used on Mac.
128      return;
129    case ui::AX_EVENT_LOCATION_CHANGED:
130      // Not used on Mac.
131      return;
132    case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED:
133      // Not used on Mac.
134      return;
135    case ui::AX_EVENT_TEXT_CHANGED:
136      // Not used on Mac.
137      return;
138    default:
139      LOG(WARNING) << "Unknown accessibility event: " << event_type;
140      return;
141  }
142  BrowserAccessibilityCocoa* native_node = node->ToBrowserAccessibilityCocoa();
143  DCHECK(native_node);
144  NSAccessibilityPostNotification(native_node, event_id);
145}
146
147void BrowserAccessibilityManagerMac::OnNodeCreationFinished(ui::AXNode* node) {
148  BrowserAccessibility* obj = GetFromAXNode(node);
149  if (obj && obj->HasStringAttribute(ui::AX_ATTR_LIVE_STATUS))
150    created_live_region_ = true;
151}
152
153void BrowserAccessibilityManagerMac::OnTreeUpdateFinished() {
154  if (!created_live_region_)
155    return;
156
157  // This code is to work around a bug in VoiceOver, where a new live
158  // region that gets added is ignored. VoiceOver seems to only scan the
159  // page for live regions once. By recreating the NSAccessibility
160  // object for the root of the tree, we force VoiceOver to clear out its
161  // internal state and find newly-added live regions this time.
162  BrowserAccessibilityMac* root =
163      static_cast<BrowserAccessibilityMac*>(GetRoot());
164  root->RecreateNativeObject();
165  NotifyAccessibilityEvent(ui::AX_EVENT_CHILDREN_CHANGED, root);
166
167  created_live_region_ = false;
168}
169
170}  // namespace content
171