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, ¬ification_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(¬ification_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