browser_accessibility_manager.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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 "chrome/browser/accessibility/browser_accessibility.h"
8
9using webkit_glue::WebAccessibility;
10
11// Start child IDs at -1 and decrement each time, because clients use
12// child IDs of 1, 2, 3, ... to access the children of an object by
13// index, so we use negative IDs to clearly distinguish between indices
14// and unique IDs.
15// static
16int32 BrowserAccessibilityManager::next_child_id_ = -1;
17
18BrowserAccessibilityManager::BrowserAccessibilityManager(
19    gfx::NativeView parent_view,
20      const WebAccessibility& src,
21      BrowserAccessibilityDelegate* delegate,
22      BrowserAccessibilityFactory* factory)
23    : parent_view_(parent_view),
24      delegate_(delegate),
25      factory_(factory),
26      focus_(NULL) {
27  root_ = CreateAccessibilityTree(NULL, GetNextChildID(), src, 0);
28  if (!focus_)
29    focus_ = root_;
30}
31
32// static
33int32 BrowserAccessibilityManager::GetNextChildID() {
34  // Get the next child ID, and wrap around when we get near the end
35  // of a 32-bit integer range. It's okay to wrap around; we just want
36  // to avoid it as long as possible because clients may cache the ID of
37  // an object for a while to determine if they've seen it before.
38  next_child_id_--;
39  if (next_child_id_ == -2000000000)
40    next_child_id_ = -1;
41
42  return next_child_id_;
43}
44
45BrowserAccessibilityManager::~BrowserAccessibilityManager() {
46  // Clients could still hold references to some nodes of the tree, so
47  // calling Inactivate will make sure that as many nodes as possible are
48  // released now, and remaining nodes are marked as inactive so that
49  // calls to any methods on them will return E_FAIL;
50  root_->ReleaseTree();
51  root_->ReleaseReference();
52}
53
54BrowserAccessibility* BrowserAccessibilityManager::GetRoot() {
55  return root_;
56}
57
58BrowserAccessibility* BrowserAccessibilityManager::GetFromChildID(
59    int32 child_id) {
60  base::hash_map<int32, BrowserAccessibility*>::iterator iter =
61      child_id_map_.find(child_id);
62  if (iter != child_id_map_.end()) {
63    return iter->second;
64  } else {
65    return NULL;
66  }
67}
68
69void BrowserAccessibilityManager::Remove(int32 child_id) {
70  child_id_map_.erase(child_id);
71}
72
73void BrowserAccessibilityManager::OnAccessibilityNotifications(
74    const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) {
75  for (uint32 index = 0; index < params.size(); index++) {
76    const ViewHostMsg_AccessibilityNotification_Params& param = params[index];
77
78    switch (param.notification_type) {
79      case ViewHostMsg_AccessibilityNotification_Params::
80          NOTIFICATION_TYPE_CHECK_STATE_CHANGED:
81        OnAccessibilityObjectStateChange(param.acc_obj);
82        break;
83      case ViewHostMsg_AccessibilityNotification_Params::
84          NOTIFICATION_TYPE_CHILDREN_CHANGED:
85        OnAccessibilityObjectChildrenChange(param.acc_obj);
86        break;
87      case ViewHostMsg_AccessibilityNotification_Params::
88          NOTIFICATION_TYPE_FOCUS_CHANGED:
89        OnAccessibilityObjectFocusChange(param.acc_obj);
90        break;
91      case ViewHostMsg_AccessibilityNotification_Params::
92          NOTIFICATION_TYPE_LOAD_COMPLETE:
93        OnAccessibilityObjectLoadComplete(param.acc_obj);
94        break;
95      case ViewHostMsg_AccessibilityNotification_Params::
96          NOTIFICATION_TYPE_VALUE_CHANGED:
97        OnAccessibilityObjectValueChange(param.acc_obj);
98        break;
99      case ViewHostMsg_AccessibilityNotification_Params::
100          NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED:
101        OnAccessibilityObjectTextChange(param.acc_obj);
102        break;
103      default:
104        DCHECK(0);
105        break;
106    }
107  }
108}
109
110void BrowserAccessibilityManager::OnAccessibilityObjectStateChange(
111    const WebAccessibility& acc_obj) {
112  BrowserAccessibility* new_browser_acc = UpdateTree(acc_obj);
113  if (!new_browser_acc)
114    return;
115
116  NotifyAccessibilityEvent(
117      ViewHostMsg_AccessibilityNotification_Params::
118          NOTIFICATION_TYPE_CHECK_STATE_CHANGED,
119      new_browser_acc);
120}
121
122void BrowserAccessibilityManager::OnAccessibilityObjectChildrenChange(
123    const WebAccessibility& acc_obj) {
124  BrowserAccessibility* new_browser_acc = UpdateTree(acc_obj);
125  if (!new_browser_acc)
126    return;
127
128  NotifyAccessibilityEvent(
129      ViewHostMsg_AccessibilityNotification_Params::
130          NOTIFICATION_TYPE_CHILDREN_CHANGED,
131      new_browser_acc);
132}
133
134void BrowserAccessibilityManager::OnAccessibilityObjectFocusChange(
135  const WebAccessibility& acc_obj) {
136  BrowserAccessibility* new_browser_acc = UpdateTree(acc_obj);
137  if (!new_browser_acc)
138    return;
139
140  focus_ = new_browser_acc;
141  if (delegate_ && delegate_->HasFocus())
142    GotFocus();
143  // Mac currently does not have a BrowserAccessibilityDelegate.
144  else if (!delegate_) {
145    NotifyAccessibilityEvent(
146        ViewHostMsg_AccessibilityNotification_Params::
147            NOTIFICATION_TYPE_FOCUS_CHANGED,
148        focus_);
149  }
150}
151
152void BrowserAccessibilityManager::OnAccessibilityObjectLoadComplete(
153  const WebAccessibility& acc_obj) {
154  root_->ReleaseTree();
155  root_->ReleaseReference();
156  focus_ = NULL;
157
158  root_ = CreateAccessibilityTree(NULL, GetNextChildID(), acc_obj, 0);
159  if (!focus_)
160    focus_ = root_;
161
162  NotifyAccessibilityEvent(
163      ViewHostMsg_AccessibilityNotification_Params::
164          NOTIFICATION_TYPE_LOAD_COMPLETE,
165      root_);
166  if (delegate_ && delegate_->HasFocus())
167    GotFocus();
168}
169
170void BrowserAccessibilityManager::OnAccessibilityObjectValueChange(
171    const WebAccessibility& acc_obj) {
172  BrowserAccessibility* new_browser_acc = UpdateTree(acc_obj);
173  if (!new_browser_acc)
174    return;
175
176  NotifyAccessibilityEvent(
177      ViewHostMsg_AccessibilityNotification_Params::
178          NOTIFICATION_TYPE_VALUE_CHANGED,
179      root_);
180}
181
182void BrowserAccessibilityManager::OnAccessibilityObjectTextChange(
183    const WebAccessibility& acc_obj) {
184  BrowserAccessibility* new_browser_acc = UpdateTree(acc_obj);
185  if (!new_browser_acc)
186    return;
187
188  NotifyAccessibilityEvent(
189      ViewHostMsg_AccessibilityNotification_Params::
190          NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED,
191      root_);
192}
193
194void BrowserAccessibilityManager::GotFocus() {
195  // TODO(ctguil): Remove when tree update logic handles focus changes.
196  if (!focus_)
197    return;
198
199  NotifyAccessibilityEvent(
200      ViewHostMsg_AccessibilityNotification_Params::
201          NOTIFICATION_TYPE_FOCUS_CHANGED,
202      focus_);
203}
204
205gfx::NativeView BrowserAccessibilityManager::GetParentView() {
206  return parent_view_;
207}
208
209BrowserAccessibility* BrowserAccessibilityManager::GetFocus(
210    BrowserAccessibility* root) {
211  if (focus_ && (!root || focus_->IsDescendantOf(root)))
212    return focus_;
213
214  return NULL;
215}
216
217void BrowserAccessibilityManager::SetFocus(
218    const BrowserAccessibility& node) {
219  if (delegate_)
220    delegate_->SetAccessibilityFocus(node.renderer_id());
221}
222
223void BrowserAccessibilityManager::DoDefaultAction(
224    const BrowserAccessibility& node) {
225  if (delegate_)
226    delegate_->AccessibilityDoDefaultAction(node.renderer_id());
227}
228
229bool BrowserAccessibilityManager::CanModifyTreeInPlace(
230    BrowserAccessibility* current_root,
231    const WebAccessibility& new_root) {
232  if (current_root->renderer_id() != new_root.id)
233    return false;
234  if (current_root->GetChildCount() != new_root.children.size())
235    return false;
236  for (unsigned int i = 0; i < current_root->GetChildCount(); i++) {
237    if (!CanModifyTreeInPlace(current_root->GetChild(i),
238                              new_root.children[i])) {
239      return false;
240    }
241  }
242  return true;
243}
244
245void BrowserAccessibilityManager::ModifyTreeInPlace(
246      BrowserAccessibility* current_root,
247      const WebAccessibility& new_root) {
248  DCHECK_EQ(current_root->renderer_id(), new_root.id);
249  DCHECK_EQ(current_root->GetChildCount(), new_root.children.size());
250  for (unsigned int i = 0; i < current_root->GetChildCount(); i++)
251    ModifyTreeInPlace(current_root->GetChild(i), new_root.children[i]);
252  current_root->Initialize(
253      this,
254      current_root->GetParent(),
255      current_root->child_id(),
256      current_root->index_in_parent(),
257      new_root);
258}
259
260
261BrowserAccessibility* BrowserAccessibilityManager::UpdateTree(
262    const WebAccessibility& acc_obj) {
263  base::hash_map<int, int32>::iterator iter =
264      renderer_id_to_child_id_map_.find(acc_obj.id);
265  if (iter == renderer_id_to_child_id_map_.end())
266    return NULL;
267
268  int32 child_id = iter->second;
269  BrowserAccessibility* old_browser_acc = GetFromChildID(child_id);
270  if (!old_browser_acc)
271    return NULL;
272
273  if (CanModifyTreeInPlace(old_browser_acc, acc_obj)) {
274    ModifyTreeInPlace(old_browser_acc, acc_obj);
275    return old_browser_acc;
276  }
277
278  BrowserAccessibility* new_browser_acc = CreateAccessibilityTree(
279        old_browser_acc->GetParent(),
280        child_id,
281        acc_obj,
282        old_browser_acc->index_in_parent());
283
284    if (old_browser_acc->GetParent()) {
285      old_browser_acc->GetParent()->ReplaceChild(
286          old_browser_acc,
287          new_browser_acc);
288    } else {
289      DCHECK_EQ(old_browser_acc, root_);
290      root_ = new_browser_acc;
291    }
292    old_browser_acc->ReleaseTree();
293    old_browser_acc->ReleaseReference();
294    child_id_map_[child_id] = new_browser_acc;
295
296    return new_browser_acc;
297}
298
299BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree(
300    BrowserAccessibility* parent,
301    int child_id,
302    const WebAccessibility& src,
303    int index_in_parent) {
304  BrowserAccessibility* instance = factory_->Create();
305
306  instance->Initialize(this, parent, child_id, index_in_parent, src);
307  child_id_map_[child_id] = instance;
308  renderer_id_to_child_id_map_[src.id] = child_id;
309  if ((src.state >> WebAccessibility::STATE_FOCUSED) & 1)
310    focus_ = instance;
311  for (int i = 0; i < static_cast<int>(src.children.size()); ++i) {
312    BrowserAccessibility* child = CreateAccessibilityTree(
313        instance, GetNextChildID(), src.children[i], i);
314    instance->AddChild(child);
315  }
316
317  return instance;
318}
319