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
12using webkit_glue::WebAccessibility;
13
14namespace {
15
16// Subclass of BrowserAccessibilityWin that counts the number of instances.
17class CountedBrowserAccessibility : public BrowserAccessibilityWin {
18 public:
19  CountedBrowserAccessibility() { global_obj_count_++; }
20  virtual ~CountedBrowserAccessibility() { global_obj_count_--; }
21  static int global_obj_count_;
22};
23
24int CountedBrowserAccessibility::global_obj_count_ = 0;
25
26// Factory that creates a CountedBrowserAccessibility.
27class 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
43VARIANT 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
52class 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.
70TEST_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
146TEST_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
226TEST_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
282TEST_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