| // 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 <stddef.h> |
| |
| #include "base/logging.h" |
| #include "build/build_config.h" |
| #include "content/browser/accessibility/browser_accessibility.h" |
| #include "content/common/accessibility_messages.h" |
| #include "ui/accessibility/ax_tree_serializer.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Search the tree recursively from |node| and return any node that has |
| // a child tree ID of |ax_tree_id|. |
| BrowserAccessibility* FindNodeWithChildTreeId(BrowserAccessibility* node, |
| int ax_tree_id) { |
| if (!node) |
| return nullptr; |
| |
| if (node->GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID) == ax_tree_id) |
| return node; |
| |
| for (unsigned int i = 0; i < node->InternalChildCount(); ++i) { |
| BrowserAccessibility* child = node->InternalGetChild(i); |
| BrowserAccessibility* result = FindNodeWithChildTreeId(child, ax_tree_id); |
| if (result) |
| return result; |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| // Map from AXTreeID to BrowserAccessibilityManager |
| using AXTreeIDMap = |
| base::hash_map<AXTreeIDRegistry::AXTreeID, BrowserAccessibilityManager*>; |
| base::LazyInstance<AXTreeIDMap> g_ax_tree_id_map = LAZY_INSTANCE_INITIALIZER; |
| |
| ui::AXTreeUpdate MakeAXTreeUpdate( |
| const ui::AXNodeData& node1, |
| const ui::AXNodeData& node2 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node3 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node4 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node5 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node6 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node7 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node8 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node9 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node10 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node11 /* = ui::AXNodeData() */, |
| const ui::AXNodeData& node12 /* = ui::AXNodeData() */) { |
| CR_DEFINE_STATIC_LOCAL(ui::AXNodeData, empty_data, ()); |
| int32_t no_id = empty_data.id; |
| |
| ui::AXTreeUpdate update; |
| update.nodes.push_back(node1); |
| if (node2.id != no_id) |
| update.nodes.push_back(node2); |
| if (node3.id != no_id) |
| update.nodes.push_back(node3); |
| if (node4.id != no_id) |
| update.nodes.push_back(node4); |
| if (node5.id != no_id) |
| update.nodes.push_back(node5); |
| if (node6.id != no_id) |
| update.nodes.push_back(node6); |
| if (node7.id != no_id) |
| update.nodes.push_back(node7); |
| if (node8.id != no_id) |
| update.nodes.push_back(node8); |
| if (node9.id != no_id) |
| update.nodes.push_back(node9); |
| if (node10.id != no_id) |
| update.nodes.push_back(node10); |
| if (node11.id != no_id) |
| update.nodes.push_back(node11); |
| if (node12.id != no_id) |
| update.nodes.push_back(node12); |
| return update; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityFactory::Create() { |
| #if defined(OS_ANDROID) && defined(USE_AURA) |
| return nullptr; |
| #else |
| return BrowserAccessibility::Create(); |
| #endif |
| } |
| |
| BrowserAccessibilityFindInPageInfo::BrowserAccessibilityFindInPageInfo() |
| : request_id(-1), |
| match_index(-1), |
| start_id(-1), |
| start_offset(0), |
| end_id(-1), |
| end_offset(-1), |
| active_request_id(-1) {} |
| |
| #if !defined(PLATFORM_HAS_NATIVE_ACCESSIBILITY_IMPL) |
| // static |
| BrowserAccessibilityManager* BrowserAccessibilityManager::Create( |
| const ui::AXTreeUpdate& initial_tree, |
| BrowserAccessibilityDelegate* delegate, |
| BrowserAccessibilityFactory* factory) { |
| return new BrowserAccessibilityManager(initial_tree, delegate, factory); |
| } |
| #endif |
| |
| // static |
| BrowserAccessibilityManager* BrowserAccessibilityManager::FromID( |
| AXTreeIDRegistry::AXTreeID ax_tree_id) { |
| AXTreeIDMap* ax_tree_id_map = g_ax_tree_id_map.Pointer(); |
| auto iter = ax_tree_id_map->find(ax_tree_id); |
| return iter == ax_tree_id_map->end() ? nullptr : iter->second; |
| } |
| |
| BrowserAccessibilityManager::BrowserAccessibilityManager( |
| BrowserAccessibilityDelegate* delegate, |
| BrowserAccessibilityFactory* factory) |
| : delegate_(delegate), |
| factory_(factory), |
| tree_(new ui::AXSerializableTree()), |
| focus_(NULL), |
| user_is_navigating_away_(false), |
| osk_state_(OSK_ALLOWED), |
| ax_tree_id_(AXTreeIDRegistry::kNoAXTreeID), |
| parent_node_id_from_parent_tree_(0) { |
| tree_->SetDelegate(this); |
| } |
| |
| BrowserAccessibilityManager::BrowserAccessibilityManager( |
| const ui::AXTreeUpdate& initial_tree, |
| BrowserAccessibilityDelegate* delegate, |
| BrowserAccessibilityFactory* factory) |
| : delegate_(delegate), |
| factory_(factory), |
| tree_(new ui::AXSerializableTree()), |
| focus_(NULL), |
| user_is_navigating_away_(false), |
| osk_state_(OSK_ALLOWED), |
| ax_tree_id_(AXTreeIDRegistry::kNoAXTreeID), |
| parent_node_id_from_parent_tree_(0) { |
| tree_->SetDelegate(this); |
| Initialize(initial_tree); |
| } |
| |
| BrowserAccessibilityManager::~BrowserAccessibilityManager() { |
| tree_.reset(NULL); |
| g_ax_tree_id_map.Get().erase(ax_tree_id_); |
| } |
| |
| void BrowserAccessibilityManager::Initialize( |
| const ui::AXTreeUpdate& initial_tree) { |
| if (!tree_->Unserialize(initial_tree)) { |
| if (delegate_) { |
| LOG(ERROR) << tree_->error(); |
| delegate_->AccessibilityFatalError(); |
| } else { |
| LOG(FATAL) << tree_->error(); |
| } |
| } |
| |
| if (!focus_) |
| SetFocus(tree_->root(), false); |
| } |
| |
| // static |
| ui::AXTreeUpdate |
| BrowserAccessibilityManager::GetEmptyDocument() { |
| ui::AXNodeData empty_document; |
| empty_document.id = 0; |
| empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA; |
| ui::AXTreeUpdate update; |
| update.nodes.push_back(empty_document); |
| return update; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetRoot() { |
| // tree_ can be null during destruction. |
| if (!tree_) |
| return nullptr; |
| |
| // tree_->root() can be null during AXTreeDelegate callbacks. |
| ui::AXNode* root = tree_->root(); |
| return root ? GetFromAXNode(root) : nullptr; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetFromAXNode( |
| const ui::AXNode* node) const { |
| if (!node) |
| return nullptr; |
| return GetFromID(node->id()); |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetFromID(int32_t id) const { |
| const auto iter = id_wrapper_map_.find(id); |
| if (iter != id_wrapper_map_.end()) |
| return iter->second; |
| |
| return nullptr; |
| } |
| |
| BrowserAccessibility* |
| BrowserAccessibilityManager::GetParentNodeFromParentTree() { |
| if (!GetRoot()) |
| return nullptr; |
| |
| int parent_tree_id = GetTreeData().parent_tree_id; |
| BrowserAccessibilityManager* parent_manager = |
| BrowserAccessibilityManager::FromID(parent_tree_id); |
| if (!parent_manager) |
| return nullptr; |
| |
| // Try to use the cached parent node from the most recent time this |
| // was called. |
| if (parent_node_id_from_parent_tree_) { |
| BrowserAccessibility* parent_node = parent_manager->GetFromID( |
| parent_node_id_from_parent_tree_); |
| if (parent_node) { |
| int parent_child_tree_id = |
| parent_node->GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID); |
| if (parent_child_tree_id == ax_tree_id_) |
| return parent_node; |
| } |
| } |
| |
| // If that fails, search for it and cache it for next time. |
| BrowserAccessibility* parent_node = FindNodeWithChildTreeId( |
| parent_manager->GetRoot(), ax_tree_id_); |
| if (parent_node) { |
| parent_node_id_from_parent_tree_ = parent_node->GetId(); |
| return parent_node; |
| } |
| |
| return nullptr; |
| } |
| |
| const ui::AXTreeData& BrowserAccessibilityManager::GetTreeData() { |
| return tree_->data(); |
| } |
| |
| void BrowserAccessibilityManager::OnWindowFocused() { |
| if (focus_) |
| NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetFromAXNode(focus_)); |
| } |
| |
| void BrowserAccessibilityManager::OnWindowBlurred() { |
| if (focus_) |
| NotifyAccessibilityEvent(ui::AX_EVENT_BLUR, GetFromAXNode(focus_)); |
| } |
| |
| void BrowserAccessibilityManager::UserIsNavigatingAway() { |
| user_is_navigating_away_ = true; |
| } |
| |
| void BrowserAccessibilityManager::UserIsReloading() { |
| user_is_navigating_away_ = true; |
| } |
| |
| void BrowserAccessibilityManager::NavigationSucceeded() { |
| user_is_navigating_away_ = false; |
| } |
| |
| void BrowserAccessibilityManager::NavigationFailed() { |
| user_is_navigating_away_ = false; |
| } |
| |
| void BrowserAccessibilityManager::GotMouseDown() { |
| if (!focus_) |
| return; |
| |
| osk_state_ = OSK_ALLOWED_WITHIN_FOCUSED_OBJECT; |
| NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetFromAXNode(focus_)); |
| } |
| |
| bool BrowserAccessibilityManager::UseRootScrollOffsetsWhenComputingBounds() { |
| return true; |
| } |
| |
| void BrowserAccessibilityManager::OnAccessibilityEvents( |
| const std::vector<AXEventNotificationDetails>& details) { |
| bool should_send_initial_focus = false; |
| |
| // Process all changes to the accessibility tree first. |
| for (uint32_t index = 0; index < details.size(); ++index) { |
| const AXEventNotificationDetails& detail = details[index]; |
| if (!tree_->Unserialize(detail.update)) { |
| if (delegate_) { |
| LOG(ERROR) << tree_->error(); |
| delegate_->AccessibilityFatalError(); |
| } else { |
| CHECK(false) << tree_->error(); |
| } |
| return; |
| } |
| |
| // Set focus to the root if it's not anywhere else. |
| if (!focus_) { |
| SetFocus(tree_->root(), false); |
| should_send_initial_focus = true; |
| } |
| } |
| |
| if (should_send_initial_focus && NativeViewHasFocus()) |
| NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetFromAXNode(focus_)); |
| |
| // Now iterate over the events again and fire the events. |
| for (uint32_t index = 0; index < details.size(); index++) { |
| const AXEventNotificationDetails& detail = details[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). |
| ui::AXNode* node = tree_->GetFromId(detail.id); |
| if (!node) |
| continue; |
| |
| ui::AXEvent event_type = detail.event_type; |
| if (event_type == ui::AX_EVENT_FOCUS || |
| event_type == ui::AX_EVENT_BLUR) { |
| 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 (!NativeViewHasFocus()) |
| continue; |
| } |
| |
| // Send the event event to the operating system. |
| NotifyAccessibilityEvent(event_type, GetFromAXNode(node)); |
| } |
| } |
| |
| void BrowserAccessibilityManager::OnLocationChanges( |
| const std::vector<AccessibilityHostMsg_LocationChangeParams>& params) { |
| for (size_t i = 0; i < params.size(); ++i) { |
| BrowserAccessibility* obj = GetFromID(params[i].id); |
| if (!obj) |
| continue; |
| ui::AXNode* node = obj->node(); |
| node->SetLocation(params[i].new_location); |
| obj->OnLocationChanged(); |
| } |
| } |
| |
| void BrowserAccessibilityManager::OnFindInPageResult( |
| int request_id, int match_index, int start_id, int start_offset, |
| int end_id, int end_offset) { |
| find_in_page_info_.request_id = request_id; |
| find_in_page_info_.match_index = match_index; |
| find_in_page_info_.start_id = start_id; |
| find_in_page_info_.start_offset = start_offset; |
| find_in_page_info_.end_id = end_id; |
| find_in_page_info_.end_offset = end_offset; |
| |
| if (find_in_page_info_.active_request_id == request_id) |
| ActivateFindInPageResult(request_id); |
| } |
| |
| void BrowserAccessibilityManager::ActivateFindInPageResult( |
| int request_id) { |
| find_in_page_info_.active_request_id = request_id; |
| if (find_in_page_info_.request_id != request_id) |
| return; |
| |
| BrowserAccessibility* node = GetFromID(find_in_page_info_.start_id); |
| if (!node) |
| return; |
| |
| // If an ancestor of this node is a leaf node, fire the notification on that. |
| BrowserAccessibility* ancestor = node->GetParent(); |
| while (ancestor && ancestor != GetRoot()) { |
| if (ancestor->PlatformIsLeaf()) |
| node = ancestor; |
| ancestor = ancestor->GetParent(); |
| } |
| |
| // The "scrolled to anchor" notification is a great way to get a |
| // screen reader to jump directly to a specific location in a document. |
| NotifyAccessibilityEvent(ui::AX_EVENT_SCROLLED_TO_ANCHOR, node); |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetActiveDescendantFocus( |
| BrowserAccessibility* root) { |
| BrowserAccessibility* node = BrowserAccessibilityManager::GetFocus(root); |
| if (!node) |
| return NULL; |
| |
| int active_descendant_id; |
| if (node->GetIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID, |
| &active_descendant_id)) { |
| BrowserAccessibility* active_descendant = |
| node->manager()->GetFromID(active_descendant_id); |
| if (active_descendant) |
| return active_descendant; |
| } |
| return node; |
| } |
| |
| bool BrowserAccessibilityManager::NativeViewHasFocus() { |
| BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager(); |
| if (delegate) |
| return delegate->AccessibilityViewHasFocus(); |
| return false; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetFocus( |
| BrowserAccessibility* root) { |
| if (!focus_) |
| return nullptr; |
| |
| if (root && !focus_->IsDescendantOf(root->node())) |
| return nullptr; |
| |
| BrowserAccessibility* obj = GetFromAXNode(focus_); |
| DCHECK(obj); |
| if (obj->HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID)) { |
| BrowserAccessibilityManager* child_manager = |
| BrowserAccessibilityManager::FromID( |
| obj->GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID)); |
| if (child_manager) |
| return child_manager->GetFocus(child_manager->GetRoot()); |
| } |
| |
| return obj; |
| } |
| |
| void BrowserAccessibilityManager::SetFocus(ui::AXNode* node, bool notify) { |
| if (focus_ != node) |
| focus_ = node; |
| |
| if (notify && node && delegate_) |
| delegate_->AccessibilitySetFocus(node->id()); |
| } |
| |
| void BrowserAccessibilityManager::SetFocus( |
| BrowserAccessibility* obj, bool notify) { |
| if (obj->node()) |
| SetFocus(obj->node(), notify); |
| } |
| |
| void BrowserAccessibilityManager::DoDefaultAction( |
| const BrowserAccessibility& node) { |
| if (delegate_) |
| delegate_->AccessibilityDoDefaultAction(node.GetId()); |
| } |
| |
| void BrowserAccessibilityManager::ScrollToMakeVisible( |
| const BrowserAccessibility& node, gfx::Rect subfocus) { |
| if (delegate_) { |
| delegate_->AccessibilityScrollToMakeVisible(node.GetId(), subfocus); |
| } |
| } |
| |
| void BrowserAccessibilityManager::ScrollToPoint( |
| const BrowserAccessibility& node, gfx::Point point) { |
| if (delegate_) { |
| delegate_->AccessibilityScrollToPoint(node.GetId(), point); |
| } |
| } |
| |
| void BrowserAccessibilityManager::SetScrollOffset( |
| const BrowserAccessibility& node, gfx::Point offset) { |
| if (delegate_) { |
| delegate_->AccessibilitySetScrollOffset(node.GetId(), offset); |
| } |
| } |
| |
| void BrowserAccessibilityManager::SetValue( |
| const BrowserAccessibility& node, |
| const base::string16& value) { |
| if (delegate_) |
| delegate_->AccessibilitySetValue(node.GetId(), value); |
| } |
| |
| void BrowserAccessibilityManager::SetTextSelection( |
| const BrowserAccessibility& node, |
| int start_offset, |
| int end_offset) { |
| if (delegate_) { |
| delegate_->AccessibilitySetSelection(node.GetId(), start_offset, |
| node.GetId(), end_offset); |
| } |
| } |
| |
| gfx::Rect BrowserAccessibilityManager::GetViewBounds() { |
| BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager(); |
| if (delegate) |
| return delegate->AccessibilityGetViewBounds(); |
| return gfx::Rect(); |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::NextInTreeOrder( |
| BrowserAccessibility* node) const { |
| if (!node) |
| return nullptr; |
| |
| if (node->PlatformChildCount()) |
| return node->PlatformGetChild(0); |
| |
| while (node) { |
| const auto sibling = node->GetNextSibling(); |
| if (sibling) |
| return sibling; |
| |
| node = node->GetParent(); |
| } |
| |
| return nullptr; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::PreviousInTreeOrder( |
| BrowserAccessibility* node) const { |
| if (!node) |
| return nullptr; |
| |
| const auto sibling = node->GetPreviousSibling(); |
| if (!sibling) |
| return node->GetParent(); |
| |
| if (sibling->PlatformChildCount()) |
| return sibling->PlatformDeepestLastChild(); |
| |
| return sibling; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::PreviousTextOnlyObject( |
| BrowserAccessibility* node) const { |
| BrowserAccessibility* previous_node = PreviousInTreeOrder(node); |
| while (previous_node && !previous_node->IsTextOnlyObject()) |
| previous_node = PreviousInTreeOrder(previous_node); |
| |
| return previous_node; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::NextTextOnlyObject( |
| BrowserAccessibility* node) const { |
| BrowserAccessibility* next_node = NextInTreeOrder(node); |
| while (next_node && !next_node->IsTextOnlyObject()) |
| next_node = NextInTreeOrder(next_node); |
| |
| return next_node; |
| } |
| |
| void BrowserAccessibilityManager::OnTreeDataChanged(ui::AXTree* tree) { |
| } |
| |
| void BrowserAccessibilityManager::OnNodeWillBeDeleted(ui::AXTree* tree, |
| ui::AXNode* node) { |
| DCHECK(node); |
| if (node == focus_ && tree_) { |
| if (node != tree_->root()) |
| SetFocus(tree_->root(), false); |
| else |
| focus_ = NULL; |
| } |
| if (id_wrapper_map_.find(node->id()) == id_wrapper_map_.end()) |
| return; |
| GetFromAXNode(node)->Destroy(); |
| id_wrapper_map_.erase(node->id()); |
| } |
| |
| void BrowserAccessibilityManager::OnSubtreeWillBeDeleted(ui::AXTree* tree, |
| ui::AXNode* node) { |
| DCHECK(node); |
| BrowserAccessibility* obj = GetFromAXNode(node); |
| if (obj) |
| obj->OnSubtreeWillBeDeleted(); |
| } |
| |
| void BrowserAccessibilityManager::OnNodeCreated(ui::AXTree* tree, |
| ui::AXNode* node) { |
| BrowserAccessibility* wrapper = factory_->Create(); |
| wrapper->Init(this, node); |
| id_wrapper_map_[node->id()] = wrapper; |
| wrapper->OnDataChanged(); |
| } |
| |
| void BrowserAccessibilityManager::OnNodeChanged(ui::AXTree* tree, |
| ui::AXNode* node) { |
| DCHECK(node); |
| GetFromAXNode(node)->OnDataChanged(); |
| } |
| |
| void BrowserAccessibilityManager::OnAtomicUpdateFinished( |
| ui::AXTree* tree, |
| bool root_changed, |
| const std::vector<ui::AXTreeDelegate::Change>& changes) { |
| bool ax_tree_id_changed = false; |
| if (GetTreeData().tree_id != -1 && GetTreeData().tree_id != ax_tree_id_) { |
| g_ax_tree_id_map.Get().erase(ax_tree_id_); |
| ax_tree_id_ = GetTreeData().tree_id; |
| g_ax_tree_id_map.Get().insert(std::make_pair(ax_tree_id_, this)); |
| ax_tree_id_changed = true; |
| } |
| |
| if (ax_tree_id_changed || root_changed) { |
| BrowserAccessibility* parent = GetParentNodeFromParentTree(); |
| if (parent) { |
| parent->OnDataChanged(); |
| parent->manager()->NotifyAccessibilityEvent( |
| ui::AX_EVENT_CHILDREN_CHANGED, parent); |
| } |
| } |
| } |
| |
| BrowserAccessibilityManager* BrowserAccessibilityManager::GetRootManager() { |
| if (!GetRoot()) |
| return nullptr; |
| int parent_tree_id = GetTreeData().parent_tree_id; |
| BrowserAccessibilityManager* parent_manager = |
| BrowserAccessibilityManager::FromID(parent_tree_id); |
| if (parent_manager) |
| return parent_manager->GetRootManager(); |
| return this; |
| } |
| |
| BrowserAccessibilityDelegate* |
| BrowserAccessibilityManager::GetDelegateFromRootManager() { |
| BrowserAccessibilityManager* root_manager = GetRootManager(); |
| if (root_manager) |
| return root_manager->delegate(); |
| return nullptr; |
| } |
| |
| ui::AXTreeUpdate |
| BrowserAccessibilityManager::SnapshotAXTreeForTesting() { |
| scoped_ptr<ui::AXTreeSource<const ui::AXNode*, |
| ui::AXNodeData, |
| ui::AXTreeData> > tree_source( |
| tree_->CreateTreeSource()); |
| ui::AXTreeSerializer<const ui::AXNode*, |
| ui::AXNodeData, |
| ui::AXTreeData> serializer(tree_source.get()); |
| ui::AXTreeUpdate update; |
| serializer.SerializeChanges(tree_->root(), &update); |
| return update; |
| } |
| |
| } // namespace content |