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.
5 #include "content/renderer/accessibility/renderer_accessibility_complete.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"
20 using WebKit::WebAXObject;
21 using WebKit::WebDocument;
22 using WebKit::WebFrame;
23 using WebKit::WebNode;
24 using WebKit::WebPoint;
25 using WebKit::WebRect;
26 using WebKit::WebSize;
27 using WebKit::WebView;
31 RendererAccessibilityComplete::RendererAccessibilityComplete(
32 RenderViewImpl* render_view)
33 : RendererAccessibility(render_view),
36 last_scroll_offset_(gfx::Size()),
38 WebAXObject::enableAccessibility();
39 WebAXObject::enableInlineTextBoxAccessibility();
41 const WebDocument& document = GetMainDocument();
42 if (!document.isNull()) {
43 // It's possible that the webview has already loaded a webpage without
44 // accessibility being enabled. Initialize the browser's cached
45 // accessibility tree by sending it a notification.
46 HandleWebAccessibilityEvent(document.accessibilityObject(),
47 WebKit::WebAXEventLayoutComplete);
51 RendererAccessibilityComplete::~RendererAccessibilityComplete() {
54 bool RendererAccessibilityComplete::OnMessageReceived(
55 const IPC::Message& message) {
57 IPC_BEGIN_MESSAGE_MAP(RendererAccessibilityComplete, message)
58 IPC_MESSAGE_HANDLER(AccessibilityMsg_SetFocus, OnSetFocus)
59 IPC_MESSAGE_HANDLER(AccessibilityMsg_DoDefaultAction,
61 IPC_MESSAGE_HANDLER(AccessibilityMsg_Events_ACK,
63 IPC_MESSAGE_HANDLER(AccessibilityMsg_ScrollToMakeVisible,
64 OnScrollToMakeVisible)
65 IPC_MESSAGE_HANDLER(AccessibilityMsg_ScrollToPoint,
67 IPC_MESSAGE_HANDLER(AccessibilityMsg_SetTextSelection,
69 IPC_MESSAGE_HANDLER(AccessibilityMsg_FatalError, OnFatalError)
70 IPC_MESSAGE_UNHANDLED(handled = false)
75 void RendererAccessibilityComplete::FocusedNodeChanged(const WebNode& node) {
76 const WebDocument& document = GetMainDocument();
77 if (document.isNull())
81 // When focus is cleared, implicitly focus the document.
82 // TODO(dmazzoni): Make WebKit send this notification instead.
83 HandleWebAccessibilityEvent(document.accessibilityObject(),
84 WebKit::WebAXEventBlur);
88 void RendererAccessibilityComplete::DidFinishLoad(WebKit::WebFrame* frame) {
89 const WebDocument& document = GetMainDocument();
90 if (document.isNull())
93 // Check to see if the root accessibility object has changed, to work
94 // around WebKit bugs that cause AXObjectCache to be cleared
96 // TODO(dmazzoni): remove this once rdar://5794454 is fixed.
97 WebAXObject new_root = document.accessibilityObject();
98 if (!browser_root_ || new_root.axID() != browser_root_->id)
99 HandleWebAccessibilityEvent(new_root, WebKit::WebAXEventLayoutComplete);
102 void RendererAccessibilityComplete::HandleWebAccessibilityEvent(
103 const WebKit::WebAXObject& obj,
104 WebKit::WebAXEvent event) {
105 const WebDocument& document = GetMainDocument();
106 if (document.isNull())
109 gfx::Size scroll_offset = document.frame()->scrollOffset();
110 if (scroll_offset != last_scroll_offset_) {
111 // Make sure the browser is always aware of the scroll position of
112 // the root document element by posting a generic notification that
114 // TODO(dmazzoni): remove this as soon as
115 // https://bugs.webkit.org/show_bug.cgi?id=73460 is fixed.
116 last_scroll_offset_ = scroll_offset;
117 if (!obj.equals(document.accessibilityObject())) {
118 HandleWebAccessibilityEvent(
119 document.accessibilityObject(),
120 WebKit::WebAXEventLayoutComplete);
124 // Add the accessibility object to our cache and ensure it's valid.
125 AccessibilityHostMsg_EventParams acc_event;
126 acc_event.id = obj.axID();
127 acc_event.event_type = event;
129 // Discard duplicate accessibility events.
130 for (uint32 i = 0; i < pending_events_.size(); ++i) {
131 if (pending_events_[i].id == acc_event.id &&
132 pending_events_[i].event_type ==
133 acc_event.event_type) {
137 pending_events_.push_back(acc_event);
139 if (!ack_pending_ && !weak_factory_.HasWeakPtrs()) {
140 // When no accessibility events are in-flight post a task to send
141 // the events to the browser. We use PostTask so that we can queue
142 // up additional events.
143 base::MessageLoop::current()->PostTask(
145 base::Bind(&RendererAccessibilityComplete::
146 SendPendingAccessibilityEvents,
147 weak_factory_.GetWeakPtr()));
151 RendererAccessibilityComplete::BrowserTreeNode::BrowserTreeNode() : id(0) {}
153 RendererAccessibilityComplete::BrowserTreeNode::~BrowserTreeNode() {}
155 void RendererAccessibilityComplete::SendPendingAccessibilityEvents() {
156 const WebDocument& document = GetMainDocument();
157 if (document.isNull())
160 if (pending_events_.empty())
163 if (render_view_->is_swapped_out())
168 // Make a copy of the events, because it's possible that
169 // actions inside this loop will cause more events to be
171 std::vector<AccessibilityHostMsg_EventParams> src_events =
173 pending_events_.clear();
175 // Generate an event message from each WebKit event.
176 std::vector<AccessibilityHostMsg_EventParams> event_msgs;
178 // Loop over each event and generate an updated event message.
179 for (size_t i = 0; i < src_events.size(); ++i) {
180 AccessibilityHostMsg_EventParams& event =
183 WebAXObject obj = document.accessibilityObjectFromID(
185 if (!obj.updateBackingStoreAndCheckValidity())
188 // When we get a "selected children changed" event, WebKit
189 // doesn't also send us events for each child that changed
190 // selection state, so make sure we re-send that whole subtree.
191 if (event.event_type ==
192 WebKit::WebAXEventSelectedChildrenChanged) {
193 base::hash_map<int32, BrowserTreeNode*>::iterator iter =
194 browser_id_map_.find(obj.axID());
195 if (iter != browser_id_map_.end())
196 ClearBrowserTreeNode(iter->second);
199 // The browser may not have this object yet, for example if we get a
200 // event on an object that was recently added, or if we get a
201 // event on a node before the page has loaded. Work our way
202 // up the parent chain until we find a node the browser has, or until
203 // we reach the root.
204 WebAXObject root_object = document.accessibilityObject();
205 int root_id = root_object.axID();
206 while (browser_id_map_.find(obj.axID()) == browser_id_map_.end() &&
208 obj.axID() != root_id) {
209 obj = obj.parentObject();
210 if (event.event_type ==
211 WebKit::WebAXEventChildrenChanged) {
212 event.id = obj.axID();
216 if (obj.isDetached()) {
219 LOG(WARNING) << "Got event on object that is invalid or has"
220 << " invalid ancestor. Id: " << obj.axID();
225 // Another potential problem is that this event may be on an
226 // object that is detached from the tree. Determine if this node is not a
227 // child of its parent, and if so move the event to the parent.
228 // TODO(dmazzoni): see if this can be removed after
229 // https://bugs.webkit.org/show_bug.cgi?id=68466 is fixed.
230 if (obj.axID() != root_id) {
231 WebAXObject parent = obj.parentObject();
232 while (!parent.isDetached() &&
233 parent.accessibilityIsIgnored()) {
234 parent = parent.parentObject();
237 if (parent.isDetached()) {
241 bool is_child_of_parent = false;
242 for (unsigned int i = 0; i < parent.childCount(); ++i) {
243 if (parent.childAt(i).equals(obj)) {
244 is_child_of_parent = true;
249 if (!is_child_of_parent) {
251 event.id = obj.axID();
255 // Allow WebKit to cache intermediate results since we're doing a bunch
256 // of read-only queries at once.
257 root_object.startCachingComputedObjectAttributesUntilTreeMutates();
259 AccessibilityHostMsg_EventParams event_msg;
260 event_msg.event_type = event.event_type;
261 event_msg.id = event.id;
262 std::set<int> ids_serialized;
263 SerializeChangedNodes(obj, &event_msg.nodes, &ids_serialized);
264 event_msgs.push_back(event_msg);
268 AccessibilityNodeDataTreeNode tree;
269 MakeAccessibilityNodeDataTree(event_msg.nodes, &tree);
270 LOG(INFO) << "Accessibility update: \n"
271 << "routing id=" << routing_id()
273 << AccessibilityEventToString(event.event_type)
274 << "\n" << tree.DebugString(true);
279 AppendLocationChangeEvents(&event_msgs);
281 Send(new AccessibilityHostMsg_Events(routing_id(), event_msgs));
284 void RendererAccessibilityComplete::AppendLocationChangeEvents(
285 std::vector<AccessibilityHostMsg_EventParams>* event_msgs) {
286 std::queue<WebAXObject> objs_to_explore;
287 std::vector<BrowserTreeNode*> location_changes;
288 WebAXObject root_object = GetMainDocument().accessibilityObject();
289 objs_to_explore.push(root_object);
291 while (objs_to_explore.size()) {
292 WebAXObject obj = objs_to_explore.front();
293 objs_to_explore.pop();
295 if (browser_id_map_.find(id) != browser_id_map_.end()) {
296 BrowserTreeNode* browser_node = browser_id_map_[id];
297 gfx::Rect new_location = obj.boundingBoxRect();
298 if (browser_node->location != new_location) {
299 browser_node->location = new_location;
300 location_changes.push_back(browser_node);
304 for (unsigned i = 0; i < obj.childCount(); ++i)
305 objs_to_explore.push(obj.childAt(i));
308 if (location_changes.size() == 0)
311 AccessibilityHostMsg_EventParams event_msg;
312 event_msg.event_type = static_cast<WebKit::WebAXEvent>(-1);
313 event_msg.id = root_object.axID();
314 event_msg.nodes.resize(location_changes.size());
315 for (size_t i = 0; i < location_changes.size(); i++) {
316 AccessibilityNodeData& serialized_node = event_msg.nodes[i];
317 serialized_node.id = location_changes[i]->id;
318 serialized_node.location = location_changes[i]->location;
319 serialized_node.AddBoolAttribute(
320 AccessibilityNodeData::ATTR_UPDATE_LOCATION_ONLY, true);
323 event_msgs->push_back(event_msg);
326 RendererAccessibilityComplete::BrowserTreeNode*
327 RendererAccessibilityComplete::CreateBrowserTreeNode() {
328 return new RendererAccessibilityComplete::BrowserTreeNode();
331 void RendererAccessibilityComplete::SerializeChangedNodes(
332 const WebKit::WebAXObject& obj,
333 std::vector<AccessibilityNodeData>* dst,
334 std::set<int>* ids_serialized) {
335 if (ids_serialized->find(obj.axID()) != ids_serialized->end())
337 ids_serialized->insert(obj.axID());
339 // This method has three responsibilities:
340 // 1. Serialize |obj| into an AccessibilityNodeData, and append it to
341 // the end of the |dst| vector to be send to the browser process.
342 // 2. Determine if |obj| has any new children that the browser doesn't
343 // know about yet, and call SerializeChangedNodes recursively on those.
344 // 3. Update our internal data structure that keeps track of what nodes
345 // the browser knows about.
347 // First, find the BrowserTreeNode for this id in our data structure where
348 // we keep track of what accessibility objects the browser already knows
349 // about. If we don't find it, then this must be the new root of the
350 // accessibility tree.
351 BrowserTreeNode* browser_node = NULL;
352 base::hash_map<int32, BrowserTreeNode*>::iterator iter =
353 browser_id_map_.find(obj.axID());
354 if (iter != browser_id_map_.end()) {
355 browser_node = iter->second;
358 ClearBrowserTreeNode(browser_root_);
359 browser_id_map_.erase(browser_root_->id);
360 delete browser_root_;
362 browser_root_ = CreateBrowserTreeNode();
363 browser_node = browser_root_;
364 browser_node->id = obj.axID();
365 browser_node->location = obj.boundingBoxRect();
366 browser_node->parent = NULL;
367 browser_id_map_[browser_node->id] = browser_node;
370 // Iterate over the ids of the children of |obj|.
371 // Create a set of the child ids so we can quickly look
372 // up which children are new and which ones were there before.
373 // Also catch the case where a child is already in the browser tree
374 // data structure with a different parent, and make sure the old parent
375 // clears this node first.
376 base::hash_set<int32> new_child_ids;
377 const WebDocument& document = GetMainDocument();
378 for (unsigned i = 0; i < obj.childCount(); i++) {
379 WebAXObject child = obj.childAt(i);
380 if (ShouldIncludeChildNode(obj, child)) {
381 int new_child_id = child.axID();
382 new_child_ids.insert(new_child_id);
384 BrowserTreeNode* child = browser_id_map_[new_child_id];
385 if (child && child->parent != browser_node) {
386 // The child is being reparented. Find the WebKit accessibility
387 // object corresponding to the old parent, or the closest ancestor
388 // still in the tree.
389 BrowserTreeNode* parent = child->parent;
390 WebAXObject parent_obj;
392 parent_obj = document.accessibilityObjectFromID(parent->id);
394 if (!parent_obj.isDetached())
396 parent = parent->parent;
399 // Call SerializeChangedNodes recursively on the old parent,
400 // so that the update that clears |child| from its old parent
401 // occurs stricly before the update that adds |child| to its
403 SerializeChangedNodes(parent_obj, dst, ids_serialized);
408 // Go through the old children and delete subtrees for child
409 // ids that are no longer present, and create a map from
410 // id to BrowserTreeNode for the rest. It's important to delete
411 // first in a separate pass so that nodes that are reparented
412 // don't end up children of two different parents in the middle
413 // of an update, which can lead to a double-free.
414 base::hash_map<int32, BrowserTreeNode*> browser_child_id_map;
415 std::vector<BrowserTreeNode*> old_children;
416 old_children.swap(browser_node->children);
417 for (size_t i = 0; i < old_children.size(); i++) {
418 BrowserTreeNode* old_child = old_children[i];
419 int old_child_id = old_child->id;
420 if (new_child_ids.find(old_child_id) == new_child_ids.end()) {
421 browser_id_map_.erase(old_child_id);
422 ClearBrowserTreeNode(old_child);
425 browser_child_id_map[old_child_id] = old_child;
429 // Serialize this node. This fills in all of the fields in
430 // AccessibilityNodeData except child_ids, which we handle below.
431 dst->push_back(AccessibilityNodeData());
432 AccessibilityNodeData* serialized_node = &dst->back();
433 SerializeAccessibilityNode(obj, serialized_node);
434 if (serialized_node->id == browser_root_->id)
435 serialized_node->role = WebKit::WebAXRoleRootWebArea;
437 // Iterate over the children, make note of the ones that are new
438 // and need to be serialized, and update the BrowserTreeNode
439 // data structure to reflect the new tree.
440 std::vector<WebAXObject> children_to_serialize;
441 int child_count = obj.childCount();
442 browser_node->children.reserve(child_count);
443 for (int i = 0; i < child_count; i++) {
444 WebAXObject child = obj.childAt(i);
445 int child_id = child.axID();
447 // Checks to make sure the child is valid, attached to this node,
448 // and one we want to include in the tree.
449 if (!ShouldIncludeChildNode(obj, child))
452 // No need to do anything more with children that aren't new;
453 // the browser will reuse its existing object.
454 if (new_child_ids.find(child_id) == new_child_ids.end())
457 new_child_ids.erase(child_id);
458 serialized_node->child_ids.push_back(child_id);
459 if (browser_child_id_map.find(child_id) != browser_child_id_map.end()) {
460 BrowserTreeNode* reused_child = browser_child_id_map[child_id];
461 reused_child->location = obj.boundingBoxRect();
462 browser_node->children.push_back(reused_child);
464 BrowserTreeNode* new_child = CreateBrowserTreeNode();
465 new_child->id = child_id;
466 new_child->location = obj.boundingBoxRect();
467 new_child->parent = browser_node;
468 browser_node->children.push_back(new_child);
469 browser_id_map_[child_id] = new_child;
470 children_to_serialize.push_back(child);
474 // Serialize all of the new children, recursively.
475 for (size_t i = 0; i < children_to_serialize.size(); ++i)
476 SerializeChangedNodes(children_to_serialize[i], dst, ids_serialized);
479 void RendererAccessibilityComplete::ClearBrowserTreeNode(
480 BrowserTreeNode* browser_node) {
481 for (size_t i = 0; i < browser_node->children.size(); ++i) {
482 browser_id_map_.erase(browser_node->children[i]->id);
483 ClearBrowserTreeNode(browser_node->children[i]);
484 delete browser_node->children[i];
486 browser_node->children.clear();
489 void RendererAccessibilityComplete::OnDoDefaultAction(int acc_obj_id) {
490 const WebDocument& document = GetMainDocument();
491 if (document.isNull())
494 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
495 if (obj.isDetached()) {
498 LOG(WARNING) << "DoDefaultAction on invalid object id " << acc_obj_id;
503 obj.performDefaultAction();
506 void RendererAccessibilityComplete::OnScrollToMakeVisible(
507 int acc_obj_id, gfx::Rect subfocus) {
508 const WebDocument& document = GetMainDocument();
509 if (document.isNull())
512 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
513 if (obj.isDetached()) {
516 LOG(WARNING) << "ScrollToMakeVisible on invalid object id " << acc_obj_id;
521 obj.scrollToMakeVisibleWithSubFocus(
522 WebRect(subfocus.x(), subfocus.y(),
523 subfocus.width(), subfocus.height()));
525 // Make sure the browser gets an event when the scroll
526 // position actually changes.
527 // TODO(dmazzoni): remove this once this bug is fixed:
528 // https://bugs.webkit.org/show_bug.cgi?id=73460
529 HandleWebAccessibilityEvent(
530 document.accessibilityObject(),
531 WebKit::WebAXEventLayoutComplete);
534 void RendererAccessibilityComplete::OnScrollToPoint(
535 int acc_obj_id, gfx::Point point) {
536 const WebDocument& document = GetMainDocument();
537 if (document.isNull())
540 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
541 if (obj.isDetached()) {
544 LOG(WARNING) << "ScrollToPoint on invalid object id " << acc_obj_id;
549 obj.scrollToGlobalPoint(WebPoint(point.x(), point.y()));
551 // Make sure the browser gets an event when the scroll
552 // position actually changes.
553 // TODO(dmazzoni): remove this once this bug is fixed:
554 // https://bugs.webkit.org/show_bug.cgi?id=73460
555 HandleWebAccessibilityEvent(
556 document.accessibilityObject(),
557 WebKit::WebAXEventLayoutComplete);
560 void RendererAccessibilityComplete::OnSetTextSelection(
561 int acc_obj_id, int start_offset, int end_offset) {
562 const WebDocument& document = GetMainDocument();
563 if (document.isNull())
566 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
567 if (obj.isDetached()) {
570 LOG(WARNING) << "SetTextSelection on invalid object id " << acc_obj_id;
575 // TODO(dmazzoni): support elements other than <input>.
576 WebKit::WebNode node = obj.node();
577 if (!node.isNull() && node.isElementNode()) {
578 WebKit::WebElement element = node.to<WebKit::WebElement>();
579 WebKit::WebInputElement* input_element =
580 WebKit::toWebInputElement(&element);
581 if (input_element && input_element->isTextField())
582 input_element->setSelectionRange(start_offset, end_offset);
586 void RendererAccessibilityComplete::OnEventsAck() {
587 DCHECK(ack_pending_);
588 ack_pending_ = false;
589 SendPendingAccessibilityEvents();
592 void RendererAccessibilityComplete::OnSetFocus(int acc_obj_id) {
593 const WebDocument& document = GetMainDocument();
594 if (document.isNull())
597 WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
598 if (obj.isDetached()) {
601 LOG(WARNING) << "OnSetAccessibilityFocus on invalid object id "
608 WebAXObject root = document.accessibilityObject();
609 if (root.isDetached()) {
612 LOG(WARNING) << "OnSetAccessibilityFocus but root is invalid";
618 // By convention, calling SetFocus on the root of the tree should clear the
619 // current focus. Otherwise set the focus to the new node.
620 if (acc_obj_id == root.axID())
621 render_view()->GetWebView()->clearFocusedNode();
623 obj.setFocused(true);
626 void RendererAccessibilityComplete::OnFatalError() {
627 CHECK(false) << "Invalid accessibility tree.";
630 } // namespace content