#include "base/bind.h"
#include "base/message_loop/message_loop.h"
-#include "content/renderer/accessibility/accessibility_node_serializer.h"
#include "content/renderer/accessibility/blink_ax_enum_conversion.h"
+#include "content/renderer/render_frame_impl.h"
#include "content/renderer/render_view_impl.h"
#include "third_party/WebKit/public/web/WebAXObject.h"
#include "third_party/WebKit/public/web/WebDocument.h"
-#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebInputElement.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebNode.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "ui/accessibility/ax_tree.h"
using blink::WebAXObject;
using blink::WebDocument;
-using blink::WebFrame;
using blink::WebNode;
using blink::WebPoint;
using blink::WebRect;
-using blink::WebSize;
using blink::WebView;
namespace content {
RendererAccessibilityComplete::RendererAccessibilityComplete(
- RenderViewImpl* render_view)
- : RendererAccessibility(render_view),
+ RenderFrameImpl* render_frame)
+ : RendererAccessibility(render_frame),
weak_factory_(this),
- browser_root_(NULL),
+ tree_source_(render_frame),
+ serializer_(&tree_source_),
last_scroll_offset_(gfx::Size()),
ack_pending_(false) {
WebAXObject::enableAccessibility();
}
RendererAccessibilityComplete::~RendererAccessibilityComplete() {
- if (browser_root_) {
- ClearBrowserTreeNode(browser_root_);
- browser_id_map_.erase(browser_root_->id);
- delete browser_root_;
- }
- DCHECK(browser_id_map_.empty());
}
bool RendererAccessibilityComplete::OnMessageReceived(
OnScrollToPoint)
IPC_MESSAGE_HANDLER(AccessibilityMsg_SetTextSelection,
OnSetTextSelection)
+ IPC_MESSAGE_HANDLER(AccessibilityMsg_HitTest, OnHitTest)
IPC_MESSAGE_HANDLER(AccessibilityMsg_FatalError, OnFatalError)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
}
}
-void RendererAccessibilityComplete::DidFinishLoad(blink::WebFrame* frame) {
- const WebDocument& document = GetMainDocument();
- if (document.isNull())
- return;
-
- // Check to see if the root accessibility object has changed, to work
- // around Blink bugs that cause AXObjectCache to be cleared
- // unnecessarily.
- // TODO(dmazzoni): remove this once rdar://5794454 is fixed.
- WebAXObject new_root = document.accessibilityObject();
- if (!browser_root_ || new_root.axID() != browser_root_->id)
- HandleAXEvent(new_root, ui::AX_EVENT_LAYOUT_COMPLETE);
-}
-
-
void RendererAccessibilityComplete::HandleWebAccessibilityEvent(
const blink::WebAXObject& obj, blink::WebAXEvent event) {
HandleAXEvent(obj, AXEventFromBlink(event));
return RendererAccessibilityTypeComplete;
}
-RendererAccessibilityComplete::BrowserTreeNode::BrowserTreeNode() : id(0) {}
-
-RendererAccessibilityComplete::BrowserTreeNode::~BrowserTreeNode() {}
-
void RendererAccessibilityComplete::SendPendingAccessibilityEvents() {
const WebDocument& document = GetMainDocument();
if (document.isNull())
if (pending_events_.empty())
return;
- if (render_view_->is_swapped_out())
+ if (render_frame_->is_swapped_out())
return;
ack_pending_ = true;
// Generate an event message from each Blink event.
std::vector<AccessibilityHostMsg_EventParams> event_msgs;
+ // If there's a layout complete message, we need to send location changes.
+ bool had_layout_complete_messages = false;
+
// Loop over each event and generate an updated event message.
for (size_t i = 0; i < src_events.size(); ++i) {
- AccessibilityHostMsg_EventParams& event =
- src_events[i];
+ AccessibilityHostMsg_EventParams& event = src_events[i];
+ if (event.event_type == ui::AX_EVENT_LAYOUT_COMPLETE)
+ had_layout_complete_messages = true;
+
+ WebAXObject obj = document.accessibilityObjectFromID(event.id);
- WebAXObject obj = document.accessibilityObjectFromID(
- event.id);
+ // Make sure the object still exists.
if (!obj.updateBackingStoreAndCheckValidity())
continue;
+ // Make sure it's a descendant of our root node - exceptions include the
+ // scroll area that's the parent of the main document (we ignore it), and
+ // possibly nodes attached to a different document.
+ if (!tree_source_.IsInTree(obj))
+ continue;
+
// When we get a "selected children changed" event, Blink
// doesn't also send us events for each child that changed
// selection state, so make sure we re-send that whole subtree.
if (event.event_type ==
ui::AX_EVENT_SELECTED_CHILDREN_CHANGED) {
- base::hash_map<int32, BrowserTreeNode*>::iterator iter =
- browser_id_map_.find(obj.axID());
- if (iter != browser_id_map_.end())
- ClearBrowserTreeNode(iter->second);
+ serializer_.DeleteClientSubtree(obj);
}
- // The browser may not have this object yet, for example if we get a
- // event on an object that was recently added, or if we get a
- // event 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.
- WebAXObject root_object = document.accessibilityObject();
- int root_id = root_object.axID();
- while (browser_id_map_.find(obj.axID()) == browser_id_map_.end() &&
- !obj.isDetached() &&
- obj.axID() != root_id) {
- obj = obj.parentObject();
- if (event.event_type ==
- ui::AX_EVENT_CHILDREN_CHANGED) {
- event.id = obj.axID();
- }
- }
-
- if (obj.isDetached()) {
-#ifndef NDEBUG
- LOG(WARNING) << "Got event on object that is invalid or has"
- << " invalid ancestor. Id: " << obj.axID();
-#endif
- continue;
- }
-
- // Another potential problem is that this event 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 event 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) {
- WebAXObject parent = obj.parentObject();
- while (!parent.isDetached() &&
- parent.accessibilityIsIgnored()) {
- parent = parent.parentObject();
- }
-
- if (parent.isDetached()) {
- NOTREACHED();
- continue;
- }
- 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;
- event.id = obj.axID();
- }
- }
-
- // Allow Blink to cache intermediate results since we're doing a bunch
- // of read-only queries at once.
- root_object.startCachingComputedObjectAttributesUntilTreeMutates();
-
AccessibilityHostMsg_EventParams event_msg;
event_msg.event_type = event.event_type;
event_msg.id = event.id;
- std::set<int> ids_serialized;
- SerializeChangedNodes(obj, &event_msg.nodes, &ids_serialized);
+ serializer_.SerializeChanges(obj, &event_msg.update);
event_msgs.push_back(event_msg);
-#ifndef NDEBUG
- ui::AXTree tree;
- ui::AXTreeUpdate update;
- update.nodes = event_msg.nodes;
- tree.Unserialize(update);
- VLOG(0) << "Accessibility update: \n"
- << "routing id=" << routing_id()
- << " event="
- << AccessibilityEventToString(event.event_type)
- << "\n" << tree.ToString();
-#endif
+ // For each node in the update, set the location in our map from
+ // ids to locations.
+ for (size_t i = 0; i < event_msg.update.nodes.size(); ++i) {
+ locations_[event_msg.update.nodes[i].id] =
+ event_msg.update.nodes[i].location;
+ }
+
+ VLOG(0) << "Accessibility event: " << ui::ToString(event.event_type)
+ << " on node id " << event_msg.id
+ << "\n" << event_msg.update.ToString();
}
Send(new AccessibilityHostMsg_Events(routing_id(), event_msgs));
- SendLocationChanges();
+ if (had_layout_complete_messages)
+ SendLocationChanges();
}
void RendererAccessibilityComplete::SendLocationChanges() {
- std::queue<WebAXObject> objs_to_explore;
- std::vector<BrowserTreeNode*> location_changes;
- WebAXObject root_object = GetMainDocument().accessibilityObject();
- objs_to_explore.push(root_object);
+ std::vector<AccessibilityHostMsg_LocationChangeParams> messages;
+ // Do a breadth-first explore of the whole blink AX tree.
+ base::hash_map<int, gfx::Rect> new_locations;
+ std::queue<WebAXObject> objs_to_explore;
+ objs_to_explore.push(tree_source_.GetRoot());
while (objs_to_explore.size()) {
WebAXObject obj = objs_to_explore.front();
objs_to_explore.pop();
- int id = obj.axID();
- if (browser_id_map_.find(id) != browser_id_map_.end()) {
- BrowserTreeNode* browser_node = browser_id_map_[id];
- gfx::Rect new_location = obj.boundingBoxRect();
- if (browser_node->location != new_location) {
- browser_node->location = new_location;
- location_changes.push_back(browser_node);
- }
- }
-
- for (unsigned i = 0; i < obj.childCount(); ++i)
- objs_to_explore.push(obj.childAt(i));
- }
- if (location_changes.size() == 0)
- return;
-
- std::vector<AccessibilityHostMsg_LocationChangeParams> messages;
- messages.resize(location_changes.size());
- for (size_t i = 0; i < location_changes.size(); i++) {
- messages[i].id = location_changes[i]->id;
- messages[i].new_location = location_changes[i]->location;
- }
- Send(new AccessibilityHostMsg_LocationChanges(routing_id(), messages));
-}
-
-RendererAccessibilityComplete::BrowserTreeNode*
-RendererAccessibilityComplete::CreateBrowserTreeNode() {
- return new RendererAccessibilityComplete::BrowserTreeNode();
-}
-
-void RendererAccessibilityComplete::SerializeChangedNodes(
- const blink::WebAXObject& obj,
- std::vector<ui::AXNodeData>* dst,
- std::set<int>* ids_serialized) {
- if (ids_serialized->find(obj.axID()) != ids_serialized->end())
- return;
- ids_serialized->insert(obj.axID());
-
- // This method has three responsibilities:
- // 1. Serialize |obj| into an ui::AXNodeData, and append it to
- // the end of the |dst| vector to be send to the browser process.
- // 2. Determine if |obj| has any new children that the browser doesn't
- // know about yet, and call SerializeChangedNodes recursively on those.
- // 3. Update our internal data structure that keeps track of what nodes
- // the browser knows about.
-
- // First, find the BrowserTreeNode for this id in our data structure where
- // we keep track of what accessibility objects the browser already knows
- // about. If we don't find it, then this must be the new root of the
- // accessibility tree.
- BrowserTreeNode* browser_node = NULL;
- base::hash_map<int32, BrowserTreeNode*>::iterator iter =
- browser_id_map_.find(obj.axID());
- if (iter != browser_id_map_.end()) {
- browser_node = iter->second;
- } else {
- if (browser_root_) {
- ClearBrowserTreeNode(browser_root_);
- browser_id_map_.erase(browser_root_->id);
- delete browser_root_;
- }
- browser_root_ = CreateBrowserTreeNode();
- browser_node = browser_root_;
- browser_node->id = obj.axID();
- browser_node->location = obj.boundingBoxRect();
- browser_node->parent = NULL;
- browser_id_map_[browser_node->id] = browser_node;
- }
-
- // Iterate over the ids of the children of |obj|.
- // Create a set of the child ids so we can quickly look
- // up which children are new and which ones were there before.
- // Also catch the case where a child is already in the browser tree
- // data structure with a different parent, and make sure the old parent
- // clears this node first.
- base::hash_set<int32> new_child_ids;
- const WebDocument& document = GetMainDocument();
- for (unsigned i = 0; i < obj.childCount(); i++) {
- WebAXObject child = obj.childAt(i);
- if (ShouldIncludeChildNode(obj, child)) {
- int new_child_id = child.axID();
- new_child_ids.insert(new_child_id);
-
- BrowserTreeNode* child = browser_id_map_[new_child_id];
- if (child && child->parent != browser_node) {
- // The child is being reparented. Find the Blink accessibility
- // object corresponding to the old parent, or the closest ancestor
- // still in the tree.
- BrowserTreeNode* parent = child->parent;
- WebAXObject parent_obj;
- while (parent) {
- parent_obj = document.accessibilityObjectFromID(parent->id);
-
- if (!parent_obj.isDetached())
- break;
- parent = parent->parent;
- }
- CHECK(parent);
- // Call SerializeChangedNodes recursively on the old parent,
- // so that the update that clears |child| from its old parent
- // occurs stricly before the update that adds |child| to its
- // new parent.
- SerializeChangedNodes(parent_obj, dst, ids_serialized);
- }
- }
- }
-
- // Go through the old children and delete subtrees for child
- // ids that are no longer present, and create a map from
- // id to BrowserTreeNode for the rest. It's important to delete
- // first in a separate pass so that nodes that are reparented
- // don't end up children of two different parents in the middle
- // of an update, which can lead to a double-free.
- base::hash_map<int32, BrowserTreeNode*> browser_child_id_map;
- std::vector<BrowserTreeNode*> old_children;
- old_children.swap(browser_node->children);
- for (size_t i = 0; i < old_children.size(); i++) {
- BrowserTreeNode* old_child = old_children[i];
- int old_child_id = old_child->id;
- if (new_child_ids.find(old_child_id) == new_child_ids.end()) {
- browser_id_map_.erase(old_child_id);
- ClearBrowserTreeNode(old_child);
- delete old_child;
- } else {
- browser_child_id_map[old_child_id] = old_child;
- }
- }
-
- // Serialize this node. This fills in all of the fields in
- // ui::AXNodeData except child_ids, which we handle below.
- dst->push_back(ui::AXNodeData());
- ui::AXNodeData* serialized_node = &dst->back();
- SerializeAccessibilityNode(obj, serialized_node);
- if (serialized_node->id == browser_root_->id)
- serialized_node->role = ui::AX_ROLE_ROOT_WEB_AREA;
-
- // Iterate over the children, make note of the ones that are new
- // and need to be serialized, and update the BrowserTreeNode
- // data structure to reflect the new tree.
- std::vector<WebAXObject> children_to_serialize;
- int child_count = obj.childCount();
- browser_node->children.reserve(child_count);
- for (int i = 0; i < child_count; i++) {
- WebAXObject child = obj.childAt(i);
- int child_id = child.axID();
-
- // Checks to make sure the child is valid, attached to this node,
- // and one we want to include in the tree.
- if (!ShouldIncludeChildNode(obj, child))
- continue;
-
- // No need to do anything more with children that aren't new;
- // the browser will reuse its existing object.
- if (new_child_ids.find(child_id) == new_child_ids.end())
+ // See if we had a previous location. If not, this whole subtree must
+ // be new, so don't continue to explore this branch.
+ int id = obj.axID();
+ base::hash_map<int, gfx::Rect>::iterator iter = locations_.find(id);
+ if (iter == locations_.end())
continue;
- new_child_ids.erase(child_id);
- serialized_node->child_ids.push_back(child_id);
- if (browser_child_id_map.find(child_id) != browser_child_id_map.end()) {
- BrowserTreeNode* reused_child = browser_child_id_map[child_id];
- browser_node->children.push_back(reused_child);
- } else {
- BrowserTreeNode* new_child = CreateBrowserTreeNode();
- new_child->id = child_id;
- new_child->location = obj.boundingBoxRect();
- new_child->parent = browser_node;
- browser_node->children.push_back(new_child);
- browser_id_map_[child_id] = new_child;
- children_to_serialize.push_back(child);
+ // If the location has changed, append it to the IPC message.
+ gfx::Rect new_location = obj.boundingBoxRect();
+ if (iter != locations_.end() && iter->second != new_location) {
+ AccessibilityHostMsg_LocationChangeParams message;
+ message.id = id;
+ message.new_location = new_location;
+ messages.push_back(message);
}
- }
- // Serialize all of the new children, recursively.
- for (size_t i = 0; i < children_to_serialize.size(); ++i)
- SerializeChangedNodes(children_to_serialize[i], dst, ids_serialized);
-}
+ // Save the new location.
+ new_locations[id] = new_location;
-void RendererAccessibilityComplete::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];
+ // Explore children of this object.
+ std::vector<blink::WebAXObject> children;
+ tree_source_.GetChildren(obj, &children);
+ for (size_t i = 0; i < children.size(); ++i)
+ objs_to_explore.push(children[i]);
}
- browser_node->children.clear();
+ locations_.swap(new_locations);
+
+ Send(new AccessibilityHostMsg_LocationChanges(routing_id(), messages));
}
void RendererAccessibilityComplete::OnDoDefaultAction(int acc_obj_id) {
}
}
+void RendererAccessibilityComplete::OnHitTest(gfx::Point point) {
+ const WebDocument& document = GetMainDocument();
+ if (document.isNull())
+ return;
+ WebAXObject root_obj = document.accessibilityObject();
+ if (!root_obj.updateBackingStoreAndCheckValidity())
+ return;
+
+ WebAXObject obj = root_obj.hitTest(point);
+ if (!obj.isDetached())
+ HandleAXEvent(obj, ui::AX_EVENT_HOVER);
+}
+
void RendererAccessibilityComplete::OnEventsAck() {
DCHECK(ack_pending_);
ack_pending_ = false;
// 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()->GetWebView()->clearFocusedElement();
+ render_frame_->GetRenderView()->GetWebView()->clearFocusedElement();
else
obj.setFocused(true);
}