Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(13)

Side by Side Diff: content/renderer/renderer_accessibility_complete.cc

Issue 12226061: Move renderer accessibility to subdir and add OWNERS (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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/renderer_accessibility_complete.h"
6
7 #include "base/bind.h"
8 #include "base/message_loop.h"
9 #include "content/renderer/accessibility_node_serializer.h"
10 #include "content/renderer/render_view_impl.h"
11 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityObjec t.h"
12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h"
15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h"
16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
17
18 using WebKit::WebAccessibilityNotification;
19 using WebKit::WebAccessibilityObject;
20 using WebKit::WebDocument;
21 using WebKit::WebFrame;
22 using WebKit::WebNode;
23 using WebKit::WebPoint;
24 using WebKit::WebRect;
25 using WebKit::WebSize;
26 using WebKit::WebView;
27
28 namespace content {
29
30 bool WebAccessibilityNotificationToAccessibilityNotification(
31 WebAccessibilityNotification notification,
32 AccessibilityNotification* type) {
33 switch (notification) {
34 case WebKit::WebAccessibilityNotificationActiveDescendantChanged:
35 *type = AccessibilityNotificationActiveDescendantChanged;
36 break;
37 case WebKit::WebAccessibilityNotificationCheckedStateChanged:
38 *type = AccessibilityNotificationCheckStateChanged;
39 break;
40 case WebKit::WebAccessibilityNotificationChildrenChanged:
41 *type = AccessibilityNotificationChildrenChanged;
42 break;
43 case WebKit::WebAccessibilityNotificationFocusedUIElementChanged:
44 *type = AccessibilityNotificationFocusChanged;
45 break;
46 case WebKit::WebAccessibilityNotificationLayoutComplete:
47 *type = AccessibilityNotificationLayoutComplete;
48 break;
49 case WebKit::WebAccessibilityNotificationLiveRegionChanged:
50 *type = AccessibilityNotificationLiveRegionChanged;
51 break;
52 case WebKit::WebAccessibilityNotificationLoadComplete:
53 *type = AccessibilityNotificationLoadComplete;
54 break;
55 case WebKit::WebAccessibilityNotificationMenuListItemSelected:
56 *type = AccessibilityNotificationMenuListItemSelected;
57 break;
58 case WebKit::WebAccessibilityNotificationMenuListValueChanged:
59 *type = AccessibilityNotificationMenuListValueChanged;
60 break;
61 case WebKit::WebAccessibilityNotificationRowCollapsed:
62 *type = AccessibilityNotificationRowCollapsed;
63 break;
64 case WebKit::WebAccessibilityNotificationRowCountChanged:
65 *type = AccessibilityNotificationRowCountChanged;
66 break;
67 case WebKit::WebAccessibilityNotificationRowExpanded:
68 *type = AccessibilityNotificationRowExpanded;
69 break;
70 case WebKit::WebAccessibilityNotificationScrolledToAnchor:
71 *type = AccessibilityNotificationScrolledToAnchor;
72 break;
73 case WebKit::WebAccessibilityNotificationSelectedChildrenChanged:
74 *type = AccessibilityNotificationSelectedChildrenChanged;
75 break;
76 case WebKit::WebAccessibilityNotificationSelectedTextChanged:
77 *type = AccessibilityNotificationSelectedTextChanged;
78 break;
79 case WebKit::WebAccessibilityNotificationValueChanged:
80 *type = AccessibilityNotificationValueChanged;
81 break;
82 default:
83 DLOG(WARNING)
84 << "WebKit accessibility notification not handled in switch!";
85 return false;
86 }
87 return true;
88 }
89
90 RendererAccessibilityComplete::RendererAccessibilityComplete(
91 RenderViewImpl* render_view)
92 : RendererAccessibility(render_view),
93 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
94 browser_root_(NULL),
95 last_scroll_offset_(gfx::Size()),
96 ack_pending_(false) {
97 WebAccessibilityObject::enableAccessibility();
98
99 const WebDocument& document = GetMainDocument();
100 if (!document.isNull()) {
101 // It's possible that the webview has already loaded a webpage without
102 // accessibility being enabled. Initialize the browser's cached
103 // accessibility tree by sending it a 'load complete' notification.
104 HandleAccessibilityNotification(
105 document.accessibilityObject(),
106 AccessibilityNotificationLayoutComplete);
107 }
108 }
109
110 RendererAccessibilityComplete::~RendererAccessibilityComplete() {
111 }
112
113 bool RendererAccessibilityComplete::OnMessageReceived(
114 const IPC::Message& message) {
115 bool handled = true;
116 IPC_BEGIN_MESSAGE_MAP(RendererAccessibilityComplete, message)
117 IPC_MESSAGE_HANDLER(AccessibilityMsg_SetFocus, OnSetFocus)
118 IPC_MESSAGE_HANDLER(AccessibilityMsg_DoDefaultAction,
119 OnDoDefaultAction)
120 IPC_MESSAGE_HANDLER(AccessibilityMsg_Notifications_ACK,
121 OnNotificationsAck)
122 IPC_MESSAGE_HANDLER(AccessibilityMsg_ScrollToMakeVisible,
123 OnScrollToMakeVisible)
124 IPC_MESSAGE_HANDLER(AccessibilityMsg_ScrollToPoint,
125 OnScrollToPoint)
126 IPC_MESSAGE_HANDLER(AccessibilityMsg_SetTextSelection,
127 OnSetTextSelection)
128 IPC_MESSAGE_UNHANDLED(handled = false)
129 IPC_END_MESSAGE_MAP()
130 return handled;
131 }
132
133 void RendererAccessibilityComplete::FocusedNodeChanged(const WebNode& node) {
134 const WebDocument& document = GetMainDocument();
135 if (document.isNull())
136 return;
137
138 if (node.isNull()) {
139 // When focus is cleared, implicitly focus the document.
140 // TODO(dmazzoni): Make WebKit send this notification instead.
141 HandleAccessibilityNotification(
142 document.accessibilityObject(),
143 AccessibilityNotificationBlur);
144 }
145 }
146
147 void RendererAccessibilityComplete::DidFinishLoad(WebKit::WebFrame* frame) {
148 const WebDocument& document = GetMainDocument();
149 if (document.isNull())
150 return;
151
152 // Check to see if the root accessibility object has changed, to work
153 // around WebKit bugs that cause AXObjectCache to be cleared
154 // unnecessarily.
155 // TODO(dmazzoni): remove this once rdar://5794454 is fixed.
156 WebAccessibilityObject new_root = document.accessibilityObject();
157 if (!browser_root_ || new_root.axID() != browser_root_->id) {
158 HandleAccessibilityNotification(
159 new_root,
160 AccessibilityNotificationLayoutComplete);
161 }
162 }
163
164 void RendererAccessibilityComplete::HandleWebAccessibilityNotification(
165 const WebAccessibilityObject& obj,
166 WebAccessibilityNotification notification) {
167 AccessibilityNotification temp;
168 if (!WebAccessibilityNotificationToAccessibilityNotification(
169 notification, &temp)) {
170 return;
171 }
172
173 HandleAccessibilityNotification(obj, temp);
174 }
175
176 void RendererAccessibilityComplete::HandleAccessibilityNotification(
177 const WebKit::WebAccessibilityObject& obj,
178 AccessibilityNotification notification) {
179 const WebDocument& document = GetMainDocument();
180 if (document.isNull())
181 return;
182
183 gfx::Size scroll_offset = document.frame()->scrollOffset();
184 if (scroll_offset != last_scroll_offset_) {
185 // Make sure the browser is always aware of the scroll position of
186 // the root document element by posting a generic notification that
187 // will update it.
188 // TODO(dmazzoni): remove this as soon as
189 // https://bugs.webkit.org/show_bug.cgi?id=73460 is fixed.
190 last_scroll_offset_ = scroll_offset;
191 if (!obj.equals(document.accessibilityObject())) {
192 HandleAccessibilityNotification(
193 document.accessibilityObject(),
194 AccessibilityNotificationLayoutComplete);
195 }
196 }
197
198 // Add the accessibility object to our cache and ensure it's valid.
199 AccessibilityHostMsg_NotificationParams acc_notification;
200 acc_notification.id = obj.axID();
201 acc_notification.notification_type = notification;
202
203 // Discard duplicate accessibility notifications.
204 for (uint32 i = 0; i < pending_notifications_.size(); ++i) {
205 if (pending_notifications_[i].id == acc_notification.id &&
206 pending_notifications_[i].notification_type ==
207 acc_notification.notification_type) {
208 return;
209 }
210 }
211 pending_notifications_.push_back(acc_notification);
212
213 if (!ack_pending_ && !weak_factory_.HasWeakPtrs()) {
214 // When no accessibility notifications are in-flight post a task to send
215 // the notifications to the browser. We use PostTask so that we can queue
216 // up additional notifications.
217 MessageLoop::current()->PostTask(
218 FROM_HERE,
219 base::Bind(
220 &RendererAccessibilityComplete::
221 SendPendingAccessibilityNotifications,
222 weak_factory_.GetWeakPtr()));
223 }
224 }
225
226 RendererAccessibilityComplete::BrowserTreeNode::BrowserTreeNode() : id(0) {}
227
228 RendererAccessibilityComplete::BrowserTreeNode::~BrowserTreeNode() {}
229
230 void RendererAccessibilityComplete::SendPendingAccessibilityNotifications() {
231 const WebDocument& document = GetMainDocument();
232 if (document.isNull())
233 return;
234
235 if (pending_notifications_.empty())
236 return;
237
238 ack_pending_ = true;
239
240 // Make a copy of the notifications, because it's possible that
241 // actions inside this loop will cause more notifications to be
242 // queued up.
243 std::vector<AccessibilityHostMsg_NotificationParams> src_notifications =
244 pending_notifications_;
245 pending_notifications_.clear();
246
247 // Allow WebKit to cache intermediate results since we're doing a bunch
248 // of read-only queries at once.
249 WebAccessibilityObject rootObject = document.accessibilityObject();
250 rootObject.startCachingComputedObjectAttributesUntilTreeMutates();
251
252 // Generate a notification message from each WebKit notification.
253 std::vector<AccessibilityHostMsg_NotificationParams> notification_msgs;
254
255 // Loop over each notification and generate an updated notification message.
256 for (size_t i = 0; i < src_notifications.size(); ++i) {
257 AccessibilityHostMsg_NotificationParams& notification =
258 src_notifications[i];
259
260 // TODO(dtseng): Come up with a cleaner way of deciding to include children.
261 int root_id = rootObject.axID();
262 bool includes_children = ShouldIncludeChildren(notification) ||
263 root_id == notification.id;
264 WebAccessibilityObject obj = document.accessibilityObjectFromID(
265 notification.id);
266 if (!obj.updateBackingStoreAndCheckValidity())
267 continue;
268
269 // The browser may not have this object yet, for example if we get a
270 // notification on an object that was recently added, or if we get a
271 // notification on a node before the page has loaded. Work our way
272 // up the parent chain until we find a node the browser has, or until
273 // we reach the root.
274 while (browser_id_map_.find(obj.axID()) == browser_id_map_.end() &&
275 !obj.isDetached() &&
276 obj.axID() != root_id) {
277 obj = obj.parentObject();
278 includes_children = true;
279 if (notification.notification_type ==
280 AccessibilityNotificationChildrenChanged) {
281 notification.id = obj.axID();
282 }
283 }
284
285 if (obj.isDetached()) {
286 #ifndef NDEBUG
287 if (logging_)
288 LOG(WARNING) << "Got notification on object that is invalid or has"
289 << " invalid ancestor. Id: " << obj.axID();
290 #endif
291 continue;
292 }
293
294 // Another potential problem is that this notification may be on an
295 // object that is detached from the tree. Determine if this node is not a
296 // child of its parent, and if so move the notification to the parent.
297 // TODO(dmazzoni): see if this can be removed after
298 // https://bugs.webkit.org/show_bug.cgi?id=68466 is fixed.
299 if (obj.axID() != root_id) {
300 WebAccessibilityObject parent = obj.parentObject();
301 while (!parent.isDetached() &&
302 parent.accessibilityIsIgnored()) {
303 parent = parent.parentObject();
304 }
305
306 if (parent.isDetached()) {
307 NOTREACHED();
308 continue;
309 }
310 bool is_child_of_parent = false;
311 for (unsigned int i = 0; i < parent.childCount(); ++i) {
312 if (parent.childAt(i).equals(obj)) {
313 is_child_of_parent = true;
314 break;
315 }
316 }
317
318 if (!is_child_of_parent) {
319 obj = parent;
320 notification.id = obj.axID();
321 includes_children = true;
322 }
323 }
324
325 AccessibilityHostMsg_NotificationParams notification_msg;
326 notification_msg.notification_type = notification.notification_type;
327 notification_msg.id = notification.id;
328 notification_msg.includes_children = includes_children;
329 SerializeAccessibilityNode(obj,
330 &notification_msg.acc_tree,
331 includes_children);
332 if (obj.axID() == root_id) {
333 DCHECK_EQ(notification_msg.acc_tree.role,
334 AccessibilityNodeData::ROLE_WEB_AREA);
335 notification_msg.acc_tree.role =
336 AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
337 }
338 notification_msgs.push_back(notification_msg);
339
340 if (includes_children)
341 UpdateBrowserTree(notification_msg.acc_tree);
342
343 #ifndef NDEBUG
344 if (logging_) {
345 LOG(INFO) << "Accessibility update: \n"
346 << "routing id=" << routing_id()
347 << " notification="
348 << AccessibilityNotificationToString(notification.notification_type)
349 << "\n" << notification_msg.acc_tree.DebugString(true);
350 }
351 #endif
352 }
353
354 Send(new AccessibilityHostMsg_Notifications(routing_id(), notification_msgs));
355 }
356
357 void RendererAccessibilityComplete::UpdateBrowserTree(
358 const AccessibilityNodeData& renderer_node) {
359 BrowserTreeNode* browser_node = NULL;
360 base::hash_map<int32, BrowserTreeNode*>::iterator iter =
361 browser_id_map_.find(renderer_node.id);
362 if (iter != browser_id_map_.end()) {
363 browser_node = iter->second;
364 ClearBrowserTreeNode(browser_node);
365 } else {
366 DCHECK_EQ(renderer_node.role, AccessibilityNodeData::ROLE_ROOT_WEB_AREA);
367 if (browser_root_) {
368 ClearBrowserTreeNode(browser_root_);
369 browser_id_map_.erase(browser_root_->id);
370 delete browser_root_;
371 }
372 browser_root_ = new BrowserTreeNode;
373 browser_node = browser_root_;
374 browser_node->id = renderer_node.id;
375 browser_id_map_[browser_node->id] = browser_node;
376 }
377 browser_node->children.reserve(renderer_node.children.size());
378 for (size_t i = 0; i < renderer_node.children.size(); ++i) {
379 BrowserTreeNode* browser_child_node = new BrowserTreeNode;
380 browser_child_node->id = renderer_node.children[i].id;
381 browser_id_map_[browser_child_node->id] = browser_child_node;
382 browser_node->children.push_back(browser_child_node);
383 UpdateBrowserTree(renderer_node.children[i]);
384 }
385 }
386
387 void RendererAccessibilityComplete::ClearBrowserTreeNode(
388 BrowserTreeNode* browser_node) {
389 for (size_t i = 0; i < browser_node->children.size(); ++i) {
390 browser_id_map_.erase(browser_node->children[i]->id);
391 ClearBrowserTreeNode(browser_node->children[i]);
392 delete browser_node->children[i];
393 }
394 browser_node->children.clear();
395 }
396
397 void RendererAccessibilityComplete::OnDoDefaultAction(int acc_obj_id) {
398 const WebDocument& document = GetMainDocument();
399 if (document.isNull())
400 return;
401
402 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
403 if (obj.isDetached()) {
404 #ifndef NDEBUG
405 if (logging_)
406 LOG(WARNING) << "DoDefaultAction on invalid object id " << acc_obj_id;
407 #endif
408 return;
409 }
410
411 obj.performDefaultAction();
412 }
413
414 void RendererAccessibilityComplete::OnScrollToMakeVisible(
415 int acc_obj_id, gfx::Rect subfocus) {
416 const WebDocument& document = GetMainDocument();
417 if (document.isNull())
418 return;
419
420 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
421 if (obj.isDetached()) {
422 #ifndef NDEBUG
423 if (logging_)
424 LOG(WARNING) << "ScrollToMakeVisible on invalid object id " << acc_obj_id;
425 #endif
426 return;
427 }
428
429 obj.scrollToMakeVisibleWithSubFocus(
430 WebRect(subfocus.x(), subfocus.y(),
431 subfocus.width(), subfocus.height()));
432
433 // Make sure the browser gets a notification when the scroll
434 // position actually changes.
435 // TODO(dmazzoni): remove this once this bug is fixed:
436 // https://bugs.webkit.org/show_bug.cgi?id=73460
437 HandleAccessibilityNotification(
438 document.accessibilityObject(),
439 AccessibilityNotificationLayoutComplete);
440 }
441
442 void RendererAccessibilityComplete::OnScrollToPoint(
443 int acc_obj_id, gfx::Point point) {
444 const WebDocument& document = GetMainDocument();
445 if (document.isNull())
446 return;
447
448 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
449 if (obj.isDetached()) {
450 #ifndef NDEBUG
451 if (logging_)
452 LOG(WARNING) << "ScrollToPoint on invalid object id " << acc_obj_id;
453 #endif
454 return;
455 }
456
457 obj.scrollToGlobalPoint(WebPoint(point.x(), point.y()));
458
459 // Make sure the browser gets a notification when the scroll
460 // position actually changes.
461 // TODO(dmazzoni): remove this once this bug is fixed:
462 // https://bugs.webkit.org/show_bug.cgi?id=73460
463 HandleAccessibilityNotification(
464 document.accessibilityObject(),
465 AccessibilityNotificationLayoutComplete);
466 }
467
468 void RendererAccessibilityComplete::OnSetTextSelection(
469 int acc_obj_id, int start_offset, int end_offset) {
470 const WebDocument& document = GetMainDocument();
471 if (document.isNull())
472 return;
473
474 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
475 if (obj.isDetached()) {
476 #ifndef NDEBUG
477 if (logging_)
478 LOG(WARNING) << "SetTextSelection on invalid object id " << acc_obj_id;
479 #endif
480 return;
481 }
482
483 // TODO(dmazzoni): support elements other than <input>.
484 WebKit::WebNode node = obj.node();
485 if (!node.isNull() && node.isElementNode()) {
486 WebKit::WebElement element = node.to<WebKit::WebElement>();
487 WebKit::WebInputElement* input_element =
488 WebKit::toWebInputElement(&element);
489 if (input_element && input_element->isTextField())
490 input_element->setSelectionRange(start_offset, end_offset);
491 }
492 }
493
494 void RendererAccessibilityComplete::OnNotificationsAck() {
495 DCHECK(ack_pending_);
496 ack_pending_ = false;
497 SendPendingAccessibilityNotifications();
498 }
499
500 void RendererAccessibilityComplete::OnSetFocus(int acc_obj_id) {
501 const WebDocument& document = GetMainDocument();
502 if (document.isNull())
503 return;
504
505 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
506 if (obj.isDetached()) {
507 #ifndef NDEBUG
508 if (logging_) {
509 LOG(WARNING) << "OnSetAccessibilityFocus on invalid object id "
510 << acc_obj_id;
511 }
512 #endif
513 return;
514 }
515
516 WebAccessibilityObject root = document.accessibilityObject();
517 if (root.isDetached()) {
518 #ifndef NDEBUG
519 if (logging_) {
520 LOG(WARNING) << "OnSetAccessibilityFocus but root is invalid";
521 }
522 #endif
523 return;
524 }
525
526 // By convention, calling SetFocus on the root of the tree should clear the
527 // current focus. Otherwise set the focus to the new node.
528 if (acc_obj_id == root.axID())
529 render_view()->GetWebView()->clearFocusedNode();
530 else
531 obj.setFocused(true);
532 }
533
534 bool RendererAccessibilityComplete::ShouldIncludeChildren(
535 const AccessibilityHostMsg_NotificationParams& notification) {
536 AccessibilityNotification type = notification.notification_type;
537 if (type == AccessibilityNotificationChildrenChanged ||
538 type == AccessibilityNotificationLoadComplete ||
539 type == AccessibilityNotificationLiveRegionChanged ||
540 type == AccessibilityNotificationSelectedChildrenChanged) {
541 return true;
542 }
543 return false;
544 }
545
546 } // namespace content
OLDNEW
« no previous file with comments | « content/renderer/renderer_accessibility_complete.h ('k') | content/renderer/renderer_accessibility_focus_only.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698