Home | History | Annotate | Download | only in accessibility
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/memory/scoped_ptr.h"
      6 #include "base/win/scoped_comptr.h"
      7 #include "chrome/browser/accessibility/browser_accessibility_manager.h"
      8 #include "chrome/browser/accessibility/browser_accessibility_win.h"
      9 #include "content/common/view_messages.h"
     10 #include "testing/gtest/include/gtest/gtest.h"
     11 
     12 using webkit_glue::WebAccessibility;
     13 
     14 namespace {
     15 
     16 // Subclass of BrowserAccessibilityWin that counts the number of instances.
     17 class CountedBrowserAccessibility : public BrowserAccessibilityWin {
     18  public:
     19   CountedBrowserAccessibility() { global_obj_count_++; }
     20   virtual ~CountedBrowserAccessibility() { global_obj_count_--; }
     21   static int global_obj_count_;
     22 };
     23 
     24 int CountedBrowserAccessibility::global_obj_count_ = 0;
     25 
     26 // Factory that creates a CountedBrowserAccessibility.
     27 class CountedBrowserAccessibilityFactory
     28     : public BrowserAccessibilityFactory {
     29  public:
     30   virtual ~CountedBrowserAccessibilityFactory() {}
     31   virtual BrowserAccessibility* Create() {
     32     CComObject<CountedBrowserAccessibility>* instance;
     33     HRESULT hr = CComObject<CountedBrowserAccessibility>::CreateInstance(
     34         &instance);
     35     DCHECK(SUCCEEDED(hr));
     36     instance->AddRef();
     37     return instance;
     38   }
     39 };
     40 
     41 }  // anonymous namespace
     42 
     43 VARIANT CreateI4Variant(LONG value) {
     44   VARIANT variant = {0};
     45 
     46   V_VT(&variant) = VT_I4;
     47   V_I4(&variant) = value;
     48 
     49   return variant;
     50 }
     51 
     52 class BrowserAccessibilityTest : public testing::Test {
     53  protected:
     54   virtual void SetUp() {
     55     // ATL needs a pointer to a COM module.
     56     static CComModule module;
     57     _pAtlModule = &module;
     58 
     59     // Make sure COM is initialized for this thread; it's safe to call twice.
     60     ::CoInitialize(NULL);
     61   }
     62 
     63   virtual void TearDown() {
     64     ::CoUninitialize();
     65   }
     66 };
     67 
     68 // Test that BrowserAccessibilityManager correctly releases the tree of
     69 // BrowserAccessibility instances upon delete.
     70 TEST_F(BrowserAccessibilityTest, TestNoLeaks) {
     71   // Create WebAccessibility objects for a simple document tree,
     72   // representing the accessibility information used to initialize
     73   // BrowserAccessibilityManager.
     74   WebAccessibility button;
     75   button.id = 2;
     76   button.name = L"Button";
     77   button.role = WebAccessibility::ROLE_BUTTON;
     78   button.state = 0;
     79 
     80   WebAccessibility checkbox;
     81   checkbox.id = 3;
     82   checkbox.name = L"Checkbox";
     83   checkbox.role = WebAccessibility::ROLE_CHECKBOX;
     84   checkbox.state = 0;
     85 
     86   WebAccessibility root;
     87   root.id = 1;
     88   root.name = L"Document";
     89   root.role = WebAccessibility::ROLE_DOCUMENT;
     90   root.state = 0;
     91   root.children.push_back(button);
     92   root.children.push_back(checkbox);
     93 
     94   // Construct a BrowserAccessibilityManager with this WebAccessibility tree
     95   // and a factory for an instance-counting BrowserAccessibility, and ensure
     96   // that exactly 3 instances were created. Note that the manager takes
     97   // ownership of the factory.
     98   CountedBrowserAccessibility::global_obj_count_ = 0;
     99   BrowserAccessibilityManager* manager =
    100       BrowserAccessibilityManager::Create(
    101           GetDesktopWindow(),
    102           root,
    103           NULL,
    104           new CountedBrowserAccessibilityFactory());
    105   ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
    106 
    107   // Delete the manager and test that all 3 instances are deleted.
    108   delete manager;
    109   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
    110 
    111   // Construct a manager again, and this time use the IAccessible interface
    112   // to get new references to two of the three nodes in the tree.
    113   manager =
    114       BrowserAccessibilityManager::Create(
    115           GetDesktopWindow(),
    116           root,
    117           NULL,
    118           new CountedBrowserAccessibilityFactory());
    119   ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
    120   IAccessible* root_accessible =
    121       manager->GetRoot()->toBrowserAccessibilityWin();
    122   IDispatch* root_iaccessible = NULL;
    123   IDispatch* child1_iaccessible = NULL;
    124   VARIANT var_child;
    125   var_child.vt = VT_I4;
    126   var_child.lVal = CHILDID_SELF;
    127   HRESULT hr = root_accessible->get_accChild(var_child, &root_iaccessible);
    128   ASSERT_EQ(S_OK, hr);
    129   var_child.lVal = 1;
    130   hr = root_accessible->get_accChild(var_child, &child1_iaccessible);
    131   ASSERT_EQ(S_OK, hr);
    132 
    133   // Now delete the manager, and only one of the three nodes in the tree
    134   // should be released.
    135   delete manager;
    136   ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_);
    137 
    138   // Release each of our references and make sure that each one results in
    139   // the instance being deleted as its reference count hits zero.
    140   root_iaccessible->Release();
    141   ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_);
    142   child1_iaccessible->Release();
    143   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
    144 }
    145 
    146 TEST_F(BrowserAccessibilityTest, TestChildrenChange) {
    147   // Create WebAccessibility objects for a simple document tree,
    148   // representing the accessibility information used to initialize
    149   // BrowserAccessibilityManager.
    150   WebAccessibility text;
    151   text.id = 2;
    152   text.role = WebAccessibility::ROLE_STATIC_TEXT;
    153   text.name = L"old text";
    154   text.state = 0;
    155 
    156   WebAccessibility root;
    157   root.id = 1;
    158   root.name = L"Document";
    159   root.role = WebAccessibility::ROLE_DOCUMENT;
    160   root.state = 0;
    161   root.children.push_back(text);
    162 
    163   // Construct a BrowserAccessibilityManager with this WebAccessibility tree
    164   // and a factory for an instance-counting BrowserAccessibility.
    165   CountedBrowserAccessibility::global_obj_count_ = 0;
    166   BrowserAccessibilityManager* manager =
    167       BrowserAccessibilityManager::Create(
    168           GetDesktopWindow(),
    169           root,
    170           NULL,
    171           new CountedBrowserAccessibilityFactory());
    172 
    173   // Query for the text IAccessible and verify that it returns "old text" as its
    174   // value.
    175   base::win::ScopedComPtr<IDispatch> text_dispatch;
    176   HRESULT hr = manager->GetRoot()->toBrowserAccessibilityWin()->get_accChild(
    177       CreateI4Variant(1), text_dispatch.Receive());
    178   ASSERT_EQ(S_OK, hr);
    179 
    180   base::win::ScopedComPtr<IAccessible> text_accessible;
    181   hr = text_dispatch.QueryInterface(text_accessible.Receive());
    182   ASSERT_EQ(S_OK, hr);
    183 
    184   CComBSTR name;
    185   hr = text_accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name);
    186   ASSERT_EQ(S_OK, hr);
    187   EXPECT_STREQ(L"old text", name.m_str);
    188 
    189   text_dispatch.Release();
    190   text_accessible.Release();
    191 
    192   // Notify the BrowserAccessibilityManager that the text child has changed.
    193   text.name = L"new text";
    194   ViewHostMsg_AccessibilityNotification_Params param;
    195   param.notification_type =
    196       ViewHostMsg_AccessibilityNotification_Type::
    197         NOTIFICATION_TYPE_CHILDREN_CHANGED;
    198   param.acc_obj = text;
    199   std::vector<ViewHostMsg_AccessibilityNotification_Params> notifications;
    200   notifications.push_back(param);
    201   manager->OnAccessibilityNotifications(notifications);
    202 
    203   // Query for the text IAccessible and verify that it now returns "new text"
    204   // as its value.
    205   hr = manager->GetRoot()->toBrowserAccessibilityWin()->get_accChild(
    206       CreateI4Variant(1),
    207       text_dispatch.Receive());
    208   ASSERT_EQ(S_OK, hr);
    209 
    210   hr = text_dispatch.QueryInterface(text_accessible.Receive());
    211   ASSERT_EQ(S_OK, hr);
    212 
    213   hr = text_accessible->get_accName(CreateI4Variant(CHILDID_SELF), &name);
    214   ASSERT_EQ(S_OK, hr);
    215   EXPECT_STREQ(L"new text", name.m_str);
    216 
    217   text_dispatch.Release();
    218   text_accessible.Release();
    219 
    220   // Delete the manager and test that all BrowserAccessibility instances are
    221   // deleted.
    222   delete manager;
    223   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
    224 }
    225 
    226 TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) {
    227   // Create WebAccessibility objects for a simple document tree,
    228   // representing the accessibility information used to initialize
    229   // BrowserAccessibilityManager.
    230   WebAccessibility text;
    231   text.id = 3;
    232   text.role = WebAccessibility::ROLE_STATIC_TEXT;
    233   text.state = 0;
    234 
    235   WebAccessibility div;
    236   div.id = 2;
    237   div.role = WebAccessibility::ROLE_GROUP;
    238   div.state = 0;
    239 
    240   div.children.push_back(text);
    241   text.id = 4;
    242   div.children.push_back(text);
    243 
    244   WebAccessibility root;
    245   root.id = 1;
    246   root.role = WebAccessibility::ROLE_DOCUMENT;
    247   root.state = 0;
    248   root.children.push_back(div);
    249 
    250   // Construct a BrowserAccessibilityManager with this WebAccessibility tree
    251   // and a factory for an instance-counting BrowserAccessibility and ensure
    252   // that exactly 4 instances were created. Note that the manager takes
    253   // ownership of the factory.
    254   CountedBrowserAccessibility::global_obj_count_ = 0;
    255   BrowserAccessibilityManager* manager =
    256       BrowserAccessibilityManager::Create(
    257           GetDesktopWindow(),
    258           root,
    259           NULL,
    260           new CountedBrowserAccessibilityFactory());
    261   ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
    262 
    263   // Notify the BrowserAccessibilityManager that the div node and its children
    264   // were removed and ensure that only one BrowserAccessibility instance exists.
    265   root.children.clear();
    266   ViewHostMsg_AccessibilityNotification_Params param;
    267   param.notification_type =
    268       ViewHostMsg_AccessibilityNotification_Type::
    269         NOTIFICATION_TYPE_CHILDREN_CHANGED;
    270   param.acc_obj = root;
    271   std::vector<ViewHostMsg_AccessibilityNotification_Params> notifications;
    272   notifications.push_back(param);
    273   manager->OnAccessibilityNotifications(notifications);
    274   ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_);
    275 
    276   // Delete the manager and test that all BrowserAccessibility instances are
    277   // deleted.
    278   delete manager;
    279   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
    280 }
    281 
    282 TEST_F(BrowserAccessibilityTest, TestTextBoundaries) {
    283   WebAccessibility text1;
    284   text1.id = 11;
    285   text1.role = WebAccessibility::ROLE_TEXT_FIELD;
    286   text1.state = 0;
    287   text1.value = L"One two three.\nFour five six.";
    288 
    289   WebAccessibility root;
    290   root.id = 1;
    291   root.role = WebAccessibility::ROLE_DOCUMENT;
    292   root.state = 0;
    293   root.children.push_back(text1);
    294 
    295   CountedBrowserAccessibility::global_obj_count_ = 0;
    296   BrowserAccessibilityManager* manager = BrowserAccessibilityManager::Create(
    297       GetDesktopWindow(), root, NULL,
    298       new CountedBrowserAccessibilityFactory());
    299   ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_);
    300 
    301   BrowserAccessibilityWin* root_obj =
    302       manager->GetRoot()->toBrowserAccessibilityWin();
    303   BrowserAccessibilityWin* text1_obj =
    304       root_obj->GetChild(0)->toBrowserAccessibilityWin();
    305 
    306   BSTR text;
    307   long start;
    308   long end;
    309 
    310   long text1_len;
    311   ASSERT_EQ(S_OK, text1_obj->get_nCharacters(&text1_len));
    312 
    313   ASSERT_EQ(S_OK, text1_obj->get_text(0, text1_len, &text));
    314   ASSERT_EQ(text, text1.value);
    315   SysFreeString(text);
    316 
    317   ASSERT_EQ(S_OK, text1_obj->get_text(0, 4, &text));
    318   ASSERT_EQ(text, string16(L"One "));
    319   SysFreeString(text);
    320 
    321   ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
    322       1, IA2_TEXT_BOUNDARY_CHAR, &start, &end, &text));
    323   ASSERT_EQ(start, 1);
    324   ASSERT_EQ(end, 2);
    325   ASSERT_EQ(text, string16(L"n"));
    326   SysFreeString(text);
    327 
    328   ASSERT_EQ(S_FALSE, text1_obj->get_textAtOffset(
    329       text1_len, IA2_TEXT_BOUNDARY_CHAR, &start, &end, &text));
    330   ASSERT_EQ(start, text1_len);
    331   ASSERT_EQ(end, text1_len);
    332 
    333   ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
    334       1, IA2_TEXT_BOUNDARY_WORD, &start, &end, &text));
    335   ASSERT_EQ(start, 0);
    336   ASSERT_EQ(end, 3);
    337   ASSERT_EQ(text, string16(L"One"));
    338   SysFreeString(text);
    339 
    340   ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
    341       6, IA2_TEXT_BOUNDARY_WORD, &start, &end, &text));
    342   ASSERT_EQ(start, 4);
    343   ASSERT_EQ(end, 7);
    344   ASSERT_EQ(text, string16(L"two"));
    345   SysFreeString(text);
    346 
    347   ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
    348       text1_len, IA2_TEXT_BOUNDARY_WORD, &start, &end, &text));
    349   ASSERT_EQ(start, 25);
    350   ASSERT_EQ(end, 29);
    351   ASSERT_EQ(text, string16(L"six."));
    352   SysFreeString(text);
    353 
    354   ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
    355       1, IA2_TEXT_BOUNDARY_LINE, &start, &end, &text));
    356   ASSERT_EQ(start, 0);
    357   ASSERT_EQ(end, 13);
    358   ASSERT_EQ(text, string16(L"One two three"));
    359   SysFreeString(text);
    360 
    361   // Delete the manager and test that all BrowserAccessibility instances are
    362   // deleted.
    363   delete manager;
    364   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
    365 }
    366