blob: 364f88976472f7c68a01e684146e9e40fa44c1db [file] [log] [blame]
// Copyright (c) 2012 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 "content/renderer/accessibility/render_accessibility_impl.h"
#include <stddef.h>
#include <stdint.h>
#include <queue>
#include "base/bind.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/common/accessibility_messages.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/platform/WebFloatRect.h"
#include "third_party/WebKit/public/web/WebAXObject.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebInputElement.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebSettings.h"
#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "ui/accessibility/ax_node.h"
using blink::WebAXObject;
using blink::WebDocument;
using blink::WebElement;
using blink::WebFloatRect;
using blink::WebLocalFrame;
using blink::WebNode;
using blink::WebPoint;
using blink::WebRect;
using blink::WebScopedAXContext;
using blink::WebSettings;
using blink::WebView;
namespace {
// The next token to use to distinguish between ack events sent to this
// RenderAccessibilityImpl and a previous instance.
static int g_next_ack_token = 1;
}
namespace content {
// Cap the number of nodes returned in an accessibility
// tree snapshot to avoid outrageous memory or bandwidth
// usage.
const size_t kMaxSnapshotNodeCount = 5000;
// static
void RenderAccessibilityImpl::SnapshotAccessibilityTree(
RenderFrameImpl* render_frame,
AXContentTreeUpdate* response) {
TRACE_EVENT0("accessibility",
"RenderAccessibilityImpl::SnapshotAccessibilityTree");
DCHECK(render_frame);
DCHECK(response);
if (!render_frame->GetWebFrame())
return;
WebDocument document = render_frame->GetWebFrame()->GetDocument();
WebScopedAXContext context(document);
WebAXObject root = context.Root();
if (!root.UpdateLayoutAndCheckValidity())
return;
BlinkAXTreeSource tree_source(
render_frame, ui::AXMode::kNativeAPIs | ui::AXMode::kWebContents |
ui::AXMode::kInlineTextBoxes |
ui::AXMode::kScreenReader | ui::AXMode::kHTML);
tree_source.SetRoot(root);
ScopedFreezeBlinkAXTreeSource freeze(&tree_source);
BlinkAXTreeSerializer serializer(&tree_source);
serializer.set_max_node_count(kMaxSnapshotNodeCount);
serializer.SerializeChanges(context.Root(), response);
}
RenderAccessibilityImpl::RenderAccessibilityImpl(RenderFrameImpl* render_frame,
ui::AXMode mode)
: RenderFrameObserver(render_frame),
render_frame_(render_frame),
tree_source_(render_frame, mode),
serializer_(&tree_source_),
plugin_tree_source_(nullptr),
last_scroll_offset_(gfx::Size()),
ack_pending_(false),
reset_token_(0),
during_action_(false),
weak_factory_(this) {
ack_token_ = g_next_ack_token++;
WebView* web_view = render_frame_->GetRenderView()->GetWebView();
WebSettings* settings = web_view->GetSettings();
settings->SetAccessibilityEnabled(true);
#if defined(OS_ANDROID)
// Password values are only passed through on Android.
settings->SetAccessibilityPasswordValuesEnabled(true);
#endif
#if !defined(OS_ANDROID)
// Inline text boxes can be enabled globally on all except Android.
// On Android they can be requested for just a specific node.
if (mode.has_mode(ui::AXMode::kInlineTextBoxes))
settings->SetInlineTextBoxAccessibilityEnabled(true);
#endif
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 notification.
HandleAXEvent(WebAXObject::FromWebDocument(document),
ui::AX_EVENT_LAYOUT_COMPLETE);
}
}
RenderAccessibilityImpl::~RenderAccessibilityImpl() {
}
void RenderAccessibilityImpl::AccessibilityModeChanged() {
ui::AXMode new_mode = render_frame_->accessibility_mode();
if (tree_source_.accessibility_mode() == new_mode)
return;
tree_source_.SetAccessibilityMode(new_mode);
#if !defined(OS_ANDROID)
// Inline text boxes can be enabled globally on all except Android.
// On Android they can be requested for just a specific node.
RenderView* render_view = render_frame_->GetRenderView();
if (render_view) {
WebView* web_view = render_view->GetWebView();
if (web_view) {
WebSettings* settings = web_view->GetSettings();
if (settings) {
if (new_mode.has_mode(ui::AXMode::kInlineTextBoxes)) {
settings->SetInlineTextBoxAccessibilityEnabled(true);
tree_source_.GetRoot().LoadInlineTextBoxes();
} else {
settings->SetInlineTextBoxAccessibilityEnabled(false);
}
}
}
}
#endif // !defined(OS_ANDROID)
serializer_.Reset();
const WebDocument& document = GetMainDocument();
if (!document.IsNull()) {
// If there are any events in flight, |HandleAXEvent| will refuse to process
// our new event.
pending_events_.clear();
auto webax_object = WebAXObject::FromWebDocument(document);
ui::AXEvent event = webax_object.IsLoaded() ? ui::AX_EVENT_LOAD_COMPLETE
: ui::AX_EVENT_LAYOUT_COMPLETE;
HandleAXEvent(webax_object, event);
}
}
bool RenderAccessibilityImpl::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
during_action_ = true;
IPC_BEGIN_MESSAGE_MAP(RenderAccessibilityImpl, message)
IPC_MESSAGE_HANDLER(AccessibilityMsg_PerformAction, OnPerformAction)
IPC_MESSAGE_HANDLER(AccessibilityMsg_Events_ACK, OnEventsAck)
IPC_MESSAGE_HANDLER(AccessibilityMsg_HitTest, OnHitTest)
IPC_MESSAGE_HANDLER(AccessibilityMsg_Reset, OnReset)
IPC_MESSAGE_HANDLER(AccessibilityMsg_FatalError, OnFatalError)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
during_action_ = false;
return handled;
}
void RenderAccessibilityImpl::HandleWebAccessibilityEvent(
const blink::WebAXObject& obj, blink::WebAXEvent event) {
HandleAXEvent(obj, AXEventFromBlink(event));
}
void RenderAccessibilityImpl::HandleAccessibilityFindInPageResult(
int identifier,
int match_index,
const blink::WebAXObject& start_object,
int start_offset,
const blink::WebAXObject& end_object,
int end_offset) {
AccessibilityHostMsg_FindInPageResultParams params;
params.request_id = identifier;
params.match_index = match_index;
params.start_id = start_object.AxID();
params.start_offset = start_offset;
params.end_id = end_object.AxID();
params.end_offset = end_offset;
Send(new AccessibilityHostMsg_FindInPageResult(routing_id(), params));
}
void RenderAccessibilityImpl::AccessibilityFocusedNodeChanged(
const WebNode& node) {
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
if (node.IsNull()) {
// When focus is cleared, implicitly focus the document.
// TODO(dmazzoni): Make Blink send this notification instead.
HandleAXEvent(WebAXObject::FromWebDocument(document), ui::AX_EVENT_BLUR);
}
}
void RenderAccessibilityImpl::DisableAccessibility() {
RenderView* render_view = render_frame_->GetRenderView();
if (!render_view)
return;
WebView* web_view = render_view->GetWebView();
if (!web_view)
return;
WebSettings* settings = web_view->GetSettings();
if (!settings)
return;
settings->SetAccessibilityEnabled(false);
}
void RenderAccessibilityImpl::HandleAXEvent(
const blink::WebAXObject& obj, ui::AXEvent event) {
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
if (document.GetFrame()) {
gfx::Size scroll_offset = document.GetFrame()->GetScrollOffset();
if (scroll_offset != last_scroll_offset_) {
// Make sure the browser is always aware of the scroll position of
// the root document element by posting a generic notification that
// will update it.
// TODO(dmazzoni): remove this as soon as
// https://bugs.webkit.org/show_bug.cgi?id=73460 is fixed.
last_scroll_offset_ = scroll_offset;
auto webax_object = WebAXObject::FromWebDocument(document);
if (!obj.Equals(webax_object)) {
HandleAXEvent(webax_object, ui::AX_EVENT_LAYOUT_COMPLETE);
}
}
}
#if defined(OS_ANDROID)
// Force the newly focused node to be re-serialized so we include its
// inline text boxes.
if (event == ui::AX_EVENT_FOCUS)
serializer_.DeleteClientSubtree(obj);
#endif
// If some cell IDs have been added or removed, we need to update the whole
// table.
if (obj.Role() == blink::kWebAXRoleRow &&
event == ui::AX_EVENT_CHILDREN_CHANGED) {
WebAXObject table_like_object = obj.ParentObject();
if (!table_like_object.IsDetached()) {
serializer_.DeleteClientSubtree(table_like_object);
HandleAXEvent(table_like_object, ui::AX_EVENT_CHILDREN_CHANGED);
}
}
// Add the accessibility object to our cache and ensure it's valid.
AccessibilityHostMsg_EventParams acc_event;
acc_event.id = obj.AxID();
acc_event.event_type = event;
if (blink::WebUserGestureIndicator::IsProcessingUserGesture())
acc_event.event_from = ui::AX_EVENT_FROM_USER;
else if (during_action_)
acc_event.event_from = ui::AX_EVENT_FROM_ACTION;
else
acc_event.event_from = ui::AX_EVENT_FROM_PAGE;
// Discard duplicate accessibility events.
for (uint32_t i = 0; i < pending_events_.size(); ++i) {
if (pending_events_[i].id == acc_event.id &&
pending_events_[i].event_type == acc_event.event_type) {
return;
}
}
pending_events_.push_back(acc_event);
if (!ack_pending_ && !weak_factory_.HasWeakPtrs()) {
// When no accessibility events are in-flight post a task to send
// the events to the browser. We use PostTask so that we can queue
// up additional events.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&RenderAccessibilityImpl::SendPendingAccessibilityEvents,
weak_factory_.GetWeakPtr()));
}
}
int RenderAccessibilityImpl::GenerateAXID() {
WebAXObject root = tree_source_.GetRoot();
return root.GenerateAXID();
}
void RenderAccessibilityImpl::SetPluginTreeSource(
RenderAccessibilityImpl::PluginAXTreeSource* plugin_tree_source) {
plugin_tree_source_ = plugin_tree_source;
plugin_serializer_.reset(new PluginAXTreeSerializer(plugin_tree_source_));
OnPluginRootNodeUpdated();
}
void RenderAccessibilityImpl::OnPluginRootNodeUpdated() {
// Search the accessibility tree for an EMBED element and post a
// children changed notification on it to force it to update the
// plugin accessibility tree.
ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
WebAXObject root = tree_source_.GetRoot();
if (!root.UpdateLayoutAndCheckValidity())
return;
std::queue<WebAXObject> objs_to_explore;
objs_to_explore.push(root);
while (objs_to_explore.size()) {
WebAXObject obj = objs_to_explore.front();
objs_to_explore.pop();
WebNode node = obj.GetNode();
if (!node.IsNull() && node.IsElementNode()) {
WebElement element = node.To<WebElement>();
if (element.HasHTMLTagName("embed")) {
HandleAXEvent(obj, ui::AX_EVENT_CHILDREN_CHANGED);
break;
}
}
// 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]);
}
}
WebDocument RenderAccessibilityImpl::GetMainDocument() {
if (render_frame_ && render_frame_->GetWebFrame())
return render_frame_->GetWebFrame()->GetDocument();
return WebDocument();
}
void RenderAccessibilityImpl::SendPendingAccessibilityEvents() {
TRACE_EVENT0("accessibility",
"RenderAccessibilityImpl::SendPendingAccessibilityEvents");
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
if (pending_events_.empty())
return;
ack_pending_ = true;
// Make a copy of the events, because it's possible that
// actions inside this loop will cause more events to be
// queued up.
std::vector<AccessibilityHostMsg_EventParams> src_events = pending_events_;
pending_events_.clear();
// 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];
if (event.event_type == ui::AX_EVENT_LAYOUT_COMPLETE)
had_layout_complete_messages = true;
auto obj = WebAXObject::FromWebDocumentByID(document, event.id);
// Make sure the object still exists.
if (!obj.UpdateLayoutAndCheckValidity())
continue;
// If it's ignored, find the first ancestor that's not ignored.
while (!obj.IsDetached() && obj.AccessibilityIsIgnored())
obj = obj.ParentObject();
ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
// 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;
AccessibilityHostMsg_EventParams event_msg;
event_msg.event_type = event.event_type;
event_msg.id = event.id;
event_msg.event_from = event.event_from;
if (!serializer_.SerializeChanges(obj, &event_msg.update)) {
VLOG(1) << "Failed to serialize one accessibility event.";
continue;
}
if (plugin_tree_source_)
AddPluginTreeToUpdate(&event_msg.update);
event_msgs.push_back(event_msg);
// 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) {
ui::AXNodeData& src = event_msg.update.nodes[i];
ui::AXRelativeBounds& dst = locations_[event_msg.update.nodes[i].id];
dst.offset_container_id = src.offset_container_id;
dst.bounds = src.location;
dst.transform.reset(nullptr);
if (src.transform)
dst.transform.reset(new gfx::Transform(*src.transform));
}
VLOG(1) << "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, reset_token_,
ack_token_));
reset_token_ = 0;
if (had_layout_complete_messages)
SendLocationChanges();
}
void RenderAccessibilityImpl::SendLocationChanges() {
TRACE_EVENT0("accessibility", "RenderAccessibilityImpl::SendLocationChanges");
std::vector<AccessibilityHostMsg_LocationChangeParams> messages;
// Update layout on the root of the tree.
ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
WebAXObject root = tree_source_.GetRoot();
if (!root.UpdateLayoutAndCheckValidity())
return;
// Do a breadth-first explore of the whole blink AX tree.
base::hash_map<int, ui::AXRelativeBounds> new_locations;
std::queue<WebAXObject> objs_to_explore;
objs_to_explore.push(root);
while (objs_to_explore.size()) {
WebAXObject obj = objs_to_explore.front();
objs_to_explore.pop();
// 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();
auto iter = locations_.find(id);
if (iter == locations_.end())
continue;
// If the location has changed, append it to the IPC message.
WebAXObject offset_container;
WebFloatRect bounds_in_container;
SkMatrix44 container_transform;
obj.GetRelativeBounds(offset_container, bounds_in_container,
container_transform);
ui::AXRelativeBounds new_location;
new_location.offset_container_id = offset_container.AxID();
new_location.bounds = bounds_in_container;
if (!container_transform.isIdentity())
new_location.transform = base::WrapUnique(
new gfx::Transform(container_transform));
if (iter->second != new_location) {
AccessibilityHostMsg_LocationChangeParams message;
message.id = id;
message.new_location = new_location;
messages.push_back(message);
}
// Save the new location.
new_locations[id] = new_location;
// 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]);
}
locations_.swap(new_locations);
Send(new AccessibilityHostMsg_LocationChanges(routing_id(), messages));
}
void RenderAccessibilityImpl::OnPerformAction(
const ui::AXActionData& data) {
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
auto root = WebAXObject::FromWebDocument(document);
if (!root.UpdateLayoutAndCheckValidity())
return;
auto target = WebAXObject::FromWebDocumentByID(document, data.target_node_id);
auto anchor = WebAXObject::FromWebDocumentByID(document, data.anchor_node_id);
auto focus = WebAXObject::FromWebDocumentByID(document, data.focus_node_id);
switch (data.action) {
case ui::AX_ACTION_BLUR:
target.SetFocused(false);
break;
case ui::AX_ACTION_DECREMENT:
target.Decrement();
break;
case ui::AX_ACTION_DO_DEFAULT:
target.PerformDefaultAction();
break;
case ui::AX_ACTION_GET_IMAGE_DATA:
OnGetImageData(target, data.target_rect.size());
break;
case ui::AX_ACTION_HIT_TEST:
DCHECK(data.hit_test_event_to_fire != ui::AX_EVENT_NONE);
OnHitTest(data.target_point, data.hit_test_event_to_fire);
break;
case ui::AX_ACTION_INCREMENT:
target.Increment();
break;
case ui::AX_ACTION_SCROLL_TO_MAKE_VISIBLE:
target.ScrollToMakeVisibleWithSubFocus(
WebRect(data.target_rect.x(), data.target_rect.y(),
data.target_rect.width(), data.target_rect.height()));
break;
case ui::AX_ACTION_SCROLL_TO_POINT:
target.ScrollToGlobalPoint(
WebPoint(data.target_point.x(), data.target_point.y()));
break;
case ui::AX_ACTION_SET_ACCESSIBILITY_FOCUS:
OnSetAccessibilityFocus(target);
break;
case ui::AX_ACTION_FOCUS:
// By convention, calling SetFocus on the root of the tree should
// clear the current focus. Otherwise set the focus to the new node.
if (data.target_node_id == root.AxID())
render_frame_->GetRenderView()->GetWebView()->ClearFocusedElement();
else
target.SetFocused(true);
break;
case ui::AX_ACTION_SET_SCROLL_OFFSET:
target.SetScrollOffset(
WebPoint(data.target_point.x(), data.target_point.y()));
break;
case ui::AX_ACTION_SET_SELECTION:
anchor.SetSelection(anchor, data.anchor_offset, focus, data.focus_offset);
HandleAXEvent(root, ui::AX_EVENT_LAYOUT_COMPLETE);
break;
case ui::AX_ACTION_SET_SEQUENTIAL_FOCUS_NAVIGATION_STARTING_POINT:
target.SetSequentialFocusNavigationStartingPoint();
break;
case ui::AX_ACTION_SET_VALUE:
target.SetValue(blink::WebString::FromUTF16(data.value));
HandleAXEvent(target, ui::AX_EVENT_VALUE_CHANGED);
break;
case ui::AX_ACTION_SHOW_CONTEXT_MENU:
target.ShowContextMenu();
break;
case ui::AX_ACTION_CUSTOM_ACTION:
case ui::AX_ACTION_REPLACE_SELECTED_TEXT:
case ui::AX_ACTION_NONE:
NOTREACHED();
break;
}
}
void RenderAccessibilityImpl::OnEventsAck(int ack_token) {
// Ignore acks intended for a different or previous instance.
if (ack_token_ != ack_token)
return;
DCHECK(ack_pending_);
ack_pending_ = false;
SendPendingAccessibilityEvents();
}
void RenderAccessibilityImpl::OnFatalError() {
CHECK(false) << "Invalid accessibility tree.";
}
void RenderAccessibilityImpl::OnHitTest(const gfx::Point& point,
ui::AXEvent event_to_fire) {
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
auto root_obj = WebAXObject::FromWebDocument(document);
if (!root_obj.UpdateLayoutAndCheckValidity())
return;
WebAXObject obj = root_obj.HitTest(point);
if (obj.IsDetached())
return;
// If the object that was hit has a child frame, we have to send a
// message back to the browser to do the hit test in the child frame,
// recursively.
AXContentNodeData data;
ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
tree_source_.SerializeNode(obj, &data);
if (data.HasContentIntAttribute(AX_CONTENT_ATTR_CHILD_ROUTING_ID) ||
data.HasContentIntAttribute(
AX_CONTENT_ATTR_CHILD_BROWSER_PLUGIN_INSTANCE_ID)) {
Send(new AccessibilityHostMsg_ChildFrameHitTestResult(
routing_id(), point, obj.AxID(), event_to_fire));
return;
}
// Otherwise, send an event on the node that was hit.
HandleAXEvent(obj, event_to_fire);
}
void RenderAccessibilityImpl::OnSetAccessibilityFocus(
const blink::WebAXObject& obj) {
ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
if (tree_source_.accessibility_focus_id() == obj.AxID())
return;
tree_source_.set_accessibility_focus_id(obj.AxID());
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
// This object may not be a leaf node. Force the whole subtree to be
// re-serialized.
serializer_.DeleteClientSubtree(obj);
// Explicitly send a tree change update event now.
HandleAXEvent(obj, ui::AX_EVENT_TREE_CHANGED);
}
void RenderAccessibilityImpl::OnGetImageData(
const blink::WebAXObject& obj, const gfx::Size& max_size) {
ScopedFreezeBlinkAXTreeSource freeze(&tree_source_);
if (tree_source_.image_data_node_id() == obj.AxID())
return;
tree_source_.set_image_data_node_id(obj.AxID());
tree_source_.set_max_image_data_size(max_size);
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
serializer_.DeleteClientSubtree(obj);
HandleAXEvent(obj, ui::AX_EVENT_IMAGE_FRAME_UPDATED);
}
void RenderAccessibilityImpl::OnReset(int reset_token) {
reset_token_ = reset_token;
serializer_.Reset();
pending_events_.clear();
const WebDocument& document = GetMainDocument();
if (!document.IsNull()) {
// Tree-only mode gets used by the automation extension API which requires a
// load complete event to invoke listener callbacks.
auto webax_object = WebAXObject::FromWebDocument(document);
ui::AXEvent evt = webax_object.IsLoaded() ? ui::AX_EVENT_LOAD_COMPLETE
: ui::AX_EVENT_LAYOUT_COMPLETE;
HandleAXEvent(webax_object, evt);
}
}
void RenderAccessibilityImpl::OnDestruct() {
delete this;
}
void RenderAccessibilityImpl::AddPluginTreeToUpdate(
AXContentTreeUpdate* update) {
for (size_t i = 0; i < update->nodes.size(); ++i) {
if (update->nodes[i].role == ui::AX_ROLE_EMBEDDED_OBJECT) {
const ui::AXNode* root = plugin_tree_source_->GetRoot();
update->nodes[i].child_ids.push_back(root->id());
ui::AXTreeUpdate plugin_update;
plugin_serializer_->SerializeChanges(root, &plugin_update);
// We have to copy the updated nodes using a loop because we're
// converting from a generic ui::AXNodeData to a vector of its
// content-specific subclass AXContentNodeData.
size_t old_count = update->nodes.size();
size_t new_count = plugin_update.nodes.size();
update->nodes.resize(old_count + new_count);
for (size_t i = 0; i < new_count; ++i)
update->nodes[old_count + i] = plugin_update.nodes[i];
break;
}
}
}
void RenderAccessibilityImpl::ScrollPlugin(int id_to_make_visible) {
// Plugin content doesn't scroll itself, so when we're requested to
// scroll to make a particular plugin node visible, get the
// coordinates of the target plugin node and then tell the document
// node to scroll to those coordinates.
//
// Note that calling scrollToMakeVisibleWithSubFocus() is preferable to
// telling the document to scroll to a specific coordinate because it will
// first compute whether that rectangle is visible and do nothing if it is.
// If it's not visible, it will automatically center it.
DCHECK(plugin_tree_source_);
ui::AXNodeData root_data = plugin_tree_source_->GetRoot()->data();
ui::AXNodeData target_data =
plugin_tree_source_->GetFromId(id_to_make_visible)->data();
gfx::RectF bounds = target_data.location;
if (root_data.transform)
root_data.transform->TransformRect(&bounds);
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
WebAXObject::FromWebDocument(document).ScrollToMakeVisibleWithSubFocus(
WebRect(bounds.x(), bounds.y(), bounds.width(), bounds.height()));
}
} // namespace content