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:
CountedBrowserAccessibility()19 CountedBrowserAccessibility() { global_obj_count_++; }
~CountedBrowserAccessibility()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:
~CountedBrowserAccessibilityFactory()30 virtual ~CountedBrowserAccessibilityFactory() {}
Create()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
CreateI4Variant(LONG value)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:
SetUp()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
TearDown()63 virtual void TearDown() {
64 ::CoUninitialize();
65 }
66 };
67
68 // Test that BrowserAccessibilityManager correctly releases the tree of
69 // BrowserAccessibility instances upon delete.
TEST_F(BrowserAccessibilityTest,TestNoLeaks)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
TEST_F(BrowserAccessibilityTest,TestChildrenChange)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
TEST_F(BrowserAccessibilityTest,TestChildrenChangeNoLeaks)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
TEST_F(BrowserAccessibilityTest,TestTextBoundaries)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