| // Copyright 2014 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 { |
| |
| 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() */) { |
| CR_DEFINE_STATIC_LOCAL(ui::AXNodeData, empty_data, ()); |
| int32 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); |
| return update; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityFactory::Create() { |
| return BrowserAccessibility::Create(); |
| } |
| |
| #if !defined(OS_MACOSX) && \ |
| !defined(OS_WIN) && \ |
| !defined(OS_ANDROID) \ |
| // We have subclassess of BrowserAccessibilityManager on Mac, Win, and Android. |
| // These are the default implementations of these functions |
| |
| // static |
| BrowserAccessibilityManager* BrowserAccessibilityManager::Create( |
| const ui::AXTreeUpdate& initial_tree, |
| BrowserAccessibilityDelegate* delegate, |
| BrowserAccessibilityFactory* factory) { |
| return new BrowserAccessibilityManager(initial_tree, delegate, factory); |
| } |
| |
| // 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; |
| } |
| #endif |
| |
| BrowserAccessibilityManager::BrowserAccessibilityManager( |
| BrowserAccessibilityDelegate* delegate, |
| BrowserAccessibilityFactory* factory) |
| : delegate_(delegate), |
| factory_(factory), |
| tree_(new ui::AXTree()), |
| focus_(NULL), |
| osk_state_(OSK_ALLOWED) { |
| tree_->SetDelegate(this); |
| } |
| |
| BrowserAccessibilityManager::BrowserAccessibilityManager( |
| const ui::AXTreeUpdate& initial_tree, |
| BrowserAccessibilityDelegate* delegate, |
| BrowserAccessibilityFactory* factory) |
| : delegate_(delegate), |
| factory_(factory), |
| tree_(new ui::AXTree()), |
| focus_(NULL), |
| osk_state_(OSK_ALLOWED) { |
| tree_->SetDelegate(this); |
| Initialize(initial_tree); |
| } |
| |
| BrowserAccessibilityManager::~BrowserAccessibilityManager() { |
| tree_.reset(NULL); |
| } |
| |
| 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_->GetRoot(), false); |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetRoot() { |
| return GetFromAXNode(tree_->GetRoot()); |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetFromAXNode( |
| ui::AXNode* node) { |
| return GetFromID(node->id()); |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetFromID(int32 id) { |
| base::hash_map<int32, BrowserAccessibility*>::iterator iter = |
| id_wrapper_map_.find(id); |
| if (iter != id_wrapper_map_.end()) |
| return iter->second; |
| return NULL; |
| } |
| |
| 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::OnNavigation(bool is_reload) { |
| // Exit if we don't even have the first document loaded yet. |
| if (GetRoot()->GetId() == 0) |
| return; |
| |
| LOG(ERROR) << "AX: BrowserAccessibilityManager::OnNavigation " << is_reload; |
| // Create an update that replaces the current tree with an empty document |
| // (which should be in the "busy" state by default) and apply it. |
| ui::AXTreeUpdate update = GetEmptyDocument(); |
| |
| LOG(ERROR) << "AX: OnNavigation empty doc update state: " |
| << update.nodes[0].state; |
| |
| update.nodes[0].id = GetRoot()->GetId(); |
| |
| LOG(ERROR) << "AX: State before unserializing the empty doc: " |
| << GetRoot()->GetState(); |
| LOG(ERROR) << "AX: Root id before unserializing the empty doc: " |
| << GetRoot()->GetId(); |
| LOG(ERROR) << "AX: Root children before: " |
| << GetRoot()->PlatformChildCount(); |
| |
| LOG(ERROR) << "AX: State of first node in update: " |
| << update.nodes[0].state; |
| |
| CHECK(tree_->Unserialize(update)); |
| LOG(ERROR) << "AX: State after unserializing the empty doc: " |
| << GetRoot()->GetState(); |
| LOG(ERROR) << "AX: Root id after unserializing the empty doc: " |
| << GetRoot()->GetId(); |
| LOG(ERROR) << "AX: Root children after: " |
| << GetRoot()->PlatformChildCount(); |
| } |
| |
| void BrowserAccessibilityManager::GotMouseDown() { |
| 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<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 (!tree_->Unserialize(param.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_->GetRoot(), false); |
| should_send_initial_focus = true; |
| } |
| } |
| |
| OnTreeUpdateFinished(); |
| |
| if (should_send_initial_focus && |
| (!delegate_ || delegate_->AccessibilityViewHasFocus())) { |
| NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetFromAXNode(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). |
| ui::AXNode* node = tree_->GetFromId(param.id); |
| if (!node) |
| continue; |
| |
| ui::AXEvent event_type = param.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 (delegate_ && !delegate_->AccessibilityViewHasFocus()) |
| 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(); |
| } |
| } |
| |
| 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; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::GetFocus( |
| BrowserAccessibility* root) { |
| if (focus_ && (!root || focus_->IsDescendantOf(root->node()))) |
| return GetFromAXNode(focus_); |
| |
| return NULL; |
| } |
| |
| 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::SetTextSelection( |
| const BrowserAccessibility& node, int start_offset, int end_offset) { |
| if (delegate_) { |
| delegate_->AccessibilitySetTextSelection( |
| node.GetId(), start_offset, end_offset); |
| } |
| } |
| |
| gfx::Rect BrowserAccessibilityManager::GetViewBounds() { |
| if (delegate_) |
| return delegate_->AccessibilityGetViewBounds(); |
| return gfx::Rect(); |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::NextInTreeOrder( |
| BrowserAccessibility* node) { |
| if (!node) |
| return NULL; |
| |
| if (node->PlatformChildCount() > 0) |
| return node->PlatformGetChild(0); |
| while (node) { |
| if (node->GetParent() && |
| node->GetIndexInParent() < |
| static_cast<int>(node->GetParent()->PlatformChildCount()) - 1) { |
| return node->GetParent()->PlatformGetChild(node->GetIndexInParent() + 1); |
| } |
| node = node->GetParent(); |
| } |
| |
| return NULL; |
| } |
| |
| BrowserAccessibility* BrowserAccessibilityManager::PreviousInTreeOrder( |
| BrowserAccessibility* node) { |
| if (!node) |
| return NULL; |
| |
| if (node->GetParent() && node->GetIndexInParent() > 0) { |
| node = node->GetParent()->PlatformGetChild(node->GetIndexInParent() - 1); |
| while (node->PlatformChildCount() > 0) |
| node = node->PlatformGetChild(node->PlatformChildCount() - 1); |
| return node; |
| } |
| |
| return node->GetParent(); |
| } |
| |
| void BrowserAccessibilityManager::OnNodeWillBeDeleted(ui::AXNode* node) { |
| if (node == focus_ && tree_) { |
| if (node != tree_->GetRoot()) |
| SetFocus(tree_->GetRoot(), 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::OnNodeCreated(ui::AXNode* node) { |
| BrowserAccessibility* wrapper = factory_->Create(); |
| wrapper->Init(this, node); |
| id_wrapper_map_[node->id()] = wrapper; |
| wrapper->OnDataChanged(); |
| } |
| |
| void BrowserAccessibilityManager::OnNodeChanged(ui::AXNode* node) { |
| GetFromAXNode(node)->OnDataChanged(); |
| } |
| |
| void BrowserAccessibilityManager::OnNodeCreationFinished(ui::AXNode* node) { |
| GetFromAXNode(node)->OnUpdateFinished(); |
| } |
| |
| void BrowserAccessibilityManager::OnNodeChangeFinished(ui::AXNode* node) { |
| GetFromAXNode(node)->OnUpdateFinished(); |
| } |
| |
| } // namespace content |