// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/memory/scoped_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_variant.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/accessibility/browser_accessibility_manager_win.h"
#include "content/browser/accessibility/browser_accessibility_state_impl.h"
#include "content/browser/accessibility/browser_accessibility_win.h"
#include "content/browser/renderer_host/legacy_render_widget_host_win.h"
#include "content/common/accessibility_messages.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/win/atl_module.h"

namespace content {
namespace {


// CountedBrowserAccessibility ------------------------------------------------

// Subclass of BrowserAccessibilityWin that counts the number of instances.
class CountedBrowserAccessibility : public BrowserAccessibilityWin {
 public:
  CountedBrowserAccessibility();
  ~CountedBrowserAccessibility() override;

  static void reset() { num_instances_ = 0; }
  static int num_instances() { return num_instances_; }

 private:
  static int num_instances_;

  DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibility);
};

// static
int CountedBrowserAccessibility::num_instances_ = 0;

CountedBrowserAccessibility::CountedBrowserAccessibility() {
  ++num_instances_;
}

CountedBrowserAccessibility::~CountedBrowserAccessibility() {
  --num_instances_;
}


// CountedBrowserAccessibilityFactory -----------------------------------------

// Factory that creates a CountedBrowserAccessibility.
class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory {
 public:
  CountedBrowserAccessibilityFactory();

 private:
  ~CountedBrowserAccessibilityFactory() override;

  BrowserAccessibility* Create() override;

  DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibilityFactory);
};

CountedBrowserAccessibilityFactory::CountedBrowserAccessibilityFactory() {
}

CountedBrowserAccessibilityFactory::~CountedBrowserAccessibilityFactory() {
}

BrowserAccessibility* CountedBrowserAccessibilityFactory::Create() {
  CComObject<CountedBrowserAccessibility>* instance;
  HRESULT hr = CComObject<CountedBrowserAccessibility>::CreateInstance(
      &instance);
  DCHECK(SUCCEEDED(hr));
  instance->AddRef();
  return instance;
}

}  // namespace


// BrowserAccessibilityTest ---------------------------------------------------

class BrowserAccessibilityTest : public testing::Test {
 public:
  BrowserAccessibilityTest();
  ~BrowserAccessibilityTest() override;

 private:
  void SetUp() override;

  content::TestBrowserThreadBundle thread_bundle_;

  DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityTest);
};

BrowserAccessibilityTest::BrowserAccessibilityTest() {
}

BrowserAccessibilityTest::~BrowserAccessibilityTest() {
}

void BrowserAccessibilityTest::SetUp() {
  ui::win::CreateATLModuleIfNeeded();
}


// Actual tests ---------------------------------------------------------------

// Test that BrowserAccessibilityManager correctly releases the tree of
// BrowserAccessibility instances upon delete.
TEST_F(BrowserAccessibilityTest, TestNoLeaks) {
  // Create ui::AXNodeData objects for a simple document tree,
  // representing the accessibility information used to initialize
  // BrowserAccessibilityManager.
  ui::AXNodeData button;
  button.id = 2;
  button.SetName("Button");
  button.role = ui::AX_ROLE_BUTTON;
  button.state = 0;

  ui::AXNodeData checkbox;
  checkbox.id = 3;
  checkbox.SetName("Checkbox");
  checkbox.role = ui::AX_ROLE_CHECK_BOX;
  checkbox.state = 0;

  ui::AXNodeData root;
  root.id = 1;
  root.SetName("Document");
  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
  root.state = 0;
  root.child_ids.push_back(2);
  root.child_ids.push_back(3);

  // Construct a BrowserAccessibilityManager with this
  // ui::AXNodeData 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::reset();
  scoped_ptr<BrowserAccessibilityManager> manager(
      BrowserAccessibilityManager::Create(
          MakeAXTreeUpdate(root, button, checkbox),
          NULL, new CountedBrowserAccessibilityFactory()));
  ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());

  // Delete the manager and test that all 3 instances are deleted.
  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());

  // Construct a manager again, and this time use the IAccessible interface
  // to get new references to two of the three nodes in the tree.
  manager.reset(BrowserAccessibilityManager::Create(
      MakeAXTreeUpdate(root, button, checkbox),
      NULL, new CountedBrowserAccessibilityFactory()));
  ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
  IAccessible* root_accessible =
      manager->GetRoot()->ToBrowserAccessibilityWin();
  IDispatch* root_iaccessible = NULL;
  IDispatch* child1_iaccessible = NULL;
  base::win::ScopedVariant childid_self(CHILDID_SELF);
  HRESULT hr = root_accessible->get_accChild(childid_self, &root_iaccessible);
  ASSERT_EQ(S_OK, hr);
  base::win::ScopedVariant one(1);
  hr = root_accessible->get_accChild(one, &child1_iaccessible);
  ASSERT_EQ(S_OK, hr);

  // Now delete the manager, and only one of the three nodes in the tree
  // should be released.
  manager.reset();
  ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());

  // Release each of our references and make sure that each one results in
  // the instance being deleted as its reference count hits zero.
  root_iaccessible->Release();
  ASSERT_EQ(1, CountedBrowserAccessibility::num_instances());
  child1_iaccessible->Release();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

TEST_F(BrowserAccessibilityTest, TestChildrenChange) {
  // Create ui::AXNodeData objects for a simple document tree,
  // representing the accessibility information used to initialize
  // BrowserAccessibilityManager.
  ui::AXNodeData text;
  text.id = 2;
  text.role = ui::AX_ROLE_STATIC_TEXT;
  text.SetName("old text");
  text.state = 0;

  ui::AXNodeData root;
  root.id = 1;
  root.SetName("Document");
  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
  root.state = 0;
  root.child_ids.push_back(2);

  // Construct a BrowserAccessibilityManager with this
  // ui::AXNodeData tree and a factory for an instance-counting
  // BrowserAccessibility.
  CountedBrowserAccessibility::reset();
  scoped_ptr<BrowserAccessibilityManager> manager(
      BrowserAccessibilityManager::Create(
          MakeAXTreeUpdate(root, text),
          NULL, new CountedBrowserAccessibilityFactory()));

  // Query for the text IAccessible and verify that it returns "old text" as its
  // value.
  base::win::ScopedVariant one(1);
  base::win::ScopedComPtr<IDispatch> text_dispatch;
  HRESULT hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
      one, text_dispatch.Receive());
  ASSERT_EQ(S_OK, hr);

  base::win::ScopedComPtr<IAccessible> text_accessible;
  hr = text_dispatch.QueryInterface(text_accessible.Receive());
  ASSERT_EQ(S_OK, hr);

  base::win::ScopedVariant childid_self(CHILDID_SELF);
  base::win::ScopedBstr name;
  hr = text_accessible->get_accName(childid_self, name.Receive());
  ASSERT_EQ(S_OK, hr);
  EXPECT_EQ(L"old text", base::string16(name));
  name.Reset();

  text_dispatch.Release();
  text_accessible.Release();

  // Notify the BrowserAccessibilityManager that the text child has changed.
  ui::AXNodeData text2;
  text2.id = 2;
  text2.role = ui::AX_ROLE_STATIC_TEXT;
  text2.SetName("new text");
  text2.SetName("old text");
  AccessibilityHostMsg_EventParams param;
  param.event_type = ui::AX_EVENT_CHILDREN_CHANGED;
  param.update.nodes.push_back(text2);
  param.id = text2.id;
  std::vector<AccessibilityHostMsg_EventParams> events;
  events.push_back(param);
  manager->OnAccessibilityEvents(events);

  // Query for the text IAccessible and verify that it now returns "new text"
  // as its value.
  hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
      one, text_dispatch.Receive());
  ASSERT_EQ(S_OK, hr);

  hr = text_dispatch.QueryInterface(text_accessible.Receive());
  ASSERT_EQ(S_OK, hr);

  hr = text_accessible->get_accName(childid_self, name.Receive());
  ASSERT_EQ(S_OK, hr);
  EXPECT_EQ(L"new text", base::string16(name));

  text_dispatch.Release();
  text_accessible.Release();

  // Delete the manager and test that all BrowserAccessibility instances are
  // deleted.
  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) {
  // Create ui::AXNodeData objects for a simple document tree,
  // representing the accessibility information used to initialize
  // BrowserAccessibilityManager.
  ui::AXNodeData div;
  div.id = 2;
  div.role = ui::AX_ROLE_GROUP;
  div.state = 0;

  ui::AXNodeData text3;
  text3.id = 3;
  text3.role = ui::AX_ROLE_STATIC_TEXT;
  text3.state = 0;

  ui::AXNodeData text4;
  text4.id = 4;
  text4.role = ui::AX_ROLE_STATIC_TEXT;
  text4.state = 0;

  div.child_ids.push_back(3);
  div.child_ids.push_back(4);

  ui::AXNodeData root;
  root.id = 1;
  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
  root.state = 0;
  root.child_ids.push_back(2);

  // Construct a BrowserAccessibilityManager with this
  // ui::AXNodeData tree and a factory for an instance-counting
  // BrowserAccessibility and ensure that exactly 4 instances were
  // created. Note that the manager takes ownership of the factory.
  CountedBrowserAccessibility::reset();
  scoped_ptr<BrowserAccessibilityManager> manager(
      BrowserAccessibilityManager::Create(
          MakeAXTreeUpdate(root, div, text3, text4),
          NULL, new CountedBrowserAccessibilityFactory()));
  ASSERT_EQ(4, CountedBrowserAccessibility::num_instances());

  // Notify the BrowserAccessibilityManager that the div node and its children
  // were removed and ensure that only one BrowserAccessibility instance exists.
  root.child_ids.clear();
  AccessibilityHostMsg_EventParams param;
  param.event_type = ui::AX_EVENT_CHILDREN_CHANGED;
  param.update.nodes.push_back(root);
  param.id = root.id;
  std::vector<AccessibilityHostMsg_EventParams> events;
  events.push_back(param);
  manager->OnAccessibilityEvents(events);
  ASSERT_EQ(1, CountedBrowserAccessibility::num_instances());

  // Delete the manager and test that all BrowserAccessibility instances are
  // deleted.
  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

TEST_F(BrowserAccessibilityTest, TestTextBoundaries) {
  std::string line1 = "One two three.";
  std::string line2 = "Four five six.";
  std::string text_value = line1 + '\n' + line2;

  ui::AXNodeData root;
  root.id = 1;
  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
  root.child_ids.push_back(2);

  ui::AXNodeData text_field;
  text_field.id = 2;
  text_field.role = ui::AX_ROLE_TEXT_FIELD;
  text_field.AddStringAttribute(ui::AX_ATTR_VALUE, text_value);
  std::vector<int32> line_start_offsets;
  line_start_offsets.push_back(15);
  text_field.AddIntListAttribute(
      ui::AX_ATTR_LINE_BREAKS, line_start_offsets);
  text_field.child_ids.push_back(3);
  text_field.child_ids.push_back(5);
  text_field.child_ids.push_back(6);

  ui::AXNodeData static_text1;
  static_text1.id = 3;
  static_text1.role = ui::AX_ROLE_STATIC_TEXT;
  static_text1.AddStringAttribute(ui::AX_ATTR_VALUE, line1);
  static_text1.child_ids.push_back(4);

  ui::AXNodeData inline_box1;
  inline_box1.id = 4;
  inline_box1.role = ui::AX_ROLE_INLINE_TEXT_BOX;
  inline_box1.AddStringAttribute(ui::AX_ATTR_VALUE, line1);
  std::vector<int32> word_start_offsets1;
  word_start_offsets1.push_back(0);
  word_start_offsets1.push_back(4);
  word_start_offsets1.push_back(8);
  inline_box1.AddIntListAttribute(
      ui::AX_ATTR_WORD_STARTS, word_start_offsets1);

  ui::AXNodeData line_break;
  line_break.id = 5;
  line_break.role = ui::AX_ROLE_LINE_BREAK;
  line_break.AddStringAttribute(ui::AX_ATTR_VALUE, "\n");

  ui::AXNodeData static_text2;
  static_text2.id = 6;
  static_text2.role = ui::AX_ROLE_STATIC_TEXT;
  static_text2.AddStringAttribute(ui::AX_ATTR_VALUE, line2);
  static_text2.child_ids.push_back(7);

  ui::AXNodeData inline_box2;
  inline_box2.id = 7;
  inline_box2.role = ui::AX_ROLE_INLINE_TEXT_BOX;
  inline_box2.AddStringAttribute(ui::AX_ATTR_VALUE, line2);
  std::vector<int32> word_start_offsets2;
  word_start_offsets2.push_back(0);
  word_start_offsets2.push_back(5);
  word_start_offsets2.push_back(10);
  inline_box2.AddIntListAttribute(
      ui::AX_ATTR_WORD_STARTS, word_start_offsets2);

  CountedBrowserAccessibility::reset();
  scoped_ptr<BrowserAccessibilityManager> manager(
      BrowserAccessibilityManager::Create(
          MakeAXTreeUpdate(root, text_field, static_text1, inline_box1,
          line_break, static_text2, inline_box2),
          nullptr, new CountedBrowserAccessibilityFactory()));
  ASSERT_EQ(7, CountedBrowserAccessibility::num_instances());

  BrowserAccessibilityWin* root_obj =
      manager->GetRoot()->ToBrowserAccessibilityWin();
  ASSERT_NE(nullptr, root_obj);
  ASSERT_EQ(1, root_obj->PlatformChildCount());

  BrowserAccessibilityWin* text_field_obj =
      root_obj->PlatformGetChild(0)->ToBrowserAccessibilityWin();
  ASSERT_NE(nullptr, text_field_obj);

  long text_len;
  EXPECT_EQ(S_OK, text_field_obj->get_nCharacters(&text_len));

  base::win::ScopedBstr text;
  EXPECT_EQ(S_OK, text_field_obj->get_text(0, text_len, text.Receive()));
  EXPECT_EQ(text_value, base::UTF16ToUTF8(base::string16(text)));
  text.Reset();

  EXPECT_EQ(S_OK, text_field_obj->get_text(0, 4, text.Receive()));
  EXPECT_STREQ(L"One ", text);
  text.Reset();

  long start;
  long end;
  EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
      1, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive()));
  EXPECT_EQ(1, start);
  EXPECT_EQ(2, end);
  EXPECT_STREQ(L"n", text);
  text.Reset();

  EXPECT_EQ(S_FALSE, text_field_obj->get_textAtOffset(
      text_len, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive()));
  EXPECT_EQ(0, start);
  EXPECT_EQ(0, end);
  EXPECT_EQ(nullptr, text);
  text.Reset();

  EXPECT_EQ(S_FALSE, text_field_obj->get_textAtOffset(
      text_len, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
  EXPECT_EQ(0, start);
  EXPECT_EQ(0, end);
  EXPECT_EQ(nullptr, text);
  text.Reset();

  EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
      1, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
  EXPECT_EQ(0, start);
  EXPECT_EQ(4, end);
  EXPECT_STREQ(L"One ", text);
  text.Reset();

  EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
      6, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
  EXPECT_EQ(4, start);
  EXPECT_EQ(8, end);
  EXPECT_STREQ(L"two ", text);
  text.Reset();

  EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
      text_len - 1, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
  EXPECT_EQ(25, start);
  EXPECT_EQ(29, end);
  EXPECT_STREQ(L"six.", text);
  text.Reset();

  EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
      1, IA2_TEXT_BOUNDARY_LINE, &start, &end, text.Receive()));
  EXPECT_EQ(0, start);
  EXPECT_EQ(15, end);
  EXPECT_STREQ(L"One two three.\n", text);
  text.Reset();

  EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
      text_len, IA2_TEXT_BOUNDARY_LINE, &start, &end, text.Receive()));
  EXPECT_EQ(15, start);
  EXPECT_EQ(text_len, end);
  EXPECT_STREQ(L"Four five six.", text);
  text.Reset();

  EXPECT_EQ(S_OK, text_field_obj->get_text(
      0, IA2_TEXT_OFFSET_LENGTH, text.Receive()));
  EXPECT_EQ(text_value, base::UTF16ToUTF8(base::string16(text)));

  // Delete the manager and test that all BrowserAccessibility instances are
  // deleted.
  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) {
  const std::string text1_name = "One two three.";
  const std::string text2_name = " Four five six.";

  ui::AXNodeData text1;
  text1.id = 11;
  text1.role = ui::AX_ROLE_STATIC_TEXT;
  text1.state = 1 << ui::AX_STATE_READ_ONLY;
  text1.SetName(text1_name);

  ui::AXNodeData text2;
  text2.id = 12;
  text2.role = ui::AX_ROLE_STATIC_TEXT;
  text2.state = 1 << ui::AX_STATE_READ_ONLY;
  text2.SetName(text2_name);

  ui::AXNodeData root;
  root.id = 1;
  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
  root.state = 1 << ui::AX_STATE_READ_ONLY;
  root.child_ids.push_back(11);
  root.child_ids.push_back(12);

  CountedBrowserAccessibility::reset();
  scoped_ptr<BrowserAccessibilityManager> manager(
      BrowserAccessibilityManager::Create(
          MakeAXTreeUpdate(root, text1, text2),
          NULL, new CountedBrowserAccessibilityFactory()));
  ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());

  BrowserAccessibilityWin* root_obj =
      manager->GetRoot()->ToBrowserAccessibilityWin();

  long text_len;
  ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len));

  base::win::ScopedBstr text;
  ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive()));
  EXPECT_EQ(text1_name + text2_name, base::UTF16ToUTF8(base::string16(text)));

  long hyperlink_count;
  ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
  EXPECT_EQ(0, hyperlink_count);

  base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive()));
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(0, hyperlink.Receive()));
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive()));
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(29, hyperlink.Receive()));

  long hyperlink_index;
  EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
  EXPECT_EQ(-1, hyperlink_index);
  EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index));
  EXPECT_EQ(-1, hyperlink_index);
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(-1, &hyperlink_index));
  EXPECT_EQ(-1, hyperlink_index);
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(29, &hyperlink_index));
  EXPECT_EQ(-1, hyperlink_index);

  // Delete the manager and test that all BrowserAccessibility instances are
  // deleted.
  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

TEST_F(BrowserAccessibilityTest, TestComplexHypertext) {
  const std::string text1_name = "One two three.";
  const std::string text2_name = " Four five six.";
  const std::string button1_text_name = "red";
  const std::string link1_text_name = "blue";

  ui::AXNodeData text1;
  text1.id = 11;
  text1.role = ui::AX_ROLE_STATIC_TEXT;
  text1.state = 1 << ui::AX_STATE_READ_ONLY;
  text1.SetName(text1_name);

  ui::AXNodeData text2;
  text2.id = 12;
  text2.role = ui::AX_ROLE_STATIC_TEXT;
  text2.state = 1 << ui::AX_STATE_READ_ONLY;
  text2.SetName(text2_name);

  ui::AXNodeData button1, button1_text;
  button1.id = 13;
  button1_text.id = 15;
  button1_text.SetName(button1_text_name);
  button1.role = ui::AX_ROLE_BUTTON;
  button1_text.role = ui::AX_ROLE_STATIC_TEXT;
  button1.state = 1 << ui::AX_STATE_READ_ONLY;
  button1_text.state = 1 << ui::AX_STATE_READ_ONLY;
  button1.child_ids.push_back(15);

  ui::AXNodeData link1, link1_text;
  link1.id = 14;
  link1_text.id = 16;
  link1_text.SetName(link1_text_name);
  link1.role = ui::AX_ROLE_LINK;
  link1_text.role = ui::AX_ROLE_STATIC_TEXT;
  link1.state = 1 << ui::AX_STATE_READ_ONLY;
  link1_text.state = 1 << ui::AX_STATE_READ_ONLY;
  link1.child_ids.push_back(16);

  ui::AXNodeData root;
  root.id = 1;
  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
  root.state = 1 << ui::AX_STATE_READ_ONLY;
  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::reset();
  scoped_ptr<BrowserAccessibilityManager> manager(
      BrowserAccessibilityManager::Create(
          MakeAXTreeUpdate(root,
                           text1, button1, button1_text,
                           text2, link1, link1_text),
          NULL, new CountedBrowserAccessibilityFactory()));
  ASSERT_EQ(7, CountedBrowserAccessibility::num_instances());

  BrowserAccessibilityWin* root_obj =
      manager->GetRoot()->ToBrowserAccessibilityWin();

  long text_len;
  ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len));

  base::win::ScopedBstr text;
  ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive()));
  const std::string embed = base::UTF16ToUTF8(
      base::string16(1, BrowserAccessibilityWin::kEmbeddedCharacter));
  EXPECT_EQ(text1_name + embed + text2_name + embed,
            base::UTF16ToUTF8(base::string16(text)));
  text.Reset();

  long hyperlink_count;
  ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
  EXPECT_EQ(2, hyperlink_count);

  base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
  base::win::ScopedComPtr<IAccessibleText> hypertext;
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive()));
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(2, hyperlink.Receive()));
  EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive()));

  EXPECT_EQ(S_OK, root_obj->get_hyperlink(0, hyperlink.Receive()));
  EXPECT_EQ(S_OK,
            hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive()));
  EXPECT_EQ(S_OK, hypertext->get_text(0, 3, text.Receive()));
  EXPECT_STREQ(button1_text_name.c_str(),
               base::UTF16ToUTF8(base::string16(text)).c_str());
  text.Reset();
  hyperlink.Release();
  hypertext.Release();

  EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, hyperlink.Receive()));
  EXPECT_EQ(S_OK,
            hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive()));
  EXPECT_EQ(S_OK, hypertext->get_text(0, 4, text.Receive()));
  EXPECT_STREQ(link1_text_name.c_str(),
               base::UTF16ToUTF8(base::string16(text)).c_str());
  text.Reset();
  hyperlink.Release();
  hypertext.Release();

  long hyperlink_index;
  EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
  EXPECT_EQ(-1, hyperlink_index);
  EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index));
  EXPECT_EQ(-1, hyperlink_index);
  EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(14, &hyperlink_index));
  EXPECT_EQ(0, hyperlink_index);
  EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(30, &hyperlink_index));
  EXPECT_EQ(1, hyperlink_index);

  // Delete the manager and test that all BrowserAccessibility instances are
  // deleted.
  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

TEST_F(BrowserAccessibilityTest, TestCreateEmptyDocument) {
  // Try creating an empty document with busy state. Readonly is
  // set automatically.
  CountedBrowserAccessibility::reset();
  const int32 busy_state = 1 << ui::AX_STATE_BUSY;
  const int32 readonly_state = 1 << ui::AX_STATE_READ_ONLY;
  const int32 enabled_state = 1 << ui::AX_STATE_ENABLED;
  scoped_ptr<BrowserAccessibilityManager> manager(
      new BrowserAccessibilityManagerWin(
          BrowserAccessibilityManagerWin::GetEmptyDocument(),
          NULL,
          new CountedBrowserAccessibilityFactory()));

  // Verify the root is as we expect by default.
  BrowserAccessibility* root = manager->GetRoot();
  EXPECT_EQ(0, root->GetId());
  EXPECT_EQ(ui::AX_ROLE_ROOT_WEB_AREA, root->GetRole());
  EXPECT_EQ(busy_state | readonly_state | enabled_state, root->GetState());

  // Tree with a child textfield.
  ui::AXNodeData tree1_1;
  tree1_1.id = 1;
  tree1_1.role = ui::AX_ROLE_ROOT_WEB_AREA;
  tree1_1.child_ids.push_back(2);

  ui::AXNodeData tree1_2;
  tree1_2.id = 2;
  tree1_2.role = ui::AX_ROLE_TEXT_FIELD;

  // Process a load complete.
  std::vector<AccessibilityHostMsg_EventParams> params;
  params.push_back(AccessibilityHostMsg_EventParams());
  AccessibilityHostMsg_EventParams* msg = &params[0];
  msg->event_type = ui::AX_EVENT_LOAD_COMPLETE;
  msg->update.nodes.push_back(tree1_1);
  msg->update.nodes.push_back(tree1_2);
  msg->id = tree1_1.id;
  manager->OnAccessibilityEvents(params);

  // Save for later comparison.
  BrowserAccessibility* acc1_2 = manager->GetFromID(2);

  // Verify the root has changed.
  EXPECT_NE(root, manager->GetRoot());

  // And the proper child remains.
  EXPECT_EQ(ui::AX_ROLE_TEXT_FIELD, acc1_2->GetRole());
  EXPECT_EQ(2, acc1_2->GetId());

  // Tree with a child button.
  ui::AXNodeData tree2_1;
  tree2_1.id = 1;
  tree2_1.role = ui::AX_ROLE_ROOT_WEB_AREA;
  tree2_1.child_ids.push_back(3);

  ui::AXNodeData tree2_2;
  tree2_2.id = 3;
  tree2_2.role = ui::AX_ROLE_BUTTON;

  msg->update.nodes.clear();
  msg->update.nodes.push_back(tree2_1);
  msg->update.nodes.push_back(tree2_2);
  msg->id = tree2_1.id;

  // Fire another load complete.
  manager->OnAccessibilityEvents(params);

  BrowserAccessibility* acc2_2 = manager->GetFromID(3);

  // Verify the root has changed.
  EXPECT_NE(root, manager->GetRoot());

  // And the new child exists.
  EXPECT_EQ(ui::AX_ROLE_BUTTON, acc2_2->GetRole());
  EXPECT_EQ(3, acc2_2->GetId());

  // Ensure we properly cleaned up.
  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

// This is a regression test for a bug where the initial empty document
// loaded by a BrowserAccessibilityManagerWin couldn't be looked up by
// its UniqueIDWin, because the AX Tree was loaded in
// BrowserAccessibilityManager code before BrowserAccessibilityManagerWin
// was initialized.
TEST_F(BrowserAccessibilityTest, EmptyDocHasUniqueIdWin) {
  scoped_ptr<BrowserAccessibilityManagerWin> manager(
      new BrowserAccessibilityManagerWin(
          BrowserAccessibilityManagerWin::GetEmptyDocument(),
          NULL,
          new CountedBrowserAccessibilityFactory()));

  // Verify the root is as we expect by default.
  BrowserAccessibility* root = manager->GetRoot();
  EXPECT_EQ(0, root->GetId());
  EXPECT_EQ(ui::AX_ROLE_ROOT_WEB_AREA, root->GetRole());
  EXPECT_EQ(1 << ui::AX_STATE_BUSY |
            1 << ui::AX_STATE_READ_ONLY |
            1 << ui::AX_STATE_ENABLED,
            root->GetState());

  LONG unique_id_win = root->ToBrowserAccessibilityWin()->unique_id_win();
  ASSERT_EQ(root, manager->GetFromUniqueIdWin(unique_id_win));
}

TEST_F(BrowserAccessibilityTest, TestIA2Attributes) {
  ui::AXNodeData checkbox;
  checkbox.id = 2;
  checkbox.SetName("Checkbox");
  checkbox.role = ui::AX_ROLE_CHECK_BOX;
  checkbox.state = 1 << ui::AX_STATE_CHECKED;

  ui::AXNodeData root;
  root.id = 1;
  root.SetName("Document");
  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
  root.state = (1 << ui::AX_STATE_READ_ONLY) | (1 << ui::AX_STATE_FOCUSABLE);
  root.child_ids.push_back(2);

  CountedBrowserAccessibility::reset();
  scoped_ptr<BrowserAccessibilityManager> manager(
      BrowserAccessibilityManager::Create(
          MakeAXTreeUpdate(root, checkbox),
          nullptr, new CountedBrowserAccessibilityFactory()));
  ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());

  ASSERT_NE(nullptr, manager->GetRoot());
  BrowserAccessibilityWin* root_accessible =
      manager->GetRoot()->ToBrowserAccessibilityWin();
      ASSERT_NE(nullptr, root_accessible);
  ASSERT_EQ(1, root_accessible->PlatformChildCount());
  BrowserAccessibilityWin* checkbox_accessible =
      root_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
  ASSERT_NE(nullptr, checkbox_accessible);

  base::win::ScopedBstr attributes;
  HRESULT hr = checkbox_accessible->get_attributes(attributes.Receive());
  EXPECT_EQ(S_OK, hr);
  EXPECT_NE(nullptr, static_cast<BSTR>(attributes));
  std::wstring attributes_str(attributes, attributes.Length());
  EXPECT_EQ(L"checkable:true;", attributes_str);

  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

/**
 * Ensures that ui::AX_ATTR_TEXT_SEL_START/END attributes are correctly used to
 * determine caret position and text selection in various types of editable
 * elements.
 */
TEST_F(BrowserAccessibilityTest, TestCaretAndTextSelection) {
  ui::AXNodeData root;
  root.id = 1;
  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
  root.state = (1 << ui::AX_STATE_READ_ONLY) | (1 << ui::AX_STATE_FOCUSABLE);

  ui::AXNodeData combo_box;
  combo_box.id = 2;
  combo_box.role = ui::AX_ROLE_COMBO_BOX;
  combo_box.state = (1 << ui::AX_STATE_FOCUSABLE) | (1 << ui::AX_STATE_FOCUSED);
  combo_box.SetValue("Test1");
  // Place the caret between 't' and 'e'.
  combo_box.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, 1);
  combo_box.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, 1);

  ui::AXNodeData text_field;
  text_field.id = 3;
  text_field.role = ui::AX_ROLE_TEXT_FIELD;
  text_field.state = 1 << ui::AX_STATE_FOCUSABLE;
  text_field.SetValue("Test2");
  // Select the letter 'e'.
  text_field.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, 1);
  text_field.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, 2);

  root.child_ids.push_back(2);
  root.child_ids.push_back(3);

  CountedBrowserAccessibility::reset();
  scoped_ptr<BrowserAccessibilityManager> manager(
      BrowserAccessibilityManager::Create(
          MakeAXTreeUpdate(root, combo_box, text_field),
          nullptr, new CountedBrowserAccessibilityFactory()));
  ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());

  ASSERT_NE(nullptr, manager->GetRoot());
  BrowserAccessibilityWin* root_accessible =
      manager->GetRoot()->ToBrowserAccessibilityWin();
      ASSERT_NE(nullptr, root_accessible);
  ASSERT_EQ(2, root_accessible->PlatformChildCount());

  BrowserAccessibilityWin* combo_box_accessible =
      root_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
  ASSERT_NE(nullptr, combo_box_accessible);
  manager->SetFocus(combo_box_accessible, false /* notify */);
  ASSERT_EQ(combo_box_accessible,
      manager->GetFocus(root_accessible)->ToBrowserAccessibilityWin());
  BrowserAccessibilityWin* text_field_accessible =
      root_accessible->PlatformGetChild(1)->ToBrowserAccessibilityWin();
  ASSERT_NE(nullptr, text_field_accessible);

  // -2 is never a valid offset.
  LONG caret_offset = -2;
  LONG n_selections = -2;
  LONG selection_start = -2;
  LONG selection_end = -2;

  // Test get_caretOffset.
  HRESULT hr = combo_box_accessible->get_caretOffset(&caret_offset);;
  EXPECT_EQ(S_OK, hr);
  EXPECT_EQ(1L, caret_offset);
  // caret_offset should be -1 when the object is not focused.
  hr = text_field_accessible->get_caretOffset(&caret_offset);;
  EXPECT_EQ(S_FALSE, hr);
  EXPECT_EQ(-1L, caret_offset);

  // Move the focus to the text field.
  combo_box.state &= ~(1 << ui::AX_STATE_FOCUSED);
  text_field.state |= 1 << ui::AX_STATE_FOCUSED;
  manager->SetFocus(text_field_accessible, false /* notify */);
  ASSERT_EQ(text_field_accessible,
      manager->GetFocus(root_accessible)->ToBrowserAccessibilityWin());

  // The caret should be at the start of the selection.
  hr = text_field_accessible->get_caretOffset(&caret_offset);;
  EXPECT_EQ(S_OK, hr);
  EXPECT_EQ(1L, caret_offset);

  // Test get_nSelections.
  hr = combo_box_accessible->get_nSelections(&n_selections);;
  EXPECT_EQ(S_OK, hr);
  EXPECT_EQ(0L, n_selections);
  hr = text_field_accessible->get_nSelections(&n_selections);;
  EXPECT_EQ(S_OK, hr);
  EXPECT_EQ(1L, n_selections);

  // Test get_selection.
  hr = combo_box_accessible->get_selection(
      0L /* selection_index */, &selection_start, &selection_end);;
  EXPECT_EQ(E_INVALIDARG, hr); // No selections available.
  // Invalid in_args should not modify out_args.
  EXPECT_EQ(-2L, selection_start);
  EXPECT_EQ(-2L, selection_end);
  hr = text_field_accessible->get_selection(
      0L /* selection_index */, &selection_start, &selection_end);;
  EXPECT_EQ(S_OK, hr);
  EXPECT_EQ(1L, selection_start);
  EXPECT_EQ(2L, selection_end);

  manager.reset();
  ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
}

}  // namespace content
