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/renderer/accessibility/renderer_accessibility_complete.h"
6
7 #include <queue>
8
9 #include "base/bind.h"
10 #include "base/message_loop/message_loop.h"
11 #include "content/renderer/accessibility/accessibility_node_serializer.h"
12 #include "content/renderer/render_view_impl.h"
13 #include "third_party/WebKit/public/web/WebAXObject.h"
14 #include "third_party/WebKit/public/web/WebDocument.h"
15 #include "third_party/WebKit/public/web/WebFrame.h"
16 #include "third_party/WebKit/public/web/WebInputElement.h"
17 #include "third_party/WebKit/public/web/WebNode.h"
18 #include "third_party/WebKit/public/web/WebView.h"
19
20 using blink::WebAXObject;
21 using blink::WebDocument;
22 using blink::WebFrame;
23 using blink::WebNode;
24 using blink::WebPoint;
25 using blink::WebRect;
26 using blink::WebSize;
27 using blink::WebView;
28
29 namespace content {
30
RendererAccessibilityComplete(RenderViewImpl * render_view)31 RendererAccessibilityComplete::RendererAccessibilityComplete(
32 RenderViewImpl* render_view)
33 : RendererAccessibility(render_view),
34 weak_factory_(this),
35 browser_root_(NULL),
36 last_scroll_offset_(gfx::Size()),
37 ack_pending_(false) {
38 WebAXObject::enableAccessibility();
39
40 #if !defined(OS_ANDROID)
41 // Skip inline text boxes on Android - since there are no native Android
42 // APIs that compute the bounds of a range of text, it's a waste to
43 // include these in the AX tree.
44 WebAXObject::enableInlineTextBoxAccessibility();
45 #endif
46
47 const WebDocument& document = GetMainDocument();
48 if (!document.isNull()) {
49 // It's possible that the webview has already loaded a webpage without
50 // accessibility being enabled. Initialize the browser's cached
51 // accessibility tree by sending it a notification.
52 HandleWebAccessibilityEvent(document.accessibilityObject(),
53 blink::WebAXEventLayoutComplete);
54 }
55 }
56
~RendererAccessibilityComplete()57 RendererAccessibilityComplete::~RendererAccessibilityComplete() {
58 if (browser_root_) {
59 ClearBrowserTreeNode(browser_root_);
60 browser_id_map_.erase(browser_root_->id);
61 delete browser_root_;
62 }
63 DCHECK(browser_id_map_.empty());
64 }
65
OnMessageReceived(const IPC::Message & message)66 bool RendererAccessibilityComplete::OnMessageReceived(
67 const IPC::Message& message) {
68 bool handled = true;
69 IPC_BEGIN_MESSAGE_MAP(RendererAccessibilityComplete, message)
70 IPC_MESSAGE_HANDLER(AccessibilityMsg_SetFocus, OnSetFocus)
71 IPC_MESSAGE_HANDLER(AccessibilityMsg_DoDefaultAction,
72 OnDoDefaultAction)
73 IPC_MESSAGE_HANDLER(AccessibilityMsg_Events_ACK,
74 OnEventsAck)
75 IPC_MESSAGE_HANDLER(AccessibilityMsg_ScrollToMakeVisible,
76 OnScrollToMakeVisible)
77 IPC_MESSAGE_HANDLER(AccessibilityMsg_ScrollToPoint,
78 OnScrollToPoint)
79 IPC_MESSAGE_HANDLER(AccessibilityMsg_SetTextSelection,
80 OnSetTextSelection)
81 IPC_MESSAGE_HANDLER(AccessibilityMsg_FatalError, OnFatalError)
82 IPC_MESSAGE_UNHANDLED(handled = false)
83 IPC_END_MESSAGE_MAP()
84 return handled;
85 }
86
FocusedNodeChanged(const WebNode & node)87 void RendererAccessibilityComplete::FocusedNodeChanged(const WebNode& node) {
88 const WebDocument& document = GetMainDocument();
89 if (document.isNull())
90 return;
91
92 if (node.isNull()) {
93 // When focus is cleared, implicitly focus the document.
94 // TODO(dmazzoni): Make WebKit send this notification instead.
95 HandleWebAccessibilityEvent(document.accessibilityObject(),
96 blink::WebAXEventBlur);
97 }
98 }
99
DidFinishLoad(blink::WebFrame * frame)100 void RendererAccessibilityComplete::DidFinishLoad(blink::WebFrame* frame) {
101 const WebDocument& document = GetMainDocument();
102 if (document.isNull())
103 return;
104
105 // Check to see if the root accessibility object has changed, to work
106 // around WebKit bugs that cause AXObjectCache to be cleared
107 // unnecessarily.
108 // TODO(dmazzoni): remove this once rdar://5794454 is fixed.
109 WebAXObject new_root = document.accessibilityObject();
110 if (!browser_root_ || new_root.axID() != browser_root_->id)
111 HandleWebAccessibilityEvent(new_root, blink::WebAXEventLayoutComplete);
112 }
113
HandleWebAccessibilityEvent(const blink::WebAXObject & obj,blink::WebAXEvent event)114 void RendererAccessibilityComplete::HandleWebAccessibilityEvent(
115 const blink::WebAXObject& obj,
116 blink::WebAXEvent event) {
117 const WebDocument& document = GetMainDocument();
118 if (document.isNull())
119 return;
120
121 gfx::Size scroll_offset = document.frame()->scrollOffset();
122 if (scroll_offset != last_scroll_offset_) {
123 // Make sure the browser is always aware of the scroll position of
124 // the root document element by posting a generic notification that
125 // will update it.
126 // TODO(dmazzoni): remove this as soon as
127 // https://bugs.webkit.org/show_bug.cgi?id=73460 is fixed.
128 last_scroll_offset_ = scroll_offset;
129 if (!obj.equals(document.accessibilityObject())) {
130 HandleWebAccessibilityEvent(
131 document.accessibilityObject(),
132 blink::WebAXEventLayoutComplete);
133 }
134 }
135
136 // Add the accessibility object to our cache and ensure it's valid.
137 AccessibilityHostMsg_EventParams acc_event;
138 acc_event.id = obj.axID();
139 acc_event.event_type = event;
140
141 // Discard duplicate accessibility events.
142 for (uint32 i = 0; i < pending_events_.size(); ++i) {
143 if (pending_events_[i].id == acc_event.id &&
144 pending_events_[i].event_type ==
145 acc_event.event_type) {
146 return;
147 }
148 }
149 pending_events_.push_back(acc_event);
150
151 if (!ack_pending_ && !weak_factory_.HasWeakPtrs()) {
152 // When no accessibility events are in-flight post a task to send
153 // the events to the browser. We use PostTask so that we can queue
154 // up additional events.
155 base::MessageLoop::current()->PostTask(
156 FROM_HERE,
157 base::Bind(&RendererAccessibilityComplete::
158 SendPendingAccessibilityEvents,
159 weak_factory_.GetWeakPtr()));
160 }
161 }
162
BrowserTreeNode()163 RendererAccessibilityComplete::BrowserTreeNode::BrowserTreeNode() : id(0) {}
164
~BrowserTreeNode()165 RendererAccessibilityComplete::BrowserTreeNode::~BrowserTreeNode() {}
166
SendPendingAccessibilityEvents()167 void RendererAccessibilityComplete::SendPendingAccessibilityEvents() {
168 const WebDocument& document = GetMainDocument();
169 if (document.isNull())
170 return;
171
172 if (pending_events_.empty())
173 return;
174
175 if (render_view_->is_swapped_out())
176 return;
177
178 ack_pending_ = true;
179
180 // Make a copy of the events, because it's possible that
181 // actions inside this loop will cause more events to be
182 // queued up.
183 std::vector<AccessibilityHostMsg_EventParams> src_events =
184 pending_events_;
185 pending_events_.clear();
186
187 // Generate an event message from each WebKit event.
188 std::vector<AccessibilityHostMsg_EventParams> event_msgs;
189
190 // Loop over each event and generate an updated event message.
191 for (size_t i = 0; i < src_events.size(); ++i) {
192 AccessibilityHostMsg_EventParams& event =
193 src_events[i];
194
195 WebAXObject obj = document.accessibilityObjectFromID(
196 event.id);
197 if (!obj.updateBackingStoreAndCheckValidity())
198 continue;
199
200 // When we get a "selected children changed" event, WebKit
201 // doesn't also send us events for each child that changed
202 // selection state, so make sure we re-send that whole subtree.
203 if (event.event_type ==
204 blink::WebAXEventSelectedChildrenChanged) {
205 base::hash_map<int32, BrowserTreeNode*>::iterator iter =
206 browser_id_map_.find(obj.axID());
207 if (iter != browser_id_map_.end())
208 ClearBrowserTreeNode(iter->second);
209 }
210
211 // The browser may not have this object yet, for example if we get a
212 // event on an object that was recently added, or if we get a
213 // event on a node before the page has loaded. Work our way
214 // up the parent chain until we find a node the browser has, or until
215 // we reach the root.
216 WebAXObject root_object = document.accessibilityObject();
217 int root_id = root_object.axID();
218 while (browser_id_map_.find(obj.axID()) == browser_id_map_.end() &&
219 !obj.isDetached() &&
220 obj.axID() != root_id) {
221 obj = obj.parentObject();
222 if (event.event_type ==
223 blink::WebAXEventChildrenChanged) {
224 event.id = obj.axID();
225 }
226 }
227
228 if (obj.isDetached()) {
229 #ifndef NDEBUG
230 if (logging_)
231 LOG(WARNING) << "Got event on object that is invalid or has"
232 << " invalid ancestor. Id: " << obj.axID();
233 #endif
234 continue;
235 }
236
237 // Another potential problem is that this event may be on an
238 // object that is detached from the tree. Determine if this node is not a
239 // child of its parent, and if so move the event to the parent.
240 // TODO(dmazzoni): see if this can be removed after
241 // https://bugs.webkit.org/show_bug.cgi?id=68466 is fixed.
242 if (obj.axID() != root_id) {
243 WebAXObject parent = obj.parentObject();
244 while (!parent.isDetached() &&
245 parent.accessibilityIsIgnored()) {
246 parent = parent.parentObject();
247 }
248
249 if (parent.isDetached()) {
250 NOTREACHED();
251 continue;
252 }
253 bool is_child_of_parent = false;
254 for (unsigned int i = 0; i < parent.childCount(); ++i) {
255 if (parent.childAt(i).equals(obj)) {
256 is_child_of_parent = true;
257 break;
258 }
259 }
260
261 if (!is_child_of_parent) {
262 obj = parent;
263 event.id = obj.axID();
264 }
265 }
266
267 // Allow WebKit to cache intermediate results since we're doing a bunch
268 // of read-only queries at once.
269 root_object.startCachingComputedObjectAttributesUntilTreeMutates();
270
271 AccessibilityHostMsg_EventParams event_msg;
272 event_msg.event_type = event.event_type;
273 event_msg.id = event.id;
274 std::set<int> ids_serialized;
275 SerializeChangedNodes(obj, &event_msg.nodes, &ids_serialized);
276 event_msgs.push_back(event_msg);
277
278 #ifndef NDEBUG
279 if (logging_) {
280 AccessibilityNodeDataTreeNode tree;
281 MakeAccessibilityNodeDataTree(event_msg.nodes, &tree);
282 VLOG(0) << "Accessibility update: \n"
283 << "routing id=" << routing_id()
284 << " event="
285 << AccessibilityEventToString(event.event_type)
286 << "\n" << tree.DebugString(true);
287 }
288 #endif
289 }
290
291 AppendLocationChangeEvents(&event_msgs);
292
293 Send(new AccessibilityHostMsg_Events(routing_id(), event_msgs));
294 }
295
AppendLocationChangeEvents(std::vector<AccessibilityHostMsg_EventParams> * event_msgs)296 void RendererAccessibilityComplete::AppendLocationChangeEvents(
297 std::vector<AccessibilityHostMsg_EventParams>* event_msgs) {
298 std::queue<WebAXObject> objs_to_explore;
299 std::vector<BrowserTreeNode*> location_changes;
300 WebAXObject root_object = GetMainDocument().accessibilityObject();
301 objs_to_explore.push(root_object);
302
303 while (objs_to_explore.size()) {
304 WebAXObject obj = objs_to_explore.front();
305 objs_to_explore.pop();
306 int id = obj.axID();
307 if (browser_id_map_.find(id) != browser_id_map_.end()) {
308 BrowserTreeNode* browser_node = browser_id_map_[id];
309 gfx::Rect new_location = obj.boundingBoxRect();
310 if (browser_node->location != new_location) {
311 browser_node->location = new_location;
312 location_changes.push_back(browser_node);
313 }
314 }
315
316 for (unsigned i = 0; i < obj.childCount(); ++i)
317 objs_to_explore.push(obj.childAt(i));
318 }
319
320 if (location_changes.size() == 0)
321 return;
322
323 AccessibilityHostMsg_EventParams event_msg;
324 event_msg.event_type = static_cast<blink::WebAXEvent>(-1);
325 event_msg.id = root_object.axID();
326 event_msg.nodes.resize(location_changes.size());
327 for (size_t i = 0; i < location_changes.size(); i++) {
328 AccessibilityNodeData& serialized_node = event_msg.nodes[i];
329 serialized_node.id = location_changes[i]->id;
330 serialized_node.location = location_changes[i]->location;
331 serialized_node.AddBoolAttribute(
332 AccessibilityNodeData::ATTR_UPDATE_LOCATION_ONLY, true);
333 }
334
335 event_msgs->push_back(event_msg);
336 }
337
338 RendererAccessibilityComplete::BrowserTreeNode*
CreateBrowserTreeNode()339 RendererAccessibilityComplete::CreateBrowserTreeNode() {
340 return new RendererAccessibilityComplete::BrowserTreeNode();
341 }
342
SerializeChangedNodes(const blink::WebAXObject & obj,std::vector<AccessibilityNodeData> * dst,std::set<int> * ids_serialized)343 void RendererAccessibilityComplete::SerializeChangedNodes(
344 const blink::WebAXObject& obj,
345 std::vector<AccessibilityNodeData>* dst,
346 std::set<int>* ids_serialized) {
347 if (ids_serialized->find(obj.axID()) != ids_serialized->end())
348 return;
349 ids_serialized->insert(obj.axID());
350
351 // This method has three responsibilities:
352 // 1. Serialize |obj| into an AccessibilityNodeData, and append it to
353 // the end of the |dst| vector to be send to the browser process.
354 // 2. Determine if |obj| has any new children that the browser doesn't
355 // know about yet, and call SerializeChangedNodes recursively on those.
356 // 3. Update our internal data structure that keeps track of what nodes
357 // the browser knows about.
358
359 // First, find the BrowserTreeNode for this id in our data structure where
360 // we keep track of what accessibility objects the browser already knows
361 // about. If we don't find it, then this must be the new root of the
362 // accessibility tree.
363 BrowserTreeNode* browser_node = NULL;
364 base::hash_map<int32, BrowserTreeNode*>::iterator iter =
365 browser_id_map_.find(obj.axID());
366 if (iter != browser_id_map_.end()) {
367 browser_node = iter->second;
368 } else {
369 if (browser_root_) {
370 ClearBrowserTreeNode(browser_root_);
371 browser_id_map_.erase(browser_root_->id);
372 delete browser_root_;
373 }
374 browser_root_ = CreateBrowserTreeNode();
375 browser_node = browser_root_;
376 browser_node->id = obj.axID();
377 browser_node->location = obj.boundingBoxRect();
378 browser_node->parent = NULL;
379 browser_id_map_[browser_node->id] = browser_node;
380 }
381
382 // Iterate over the ids of the children of |obj|.
383 // Create a set of the child ids so we can quickly look
384 // up which children are new and which ones were there before.
385 // Also catch the case where a child is already in the browser tree
386 // data structure with a different parent, and make sure the old parent
387 // clears this node first.
388 base::hash_set<int32> new_child_ids;
389 const WebDocument& document = GetMainDocument();
390 for (unsigned i = 0; i < obj.childCount(); i++) {
391 WebAXObject child = obj.childAt(i);
392 if (ShouldIncludeChildNode(obj, child)) {
393 int new_child_id = child.axID();
394 new_child_ids.insert(new_child_id);
395
396 BrowserTreeNode* child = browser_id_map_[new_child_id];
397 if (child && child->parent != browser_node) {
398 // The child is being reparented. Find the WebKit accessibility
399 // object corresponding to the old parent, or the closest ancestor
400 // still in the tree.
401 BrowserTreeNode* parent = child->parent;
402 WebAXObject parent_obj;
403 while (parent) {
404 parent_obj = document.accessibilityObjectFromID(parent->id);
405
406 if (!parent_obj.isDetached())
407 break;
408 parent = parent->parent;
409 }
410 CHECK(parent);
411 // Call SerializeChangedNodes recursively on the old parent,
412 // so that the update that clears |child| from its old parent
413 // occurs stricly before the update that adds |child| to its
414 // new parent.
415 SerializeChangedNodes(parent_obj, dst, ids_serialized);
416 }
417 }
418 }
419
420 // Go through the old children and delete subtrees for child
421 // ids that are no longer present, and create a map from
422 // id to BrowserTreeNode for the rest. It's important to delete
423 // first in a separate pass so that nodes that are reparented
424 // don't end up children of two different parents in the middle
425 // of an update, which can lead to a double-free.
426 base::hash_map<int32, BrowserTreeNode*> browser_child_id_map;
427 std::vector<BrowserTreeNode*> old_children;
428 old_children.swap(browser_node->children);
429 for (size_t i = 0; i < old_children.size(); i++) {
430 BrowserTreeNode* old_child = old_children[i];
431 int old_child_id = old_child->id;
432 if (new_child_ids.find(old_child_id) == new_child_ids.end()) {
433 browser_id_map_.erase(old_child_id);
434 ClearBrowserTreeNode(old_child);
435 delete old_child;
436 } else {
437 browser_child_id_map[old_child_id] = old_child;
438 }
439 }
440
441 // Serialize this node. This fills in all of the fields in
442 // AccessibilityNodeData except child_ids, which we handle below.
443 dst->push_back(AccessibilityNodeData());
444 AccessibilityNodeData* serialized_node = &dst->back();
445 SerializeAccessibilityNode(obj, serialized_node);
446 if (serialized_node->id == browser_root_->id)
447 serialized_node->role = blink::WebAXRoleRootWebArea;
448
449 // Iterate over the children, make note of the ones that are new
450 // and need to be serialized, and update the BrowserTreeNode
451 // data structure to reflect the new tree.
452 std::vector<WebAXObject> children_to_serialize;
453 int child_count = obj.childCount();
454 browser_node->children.reserve(child_count);
455 for (int i = 0; i < child_count; i++) {
456 WebAXObject child = obj.childAt(i);
457 int child_id = child.axID();
458
459 // Checks to make sure the child is valid, attached to this node,
460 // and one we want to include in the tree.
461 if (!ShouldIncludeChildNode(obj, child))
462 continue;
463
464 // No need to do anything more with children that aren't new;
465 // the browser will reuse its existing object.
466 if (new_child_ids.find(child_id) == new_child_ids.end())
467 continue;
468
469 new_child_ids.erase(child_id);
470 serialized_node->child_ids.push_back(child_id);
471 if (browser_child_id_map.find(child_id) != browser_child_id_map.end()) {
472 BrowserTreeNode* reused_child = browser_child_id_map[child_id];
473 browser_node->children.push_back(reused_child);
474 } else {
475 BrowserTreeNode* new_child = CreateBrowserTreeNode();
476 new_child->id = child_id;
477 new_child->location = obj.boundingBoxRect();
478 new_child->parent = browser_node;
479 browser_node->children.push_back(new_child);
480 browser_id_map_[child_id] = new_child;
481 children_to_serialize.push_back(child);
482 }
483 }
484
485 // Serialize all of the new children, recursively.
486 for (size_t i = 0; i < children_to_serialize.size(); ++i)
487 SerializeChangedNodes(children_to_serialize[i], dst, ids_serialized);
488 }
489
ClearBrowserTreeNode(BrowserTreeNode * browser_node)490 void RendererAccessibilityComplete::ClearBrowserTreeNode(
491 BrowserTreeNode* browser_node) {
492 for (size_t i = 0; i < browser_node->children.size(); ++i) {
493 browser_id_map_.erase(browser_node->children[i]->id);
494 ClearBrowserTreeNode(browser_node->children[i]);
495 delete browser_node->children[i];
496 }
497 browser_node->children.clear();
498 }
499
OnDoDefaultAction(int acc_obj_id)500 void RendererAccessibilityComplete::OnDoDefaultAction(int acc_obj_id) {
501 const WebDocument& document = GetMainDocument();
502 if (document.isNull())
503 return;
504
505 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
506 if (obj.isDetached()) {
507 #ifndef NDEBUG
508 if (logging_)
509 LOG(WARNING) << "DoDefaultAction on invalid object id " << acc_obj_id;
510 #endif
511 return;
512 }
513
514 obj.performDefaultAction();
515 }
516
OnScrollToMakeVisible(int acc_obj_id,gfx::Rect subfocus)517 void RendererAccessibilityComplete::OnScrollToMakeVisible(
518 int acc_obj_id, gfx::Rect subfocus) {
519 const WebDocument& document = GetMainDocument();
520 if (document.isNull())
521 return;
522
523 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
524 if (obj.isDetached()) {
525 #ifndef NDEBUG
526 if (logging_)
527 LOG(WARNING) << "ScrollToMakeVisible on invalid object id " << acc_obj_id;
528 #endif
529 return;
530 }
531
532 obj.scrollToMakeVisibleWithSubFocus(
533 WebRect(subfocus.x(), subfocus.y(),
534 subfocus.width(), subfocus.height()));
535
536 // Make sure the browser gets an event when the scroll
537 // position actually changes.
538 // TODO(dmazzoni): remove this once this bug is fixed:
539 // https://bugs.webkit.org/show_bug.cgi?id=73460
540 HandleWebAccessibilityEvent(
541 document.accessibilityObject(),
542 blink::WebAXEventLayoutComplete);
543 }
544
OnScrollToPoint(int acc_obj_id,gfx::Point point)545 void RendererAccessibilityComplete::OnScrollToPoint(
546 int acc_obj_id, gfx::Point point) {
547 const WebDocument& document = GetMainDocument();
548 if (document.isNull())
549 return;
550
551 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
552 if (obj.isDetached()) {
553 #ifndef NDEBUG
554 if (logging_)
555 LOG(WARNING) << "ScrollToPoint on invalid object id " << acc_obj_id;
556 #endif
557 return;
558 }
559
560 obj.scrollToGlobalPoint(WebPoint(point.x(), point.y()));
561
562 // Make sure the browser gets an event when the scroll
563 // position actually changes.
564 // TODO(dmazzoni): remove this once this bug is fixed:
565 // https://bugs.webkit.org/show_bug.cgi?id=73460
566 HandleWebAccessibilityEvent(
567 document.accessibilityObject(),
568 blink::WebAXEventLayoutComplete);
569 }
570
OnSetTextSelection(int acc_obj_id,int start_offset,int end_offset)571 void RendererAccessibilityComplete::OnSetTextSelection(
572 int acc_obj_id, int start_offset, int end_offset) {
573 const WebDocument& document = GetMainDocument();
574 if (document.isNull())
575 return;
576
577 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
578 if (obj.isDetached()) {
579 #ifndef NDEBUG
580 if (logging_)
581 LOG(WARNING) << "SetTextSelection on invalid object id " << acc_obj_id;
582 #endif
583 return;
584 }
585
586 // TODO(dmazzoni): support elements other than <input>.
587 blink::WebNode node = obj.node();
588 if (!node.isNull() && node.isElementNode()) {
589 blink::WebElement element = node.to<blink::WebElement>();
590 blink::WebInputElement* input_element =
591 blink::toWebInputElement(&element);
592 if (input_element && input_element->isTextField())
593 input_element->setSelectionRange(start_offset, end_offset);
594 }
595 }
596
OnEventsAck()597 void RendererAccessibilityComplete::OnEventsAck() {
598 DCHECK(ack_pending_);
599 ack_pending_ = false;
600 SendPendingAccessibilityEvents();
601 }
602
OnSetFocus(int acc_obj_id)603 void RendererAccessibilityComplete::OnSetFocus(int acc_obj_id) {
604 const WebDocument& document = GetMainDocument();
605 if (document.isNull())
606 return;
607
608 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
609 if (obj.isDetached()) {
610 #ifndef NDEBUG
611 if (logging_) {
612 LOG(WARNING) << "OnSetAccessibilityFocus on invalid object id "
613 << acc_obj_id;
614 }
615 #endif
616 return;
617 }
618
619 WebAXObject root = document.accessibilityObject();
620 if (root.isDetached()) {
621 #ifndef NDEBUG
622 if (logging_) {
623 LOG(WARNING) << "OnSetAccessibilityFocus but root is invalid";
624 }
625 #endif
626 return;
627 }
628
629 // By convention, calling SetFocus on the root of the tree should clear the
630 // current focus. Otherwise set the focus to the new node.
631 if (acc_obj_id == root.axID())
632 render_view()->GetWebView()->clearFocusedNode();
633 else
634 obj.setFocused(true);
635 }
636
OnFatalError()637 void RendererAccessibilityComplete::OnFatalError() {
638 CHECK(false) << "Invalid accessibility tree.";
639 }
640
641 } // namespace content
642