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
11 using webkit_glue::WebAccessibility;
12
Create()13 BrowserAccessibility* 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
22 int32 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
Create(gfx::NativeView parent_view,const WebAccessibility & src,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)28 BrowserAccessibilityManager* 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
BrowserAccessibilityManager(gfx::NativeView parent_view,const WebAccessibility & src,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)38 BrowserAccessibilityManager::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
GetNextChildID()53 int32 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
~BrowserAccessibilityManager()65 BrowserAccessibilityManager::~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
GetRoot()74 BrowserAccessibility* BrowserAccessibilityManager::GetRoot() {
75 return root_;
76 }
77
GetFromChildID(int32 child_id)78 BrowserAccessibility* 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
Remove(int32 child_id,int32 renderer_id)89 void 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
OnAccessibilityNotifications(const std::vector<ViewHostMsg_AccessibilityNotification_Params> & params)94 void 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
OnAccessibilityObjectStateChange(const WebAccessibility & acc_obj)131 void 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
OnAccessibilityObjectChildrenChange(const WebAccessibility & acc_obj)143 void 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
OnAccessibilityObjectFocusChange(const WebAccessibility & acc_obj)155 void 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
OnAccessibilityObjectLoadComplete(const WebAccessibility & acc_obj)173 void 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
OnAccessibilityObjectValueChange(const WebAccessibility & acc_obj)190 void 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
OnAccessibilityObjectTextChange(const WebAccessibility & acc_obj)202 void 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
GotFocus()214 void 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
GetParentView()225 gfx::NativeView BrowserAccessibilityManager::GetParentView() {
226 return parent_view_;
227 }
228
GetFocus(BrowserAccessibility * root)229 BrowserAccessibility* BrowserAccessibilityManager::GetFocus(
230 BrowserAccessibility* root) {
231 if (focus_ && (!root || focus_->IsDescendantOf(root)))
232 return focus_;
233
234 return NULL;
235 }
236
SetFocus(BrowserAccessibility * node,bool notify)237 void 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
DoDefaultAction(const BrowserAccessibility & node)249 void BrowserAccessibilityManager::DoDefaultAction(
250 const BrowserAccessibility& node) {
251 if (delegate_)
252 delegate_->AccessibilityDoDefaultAction(node.renderer_id());
253 }
254
GetViewBounds()255 gfx::Rect BrowserAccessibilityManager::GetViewBounds() {
256 if (delegate_)
257 return delegate_->GetViewBounds();
258 return gfx::Rect();
259 }
260
UpdateNode(const WebAccessibility & src,bool include_children)261 BrowserAccessibility* 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
CreateAccessibilityTree(BrowserAccessibility * parent,const WebAccessibility & src,int index_in_parent)306 BrowserAccessibility* 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