diff options
Diffstat (limited to 'content')
26 files changed, 1016 insertions, 548 deletions
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc index 7484ced..84913a6 100644 --- a/content/browser/accessibility/browser_accessibility.cc +++ b/content/browser/accessibility/browser_accessibility.cc @@ -34,7 +34,6 @@ BrowserAccessibility::BrowserAccessibility() child_id_(0), index_in_parent_(0), renderer_id_(0), - ref_count_(1), role_(0), state_(0), instance_active_(false) { @@ -52,23 +51,25 @@ void BrowserAccessibility::DetachTree( parent_ = NULL; } -void BrowserAccessibility::PreInitialize( +void BrowserAccessibility::InitializeTreeStructure( BrowserAccessibilityManager* manager, BrowserAccessibility* parent, int32 child_id, - int32 index_in_parent, - const AccessibilityNodeData& src) { + int32 renderer_id, + int32 index_in_parent) { manager_ = manager; parent_ = parent; child_id_ = child_id; + renderer_id_ = renderer_id; index_in_parent_ = index_in_parent; +} - // Update all of the rest of the attributes. +void BrowserAccessibility::InitializeData(const AccessibilityNodeData& src) { + DCHECK_EQ(renderer_id_, src.id); name_ = src.name; value_ = src.value; role_ = src.role; state_ = src.state; - renderer_id_ = src.id; string_attributes_ = src.string_attributes; int_attributes_ = src.int_attributes; float_attributes_ = src.float_attributes; @@ -79,6 +80,7 @@ void BrowserAccessibility::PreInitialize( line_breaks_ = src.line_breaks; cell_ids_ = src.cell_ids; unique_cell_ids_ = src.unique_cell_ids; + instance_active_ = true; PreInitialize(); } @@ -91,6 +93,11 @@ void BrowserAccessibility::AddChild(BrowserAccessibility* child) { children_.push_back(child); } +void BrowserAccessibility::SwapChildren( + std::vector<BrowserAccessibility*>& children) { + children.swap(children_); +} + void BrowserAccessibility::UpdateParent(BrowserAccessibility* parent, int index_in_parent) { parent_ = parent; @@ -183,41 +190,25 @@ BrowserAccessibility* BrowserAccessibility::BrowserAccessibilityForPoint( return this; } -void BrowserAccessibility::InternalAddReference() { - ref_count_++; -} - -void BrowserAccessibility::InternalReleaseReference(bool recursive) { - DCHECK_GT(ref_count_, 0); - // It is a bug for ref_count_ to be gt 1 when |recursive| is true. - DCHECK(!recursive || ref_count_ == 1); - - if (recursive || ref_count_ == 1) { - for (std::vector<BrowserAccessibility*>::iterator iter = children_.begin(); - iter != children_.end(); - ++iter) { - (*iter)->InternalReleaseReference(true); - } - children_.clear(); - // Force this to be the last ref. As the DCHECK above indicates, this - // should always be the case. Make it so defensively. - ref_count_ = 1; +void BrowserAccessibility::Destroy() { + for (std::vector<BrowserAccessibility*>::iterator iter = children_.begin(); + iter != children_.end(); + ++iter) { + (*iter)->Destroy(); } + children_.clear(); - ref_count_--; - if (ref_count_ == 0) { - // Allow the object to fire a TextRemoved notification. - name_.clear(); - value_.clear(); - PostInitialize(); + // Allow the object to fire a TextRemoved notification. + name_.clear(); + value_.clear(); + PostInitialize(); - manager_->NotifyAccessibilityEvent( - AccessibilityNotificationObjectHide, this); + manager_->NotifyAccessibilityEvent( + AccessibilityNotificationObjectHide, this); - instance_active_ = false; - manager_->Remove(child_id_, renderer_id_); - NativeReleaseReference(); - } + instance_active_ = false; + manager_->Remove(this); + NativeReleaseReference(); } void BrowserAccessibility::NativeReleaseReference() { @@ -332,8 +323,4 @@ string16 BrowserAccessibility::GetTextRecursive() const { return result; } -void BrowserAccessibility::PreInitialize() { - instance_active_ = true; -} - } // namespace content diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h index 07a79af..ee74077 100644 --- a/content/browser/accessibility/browser_accessibility.h +++ b/content/browser/accessibility/browser_accessibility.h @@ -56,7 +56,7 @@ class CONTENT_EXPORT BrowserAccessibility { // including this node, onto the end of |nodes|. virtual void DetachTree(std::vector<BrowserAccessibility*>* nodes); - // Perform platform specific initialization. This can be called multiple times + // Perform platform-specific initialization. This can be called multiple times // during the lifetime of this instance after the members of this base object // have been reset with new values from the renderer process. // Child dependent initialization can be done here. @@ -66,17 +66,22 @@ class CONTENT_EXPORT BrowserAccessibility { // cross-platform generic object. virtual bool IsNative() const; - // Initialize this object, reading attributes from |src|. Does not - // recurse into children of |src| and build the whole subtree. - void PreInitialize(BrowserAccessibilityManager* manager, + // Initialize the tree structure of this object. + void InitializeTreeStructure( + BrowserAccessibilityManager* manager, BrowserAccessibility* parent, int32 child_id, - int32 index_in_parent, - const AccessibilityNodeData& src); + int32 renderer_id, + int32 index_in_parent); + + // Initialize this object's data. + void InitializeData(const AccessibilityNodeData& src); // Add a child of this object. void AddChild(BrowserAccessibility* child); + void SwapChildren(std::vector<BrowserAccessibility*>& children); + // Update the parent and index in parent if this node has been moved. void UpdateParent(BrowserAccessibility* parent, int index_in_parent); @@ -111,32 +116,16 @@ class CONTENT_EXPORT BrowserAccessibility { // (in global screen coordinates). BrowserAccessibility* BrowserAccessibilityForPoint(const gfx::Point& point); + // Marks this object for deletion, releases our reference to it, and + // recursively calls Destroy() on its children. May not delete + // immediately due to reference counting. // - // Reference counting - // - // Each object has an internal reference count and many platform - // implementations may also use native reference counting. - // - // The internal reference counting is used because sometimes - // multiple references to the same object exist temporarily during - // an update. When the internal reference count reaches zero, - // NativeReleaseReference is called. - // - // Native reference counting is used on some platforms because the + // Reference counting is used on some platforms because the // operating system may hold onto a reference to a BrowserAccessibility - // object even after we're through with it. On these platforms, when - // the internal reference count reaches zero, instance_active is set - // to zero, and all queries on this object should return failure. - // The object isn't actually deleted until the operating system releases - // all of its references. - // - - // Increment this node's internal reference count. - virtual void InternalAddReference(); - - // Decrement this node's internal reference count. If the reference count - // reaches zero, call NativeReleaseReference(). - virtual void InternalReleaseReference(bool recursive); + // object even after we're through with it. When a BrowserAccessibility + // has had Destroy() called but its reference count is not yet zero, + // queries on this object return failure + virtual void Destroy(); // Subclasses should override this to support platform reference counting. virtual void NativeAddReference() { } @@ -193,7 +182,6 @@ class CONTENT_EXPORT BrowserAccessibility { int32 state() const { return state_; } const string16& value() const { return value_; } bool instance_active() const { return instance_active_; } - int32 ref_count() const { return ref_count_; } #if defined(OS_MACOSX) && __OBJC__ BrowserAccessibilityCocoa* ToBrowserAccessibilityCocoa(); @@ -257,7 +245,7 @@ class CONTENT_EXPORT BrowserAccessibility { // during the lifetime of this instance after the members of this base object // have been reset with new values from the renderer process. // Perform child independent initialization in this method. - virtual void PreInitialize(); + virtual void PreInitialize() {} BrowserAccessibility(); @@ -280,9 +268,6 @@ class CONTENT_EXPORT BrowserAccessibility { // The children of this object. std::vector<BrowserAccessibility*> children_; - // The number of internal references to this object. - int32 ref_count_; - // Accessibility metadata from the renderer string16 name_; string16 value_; diff --git a/content/browser/accessibility/browser_accessibility_mac_unittest.mm b/content/browser/accessibility/browser_accessibility_mac_unittest.mm index 37b0f9a..d047e33 100644 --- a/content/browser/accessibility/browser_accessibility_mac_unittest.mm +++ b/content/browser/accessibility/browser_accessibility_mac_unittest.mm @@ -61,9 +61,11 @@ class BrowserAccessibilityTest : public ui::CocoaTest { root.id = 1000; root.location.set_width(500); root.location.set_height(100); - root.role = AccessibilityNodeData::ROLE_WEB_AREA; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; root.string_attributes[AccessibilityNodeData::ATTR_HELP] = ASCIIToUTF16("HelpText"); + root.child_ids.push_back(1001); + root.child_ids.push_back(1002); AccessibilityNodeData child1; child1.id = 1001; @@ -79,12 +81,10 @@ class BrowserAccessibilityTest : public ui::CocoaTest { child2.location.set_height(100); child2.role = AccessibilityNodeData::ROLE_HEADING; - root.children.push_back(child1); - root.children.push_back(child2); - delegate_.reset([[MockAccessibilityDelegate alloc] init]); manager_.reset( BrowserAccessibilityManager::Create(delegate_, root, NULL)); + manager_->UpdateNodesForTesting(child1, child2); accessibility_.reset([manager_->GetRoot()->ToBrowserAccessibilityCocoa() retain]); } diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc index 7b3bf26..a60eaf3 100644 --- a/content/browser/accessibility/browser_accessibility_manager.cc +++ b/content/browser/accessibility/browser_accessibility_manager.cc @@ -61,9 +61,13 @@ BrowserAccessibilityManager::BrowserAccessibilityManager( : parent_view_(parent_view), delegate_(delegate), factory_(factory), + root_(NULL), focus_(NULL), osk_state_(OSK_ALLOWED) { - root_ = CreateAccessibilityTree(NULL, src, 0, false); + std::vector<AccessibilityNodeData> nodes; + nodes.push_back(src); + if (!UpdateNodes(nodes)) + return; if (!focus_) SetFocus(root_, false); } @@ -82,12 +86,8 @@ int32 BrowserAccessibilityManager::GetNextChildID() { } 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); + if (root_) + root_->Destroy(); } BrowserAccessibility* BrowserAccessibilityManager::GetRoot() { @@ -143,10 +143,12 @@ bool BrowserAccessibilityManager::IsOSKAllowed(const gfx::Rect& bounds) { return bounds.Contains(touch_point); } -void BrowserAccessibilityManager::Remove(int32 child_id, int32 renderer_id) { +void BrowserAccessibilityManager::Remove(BrowserAccessibility* node) { + if (node == focus_) + SetFocus(root_, false); + int child_id = node->child_id(); + int renderer_id = node->renderer_id(); child_id_map_.erase(child_id); - - // TODO(ctguil): Investigate if hit. We should never have a newer entry. DCHECK(renderer_id_to_child_id_map_[renderer_id] == child_id); // Make sure we don't overwrite a newer entry (see UpdateNode for a possible // corner case). @@ -159,22 +161,15 @@ void BrowserAccessibilityManager::OnAccessibilityNotifications( for (uint32 index = 0; index < params.size(); index++) { const AccessibilityHostMsg_NotificationParams& param = params[index]; - // Update the tree. - UpdateNode(param.acc_tree, param.includes_children); + // Update nodes that changed. + if (!UpdateNodes(param.nodes)) + return; // Find the node corresponding to the id that's the target of the // notification (which may not be the root of the update tree). - base::hash_map<int32, int32>::iterator iter = - renderer_id_to_child_id_map_.find(param.id); - if (iter == renderer_id_to_child_id_map_.end()) { + BrowserAccessibility* node = GetFromRendererID(param.id); + if (!node) continue; - } - int32 child_id = iter->second; - BrowserAccessibility* node = GetFromChildID(child_id); - if (!node) { - NOTREACHED(); - continue; - } int notification_type = param.notification_type; if (notification_type == AccessibilityNotificationFocusChanged || @@ -218,13 +213,8 @@ BrowserAccessibility* BrowserAccessibilityManager::GetFocus( void BrowserAccessibilityManager::SetFocus( BrowserAccessibility* node, bool notify) { - if (focus_ != node) { - if (focus_) - focus_->InternalReleaseReference(false); + if (focus_ != node) focus_ = node; - if (focus_) - focus_->InternalAddReference(); - } if (notify && node && delegate_) delegate_->SetAccessibilityFocus(node->renderer_id()); @@ -264,142 +254,159 @@ gfx::Rect BrowserAccessibilityManager::GetViewBounds() { return gfx::Rect(); } -void BrowserAccessibilityManager::UpdateNode( - const AccessibilityNodeData& src, - bool include_children) { - BrowserAccessibility* current = NULL; - - // Look for the node to replace. Either we're replacing the whole tree - // (role is ROOT_WEB_AREA) or we look it up based on its renderer ID. - if (src.role == AccessibilityNodeData::ROLE_ROOT_WEB_AREA) { - current = root_; - } else { - 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()) { - int32 child_id = iter->second; - current = GetFromChildID(child_id); - } - } +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); +} - // If we can't find the node to replace, we're out of sync with the - // renderer (this would be a bug). - DCHECK(current); - if (!current) - return; +bool BrowserAccessibilityManager::UpdateNodes( + const std::vector<AccessibilityNodeData>& nodes) { + bool success = true; - // If this update is just for a single node (|include_children| is false), - // modify |current| directly and return - no tree changes are needed. - if (!include_children) { - DCHECK_EQ(0U, src.children.size()); - current->PreInitialize( - this, - current->parent(), - current->child_id(), - current->index_in_parent(), - src); - current->PostInitialize(); - return; + // 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; } - BrowserAccessibility* current_parent = current->parent(); - int current_index_in_parent = current->index_in_parent(); - - // 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. - CreateAccessibilityTree(current_parent, src, current_index_in_parent, true); - - // 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); - - // If the only reference to the focused node is focus_ itself, then the - // focused node is no longer in the tree, so set the focus to the root. - if (focus_ && focus_->ref_count() == 1) { - SetFocus(root_, 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++) { + BrowserAccessibility* instance = GetFromRendererID(nodes[i].id); + if (instance) { + instance->PostInitialize(); + } else { + success = false; + } + } - if (delegate_ && delegate_->HasFocus()) - NotifyAccessibilityEvent(AccessibilityNotificationBlur, focus_); + 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::CreateAccessibilityTree( +BrowserAccessibility* BrowserAccessibilityManager::CreateNode( BrowserAccessibility* parent, - const AccessibilityNodeData& src, - int index_in_parent, - bool send_show_events) { - BrowserAccessibility* instance = NULL; - int32 child_id = 0; - bool children_can_send_show_events = send_show_events; - base::hash_map<int32, int32>::iterator iter = - renderer_id_to_child_id_map_.find(src.id); + int32 renderer_id, + int32 index_in_parent) { + BrowserAccessibility* instance = factory_->Create(); + int32 child_id = GetNextChildID(); + instance->InitializeTreeStructure( + this, parent, child_id, renderer_id, index_in_parent); + child_id_map_[child_id] = instance; + renderer_id_to_child_id_map_[renderer_id] = child_id; + return instance; +} - // 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); +bool BrowserAccessibilityManager::UpdateNode(const AccessibilityNodeData& src) { + // This method updates one node in the tree based on serialized data + // received from the renderer. First, 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 != AccessibilityNodeData::ROLE_ROOT_WEB_AREA) + return false; + instance = CreateNode(NULL, src.id, 0); } - // If the node has changed roles, don't reuse a BrowserAccessibility - // object, that could confuse a screen reader. - // TODO(dtseng): Investigate when this gets hit; See crbug.com/93095. - DCHECK(!instance || instance->role() == src.role); - - // 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. - if (instance && (instance->parent() != NULL || instance->child_count() > 0)) { - // TODO(dmazzoni): investigate this: http://crbug.com/161726 - LOG(WARNING) << "Reusing node that wasn't detached from parent"; - instance = NULL; + // 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. + 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]); } - - if (instance) { - // If we're reusing a node, update its parent and increment its - // reference count. - instance->UpdateParent(parent, index_in_parent); - instance->InternalAddReference(); - send_show_events = false; - } else { - // Otherwise, create a new instance. - instance = factory_->Create(); - child_id = GetNextChildID(); - children_can_send_show_events = false; + 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(); } - instance->PreInitialize(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 >> AccessibilityNodeData::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, children_can_send_show_events); - instance->AddChild(child); + // Now build a vector of new children, reusing objects that were already + // children of this node before. + std::vector<BrowserAccessibility*> new_children; + 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) { + instance->SwapChildren(new_children); + return false; + } + child->UpdateParent(instance, index_in_parent); + } else { + child = CreateNode(instance, child_renderer_id, index_in_parent); + } + new_children.push_back(child); } - if (src.role == AccessibilityNodeData::ROLE_ROOT_WEB_AREA) - root_ = instance; + // Finally, swap in the new children vector for the old. + instance->SwapChildren(new_children); - // Note: the purpose of send_show_events and children_can_send_show_events - // is so that we send a single ObjectShow event for the root of a subtree - // that just appeared for the first time, but not on any descendant of - // that subtree. - if (send_show_events) - NotifyAccessibilityEvent(AccessibilityNotificationObjectShow, instance); + // Handle the case where this node is the new root of the tree. + if (src.role == AccessibilityNodeData::ROLE_ROOT_WEB_AREA && + (!root_ || root_->renderer_id() != src.id)) { + if (root_) + root_->Destroy(); + if (focus_ == root_) + focus_ = instance; + root_ = instance; + } - instance->PostInitialize(); + // Keep track of what node is focused. + if ((src.state >> AccessibilityNodeData::STATE_FOCUSED) & 1) + SetFocus(instance, false); - return instance; + return true; } } // namespace content diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h index 7a0cdb6..08010bf 100644 --- a/content/browser/accessibility/browser_accessibility_manager.h +++ b/content/browser/accessibility/browser_accessibility_manager.h @@ -37,6 +37,7 @@ class CONTENT_EXPORT BrowserAccessibilityDelegate { virtual bool HasFocus() const = 0; virtual gfx::Rect GetViewBounds() const = 0; virtual gfx::Point GetLastTouchEventLocation() const = 0; + virtual void FatalAccessibilityTreeError() = 0; }; class CONTENT_EXPORT BrowserAccessibilityFactory { @@ -82,8 +83,8 @@ class CONTENT_EXPORT BrowserAccessibilityManager { // Return a pointer to the root of the tree, does not make a new reference. BrowserAccessibility* GetRoot(); - // Removes the BrowserAccessibility child_id and renderer_id from the manager. - void Remove(int32 child_id, int32 renderer_id); + // Removes a node from the manager. + void Remove(BrowserAccessibility* node); // Return a pointer to the object corresponding to the given child_id, // does not make a new reference. @@ -154,6 +155,19 @@ class CONTENT_EXPORT BrowserAccessibilityManager { // focus event on a text box? bool IsOSKAllowed(const gfx::Rect& bounds); + // For testing only: update the given nodes as if they were + // received from the renderer process in OnAccessibilityNotifications. + // Takes up to 7 nodes at once so tests don't need to create a vector + // each time. + void UpdateNodesForTesting( + const AccessibilityNodeData& node, + const AccessibilityNodeData& node2 = AccessibilityNodeData(), + const AccessibilityNodeData& node3 = AccessibilityNodeData(), + const AccessibilityNodeData& node4 = AccessibilityNodeData(), + const AccessibilityNodeData& node5 = AccessibilityNodeData(), + const AccessibilityNodeData& node6 = AccessibilityNodeData(), + const AccessibilityNodeData& node7 = AccessibilityNodeData()); + protected: BrowserAccessibilityManager( gfx::NativeView parent_view, @@ -184,19 +198,18 @@ class CONTENT_EXPORT BrowserAccessibilityManager { OSK_ALLOWED }; - // Update an accessibility node with an updated AccessibilityNodeData node - // received from the renderer process. When |include_children| is true - // the node's children will also be updated, otherwise only the node - // itself is updated. - void UpdateNode(const AccessibilityNodeData& src, bool include_children); + // Update a set of nodes using data received from the renderer + // process. + bool UpdateNodes(const std::vector<AccessibilityNodeData>& nodes); + + // Update one node from the tree using data received from the renderer + // process. Returns true on success, false on fatal error. + bool UpdateNode(const AccessibilityNodeData& src); - // Recursively build a tree of BrowserAccessibility objects from - // the AccessibilityNodeData tree received from the renderer process. - BrowserAccessibility* CreateAccessibilityTree( + BrowserAccessibility* CreateNode( BrowserAccessibility* parent, - const AccessibilityNodeData& src, - int index_in_parent, - bool send_show_events); + int32 renderer_id, + int32 index_in_parent); protected: // The next unique id for a BrowserAccessibility instance. @@ -211,7 +224,7 @@ class CONTENT_EXPORT BrowserAccessibilityManager { // Factory to create BrowserAccessibility objects (for dependency injection). scoped_ptr<BrowserAccessibilityFactory> factory_; - // The root of the tree of IAccessible objects and the element that + // The root of the tree of accessible objects and the element that // currently has focus, if any. BrowserAccessibility* root_; BrowserAccessibility* focus_; diff --git a/content/browser/accessibility/browser_accessibility_manager_unittest.cc b/content/browser/accessibility/browser_accessibility_manager_unittest.cc index 4ddf741..dfcc738 100644 --- a/content/browser/accessibility/browser_accessibility_manager_unittest.cc +++ b/content/browser/accessibility/browser_accessibility_manager_unittest.cc @@ -50,6 +50,40 @@ class CountedBrowserAccessibilityFactory } }; +class TestBrowserAccessibilityDelegate + : public BrowserAccessibilityDelegate { + public: + TestBrowserAccessibilityDelegate() + : got_fatal_error_(false) {} + + virtual void SetAccessibilityFocus(int acc_obj_id) OVERRIDE {} + virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE {} + virtual void AccessibilityScrollToMakeVisible( + int acc_obj_id, gfx::Rect subfocus) OVERRIDE {} + virtual void AccessibilityScrollToPoint( + int acc_obj_id, gfx::Point point) OVERRIDE {} + virtual void AccessibilitySetTextSelection( + int acc_obj_id, int start_offset, int end_offset) OVERRIDE {} + virtual bool HasFocus() const OVERRIDE { + return false; + } + virtual gfx::Rect GetViewBounds() const OVERRIDE { + return gfx::Rect(); + } + virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE { + return gfx::Point(); + } + virtual void FatalAccessibilityTreeError() OVERRIDE { + got_fatal_error_ = true; + } + + bool got_fatal_error() const { return got_fatal_error_; } + void reset_got_fatal_error() { got_fatal_error_ = false; } + +private: + bool got_fatal_error_; +}; + } // anonymous namespace TEST(BrowserAccessibilityManagerTest, TestNoLeaks) { @@ -71,10 +105,10 @@ TEST(BrowserAccessibilityManagerTest, TestNoLeaks) { AccessibilityNodeData root; root.id = 1; root.name = UTF8ToUTF16("Document"); - root.role = AccessibilityNodeData::ROLE_DOCUMENT; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; root.state = 0; - root.children.push_back(button); - root.children.push_back(checkbox); + root.child_ids.push_back(2); + root.child_ids.push_back(3); // Construct a BrowserAccessibilityManager with this // AccessibilityNodeData tree and a factory for an instance-counting @@ -87,6 +121,7 @@ TEST(BrowserAccessibilityManagerTest, TestNoLeaks) { root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(button, checkbox); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); @@ -102,6 +137,7 @@ TEST(BrowserAccessibilityManagerTest, TestNoLeaks) { root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(button, checkbox); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); CountedBrowserAccessibility* root_accessible = @@ -155,11 +191,11 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) { AccessibilityNodeData tree1_root; tree1_root.id = 1; tree1_root.name = UTF8ToUTF16("Document"); - tree1_root.role = AccessibilityNodeData::ROLE_DOCUMENT; + tree1_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; tree1_root.state = 0; - tree1_root.children.push_back(tree1_child1); - tree1_root.children.push_back(tree1_child2); - tree1_root.children.push_back(tree1_child3); + tree1_root.child_ids.push_back(2); + tree1_root.child_ids.push_back(3); + tree1_root.child_ids.push_back(4); // Tree 2: // @@ -175,26 +211,14 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) { tree2_child0.role = AccessibilityNodeData::ROLE_BUTTON; tree2_child0.state = 0; - AccessibilityNodeData tree2_child1; - tree2_child1.id = 2; - tree2_child1.name = UTF8ToUTF16("Child1"); - tree2_child1.role = AccessibilityNodeData::ROLE_BUTTON; - tree2_child1.state = 0; - - AccessibilityNodeData tree2_child2; - tree2_child2.id = 3; - tree2_child2.name = UTF8ToUTF16("Child2"); - tree2_child2.role = AccessibilityNodeData::ROLE_BUTTON; - tree2_child2.state = 0; - AccessibilityNodeData tree2_root; tree2_root.id = 1; tree2_root.name = UTF8ToUTF16("DocumentChanged"); - tree2_root.role = AccessibilityNodeData::ROLE_DOCUMENT; + tree2_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; tree2_root.state = 0; - tree2_root.children.push_back(tree2_child0); - tree2_root.children.push_back(tree2_child1); - tree2_root.children.push_back(tree2_child2); + tree2_root.child_ids.push_back(5); + tree2_root.child_ids.push_back(2); + tree2_root.child_ids.push_back(3); // Construct a BrowserAccessibilityManager with tree1. CountedBrowserAccessibility::global_obj_count_ = 0; @@ -204,6 +228,7 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) { tree1_root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(tree1_child1, tree1_child2, tree1_child3); ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); // Save references to all of the objects. @@ -230,8 +255,8 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) { params.push_back(AccessibilityHostMsg_NotificationParams()); AccessibilityHostMsg_NotificationParams* msg = ¶ms[0]; msg->notification_type = AccessibilityNotificationChildrenChanged; - msg->acc_tree = tree2_root; - msg->includes_children = true; + msg->nodes.push_back(tree2_root); + msg->nodes.push_back(tree2_child0); msg->id = tree2_root.id; manager->OnAccessibilityNotifications(params); @@ -289,7 +314,7 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { tree1_child1.name = UTF8ToUTF16("Child1"); tree1_child1.role = AccessibilityNodeData::ROLE_BUTTON; tree1_child1.state = 0; - tree1_child1.children.push_back(tree1_grandchild1); + tree1_child1.child_ids.push_back(4); AccessibilityNodeData tree1_grandchild2; tree1_grandchild2.id = 6; @@ -302,7 +327,7 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { tree1_child2.name = UTF8ToUTF16("Child2"); tree1_child2.role = AccessibilityNodeData::ROLE_BUTTON; tree1_child2.state = 0; - tree1_child2.children.push_back(tree1_grandchild2); + tree1_child2.child_ids.push_back(6); AccessibilityNodeData tree1_grandchild3; tree1_grandchild3.id = 8; @@ -315,23 +340,23 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { tree1_child3.name = UTF8ToUTF16("Child3"); tree1_child3.role = AccessibilityNodeData::ROLE_BUTTON; tree1_child3.state = 0; - tree1_child3.children.push_back(tree1_grandchild3); + tree1_child3.child_ids.push_back(8); AccessibilityNodeData tree1_container; tree1_container.id = 2; tree1_container.name = UTF8ToUTF16("Container"); tree1_container.role = AccessibilityNodeData::ROLE_GROUP; tree1_container.state = 0; - tree1_container.children.push_back(tree1_child1); - tree1_container.children.push_back(tree1_child2); - tree1_container.children.push_back(tree1_child3); + tree1_container.child_ids.push_back(3); + tree1_container.child_ids.push_back(5); + tree1_container.child_ids.push_back(7); AccessibilityNodeData tree1_root; tree1_root.id = 1; tree1_root.name = UTF8ToUTF16("Document"); - tree1_root.role = AccessibilityNodeData::ROLE_DOCUMENT; + tree1_root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; tree1_root.state = 0; - tree1_root.children.push_back(tree1_container); + tree1_root.child_ids.push_back(2); // Tree 2: // @@ -356,49 +381,16 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { tree2_child0.name = UTF8ToUTF16("Child0"); tree2_child0.role = AccessibilityNodeData::ROLE_BUTTON; tree2_child0.state = 0; - tree2_child0.children.push_back(tree2_grandchild0); - - AccessibilityNodeData tree2_grandchild1; - tree2_grandchild1.id = 4; - tree2_grandchild1.name = UTF8ToUTF16("GrandChild1"); - tree2_grandchild1.role = AccessibilityNodeData::ROLE_BUTTON; - tree2_grandchild1.state = 0; - - AccessibilityNodeData tree2_child1; - tree2_child1.id = 3; - tree2_child1.name = UTF8ToUTF16("Child1"); - tree2_child1.role = AccessibilityNodeData::ROLE_BUTTON; - tree2_child1.state = 0; - tree2_child1.children.push_back(tree2_grandchild1); - - AccessibilityNodeData tree2_grandchild2; - tree2_grandchild2.id = 6; - tree2_grandchild2.name = UTF8ToUTF16("GrandChild1"); - tree2_grandchild2.role = AccessibilityNodeData::ROLE_BUTTON; - tree2_grandchild2.state = 0; - - AccessibilityNodeData tree2_child2; - tree2_child2.id = 5; - tree2_child2.name = UTF8ToUTF16("Child2"); - tree2_child2.role = AccessibilityNodeData::ROLE_BUTTON; - tree2_child2.state = 0; - tree2_child2.children.push_back(tree2_grandchild2); + tree2_child0.child_ids.push_back(9); AccessibilityNodeData tree2_container; tree2_container.id = 2; tree2_container.name = UTF8ToUTF16("Container"); tree2_container.role = AccessibilityNodeData::ROLE_GROUP; tree2_container.state = 0; - tree2_container.children.push_back(tree2_child0); - tree2_container.children.push_back(tree2_child1); - tree2_container.children.push_back(tree2_child2); - - AccessibilityNodeData tree2_root; - tree2_root.id = 1; - tree2_root.name = UTF8ToUTF16("Document"); - tree2_root.role = AccessibilityNodeData::ROLE_DOCUMENT; - tree2_root.state = 0; - tree2_root.children.push_back(tree2_container); + tree2_container.child_ids.push_back(10); + tree2_container.child_ids.push_back(3); + tree2_container.child_ids.push_back(5); // Construct a BrowserAccessibilityManager with tree1. CountedBrowserAccessibility::global_obj_count_ = 0; @@ -408,6 +400,10 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { tree1_root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(tree1_container, + tree1_child1, tree1_grandchild1, + tree1_child2, tree1_grandchild2, + tree1_child3, tree1_grandchild3); ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_); // Save references to some objects. @@ -436,8 +432,9 @@ TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { params.push_back(AccessibilityHostMsg_NotificationParams()); AccessibilityHostMsg_NotificationParams* msg = ¶ms[0]; msg->notification_type = AccessibilityNotificationChildrenChanged; - msg->acc_tree = tree2_container; - msg->includes_children = true; + msg->nodes.push_back(tree2_container); + msg->nodes.push_back(tree2_child0); + msg->nodes.push_back(tree2_grandchild0); msg->id = tree2_container.id; manager->OnAccessibilityNotifications(params); @@ -488,7 +485,7 @@ TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) { AccessibilityNodeData tree1_3; tree1_3.id = 3; tree1_3.state = 0; - tree1_3.children.push_back(tree1_4); + tree1_3.child_ids.push_back(4); AccessibilityNodeData tree1_2; tree1_2.id = 2; @@ -496,9 +493,10 @@ TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) { AccessibilityNodeData tree1_1; tree1_1.id = 1; + tree1_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; tree1_1.state = 0; - tree1_1.children.push_back(tree1_2); - tree1_1.children.push_back(tree1_3); + tree1_1.child_ids.push_back(2); + tree1_1.child_ids.push_back(3); // Tree 2: // @@ -518,13 +516,13 @@ TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) { AccessibilityNodeData tree2_4; tree2_4.id = 4; tree2_4.state = 0; - tree2_4.children.push_back(tree2_6); + tree2_4.child_ids.push_back(6); AccessibilityNodeData tree2_1; tree2_1.id = 1; tree2_1.state = 0; - tree2_1.children.push_back(tree2_4); - tree2_1.children.push_back(tree2_5); + tree2_1.child_ids.push_back(4); + tree2_1.child_ids.push_back(5); // Construct a BrowserAccessibilityManager with tree1. CountedBrowserAccessibility::global_obj_count_ = 0; @@ -534,6 +532,7 @@ TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) { tree1_1, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(tree1_2, tree1_3, tree1_4); ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); // Process a notification containing the changed subtree. @@ -541,8 +540,10 @@ TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) { params.push_back(AccessibilityHostMsg_NotificationParams()); AccessibilityHostMsg_NotificationParams* msg = ¶ms[0]; msg->notification_type = AccessibilityNotificationChildrenChanged; - msg->acc_tree = tree2_1; - msg->includes_children = true; + msg->nodes.push_back(tree2_1); + msg->nodes.push_back(tree2_4); + msg->nodes.push_back(tree2_5); + msg->nodes.push_back(tree2_6); msg->id = tree2_1.id; manager->OnAccessibilityNotifications(params); @@ -576,20 +577,19 @@ TEST(BrowserAccessibilityManagerTest, TestCreateEmptyDocument) { AccessibilityNodeData tree1_1; tree1_1.id = 1; tree1_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + tree1_1.child_ids.push_back(2); AccessibilityNodeData tree1_2; tree1_2.id = 2; tree1_2.role = AccessibilityNodeData::ROLE_TEXT_FIELD; - tree1_1.children.push_back(tree1_2); - // Process a load complete. std::vector<AccessibilityHostMsg_NotificationParams> params; params.push_back(AccessibilityHostMsg_NotificationParams()); AccessibilityHostMsg_NotificationParams* msg = ¶ms[0]; msg->notification_type = AccessibilityNotificationLoadComplete; - msg->acc_tree = tree1_1; - msg->includes_children = true; + msg->nodes.push_back(tree1_1); + msg->nodes.push_back(tree1_2); msg->id = tree1_1.id; manager->OnAccessibilityNotifications(params); @@ -607,15 +607,15 @@ TEST(BrowserAccessibilityManagerTest, TestCreateEmptyDocument) { AccessibilityNodeData tree2_1; tree2_1.id = 1; tree2_1.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + tree2_1.child_ids.push_back(3); AccessibilityNodeData tree2_2; tree2_2.id = 3; tree2_2.role = AccessibilityNodeData::ROLE_BUTTON; - tree2_1.children.push_back(tree2_2); - - msg->acc_tree = tree2_1; - msg->includes_children = true; + msg->nodes.clear(); + msg->nodes.push_back(tree2_1); + msg->nodes.push_back(tree2_2); msg->id = tree2_1.id; // Fire another load complete. @@ -635,4 +635,56 @@ TEST(BrowserAccessibilityManagerTest, TestCreateEmptyDocument) { ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } +TEST(BrowserAccessibilityManagerTest, TestFatalError) { + // Test that BrowserAccessibilityManager raises a fatal error + // (which will crash the renderer) if the same id is used in + // two places in the tree. + + AccessibilityNodeData root; + root.id = 1; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.child_ids.push_back(2); + root.child_ids.push_back(2); + + CountedBrowserAccessibilityFactory* factory = + new CountedBrowserAccessibilityFactory(); + scoped_ptr<TestBrowserAccessibilityDelegate> delegate( + new TestBrowserAccessibilityDelegate()); + scoped_ptr<BrowserAccessibilityManager> manager; + ASSERT_FALSE(delegate->got_fatal_error()); + manager.reset(BrowserAccessibilityManager::Create( + NULL, + root, + delegate.get(), + factory)); + ASSERT_TRUE(delegate->got_fatal_error()); + + AccessibilityNodeData root2; + root2.id = 1; + root2.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root2.child_ids.push_back(2); + root2.child_ids.push_back(3); + + AccessibilityNodeData child1; + child1.id = 2; + child1.child_ids.push_back(4); + child1.child_ids.push_back(5); + + AccessibilityNodeData child2; + child2.id = 3; + child2.child_ids.push_back(6); + child2.child_ids.push_back(5); // Duplicate + + delegate->reset_got_fatal_error(); + factory = new CountedBrowserAccessibilityFactory(); + manager.reset(BrowserAccessibilityManager::Create( + NULL, + root2, + delegate.get(), + factory)); + ASSERT_FALSE(delegate->got_fatal_error()); + manager->UpdateNodesForTesting(child1, child2); + ASSERT_TRUE(delegate->got_fatal_error()); +} + } // namespace content diff --git a/content/browser/accessibility/browser_accessibility_win_unittest.cc b/content/browser/accessibility/browser_accessibility_win_unittest.cc index 55aff43..17b2cbd 100644 --- a/content/browser/accessibility/browser_accessibility_win_unittest.cc +++ b/content/browser/accessibility/browser_accessibility_win_unittest.cc @@ -82,10 +82,10 @@ TEST_F(BrowserAccessibilityTest, TestNoLeaks) { AccessibilityNodeData root; root.id = 1; root.name = L"Document"; - root.role = AccessibilityNodeData::ROLE_DOCUMENT; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; root.state = 0; - root.children.push_back(button); - root.children.push_back(checkbox); + root.child_ids.push_back(2); + root.child_ids.push_back(3); // Construct a BrowserAccessibilityManager with this // AccessibilityNodeData tree and a factory for an instance-counting @@ -98,6 +98,7 @@ TEST_F(BrowserAccessibilityTest, TestNoLeaks) { root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(button, checkbox); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); // Delete the manager and test that all 3 instances are deleted. @@ -112,6 +113,7 @@ TEST_F(BrowserAccessibilityTest, TestNoLeaks) { root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(button, checkbox); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); IAccessible* root_accessible = manager->GetRoot()->ToBrowserAccessibilityWin(); @@ -152,9 +154,9 @@ TEST_F(BrowserAccessibilityTest, TestChildrenChange) { AccessibilityNodeData root; root.id = 1; root.name = L"Document"; - root.role = AccessibilityNodeData::ROLE_DOCUMENT; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; root.state = 0; - root.children.push_back(text); + root.child_ids.push_back(2); // Construct a BrowserAccessibilityManager with this // AccessibilityNodeData tree and a factory for an instance-counting @@ -166,6 +168,7 @@ TEST_F(BrowserAccessibilityTest, TestChildrenChange) { root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(text); // Query for the text IAccessible and verify that it returns "old text" as its // value. @@ -190,8 +193,7 @@ TEST_F(BrowserAccessibilityTest, TestChildrenChange) { text.name = L"new text"; AccessibilityHostMsg_NotificationParams param; param.notification_type = AccessibilityNotificationChildrenChanged; - param.acc_tree = text; - param.includes_children = true; + param.nodes.push_back(text); param.id = text.id; std::vector<AccessibilityHostMsg_NotificationParams> notifications; notifications.push_back(param); @@ -224,25 +226,29 @@ TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) { // Create AccessibilityNodeData objects for a simple document tree, // representing the accessibility information used to initialize // BrowserAccessibilityManager. - AccessibilityNodeData text; - text.id = 3; - text.role = AccessibilityNodeData::ROLE_STATIC_TEXT; - text.state = 0; - AccessibilityNodeData div; div.id = 2; div.role = AccessibilityNodeData::ROLE_GROUP; div.state = 0; - div.children.push_back(text); - text.id = 4; - div.children.push_back(text); + AccessibilityNodeData text3; + text3.id = 3; + text3.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text3.state = 0; + + AccessibilityNodeData text4; + text4.id = 4; + text4.role = AccessibilityNodeData::ROLE_STATIC_TEXT; + text4.state = 0; + + div.child_ids.push_back(3); + div.child_ids.push_back(4); AccessibilityNodeData root; root.id = 1; - root.role = AccessibilityNodeData::ROLE_DOCUMENT; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; root.state = 0; - root.children.push_back(div); + root.child_ids.push_back(2); // Construct a BrowserAccessibilityManager with this // AccessibilityNodeData tree and a factory for an instance-counting @@ -255,15 +261,15 @@ TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) { root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(div, text3, text4); ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); // Notify the BrowserAccessibilityManager that the div node and its children // were removed and ensure that only one BrowserAccessibility instance exists. - root.children.clear(); + root.child_ids.clear(); AccessibilityHostMsg_NotificationParams param; param.notification_type = AccessibilityNotificationChildrenChanged; - param.acc_tree = root; - param.includes_children = true; + param.nodes.push_back(root); param.id = root.id; std::vector<AccessibilityHostMsg_NotificationParams> notifications; notifications.push_back(param); @@ -286,14 +292,15 @@ TEST_F(BrowserAccessibilityTest, TestTextBoundaries) { AccessibilityNodeData root; root.id = 1; - root.role = AccessibilityNodeData::ROLE_DOCUMENT; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; root.state = 0; - root.children.push_back(text1); + root.child_ids.push_back(11); CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(text1); ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_); BrowserAccessibilityWin* root_obj = @@ -381,15 +388,16 @@ TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) { AccessibilityNodeData root; root.id = 1; - root.role = AccessibilityNodeData::ROLE_DOCUMENT; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; root.state = 1 << AccessibilityNodeData::STATE_READONLY; - root.children.push_back(text1); - root.children.push_back(text2); + root.child_ids.push_back(11); + root.child_ids.push_back(12); CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(root, text1, text2); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); BrowserAccessibilityWin* root_obj = @@ -451,7 +459,7 @@ TEST_F(BrowserAccessibilityTest, TestComplexHypertext) { button1_text.role = AccessibilityNodeData::ROLE_STATIC_TEXT; button1.state = 1 << AccessibilityNodeData::STATE_READONLY; button1_text.state = 1 << AccessibilityNodeData::STATE_READONLY; - button1.children.push_back(button1_text); + button1.child_ids.push_back(15); AccessibilityNodeData link1, link1_text; link1.id = 14; @@ -461,21 +469,25 @@ TEST_F(BrowserAccessibilityTest, TestComplexHypertext) { link1_text.role = AccessibilityNodeData::ROLE_STATIC_TEXT; link1.state = 1 << AccessibilityNodeData::STATE_READONLY; link1_text.state = 1 << AccessibilityNodeData::STATE_READONLY; - link1.children.push_back(link1_text); + link1.child_ids.push_back(16); AccessibilityNodeData root; root.id = 1; - root.role = AccessibilityNodeData::ROLE_DOCUMENT; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; root.state = 1 << AccessibilityNodeData::STATE_READONLY; - root.children.push_back(text1); - root.children.push_back(button1); - root.children.push_back(text2); - root.children.push_back(link1); + root.child_ids.push_back(11); + root.child_ids.push_back(13); + root.child_ids.push_back(12); + root.child_ids.push_back(14); CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( GetDesktopWindow(), root, NULL, new CountedBrowserAccessibilityFactory()); + manager->UpdateNodesForTesting(root, + text1, button1, button1_text, + text2, link1, link1_text); + ASSERT_EQ(7, CountedBrowserAccessibility::global_obj_count_); BrowserAccessibilityWin* root_obj = diff --git a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc index 0ce2b50..9fe99c4 100644 --- a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc +++ b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc @@ -38,7 +38,7 @@ class CrossPlatformAccessibilityBrowserTest : public ContentBrowserTest { // Tell the renderer to send an accessibility tree, then wait for the // notification that it's been received. - const AccessibilityNodeData& GetAccessibilityNodeDataTree( + const AccessibilityNodeDataTreeNode& GetAccessibilityNodeDataTree( AccessibilityMode accessibility_mode = AccessibilityModeComplete) { scoped_refptr<MessageLoopRunner> loop_runner(new MessageLoopRunner); RenderWidgetHostView* host_view = @@ -56,7 +56,7 @@ class CrossPlatformAccessibilityBrowserTest : public ContentBrowserTest { // Make sure each node in the tree has an unique id. void RecursiveAssertUniqueIds( - const AccessibilityNodeData& node, base::hash_set<int>* ids) { + const AccessibilityNodeDataTreeNode& node, base::hash_set<int>* ids) { ASSERT_TRUE(ids->find(node.id) == ids->end()); ids->insert(node.id); for (size_t i = 0; i < node.children.size(); i++) @@ -148,7 +148,7 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, "</body></html>"; GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); // Check properties of the root element of the tree. EXPECT_STREQ(url_str, @@ -166,7 +166,7 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, // Check properites of the BODY element. ASSERT_EQ(1U, tree.children.size()); - const AccessibilityNodeData& body = tree.children[0]; + const AccessibilityNodeDataTreeNode& body = tree.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_GROUP, body.role); EXPECT_STREQ("body", GetAttr(body, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); @@ -176,7 +176,7 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, // Check properties of the two children of the BODY element. ASSERT_EQ(2U, body.children.size()); - const AccessibilityNodeData& button = body.children[0]; + const AccessibilityNodeDataTreeNode& button = body.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button.role); EXPECT_STREQ( "input", GetAttr(button, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); @@ -190,7 +190,7 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, EXPECT_STREQ("value", UTF16ToUTF8(button.html_attributes[1].first).c_str()); EXPECT_STREQ("push", UTF16ToUTF8(button.html_attributes[1].second).c_str()); - const AccessibilityNodeData& checkbox = body.children[1]; + const AccessibilityNodeDataTreeNode& checkbox = body.children[1]; EXPECT_EQ(AccessibilityNodeData::ROLE_CHECKBOX, checkbox.role); EXPECT_STREQ( "input", GetAttr(checkbox, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); @@ -216,11 +216,11 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); ASSERT_EQ(1U, tree.children.size()); - const AccessibilityNodeData& body = tree.children[0]; + const AccessibilityNodeDataTreeNode& body = tree.children[0]; ASSERT_EQ(1U, body.children.size()); - const AccessibilityNodeData& text = body.children[0]; + const AccessibilityNodeDataTreeNode& text = body.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_TEXT_FIELD, text.role); EXPECT_STREQ( "input", GetAttr(text, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); @@ -245,11 +245,11 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); ASSERT_EQ(1U, tree.children.size()); - const AccessibilityNodeData& body = tree.children[0]; + const AccessibilityNodeDataTreeNode& body = tree.children[0]; ASSERT_EQ(1U, body.children.size()); - const AccessibilityNodeData& text = body.children[0]; + const AccessibilityNodeDataTreeNode& text = body.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_TEXT_FIELD, text.role); EXPECT_STREQ( "input", GetAttr(text, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); @@ -272,22 +272,22 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); ASSERT_EQ(1U, tree.children.size()); - const AccessibilityNodeData& table = tree.children[0]; + const AccessibilityNodeDataTreeNode& table = tree.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_TABLE, table.role); - const AccessibilityNodeData& row = table.children[0]; + const AccessibilityNodeDataTreeNode& row = table.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_ROW, row.role); - const AccessibilityNodeData& cell1 = row.children[0]; + const AccessibilityNodeDataTreeNode& cell1 = row.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_CELL, cell1.role); - const AccessibilityNodeData& cell2 = row.children[1]; + const AccessibilityNodeDataTreeNode& cell2 = row.children[1]; EXPECT_EQ(AccessibilityNodeData::ROLE_CELL, cell2.role); - const AccessibilityNodeData& column1 = table.children[1]; + const AccessibilityNodeDataTreeNode& column1 = table.children[1]; EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, column1.role); EXPECT_EQ(0U, column1.children.size()); EXPECT_EQ(1U, column1.indirect_child_ids.size()); EXPECT_EQ(cell1.id, column1.indirect_child_ids[0]); - const AccessibilityNodeData& column2 = table.children[2]; + const AccessibilityNodeDataTreeNode& column2 = table.children[2]; EXPECT_EQ(AccessibilityNodeData::ROLE_COLUMN, column2.role); EXPECT_EQ(0U, column2.children.size()); EXPECT_EQ(1U, column2.indirect_child_ids.size()); @@ -311,7 +311,7 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); base::hash_set<int> ids; RecursiveAssertUniqueIds(tree, &ids); } @@ -331,36 +331,36 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); ASSERT_EQ(1U, tree.children.size()); - const AccessibilityNodeData& body = tree.children[0]; + const AccessibilityNodeDataTreeNode& body = tree.children[0]; ASSERT_EQ(3U, body.children.size()); - const AccessibilityNodeData& button1 = body.children[0]; + const AccessibilityNodeDataTreeNode& button1 = body.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button1.role); EXPECT_STREQ("Button 1", UTF16ToUTF8(button1.name).c_str()); - const AccessibilityNodeData& iframe = body.children[1]; + const AccessibilityNodeDataTreeNode& iframe = body.children[1]; EXPECT_STREQ("iframe", GetAttr(iframe, AccessibilityNodeData::ATTR_HTML_TAG).c_str()); ASSERT_EQ(1U, iframe.children.size()); - const AccessibilityNodeData& scroll_area = iframe.children[0]; + const AccessibilityNodeDataTreeNode& scroll_area = iframe.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_SCROLLAREA, scroll_area.role); ASSERT_EQ(1U, scroll_area.children.size()); - const AccessibilityNodeData& sub_document = scroll_area.children[0]; + const AccessibilityNodeDataTreeNode& sub_document = scroll_area.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_WEB_AREA, sub_document.role); ASSERT_EQ(1U, sub_document.children.size()); - const AccessibilityNodeData& sub_body = sub_document.children[0]; + const AccessibilityNodeDataTreeNode& sub_body = sub_document.children[0]; ASSERT_EQ(1U, sub_body.children.size()); - const AccessibilityNodeData& button2 = sub_body.children[0]; + const AccessibilityNodeDataTreeNode& button2 = sub_body.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button2.role); EXPECT_STREQ("Button 2", UTF16ToUTF8(button2.name).c_str()); - const AccessibilityNodeData& button3 = body.children[2]; + const AccessibilityNodeDataTreeNode& button3 = body.children[2]; EXPECT_EQ(AccessibilityNodeData::ROLE_BUTTON, button3.role); EXPECT_STREQ("Button 3", UTF16ToUTF8(button3.name).c_str()); } @@ -377,7 +377,7 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); base::hash_set<int> ids; RecursiveAssertUniqueIds(tree, &ids); } @@ -404,8 +404,8 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); - const AccessibilityNodeData& table = tree.children[0]; + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& table = tree.children[0]; EXPECT_EQ(AccessibilityNodeData::ROLE_TABLE, table.role); ASSERT_GE(table.children.size(), 5U); EXPECT_EQ(AccessibilityNodeData::ROLE_ROW, table.children[0].role); @@ -417,10 +417,10 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, GetIntAttr(table, AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT)); EXPECT_EQ(2, GetIntAttr(table, AccessibilityNodeData::ATTR_TABLE_ROW_COUNT)); - const AccessibilityNodeData& cell1 = table.children[0].children[0]; - const AccessibilityNodeData& cell2 = table.children[0].children[1]; - const AccessibilityNodeData& cell3 = table.children[1].children[0]; - const AccessibilityNodeData& cell4 = table.children[1].children[1]; + const AccessibilityNodeDataTreeNode& cell1 = table.children[0].children[0]; + const AccessibilityNodeDataTreeNode& cell2 = table.children[0].children[1]; + const AccessibilityNodeDataTreeNode& cell3 = table.children[1].children[0]; + const AccessibilityNodeDataTreeNode& cell4 = table.children[1].children[1]; ASSERT_EQ(6U, table.cell_ids.size()); EXPECT_EQ(cell1.id, table.cell_ids[0]); @@ -462,10 +462,10 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest, "</div>"; GURL url(url_str); NavigateToURL(shell(), url); - const AccessibilityNodeData& tree = GetAccessibilityNodeDataTree(); + const AccessibilityNodeDataTreeNode& tree = GetAccessibilityNodeDataTree(); ASSERT_EQ(1U, tree.children.size()); - const AccessibilityNodeData& textbox = tree.children[0]; + const AccessibilityNodeDataTreeNode& textbox = tree.children[0]; EXPECT_EQ( true, GetBoolAttr(textbox, AccessibilityNodeData::ATTR_CAN_SET_VALUE)); diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc index 71c2275..40f36c0 100644 --- a/content/browser/renderer_host/render_view_host_impl.cc +++ b/content/browser/renderer_host/render_view_host_impl.cc @@ -1902,7 +1902,7 @@ void RenderViewHostImpl::OnAccessibilityNotifications( if ((src_type == AccessibilityNotificationLayoutComplete || src_type == AccessibilityNotificationLoadComplete) && save_accessibility_tree_for_testing_) { - accessibility_tree_ = param.acc_tree; + MakeAccessibilityNodeDataTree(param.nodes, &accessibility_tree_); } if (src_type == AccessibilityNotificationLayoutComplete) { diff --git a/content/browser/renderer_host/render_view_host_impl.h b/content/browser/renderer_host/render_view_host_impl.h index 1ee4483..2a0c8a2 100644 --- a/content/browser/renderer_host/render_view_host_impl.h +++ b/content/browser/renderer_host/render_view_host_impl.h @@ -418,7 +418,7 @@ class CONTENT_EXPORT RenderViewHostImpl save_accessibility_tree_for_testing_ = save; } - const AccessibilityNodeData& accessibility_tree_for_testing() { + const AccessibilityNodeDataTreeNode& accessibility_tree_for_testing() { return accessibility_tree_; } @@ -675,7 +675,7 @@ class CONTENT_EXPORT RenderViewHostImpl std::string frame_tree_; // The most recently received accessibility tree - for unit testing only. - AccessibilityNodeData accessibility_tree_; + AccessibilityNodeDataTreeNode accessibility_tree_; // The termination status of the last render view that terminated. base::TerminationStatus render_view_termination_status_; diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc index e0c3d28..176784c 100644 --- a/content/browser/renderer_host/render_widget_host_impl.cc +++ b/content/browser/renderer_host/render_widget_host_impl.cc @@ -2246,6 +2246,10 @@ void RenderWidgetHostImpl::AccessibilitySetTextSelection( GetRoutingID(), object_id, start_offset, end_offset)); } +void RenderWidgetHostImpl::FatalAccessibilityTreeError() { + Send(new AccessibilityMsg_FatalError(GetRoutingID())); +} + void RenderWidgetHostImpl::ExecuteEditCommand(const std::string& command, const std::string& value) { Send(new ViewMsg_ExecuteEditCommand(GetRoutingID(), command, value)); diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h index 201f258..61d8bfd 100644 --- a/content/browser/renderer_host/render_widget_host_impl.h +++ b/content/browser/renderer_host/render_widget_host_impl.h @@ -355,6 +355,9 @@ class CONTENT_EXPORT RenderWidgetHostImpl : virtual public RenderWidgetHost, void AccessibilitySetTextSelection( int acc_obj_id, int start_offset, int end_offset); + // Kill the renderer because we got a fatal accessibility error. + void FatalAccessibilityTreeError(); + // Executes the edit command on the RenderView. void ExecuteEditCommand(const std::string& command, const std::string& value); diff --git a/content/browser/renderer_host/render_widget_host_view_gtk.cc b/content/browser/renderer_host/render_widget_host_view_gtk.cc index bb3351e..6f525f3 100644 --- a/content/browser/renderer_host/render_widget_host_view_gtk.cc +++ b/content/browser/renderer_host/render_widget_host_view_gtk.cc @@ -1571,6 +1571,15 @@ gfx::Point RenderWidgetHostViewGtk::GetLastTouchEventLocation() const { return gfx::Point(); } +void RenderWidgetHostViewGtk::FatalAccessibilityTreeError() { + if (host_) { + host_->FatalAccessibilityTreeError(); + SetBrowserAccessibilityManager(NULL); + } else { + CHECK(FALSE); + } +} + void RenderWidgetHostViewGtk::OnAccessibilityNotifications( const std::vector<AccessibilityHostMsg_NotificationParams>& params) { if (!browser_accessibility_manager_.get()) { diff --git a/content/browser/renderer_host/render_widget_host_view_gtk.h b/content/browser/renderer_host/render_widget_host_view_gtk.h index b998cf4..46daaac 100644 --- a/content/browser/renderer_host/render_widget_host_view_gtk.h +++ b/content/browser/renderer_host/render_widget_host_view_gtk.h @@ -175,6 +175,7 @@ class CONTENT_EXPORT RenderWidgetHostViewGtk virtual void AccessibilitySetTextSelection( int acc_obj_id, int start_offset, int end_offset) OVERRIDE; virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE; + virtual void FatalAccessibilityTreeError() OVERRIDE; // Get the root of the AtkObject* tree for accessibility. AtkObject* GetAccessible(); diff --git a/content/browser/renderer_host/render_widget_host_view_win.cc b/content/browser/renderer_host/render_widget_host_view_win.cc index 6500a8b..a5cb6cf 100644 --- a/content/browser/renderer_host/render_widget_host_view_win.cc +++ b/content/browser/renderer_host/render_widget_host_view_win.cc @@ -2592,6 +2592,11 @@ gfx::Point RenderWidgetHostViewWin::GetLastTouchEventLocation() const { return last_touch_location_; } +void RenderWidgetHostViewWin::FatalAccessibilityTreeError() { + render_widget_host_->FatalAccessibilityTreeError(); + SetBrowserAccessibilityManager(NULL); +} + LRESULT RenderWidgetHostViewWin::OnGetObject(UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { TRACE_EVENT0("browser", "RenderWidgetHostViewWin::OnGetObject"); diff --git a/content/browser/renderer_host/render_widget_host_view_win.h b/content/browser/renderer_host/render_widget_host_view_win.h index 8c9a768..6f8c236 100644 --- a/content/browser/renderer_host/render_widget_host_view_win.h +++ b/content/browser/renderer_host/render_widget_host_view_win.h @@ -250,6 +250,7 @@ class RenderWidgetHostViewWin virtual void AccessibilitySetTextSelection( int acc_obj_id, int start_offset, int end_offset) OVERRIDE; virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE; + virtual void FatalAccessibilityTreeError() OVERRIDE; // Overridden from ui::GestureEventHelper. virtual bool DispatchLongPressGestureEvent(ui::GestureEvent* event) OVERRIDE; diff --git a/content/common/accessibility_messages.h b/content/common/accessibility_messages.h index c74907a..2cbc24d 100644 --- a/content/common/accessibility_messages.h +++ b/content/common/accessibility_messages.h @@ -128,7 +128,7 @@ IPC_STRUCT_TRAITS_BEGIN(content::AccessibilityNodeData) IPC_STRUCT_TRAITS_MEMBER(int_attributes) IPC_STRUCT_TRAITS_MEMBER(float_attributes) IPC_STRUCT_TRAITS_MEMBER(bool_attributes) - IPC_STRUCT_TRAITS_MEMBER(children) + IPC_STRUCT_TRAITS_MEMBER(child_ids) IPC_STRUCT_TRAITS_MEMBER(indirect_child_ids) IPC_STRUCT_TRAITS_MEMBER(html_attributes) IPC_STRUCT_TRAITS_MEMBER(line_breaks) @@ -137,18 +137,15 @@ IPC_STRUCT_TRAITS_BEGIN(content::AccessibilityNodeData) IPC_STRUCT_TRAITS_END() IPC_STRUCT_BEGIN(AccessibilityHostMsg_NotificationParams) + // Vector of nodes in the tree that need to be updated before + // sending the notification. + IPC_STRUCT_MEMBER(std::vector<content::AccessibilityNodeData>, nodes) + // Type of notification. IPC_STRUCT_MEMBER(AccessibilityNotification, notification_type) // ID of the node that the notification applies to. IPC_STRUCT_MEMBER(int, id) - - // The accessibility node tree. - IPC_STRUCT_MEMBER(content::AccessibilityNodeData, acc_tree) - - // Whether children are included in this tree, otherwise it's just an - // update to this one node and existing children are left in place. - IPC_STRUCT_MEMBER(bool, includes_children) IPC_STRUCT_END() // Messages sent from the browser to the renderer. @@ -189,6 +186,10 @@ IPC_MESSAGE_ROUTED3(AccessibilityMsg_SetTextSelection, // message was processed and it can send addition notifications. IPC_MESSAGE_ROUTED0(AccessibilityMsg_Notifications_ACK) + +// Kill the renderer because we got a fatal error in the accessibility tree. +IPC_MESSAGE_ROUTED0(AccessibilityMsg_FatalError) + // Messages sent from the renderer to the browser. // Sent to notify the browser about renderer accessibility notifications. diff --git a/content/common/accessibility_node_data.cc b/content/common/accessibility_node_data.cc index 6c87d72..a34b376 100644 --- a/content/common/accessibility_node_data.cc +++ b/content/common/accessibility_node_data.cc @@ -6,6 +6,7 @@ #include <set> +#include "base/hash_tables.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" @@ -40,13 +41,70 @@ AccessibilityNodeData::AccessibilityNodeData() AccessibilityNodeData::~AccessibilityNodeData() { } +AccessibilityNodeDataTreeNode::AccessibilityNodeDataTreeNode() + : AccessibilityNodeData() { +} + +AccessibilityNodeDataTreeNode::~AccessibilityNodeDataTreeNode() { +} + +AccessibilityNodeDataTreeNode& AccessibilityNodeDataTreeNode::operator=( + const AccessibilityNodeData& src) { + AccessibilityNodeData::operator=(src); + return *this; +} + +void MakeAccessibilityNodeDataTree( + const std::vector<AccessibilityNodeData>& src_vector, + AccessibilityNodeDataTreeNode* dst_root) { + // This method assumes |src_vector| contains all of the nodes of + // an accessibility tree, and that each parent comes before its + // children. Each node has an id, and the ids of its children. + // The output is a tree where each node contains its children. + + // Initialize a hash map with all of the ids in |src_vector|. + base::hash_map<int32, AccessibilityNodeDataTreeNode*> id_map; + for (size_t i = 0; i < src_vector.size(); ++i) + id_map[src_vector[i].id] = NULL; + + // Copy the nodes to the output tree one at a time. + for (size_t i = 0; i < src_vector.size(); ++i) { + const AccessibilityNodeData& src_node = src_vector[i]; + AccessibilityNodeDataTreeNode* dst_node; + + // If it's the first element in the vector, assume it's + // the root. For any other element, look for it in our + // hash map, and skip it if not there (meaning there was + // an extranous node, or the nodes were sent in the wrong + // order). + if (i == 0) { + dst_node = dst_root; + } else { + dst_node = id_map[src_node.id]; + if (!dst_node) + continue; + } + + // Copy the node data. + *dst_node = src_node; + + // Add placeholders for all of the node's children in the tree, + // and add them to the hash map so we can find them when we + // encounter them in |src_vector|. + dst_node->children.reserve(src_node.child_ids.size()); + for (size_t j = 0; j < src_node.child_ids.size(); ++j) { + int child_id = src_node.child_ids[j]; + if (id_map.find(child_id) != id_map.end()) { + dst_node->children.push_back(AccessibilityNodeDataTreeNode()); + id_map[child_id] = &dst_node->children.back(); + } + } + } +} + #ifndef NDEBUG std::string AccessibilityNodeData::DebugString(bool recursive) const { std::string result; - static int indent = 0; - result += "\n"; - for (int i = 0; i < indent; ++i) - result += " "; result += "id=" + IntToString(id); @@ -407,8 +465,8 @@ std::string AccessibilityNodeData::DebugString(bool recursive) const { } } - if (!children.empty()) - result += " children=" + IntToString(children.size()); + if (!child_ids.empty()) + result += " child_ids=" + IntVectorToString(child_ids); if (!indirect_child_ids.empty()) result += " indirect_child_ids=" + IntVectorToString(indirect_child_ids); @@ -419,6 +477,19 @@ std::string AccessibilityNodeData::DebugString(bool recursive) const { if (!cell_ids.empty()) result += " cell_ids=" + IntVectorToString(cell_ids); + return result; +} + +std::string AccessibilityNodeDataTreeNode::DebugString(bool recursive) const { + std::string result; + + static int indent = 0; + result += "\n"; + for (int i = 0; i < indent; ++i) + result += " "; + + result += AccessibilityNodeData::DebugString(recursive); + if (recursive) { result += "\n"; ++indent; @@ -429,6 +500,7 @@ std::string AccessibilityNodeData::DebugString(bool recursive) const { return result; } + #endif // ifndef NDEBUG } // namespace content diff --git a/content/common/accessibility_node_data.h b/content/common/accessibility_node_data.h index 2fd9271..94eeb2e 100644 --- a/content/common/accessibility_node_data.h +++ b/content/common/accessibility_node_data.h @@ -264,10 +264,10 @@ struct CONTENT_EXPORT AccessibilityNodeData { }; AccessibilityNodeData(); - ~AccessibilityNodeData(); + virtual ~AccessibilityNodeData(); #ifndef NDEBUG - std::string DebugString(bool recursive) const; + virtual std::string DebugString(bool recursive) const; #endif // This is a simple serializable struct. All member variables should be @@ -282,7 +282,7 @@ struct CONTENT_EXPORT AccessibilityNodeData { std::map<IntAttribute, int32> int_attributes; std::map<FloatAttribute, float> float_attributes; std::map<BoolAttribute, bool> bool_attributes; - std::vector<AccessibilityNodeData> children; + std::vector<int32> child_ids; std::vector<int32> indirect_child_ids; std::vector<std::pair<string16, string16> > html_attributes; std::vector<int32> line_breaks; @@ -297,6 +297,33 @@ struct CONTENT_EXPORT AccessibilityNodeData { std::vector<int32> unique_cell_ids; }; +// For testing and debugging only: this subclass of AccessibilityNodeData +// is used to represent a whole tree of accessibility nodes, where each +// node owns its children. This makes it easy to print the tree structure +// or search it recursively. +struct CONTENT_EXPORT AccessibilityNodeDataTreeNode + : public AccessibilityNodeData { + AccessibilityNodeDataTreeNode(); + virtual ~AccessibilityNodeDataTreeNode(); + + AccessibilityNodeDataTreeNode& operator=(const AccessibilityNodeData& src); + + #ifndef NDEBUG + virtual std::string DebugString(bool recursive) const OVERRIDE; + #endif + + std::vector<AccessibilityNodeDataTreeNode> children; +}; + +// Given a vector of accessibility nodes that represent a complete +// accessibility tree, where each node appears before its children, +// build a tree of AccessibilityNodeDataTreeNode objects for easier +// testing and debugging, where each node contains its children. +// The |dst| argument will become the root of the new tree. +void MakeAccessibilityNodeDataTree( + const std::vector<AccessibilityNodeData>& src, + AccessibilityNodeDataTreeNode* dst); + } // namespace content #endif // CONTENT_COMMON_ACCESSIBILITY_NODE_DATA_H_ diff --git a/content/renderer/accessibility/accessibility_node_serializer.cc b/content/renderer/accessibility/accessibility_node_serializer.cc index 613a453..cb8f8f7f 100644 --- a/content/renderer/accessibility/accessibility_node_serializer.cc +++ b/content/renderer/accessibility/accessibility_node_serializer.cc @@ -350,8 +350,7 @@ uint32 ConvertState(const WebAccessibilityObject& o) { void SerializeAccessibilityNode( const WebAccessibilityObject& src, - AccessibilityNodeData* dst, - bool include_children) { + AccessibilityNodeData* dst) { dst->name = src.title(); dst->role = ConvertRole(src.roleValue()); dst->state = ConvertState(src); @@ -404,9 +403,6 @@ void SerializeAccessibilityNode( dst->int_attributes[dst->ATTR_HIERARCHICAL_LEVEL] = src.hierarchicalLevel(); } - if (dst->role == dst->ROLE_SLIDER) - include_children = false; - // Treat the active list box item as focused. if (dst->role == dst->ROLE_LISTBOX_OPTION && src.isSelectedOptionActive()) dst->state |= (1 << AccessibilityNodeData::STATE_FOCUSED); @@ -440,9 +436,6 @@ void SerializeAccessibilityNode( if (dst->role == dst->ROLE_EDITABLE_TEXT || dst->role == dst->ROLE_TEXTAREA || dst->role == dst->ROLE_TEXT_FIELD) { - // Jaws gets confused by children of text fields, so we ignore them. - include_children = false; - dst->int_attributes[dst->ATTR_TEXT_SEL_START] = src.selectionStart(); dst->int_attributes[dst->ATTR_TEXT_SEL_END] = src.selectionEnd(); @@ -590,46 +583,49 @@ void SerializeAccessibilityNode( dst->int_attributes[dst->ATTR_TABLE_CELL_ROW_SPAN] = src.cellRowSpan(); } - if (include_children) { - // Recursively create children. - int child_count = src.childCount(); - std::set<int32> child_ids; - for (int i = 0; i < child_count; ++i) { - WebAccessibilityObject child = src.childAt(i); - int32 child_id = child.axID(); - - // The child may be invalid due to issues in webkit accessibility code. - // Don't add children that are invalid thus preventing a crash. - // https://bugs.webkit.org/show_bug.cgi?id=44149 - // TODO(ctguil): We may want to remove this check as webkit stabilizes. - if (child.isDetached()) - continue; - - // Children may duplicated in the webkit accessibility tree. Only add a - // child once for the web accessibility tree. - // https://bugs.webkit.org/show_bug.cgi?id=58930 - if (child_ids.find(child_id) != child_ids.end()) - continue; - child_ids.insert(child_id); - - // Some nodes appear in the tree in more than one place: for example, - // a cell in a table appears as a child of both a row and a column. - // Only recursively add child nodes that have this node as its - // unignored parent. For child nodes that are actually parented to - // somethinng else, store only the ID. - // - // As an exception, also add children of an iframe element. - // https://bugs.webkit.org/show_bug.cgi?id=57066 - if (is_iframe || IsParentUnignoredOf(src, child)) { - dst->children.push_back(AccessibilityNodeData()); - SerializeAccessibilityNode(child, - &dst->children.back(), - include_children); - } else { - dst->indirect_child_ids.push_back(child_id); - } - } + // Add the ids of *indirect* children - those who are children of this node, + // but whose parent is *not* this node. One example is a table + // cell, which is a child of both a row and a column. Because the cell's + // parent is the row, the row adds it as a child, and the column adds it + // as an indirect child. + int child_count = src.childCount(); + for (int i = 0; i < child_count; ++i) { + WebAccessibilityObject child = src.childAt(i); + if (!is_iframe && !child.isDetached() && !IsParentUnignoredOf(src, child)) + dst->indirect_child_ids.push_back(child.axID()); } } +bool ShouldIncludeChildNode( + const WebAccessibilityObject& parent, + const WebAccessibilityObject& child) { + switch(parent.roleValue()) { + case WebKit::WebAccessibilityRoleSlider: + case WebKit::WebAccessibilityRoleEditableText: + case WebKit::WebAccessibilityRoleTextArea: + case WebKit::WebAccessibilityRoleTextField: + return false; + default: + break; + } + + // The child may be invalid due to issues in webkit accessibility code. + // Don't add children that are invalid thus preventing a crash. + // https://bugs.webkit.org/show_bug.cgi?id=44149 + // TODO(ctguil): We may want to remove this check as webkit stabilizes. + if (child.isDetached()) + return false; + + // Skip children whose parent isn't this - see indirect_child_ids, above. + // As an exception, include children of an iframe element. + bool is_iframe = false; + WebNode node = parent.node(); + if (!node.isNull() && node.isElementNode()) { + WebElement element = node.to<WebElement>(); + is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME")); + } + + return (is_iframe || IsParentUnignoredOf(parent, child)); +} + } // namespace content diff --git a/content/renderer/accessibility/accessibility_node_serializer.h b/content/renderer/accessibility/accessibility_node_serializer.h index d0fd74b..f0e9525 100644 --- a/content/renderer/accessibility/accessibility_node_serializer.h +++ b/content/renderer/accessibility/accessibility_node_serializer.h @@ -12,8 +12,11 @@ namespace content { void SerializeAccessibilityNode( const WebKit::WebAccessibilityObject& src, - AccessibilityNodeData* dst, - bool include_children); + AccessibilityNodeData* dst); + +bool ShouldIncludeChildNode( + const WebKit::WebAccessibilityObject& parent, + const WebKit::WebAccessibilityObject& child); } // namespace content diff --git a/content/renderer/accessibility/renderer_accessibility.h b/content/renderer/accessibility/renderer_accessibility.h index 8f33d2b..b4b56dd 100644 --- a/content/renderer/accessibility/renderer_accessibility.h +++ b/content/renderer/accessibility/renderer_accessibility.h @@ -49,7 +49,7 @@ class RenderViewImpl; // // What both subclasses have in common is that they are responsible for // -class RendererAccessibility : public RenderViewObserver { +class CONTENT_EXPORT RendererAccessibility : public RenderViewObserver { public: explicit RendererAccessibility(RenderViewImpl* render_view); virtual ~RendererAccessibility(); diff --git a/content/renderer/accessibility/renderer_accessibility_browsertest.cc b/content/renderer/accessibility/renderer_accessibility_browsertest.cc index 304822e..fbd83367 100644 --- a/content/renderer/accessibility/renderer_accessibility_browsertest.cc +++ b/content/renderer/accessibility/renderer_accessibility_browsertest.cc @@ -3,17 +3,57 @@ // found in the LICENSE file. #include "base/utf_string_conversions.h" -#include "content/common/accessibility_messages.h" #include "content/common/accessibility_node_data.h" #include "content/common/view_messages.h" #include "content/public/test/render_view_test.h" +#include "content/renderer/accessibility/renderer_accessibility_complete.h" #include "content/renderer/render_view_impl.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/Source/Platform/chromium/public/WebSize.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityObject.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +using WebKit::WebAccessibilityObject; +using WebKit::WebDocument; + namespace content { +class TestRendererAccessibilityComplete : public RendererAccessibilityComplete { + public: + explicit TestRendererAccessibilityComplete(RenderViewImpl* render_view) + : RendererAccessibilityComplete(render_view), + browser_tree_node_count_(0) { + } + + int browser_tree_node_count() { return browser_tree_node_count_; } + + struct TestBrowserTreeNode : public BrowserTreeNode { + TestBrowserTreeNode(TestRendererAccessibilityComplete* owner) + : owner_(owner) { + owner_->browser_tree_node_count_++; + } + + virtual ~TestBrowserTreeNode() { + owner_->browser_tree_node_count_--; + } + + private: + TestRendererAccessibilityComplete* owner_; + }; + + virtual BrowserTreeNode* CreateBrowserTreeNode() { + return new TestBrowserTreeNode(this); + } + + void SendPendingAccessibilityNotifications() { + RendererAccessibilityComplete::SendPendingAccessibilityNotifications(); + } + +private: + int browser_tree_node_count_; +}; + class RendererAccessibilityTest : public RenderViewTest { public: RendererAccessibilityTest() {} @@ -38,10 +78,16 @@ class RendererAccessibilityTest : public RenderViewTest { ASSERT_TRUE(message); Tuple1<std::vector<AccessibilityHostMsg_NotificationParams> > param; AccessibilityHostMsg_Notifications::Read(message, ¶m); - ASSERT_EQ(param.a.size(), 1U); + ASSERT_GE(param.a.size(), 1U); *params = param.a[0]; } + int CountAccessibilityNodesSentToBrowser() { + AccessibilityHostMsg_NotificationParams notification; + GetLastAccNotification(¬ification); + return notification.nodes.size(); + } + protected: IPC::TestSink* sink_; @@ -49,6 +95,10 @@ class RendererAccessibilityTest : public RenderViewTest { }; TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { + // This is not a test of true web accessibility, it's a test of + // a mode used on Windows 8 in Metro mode where an extremely simplified + // accessibility tree containing only the current focused node is + // generated. SetMode(AccessibilityModeEditableTextOnly); // Set a minimum size and give focus so simulated events work. @@ -76,16 +126,16 @@ TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { GetLastAccNotification(¬ification); EXPECT_EQ(notification.notification_type, AccessibilityNotificationLayoutComplete); - EXPECT_EQ(notification.includes_children, true); EXPECT_EQ(notification.id, 1); - EXPECT_EQ(notification.acc_tree.id, 1); - EXPECT_EQ(notification.acc_tree.role, + EXPECT_EQ(notification.nodes.size(), 2U); + EXPECT_EQ(notification.nodes[0].id, 1); + EXPECT_EQ(notification.nodes[0].role, AccessibilityNodeData::ROLE_ROOT_WEB_AREA); - EXPECT_EQ(notification.acc_tree.state, + EXPECT_EQ(notification.nodes[0].state, (1U << AccessibilityNodeData::STATE_READONLY) | (1U << AccessibilityNodeData::STATE_FOCUSABLE) | (1U << AccessibilityNodeData::STATE_FOCUSED)); - EXPECT_EQ(notification.acc_tree.children.size(), 1U); + EXPECT_EQ(notification.nodes[0].child_ids.size(), 1U); } // Now focus the input element, and check everything again. @@ -97,19 +147,18 @@ TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { GetLastAccNotification(¬ification); EXPECT_EQ(notification.notification_type, AccessibilityNotificationFocusChanged); - EXPECT_EQ(notification.includes_children, true); EXPECT_EQ(notification.id, 3); - EXPECT_EQ(notification.acc_tree.id, 1); - EXPECT_EQ(notification.acc_tree.role, + EXPECT_EQ(notification.nodes[0].id, 1); + EXPECT_EQ(notification.nodes[0].role, AccessibilityNodeData::ROLE_ROOT_WEB_AREA); - EXPECT_EQ(notification.acc_tree.state, + EXPECT_EQ(notification.nodes[0].state, (1U << AccessibilityNodeData::STATE_READONLY) | (1U << AccessibilityNodeData::STATE_FOCUSABLE)); - EXPECT_EQ(notification.acc_tree.children.size(), 1U); - EXPECT_EQ(notification.acc_tree.children[0].id, 3); - EXPECT_EQ(notification.acc_tree.children[0].role, + EXPECT_EQ(notification.nodes[0].child_ids.size(), 1U); + EXPECT_EQ(notification.nodes[1].id, 3); + EXPECT_EQ(notification.nodes[1].role, AccessibilityNodeData::ROLE_GROUP); - EXPECT_EQ(notification.acc_tree.children[0].state, + EXPECT_EQ(notification.nodes[1].state, (1U << AccessibilityNodeData::STATE_FOCUSABLE) | (1U << AccessibilityNodeData::STATE_FOCUSED)); } @@ -122,7 +171,7 @@ TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { AccessibilityHostMsg_NotificationParams notification; GetLastAccNotification(¬ification); EXPECT_EQ(notification.id, 4); - EXPECT_EQ(notification.acc_tree.children[0].state, + EXPECT_EQ(notification.nodes[1].state, (1U << AccessibilityNodeData::STATE_FOCUSABLE) | (1U << AccessibilityNodeData::STATE_FOCUSED)); } @@ -134,7 +183,7 @@ TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { AccessibilityHostMsg_NotificationParams notification; GetLastAccNotification(¬ification); EXPECT_EQ(notification.id, 5); - EXPECT_EQ(notification.acc_tree.children[0].state, + EXPECT_EQ(notification.nodes[1].state, (1U << AccessibilityNodeData::STATE_FOCUSABLE) | (1U << AccessibilityNodeData::STATE_FOCUSED)); } @@ -146,7 +195,7 @@ TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { AccessibilityHostMsg_NotificationParams notification; GetLastAccNotification(¬ification); EXPECT_EQ(notification.id, 6); - EXPECT_EQ(notification.acc_tree.children[0].state, + EXPECT_EQ(notification.nodes[1].state, (1U << AccessibilityNodeData::STATE_FOCUSABLE) | (1U << AccessibilityNodeData::STATE_FOCUSED)); } @@ -159,7 +208,7 @@ TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { AccessibilityHostMsg_NotificationParams notification; GetLastAccNotification(¬ification); EXPECT_EQ(notification.id, 7); - EXPECT_EQ(notification.acc_tree.children[0].state, + EXPECT_EQ(notification.nodes[1].state, (1U << AccessibilityNodeData::STATE_FOCUSABLE) | (1U << AccessibilityNodeData::STATE_FOCUSED) | (1U << AccessibilityNodeData::STATE_READONLY)); @@ -172,7 +221,7 @@ TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { AccessibilityHostMsg_NotificationParams notification; GetLastAccNotification(¬ification); EXPECT_EQ(notification.id, 8); - EXPECT_EQ(notification.acc_tree.children[0].state, + EXPECT_EQ(notification.nodes[1].state, (1U << AccessibilityNodeData::STATE_FOCUSABLE) | (1U << AccessibilityNodeData::STATE_FOCUSED) | (1U << AccessibilityNodeData::STATE_READONLY)); @@ -189,4 +238,163 @@ TEST_F(RendererAccessibilityTest, EditableTextModeFocusNotifications) { } } +TEST_F(RendererAccessibilityTest, SendFullAccessibilityTreeOnReload) { + // The job of RendererAccessibilityComplete is to serialize the + // accessibility tree built by WebKit and send it to the browser. + // When the accessibility tree changes, it tries to send only + // the nodes that actually changed or were reparented. This test + // ensures that the messages sent are correct in cases when a page + // reloads, and that internal state is properly garbage-collected. + std::string html = + "<body>" + " <div role='group' id='A'>" + " <div role='group' id='A1'></div>" + " <div role='group' id='A2'></div>" + " </div>" + "</body>"; + LoadHTML(html.c_str()); + + // Creating a RendererAccessibilityComplete should sent the tree + // to the browser. + scoped_ptr<TestRendererAccessibilityComplete> accessibility( + new TestRendererAccessibilityComplete(view())); + accessibility->SendPendingAccessibilityNotifications(); + EXPECT_EQ(4, accessibility->browser_tree_node_count()); + EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser()); + + // If we post another notification but the tree doesn't change, + // we should only send 1 node to the browser. + sink_->ClearMessages(); + WebDocument document = view()->GetWebView()->mainFrame()->document(); + WebAccessibilityObject root_obj = document.accessibilityObject(); + accessibility->HandleWebAccessibilityNotification( + root_obj, + WebKit::WebAccessibilityNotificationLayoutComplete); + accessibility->SendPendingAccessibilityNotifications(); + EXPECT_EQ(4, accessibility->browser_tree_node_count()); + EXPECT_EQ(1, CountAccessibilityNodesSentToBrowser()); + { + // Make sure it's the root object that was updated. + AccessibilityHostMsg_NotificationParams notification; + GetLastAccNotification(¬ification); + EXPECT_EQ(root_obj.axID(), notification.nodes[0].id); + } + + // If we reload the page and send a notification, we should send + // all 4 nodes to the browser. Also double-check that we didn't + // leak any of the old BrowserTreeNodes. + LoadHTML(html.c_str()); + document = view()->GetWebView()->mainFrame()->document(); + root_obj = document.accessibilityObject(); + sink_->ClearMessages(); + accessibility->HandleWebAccessibilityNotification( + root_obj, + WebKit::WebAccessibilityNotificationLayoutComplete); + accessibility->SendPendingAccessibilityNotifications(); + EXPECT_EQ(4, accessibility->browser_tree_node_count()); + EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser()); + + // Even if the first notification is sent on an element other than + // the root, the whole tree should be updated because we know + // the browser doesn't have the root element. + LoadHTML(html.c_str()); + document = view()->GetWebView()->mainFrame()->document(); + root_obj = document.accessibilityObject(); + sink_->ClearMessages(); + const WebAccessibilityObject& first_child = root_obj.firstChild(); + accessibility->HandleWebAccessibilityNotification( + first_child, + WebKit::WebAccessibilityNotificationLiveRegionChanged); + accessibility->SendPendingAccessibilityNotifications(); + EXPECT_EQ(4, accessibility->browser_tree_node_count()); + EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser()); +} + +TEST_F(RendererAccessibilityTest, HideAccessibilityObject) { + // Test RendererAccessibilityComplete and make sure it sends the + // proper notification to the browser when an object in the tree + // is hidden, but its children are not. + std::string html = + "<body>" + " <div role='group' id='A'>" + " <div role='group' id='B'>" + " <div role='group' id='C' style='visibility:visible'>" + " </div>" + " </div>" + " </div>" + "</body>"; + LoadHTML(html.c_str()); + + scoped_ptr<TestRendererAccessibilityComplete> accessibility( + new TestRendererAccessibilityComplete(view())); + accessibility->SendPendingAccessibilityNotifications(); + EXPECT_EQ(4, accessibility->browser_tree_node_count()); + EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser()); + + // Hide node 'B' ('C' stays visible). + ExecuteJavaScript( + "document.getElementById('B').style.visibility = 'hidden';"); + // Force layout now. + ExecuteJavaScript("document.getElementById('B').offsetLeft;"); + + // Send a childrenChanged on 'A'. + sink_->ClearMessages(); + WebDocument document = view()->GetWebView()->mainFrame()->document(); + WebAccessibilityObject root_obj = document.accessibilityObject(); + WebAccessibilityObject node_a = root_obj.childAt(0); + accessibility->HandleWebAccessibilityNotification( + node_a, + WebKit::WebAccessibilityNotificationChildrenChanged); + + accessibility->SendPendingAccessibilityNotifications(); + EXPECT_EQ(3, accessibility->browser_tree_node_count()); + AccessibilityHostMsg_NotificationParams notification; + GetLastAccNotification(¬ification); + ASSERT_EQ(2U, notification.nodes.size()); + EXPECT_EQ(2, CountAccessibilityNodesSentToBrowser()); +} + +TEST_F(RendererAccessibilityTest, ShowAccessibilityObject) { + // Test RendererAccessibilityComplete and make sure it sends the + // proper notification to the browser when an object in the tree + // is shown, causing its own already-visible children to be + // reparented to it. + std::string html = + "<body>" + " <div role='group' id='A'>" + " <div role='group' id='B' style='visibility:hidden'>" + " <div role='group' id='C' style='visibility:visible'>" + " </div>" + " </div>" + " </div>" + "</body>"; + LoadHTML(html.c_str()); + + scoped_ptr<TestRendererAccessibilityComplete> accessibility( + new TestRendererAccessibilityComplete(view())); + accessibility->SendPendingAccessibilityNotifications(); + EXPECT_EQ(3, accessibility->browser_tree_node_count()); + EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser()); + + // Show node 'B', then send a childrenChanged on 'A'. + ExecuteJavaScript( + "document.getElementById('B').style.visibility = 'visible';"); + ExecuteJavaScript("document.getElementById('B').offsetLeft;"); + + sink_->ClearMessages(); + WebDocument document = view()->GetWebView()->mainFrame()->document(); + WebAccessibilityObject root_obj = document.accessibilityObject(); + WebAccessibilityObject node_a = root_obj.childAt(0); + accessibility->HandleWebAccessibilityNotification( + node_a, + WebKit::WebAccessibilityNotificationChildrenChanged); + + accessibility->SendPendingAccessibilityNotifications(); + EXPECT_EQ(4, accessibility->browser_tree_node_count()); + AccessibilityHostMsg_NotificationParams notification; + GetLastAccNotification(¬ification); + ASSERT_EQ(3U, notification.nodes.size()); + EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser()); +} + } // namespace content diff --git a/content/renderer/accessibility/renderer_accessibility_complete.cc b/content/renderer/accessibility/renderer_accessibility_complete.cc index 80923d1..6ece9dc 100644 --- a/content/renderer/accessibility/renderer_accessibility_complete.cc +++ b/content/renderer/accessibility/renderer_accessibility_complete.cc @@ -85,6 +85,9 @@ bool WebAccessibilityNotificationToAccessibilityNotification( case WebKit::WebAccessibilityNotificationSelectedTextChanged: *type = AccessibilityNotificationSelectedTextChanged; break; + case WebKit::WebAccessibilityNotificationTextChanged: + *type = AccessibilityNotificationTextChanged; + break; case WebKit::WebAccessibilityNotificationValueChanged: *type = AccessibilityNotificationValueChanged; break; @@ -134,6 +137,7 @@ bool RendererAccessibilityComplete::OnMessageReceived( OnScrollToPoint) IPC_MESSAGE_HANDLER(AccessibilityMsg_SetTextSelection, OnSetTextSelection) + IPC_MESSAGE_HANDLER(AccessibilityMsg_FatalError, OnFatalError) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; @@ -261,11 +265,6 @@ void RendererAccessibilityComplete::SendPendingAccessibilityNotifications() { AccessibilityHostMsg_NotificationParams& notification = src_notifications[i]; - // TODO(dtseng): Come up with a cleaner way of deciding to include children. - WebAccessibilityObject rootObject = document.accessibilityObject(); - int root_id = rootObject.axID(); - bool includes_children = ShouldIncludeChildren(notification) || - root_id == notification.id; WebAccessibilityObject obj = document.accessibilityObjectFromID( notification.id); if (!obj.updateBackingStoreAndCheckValidity()) @@ -276,11 +275,12 @@ void RendererAccessibilityComplete::SendPendingAccessibilityNotifications() { // notification on a node before the page has loaded. Work our way // up the parent chain until we find a node the browser has, or until // we reach the root. + WebAccessibilityObject root_object = document.accessibilityObject(); + int root_id = root_object.axID(); while (browser_id_map_.find(obj.axID()) == browser_id_map_.end() && !obj.isDetached() && obj.axID() != root_id) { obj = obj.parentObject(); - includes_children = true; if (notification.notification_type == AccessibilityNotificationChildrenChanged) { notification.id = obj.axID(); @@ -323,39 +323,28 @@ void RendererAccessibilityComplete::SendPendingAccessibilityNotifications() { if (!is_child_of_parent) { obj = parent; notification.id = obj.axID(); - includes_children = true; } } // Allow WebKit to cache intermediate results since we're doing a bunch // of read-only queries at once. - rootObject.startCachingComputedObjectAttributesUntilTreeMutates(); + root_object.startCachingComputedObjectAttributesUntilTreeMutates(); AccessibilityHostMsg_NotificationParams notification_msg; notification_msg.notification_type = notification.notification_type; notification_msg.id = notification.id; - notification_msg.includes_children = includes_children; - SerializeAccessibilityNode(obj, - ¬ification_msg.acc_tree, - includes_children); - if (obj.axID() == root_id) { - DCHECK_EQ(notification_msg.acc_tree.role, - AccessibilityNodeData::ROLE_WEB_AREA); - notification_msg.acc_tree.role = - AccessibilityNodeData::ROLE_ROOT_WEB_AREA; - } + SerializeChangedNodes(obj, ¬ification_msg.nodes); notification_msgs.push_back(notification_msg); - if (includes_children) - UpdateBrowserTree(notification_msg.acc_tree); - #ifndef NDEBUG if (logging_) { + AccessibilityNodeDataTreeNode tree; + MakeAccessibilityNodeDataTree(notification_msg.nodes, &tree); LOG(INFO) << "Accessibility update: \n" << "routing id=" << routing_id() << " notification=" << AccessibilityNotificationToString(notification.notification_type) - << "\n" << notification_msg.acc_tree.DebugString(true); + << "\n" << tree.DebugString(true); } #endif } @@ -363,34 +352,118 @@ void RendererAccessibilityComplete::SendPendingAccessibilityNotifications() { Send(new AccessibilityHostMsg_Notifications(routing_id(), notification_msgs)); } -void RendererAccessibilityComplete::UpdateBrowserTree( - const AccessibilityNodeData& renderer_node) { +RendererAccessibilityComplete::BrowserTreeNode* +RendererAccessibilityComplete::CreateBrowserTreeNode() { + return new RendererAccessibilityComplete::BrowserTreeNode(); +} + +void RendererAccessibilityComplete::SerializeChangedNodes( + const WebKit::WebAccessibilityObject& obj, + std::vector<AccessibilityNodeData>* dst) { + // This method has three responsibilities: + // 1. Serialize |obj| into an AccessibilityNodeData, and append it to + // the end of the |dst| vector to be send to the browser process. + // 2. Determine if |obj| has any new children that the browser doesn't + // know about yet, and call SerializeChangedNodes recursively on those. + // 3. Update our internal data structure that keeps track of what nodes + // the browser knows about. + + // First, find the BrowserTreeNode for this id in our data structure where + // we keep track of what accessibility objects the browser already knows + // about. If we don't find it, then this must be the new root of the + // accessibility tree. BrowserTreeNode* browser_node = NULL; base::hash_map<int32, BrowserTreeNode*>::iterator iter = - browser_id_map_.find(renderer_node.id); + browser_id_map_.find(obj.axID()); if (iter != browser_id_map_.end()) { browser_node = iter->second; - ClearBrowserTreeNode(browser_node); } else { - DCHECK_EQ(renderer_node.role, AccessibilityNodeData::ROLE_ROOT_WEB_AREA); if (browser_root_) { ClearBrowserTreeNode(browser_root_); browser_id_map_.erase(browser_root_->id); delete browser_root_; } - browser_root_ = new BrowserTreeNode; + browser_root_ = CreateBrowserTreeNode(); browser_node = browser_root_; - browser_node->id = renderer_node.id; + browser_node->id = obj.axID(); browser_id_map_[browser_node->id] = browser_node; } - browser_node->children.reserve(renderer_node.children.size()); - for (size_t i = 0; i < renderer_node.children.size(); ++i) { - BrowserTreeNode* browser_child_node = new BrowserTreeNode; - browser_child_node->id = renderer_node.children[i].id; - browser_id_map_[browser_child_node->id] = browser_child_node; - browser_node->children.push_back(browser_child_node); - UpdateBrowserTree(renderer_node.children[i]); + + // Serialize this node. This fills in all of the fields in + // AccessibilityNodeData except child_ids, which we handle below. + dst->push_back(AccessibilityNodeData()); + AccessibilityNodeData* serialized_node = &dst->back(); + SerializeAccessibilityNode(obj, serialized_node); + if (serialized_node->id == browser_root_->id) + serialized_node->role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + + // Create set of the ids of the children of |obj| so we can quickly look + // up which children are new and which ones were there before. + base::hash_set<int32> new_child_ids; + for (unsigned i = 0; i < obj.childCount(); i++) { + WebAccessibilityObject child = obj.childAt(i); + if (ShouldIncludeChildNode(obj, child)) { + new_child_ids.insert(child.axID()); + } + } + + // Go through the old children and delete subtrees for child + // ids that are no longer present, and create a map from + // id to BrowserTreeNode for the rest. It's important to delete + // first in a separate pass so that nodes that are reparented + // don't end up children of two different parents in the middle + // of an update, which can lead to a double-free. + base::hash_map<int32, BrowserTreeNode*> browser_child_id_map; + std::vector<BrowserTreeNode*> old_children; + old_children.swap(browser_node->children); + for (size_t i = 0; i < old_children.size(); i++) { + BrowserTreeNode* old_child = old_children[i]; + int old_child_id = old_child->id; + if (new_child_ids.find(old_child_id) == new_child_ids.end()) { + browser_id_map_.erase(old_child_id); + ClearBrowserTreeNode(old_child); + delete old_child; + } else { + browser_child_id_map[old_child_id] = old_child; + } } + + // Iterate over the children, make note of the ones that are new + // and need to be serialized, and update the BrowserTreeNode + // data structure to reflect the new tree. + std::vector<WebAccessibilityObject> children_to_serialize; + int child_count = obj.childCount(); + browser_node->children.reserve(child_count); + for (int i = 0; i < child_count; i++) { + WebAccessibilityObject child = obj.childAt(i); + int child_id = child.axID(); + + // Checks to make sure the child is valid, attached to this node, + // and one we want to include in the tree. + if (!ShouldIncludeChildNode(obj, child)) + continue; + + // No need to do anything more with children that aren't new; + // the browser will reuse its existing object. + if (new_child_ids.find(child_id) == new_child_ids.end()) + continue; + + new_child_ids.erase(child_id); + serialized_node->child_ids.push_back(child_id); + if (browser_child_id_map.find(child_id) != browser_child_id_map.end()) { + browser_node->children.push_back(browser_child_id_map[child_id]); + } else { + BrowserTreeNode* new_child = CreateBrowserTreeNode(); + new_child->id = child_id; + browser_node->children.push_back(new_child); + browser_id_map_[child_id] = new_child; + children_to_serialize.push_back(child); + } + } + + // Serialize all of the new children, recursively. + for (size_t i = 0; i < children_to_serialize.size(); ++i) + SerializeChangedNodes(children_to_serialize[i], dst); } void RendererAccessibilityComplete::ClearBrowserTreeNode( @@ -540,6 +613,10 @@ void RendererAccessibilityComplete::OnSetFocus(int acc_obj_id) { obj.setFocused(true); } +void RendererAccessibilityComplete::OnFatalError() { + CHECK(false); +} + bool RendererAccessibilityComplete::ShouldIncludeChildren( const AccessibilityHostMsg_NotificationParams& notification) { AccessibilityNotification type = notification.notification_type; diff --git a/content/renderer/accessibility/renderer_accessibility_complete.h b/content/renderer/accessibility/renderer_accessibility_complete.h index e03c0e9..b338a90 100644 --- a/content/renderer/accessibility/renderer_accessibility_complete.h +++ b/content/renderer/accessibility/renderer_accessibility_complete.h @@ -31,7 +31,8 @@ class RenderViewImpl; // a serialized representation of that tree whenever it changes. It also // handles requests from the browser to perform accessibility actions on // nodes in the tree (e.g., change focus, or click on a button). -class RendererAccessibilityComplete : public RendererAccessibility { +class CONTENT_EXPORT RendererAccessibilityComplete + : public RendererAccessibility { public: explicit RendererAccessibilityComplete(RenderViewImpl* render_view); virtual ~RendererAccessibilityComplete(); @@ -46,28 +47,33 @@ class RendererAccessibilityComplete : public RendererAccessibility { const WebKit::WebAccessibilityObject& obj, WebKit::WebAccessibilityNotification notification) OVERRIDE; - private: - // Handle an accessibility notification to be sent to the browser process. - void HandleAccessibilityNotification( - const WebKit::WebAccessibilityObject& obj, - AccessibilityNotification notification); - // In order to keep track of what nodes the browser knows about, we keep a // representation of the browser tree - just IDs and parent/child // relationships. - struct BrowserTreeNode { + struct CONTENT_EXPORT BrowserTreeNode { BrowserTreeNode(); - ~BrowserTreeNode(); + virtual ~BrowserTreeNode(); int32 id; std::vector<BrowserTreeNode*> children; }; + virtual BrowserTreeNode* CreateBrowserTreeNode(); + + protected: // Send queued notifications from the renderer to the browser. void SendPendingAccessibilityNotifications(); - // Update our representation of what nodes the browser has, given a - // tree of nodes. - void UpdateBrowserTree(const AccessibilityNodeData& renderer_node); + private: + // Handle an accessibility notification to be sent to the browser process. + void HandleAccessibilityNotification( + const WebKit::WebAccessibilityObject& obj, + AccessibilityNotification notification); + + // Serialize the given accessibility object |obj| and append it to + // |dst|, and then recursively also serialize any *new* children of + // |obj|, based on what object ids we know the browser already has. + void SerializeChangedNodes(const WebKit::WebAccessibilityObject& obj, + std::vector<AccessibilityNodeData>* dst); // Clear the given node and recursively delete all of its descendants // from the browser tree. (Does not delete |browser_node|). @@ -80,8 +86,8 @@ class RendererAccessibilityComplete : public RendererAccessibility { void OnScrollToMakeVisible(int acc_obj_id, gfx::Rect subfocus); void OnScrollToPoint(int acc_obj_id, gfx::Point point); void OnSetFocus(int acc_obj_id); - void OnSetTextSelection(int acc_obj_id, int start_offset, int end_offset); + void OnFatalError(); // Whether or not this notification typically needs to send // updates to its children, too. diff --git a/content/renderer/accessibility/renderer_accessibility_focus_only.cc b/content/renderer/accessibility/renderer_accessibility_focus_only.cc index 601bc16..71da433 100644 --- a/content/renderer/accessibility/renderer_accessibility_focus_only.cc +++ b/content/renderer/accessibility/renderer_accessibility_focus_only.cc @@ -92,26 +92,25 @@ void RendererAccessibilityFocusOnly::HandleFocusedNodeChanged( AccessibilityNotificationFocusChanged : AccessibilityNotificationLayoutComplete; - // This means that the new tree we send supercedes any previous tree, - // not just a previous node. - notification.includes_children = true; - // Set the id that the notification applies to: the root node if nothing // has focus, otherwise the focused node. notification.id = node_has_focus ? next_id_ : 1; // Always include the root of the tree, the document. It always has id 1. - notification.acc_tree.id = 1; - notification.acc_tree.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; - notification.acc_tree.state = + notification.nodes.push_back(AccessibilityNodeData()); + AccessibilityNodeData& root = notification.nodes[0]; + root.id = 1; + root.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; + root.state = (1 << AccessibilityNodeData::STATE_READONLY) | (1 << AccessibilityNodeData::STATE_FOCUSABLE); if (!node_has_focus) - notification.acc_tree.state |= (1 << AccessibilityNodeData::STATE_FOCUSED); - notification.acc_tree.location = gfx::Rect(render_view_->size()); + root.state |= (1 << AccessibilityNodeData::STATE_FOCUSED); + root.location = gfx::Rect(render_view_->size()); + root.child_ids.push_back(next_id_); - notification.acc_tree.children.push_back(AccessibilityNodeData()); - AccessibilityNodeData& child = notification.acc_tree.children[0]; + notification.nodes.push_back(AccessibilityNodeData()); + AccessibilityNodeData& child = notification.nodes[1]; child.id = next_id_; child.role = AccessibilityNodeData::ROLE_GROUP; @@ -119,7 +118,7 @@ void RendererAccessibilityFocusOnly::HandleFocusedNodeChanged( child.location = gfx::Rect( const_cast<WebNode&>(node).to<WebElement>().boundsInViewportSpace()); } else if (render_view_->HasIMETextFocus()) { - child.location = notification.acc_tree.location; + child.location = root.location; } else { child.location = gfx::Rect(); } @@ -138,7 +137,7 @@ void RendererAccessibilityFocusOnly::HandleFocusedNodeChanged( << "routing id=" << routing_id() << " notification=" << AccessibilityNotificationToString(notification.notification_type) - << "\n" << notification.acc_tree.DebugString(true); + << "\n" << notification.nodes[0].DebugString(true); } #endif |