• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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