// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/string16.h" #include "base/utf_string_conversions.h" #include "chrome/browser/accessibility/browser_accessibility.h" #include "chrome/browser/accessibility/browser_accessibility_manager.h" #include "content/common/view_messages.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/glue/webaccessibility.h" using webkit_glue::WebAccessibility; namespace { // Subclass of BrowserAccessibility that counts the number of instances. class CountedBrowserAccessibility : public BrowserAccessibility { public: CountedBrowserAccessibility() { global_obj_count_++; native_ref_count_ = 1; } virtual ~CountedBrowserAccessibility() { global_obj_count_--; } virtual void NativeAddReference() OVERRIDE { native_ref_count_++; } virtual void NativeReleaseReference() OVERRIDE { native_ref_count_--; if (native_ref_count_ == 0) delete this; } int native_ref_count_; static int global_obj_count_; }; int CountedBrowserAccessibility::global_obj_count_ = 0; // Factory that creates a CountedBrowserAccessibility. class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory { public: virtual ~CountedBrowserAccessibilityFactory() {} virtual BrowserAccessibility* Create() { return new CountedBrowserAccessibility(); } }; } // anonymous namespace TEST(BrowserAccessibilityManagerTest, TestNoLeaks) { // Create WebAccessibility objects for a simple document tree, // representing the accessibility information used to initialize // BrowserAccessibilityManager. WebAccessibility button; button.id = 2; button.name = UTF8ToUTF16("Button"); button.role = WebAccessibility::ROLE_BUTTON; button.state = 0; WebAccessibility checkbox; checkbox.id = 3; checkbox.name = UTF8ToUTF16("Checkbox"); checkbox.role = WebAccessibility::ROLE_CHECKBOX; checkbox.state = 0; WebAccessibility root; root.id = 1; root.name = UTF8ToUTF16("Document"); root.role = WebAccessibility::ROLE_DOCUMENT; root.state = 0; root.children.push_back(button); root.children.push_back(checkbox); // Construct a BrowserAccessibilityManager with this WebAccessibility tree // and a factory for an instance-counting BrowserAccessibility, and ensure // that exactly 3 instances were created. Note that the manager takes // ownership of the factory. CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( NULL, root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); // Delete the manager and test that all 3 instances are deleted. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); // Construct a manager again, and this time save references to two of // the three nodes in the tree. manager = BrowserAccessibilityManager::Create( NULL, root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_); CountedBrowserAccessibility* root_accessible = static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); root_accessible->NativeAddReference(); CountedBrowserAccessibility* child1_accessible = static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(1)); child1_accessible->NativeAddReference(); // Now delete the manager, and only one of the three nodes in the tree // should be released. delete manager; ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_); // Release each of our references and make sure that each one results in // the instance being deleted as its reference count hits zero. root_accessible->NativeReleaseReference(); ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_); child1_accessible->NativeReleaseReference(); ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) { // Make sure that changes to a subtree reuse as many objects as possible. // Tree 1: // // root // child1 // child2 // child3 WebAccessibility tree1_child1; tree1_child1.id = 2; tree1_child1.name = UTF8ToUTF16("Child1"); tree1_child1.role = WebAccessibility::ROLE_BUTTON; tree1_child1.state = 0; WebAccessibility tree1_child2; tree1_child2.id = 3; tree1_child2.name = UTF8ToUTF16("Child2"); tree1_child2.role = WebAccessibility::ROLE_BUTTON; tree1_child2.state = 0; WebAccessibility tree1_child3; tree1_child3.id = 4; tree1_child3.name = UTF8ToUTF16("Child3"); tree1_child3.role = WebAccessibility::ROLE_BUTTON; tree1_child3.state = 0; WebAccessibility tree1_root; tree1_root.id = 1; tree1_root.name = UTF8ToUTF16("Document"); tree1_root.role = WebAccessibility::ROLE_DOCUMENT; 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); // Tree 2: // // root // child0 <-- inserted // child1 // child2 // <-- child3 deleted WebAccessibility tree2_child0; tree2_child0.id = 5; tree2_child0.name = UTF8ToUTF16("Child0"); tree2_child0.role = WebAccessibility::ROLE_BUTTON; tree2_child0.state = 0; WebAccessibility tree2_child1; tree2_child1.id = 2; tree2_child1.name = UTF8ToUTF16("Child1"); tree2_child1.role = WebAccessibility::ROLE_BUTTON; tree2_child1.state = 0; WebAccessibility tree2_child2; tree2_child2.id = 3; tree2_child2.name = UTF8ToUTF16("Child2"); tree2_child2.role = WebAccessibility::ROLE_BUTTON; tree2_child2.state = 0; WebAccessibility tree2_root; tree2_root.id = 1; tree2_root.name = UTF8ToUTF16("DocumentChanged"); tree2_root.role = WebAccessibility::ROLE_DOCUMENT; 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); // Construct a BrowserAccessibilityManager with tree1. CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( NULL, tree1_root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); // Save references to all of the objects. CountedBrowserAccessibility* root_accessible = static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); root_accessible->NativeAddReference(); CountedBrowserAccessibility* child1_accessible = static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(0)); child1_accessible->NativeAddReference(); CountedBrowserAccessibility* child2_accessible = static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(1)); child2_accessible->NativeAddReference(); CountedBrowserAccessibility* child3_accessible = static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(2)); child3_accessible->NativeAddReference(); // Check the index in parent. EXPECT_EQ(0, child1_accessible->index_in_parent()); EXPECT_EQ(1, child2_accessible->index_in_parent()); EXPECT_EQ(2, child3_accessible->index_in_parent()); // Process a notification containing the changed subtree. std::vector<ViewHostMsg_AccessibilityNotification_Params> params; params.push_back(ViewHostMsg_AccessibilityNotification_Params()); ViewHostMsg_AccessibilityNotification_Params* msg = ¶ms[0]; msg->notification_type = ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_CHILDREN_CHANGED; msg->acc_obj = tree2_root; manager->OnAccessibilityNotifications(params); // There should be 5 objects now: the 4 from the new tree, plus the // reference to child3 we kept. EXPECT_EQ(5, CountedBrowserAccessibility::global_obj_count_); // Check that our references to the root, child1, and child2 are still valid, // but that the reference to child3 is now invalid. EXPECT_TRUE(root_accessible->instance_active()); EXPECT_TRUE(child1_accessible->instance_active()); EXPECT_TRUE(child2_accessible->instance_active()); EXPECT_FALSE(child3_accessible->instance_active()); // Check that the index in parent has been updated. EXPECT_EQ(1, child1_accessible->index_in_parent()); EXPECT_EQ(2, child2_accessible->index_in_parent()); // Release our references. The object count should only decrease by 1 // for child3. root_accessible->NativeReleaseReference(); child1_accessible->NativeReleaseReference(); child2_accessible->NativeReleaseReference(); child3_accessible->NativeReleaseReference(); EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_); // Delete the manager and make sure all memory is cleaned up. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) { // Similar to the test above, but with a more complicated tree. // Tree 1: // // root // container // child1 // grandchild1 // child2 // grandchild2 // child3 // grandchild3 WebAccessibility tree1_grandchild1; tree1_grandchild1.id = 4; tree1_grandchild1.name = UTF8ToUTF16("GrandChild1"); tree1_grandchild1.role = WebAccessibility::ROLE_BUTTON; tree1_grandchild1.state = 0; WebAccessibility tree1_child1; tree1_child1.id = 3; tree1_child1.name = UTF8ToUTF16("Child1"); tree1_child1.role = WebAccessibility::ROLE_BUTTON; tree1_child1.state = 0; tree1_child1.children.push_back(tree1_grandchild1); WebAccessibility tree1_grandchild2; tree1_grandchild2.id = 6; tree1_grandchild2.name = UTF8ToUTF16("GrandChild1"); tree1_grandchild2.role = WebAccessibility::ROLE_BUTTON; tree1_grandchild2.state = 0; WebAccessibility tree1_child2; tree1_child2.id = 5; tree1_child2.name = UTF8ToUTF16("Child2"); tree1_child2.role = WebAccessibility::ROLE_BUTTON; tree1_child2.state = 0; tree1_child2.children.push_back(tree1_grandchild2); WebAccessibility tree1_grandchild3; tree1_grandchild3.id = 8; tree1_grandchild3.name = UTF8ToUTF16("GrandChild3"); tree1_grandchild3.role = WebAccessibility::ROLE_BUTTON; tree1_grandchild3.state = 0; WebAccessibility tree1_child3; tree1_child3.id = 7; tree1_child3.name = UTF8ToUTF16("Child3"); tree1_child3.role = WebAccessibility::ROLE_BUTTON; tree1_child3.state = 0; tree1_child3.children.push_back(tree1_grandchild3); WebAccessibility tree1_container; tree1_container.id = 2; tree1_container.name = UTF8ToUTF16("Container"); tree1_container.role = WebAccessibility::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); WebAccessibility tree1_root; tree1_root.id = 1; tree1_root.name = UTF8ToUTF16("Document"); tree1_root.role = WebAccessibility::ROLE_DOCUMENT; tree1_root.state = 0; tree1_root.children.push_back(tree1_container); // Tree 2: // // root // container // child0 <-- inserted // grandchild0 <-- // child1 // grandchild1 // child2 // grandchild2 // <-- child3 (and grandchild3) deleted WebAccessibility tree2_grandchild0; tree2_grandchild0.id = 9; tree2_grandchild0.name = UTF8ToUTF16("GrandChild0"); tree2_grandchild0.role = WebAccessibility::ROLE_BUTTON; tree2_grandchild0.state = 0; WebAccessibility tree2_child0; tree2_child0.id = 10; tree2_child0.name = UTF8ToUTF16("Child0"); tree2_child0.role = WebAccessibility::ROLE_BUTTON; tree2_child0.state = 0; tree2_child0.children.push_back(tree2_grandchild0); WebAccessibility tree2_grandchild1; tree2_grandchild1.id = 4; tree2_grandchild1.name = UTF8ToUTF16("GrandChild1"); tree2_grandchild1.role = WebAccessibility::ROLE_BUTTON; tree2_grandchild1.state = 0; WebAccessibility tree2_child1; tree2_child1.id = 3; tree2_child1.name = UTF8ToUTF16("Child1"); tree2_child1.role = WebAccessibility::ROLE_BUTTON; tree2_child1.state = 0; tree2_child1.children.push_back(tree2_grandchild1); WebAccessibility tree2_grandchild2; tree2_grandchild2.id = 6; tree2_grandchild2.name = UTF8ToUTF16("GrandChild1"); tree2_grandchild2.role = WebAccessibility::ROLE_BUTTON; tree2_grandchild2.state = 0; WebAccessibility tree2_child2; tree2_child2.id = 5; tree2_child2.name = UTF8ToUTF16("Child2"); tree2_child2.role = WebAccessibility::ROLE_BUTTON; tree2_child2.state = 0; tree2_child2.children.push_back(tree2_grandchild2); WebAccessibility tree2_container; tree2_container.id = 2; tree2_container.name = UTF8ToUTF16("Container"); tree2_container.role = WebAccessibility::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); WebAccessibility tree2_root; tree2_root.id = 1; tree2_root.name = UTF8ToUTF16("Document"); tree2_root.role = WebAccessibility::ROLE_DOCUMENT; tree2_root.state = 0; tree2_root.children.push_back(tree2_container); // Construct a BrowserAccessibilityManager with tree1. CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( NULL, tree1_root, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_); // Save references to some objects. CountedBrowserAccessibility* root_accessible = static_cast<CountedBrowserAccessibility*>(manager->GetRoot()); root_accessible->NativeAddReference(); CountedBrowserAccessibility* container_accessible = static_cast<CountedBrowserAccessibility*>(root_accessible->GetChild(0)); container_accessible->NativeAddReference(); CountedBrowserAccessibility* child2_accessible = static_cast<CountedBrowserAccessibility*>( container_accessible->GetChild(1)); child2_accessible->NativeAddReference(); CountedBrowserAccessibility* child3_accessible = static_cast<CountedBrowserAccessibility*>( container_accessible->GetChild(2)); child3_accessible->NativeAddReference(); // Check the index in parent. EXPECT_EQ(1, child2_accessible->index_in_parent()); EXPECT_EQ(2, child3_accessible->index_in_parent()); // Process a notification containing the changed subtree rooted at // the container. std::vector<ViewHostMsg_AccessibilityNotification_Params> params; params.push_back(ViewHostMsg_AccessibilityNotification_Params()); ViewHostMsg_AccessibilityNotification_Params* msg = ¶ms[0]; msg->notification_type = ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_CHILDREN_CHANGED; msg->acc_obj = tree2_container; manager->OnAccessibilityNotifications(params); // There should be 9 objects now: the 8 from the new tree, plus the // reference to child3 we kept. EXPECT_EQ(9, CountedBrowserAccessibility::global_obj_count_); // Check that our references to the root and container and child2 are // still valid, but that the reference to child3 is now invalid. EXPECT_TRUE(root_accessible->instance_active()); EXPECT_TRUE(container_accessible->instance_active()); EXPECT_TRUE(child2_accessible->instance_active()); EXPECT_FALSE(child3_accessible->instance_active()); // Check that the index in parent has been updated. EXPECT_EQ(2, child2_accessible->index_in_parent()); // Release our references. The object count should only decrease by 1 // for child3. root_accessible->NativeReleaseReference(); container_accessible->NativeReleaseReference(); child2_accessible->NativeReleaseReference(); child3_accessible->NativeReleaseReference(); EXPECT_EQ(8, CountedBrowserAccessibility::global_obj_count_); // Delete the manager and make sure all memory is cleaned up. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); } TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) { // Tree 1: // // 1 // 2 // 3 // 4 WebAccessibility tree1_4; tree1_4.id = 4; tree1_4.state = 0; WebAccessibility tree1_3; tree1_3.id = 3; tree1_3.state = 0; tree1_3.children.push_back(tree1_4); WebAccessibility tree1_2; tree1_2.id = 2; tree1_2.state = 0; WebAccessibility tree1_1; tree1_1.id = 1; tree1_1.state = 0; tree1_1.children.push_back(tree1_2); tree1_1.children.push_back(tree1_3); // Tree 2: // // 1 // 4 <-- moves up a level and gains child // 6 <-- new // 5 <-- new WebAccessibility tree2_6; tree2_6.id = 6; tree2_6.state = 0; WebAccessibility tree2_5; tree2_5.id = 5; tree2_5.state = 0; WebAccessibility tree2_4; tree2_4.id = 4; tree2_4.state = 0; tree2_4.children.push_back(tree2_6); WebAccessibility tree2_1; tree2_1.id = 1; tree2_1.state = 0; tree2_1.children.push_back(tree2_4); tree2_1.children.push_back(tree2_5); // Construct a BrowserAccessibilityManager with tree1. CountedBrowserAccessibility::global_obj_count_ = 0; BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create( NULL, tree1_1, NULL, new CountedBrowserAccessibilityFactory()); ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_); // Process a notification containing the changed subtree. std::vector<ViewHostMsg_AccessibilityNotification_Params> params; params.push_back(ViewHostMsg_AccessibilityNotification_Params()); ViewHostMsg_AccessibilityNotification_Params* msg = ¶ms[0]; msg->notification_type = ViewHostMsg_AccessibilityNotification_Type:: NOTIFICATION_TYPE_CHILDREN_CHANGED; msg->acc_obj = tree2_1; manager->OnAccessibilityNotifications(params); // There should be 4 objects now. EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_); // Delete the manager and make sure all memory is cleaned up. delete manager; ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_); }