1// Copyright (c) 2011 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 "chrome/browser/accessibility/browser_accessibility_manager.h"
6
7#include "base/logging.h"
8#include "chrome/browser/accessibility/browser_accessibility.h"
9#include "content/common/view_messages.h"
10
11using webkit_glue::WebAccessibility;
12
13BrowserAccessibility* BrowserAccessibilityFactory::Create() {
14  return BrowserAccessibility::Create();
15}
16
17// Start child IDs at -1 and decrement each time, because clients use
18// child IDs of 1, 2, 3, ... to access the children of an object by
19// index, so we use negative IDs to clearly distinguish between indices
20// and unique IDs.
21// static
22int32 BrowserAccessibilityManager::next_child_id_ = -1;
23
24#if defined(OS_LINUX)
25// There's no OS-specific implementation of BrowserAccessibilityManager
26// on Linux, so just instantiate the base class.
27// static
28BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
29    gfx::NativeView parent_view,
30    const WebAccessibility& src,
31    BrowserAccessibilityDelegate* delegate,
32    BrowserAccessibilityFactory* factory) {
33  return new BrowserAccessibilityManager(
34      parent_view, src, delegate, factory);
35}
36#endif
37
38BrowserAccessibilityManager::BrowserAccessibilityManager(
39    gfx::NativeView parent_view,
40    const WebAccessibility& src,
41    BrowserAccessibilityDelegate* delegate,
42    BrowserAccessibilityFactory* factory)
43    : parent_view_(parent_view),
44      delegate_(delegate),
45      factory_(factory),
46      focus_(NULL) {
47  root_ = CreateAccessibilityTree(NULL, src, 0);
48  if (!focus_)
49    SetFocus(root_, false);
50}
51
52// static
53int32 BrowserAccessibilityManager::GetNextChildID() {
54  // Get the next child ID, and wrap around when we get near the end
55  // of a 32-bit integer range. It's okay to wrap around; we just want
56  // to avoid it as long as possible because clients may cache the ID of
57  // an object for a while to determine if they've seen it before.
58  next_child_id_--;
59  if (next_child_id_ == -2000000000)
60    next_child_id_ = -1;
61
62  return next_child_id_;
63}
64
65BrowserAccessibilityManager::~BrowserAccessibilityManager() {
66  // Clients could still hold references to some nodes of the tree, so
67  // calling InternalReleaseReference will make sure that as many nodes
68  // as possible are released now, and remaining nodes are marked as
69  // inactive so that calls to any methods on them will fail gracefully.
70  focus_->InternalReleaseReference(false);
71  root_->InternalReleaseReference(true);
72}
73
74BrowserAccessibility* BrowserAccessibilityManager::GetRoot() {
75  return root_;
76}
77
78BrowserAccessibility* BrowserAccessibilityManager::GetFromChildID(
79    int32 child_id) {
80  base::hash_map<int32, BrowserAccessibility*>::iterator iter =
81      child_id_map_.find(child_id);
82  if (iter != child_id_map_.end()) {
83    return iter->second;
84  } else {
85    return NULL;
86  }
87}
88
89void BrowserAccessibilityManager::Remove(int32 child_id, int32 renderer_id) {
90  child_id_map_.erase(child_id);
91  renderer_id_to_child_id_map_.erase(renderer_id);
92}
93
94void BrowserAccessibilityManager::OnAccessibilityNotifications(
95    const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) {
96  for (uint32 index = 0; index < params.size(); index++) {
97    const ViewHostMsg_AccessibilityNotification_Params& param = params[index];
98
99    switch (param.notification_type) {
100      case ViewHostMsg_AccessibilityNotification_Type::
101          NOTIFICATION_TYPE_CHECK_STATE_CHANGED:
102        OnAccessibilityObjectStateChange(param.acc_obj);
103        break;
104      case ViewHostMsg_AccessibilityNotification_Type::
105          NOTIFICATION_TYPE_CHILDREN_CHANGED:
106        OnAccessibilityObjectChildrenChange(param.acc_obj);
107        break;
108      case ViewHostMsg_AccessibilityNotification_Type::
109          NOTIFICATION_TYPE_FOCUS_CHANGED:
110        OnAccessibilityObjectFocusChange(param.acc_obj);
111        break;
112      case ViewHostMsg_AccessibilityNotification_Type::
113          NOTIFICATION_TYPE_LOAD_COMPLETE:
114        OnAccessibilityObjectLoadComplete(param.acc_obj);
115        break;
116      case ViewHostMsg_AccessibilityNotification_Type::
117          NOTIFICATION_TYPE_VALUE_CHANGED:
118        OnAccessibilityObjectValueChange(param.acc_obj);
119        break;
120      case ViewHostMsg_AccessibilityNotification_Type::
121          NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED:
122        OnAccessibilityObjectTextChange(param.acc_obj);
123        break;
124      default:
125        DCHECK(0);
126        break;
127    }
128  }
129}
130
131void BrowserAccessibilityManager::OnAccessibilityObjectStateChange(
132    const WebAccessibility& acc_obj) {
133  BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false);
134  if (!new_browser_acc)
135    return;
136
137  NotifyAccessibilityEvent(
138      ViewHostMsg_AccessibilityNotification_Type::
139          NOTIFICATION_TYPE_CHECK_STATE_CHANGED,
140      new_browser_acc);
141}
142
143void BrowserAccessibilityManager::OnAccessibilityObjectChildrenChange(
144    const WebAccessibility& acc_obj) {
145  BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, true);
146  if (!new_browser_acc)
147    return;
148
149  NotifyAccessibilityEvent(
150      ViewHostMsg_AccessibilityNotification_Type::
151          NOTIFICATION_TYPE_CHILDREN_CHANGED,
152      new_browser_acc);
153}
154
155void BrowserAccessibilityManager::OnAccessibilityObjectFocusChange(
156  const WebAccessibility& acc_obj) {
157  BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false);
158  if (!new_browser_acc)
159    return;
160
161  SetFocus(new_browser_acc, false);
162  if (delegate_ && delegate_->HasFocus()) {
163    GotFocus();
164  } else if (!delegate_) {
165    // Mac currently does not have a BrowserAccessibilityDelegate.
166    NotifyAccessibilityEvent(
167        ViewHostMsg_AccessibilityNotification_Type::
168        NOTIFICATION_TYPE_FOCUS_CHANGED,
169        focus_);
170  }
171}
172
173void BrowserAccessibilityManager::OnAccessibilityObjectLoadComplete(
174  const WebAccessibility& acc_obj) {
175  SetFocus(NULL, false);
176  root_->InternalReleaseReference(true);
177
178  root_ = CreateAccessibilityTree(NULL, acc_obj, 0);
179  if (!focus_)
180    SetFocus(root_, false);
181
182  NotifyAccessibilityEvent(
183      ViewHostMsg_AccessibilityNotification_Type::
184          NOTIFICATION_TYPE_LOAD_COMPLETE,
185      root_);
186  if (delegate_ && delegate_->HasFocus())
187    GotFocus();
188}
189
190void BrowserAccessibilityManager::OnAccessibilityObjectValueChange(
191    const WebAccessibility& acc_obj) {
192  BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false);
193  if (!new_browser_acc)
194    return;
195
196  NotifyAccessibilityEvent(
197      ViewHostMsg_AccessibilityNotification_Type::
198          NOTIFICATION_TYPE_VALUE_CHANGED,
199      new_browser_acc);
200}
201
202void BrowserAccessibilityManager::OnAccessibilityObjectTextChange(
203    const WebAccessibility& acc_obj) {
204  BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false);
205  if (!new_browser_acc)
206    return;
207
208  NotifyAccessibilityEvent(
209      ViewHostMsg_AccessibilityNotification_Type::
210          NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED,
211      new_browser_acc);
212}
213
214void BrowserAccessibilityManager::GotFocus() {
215  // TODO(ctguil): Remove when tree update logic handles focus changes.
216  if (!focus_)
217    return;
218
219  NotifyAccessibilityEvent(
220      ViewHostMsg_AccessibilityNotification_Type::
221          NOTIFICATION_TYPE_FOCUS_CHANGED,
222      focus_);
223}
224
225gfx::NativeView BrowserAccessibilityManager::GetParentView() {
226  return parent_view_;
227}
228
229BrowserAccessibility* BrowserAccessibilityManager::GetFocus(
230    BrowserAccessibility* root) {
231  if (focus_ && (!root || focus_->IsDescendantOf(root)))
232    return focus_;
233
234  return NULL;
235}
236
237void BrowserAccessibilityManager::SetFocus(
238    BrowserAccessibility* node, bool notify) {
239  if (focus_)
240    focus_->InternalReleaseReference(false);
241  focus_ = node;
242  if (focus_)
243    focus_->InternalAddReference();
244
245  if (notify && node && delegate_)
246    delegate_->SetAccessibilityFocus(node->renderer_id());
247}
248
249void BrowserAccessibilityManager::DoDefaultAction(
250    const BrowserAccessibility& node) {
251  if (delegate_)
252    delegate_->AccessibilityDoDefaultAction(node.renderer_id());
253}
254
255gfx::Rect BrowserAccessibilityManager::GetViewBounds() {
256  if (delegate_)
257    return delegate_->GetViewBounds();
258  return gfx::Rect();
259}
260
261BrowserAccessibility* BrowserAccessibilityManager::UpdateNode(
262    const WebAccessibility& src,
263    bool include_children) {
264  base::hash_map<int32, int32>::iterator iter =
265      renderer_id_to_child_id_map_.find(src.id);
266  if (iter == renderer_id_to_child_id_map_.end())
267    return NULL;
268
269  int32 child_id = iter->second;
270  BrowserAccessibility* current = GetFromChildID(child_id);
271  if (!current)
272    return NULL;
273
274  if (!include_children) {
275    DCHECK_EQ(0U, src.children.size());
276    current->Initialize(
277        this,
278        current->parent(),
279        current->child_id(),
280        current->index_in_parent(),
281        src);
282    return current;
283  }
284
285  // Detach all of the nodes in the old tree and get a single flat vector
286  // of all node pointers.
287  std::vector<BrowserAccessibility*> old_tree_nodes;
288  current->DetachTree(&old_tree_nodes);
289
290  // Build a new tree, reusing old nodes if possible. Each node that's
291  // reused will have its reference count incremented by one.
292  current = CreateAccessibilityTree(NULL, src, -1);
293
294  // Decrement the reference count of all nodes in the old tree, which will
295  // delete any nodes no longer needed.
296  for (int i = 0; i < static_cast<int>(old_tree_nodes.size()); i++)
297    old_tree_nodes[i]->InternalReleaseReference(false);
298
299  DCHECK(focus_);
300  if (!focus_->instance_active())
301    SetFocus(root_, false);
302
303  return current;
304}
305
306BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree(
307    BrowserAccessibility* parent,
308    const WebAccessibility& src,
309    int index_in_parent) {
310  BrowserAccessibility* instance = NULL;
311  int32 child_id = 0;
312  base::hash_map<int32, int32>::iterator iter =
313      renderer_id_to_child_id_map_.find(src.id);
314
315  // If a BrowserAccessibility instance for this ID already exists, add a
316  // new reference to it and retrieve its children vector.
317  if (iter != renderer_id_to_child_id_map_.end()) {
318    child_id = iter->second;
319    instance = GetFromChildID(child_id);
320  }
321
322  // If the node has changed roles, don't reuse a BrowserAccessibility
323  // object, that could confuse a screen reader.
324  if (instance && instance->role() != src.role)
325    instance = NULL;
326
327  if (instance) {
328    // If we're reusing a node, it should already be detached from a parent
329    // and any children. If not, that means we have a serious bug somewhere,
330    // like the same child is reachable from two places in the same tree.
331    DCHECK_EQ(static_cast<BrowserAccessibility*>(NULL), instance->parent());
332    DCHECK_EQ(0U, instance->child_count());
333
334    // If we're reusing a node, update its parent and increment its
335    // reference count.
336    instance->UpdateParent(parent, index_in_parent);
337    instance->InternalAddReference();
338  } else {
339    // Otherwise, create a new instance.
340    instance = factory_->Create();
341    child_id = GetNextChildID();
342  }
343
344  instance->Initialize(this, parent, child_id, index_in_parent, src);
345  child_id_map_[child_id] = instance;
346  renderer_id_to_child_id_map_[src.id] = child_id;
347  if ((src.state >> WebAccessibility::STATE_FOCUSED) & 1)
348    SetFocus(instance, false);
349  for (int i = 0; i < static_cast<int>(src.children.size()); ++i) {
350    BrowserAccessibility* child = CreateAccessibilityTree(
351        instance, src.children[i], i);
352    instance->AddChild(child);
353  }
354
355  return instance;
356}
357