- add sources.
[platform/framework/web/crosswalk.git] / src / content / renderer / accessibility / renderer_accessibility_complete.cc
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 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;
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   WebAXObject::enableInlineTextBoxAccessibility();
40
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);
48   }
49 }
50
51 RendererAccessibilityComplete::~RendererAccessibilityComplete() {
52 }
53
54 bool RendererAccessibilityComplete::OnMessageReceived(
55     const IPC::Message& message) {
56   bool handled = true;
57   IPC_BEGIN_MESSAGE_MAP(RendererAccessibilityComplete, message)
58     IPC_MESSAGE_HANDLER(AccessibilityMsg_SetFocus, OnSetFocus)
59     IPC_MESSAGE_HANDLER(AccessibilityMsg_DoDefaultAction,
60                         OnDoDefaultAction)
61     IPC_MESSAGE_HANDLER(AccessibilityMsg_Events_ACK,
62                         OnEventsAck)
63     IPC_MESSAGE_HANDLER(AccessibilityMsg_ScrollToMakeVisible,
64                         OnScrollToMakeVisible)
65     IPC_MESSAGE_HANDLER(AccessibilityMsg_ScrollToPoint,
66                         OnScrollToPoint)
67     IPC_MESSAGE_HANDLER(AccessibilityMsg_SetTextSelection,
68                         OnSetTextSelection)
69     IPC_MESSAGE_HANDLER(AccessibilityMsg_FatalError, OnFatalError)
70     IPC_MESSAGE_UNHANDLED(handled = false)
71   IPC_END_MESSAGE_MAP()
72   return handled;
73 }
74
75 void RendererAccessibilityComplete::FocusedNodeChanged(const WebNode& node) {
76   const WebDocument& document = GetMainDocument();
77   if (document.isNull())
78     return;
79
80   if (node.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);
85   }
86 }
87
88 void RendererAccessibilityComplete::DidFinishLoad(WebKit::WebFrame* frame) {
89   const WebDocument& document = GetMainDocument();
90   if (document.isNull())
91     return;
92
93   // Check to see if the root accessibility object has changed, to work
94   // around WebKit bugs that cause AXObjectCache to be cleared
95   // unnecessarily.
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);
100 }
101
102 void RendererAccessibilityComplete::HandleWebAccessibilityEvent(
103     const WebKit::WebAXObject& obj,
104     WebKit::WebAXEvent event) {
105   const WebDocument& document = GetMainDocument();
106   if (document.isNull())
107     return;
108
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
113     // will update it.
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);
121     }
122   }
123
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;
128
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) {
134       return;
135     }
136   }
137   pending_events_.push_back(acc_event);
138
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(
144         FROM_HERE,
145         base::Bind(&RendererAccessibilityComplete::
146                        SendPendingAccessibilityEvents,
147                    weak_factory_.GetWeakPtr()));
148   }
149 }
150
151 RendererAccessibilityComplete::BrowserTreeNode::BrowserTreeNode() : id(0) {}
152
153 RendererAccessibilityComplete::BrowserTreeNode::~BrowserTreeNode() {}
154
155 void RendererAccessibilityComplete::SendPendingAccessibilityEvents() {
156   const WebDocument& document = GetMainDocument();
157   if (document.isNull())
158     return;
159
160   if (pending_events_.empty())
161     return;
162
163   if (render_view_->is_swapped_out())
164     return;
165
166   ack_pending_ = true;
167
168   // Make a copy of the events, because it's possible that
169   // actions inside this loop will cause more events to be
170   // queued up.
171   std::vector<AccessibilityHostMsg_EventParams> src_events =
172       pending_events_;
173   pending_events_.clear();
174
175   // Generate an event message from each WebKit event.
176   std::vector<AccessibilityHostMsg_EventParams> event_msgs;
177
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 =
181         src_events[i];
182
183     WebAXObject obj = document.accessibilityObjectFromID(
184         event.id);
185     if (!obj.updateBackingStoreAndCheckValidity())
186       continue;
187
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);
197     }
198
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() &&
207            !obj.isDetached() &&
208            obj.axID() != root_id) {
209       obj = obj.parentObject();
210       if (event.event_type ==
211           WebKit::WebAXEventChildrenChanged) {
212         event.id = obj.axID();
213       }
214     }
215
216     if (obj.isDetached()) {
217 #ifndef NDEBUG
218       if (logging_)
219         LOG(WARNING) << "Got event on object that is invalid or has"
220                      << " invalid ancestor. Id: " << obj.axID();
221 #endif
222       continue;
223     }
224
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();
235       }
236
237       if (parent.isDetached()) {
238         NOTREACHED();
239         continue;
240       }
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;
245           break;
246         }
247       }
248
249       if (!is_child_of_parent) {
250         obj = parent;
251         event.id = obj.axID();
252       }
253     }
254
255     // Allow WebKit to cache intermediate results since we're doing a bunch
256     // of read-only queries at once.
257     root_object.startCachingComputedObjectAttributesUntilTreeMutates();
258
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);
265
266 #ifndef NDEBUG
267     if (logging_) {
268       AccessibilityNodeDataTreeNode tree;
269       MakeAccessibilityNodeDataTree(event_msg.nodes, &tree);
270       LOG(INFO) << "Accessibility update: \n"
271           << "routing id=" << routing_id()
272           << " event="
273           << AccessibilityEventToString(event.event_type)
274           << "\n" << tree.DebugString(true);
275     }
276 #endif
277   }
278
279   AppendLocationChangeEvents(&event_msgs);
280
281   Send(new AccessibilityHostMsg_Events(routing_id(), event_msgs));
282 }
283
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);
290
291   while (objs_to_explore.size()) {
292     WebAXObject obj = objs_to_explore.front();
293     objs_to_explore.pop();
294     int id = obj.axID();
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);
301       }
302     }
303
304     for (unsigned i = 0; i < obj.childCount(); ++i)
305       objs_to_explore.push(obj.childAt(i));
306   }
307
308   if (location_changes.size() == 0)
309     return;
310
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);
321   }
322
323   event_msgs->push_back(event_msg);
324 }
325
326 RendererAccessibilityComplete::BrowserTreeNode*
327 RendererAccessibilityComplete::CreateBrowserTreeNode() {
328   return new RendererAccessibilityComplete::BrowserTreeNode();
329 }
330
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())
336     return;
337   ids_serialized->insert(obj.axID());
338
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.
346
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;
356   } else {
357     if (browser_root_) {
358       ClearBrowserTreeNode(browser_root_);
359       browser_id_map_.erase(browser_root_->id);
360       delete browser_root_;
361     }
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;
368   }
369
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);
383
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;
391         while (parent) {
392           parent_obj = document.accessibilityObjectFromID(parent->id);
393
394           if (!parent_obj.isDetached())
395             break;
396           parent = parent->parent;
397         }
398         CHECK(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
402         // new parent.
403         SerializeChangedNodes(parent_obj, dst, ids_serialized);
404       }
405     }
406   }
407
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);
423       delete old_child;
424     } else {
425       browser_child_id_map[old_child_id] = old_child;
426     }
427   }
428
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;
436
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();
446
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))
450       continue;
451
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())
455       continue;
456
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);
463     } else {
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);
471     }
472   }
473
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);
477 }
478
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];
485   }
486   browser_node->children.clear();
487 }
488
489 void RendererAccessibilityComplete::OnDoDefaultAction(int acc_obj_id) {
490   const WebDocument& document = GetMainDocument();
491   if (document.isNull())
492     return;
493
494   WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
495   if (obj.isDetached()) {
496 #ifndef NDEBUG
497     if (logging_)
498       LOG(WARNING) << "DoDefaultAction on invalid object id " << acc_obj_id;
499 #endif
500     return;
501   }
502
503   obj.performDefaultAction();
504 }
505
506 void RendererAccessibilityComplete::OnScrollToMakeVisible(
507     int acc_obj_id, gfx::Rect subfocus) {
508   const WebDocument& document = GetMainDocument();
509   if (document.isNull())
510     return;
511
512   WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
513   if (obj.isDetached()) {
514 #ifndef NDEBUG
515     if (logging_)
516       LOG(WARNING) << "ScrollToMakeVisible on invalid object id " << acc_obj_id;
517 #endif
518     return;
519   }
520
521   obj.scrollToMakeVisibleWithSubFocus(
522       WebRect(subfocus.x(), subfocus.y(),
523               subfocus.width(), subfocus.height()));
524
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);
532 }
533
534 void RendererAccessibilityComplete::OnScrollToPoint(
535     int acc_obj_id, gfx::Point point) {
536   const WebDocument& document = GetMainDocument();
537   if (document.isNull())
538     return;
539
540   WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
541   if (obj.isDetached()) {
542 #ifndef NDEBUG
543     if (logging_)
544       LOG(WARNING) << "ScrollToPoint on invalid object id " << acc_obj_id;
545 #endif
546     return;
547   }
548
549   obj.scrollToGlobalPoint(WebPoint(point.x(), point.y()));
550
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);
558 }
559
560 void RendererAccessibilityComplete::OnSetTextSelection(
561     int acc_obj_id, int start_offset, int end_offset) {
562   const WebDocument& document = GetMainDocument();
563   if (document.isNull())
564     return;
565
566   WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
567   if (obj.isDetached()) {
568 #ifndef NDEBUG
569     if (logging_)
570       LOG(WARNING) << "SetTextSelection on invalid object id " << acc_obj_id;
571 #endif
572     return;
573   }
574
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);
583   }
584 }
585
586 void RendererAccessibilityComplete::OnEventsAck() {
587   DCHECK(ack_pending_);
588   ack_pending_ = false;
589   SendPendingAccessibilityEvents();
590 }
591
592 void RendererAccessibilityComplete::OnSetFocus(int acc_obj_id) {
593   const WebDocument& document = GetMainDocument();
594   if (document.isNull())
595     return;
596
597   WebAXObject obj = document.accessibilityObjectFromID(acc_obj_id);
598   if (obj.isDetached()) {
599 #ifndef NDEBUG
600     if (logging_) {
601       LOG(WARNING) << "OnSetAccessibilityFocus on invalid object id "
602                    << acc_obj_id;
603     }
604 #endif
605     return;
606   }
607
608   WebAXObject root = document.accessibilityObject();
609   if (root.isDetached()) {
610 #ifndef NDEBUG
611     if (logging_) {
612       LOG(WARNING) << "OnSetAccessibilityFocus but root is invalid";
613     }
614 #endif
615     return;
616   }
617
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();
622   else
623     obj.setFocused(true);
624 }
625
626 void RendererAccessibilityComplete::OnFatalError() {
627   CHECK(false) << "Invalid accessibility tree.";
628 }
629
630 }  // namespace content