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

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
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 return false;
David Tseng 2011/09/27 21:26:13 NOTREACHED?
dmazzoni 2011/09/28 05:28:50 Done.
75 }
76 return true;
77 }
78
79 RendererAccessibility::RendererAccessibility(RenderView* render_view)
80 : RenderViewObserver(render_view),
81 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
82 browser_root_(NULL),
83 ack_pending_(false),
84 logging_(false),
85 sent_load_complete_(false) {
86 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
87 if (command_line.HasSwitch(switches::kEnableAccessibility))
88 WebAccessibilityObject::enableAccessibility();
89 if (command_line.HasSwitch(switches::kEnableAccessibilityLogging))
90 logging_ = true;
91 }
92
93 RendererAccessibility::~RendererAccessibility() {
94 }
95
96 bool RendererAccessibility::OnMessageReceived(const IPC::Message& message) {
97 bool handled = true;
98 IPC_BEGIN_MESSAGE_MAP(RendererAccessibility, message)
99 IPC_MESSAGE_HANDLER(ViewMsg_EnableAccessibility, OnEnableAccessibility)
100 IPC_MESSAGE_HANDLER(ViewMsg_SetAccessibilityFocus, OnSetAccessibilityFocus)
101 IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityDoDefaultAction,
102 OnAccessibilityDoDefaultAction)
103 IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityNotifications_ACK,
104 OnAccessibilityNotificationsAck)
105 IPC_MESSAGE_UNHANDLED(handled = false)
106 IPC_END_MESSAGE_MAP()
107 return handled;
108 }
109
110 void RendererAccessibility::FocusedNodeChanged(const WebNode& node) {
111 if (!WebAccessibilityObject::accessibilityEnabled())
112 return;
113
114 const WebDocument& document = GetMainDocument();
115 if (document.isNull())
116 return;
David Tseng 2011/09/27 21:26:13 Should this be a DCHECK?
dmazzoni 2011/09/28 05:28:50 I don't think so; I hit this when the page was fir
117
118 if (node.isNull()) {
119 // When focus is cleared, implicitly focus the document.
120 // TODO(dmazzoni): Make WebKit send this notification instead.
121 PostAccessibilityNotification(
122 document.accessibilityObject(),
123 WebKit::WebAccessibilityNotificationFocusedUIElementChanged);
124 }
125 }
126
127 void RendererAccessibility::DidFinishLoad(WebKit::WebFrame* frame) {
128 if (!WebAccessibilityObject::accessibilityEnabled())
129 return;
130
131 const WebDocument& document = GetMainDocument();
132 if (document.isNull())
133 return;
134
135 // Check to see if the root accessibility object has changed, to work
136 // around WebKit bugs that cause AXObjectCache to be cleared
137 // unnecessarily.
138 // TODO(dmazzoni): remove this once rdar://5794454 is fixed.
139 WebAccessibilityObject new_root = document.accessibilityObject();
140 if (!browser_root_ || new_root.axID() != browser_root_->id) {
141 PostAccessibilityNotification(
142 new_root,
143 WebKit::WebAccessibilityNotificationLoadComplete);
144 }
145 }
146
147 void RendererAccessibility::PostAccessibilityNotification(
148 const WebAccessibilityObject& obj,
149 WebAccessibilityNotification notification) {
150 if (!WebAccessibilityObject::accessibilityEnabled())
151 return;
152
153 const WebDocument& document = GetMainDocument();
154 if (document.isNull())
155 return;
David Tseng 2011/09/27 21:26:13 DCHECK?
156
157 if (notification != WebKit::WebAccessibilityNotificationLoadComplete &&
158 !sent_load_complete_) {
159 // Load complete should be our first notification sent. Send it manually
160 // in cases where we don't get it first to avoid focus problems.
161 PostAccessibilityNotification(
162 document.accessibilityObject(),
163 WebKit::WebAccessibilityNotificationLoadComplete);
164 sent_load_complete_ = true;
David Tseng 2011/09/27 21:26:13 Is this necessary given the below if clause?
dmazzoni 2011/09/28 05:28:50 Done.
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++) {
David Tseng 2011/09/27 21:26:13 ++i
dmazzoni 2011/09/28 05:28:50 Fixed throughout.
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;
David Tseng 2011/09/27 21:26:13 DCHECK?
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 continue;
David Tseng 2011/09/27 21:26:13 Do we want to place some logging here? Is this eve
dmazzoni 2011/09/28 05:28:50 This does happen, but it's a WebKit bug when it do
218
219 // The browser may not have this object yet, for example if we get a
220 // notification on an object that was recently added, or if we get a
221 // notification on a node before the page has loaded. Work our way
222 // up the parent chain until we find a node the browser has, or until
223 // we reach the root.
224 int root_id = document.accessibilityObject().axID();
225 while (browser_id_map_.find(obj.axID()) == browser_id_map_.end() &&
226 obj.axID() != root_id) {
227 obj = obj.parentObject();
228 includes_children = true;
229 if (notification.type ==
230 WebKit::WebAccessibilityNotificationChildrenChanged) {
231 notification.id = obj.axID();
232 }
233 }
234
235 // Another potential problem is that this notification may be on an
236 // object that is detached from the tree. Determine if this node is not a
237 // child of its parent, and if so move the notification to the parent.
238 // TODO(dmazzoni): see if this can be removed after
239 // https://bugs.webkit.org/show_bug.cgi?id=68466 is fixed.
240 if (obj.axID() != root_id) {
241 WebAccessibilityObject parent = obj.parentObject();
242 while (!parent.isNull() && parent.accessibilityIsIgnored())
243 parent = parent.parentObject();
244 if (parent.isNull()) {
245 NOTREACHED();
246 }
247 bool is_child_of_parent = false;
248 for (unsigned int i = 0; i < parent.childCount(); i++) {
David Tseng 2011/09/27 21:26:13 ++i
249 if (parent.childAt(i).equals(obj)) {
250 is_child_of_parent = true;
251 break;
252 }
253 }
254 if (!is_child_of_parent) {
255 obj = parent;
256 notification.id = obj.axID();
257 includes_children = true;
258 }
259 }
260
261 ViewHostMsg_AccessibilityNotification_Params param;
262 WebAccessibilityNotificationToViewHostMsg(
263 notification.type, &param.notification_type);
264 param.id = notification.id;
265 param.includes_children = includes_children;
266 param.acc_tree = WebAccessibility(obj, includes_children);
267 if (obj.axID() == root_id) {
268 DCHECK_EQ(param.acc_tree.role, WebAccessibility::ROLE_WEB_AREA);
269 param.acc_tree.role = WebAccessibility::ROLE_ROOT_WEB_AREA;
270 }
271 notifications.push_back(param);
272
273 if (includes_children)
274 UpdateBrowserTree(param.acc_tree);
275
276 #ifndef NDEBUG
277 if (logging_) {
278 LOG(INFO) << "Accessibility update: "
279 << param.acc_tree.DebugString(true,
280 routing_id(),
281 param.notification_type);
282 }
283 #endif
284 }
285 pending_notifications_.clear();
286 Send(new ViewHostMsg_AccessibilityNotifications(routing_id(), notifications));
287 ack_pending_ = true;
288 }
289
290 void RendererAccessibility::UpdateBrowserTree(
291 const webkit_glue::WebAccessibility& renderer_node) {
292 BrowserTreeNode* browser_node = NULL;
293 base::hash_map<int32, BrowserTreeNode*>::iterator iter =
294 browser_id_map_.find(renderer_node.id);
295 if (iter != browser_id_map_.end()) {
296 browser_node = iter->second;
297 ClearBrowserTree(browser_node);
298 } else {
299 DCHECK_EQ(renderer_node.role, WebAccessibility::ROLE_ROOT_WEB_AREA);
300 if (browser_root_) {
301 ClearBrowserTree(browser_root_);
302 browser_id_map_.erase(browser_root_->id);
David Tseng 2011/09/27 21:26:13 optional: you could do this within ClearBrowserTre
dmazzoni 2011/09/28 05:28:50 I don't always want to delete the parameter node;
303 delete browser_root_;
304 }
305 browser_root_ = new BrowserTreeNode;
306 browser_node = browser_root_;
307 browser_node->id = renderer_node.id;
308 browser_id_map_[browser_node->id] = browser_node;
309 }
310 browser_node->children.reserve(renderer_node.children.size());
311 for (size_t i = 0; i < renderer_node.children.size(); i++) {
David Tseng 2011/09/27 21:26:13 ++i
312 BrowserTreeNode* browser_child_node = new BrowserTreeNode;
313 browser_child_node->id = renderer_node.children[i].id;
314 browser_id_map_[browser_child_node->id] = browser_child_node;
315 browser_node->children.push_back(browser_child_node);
316 UpdateBrowserTree(renderer_node.children[i]);
317 }
318 }
319
320 void RendererAccessibility::ClearBrowserTree(BrowserTreeNode* browser_node) {
321 for (size_t i = 0; i < browser_node->children.size(); i++) {
David Tseng 2011/09/27 21:26:13 ++i
322 browser_id_map_.erase(browser_node->children[i]->id);
323 ClearBrowserTree(browser_node->children[i]);
324 delete browser_node->children[i];
325 }
326 browser_node->children.clear();
327 }
328
329 void RendererAccessibility::OnAccessibilityDoDefaultAction(int acc_obj_id) {
330 if (!WebAccessibilityObject::accessibilityEnabled())
331 return;
332
333 const WebDocument& document = GetMainDocument();
334 if (document.isNull())
335 return;
David Tseng 2011/09/27 21:26:13 DCHECK?
336
337 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
338 if (!obj.isValid())
339 return;
David Tseng 2011/09/27 21:26:13 DCHECK? (out of sync renderer <-> browser).
dmazzoni 2011/09/28 05:28:50 I added logging instead; I think this can happen l
340
341 obj.performDefaultAction();
342 }
343
344 void RendererAccessibility::OnAccessibilityNotificationsAck() {
345 DCHECK(ack_pending_);
346 ack_pending_ = false;
347 SendPendingAccessibilityNotifications();
348 }
349
350 void RendererAccessibility::OnEnableAccessibility() {
351 if (WebAccessibilityObject::accessibilityEnabled())
352 return;
353
354 WebAccessibilityObject::enableAccessibility();
355
356 if (render_view()->webview()) {
David Tseng 2011/09/27 21:26:13 GetMainDocument().isNull()?
dmazzoni 2011/09/28 05:28:50 Done.
357 // It's possible that the webview has already loaded a webpage without
358 // accessibility being enabled. Initialize the browser's cached
359 // accessibility tree by sending it a 'load complete' notification.
360 PostAccessibilityNotification(
361 render_view()->webview()->accessibilityObject(),
David Tseng 2011/09/27 21:26:13 GetMainDocument().accessibilityObject()?
dmazzoni 2011/09/28 05:28:50 Done.
362 WebKit::WebAccessibilityNotificationLoadComplete);
363 sent_load_complete_ = true;
364 }
365 }
366
367 void RendererAccessibility::OnSetAccessibilityFocus(int acc_obj_id) {
368 if (!WebAccessibilityObject::accessibilityEnabled())
369 return;
370
371 const WebDocument& document = GetMainDocument();
372 if (document.isNull())
373 return;
374
375 WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
376 WebAccessibilityObject root = document.accessibilityObject();
377 if (!obj.isValid() || !root.isValid())
378 return;
379
380 // By convention, calling SetFocus on the root of the tree should clear the
381 // current focus. Otherwise set the focus to the new node.
382 if (acc_obj_id == root.axID())
383 render_view()->webview()->clearFocusedNode();
384 else
385 obj.setFocused(true);
386 }
387
388 bool RendererAccessibility::ShouldIncludeChildren(
389 const RendererAccessibility::Notification& notification) {
390 WebKit::WebAccessibilityNotification type = notification.type;
391 if (type == WebKit::WebAccessibilityNotificationChildrenChanged ||
392 type == WebKit::WebAccessibilityNotificationLoadComplete ||
393 type == WebKit::WebAccessibilityNotificationLiveRegionChanged) {
394 return true;
395 }
396 return false;
397 }
398
399 WebDocument RendererAccessibility::GetMainDocument() {
400 WebView* view = render_view()->webview();
401 WebFrame* main_frame = view ? view->mainFrame() : NULL;
402
403 if (main_frame)
404 return main_frame->document();
405 else
406 return WebDocument();
407 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698