// Copyright (c) 2011 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 "chrome/browser/accessibility/browser_accessibility_manager.h" #include "base/logging.h" #include "chrome/browser/accessibility/browser_accessibility.h" #include "content/common/view_messages.h" using webkit_glue::WebAccessibility; BrowserAccessibility* BrowserAccessibilityFactory::Create() { return BrowserAccessibility::Create(); } // Start child IDs at -1 and decrement each time, because clients use // child IDs of 1, 2, 3, ... to access the children of an object by // index, so we use negative IDs to clearly distinguish between indices // and unique IDs. // static int32 BrowserAccessibilityManager::next_child_id_ = -1; #if defined(OS_LINUX) // There's no OS-specific implementation of BrowserAccessibilityManager // on Linux, so just instantiate the base class. // static BrowserAccessibilityManager* BrowserAccessibilityManager::Create( gfx::NativeView parent_view, const WebAccessibility& src, BrowserAccessibilityDelegate* delegate, BrowserAccessibilityFactory* factory) { return new BrowserAccessibilityManager( parent_view, src, delegate, factory); } #endif BrowserAccessibilityManager::BrowserAccessibilityManager( gfx::NativeView parent_view, const WebAccessibility& src, BrowserAccessibilityDelegate* delegate, BrowserAccessibilityFactory* factory) : parent_view_(parent_view), delegate_(delegate), factory_(factory), focus_(NULL) { root_ = CreateAccessibilityTree(NULL, src, 0); if (!focus_) SetFocus(root_, false); } // static int32 BrowserAccessibilityManager::GetNextChildID() { // Get the next child ID, and wrap around when we get near the end // of a 32-bit integer range. It's okay to wrap around; we just want // to avoid it as long as possible because clients may cache the ID of // an object for a while to determine if they've seen it before. next_child_id_--; if (next_child_id_ == -2000000000) next_child_id_ = -1; return next_child_id_; } BrowserAccessibilityManager::~BrowserAccessibilityManager() { // Clients could still hold references to some nodes of the tree, so // calling InternalReleaseReference will make sure that as many nodes // as possible are released now, and remaining nodes are marked as // inactive so that calls to any methods on them will fail gracefully. focus_->InternalReleaseReference(false); root_->InternalReleaseReference(true); } BrowserAccessibility* BrowserAccessibilityManager::GetRoot() { return root_; } BrowserAccessibility* BrowserAccessibilityManager::GetFromChildID( int32 child_id) { base::hash_map<int32, BrowserAccessibility*>::iterator iter = child_id_map_.find(child_id); if (iter != child_id_map_.end()) { return iter->second; } else { return NULL; } } void BrowserAccessibilityManager::Remove(int32 child_id, int32 renderer_id) { child_id_map_.erase(child_id); renderer_id_to_child_id_map_.erase(renderer_id); } void BrowserAccessibilityManager::OnAccessibilityNotifications( const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) { for (uint32 index = 0; index < params.size(); index++) { const ViewHostMsg_AccessibilityNotification_Params& param = params[index]; switch (param.notification_type) { case ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_CHECK_STATE_CHANGED: OnAccessibilityObjectStateChange(param.acc_obj); break; case ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_CHILDREN_CHANGED: OnAccessibilityObjectChildrenChange(param.acc_obj); break; case ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_FOCUS_CHANGED: OnAccessibilityObjectFocusChange(param.acc_obj); break; case ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_LOAD_COMPLETE: OnAccessibilityObjectLoadComplete(param.acc_obj); break; case ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_VALUE_CHANGED: OnAccessibilityObjectValueChange(param.acc_obj); break; case ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED: OnAccessibilityObjectTextChange(param.acc_obj); break; default: DCHECK(0); break; } } } void BrowserAccessibilityManager::OnAccessibilityObjectStateChange( const WebAccessibility& acc_obj) { BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false); if (!new_browser_acc) return; NotifyAccessibilityEvent( ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_CHECK_STATE_CHANGED, new_browser_acc); } void BrowserAccessibilityManager::OnAccessibilityObjectChildrenChange( const WebAccessibility& acc_obj) { BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, true); if (!new_browser_acc) return; NotifyAccessibilityEvent( ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_CHILDREN_CHANGED, new_browser_acc); } void BrowserAccessibilityManager::OnAccessibilityObjectFocusChange( const WebAccessibility& acc_obj) { BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false); if (!new_browser_acc) return; SetFocus(new_browser_acc, false); if (delegate_ && delegate_->HasFocus()) { GotFocus(); } else if (!delegate_) { // Mac currently does not have a BrowserAccessibilityDelegate. NotifyAccessibilityEvent( ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_FOCUS_CHANGED, focus_); } } void BrowserAccessibilityManager::OnAccessibilityObjectLoadComplete( const WebAccessibility& acc_obj) { SetFocus(NULL, false); root_->InternalReleaseReference(true); root_ = CreateAccessibilityTree(NULL, acc_obj, 0); if (!focus_) SetFocus(root_, false); NotifyAccessibilityEvent( ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_LOAD_COMPLETE, root_); if (delegate_ && delegate_->HasFocus()) GotFocus(); } void BrowserAccessibilityManager::OnAccessibilityObjectValueChange( const WebAccessibility& acc_obj) { BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false); if (!new_browser_acc) return; NotifyAccessibilityEvent( ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_VALUE_CHANGED, new_browser_acc); } void BrowserAccessibilityManager::OnAccessibilityObjectTextChange( const WebAccessibility& acc_obj) { BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false); if (!new_browser_acc) return; NotifyAccessibilityEvent( ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED, new_browser_acc); } void BrowserAccessibilityManager::GotFocus() { // TODO(ctguil): Remove when tree update logic handles focus changes. if (!focus_) return; NotifyAccessibilityEvent( ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_FOCUS_CHANGED, focus_); } gfx::NativeView BrowserAccessibilityManager::GetParentView() { return parent_view_; } BrowserAccessibility* BrowserAccessibilityManager::GetFocus( BrowserAccessibility* root) { if (focus_ && (!root || focus_->IsDescendantOf(root))) return focus_; return NULL; } void BrowserAccessibilityManager::SetFocus( BrowserAccessibility* node, bool notify) { if (focus_) focus_->InternalReleaseReference(false); focus_ = node; if (focus_) focus_->InternalAddReference(); if (notify && node && delegate_) delegate_->SetAccessibilityFocus(node->renderer_id()); } void BrowserAccessibilityManager::DoDefaultAction( const BrowserAccessibility& node) { if (delegate_) delegate_->AccessibilityDoDefaultAction(node.renderer_id()); } gfx::Rect BrowserAccessibilityManager::GetViewBounds() { if (delegate_) return delegate_->GetViewBounds(); return gfx::Rect(); } BrowserAccessibility* BrowserAccessibilityManager::UpdateNode( const WebAccessibility& src, bool include_children) { base::hash_map<int32, int32>::iterator iter = renderer_id_to_child_id_map_.find(src.id); if (iter == renderer_id_to_child_id_map_.end()) return NULL; int32 child_id = iter->second; BrowserAccessibility* current = GetFromChildID(child_id); if (!current) return NULL; if (!include_children) { DCHECK_EQ(0U, src.children.size()); current->Initialize( this, current->parent(), current->child_id(), current->index_in_parent(), src); return current; } // Detach all of the nodes in the old tree and get a single flat vector // of all node pointers. std::vector<BrowserAccessibility*> old_tree_nodes; current->DetachTree(&old_tree_nodes); // Build a new tree, reusing old nodes if possible. Each node that's // reused will have its reference count incremented by one. current = CreateAccessibilityTree(NULL, src, -1); // Decrement the reference count of all nodes in the old tree, which will // delete any nodes no longer needed. for (int i = 0; i < static_cast<int>(old_tree_nodes.size()); i++) old_tree_nodes[i]->InternalReleaseReference(false); DCHECK(focus_); if (!focus_->instance_active()) SetFocus(root_, false); return current; } BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree( BrowserAccessibility* parent, const WebAccessibility& src, int index_in_parent) { BrowserAccessibility* instance = NULL; int32 child_id = 0; base::hash_map<int32, int32>::iterator iter = renderer_id_to_child_id_map_.find(src.id); // If a BrowserAccessibility instance for this ID already exists, add a // new reference to it and retrieve its children vector. if (iter != renderer_id_to_child_id_map_.end()) { child_id = iter->second; instance = GetFromChildID(child_id); } // If the node has changed roles, don't reuse a BrowserAccessibility // object, that could confuse a screen reader. if (instance && instance->role() != src.role) instance = NULL; if (instance) { // If we're reusing a node, it should already be detached from a parent // and any children. If not, that means we have a serious bug somewhere, // like the same child is reachable from two places in the same tree. DCHECK_EQ(static_cast<BrowserAccessibility*>(NULL), instance->parent()); DCHECK_EQ(0U, instance->child_count()); // If we're reusing a node, update its parent and increment its // reference count. instance->UpdateParent(parent, index_in_parent); instance->InternalAddReference(); } else { // Otherwise, create a new instance. instance = factory_->Create(); child_id = GetNextChildID(); } instance->Initialize(this, parent, child_id, index_in_parent, src); child_id_map_[child_id] = instance; renderer_id_to_child_id_map_[src.id] = child_id; if ((src.state >> WebAccessibility::STATE_FOCUSED) & 1) SetFocus(instance, false); for (int i = 0; i < static_cast<int>(src.children.size()); ++i) { BrowserAccessibility* child = CreateAccessibilityTree( instance, src.children[i], i); instance->AddChild(child); } return instance; }