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

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

Issue 7966013: Rewrite renderer accessibility to not use WebAccessibilityCache. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 9 years, 2 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
« no previous file with comments | « content/renderer/renderer_accessibility.h ('k') | webkit/glue/webaccessibility.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 "base/command_line.h"
6 #include "content/common/content_switches.h"
7 #include "content/common/view_messages.h"
8 #include "content/renderer/render_view.h"
9 #include "content/renderer/renderer_accessibility.h"
10 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityObjec t.h"
11 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
14 #include "webkit/glue/webaccessibility.h"
15
16 using WebKit::WebAccessibilityNotification;
17 using WebKit::WebAccessibilityObject;
18 using WebKit::WebDocument;
19 using WebKit::WebFrame;
20 using WebKit::WebNode;
21 using WebKit::WebView;
22 using webkit_glue::WebAccessibility;
23
24 bool WebAccessibilityNotificationToViewHostMsg(
25 WebAccessibilityNotification notification,
26 ViewHostMsg_AccEvent::Value* type) {
27 switch (notification) {
28 case WebKit::WebAccessibilityNotificationActiveDescendantChanged:
29 *type = ViewHostMsg_AccEvent::ACTIVE_DESCENDANT_CHANGED;
30 break;
31 case WebKit::WebAccessibilityNotificationCheckedStateChanged:
32 *type = ViewHostMsg_AccEvent::CHECK_STATE_CHANGED;
33 break;
34 case WebKit::WebAccessibilityNotificationChildrenChanged:
35 *type = ViewHostMsg_AccEvent::CHILDREN_CHANGED;
36 break;
37 case WebKit::WebAccessibilityNotificationFocusedUIElementChanged:
38 *type = ViewHostMsg_AccEvent::FOCUS_CHANGED;
39 break;
40 case WebKit::WebAccessibilityNotificationLayoutComplete:
41 *type = ViewHostMsg_AccEvent::LAYOUT_COMPLETE;
42 break;
43 case WebKit::WebAccessibilityNotificationLiveRegionChanged:
44 *type = ViewHostMsg_AccEvent::LIVE_REGION_CHANGED;
45 break;
46 case WebKit::WebAccessibilityNotificationLoadComplete:
47 *type = ViewHostMsg_AccEvent::LOAD_COMPLETE;
48 break;
49 case WebKit::WebAccessibilityNotificationMenuListValueChanged:
50 *type = ViewHostMsg_AccEvent::MENU_LIST_VALUE_CHANGED;
51 break;
52 case WebKit::WebAccessibilityNotificationRowCollapsed:
53 *type = ViewHostMsg_AccEvent::ROW_COLLAPSED;
54 break;
55 case WebKit::WebAccessibilityNotificationRowCountChanged:
56 *type = ViewHostMsg_AccEvent::ROW_COUNT_CHANGED;
57 break;
58 case WebKit::WebAccessibilityNotificationRowExpanded:
59 *type = ViewHostMsg_AccEvent::ROW_EXPANDED;
60 break;
61 case WebKit::WebAccessibilityNotificationScrolledToAnchor:
62 *type = ViewHostMsg_AccEvent::SCROLLED_TO_ANCHOR;
63 break;
64 case WebKit::WebAccessibilityNotificationSelectedChildrenChanged:
65 *type = ViewHostMsg_AccEvent::SELECTED_CHILDREN_CHANGED;
66 break;
67 case WebKit::WebAccessibilityNotificationSelectedTextChanged:
68 *type = ViewHostMsg_AccEvent::SELECTED_TEXT_CHANGED;
69 break;
70 case WebKit::WebAccessibilityNotificationValueChanged:
71 *type = ViewHostMsg_AccEvent::VALUE_CHANGED;
72 break;
73 default:
74 NOTREACHED();
75 return false;
76 }
77 return true;
78 }
79
80 RendererAccessibility::RendererAccessibility(RenderView* render_view)
81 : RenderViewObserver(render_view),
82 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
83 browser_root_(NULL),
84 ack_pending_(false),
85 logging_(false),
86 sent_load_complete_(false) {
87 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
88 if (command_line.HasSwitch(switches::kEnableAccessibility))
89 WebAccessibilityObject::enableAccessibility();
90 if (command_line.HasSwitch(switches::kEnableAccessibilityLogging))
91 logging_ = true;
92 }
93
94 RendererAccessibility::~RendererAccessibility() {
95 }
96
97 bool RendererAccessibility::OnMessageReceived(const IPC::Message& message) {
98 bool handled = true;
99 IPC_BEGIN_MESSAGE_MAP(RendererAccessibility, message)
100 IPC_MESSAGE_HANDLER(ViewMsg_EnableAccessibility, OnEnableAccessibility)
101 IPC_MESSAGE_HANDLER(ViewMsg_SetAccessibilityFocus, OnSetAccessibilityFocus)
102 IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityDoDefaultAction,
103 OnAccessibilityDoDefaultAction)
104 IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityNotifications_ACK,
105 OnAccessibilityNotificationsAck)
106 IPC_MESSAGE_UNHANDLED(handled = false)
107 IPC_END_MESSAGE_MAP()
108 return handled;
109 }
110
111 void RendererAccessibility::FocusedNodeChanged(const WebNode& node) {
112 if (!WebAccessibilityObject::accessibilityEnabled())
113 return;
114
115 const WebDocument& document = GetMainDocument();
116 if (document.isNull())
117 return;
118
119 if (node.isNull()) {
120 // When focus is cleared, implicitly focus the document.
121 // TODO(dmazzoni): Make WebKit send this notification instead.
122 PostAccessibilityNotification(
123 document.accessibilityObject(),
124 WebKit::WebAccessibilityNotificationFocusedUIElementChanged);
125 }
126 }
127
128 void RendererAccessibility::DidFinishLoad(WebKit::WebFrame* frame) {
129 if (!WebAccessibilityObject::accessibilityEnabled())
130 return;
131
132 const WebDocument& document = GetMainDocument();
133 if (document.isNull())
134 return;
135
136 // Check to see if the root accessibility object has changed, to work
137 // around WebKit bugs that cause AXObjectCache to be cleared
138 // unnecessarily.
139 // TODO(dmazzoni): remove this once rdar://5794454 is fixed.
140 WebAccessibilityObject new_root = document.accessibilityObject();
141 if (!browser_root_ || new_root.axID() != browser_root_->id) {
142 PostAccessibilityNotification(
143 new_root,
144 WebKit::WebAccessibilityNotificationLoadComplete);
145 }
146 }
147
148 void RendererAccessibility::PostAccessibilityNotification(
149 const WebAccessibilityObject& obj,
150 WebAccessibilityNotification notification) {
151 if (!WebAccessibilityObject::accessibilityEnabled())
152 return;
153
154 const WebDocument& document = GetMainDocument();
155 if (document.isNull())
156 return;
157
158 if (notification != WebKit::WebAccessibilityNotificationLoadComplete &&
159 !sent_load_complete_) {
160 // Load complete should be our first notification sent. Send it manually
161 // in cases where we don't get it first to avoid focus problems.
162 PostAccessibilityNotification(
163 document.accessibilityObject(),
164 WebKit::WebAccessibilityNotificationLoadComplete);
165 }
166
167 if (notification == WebKit::WebAccessibilityNotificationLoadComplete)
168 sent_load_complete_ = true;
169
170 // Add the accessibility object to our cache and ensure it's valid.
171 Notification acc_notification;
172 acc_notification.id = obj.axID();
173 acc_notification.type = notification;
174
175 ViewHostMsg_AccEvent::Value temp;
176 if (!WebAccessibilityNotificationToViewHostMsg(notification, &temp))
177 return;
178
179 // Discard duplicate accessibility notifications.
180 for (uint32 i = 0; i < pending_notifications_.size(); ++i) {
181 if (pending_notifications_[i].id == acc_notification.id &&
182 pending_notifications_[i].type == acc_notification.type) {
183 return;
184 }
185 }
186 pending_notifications_.push_back(acc_notification);
187
188 if (!ack_pending_ && method_factory_.empty()) {
189 // When no accessibility notifications are in-flight post a task to send
190 // the notifications to the browser. We use PostTask so that we can queue
191 // up additional notifications.
192 MessageLoop::current()->PostTask(
193 FROM_HERE,
194 method_factory_.NewRunnableMethod(
195 &RendererAccessibility::SendPendingAccessibilityNotifications));
196 }
197 }
198
199 void RendererAccessibility::SendPendingAccessibilityNotifications() {
200 const WebDocument& document = GetMainDocument();
201 if (document.isNull())
202 return;
203
204 if (pending_notifications_.empty())
205 return;
206
207 // Send all pending accessibility notifications.
208 std::vector<ViewHostMsg_AccessibilityNotification_Params> notifications;
209 for (size_t i = 0; i < pending_notifications_.size(); ++i) {
210 Notification& notification = pending_notifications_[i];
211
212 bool includes_children = ShouldIncludeChildren(notification);
213 WebAccessibilityObject obj = document.accessibilityObjectFromID(
214 notification.id);
215
216 if (!obj.isValid()) {
217 #ifndef NDEBUG
218 if (logging_)
219 LOG(WARNING) << "Got notification on invalid object id " << obj.axID();
220 #endif
221 continue;
222 }
223
224 // The browser may not have this object yet, for example if we get a
225 // notification on an object that was recently added, or if we get a
226 // notification on a node before the page has loaded. Work our way
227 // up the parent chain until we find a node the browser has, or until
228 // we reach the root.
229 int root_id = document.accessibilityObject().axID();
230 while (browser_id_map_.find(obj.axID()) == browser_id_map_.end() &&
231 obj.axID() != root_id) {
232 obj = obj.parentObject();
233 includes_children = true;
234 if (notification.type ==
235 WebKit::WebAccessibilityNotificationChildrenChanged) {
236 notification.id = obj.axID();
237 }
238 }
239
240 // Another potential problem is that this notification may be on an
241 // object that is detached from the tree. Determine if this node is not a
242 // child of its parent, and if so move the notification to the parent.
243 // TODO(dmazzoni): see if this can be removed after
244 // https://bugs.webkit.org/show_bug.cgi?id=68466 is fixed.
245 if (obj.axID() != root_id) {
246 WebAccessibilityObject parent = obj.parentObject();
247 while (!parent.isNull() && parent.accessibilityIsIgnored())
248 parent = parent.parentObject();
249 if (parent.isNull()) {
250 NOTREACHED();
251 }
252 bool is_child_of_parent = false;
253 for (unsigned int i = 0; i < parent.childCount(); ++i) {
254 if (parent.childAt(i).equals(obj)) {
255 is_child_of_parent = true;
256 break;
257 }
258 }
259 if (!is_child_of_parent) {
260 obj = parent;
261 notification.id = obj.axID();
262 includes_children = true;
263 }
264 }
265
266 ViewHostMsg_AccessibilityNotification_Params param;
267 WebAccessibilityNotificationToViewHostMsg(
268 notification.type, &param.notification_type);
269 param.id = notification.id;
270 param.includes_children = includes_children;
271 param.acc_tree = WebAccessibility(obj, includes_children);
272 if (obj.axID() == root_id) {
273 DCHECK_EQ(param.acc_tree.role, WebAccessibility::ROLE_WEB_AREA);
274 param.acc_tree.role = WebAccessibility::ROLE_ROOT_WEB_AREA;
275 }
276 notifications.push_back(param);
277
278 if (includes_children)
279 UpdateBrowserTree(param.acc_tree);
280
281 #ifndef NDEBUG
282 if (logging_) {
283 LOG(INFO) << "Accessibility update: "
284 << param.acc_tree.DebugString(true,
285 routing_id(),
286 param.notification_type);
287 }
288 #endif
289 }
290 pending_notifications_.clear();
291 Send(new ViewHostMsg_AccessibilityNotifications(routing_id(), notifications));
292 ack_pending_ = true;
293 }
294
295 void RendererAccessibility::UpdateBrowserTree(
296 const webkit_glue::WebAccessibility& renderer_node) {
297 BrowserTreeNode* browser_node = NULL;
298 base::hash_map<int32, BrowserTreeNode*>::iterator iter =
299 browser_id_map_.find(renderer_node.id);
300 if (iter != browser_id_map_.end()) {
301 browser_node = iter->second;
302 ClearBrowserTreeNode(browser_node);
303 } else {
304 DCHECK_EQ(renderer_node.role, WebAccessibility::ROLE_ROOT_WEB_AREA);
305 if (browser_root_) {
306 ClearBrowserTreeNode(browser_root_);
307 browser_id_map_.erase(browser_root_->id);
308 delete browser_root_;
309 }
310 browser_root_ = new BrowserTreeNode;
311 browser_node = browser_root_;
312 browser_node->id = renderer_node.id;
313 browser_id_map_[browser_node->id] = browser_node;
314 }
315 browser_node->children.reserve(renderer_node.children.size());
316 for (size_t i = 0; i < renderer_node.children.size(); ++i) {
317 BrowserTreeNode* browser_child_node = new BrowserTreeNode;
318 browser_child_node->id = renderer_node.children[i].id;
319 browser_id_map_[browser_child_node->id] = browser_child_node;
320 browser_node->children.push_back(browser_child_node);
321 UpdateBrowserTree(renderer_node.children[i]);
322 }
323 }
324
325 void RendererAccessibility::ClearBrowserTreeNode(
326 BrowserTreeNode* browser_node) {
327 for (size_t i = 0; i < browser_node->children.size(); ++i) {
328 browser_id_map_.erase(browser_node->children[i]->id);
329 ClearBrowserTreeNode(browser_node->children[i]);
330 delete browser_node->children[i];
331 }
332 browser_node->children.clear();
333 }
334
335 void RendererAccessibility::OnAccessibilityDoDefaultAction(int acc_obj_id) {
336 if (!WebAccessibilityObject::accessibilityEnabled())
337 return;
338
339 const WebDocument& document = GetMainDocument();
340 if (document.isNull())
341 return;
342
343 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
344 if (!obj.isValid()) {
345 #ifndef NDEBUG
346 if (logging_)
347 LOG(WARNING) << "DoDefaultAction on invalid object id " << acc_obj_id;
348 #endif
349 return;
350 }
351
352 obj.performDefaultAction();
353 }
354
355 void RendererAccessibility::OnAccessibilityNotificationsAck() {
356 DCHECK(ack_pending_);
357 ack_pending_ = false;
358 SendPendingAccessibilityNotifications();
359 }
360
361 void RendererAccessibility::OnEnableAccessibility() {
362 if (WebAccessibilityObject::accessibilityEnabled())
363 return;
364
365 WebAccessibilityObject::enableAccessibility();
366
367 const WebDocument& document = GetMainDocument();
368 if (!document.isNull()) {
369 // It's possible that the webview has already loaded a webpage without
370 // accessibility being enabled. Initialize the browser's cached
371 // accessibility tree by sending it a 'load complete' notification.
372 PostAccessibilityNotification(
373 document.accessibilityObject(),
374 WebKit::WebAccessibilityNotificationLoadComplete);
375 }
376 }
377
378 void RendererAccessibility::OnSetAccessibilityFocus(int acc_obj_id) {
379 if (!WebAccessibilityObject::accessibilityEnabled())
380 return;
381
382 const WebDocument& document = GetMainDocument();
383 if (document.isNull())
384 return;
385
386 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
387 if (!obj.isValid()) {
388 #ifndef NDEBUG
389 if (logging_) {
390 LOG(WARNING) << "OnSetAccessibilityFocus on invalid object id "
391 << acc_obj_id;
392 }
393 #endif
394 return;
395 }
396
397 WebAccessibilityObject root = document.accessibilityObject();
398 if (!root.isValid()) {
399 #ifndef NDEBUG
400 if (logging_) {
401 LOG(WARNING) << "OnSetAccessibilityFocus but root is invalid";
402 }
403 #endif
404 return;
405 }
406
407 // By convention, calling SetFocus on the root of the tree should clear the
408 // current focus. Otherwise set the focus to the new node.
409 if (acc_obj_id == root.axID())
410 render_view()->webview()->clearFocusedNode();
411 else
412 obj.setFocused(true);
413 }
414
415 bool RendererAccessibility::ShouldIncludeChildren(
416 const RendererAccessibility::Notification& notification) {
417 WebKit::WebAccessibilityNotification type = notification.type;
418 if (type == WebKit::WebAccessibilityNotificationChildrenChanged ||
419 type == WebKit::WebAccessibilityNotificationLoadComplete ||
420 type == WebKit::WebAccessibilityNotificationLiveRegionChanged) {
421 return true;
422 }
423 return false;
424 }
425
426 WebDocument RendererAccessibility::GetMainDocument() {
427 WebView* view = render_view()->webview();
428 WebFrame* main_frame = view ? view->mainFrame() : NULL;
429
430 if (main_frame)
431 return main_frame->document();
432 else
433 return WebDocument();
434 }
OLDNEW
« no previous file with comments | « content/renderer/renderer_accessibility.h ('k') | webkit/glue/webaccessibility.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698