// 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/browser/accessibility/browser_accessibility_manager.h"
#include "base/logging.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/common/accessibility_messages.h"
namespace content {
BrowserAccessibility* BrowserAccessibilityFactory::Create() {
return BrowserAccessibility::Create();
}
#if !defined(OS_MACOSX) && \
!defined(OS_WIN) && \
!defined(TOOLKIT_GTK) && \
!defined(OS_ANDROID) \
// We have subclassess of BrowserAccessibilityManager on Mac, Linux/GTK,
// and Win. For any other platform, instantiate the base class.
// static
BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
const AccessibilityNodeData& src,
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory) {
return new BrowserAccessibilityManager(src, delegate, factory);
}
#endif
BrowserAccessibilityManager::BrowserAccessibilityManager(
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory)
: delegate_(delegate),
factory_(factory),
root_(NULL),
focus_(NULL),
osk_state_(OSK_ALLOWED) {
}
BrowserAccessibilityManager::BrowserAccessibilityManager(
const AccessibilityNodeData& src,
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory)
: delegate_(delegate),
factory_(factory),
root_(NULL),
focus_(NULL),
osk_state_(OSK_ALLOWED) {
Initialize(src);
}
BrowserAccessibilityManager::~BrowserAccessibilityManager() {
if (root_)
root_->Destroy();
}
void BrowserAccessibilityManager::Initialize(const AccessibilityNodeData src) {
std::vector<AccessibilityNodeData> nodes;
nodes.push_back(src);
if (!UpdateNodes(nodes))
return;
if (!focus_)
SetFocus(root_, false);
}
// static
AccessibilityNodeData BrowserAccessibilityManager::GetEmptyDocument() {
AccessibilityNodeData empty_document;
empty_document.id = 0;
empty_document.role = blink::WebAXRoleRootWebArea;
return empty_document;
}
BrowserAccessibility* BrowserAccessibilityManager::GetRoot() {
return root_;
}
BrowserAccessibility* BrowserAccessibilityManager::GetFromRendererID(
int32 renderer_id) {
base::hash_map<int32, BrowserAccessibility*>::iterator iter =
renderer_id_map_.find(renderer_id);
if (iter != renderer_id_map_.end())
return iter->second;
return NULL;
}
void BrowserAccessibilityManager::GotFocus(bool touch_event_context) {
if (!touch_event_context)
osk_state_ = OSK_DISALLOWED_BECAUSE_TAB_JUST_APPEARED;
if (!focus_)
return;
NotifyAccessibilityEvent(blink::WebAXEventFocus, focus_);
}
void BrowserAccessibilityManager::WasHidden() {
osk_state_ = OSK_DISALLOWED_BECAUSE_TAB_HIDDEN;
}
void BrowserAccessibilityManager::GotMouseDown() {
osk_state_ = OSK_ALLOWED_WITHIN_FOCUSED_OBJECT;
NotifyAccessibilityEvent(blink::WebAXEventFocus, focus_);
}
bool BrowserAccessibilityManager::IsOSKAllowed(const gfx::Rect& bounds) {
if (!delegate_ || !delegate_->HasFocus())
return false;
gfx::Point touch_point = delegate_->GetLastTouchEventLocation();
return bounds.Contains(touch_point);
}
bool BrowserAccessibilityManager::UseRootScrollOffsetsWhenComputingBounds() {
return true;
}
void BrowserAccessibilityManager::RemoveNode(BrowserAccessibility* node) {
if (node == focus_)
SetFocus(root_, false);
int renderer_id = node->renderer_id();
renderer_id_map_.erase(renderer_id);
}
void BrowserAccessibilityManager::OnAccessibilityEvents(
const std::vector<AccessibilityHostMsg_EventParams>& params) {
bool should_send_initial_focus = false;
// Process all changes to the accessibility tree first.
for (uint32 index = 0; index < params.size(); index++) {
const AccessibilityHostMsg_EventParams& param = params[index];
if (!UpdateNodes(param.nodes))
return;
// Set initial focus when a page is loaded.
blink::WebAXEvent event_type = param.event_type;
if (event_type == blink::WebAXEventLoadComplete) {
if (!focus_) {
SetFocus(root_, false);
should_send_initial_focus = true;
}
}
}
if (should_send_initial_focus &&
(!delegate_ || delegate_->HasFocus())) {
NotifyAccessibilityEvent(blink::WebAXEventFocus, focus_);
}
// Now iterate over the events again and fire the events.
for (uint32 index = 0; index < params.size(); index++) {
const AccessibilityHostMsg_EventParams& param = params[index];
// Find the node corresponding to the id that's the target of the
// event (which may not be the root of the update tree).
BrowserAccessibility* node = GetFromRendererID(param.id);
if (!node)
continue;
blink::WebAXEvent event_type = param.event_type;
if (event_type == blink::WebAXEventFocus ||
event_type == blink::WebAXEventBlur) {
SetFocus(node, false);
if (osk_state_ != OSK_DISALLOWED_BECAUSE_TAB_HIDDEN &&
osk_state_ != OSK_DISALLOWED_BECAUSE_TAB_JUST_APPEARED)
osk_state_ = OSK_ALLOWED;
// Don't send a native focus event if the window itself doesn't
// have focus.
if (delegate_ && !delegate_->HasFocus())
continue;
}
// Send the event event to the operating system.
NotifyAccessibilityEvent(event_type, node);
}
}
BrowserAccessibility* BrowserAccessibilityManager::GetFocus(
BrowserAccessibility* root) {
if (focus_ && (!root || focus_->IsDescendantOf(root)))
return focus_;
return NULL;
}
void BrowserAccessibilityManager::SetFocus(
BrowserAccessibility* node, bool notify) {
if (focus_ != node)
focus_ = node;
if (notify && node && delegate_)
delegate_->SetAccessibilityFocus(node->renderer_id());
}
void BrowserAccessibilityManager::SetRoot(BrowserAccessibility* node) {
root_ = node;
NotifyRootChanged();
}
void BrowserAccessibilityManager::DoDefaultAction(
const BrowserAccessibility& node) {
if (delegate_)
delegate_->AccessibilityDoDefaultAction(node.renderer_id());
}
void BrowserAccessibilityManager::ScrollToMakeVisible(
const BrowserAccessibility& node, gfx::Rect subfocus) {
if (delegate_) {
delegate_->AccessibilityScrollToMakeVisible(node.renderer_id(), subfocus);
}
}
void BrowserAccessibilityManager::ScrollToPoint(
const BrowserAccessibility& node, gfx::Point point) {
if (delegate_) {
delegate_->AccessibilityScrollToPoint(node.renderer_id(), point);
}
}
void BrowserAccessibilityManager::SetTextSelection(
const BrowserAccessibility& node, int start_offset, int end_offset) {
if (delegate_) {
delegate_->AccessibilitySetTextSelection(
node.renderer_id(), start_offset, end_offset);
}
}
gfx::Rect BrowserAccessibilityManager::GetViewBounds() {
if (delegate_)
return delegate_->GetViewBounds();
return gfx::Rect();
}
void BrowserAccessibilityManager::UpdateNodesForTesting(
const AccessibilityNodeData& node1,
const AccessibilityNodeData& node2 /* = AccessibilityNodeData() */,
const AccessibilityNodeData& node3 /* = AccessibilityNodeData() */,
const AccessibilityNodeData& node4 /* = AccessibilityNodeData() */,
const AccessibilityNodeData& node5 /* = AccessibilityNodeData() */,
const AccessibilityNodeData& node6 /* = AccessibilityNodeData() */,
const AccessibilityNodeData& node7 /* = AccessibilityNodeData() */) {
std::vector<AccessibilityNodeData> nodes;
nodes.push_back(node1);
if (node2.id != AccessibilityNodeData().id)
nodes.push_back(node2);
if (node3.id != AccessibilityNodeData().id)
nodes.push_back(node3);
if (node4.id != AccessibilityNodeData().id)
nodes.push_back(node4);
if (node5.id != AccessibilityNodeData().id)
nodes.push_back(node5);
if (node6.id != AccessibilityNodeData().id)
nodes.push_back(node6);
if (node7.id != AccessibilityNodeData().id)
nodes.push_back(node7);
UpdateNodes(nodes);
}
bool BrowserAccessibilityManager::UpdateNodes(
const std::vector<AccessibilityNodeData>& nodes) {
bool success = true;
// First, update all of the nodes in the tree.
for (size_t i = 0; i < nodes.size() && success; i++) {
if (!UpdateNode(nodes[i]))
success = false;
}
// In a second pass, call PostInitialize on each one - this must
// be called after all of each node's children are initialized too.
for (size_t i = 0; i < nodes.size() && success; i++) {
// Note: it's not a bug for nodes[i].id to not be found in the tree.
// Consider this example:
// Before:
// A
// B
// C
// D
// E
// F
// After:
// A
// B
// C
// F
// D
// In this example, F is being reparented. The renderer scans the tree
// in order. If can't update "C" to add "F" as a child, when "F" is still
// a child of "E". So it first updates "E", to remove "F" as a child.
// Later, it ends up deleting "E". So when we get here, "E" was updated as
// part of this sequence but it no longer exists in the final tree, so
// there's nothing to postinitialize.
BrowserAccessibility* instance = GetFromRendererID(nodes[i].id);
if (instance)
instance->PostInitialize();
}
if (!success) {
// A bad accessibility tree could lead to memory corruption.
// Ask the delegate to crash the renderer, or if not available,
// crash the browser.
if (delegate_)
delegate_->FatalAccessibilityTreeError();
else
CHECK(false);
}
return success;
}
BrowserAccessibility* BrowserAccessibilityManager::CreateNode(
BrowserAccessibility* parent,
int32 renderer_id,
int32 index_in_parent) {
BrowserAccessibility* node = factory_->Create();
node->InitializeTreeStructure(
this, parent, renderer_id, index_in_parent);
AddNodeToMap(node);
return node;
}
void BrowserAccessibilityManager::AddNodeToMap(BrowserAccessibility* node) {
renderer_id_map_[node->renderer_id()] = node;
}
bool BrowserAccessibilityManager::UpdateNode(const AccessibilityNodeData& src) {
// This method updates one node in the tree based on serialized data
// received from the renderer.
// Create a set of child ids in |src| for fast lookup. If a duplicate id is
// found, exit now with a fatal error before changing anything else.
std::set<int32> new_child_ids;
for (size_t i = 0; i < src.child_ids.size(); ++i) {
if (new_child_ids.find(src.child_ids[i]) != new_child_ids.end())
return false;
new_child_ids.insert(src.child_ids[i]);
}
// Look up the node by id. If it's not found, then either the root
// of the tree is being swapped, or we're out of sync with the renderer
// and this is a serious error.
BrowserAccessibility* instance = GetFromRendererID(src.id);
if (!instance) {
if (src.role != blink::WebAXRoleRootWebArea)
return false;
instance = CreateNode(NULL, src.id, 0);
}
// TODO(dmazzoni): avoid a linear scan here.
for (size_t i = 0; i < src.bool_attributes.size(); i++) {
if (src.bool_attributes[i].first ==
AccessibilityNodeData::ATTR_UPDATE_LOCATION_ONLY) {
instance->SetLocation(src.location);
return true;
}
}
// Update all of the node-specific data, like its role, state, name, etc.
instance->InitializeData(src);
//
// Update the children in three steps:
//
// 1. Iterate over the old children and delete nodes that are no longer
// in the tree.
// 2. Build up a vector of new children, reusing children that haven't
// changed (but may have been reordered) and adding new empty
// objects for new children.
// 3. Swap in the new children vector for the old one.
// Delete any previous children of this instance that are no longer
// children first. We make a deletion-only pass first to prevent a
// node that's being reparented from being the child of both its old
// parent and new parent, which could lead to a double-free.
// If a node is reparented, the renderer will always send us a fresh
// copy of the node.
const std::vector<BrowserAccessibility*>& old_children = instance->children();
for (size_t i = 0; i < old_children.size(); ++i) {
int old_id = old_children[i]->renderer_id();
if (new_child_ids.find(old_id) == new_child_ids.end())
old_children[i]->Destroy();
}
// Now build a vector of new children, reusing objects that were already
// children of this node before.
std::vector<BrowserAccessibility*> new_children;
bool success = true;
for (size_t i = 0; i < src.child_ids.size(); i++) {
int32 child_renderer_id = src.child_ids[i];
int32 index_in_parent = static_cast<int32>(i);
BrowserAccessibility* child = GetFromRendererID(child_renderer_id);
if (child) {
if (child->parent() != instance) {
// This is a serious error - nodes should never be reparented.
// If this case occurs, continue so this node isn't left in an
// inconsistent state, but return failure at the end.
success = false;
continue;
}
child->UpdateParent(instance, index_in_parent);
} else {
child = CreateNode(instance, child_renderer_id, index_in_parent);
}
new_children.push_back(child);
}
// Finally, swap in the new children vector for the old.
instance->SwapChildren(new_children);
// Handle the case where this node is the new root of the tree.
if (src.role == blink::WebAXRoleRootWebArea &&
(!root_ || root_->renderer_id() != src.id)) {
if (root_)
root_->Destroy();
if (focus_ == root_)
SetFocus(instance, false);
SetRoot(instance);
}
// Keep track of what node is focused.
if (src.role != blink::WebAXRoleRootWebArea &&
src.role != blink::WebAXRoleWebArea &&
(src.state >> blink::WebAXStateFocused & 1)) {
SetFocus(instance, false);
}
return success;
}
} // namespace content