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