Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_win_unittest.cc
blob2235b24be48edd8d7b7b94b4c8d8ad6777146111
1 // Copyright (c) 2012 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.
5 #include "base/memory/scoped_ptr.h"
6 #include "base/strings/utf_string_conversions.h"
7 #include "base/win/scoped_bstr.h"
8 #include "base/win/scoped_comptr.h"
9 #include "base/win/scoped_variant.h"
10 #include "content/browser/accessibility/browser_accessibility_manager.h"
11 #include "content/browser/accessibility/browser_accessibility_manager_win.h"
12 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
13 #include "content/browser/accessibility/browser_accessibility_win.h"
14 #include "content/browser/renderer_host/legacy_render_widget_host_win.h"
15 #include "content/common/accessibility_messages.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "ui/base/win/atl_module.h"
19 namespace content {
20 namespace {
23 // CountedBrowserAccessibility ------------------------------------------------
25 // Subclass of BrowserAccessibilityWin that counts the number of instances.
26 class CountedBrowserAccessibility : public BrowserAccessibilityWin {
27 public:
28 CountedBrowserAccessibility();
29 virtual ~CountedBrowserAccessibility();
31 static void reset() { num_instances_ = 0; }
32 static int num_instances() { return num_instances_; }
34 private:
35 static int num_instances_;
37 DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibility);
40 // static
41 int CountedBrowserAccessibility::num_instances_ = 0;
43 CountedBrowserAccessibility::CountedBrowserAccessibility() {
44 ++num_instances_;
47 CountedBrowserAccessibility::~CountedBrowserAccessibility() {
48 --num_instances_;
52 // CountedBrowserAccessibilityFactory -----------------------------------------
54 // Factory that creates a CountedBrowserAccessibility.
55 class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory {
56 public:
57 CountedBrowserAccessibilityFactory();
59 private:
60 virtual ~CountedBrowserAccessibilityFactory();
62 virtual BrowserAccessibility* Create() override;
64 DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibilityFactory);
67 CountedBrowserAccessibilityFactory::CountedBrowserAccessibilityFactory() {
70 CountedBrowserAccessibilityFactory::~CountedBrowserAccessibilityFactory() {
73 BrowserAccessibility* CountedBrowserAccessibilityFactory::Create() {
74 CComObject<CountedBrowserAccessibility>* instance;
75 HRESULT hr = CComObject<CountedBrowserAccessibility>::CreateInstance(
76 &instance);
77 DCHECK(SUCCEEDED(hr));
78 instance->AddRef();
79 return instance;
82 } // namespace
85 // BrowserAccessibilityTest ---------------------------------------------------
87 class BrowserAccessibilityTest : public testing::Test {
88 public:
89 BrowserAccessibilityTest();
90 virtual ~BrowserAccessibilityTest();
92 private:
93 virtual void SetUp() override;
95 DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityTest);
98 BrowserAccessibilityTest::BrowserAccessibilityTest() {
101 BrowserAccessibilityTest::~BrowserAccessibilityTest() {
104 void BrowserAccessibilityTest::SetUp() {
105 ui::win::CreateATLModuleIfNeeded();
109 // Actual tests ---------------------------------------------------------------
111 // Test that BrowserAccessibilityManager correctly releases the tree of
112 // BrowserAccessibility instances upon delete.
113 TEST_F(BrowserAccessibilityTest, TestNoLeaks) {
114 // Create ui::AXNodeData objects for a simple document tree,
115 // representing the accessibility information used to initialize
116 // BrowserAccessibilityManager.
117 ui::AXNodeData button;
118 button.id = 2;
119 button.SetName("Button");
120 button.role = ui::AX_ROLE_BUTTON;
121 button.state = 0;
123 ui::AXNodeData checkbox;
124 checkbox.id = 3;
125 checkbox.SetName("Checkbox");
126 checkbox.role = ui::AX_ROLE_CHECK_BOX;
127 checkbox.state = 0;
129 ui::AXNodeData root;
130 root.id = 1;
131 root.SetName("Document");
132 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
133 root.state = 0;
134 root.child_ids.push_back(2);
135 root.child_ids.push_back(3);
137 // Construct a BrowserAccessibilityManager with this
138 // ui::AXNodeData tree and a factory for an instance-counting
139 // BrowserAccessibility, and ensure that exactly 3 instances were
140 // created. Note that the manager takes ownership of the factory.
141 CountedBrowserAccessibility::reset();
142 scoped_ptr<BrowserAccessibilityManager> manager(
143 BrowserAccessibilityManager::Create(
144 MakeAXTreeUpdate(root, button, checkbox),
145 NULL, new CountedBrowserAccessibilityFactory()));
146 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
148 // Delete the manager and test that all 3 instances are deleted.
149 manager.reset();
150 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
152 // Construct a manager again, and this time use the IAccessible interface
153 // to get new references to two of the three nodes in the tree.
154 manager.reset(BrowserAccessibilityManager::Create(
155 MakeAXTreeUpdate(root, button, checkbox),
156 NULL, new CountedBrowserAccessibilityFactory()));
157 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
158 IAccessible* root_accessible =
159 manager->GetRoot()->ToBrowserAccessibilityWin();
160 IDispatch* root_iaccessible = NULL;
161 IDispatch* child1_iaccessible = NULL;
162 base::win::ScopedVariant childid_self(CHILDID_SELF);
163 HRESULT hr = root_accessible->get_accChild(childid_self, &root_iaccessible);
164 ASSERT_EQ(S_OK, hr);
165 base::win::ScopedVariant one(1);
166 hr = root_accessible->get_accChild(one, &child1_iaccessible);
167 ASSERT_EQ(S_OK, hr);
169 // Now delete the manager, and only one of the three nodes in the tree
170 // should be released.
171 manager.reset();
172 ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());
174 // Release each of our references and make sure that each one results in
175 // the instance being deleted as its reference count hits zero.
176 root_iaccessible->Release();
177 ASSERT_EQ(1, CountedBrowserAccessibility::num_instances());
178 child1_iaccessible->Release();
179 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
182 TEST_F(BrowserAccessibilityTest, TestChildrenChange) {
183 // Create ui::AXNodeData objects for a simple document tree,
184 // representing the accessibility information used to initialize
185 // BrowserAccessibilityManager.
186 ui::AXNodeData text;
187 text.id = 2;
188 text.role = ui::AX_ROLE_STATIC_TEXT;
189 text.SetName("old text");
190 text.state = 0;
192 ui::AXNodeData root;
193 root.id = 1;
194 root.SetName("Document");
195 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
196 root.state = 0;
197 root.child_ids.push_back(2);
199 // Construct a BrowserAccessibilityManager with this
200 // ui::AXNodeData tree and a factory for an instance-counting
201 // BrowserAccessibility.
202 CountedBrowserAccessibility::reset();
203 scoped_ptr<BrowserAccessibilityManager> manager(
204 BrowserAccessibilityManager::Create(
205 MakeAXTreeUpdate(root, text),
206 NULL, new CountedBrowserAccessibilityFactory()));
208 // Query for the text IAccessible and verify that it returns "old text" as its
209 // value.
210 base::win::ScopedVariant one(1);
211 base::win::ScopedComPtr<IDispatch> text_dispatch;
212 HRESULT hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
213 one, text_dispatch.Receive());
214 ASSERT_EQ(S_OK, hr);
216 base::win::ScopedComPtr<IAccessible> text_accessible;
217 hr = text_dispatch.QueryInterface(text_accessible.Receive());
218 ASSERT_EQ(S_OK, hr);
220 base::win::ScopedVariant childid_self(CHILDID_SELF);
221 base::win::ScopedBstr name;
222 hr = text_accessible->get_accName(childid_self, name.Receive());
223 ASSERT_EQ(S_OK, hr);
224 EXPECT_EQ(L"old text", base::string16(name));
225 name.Reset();
227 text_dispatch.Release();
228 text_accessible.Release();
230 // Notify the BrowserAccessibilityManager that the text child has changed.
231 ui::AXNodeData text2;
232 text2.id = 2;
233 text2.role = ui::AX_ROLE_STATIC_TEXT;
234 text2.SetName("new text");
235 text2.SetName("old text");
236 AccessibilityHostMsg_EventParams param;
237 param.event_type = ui::AX_EVENT_CHILDREN_CHANGED;
238 param.update.nodes.push_back(text2);
239 param.id = text2.id;
240 std::vector<AccessibilityHostMsg_EventParams> events;
241 events.push_back(param);
242 manager->OnAccessibilityEvents(events);
244 // Query for the text IAccessible and verify that it now returns "new text"
245 // as its value.
246 hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
247 one, text_dispatch.Receive());
248 ASSERT_EQ(S_OK, hr);
250 hr = text_dispatch.QueryInterface(text_accessible.Receive());
251 ASSERT_EQ(S_OK, hr);
253 hr = text_accessible->get_accName(childid_self, name.Receive());
254 ASSERT_EQ(S_OK, hr);
255 EXPECT_EQ(L"new text", base::string16(name));
257 text_dispatch.Release();
258 text_accessible.Release();
260 // Delete the manager and test that all BrowserAccessibility instances are
261 // deleted.
262 manager.reset();
263 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
266 TEST_F(BrowserAccessibilityTest, TestChildrenChangeNoLeaks) {
267 // Create ui::AXNodeData objects for a simple document tree,
268 // representing the accessibility information used to initialize
269 // BrowserAccessibilityManager.
270 ui::AXNodeData div;
271 div.id = 2;
272 div.role = ui::AX_ROLE_GROUP;
273 div.state = 0;
275 ui::AXNodeData text3;
276 text3.id = 3;
277 text3.role = ui::AX_ROLE_STATIC_TEXT;
278 text3.state = 0;
280 ui::AXNodeData text4;
281 text4.id = 4;
282 text4.role = ui::AX_ROLE_STATIC_TEXT;
283 text4.state = 0;
285 div.child_ids.push_back(3);
286 div.child_ids.push_back(4);
288 ui::AXNodeData root;
289 root.id = 1;
290 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
291 root.state = 0;
292 root.child_ids.push_back(2);
294 // Construct a BrowserAccessibilityManager with this
295 // ui::AXNodeData tree and a factory for an instance-counting
296 // BrowserAccessibility and ensure that exactly 4 instances were
297 // created. Note that the manager takes ownership of the factory.
298 CountedBrowserAccessibility::reset();
299 scoped_ptr<BrowserAccessibilityManager> manager(
300 BrowserAccessibilityManager::Create(
301 MakeAXTreeUpdate(root, div, text3, text4),
302 NULL, new CountedBrowserAccessibilityFactory()));
303 ASSERT_EQ(4, CountedBrowserAccessibility::num_instances());
305 // Notify the BrowserAccessibilityManager that the div node and its children
306 // were removed and ensure that only one BrowserAccessibility instance exists.
307 root.child_ids.clear();
308 AccessibilityHostMsg_EventParams param;
309 param.event_type = ui::AX_EVENT_CHILDREN_CHANGED;
310 param.update.nodes.push_back(root);
311 param.id = root.id;
312 std::vector<AccessibilityHostMsg_EventParams> events;
313 events.push_back(param);
314 manager->OnAccessibilityEvents(events);
315 ASSERT_EQ(1, CountedBrowserAccessibility::num_instances());
317 // Delete the manager and test that all BrowserAccessibility instances are
318 // deleted.
319 manager.reset();
320 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
323 TEST_F(BrowserAccessibilityTest, DISABLED_TestTextBoundaries) {
324 std::string text1_value = "One two three.\nFour five six.";
326 ui::AXNodeData text1;
327 text1.id = 11;
328 text1.role = ui::AX_ROLE_TEXT_FIELD;
329 text1.state = 0;
330 text1.AddStringAttribute(ui::AX_ATTR_VALUE, text1_value);
331 std::vector<int32> line_breaks;
332 line_breaks.push_back(15);
333 text1.AddIntListAttribute(
334 ui::AX_ATTR_LINE_BREAKS, line_breaks);
336 ui::AXNodeData root;
337 root.id = 1;
338 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
339 root.state = 0;
340 root.child_ids.push_back(11);
342 CountedBrowserAccessibility::reset();
343 scoped_ptr<BrowserAccessibilityManager> manager(
344 BrowserAccessibilityManager::Create(
345 MakeAXTreeUpdate(root, text1),
346 NULL, new CountedBrowserAccessibilityFactory()));
347 ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());
349 BrowserAccessibilityWin* root_obj =
350 manager->GetRoot()->ToBrowserAccessibilityWin();
351 BrowserAccessibilityWin* text1_obj =
352 root_obj->PlatformGetChild(0)->ToBrowserAccessibilityWin();
354 long text1_len;
355 ASSERT_EQ(S_OK, text1_obj->get_nCharacters(&text1_len));
357 base::win::ScopedBstr text;
358 ASSERT_EQ(S_OK, text1_obj->get_text(0, text1_len, text.Receive()));
359 ASSERT_EQ(text1_value, base::UTF16ToUTF8(base::string16(text)));
360 text.Reset();
362 ASSERT_EQ(S_OK, text1_obj->get_text(0, 4, text.Receive()));
363 ASSERT_STREQ(L"One ", text);
364 text.Reset();
366 long start;
367 long end;
368 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
369 1, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive()));
370 ASSERT_EQ(1, start);
371 ASSERT_EQ(2, end);
372 ASSERT_STREQ(L"n", text);
373 text.Reset();
375 ASSERT_EQ(S_FALSE, text1_obj->get_textAtOffset(
376 text1_len, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive()));
377 ASSERT_EQ(0, start);
378 ASSERT_EQ(0, end);
379 text.Reset();
381 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
382 1, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
383 ASSERT_EQ(0, start);
384 ASSERT_EQ(4, end);
385 ASSERT_STREQ(L"One ", text);
386 text.Reset();
388 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
389 6, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
390 ASSERT_EQ(4, start);
391 ASSERT_EQ(8, end);
392 ASSERT_STREQ(L"two\n", text);
393 text.Reset();
395 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
396 text1_len, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
397 ASSERT_EQ(25, start);
398 ASSERT_EQ(29, end);
399 ASSERT_STREQ(L"six.", text);
400 text.Reset();
402 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
403 1, IA2_TEXT_BOUNDARY_LINE, &start, &end, text.Receive()));
404 ASSERT_EQ(0, start);
405 ASSERT_EQ(15, end);
406 ASSERT_STREQ(L"One two three.\n", text);
407 text.Reset();
409 ASSERT_EQ(S_OK, text1_obj->get_textAtOffset(
410 text1_len, IA2_TEXT_BOUNDARY_LINE, &start, &end, text.Receive()));
411 ASSERT_EQ(15, start);
412 ASSERT_EQ(text1_len, end);
413 ASSERT_STREQ(L"Four five six.", text);
414 text.Reset();
416 ASSERT_EQ(S_OK,
417 text1_obj->get_text(0, IA2_TEXT_OFFSET_LENGTH, text.Receive()));
418 ASSERT_EQ(0, start);
419 ASSERT_EQ(text1_len, end);
420 ASSERT_STREQ(L"One two three.\nFour five six.", text);
422 // Delete the manager and test that all BrowserAccessibility instances are
423 // deleted.
424 manager.reset();
425 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
428 TEST_F(BrowserAccessibilityTest, TestSimpleHypertext) {
429 const std::string text1_name = "One two three.";
430 const std::string text2_name = " Four five six.";
432 ui::AXNodeData text1;
433 text1.id = 11;
434 text1.role = ui::AX_ROLE_STATIC_TEXT;
435 text1.state = 1 << ui::AX_STATE_READ_ONLY;
436 text1.SetName(text1_name);
438 ui::AXNodeData text2;
439 text2.id = 12;
440 text2.role = ui::AX_ROLE_STATIC_TEXT;
441 text2.state = 1 << ui::AX_STATE_READ_ONLY;
442 text2.SetName(text2_name);
444 ui::AXNodeData root;
445 root.id = 1;
446 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
447 root.state = 1 << ui::AX_STATE_READ_ONLY;
448 root.child_ids.push_back(11);
449 root.child_ids.push_back(12);
451 CountedBrowserAccessibility::reset();
452 scoped_ptr<BrowserAccessibilityManager> manager(
453 BrowserAccessibilityManager::Create(
454 MakeAXTreeUpdate(root, text1, text2),
455 NULL, new CountedBrowserAccessibilityFactory()));
456 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
458 BrowserAccessibilityWin* root_obj =
459 manager->GetRoot()->ToBrowserAccessibilityWin();
461 long text_len;
462 ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len));
464 base::win::ScopedBstr text;
465 ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive()));
466 EXPECT_EQ(text1_name + text2_name, base::UTF16ToUTF8(base::string16(text)));
468 long hyperlink_count;
469 ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
470 EXPECT_EQ(0, hyperlink_count);
472 base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
473 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive()));
474 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(0, hyperlink.Receive()));
475 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive()));
476 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(29, hyperlink.Receive()));
478 long hyperlink_index;
479 EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
480 EXPECT_EQ(-1, hyperlink_index);
481 EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index));
482 EXPECT_EQ(-1, hyperlink_index);
483 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(-1, &hyperlink_index));
484 EXPECT_EQ(-1, hyperlink_index);
485 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(29, &hyperlink_index));
486 EXPECT_EQ(-1, hyperlink_index);
488 // Delete the manager and test that all BrowserAccessibility instances are
489 // deleted.
490 manager.reset();
491 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
494 TEST_F(BrowserAccessibilityTest, TestComplexHypertext) {
495 const std::string text1_name = "One two three.";
496 const std::string text2_name = " Four five six.";
497 const std::string button1_text_name = "red";
498 const std::string link1_text_name = "blue";
500 ui::AXNodeData text1;
501 text1.id = 11;
502 text1.role = ui::AX_ROLE_STATIC_TEXT;
503 text1.state = 1 << ui::AX_STATE_READ_ONLY;
504 text1.SetName(text1_name);
506 ui::AXNodeData text2;
507 text2.id = 12;
508 text2.role = ui::AX_ROLE_STATIC_TEXT;
509 text2.state = 1 << ui::AX_STATE_READ_ONLY;
510 text2.SetName(text2_name);
512 ui::AXNodeData button1, button1_text;
513 button1.id = 13;
514 button1_text.id = 15;
515 button1_text.SetName(button1_text_name);
516 button1.role = ui::AX_ROLE_BUTTON;
517 button1_text.role = ui::AX_ROLE_STATIC_TEXT;
518 button1.state = 1 << ui::AX_STATE_READ_ONLY;
519 button1_text.state = 1 << ui::AX_STATE_READ_ONLY;
520 button1.child_ids.push_back(15);
522 ui::AXNodeData link1, link1_text;
523 link1.id = 14;
524 link1_text.id = 16;
525 link1_text.SetName(link1_text_name);
526 link1.role = ui::AX_ROLE_LINK;
527 link1_text.role = ui::AX_ROLE_STATIC_TEXT;
528 link1.state = 1 << ui::AX_STATE_READ_ONLY;
529 link1_text.state = 1 << ui::AX_STATE_READ_ONLY;
530 link1.child_ids.push_back(16);
532 ui::AXNodeData root;
533 root.id = 1;
534 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
535 root.state = 1 << ui::AX_STATE_READ_ONLY;
536 root.child_ids.push_back(11);
537 root.child_ids.push_back(13);
538 root.child_ids.push_back(12);
539 root.child_ids.push_back(14);
541 CountedBrowserAccessibility::reset();
542 scoped_ptr<BrowserAccessibilityManager> manager(
543 BrowserAccessibilityManager::Create(
544 MakeAXTreeUpdate(root,
545 text1, button1, button1_text,
546 text2, link1, link1_text),
547 NULL, new CountedBrowserAccessibilityFactory()));
548 ASSERT_EQ(7, CountedBrowserAccessibility::num_instances());
550 BrowserAccessibilityWin* root_obj =
551 manager->GetRoot()->ToBrowserAccessibilityWin();
553 long text_len;
554 ASSERT_EQ(S_OK, root_obj->get_nCharacters(&text_len));
556 base::win::ScopedBstr text;
557 ASSERT_EQ(S_OK, root_obj->get_text(0, text_len, text.Receive()));
558 const std::string embed = base::UTF16ToUTF8(
559 base::string16(1, BrowserAccessibilityWin::kEmbeddedCharacter));
560 EXPECT_EQ(text1_name + embed + text2_name + embed,
561 base::UTF16ToUTF8(base::string16(text)));
562 text.Reset();
564 long hyperlink_count;
565 ASSERT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
566 EXPECT_EQ(2, hyperlink_count);
568 base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
569 base::win::ScopedComPtr<IAccessibleText> hypertext;
570 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, hyperlink.Receive()));
571 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(2, hyperlink.Receive()));
572 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(28, hyperlink.Receive()));
574 EXPECT_EQ(S_OK, root_obj->get_hyperlink(0, hyperlink.Receive()));
575 EXPECT_EQ(S_OK,
576 hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive()));
577 EXPECT_EQ(S_OK, hypertext->get_text(0, 3, text.Receive()));
578 EXPECT_STREQ(button1_text_name.c_str(),
579 base::UTF16ToUTF8(base::string16(text)).c_str());
580 text.Reset();
581 hyperlink.Release();
582 hypertext.Release();
584 EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, hyperlink.Receive()));
585 EXPECT_EQ(S_OK,
586 hyperlink.QueryInterface<IAccessibleText>(hypertext.Receive()));
587 EXPECT_EQ(S_OK, hypertext->get_text(0, 4, text.Receive()));
588 EXPECT_STREQ(link1_text_name.c_str(),
589 base::UTF16ToUTF8(base::string16(text)).c_str());
590 text.Reset();
591 hyperlink.Release();
592 hypertext.Release();
594 long hyperlink_index;
595 EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
596 EXPECT_EQ(-1, hyperlink_index);
597 EXPECT_EQ(E_FAIL, root_obj->get_hyperlinkIndex(28, &hyperlink_index));
598 EXPECT_EQ(-1, hyperlink_index);
599 EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(14, &hyperlink_index));
600 EXPECT_EQ(0, hyperlink_index);
601 EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(30, &hyperlink_index));
602 EXPECT_EQ(1, hyperlink_index);
604 // Delete the manager and test that all BrowserAccessibility instances are
605 // deleted.
606 manager.reset();
607 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
610 TEST_F(BrowserAccessibilityTest, TestCreateEmptyDocument) {
611 // Try creating an empty document with busy state. Readonly is
612 // set automatically.
613 CountedBrowserAccessibility::reset();
614 const int32 busy_state = 1 << ui::AX_STATE_BUSY;
615 const int32 readonly_state = 1 << ui::AX_STATE_READ_ONLY;
616 const int32 enabled_state = 1 << ui::AX_STATE_ENABLED;
617 scoped_ptr<BrowserAccessibilityManager> manager(
618 new BrowserAccessibilityManagerWin(
619 BrowserAccessibilityManagerWin::GetEmptyDocument(),
620 NULL,
621 new CountedBrowserAccessibilityFactory()));
623 // Verify the root is as we expect by default.
624 BrowserAccessibility* root = manager->GetRoot();
625 EXPECT_EQ(0, root->GetId());
626 EXPECT_EQ(ui::AX_ROLE_ROOT_WEB_AREA, root->GetRole());
627 EXPECT_EQ(busy_state | readonly_state | enabled_state, root->GetState());
629 // Tree with a child textfield.
630 ui::AXNodeData tree1_1;
631 tree1_1.id = 1;
632 tree1_1.role = ui::AX_ROLE_ROOT_WEB_AREA;
633 tree1_1.child_ids.push_back(2);
635 ui::AXNodeData tree1_2;
636 tree1_2.id = 2;
637 tree1_2.role = ui::AX_ROLE_TEXT_FIELD;
639 // Process a load complete.
640 std::vector<AccessibilityHostMsg_EventParams> params;
641 params.push_back(AccessibilityHostMsg_EventParams());
642 AccessibilityHostMsg_EventParams* msg = &params[0];
643 msg->event_type = ui::AX_EVENT_LOAD_COMPLETE;
644 msg->update.nodes.push_back(tree1_1);
645 msg->update.nodes.push_back(tree1_2);
646 msg->id = tree1_1.id;
647 manager->OnAccessibilityEvents(params);
649 // Save for later comparison.
650 BrowserAccessibility* acc1_2 = manager->GetFromID(2);
652 // Verify the root has changed.
653 EXPECT_NE(root, manager->GetRoot());
655 // And the proper child remains.
656 EXPECT_EQ(ui::AX_ROLE_TEXT_FIELD, acc1_2->GetRole());
657 EXPECT_EQ(2, acc1_2->GetId());
659 // Tree with a child button.
660 ui::AXNodeData tree2_1;
661 tree2_1.id = 1;
662 tree2_1.role = ui::AX_ROLE_ROOT_WEB_AREA;
663 tree2_1.child_ids.push_back(3);
665 ui::AXNodeData tree2_2;
666 tree2_2.id = 3;
667 tree2_2.role = ui::AX_ROLE_BUTTON;
669 msg->update.nodes.clear();
670 msg->update.nodes.push_back(tree2_1);
671 msg->update.nodes.push_back(tree2_2);
672 msg->id = tree2_1.id;
674 // Fire another load complete.
675 manager->OnAccessibilityEvents(params);
677 BrowserAccessibility* acc2_2 = manager->GetFromID(3);
679 // Verify the root has changed.
680 EXPECT_NE(root, manager->GetRoot());
682 // And the new child exists.
683 EXPECT_EQ(ui::AX_ROLE_BUTTON, acc2_2->GetRole());
684 EXPECT_EQ(3, acc2_2->GetId());
686 // Ensure we properly cleaned up.
687 manager.reset();
688 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
691 // This is a regression test for a bug where the initial empty document
692 // loaded by a BrowserAccessibilityManagerWin couldn't be looked up by
693 // its UniqueIDWin, because the AX Tree was loaded in
694 // BrowserAccessibilityManager code before BrowserAccessibilityManagerWin
695 // was initialized.
696 TEST_F(BrowserAccessibilityTest, EmptyDocHasUniqueIdWin) {
697 scoped_ptr<BrowserAccessibilityManagerWin> manager(
698 new BrowserAccessibilityManagerWin(
699 BrowserAccessibilityManagerWin::GetEmptyDocument(),
700 NULL,
701 new CountedBrowserAccessibilityFactory()));
703 // Verify the root is as we expect by default.
704 BrowserAccessibility* root = manager->GetRoot();
705 EXPECT_EQ(0, root->GetId());
706 EXPECT_EQ(ui::AX_ROLE_ROOT_WEB_AREA, root->GetRole());
707 EXPECT_EQ(1 << ui::AX_STATE_BUSY |
708 1 << ui::AX_STATE_READ_ONLY |
709 1 << ui::AX_STATE_ENABLED,
710 root->GetState());
712 LONG unique_id_win = root->ToBrowserAccessibilityWin()->unique_id_win();
713 ASSERT_EQ(root, manager->GetFromUniqueIdWin(unique_id_win));
716 TEST_F(BrowserAccessibilityTest, TestIA2Attributes) {
717 ui::AXNodeData checkbox;
718 checkbox.id = 2;
719 checkbox.SetName("Checkbox");
720 checkbox.role = ui::AX_ROLE_CHECK_BOX;
721 checkbox.state = 1 << ui::AX_STATE_CHECKED;
723 ui::AXNodeData root;
724 root.id = 1;
725 root.SetName("Document");
726 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
727 root.state = (1 << ui::AX_STATE_READ_ONLY) | (1 << ui::AX_STATE_FOCUSABLE);
728 root.child_ids.push_back(2);
730 CountedBrowserAccessibility::reset();
731 scoped_ptr<BrowserAccessibilityManager> manager(
732 BrowserAccessibilityManager::Create(
733 MakeAXTreeUpdate(root, checkbox),
734 nullptr, new CountedBrowserAccessibilityFactory()));
735 ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());
737 ASSERT_NE(nullptr, manager->GetRoot());
738 BrowserAccessibilityWin* root_accessible =
739 manager->GetRoot()->ToBrowserAccessibilityWin();
740 ASSERT_NE(nullptr, root_accessible);
741 ASSERT_EQ(1, root_accessible->PlatformChildCount());
742 BrowserAccessibilityWin* checkbox_accessible =
743 root_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
744 ASSERT_NE(nullptr, checkbox_accessible);
746 base::win::ScopedBstr attributes;
747 HRESULT hr = checkbox_accessible->get_attributes(attributes.Receive());
748 EXPECT_EQ(S_OK, hr);
749 EXPECT_NE(nullptr, static_cast<BSTR>(attributes));
750 std::wstring attributes_str(attributes, attributes.Length());
751 EXPECT_EQ(L"checkable:true;", attributes_str);
753 manager.reset();
754 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
758 * Ensures that ui::AX_ATTR_TEXT_SEL_START/END attributes are correctly used to
759 * determine caret position and text selection in various types of editable
760 * elements.
762 TEST_F(BrowserAccessibilityTest, TestCaretAndTextSelection) {
763 ui::AXNodeData root;
764 root.id = 1;
765 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
766 root.state = (1 << ui::AX_STATE_READ_ONLY) | (1 << ui::AX_STATE_FOCUSABLE);
768 ui::AXNodeData combo_box;
769 combo_box.id = 2;
770 combo_box.role = ui::AX_ROLE_COMBO_BOX;
771 combo_box.state = (1 << ui::AX_STATE_FOCUSABLE) | (1 << ui::AX_STATE_FOCUSED);
772 combo_box.SetValue("Test1");
773 // Place the caret between 't' and 'e'.
774 combo_box.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, 1);
775 combo_box.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, 1);
777 ui::AXNodeData text_field;
778 text_field.id = 3;
779 text_field.role = ui::AX_ROLE_TEXT_FIELD;
780 text_field.state = 1 << ui::AX_STATE_FOCUSABLE;
781 text_field.SetValue("Test2");
782 // Select the letter 'e'.
783 text_field.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, 1);
784 text_field.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, 2);
786 root.child_ids.push_back(2);
787 root.child_ids.push_back(3);
789 CountedBrowserAccessibility::reset();
790 scoped_ptr<BrowserAccessibilityManager> manager(
791 BrowserAccessibilityManager::Create(
792 MakeAXTreeUpdate(root, combo_box, text_field),
793 nullptr, new CountedBrowserAccessibilityFactory()));
794 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
796 ASSERT_NE(nullptr, manager->GetRoot());
797 BrowserAccessibilityWin* root_accessible =
798 manager->GetRoot()->ToBrowserAccessibilityWin();
799 ASSERT_NE(nullptr, root_accessible);
800 ASSERT_EQ(2, root_accessible->PlatformChildCount());
802 BrowserAccessibilityWin* combo_box_accessible =
803 root_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
804 ASSERT_NE(nullptr, combo_box_accessible);
805 manager->SetFocus(combo_box_accessible, false /* notify */);
806 ASSERT_EQ(combo_box_accessible,
807 manager->GetFocus(root_accessible)->ToBrowserAccessibilityWin());
808 BrowserAccessibilityWin* text_field_accessible =
809 root_accessible->PlatformGetChild(1)->ToBrowserAccessibilityWin();
810 ASSERT_NE(nullptr, text_field_accessible);
812 // -2 is never a valid offset.
813 LONG caret_offset = -2;
814 LONG n_selections = -2;
815 LONG selection_start = -2;
816 LONG selection_end = -2;
818 // Test get_caretOffset.
819 HRESULT hr = combo_box_accessible->get_caretOffset(&caret_offset);;
820 EXPECT_EQ(S_OK, hr);
821 EXPECT_EQ(1L, caret_offset);
822 // caret_offset should be -1 when the object is not focused.
823 hr = text_field_accessible->get_caretOffset(&caret_offset);;
824 EXPECT_EQ(S_FALSE, hr);
825 EXPECT_EQ(-1L, caret_offset);
827 // Move the focus to the text field.
828 combo_box.state &= ~(1 << ui::AX_STATE_FOCUSED);
829 text_field.state |= 1 << ui::AX_STATE_FOCUSED;
830 manager->SetFocus(text_field_accessible, false /* notify */);
831 ASSERT_EQ(text_field_accessible,
832 manager->GetFocus(root_accessible)->ToBrowserAccessibilityWin());
834 // The caret should be at the start of the selection.
835 hr = text_field_accessible->get_caretOffset(&caret_offset);;
836 EXPECT_EQ(S_OK, hr);
837 EXPECT_EQ(1L, caret_offset);
839 // Test get_nSelections.
840 hr = combo_box_accessible->get_nSelections(&n_selections);;
841 EXPECT_EQ(S_OK, hr);
842 EXPECT_EQ(0L, n_selections);
843 hr = text_field_accessible->get_nSelections(&n_selections);;
844 EXPECT_EQ(S_OK, hr);
845 EXPECT_EQ(1L, n_selections);
847 // Test get_selection.
848 hr = combo_box_accessible->get_selection(
849 0L /* selection_index */, &selection_start, &selection_end);;
850 EXPECT_EQ(E_INVALIDARG, hr); // No selections available.
851 // Invalid in_args should not modify out_args.
852 EXPECT_EQ(-2L, selection_start);
853 EXPECT_EQ(-2L, selection_end);
854 hr = text_field_accessible->get_selection(
855 0L /* selection_index */, &selection_start, &selection_end);;
856 EXPECT_EQ(S_OK, hr);
857 EXPECT_EQ(1L, selection_start);
858 EXPECT_EQ(2L, selection_end);
860 manager.reset();
861 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
864 } // namespace content