Home | History | Annotate | Download | only in accessibility
      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 
     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 
     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 
     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 
     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 
    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 
    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 
    163 RendererAccessibilityComplete::BrowserTreeNode::BrowserTreeNode() : id(0) {}
    164 
    165 RendererAccessibilityComplete::BrowserTreeNode::~BrowserTreeNode() {}
    166 
    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 
    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*
    339 RendererAccessibilityComplete::CreateBrowserTreeNode() {
    340   return new RendererAccessibilityComplete::BrowserTreeNode();
    341 }
    342 
    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 
    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 
    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 
    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 
    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 
    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 
    597 void RendererAccessibilityComplete::OnEventsAck() {
    598   DCHECK(ack_pending_);
    599   ack_pending_ = false;
    600   SendPendingAccessibilityEvents();
    601 }
    602 
    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 
    637 void RendererAccessibilityComplete::OnFatalError() {
    638   CHECK(false) << "Invalid accessibility tree.";
    639 }
    640 
    641 }  // namespace content
    642