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"
23 // CountedBrowserAccessibility ------------------------------------------------
25 // Subclass of BrowserAccessibilityWin that counts the number of instances.
26 class CountedBrowserAccessibility
: public BrowserAccessibilityWin
{
28 CountedBrowserAccessibility();
29 virtual ~CountedBrowserAccessibility();
31 static void reset() { num_instances_
= 0; }
32 static int num_instances() { return num_instances_
; }
35 static int num_instances_
;
37 DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibility
);
41 int CountedBrowserAccessibility::num_instances_
= 0;
43 CountedBrowserAccessibility::CountedBrowserAccessibility() {
47 CountedBrowserAccessibility::~CountedBrowserAccessibility() {
52 // CountedBrowserAccessibilityFactory -----------------------------------------
54 // Factory that creates a CountedBrowserAccessibility.
55 class CountedBrowserAccessibilityFactory
: public BrowserAccessibilityFactory
{
57 CountedBrowserAccessibilityFactory();
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(
77 DCHECK(SUCCEEDED(hr
));
85 // BrowserAccessibilityTest ---------------------------------------------------
87 class BrowserAccessibilityTest
: public testing::Test
{
89 BrowserAccessibilityTest();
90 virtual ~BrowserAccessibilityTest();
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
;
119 button
.SetName("Button");
120 button
.role
= ui::AX_ROLE_BUTTON
;
123 ui::AXNodeData checkbox
;
125 checkbox
.SetName("Checkbox");
126 checkbox
.role
= ui::AX_ROLE_CHECK_BOX
;
131 root
.SetName("Document");
132 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
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.
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
);
165 base::win::ScopedVariant
one(1);
166 hr
= root_accessible
->get_accChild(one
, &child1_iaccessible
);
169 // Now delete the manager, and only one of the three nodes in the tree
170 // should be released.
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.
188 text
.role
= ui::AX_ROLE_STATIC_TEXT
;
189 text
.SetName("old text");
194 root
.SetName("Document");
195 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
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
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());
216 base::win::ScopedComPtr
<IAccessible
> text_accessible
;
217 hr
= text_dispatch
.QueryInterface(text_accessible
.Receive());
220 base::win::ScopedVariant
childid_self(CHILDID_SELF
);
221 base::win::ScopedBstr name
;
222 hr
= text_accessible
->get_accName(childid_self
, name
.Receive());
224 EXPECT_EQ(L
"old text", base::string16(name
));
227 text_dispatch
.Release();
228 text_accessible
.Release();
230 // Notify the BrowserAccessibilityManager that the text child has changed.
231 ui::AXNodeData text2
;
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
);
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"
246 hr
= manager
->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
247 one
, text_dispatch
.Receive());
250 hr
= text_dispatch
.QueryInterface(text_accessible
.Receive());
253 hr
= text_accessible
->get_accName(childid_self
, name
.Receive());
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
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.
272 div
.role
= ui::AX_ROLE_GROUP
;
275 ui::AXNodeData text3
;
277 text3
.role
= ui::AX_ROLE_STATIC_TEXT
;
280 ui::AXNodeData text4
;
282 text4
.role
= ui::AX_ROLE_STATIC_TEXT
;
285 div
.child_ids
.push_back(3);
286 div
.child_ids
.push_back(4);
290 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
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
);
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
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
;
328 text1
.role
= ui::AX_ROLE_TEXT_FIELD
;
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
);
338 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
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();
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
)));
362 ASSERT_EQ(S_OK
, text1_obj
->get_text(0, 4, text
.Receive()));
363 ASSERT_STREQ(L
"One ", text
);
368 ASSERT_EQ(S_OK
, text1_obj
->get_textAtOffset(
369 1, IA2_TEXT_BOUNDARY_CHAR
, &start
, &end
, text
.Receive()));
372 ASSERT_STREQ(L
"n", text
);
375 ASSERT_EQ(S_FALSE
, text1_obj
->get_textAtOffset(
376 text1_len
, IA2_TEXT_BOUNDARY_CHAR
, &start
, &end
, text
.Receive()));
381 ASSERT_EQ(S_OK
, text1_obj
->get_textAtOffset(
382 1, IA2_TEXT_BOUNDARY_WORD
, &start
, &end
, text
.Receive()));
385 ASSERT_STREQ(L
"One ", text
);
388 ASSERT_EQ(S_OK
, text1_obj
->get_textAtOffset(
389 6, IA2_TEXT_BOUNDARY_WORD
, &start
, &end
, text
.Receive()));
392 ASSERT_STREQ(L
"two\n", text
);
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
);
399 ASSERT_STREQ(L
"six.", text
);
402 ASSERT_EQ(S_OK
, text1_obj
->get_textAtOffset(
403 1, IA2_TEXT_BOUNDARY_LINE
, &start
, &end
, text
.Receive()));
406 ASSERT_STREQ(L
"One two three.\n", text
);
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
);
417 text1_obj
->get_text(0, IA2_TEXT_OFFSET_LENGTH
, text
.Receive()));
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
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
;
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
;
440 text2
.role
= ui::AX_ROLE_STATIC_TEXT
;
441 text2
.state
= 1 << ui::AX_STATE_READ_ONLY
;
442 text2
.SetName(text2_name
);
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();
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
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
;
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
;
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
;
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
;
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);
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();
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
)));
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()));
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());
584 EXPECT_EQ(S_OK
, root_obj
->get_hyperlink(1, hyperlink
.Receive()));
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());
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
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(),
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
;
632 tree1_1
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
633 tree1_1
.child_ids
.push_back(2);
635 ui::AXNodeData tree1_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
= ¶ms
[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
;
662 tree2_1
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
663 tree2_1
.child_ids
.push_back(3);
665 ui::AXNodeData tree2_2
;
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.
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
696 TEST_F(BrowserAccessibilityTest
, EmptyDocHasUniqueIdWin
) {
697 scoped_ptr
<BrowserAccessibilityManagerWin
> manager(
698 new BrowserAccessibilityManagerWin(
699 BrowserAccessibilityManagerWin::GetEmptyDocument(),
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
,
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
;
719 checkbox
.SetName("Checkbox");
720 checkbox
.role
= ui::AX_ROLE_CHECK_BOX
;
721 checkbox
.state
= 1 << ui::AX_STATE_CHECKED
;
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());
749 EXPECT_NE(nullptr, static_cast<BSTR
>(attributes
));
750 std::wstring
attributes_str(attributes
, attributes
.Length());
751 EXPECT_EQ(L
"checkable:true;", attributes_str
);
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
762 TEST_F(BrowserAccessibilityTest
, TestCaretAndTextSelection
) {
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
;
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
;
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
);;
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
);;
837 EXPECT_EQ(1L, caret_offset
);
839 // Test get_nSelections.
840 hr
= combo_box_accessible
->get_nSelections(&n_selections
);;
842 EXPECT_EQ(0L, n_selections
);
843 hr
= text_field_accessible
->get_nSelections(&n_selections
);;
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
);;
857 EXPECT_EQ(1L, selection_start
);
858 EXPECT_EQ(2L, selection_end
);
861 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
864 } // namespace content