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