// 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 = &params[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 = &params[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 = &params[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_);
}