summaryrefslogtreecommitdiffstats
path: root/content/renderer
diff options
context:
space:
mode:
authordmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-29 07:54:32 +0000
committerdmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-29 07:54:32 +0000
commit063afcb5b0c0fc3bd59da95917bed48284f6605e (patch)
tree3a83c17d34dfe7d5c56bf6a15fc4298602a53165 /content/renderer
parentf89ad23fcfd69b773a8c9a38a7be36eeea2b8a46 (diff)
downloadchromium_src-063afcb5b0c0fc3bd59da95917bed48284f6605e.zip
chromium_src-063afcb5b0c0fc3bd59da95917bed48284f6605e.tar.gz
chromium_src-063afcb5b0c0fc3bd59da95917bed48284f6605e.tar.bz2
Rewrites renderer accessibility to not use WebAccessibilityCache.
This fixes the primary underlying cause of a number of bugs where the browser's accessibility tree got out of sync with the renderer's accessibility tree, which makes the page seem not functional anymore. Splits renderer accessibility code out of render_view.cc into its own file. All code now uses the axID directly from WebCore::AccessibilityObject rather than WebAccessibilityCache, which can now be deleted. One implication of this is that the top-level node can now change its ID, so we introduce a new role ROLE_ROOT_WEB_AREA to distinguish it from an iframe's ROLE_WEB_AREA node. To solve the problem of getting out of sync, the renderer now maintains a tree of axIDs that reflects the state of the tree as of the most recent message sent to the browser. Whenever a new accessibility notification is posted, it refers to that tree to determine precisely what information needs to be sent to the browser, eliminating problems where the renderer was posting notifications about nodes that the browser didn't know about. BUG=93095,93232,92716,90787,90768 TEST=updated lots of tests Review URL: http://codereview.chromium.org/7966013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@103249 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/renderer')
-rw-r--r--content/renderer/render_view.cc263
-rw-r--r--content/renderer/render_view.h42
-rw-r--r--content/renderer/renderer_accessibility.cc434
-rw-r--r--content/renderer/renderer_accessibility.h119
4 files changed, 567 insertions, 291 deletions
diff --git a/content/renderer/render_view.cc b/content/renderer/render_view.cc
index 7218dfe..02d3cf2 100644
--- a/content/renderer/render_view.cc
+++ b/content/renderer/render_view.cc
@@ -63,6 +63,7 @@
#include "content/renderer/render_view_observer.h"
#include "content/renderer/render_view_visitor.h"
#include "content/renderer/render_widget_fullscreen_pepper.h"
+#include "content/renderer/renderer_accessibility.h"
#include "content/renderer/renderer_webapplicationcachehost_impl.h"
#include "content/renderer/renderer_webstoragenamespace_impl.h"
#include "content/renderer/speech_input_dispatcher.h"
@@ -79,7 +80,6 @@
#include "net/base/net_errors.h"
#include "net/http/http_util.h"
#include "ppapi/c/private/ppb_flash_net_connector.h"
-#include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityCache.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityObject.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebCString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDataSource.h"
@@ -138,7 +138,6 @@
#include "webkit/glue/glue_serialize.h"
#include "webkit/glue/media/video_renderer_impl.h"
#include "webkit/glue/password_form_dom_manager.h"
-#include "webkit/glue/webaccessibility.h"
#include "webkit/glue/webdropdata.h"
#include "webkit/glue/webkit_constants.h"
#include "webkit/glue/webkit_glue.h"
@@ -163,7 +162,6 @@
#include "skia/ext/skia_utils_mac.h"
#endif
-using WebKit::WebAccessibilityCache;
using WebKit::WebAccessibilityNotification;
using WebKit::WebAccessibilityObject;
using WebKit::WebApplicationCacheHost;
@@ -243,7 +241,6 @@ using webkit_glue::FormField;
using webkit_glue::PasswordForm;
using webkit_glue::PasswordFormDomManager;
using webkit_glue::ResourceFetcher;
-using webkit_glue::WebAccessibility;
//-----------------------------------------------------------------------------
@@ -274,61 +271,6 @@ static void GetRedirectChain(WebDataSource* ds, std::vector<GURL>* result) {
result->push_back(urls[i]);
}
-static bool WebAccessibilityNotificationToViewHostMsg(
- WebAccessibilityNotification notification,
- ViewHostMsg_AccEvent::Value* type) {
- switch (notification) {
- case WebKit::WebAccessibilityNotificationActiveDescendantChanged:
- *type = ViewHostMsg_AccEvent::ACTIVE_DESCENDANT_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationCheckedStateChanged:
- *type = ViewHostMsg_AccEvent::CHECK_STATE_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationChildrenChanged:
- *type = ViewHostMsg_AccEvent::CHILDREN_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationFocusedUIElementChanged:
- *type = ViewHostMsg_AccEvent::FOCUS_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationLayoutComplete:
- *type = ViewHostMsg_AccEvent::LAYOUT_COMPLETE;
- break;
- case WebKit::WebAccessibilityNotificationLiveRegionChanged:
- *type = ViewHostMsg_AccEvent::LIVE_REGION_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationLoadComplete:
- *type = ViewHostMsg_AccEvent::LOAD_COMPLETE;
- break;
- case WebKit::WebAccessibilityNotificationMenuListValueChanged:
- *type = ViewHostMsg_AccEvent::MENU_LIST_VALUE_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationRowCollapsed:
- *type = ViewHostMsg_AccEvent::ROW_COLLAPSED;
- break;
- case WebKit::WebAccessibilityNotificationRowCountChanged:
- *type = ViewHostMsg_AccEvent::ROW_COUNT_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationRowExpanded:
- *type = ViewHostMsg_AccEvent::ROW_EXPANDED;
- break;
- case WebKit::WebAccessibilityNotificationScrolledToAnchor:
- *type = ViewHostMsg_AccEvent::SCROLLED_TO_ANCHOR;
- break;
- case WebKit::WebAccessibilityNotificationSelectedChildrenChanged:
- *type = ViewHostMsg_AccEvent::SELECTED_CHILDREN_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationSelectedTextChanged:
- *type = ViewHostMsg_AccEvent::SELECTED_TEXT_CHANGED;
- break;
- case WebKit::WebAccessibilityNotificationValueChanged:
- *type = ViewHostMsg_AccEvent::VALUE_CHANGED;
- break;
- default:
- return false;
- }
- return true;
-}
-
// If |data_source| is non-null and has a NavigationState associated with it,
// the AltErrorPageResourceFetcher is reset.
static void StopAltErrorPageFetcher(WebDataSource* data_source) {
@@ -381,15 +323,13 @@ RenderView::RenderView(RenderThreadBase* render_thread,
cached_has_main_frame_horizontal_scrollbar_(false),
cached_has_main_frame_vertical_scrollbar_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(pepper_delegate_(this)),
- ALLOW_THIS_IN_INITIALIZER_LIST(accessibility_method_factory_(this)),
ALLOW_THIS_IN_INITIALIZER_LIST(cookie_jar_(this)),
geolocation_dispatcher_(NULL),
speech_input_dispatcher_(NULL),
device_orientation_dispatcher_(NULL),
- accessibility_ack_pending_(false),
- accessibility_logging_(false),
p2p_socket_dispatcher_(NULL),
devtools_agent_(NULL),
+ renderer_accessibility_(NULL),
session_storage_namespace_id_(session_storage_namespace_id),
handling_select_range_(false) {
routing_id_ = routing_id;
@@ -435,12 +375,6 @@ RenderView::RenderView(RenderThreadBase* render_thread,
host_window_ = parent_hwnd;
- const CommandLine& command_line = *CommandLine::ForCurrentProcess();
- if (command_line.HasSwitch(switches::kEnableAccessibility))
- WebAccessibilityCache::enableAccessibility();
- if (command_line.HasSwitch(switches::kEnableAccessibilityLogging))
- accessibility_logging_ = true;
-
#if defined(ENABLE_P2P_APIS)
p2p_socket_dispatcher_ = new content::P2PSocketDispatcher(this);
#endif
@@ -452,6 +386,9 @@ RenderView::RenderView(RenderThreadBase* render_thread,
devtools_agent_ = new DevToolsAgent(this);
+ renderer_accessibility_ = new RendererAccessibility(this);
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kEnableMediaStream)) {
media_stream_impl_ = new MediaStreamImpl(
RenderThread::current()->video_capture_impl_manager());
@@ -554,16 +491,6 @@ void RenderView::RemoveObserver(RenderViewObserver* observer) {
observers_.RemoveObserver(observer);
}
-bool RenderView::RendererAccessibilityNotification::ShouldIncludeChildren() {
- typedef ViewHostMsg_AccessibilityNotification_Params params;
- if (type == WebKit::WebAccessibilityNotificationChildrenChanged ||
- type == WebKit::WebAccessibilityNotificationLoadComplete ||
- type == WebKit::WebAccessibilityNotificationLiveRegionChanged) {
- return true;
- }
- return false;
-}
-
WebKit::WebView* RenderView::webview() const {
return static_cast<WebKit::WebView*>(webwidget());
}
@@ -729,12 +656,6 @@ bool RenderView::OnMessageReceived(const IPC::Message& message) {
OnSetEditCommandsForNextKeyEvent)
IPC_MESSAGE_HANDLER(ViewMsg_CustomContextMenuAction,
OnCustomContextMenuAction)
- IPC_MESSAGE_HANDLER(ViewMsg_EnableAccessibility, OnEnableAccessibility)
- IPC_MESSAGE_HANDLER(ViewMsg_SetAccessibilityFocus, OnSetAccessibilityFocus)
- IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityDoDefaultAction,
- OnAccessibilityDoDefaultAction)
- IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityNotifications_ACK,
- OnAccessibilityNotificationsAck)
IPC_MESSAGE_HANDLER(ViewMsg_AsyncOpenFile_ACK, OnAsyncFileOpened)
IPC_MESSAGE_HANDLER(ViewMsg_PpapiBrokerChannelCreated,
OnPpapiBrokerChannelCreated)
@@ -1270,13 +1191,6 @@ void RenderView::UpdateURL(WebFrame* frame) {
// If we end up reusing this WebRequest (for example, due to a #ref click),
// we don't want the transition type to persist. Just clear it.
navigation_state->set_transition_type(PageTransition::LINK);
-
- // Check if the navigation was within the same page, in which case we don't
- // want to clear the accessibility cache.
- if (accessibility_.get() && !navigation_state->was_within_same_page()) {
- accessibility_.reset();
- pending_accessibility_notifications_.clear();
- }
}
// Tell the embedding application that the title of the active page has changed
@@ -1598,43 +1512,6 @@ void RenderView::didExecuteCommand(const WebString& command_name) {
RenderThread::RecordUserMetrics(name);
}
-void RenderView::SendPendingAccessibilityNotifications() {
- if (!accessibility_.get())
- return;
-
- if (pending_accessibility_notifications_.empty())
- return;
-
- // Send all pending accessibility notifications.
- std::vector<ViewHostMsg_AccessibilityNotification_Params> notifications;
- for (size_t i = 0; i < pending_accessibility_notifications_.size(); i++) {
- RendererAccessibilityNotification& notification =
- pending_accessibility_notifications_[i];
- WebAccessibilityObject obj = accessibility_->getObjectById(notification.id);
- if (!obj.isValid())
- continue;
-
- ViewHostMsg_AccessibilityNotification_Params param;
- WebAccessibilityNotificationToViewHostMsg(
- pending_accessibility_notifications_[i].type, &param.notification_type);
- param.acc_obj = WebAccessibility(
- obj, accessibility_.get(), notification.ShouldIncludeChildren());
- notifications.push_back(param);
-
-#ifndef NDEBUG
- if (accessibility_logging_) {
- LOG(INFO) << "Accessibility update:\n"
- << param.acc_obj.DebugString(true,
- routing_id_,
- pending_accessibility_notifications_[i].type);
- }
-#endif
- }
- pending_accessibility_notifications_.clear();
- Send(new ViewHostMsg_AccessibilityNotifications(routing_id_, notifications));
- accessibility_ack_pending_ = true;
-}
-
bool RenderView::handleCurrentKeyboardEvent() {
if (edit_commands_.empty())
return false;
@@ -1858,14 +1735,6 @@ void RenderView::focusPrevious() {
void RenderView::focusedNodeChanged(const WebNode& node) {
Send(new ViewHostMsg_FocusedNodeChanged(routing_id_, IsEditableNode(node)));
- if (WebAccessibilityCache::accessibilityEnabled() && node.isNull()) {
- // TODO(ctguil): Make WebKit send this notification.
- // When focus is cleared notify accessibility that the document is focused.
- postAccessibilityNotification(
- webview()->accessibilityObject(),
- WebKit::WebAccessibilityNotificationFocusedUIElementChanged);
- }
-
FOR_EACH_OBSERVER(RenderViewObserver, observers_, FocusedNodeChanged(node));
}
@@ -1881,6 +1750,12 @@ int RenderView::historyForwardListCount() {
return history_list_length_ - historyBackListCount() - 1;
}
+void RenderView::postAccessibilityNotification(
+ const WebAccessibilityObject& obj,
+ WebAccessibilityNotification notification) {
+ renderer_accessibility_->PostAccessibilityNotification(obj, notification);
+}
+
void RenderView::didUpdateInspectorSetting(const WebString& key,
const WebString& value) {
Send(new ViewHostMsg_UpdateInspectorSetting(routing_id_,
@@ -3740,56 +3615,6 @@ void RenderView::OnMediaPlayerActionAt(const gfx::Point& location,
webview()->performMediaPlayerAction(action, location);
}
-void RenderView::OnEnableAccessibility() {
- if (WebAccessibilityCache::accessibilityEnabled())
- return;
-
- WebAccessibilityCache::enableAccessibility();
-
- if (webview()) {
- // It's possible that the webview has already loaded a webpage without
- // accessibility being enabled. Initialize the browser's cached
- // accessibility tree by sending it a 'load complete' notification.
- postAccessibilityNotification(
- webview()->accessibilityObject(),
- WebKit::WebAccessibilityNotificationLoadComplete);
- }
-}
-
-void RenderView::OnSetAccessibilityFocus(int acc_obj_id) {
- if (!accessibility_.get())
- return;
-
- WebAccessibilityObject obj = accessibility_->getObjectById(acc_obj_id);
- WebAccessibilityObject root = webview()->accessibilityObject();
- if (!obj.isValid() || !root.isValid())
- return;
-
- // By convention, calling SetFocus on the root of the tree should clear the
- // current focus. Otherwise set the focus to the new node.
- if (accessibility_->addOrGetId(obj) == accessibility_->addOrGetId(root))
- webview()->clearFocusedNode();
- else
- obj.setFocused(true);
-}
-
-void RenderView::OnAccessibilityDoDefaultAction(int acc_obj_id) {
- if (!accessibility_.get())
- return;
-
- WebAccessibilityObject obj = accessibility_->getObjectById(acc_obj_id);
- if (!obj.isValid())
- return;
-
- obj.performDefaultAction();
-}
-
-void RenderView::OnAccessibilityNotificationsAck() {
- DCHECK(accessibility_ack_pending_);
- accessibility_ack_pending_ = false;
- SendPendingAccessibilityNotifications();
-}
-
void RenderView::OnGetAllSavableResourceLinksForCurrentPage(
const GURL& page_url) {
// Prepare list to storage all savable resource links.
@@ -4099,72 +3924,6 @@ void RenderView::OnPluginImeCompositionCompleted(const string16& text,
}
#endif // OS_MACOSX
-void RenderView::postAccessibilityNotification(
- const WebAccessibilityObject& obj,
- WebAccessibilityNotification notification) {
- if (!accessibility_.get() && webview()) {
- // Create and initialize our accessibility cache
- accessibility_.reset(WebAccessibilityCache::create());
- accessibility_->initialize(webview());
-
- // Load complete should be our first notification sent. Send it manually
- // in cases where we don't get it first to avoid focus problems.
- // TODO(ctguil): Investigate if a different notification is a WebCore bug.
- if (notification != WebKit::WebAccessibilityNotificationLoadComplete) {
- postAccessibilityNotification(accessibility_->getObjectById(1000),
- WebKit::WebAccessibilityNotificationLoadComplete);
- }
- }
-
- if (!accessibility_->isCached(obj)) {
- // The browser doesn't know about objects that are not in the cache. Send a
- // children change for the first accestor that actually is in the cache.
- WebAccessibilityObject parent = obj;
- while (parent.isValid() && !accessibility_->isCached(parent))
- parent = parent.parentObject();
-
- DCHECK(parent.isValid() && accessibility_->isCached(parent));
- if (!parent.isValid())
- return;
- postAccessibilityNotification(
- parent, WebKit::WebAccessibilityNotificationChildrenChanged);
-
- // The parent's children change takes care of the child's children change.
- if (notification == WebKit::WebAccessibilityNotificationChildrenChanged)
- return;
- }
-
- // Add the accessibility object to our cache and ensure it's valid.
- RendererAccessibilityNotification acc_notification;
- acc_notification.id = accessibility_->addOrGetId(obj);
- acc_notification.type = notification;
- if (acc_notification.id < 0)
- return;
-
- ViewHostMsg_AccEvent::Value temp;
- if (!WebAccessibilityNotificationToViewHostMsg(notification, &temp))
- return;
-
- // Discard duplicate accessibility notifications.
- for (uint32 i = 0; i < pending_accessibility_notifications_.size(); i++) {
- if (pending_accessibility_notifications_[i].id == acc_notification.id &&
- pending_accessibility_notifications_[i].type == acc_notification.type) {
- return;
- }
- }
- pending_accessibility_notifications_.push_back(acc_notification);
-
- if (!accessibility_ack_pending_ && accessibility_method_factory_.empty()) {
- // When no accessibility notifications are in-flight post a task to send
- // the notifications to the browser. We use PostTask so that we can queue
- // up additional notifications.
- MessageLoop::current()->PostTask(
- FROM_HERE,
- accessibility_method_factory_.NewRunnableMethod(
- &RenderView::SendPendingAccessibilityNotifications));
- }
-}
-
void RenderView::OnSetEditCommandsForNextKeyEvent(
const EditCommands& edit_commands) {
edit_commands_ = edit_commands;
diff --git a/content/renderer/render_view.h b/content/renderer/render_view.h
index e595f20..e6be86e 100644
--- a/content/renderer/render_view.h
+++ b/content/renderer/render_view.h
@@ -32,7 +32,6 @@
#include "content/renderer/pepper_plugin_delegate_impl.h"
#include "content/renderer/render_widget.h"
#include "ipc/ipc_platform_file.h"
-#include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityNotification.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebConsoleMessage.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFileSystem.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrameClient.h"
@@ -71,6 +70,7 @@ class PrintWebViewHelper;
class RenderViewObserver;
class RenderViewVisitor;
class RenderWidgetFullscreenPepper;
+class RendererAccessibility;
class SkBitmap;
class SpeechInputDispatcher;
class WebPluginDelegateProxy;
@@ -115,8 +115,6 @@ class ResourceFetcher;
}
namespace WebKit {
-class WebAccessibilityCache;
-class WebAccessibilityObject;
class WebApplicationCacheHost;
class WebApplicationCacheHostClient;
class WebDataSource;
@@ -692,18 +690,6 @@ class RenderView : public RenderWidget,
typedef std::map<GURL, double> HostZoomLevels;
- // Identifies an accessibility notification from webkit.
- struct RendererAccessibilityNotification {
- public:
- bool ShouldIncludeChildren();
-
- // The webkit glue id of the accessibility object.
- int32 id;
-
- // The accessibility notification type.
- WebKit::WebAccessibilityNotification type;
- };
-
enum ErrorPageType {
DNS_ERROR,
HTTP_404,
@@ -756,9 +742,6 @@ class RenderView : public RenderWidget,
// Sends a message and runs a nested message loop.
bool SendAndRunNestedMessageLoop(IPC::SyncMessage* message);
- // Send queued accessibility notifications from the renderer to the browser.
- void SendPendingAccessibilityNotifications();
-
// Called when the "pinned to left/right edge" state needs to be updated.
void UpdateScrollState(WebKit::WebFrame* frame);
@@ -767,8 +750,6 @@ class RenderView : public RenderWidget,
// The documentation for these functions should be in
// render_messages_internal.h for the message that the function is handling.
- void OnAccessibilityDoDefaultAction(int acc_obj_id);
- void OnAccessibilityNotificationsAck();
void OnAllowBindings(int enabled_bindings_flags);
void OnAllowScriptToClose(bool script_can_close);
void OnAsyncFileOpened(base::PlatformFileError error_code,
@@ -825,7 +806,6 @@ class RenderView : public RenderWidget,
void OnFileChooserResponse(const std::vector<FilePath>& paths);
void OnFind(int request_id, const string16&, const WebKit::WebFindOptions&);
void OnFindReplyAck();
- void OnEnableAccessibility();
void OnGetAllSavableResourceLinksForCurrentPage(const GURL& page_url);
void OnGetSerializedHtmlDataForCurrentPageWithLocalLinks(
const std::vector<GURL>& links,
@@ -852,7 +832,6 @@ class RenderView : public RenderWidget,
bool notify_result);
void OnSelectAll();
void OnSelectRange(const gfx::Point& start, const gfx::Point& end);
- void OnSetAccessibilityFocus(int acc_obj_id);
void OnSetActive(bool active);
void OnSetAltErrorPageURL(const GURL& gurl);
void OnSetBackground(const SkBitmap& background);
@@ -1150,8 +1129,6 @@ class RenderView : public RenderWidget,
// Helper objects ------------------------------------------------------------
- ScopedRunnableMethodFactory<RenderView> accessibility_method_factory_;
-
RendererWebCookieJarImpl cookie_jar_;
// The next group of objects all implement RenderViewObserver, so are deleted
@@ -1176,26 +1153,13 @@ class RenderView : public RenderWidget,
// MediaStreamImpl attached to this view; lazily initialized.
scoped_refptr<MediaStreamImpl> media_stream_impl_;
- // Handles accessibility requests into the renderer side, as well as
- // maintains the cache and other features of the accessibility tree.
- scoped_ptr<WebKit::WebAccessibilityCache> accessibility_;
-
- // Collect renderer accessibility notifications until they are ready to be
- // sent to the browser.
- std::vector<RendererAccessibilityNotification>
- pending_accessibility_notifications_;
-
- // Set if we are waiting for a accessibility notification ack.
- bool accessibility_ack_pending_;
-
- // True if verbose logging of accessibility events is on.
- bool accessibility_logging_;
-
// Dispatches all P2P socket used by the renderer.
content::P2PSocketDispatcher* p2p_socket_dispatcher_;
DevToolsAgent* devtools_agent_;
+ RendererAccessibility* renderer_accessibility_;
+
// Misc ----------------------------------------------------------------------
// The current and pending file chooser completion objects. If the queue is
diff --git a/content/renderer/renderer_accessibility.cc b/content/renderer/renderer_accessibility.cc
new file mode 100644
index 0000000..0d20220
--- /dev/null
+++ b/content/renderer/renderer_accessibility.cc
@@ -0,0 +1,434 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "content/common/content_switches.h"
+#include "content/common/view_messages.h"
+#include "content/renderer/render_view.h"
+#include "content/renderer/renderer_accessibility.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityObject.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+#include "webkit/glue/webaccessibility.h"
+
+using WebKit::WebAccessibilityNotification;
+using WebKit::WebAccessibilityObject;
+using WebKit::WebDocument;
+using WebKit::WebFrame;
+using WebKit::WebNode;
+using WebKit::WebView;
+using webkit_glue::WebAccessibility;
+
+bool WebAccessibilityNotificationToViewHostMsg(
+ WebAccessibilityNotification notification,
+ ViewHostMsg_AccEvent::Value* type) {
+ switch (notification) {
+ case WebKit::WebAccessibilityNotificationActiveDescendantChanged:
+ *type = ViewHostMsg_AccEvent::ACTIVE_DESCENDANT_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationCheckedStateChanged:
+ *type = ViewHostMsg_AccEvent::CHECK_STATE_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationChildrenChanged:
+ *type = ViewHostMsg_AccEvent::CHILDREN_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationFocusedUIElementChanged:
+ *type = ViewHostMsg_AccEvent::FOCUS_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationLayoutComplete:
+ *type = ViewHostMsg_AccEvent::LAYOUT_COMPLETE;
+ break;
+ case WebKit::WebAccessibilityNotificationLiveRegionChanged:
+ *type = ViewHostMsg_AccEvent::LIVE_REGION_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationLoadComplete:
+ *type = ViewHostMsg_AccEvent::LOAD_COMPLETE;
+ break;
+ case WebKit::WebAccessibilityNotificationMenuListValueChanged:
+ *type = ViewHostMsg_AccEvent::MENU_LIST_VALUE_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationRowCollapsed:
+ *type = ViewHostMsg_AccEvent::ROW_COLLAPSED;
+ break;
+ case WebKit::WebAccessibilityNotificationRowCountChanged:
+ *type = ViewHostMsg_AccEvent::ROW_COUNT_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationRowExpanded:
+ *type = ViewHostMsg_AccEvent::ROW_EXPANDED;
+ break;
+ case WebKit::WebAccessibilityNotificationScrolledToAnchor:
+ *type = ViewHostMsg_AccEvent::SCROLLED_TO_ANCHOR;
+ break;
+ case WebKit::WebAccessibilityNotificationSelectedChildrenChanged:
+ *type = ViewHostMsg_AccEvent::SELECTED_CHILDREN_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationSelectedTextChanged:
+ *type = ViewHostMsg_AccEvent::SELECTED_TEXT_CHANGED;
+ break;
+ case WebKit::WebAccessibilityNotificationValueChanged:
+ *type = ViewHostMsg_AccEvent::VALUE_CHANGED;
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+ return true;
+}
+
+RendererAccessibility::RendererAccessibility(RenderView* render_view)
+ : RenderViewObserver(render_view),
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
+ browser_root_(NULL),
+ ack_pending_(false),
+ logging_(false),
+ sent_load_complete_(false) {
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kEnableAccessibility))
+ WebAccessibilityObject::enableAccessibility();
+ if (command_line.HasSwitch(switches::kEnableAccessibilityLogging))
+ logging_ = true;
+}
+
+RendererAccessibility::~RendererAccessibility() {
+}
+
+bool RendererAccessibility::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(RendererAccessibility, message)
+ IPC_MESSAGE_HANDLER(ViewMsg_EnableAccessibility, OnEnableAccessibility)
+ IPC_MESSAGE_HANDLER(ViewMsg_SetAccessibilityFocus, OnSetAccessibilityFocus)
+ IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityDoDefaultAction,
+ OnAccessibilityDoDefaultAction)
+ IPC_MESSAGE_HANDLER(ViewMsg_AccessibilityNotifications_ACK,
+ OnAccessibilityNotificationsAck)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void RendererAccessibility::FocusedNodeChanged(const WebNode& node) {
+ if (!WebAccessibilityObject::accessibilityEnabled())
+ return;
+
+ const WebDocument& document = GetMainDocument();
+ if (document.isNull())
+ return;
+
+ if (node.isNull()) {
+ // When focus is cleared, implicitly focus the document.
+ // TODO(dmazzoni): Make WebKit send this notification instead.
+ PostAccessibilityNotification(
+ document.accessibilityObject(),
+ WebKit::WebAccessibilityNotificationFocusedUIElementChanged);
+ }
+}
+
+void RendererAccessibility::DidFinishLoad(WebKit::WebFrame* frame) {
+ if (!WebAccessibilityObject::accessibilityEnabled())
+ return;
+
+ const WebDocument& document = GetMainDocument();
+ if (document.isNull())
+ return;
+
+ // Check to see if the root accessibility object has changed, to work
+ // around WebKit bugs that cause AXObjectCache to be cleared
+ // unnecessarily.
+ // TODO(dmazzoni): remove this once rdar://5794454 is fixed.
+ WebAccessibilityObject new_root = document.accessibilityObject();
+ if (!browser_root_ || new_root.axID() != browser_root_->id) {
+ PostAccessibilityNotification(
+ new_root,
+ WebKit::WebAccessibilityNotificationLoadComplete);
+ }
+}
+
+void RendererAccessibility::PostAccessibilityNotification(
+ const WebAccessibilityObject& obj,
+ WebAccessibilityNotification notification) {
+ if (!WebAccessibilityObject::accessibilityEnabled())
+ return;
+
+ const WebDocument& document = GetMainDocument();
+ if (document.isNull())
+ return;
+
+ if (notification != WebKit::WebAccessibilityNotificationLoadComplete &&
+ !sent_load_complete_) {
+ // Load complete should be our first notification sent. Send it manually
+ // in cases where we don't get it first to avoid focus problems.
+ PostAccessibilityNotification(
+ document.accessibilityObject(),
+ WebKit::WebAccessibilityNotificationLoadComplete);
+ }
+
+ if (notification == WebKit::WebAccessibilityNotificationLoadComplete)
+ sent_load_complete_ = true;
+
+ // Add the accessibility object to our cache and ensure it's valid.
+ Notification acc_notification;
+ acc_notification.id = obj.axID();
+ acc_notification.type = notification;
+
+ ViewHostMsg_AccEvent::Value temp;
+ if (!WebAccessibilityNotificationToViewHostMsg(notification, &temp))
+ return;
+
+ // Discard duplicate accessibility notifications.
+ for (uint32 i = 0; i < pending_notifications_.size(); ++i) {
+ if (pending_notifications_[i].id == acc_notification.id &&
+ pending_notifications_[i].type == acc_notification.type) {
+ return;
+ }
+ }
+ pending_notifications_.push_back(acc_notification);
+
+ if (!ack_pending_ && method_factory_.empty()) {
+ // When no accessibility notifications are in-flight post a task to send
+ // the notifications to the browser. We use PostTask so that we can queue
+ // up additional notifications.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &RendererAccessibility::SendPendingAccessibilityNotifications));
+ }
+}
+
+void RendererAccessibility::SendPendingAccessibilityNotifications() {
+ const WebDocument& document = GetMainDocument();
+ if (document.isNull())
+ return;
+
+ if (pending_notifications_.empty())
+ return;
+
+ // Send all pending accessibility notifications.
+ std::vector<ViewHostMsg_AccessibilityNotification_Params> notifications;
+ for (size_t i = 0; i < pending_notifications_.size(); ++i) {
+ Notification& notification = pending_notifications_[i];
+
+ bool includes_children = ShouldIncludeChildren(notification);
+ WebAccessibilityObject obj = document.accessibilityObjectFromID(
+ notification.id);
+
+ if (!obj.isValid()) {
+#ifndef NDEBUG
+ if (logging_)
+ LOG(WARNING) << "Got notification on invalid object id " << obj.axID();
+#endif
+ continue;
+ }
+
+ // The browser may not have this object yet, for example if we get a
+ // notification on an object that was recently added, or if we get a
+ // notification on a node before the page has loaded. Work our way
+ // up the parent chain until we find a node the browser has, or until
+ // we reach the root.
+ int root_id = document.accessibilityObject().axID();
+ while (browser_id_map_.find(obj.axID()) == browser_id_map_.end() &&
+ obj.axID() != root_id) {
+ obj = obj.parentObject();
+ includes_children = true;
+ if (notification.type ==
+ WebKit::WebAccessibilityNotificationChildrenChanged) {
+ notification.id = obj.axID();
+ }
+ }
+
+ // Another potential problem is that this notification may be on an
+ // object that is detached from the tree. Determine if this node is not a
+ // child of its parent, and if so move the notification to the parent.
+ // TODO(dmazzoni): see if this can be removed after
+ // https://bugs.webkit.org/show_bug.cgi?id=68466 is fixed.
+ if (obj.axID() != root_id) {
+ WebAccessibilityObject parent = obj.parentObject();
+ while (!parent.isNull() && parent.accessibilityIsIgnored())
+ parent = parent.parentObject();
+ if (parent.isNull()) {
+ NOTREACHED();
+ }
+ bool is_child_of_parent = false;
+ for (unsigned int i = 0; i < parent.childCount(); ++i) {
+ if (parent.childAt(i).equals(obj)) {
+ is_child_of_parent = true;
+ break;
+ }
+ }
+ if (!is_child_of_parent) {
+ obj = parent;
+ notification.id = obj.axID();
+ includes_children = true;
+ }
+ }
+
+ ViewHostMsg_AccessibilityNotification_Params param;
+ WebAccessibilityNotificationToViewHostMsg(
+ notification.type, &param.notification_type);
+ param.id = notification.id;
+ param.includes_children = includes_children;
+ param.acc_tree = WebAccessibility(obj, includes_children);
+ if (obj.axID() == root_id) {
+ DCHECK_EQ(param.acc_tree.role, WebAccessibility::ROLE_WEB_AREA);
+ param.acc_tree.role = WebAccessibility::ROLE_ROOT_WEB_AREA;
+ }
+ notifications.push_back(param);
+
+ if (includes_children)
+ UpdateBrowserTree(param.acc_tree);
+
+#ifndef NDEBUG
+ if (logging_) {
+ LOG(INFO) << "Accessibility update: "
+ << param.acc_tree.DebugString(true,
+ routing_id(),
+ param.notification_type);
+ }
+#endif
+ }
+ pending_notifications_.clear();
+ Send(new ViewHostMsg_AccessibilityNotifications(routing_id(), notifications));
+ ack_pending_ = true;
+}
+
+void RendererAccessibility::UpdateBrowserTree(
+ const webkit_glue::WebAccessibility& renderer_node) {
+ BrowserTreeNode* browser_node = NULL;
+ base::hash_map<int32, BrowserTreeNode*>::iterator iter =
+ browser_id_map_.find(renderer_node.id);
+ if (iter != browser_id_map_.end()) {
+ browser_node = iter->second;
+ ClearBrowserTreeNode(browser_node);
+ } else {
+ DCHECK_EQ(renderer_node.role, WebAccessibility::ROLE_ROOT_WEB_AREA);
+ if (browser_root_) {
+ ClearBrowserTreeNode(browser_root_);
+ browser_id_map_.erase(browser_root_->id);
+ delete browser_root_;
+ }
+ browser_root_ = new BrowserTreeNode;
+ browser_node = browser_root_;
+ browser_node->id = renderer_node.id;
+ browser_id_map_[browser_node->id] = browser_node;
+ }
+ browser_node->children.reserve(renderer_node.children.size());
+ for (size_t i = 0; i < renderer_node.children.size(); ++i) {
+ BrowserTreeNode* browser_child_node = new BrowserTreeNode;
+ browser_child_node->id = renderer_node.children[i].id;
+ browser_id_map_[browser_child_node->id] = browser_child_node;
+ browser_node->children.push_back(browser_child_node);
+ UpdateBrowserTree(renderer_node.children[i]);
+ }
+}
+
+void RendererAccessibility::ClearBrowserTreeNode(
+ BrowserTreeNode* browser_node) {
+ for (size_t i = 0; i < browser_node->children.size(); ++i) {
+ browser_id_map_.erase(browser_node->children[i]->id);
+ ClearBrowserTreeNode(browser_node->children[i]);
+ delete browser_node->children[i];
+ }
+ browser_node->children.clear();
+}
+
+void RendererAccessibility::OnAccessibilityDoDefaultAction(int acc_obj_id) {
+ if (!WebAccessibilityObject::accessibilityEnabled())
+ return;
+
+ const WebDocument& document = GetMainDocument();
+ if (document.isNull())
+ return;
+
+ WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
+ if (!obj.isValid()) {
+#ifndef NDEBUG
+ if (logging_)
+ LOG(WARNING) << "DoDefaultAction on invalid object id " << acc_obj_id;
+#endif
+ return;
+ }
+
+ obj.performDefaultAction();
+}
+
+void RendererAccessibility::OnAccessibilityNotificationsAck() {
+ DCHECK(ack_pending_);
+ ack_pending_ = false;
+ SendPendingAccessibilityNotifications();
+}
+
+void RendererAccessibility::OnEnableAccessibility() {
+ if (WebAccessibilityObject::accessibilityEnabled())
+ return;
+
+ WebAccessibilityObject::enableAccessibility();
+
+ const WebDocument& document = GetMainDocument();
+ if (!document.isNull()) {
+ // It's possible that the webview has already loaded a webpage without
+ // accessibility being enabled. Initialize the browser's cached
+ // accessibility tree by sending it a 'load complete' notification.
+ PostAccessibilityNotification(
+ document.accessibilityObject(),
+ WebKit::WebAccessibilityNotificationLoadComplete);
+ }
+}
+
+void RendererAccessibility::OnSetAccessibilityFocus(int acc_obj_id) {
+ if (!WebAccessibilityObject::accessibilityEnabled())
+ return;
+
+ const WebDocument& document = GetMainDocument();
+ if (document.isNull())
+ return;
+
+ WebAccessibilityObject obj = document.accessibilityObjectFromID(acc_obj_id);
+ if (!obj.isValid()) {
+#ifndef NDEBUG
+ if (logging_) {
+ LOG(WARNING) << "OnSetAccessibilityFocus on invalid object id "
+ << acc_obj_id;
+ }
+#endif
+ return;
+ }
+
+ WebAccessibilityObject root = document.accessibilityObject();
+ if (!root.isValid()) {
+#ifndef NDEBUG
+ if (logging_) {
+ LOG(WARNING) << "OnSetAccessibilityFocus but root is invalid";
+ }
+#endif
+ return;
+ }
+
+ // By convention, calling SetFocus on the root of the tree should clear the
+ // current focus. Otherwise set the focus to the new node.
+ if (acc_obj_id == root.axID())
+ render_view()->webview()->clearFocusedNode();
+ else
+ obj.setFocused(true);
+}
+
+bool RendererAccessibility::ShouldIncludeChildren(
+ const RendererAccessibility::Notification& notification) {
+ WebKit::WebAccessibilityNotification type = notification.type;
+ if (type == WebKit::WebAccessibilityNotificationChildrenChanged ||
+ type == WebKit::WebAccessibilityNotificationLoadComplete ||
+ type == WebKit::WebAccessibilityNotificationLiveRegionChanged) {
+ return true;
+ }
+ return false;
+}
+
+WebDocument RendererAccessibility::GetMainDocument() {
+ WebView* view = render_view()->webview();
+ WebFrame* main_frame = view ? view->mainFrame() : NULL;
+
+ if (main_frame)
+ return main_frame->document();
+ else
+ return WebDocument();
+}
diff --git a/content/renderer/renderer_accessibility.h b/content/renderer/renderer_accessibility.h
new file mode 100644
index 0000000..751ce89
--- /dev/null
+++ b/content/renderer/renderer_accessibility.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_RENDERER_RENDERER_ACCESSIBILITY_H_
+#define CONTENT_RENDERER_RENDERER_ACCESSIBILITY_H_
+#pragma once
+
+#include <vector>
+
+#include "base/hash_tables.h"
+#include "base/task.h"
+#include "content/renderer/render_view_observer.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityNotification.h"
+
+namespace WebKit {
+class WebAccessibilityObject;
+class WebDocument;
+class WebNode;
+};
+
+namespace webkit_glue {
+struct WebAccessibility;
+};
+
+// RendererAccessibility belongs to the RenderView. It's responsible for
+// sending a serialized representation of WebKit's accessibility tree from
+// the renderer to the browser and sending updates whenever it changes, and
+// handling requests from the browser to perform accessibility actions on
+// nodes in the tree (e.g., change focus, or click on a button).
+class RendererAccessibility : public RenderViewObserver {
+ public:
+ RendererAccessibility(RenderView* render_view);
+ virtual ~RendererAccessibility();
+
+ // RenderView::Observer implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message);
+ virtual void FocusedNodeChanged(const WebKit::WebNode& node);
+ virtual void DidFinishLoad(WebKit::WebFrame* frame);
+
+ // Called when an accessibility notification occurs in WebKit.
+ virtual void PostAccessibilityNotification(
+ const WebKit::WebAccessibilityObject& obj,
+ WebKit::WebAccessibilityNotification notification);
+
+ private:
+ // One accessibility notification from WebKit. These are queued up and
+ // used to send tree updates and notification messages from the
+ // renderer to the browser.
+ struct Notification {
+ public:
+ // The id of the accessibility object.
+ int32 id;
+
+ // The accessibility notification type.
+ WebKit::WebAccessibilityNotification type;
+ };
+
+ // In order to keep track of what nodes the browser knows about, we keep a
+ // representation of the browser tree - just IDs and parent/child
+ // relationships.
+ struct BrowserTreeNode {
+ BrowserTreeNode() {}
+ ~BrowserTreeNode() {}
+ int32 id;
+ std::vector<BrowserTreeNode*> children;
+ };
+
+ // Send queued notifications from the renderer to the browser.
+ void SendPendingAccessibilityNotifications();
+
+ // Update our representation of what nodes the browser has, given a
+ // tree of nodes.
+ void UpdateBrowserTree(const webkit_glue::WebAccessibility& renderer_node);
+
+ // Clear the given node and recursively delete all of its descendants
+ // from the browser tree. (Does not delete |browser_node|).
+ void ClearBrowserTreeNode(BrowserTreeNode* browser_node);
+
+ // Handlers for messages from the browser to the renderer.
+ void OnAccessibilityDoDefaultAction(int acc_obj_id);
+ void OnAccessibilityNotificationsAck();
+ void OnEnableAccessibility();
+ void OnSetAccessibilityFocus(int acc_obj_id);
+
+ // Whether or not this notification typically needs to send
+ // updates to its children, too.
+ bool ShouldIncludeChildren(const Notification& notification);
+
+ // Returns the main top-level document for this page, or NULL if there's
+ // no view or frame.
+ WebKit::WebDocument GetMainDocument();
+
+ // So we can queue up tasks to be executed later.
+ ScopedRunnableMethodFactory<RendererAccessibility> method_factory_;
+
+ // Notifications from WebKit are collected until they are ready to be
+ // sent to the browser.
+ std::vector<Notification> pending_notifications_;
+
+ // Our representation of the browser tree.
+ BrowserTreeNode* browser_root_;
+
+ // A map from IDs to nodes in the browser tree.
+ base::hash_map<int32, BrowserTreeNode*> browser_id_map_;
+
+ // Set if we are waiting for an accessibility notification ack.
+ bool ack_pending_;
+
+ // True if verbose logging of accessibility events is on.
+ bool logging_;
+
+ // True if we've sent a load complete notification for this page already.
+ bool sent_load_complete_;
+
+ DISALLOW_COPY_AND_ASSIGN(RendererAccessibility);
+};
+
+#endif // CONTENT_RENDERER_RENDERER_ACCESSIBILITY_H_