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