普通文本  |  434行  |  13.7 KB

// 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