| Index: content/renderer/renderer_accessibility.cc
|
| ===================================================================
|
| --- content/renderer/renderer_accessibility.cc (revision 0)
|
| +++ content/renderer/renderer_accessibility.cc (revision 0)
|
| @@ -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, ¶m.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();
|
| +}
|
|
|
| Property changes on: content/renderer/renderer_accessibility.cc
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|