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 "content/public/test/test_browser_thread_bundle.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "ui/base/win/atl_module.h"
24 // CountedBrowserAccessibility ------------------------------------------------
26 // Subclass of BrowserAccessibilityWin that counts the number of instances.
27 class CountedBrowserAccessibility
: public BrowserAccessibilityWin
{
29 CountedBrowserAccessibility();
30 ~CountedBrowserAccessibility() override
;
32 static void reset() { num_instances_
= 0; }
33 static int num_instances() { return num_instances_
; }
36 static int num_instances_
;
38 DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibility
);
42 int CountedBrowserAccessibility::num_instances_
= 0;
44 CountedBrowserAccessibility::CountedBrowserAccessibility() {
48 CountedBrowserAccessibility::~CountedBrowserAccessibility() {
53 // CountedBrowserAccessibilityFactory -----------------------------------------
55 // Factory that creates a CountedBrowserAccessibility.
56 class CountedBrowserAccessibilityFactory
: public BrowserAccessibilityFactory
{
58 CountedBrowserAccessibilityFactory();
61 ~CountedBrowserAccessibilityFactory() override
;
63 BrowserAccessibility
* Create() override
;
65 DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibilityFactory
);
68 CountedBrowserAccessibilityFactory::CountedBrowserAccessibilityFactory() {
71 CountedBrowserAccessibilityFactory::~CountedBrowserAccessibilityFactory() {
74 BrowserAccessibility
* CountedBrowserAccessibilityFactory::Create() {
75 CComObject
<CountedBrowserAccessibility
>* instance
;
76 HRESULT hr
= CComObject
<CountedBrowserAccessibility
>::CreateInstance(
78 DCHECK(SUCCEEDED(hr
));
86 // BrowserAccessibilityTest ---------------------------------------------------
88 class BrowserAccessibilityTest
: public testing::Test
{
90 BrowserAccessibilityTest();
91 ~BrowserAccessibilityTest() override
;
94 void SetUp() override
;
96 content::TestBrowserThreadBundle thread_bundle_
;
98 DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityTest
);
101 BrowserAccessibilityTest::BrowserAccessibilityTest() {
104 BrowserAccessibilityTest::~BrowserAccessibilityTest() {
107 void BrowserAccessibilityTest::SetUp() {
108 ui::win::CreateATLModuleIfNeeded();
112 // Actual tests ---------------------------------------------------------------
114 // Test that BrowserAccessibilityManager correctly releases the tree of
115 // BrowserAccessibility instances upon delete.
116 TEST_F(BrowserAccessibilityTest
, TestNoLeaks
) {
117 // Create ui::AXNodeData objects for a simple document tree,
118 // representing the accessibility information used to initialize
119 // BrowserAccessibilityManager.
120 ui::AXNodeData button
;
122 button
.SetName("Button");
123 button
.role
= ui::AX_ROLE_BUTTON
;
126 ui::AXNodeData checkbox
;
128 checkbox
.SetName("Checkbox");
129 checkbox
.role
= ui::AX_ROLE_CHECK_BOX
;
134 root
.SetName("Document");
135 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
137 root
.child_ids
.push_back(2);
138 root
.child_ids
.push_back(3);
140 // Construct a BrowserAccessibilityManager with this
141 // ui::AXNodeData tree and a factory for an instance-counting
142 // BrowserAccessibility, and ensure that exactly 3 instances were
143 // created. Note that the manager takes ownership of the factory.
144 CountedBrowserAccessibility::reset();
145 scoped_ptr
<BrowserAccessibilityManager
> manager(
146 BrowserAccessibilityManager::Create(
147 MakeAXTreeUpdate(root
, button
, checkbox
),
148 NULL
, new CountedBrowserAccessibilityFactory()));
149 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
151 // Delete the manager and test that all 3 instances are deleted.
153 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
155 // Construct a manager again, and this time use the IAccessible interface
156 // to get new references to two of the three nodes in the tree.
157 manager
.reset(BrowserAccessibilityManager::Create(
158 MakeAXTreeUpdate(root
, button
, checkbox
),
159 NULL
, new CountedBrowserAccessibilityFactory()));
160 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
161 IAccessible
* root_accessible
=
162 manager
->GetRoot()->ToBrowserAccessibilityWin();
163 IDispatch
* root_iaccessible
= NULL
;
164 IDispatch
* child1_iaccessible
= NULL
;
165 base::win::ScopedVariant
childid_self(CHILDID_SELF
);
166 HRESULT hr
= root_accessible
->get_accChild(childid_self
, &root_iaccessible
);
168 base::win::ScopedVariant
one(1);
169 hr
= root_accessible
->get_accChild(one
, &child1_iaccessible
);
172 // Now delete the manager, and only one of the three nodes in the tree
173 // should be released.
175 ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());
177 // Release each of our references and make sure that each one results in
178 // the instance being deleted as its reference count hits zero.
179 root_iaccessible
->Release();
180 ASSERT_EQ(1, CountedBrowserAccessibility::num_instances());
181 child1_iaccessible
->Release();
182 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
185 TEST_F(BrowserAccessibilityTest
, TestChildrenChange
) {
186 // Create ui::AXNodeData objects for a simple document tree,
187 // representing the accessibility information used to initialize
188 // BrowserAccessibilityManager.
191 text
.role
= ui::AX_ROLE_STATIC_TEXT
;
192 text
.SetName("old text");
197 root
.SetName("Document");
198 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
200 root
.child_ids
.push_back(2);
202 // Construct a BrowserAccessibilityManager with this
203 // ui::AXNodeData tree and a factory for an instance-counting
204 // BrowserAccessibility.
205 CountedBrowserAccessibility::reset();
206 scoped_ptr
<BrowserAccessibilityManager
> manager(
207 BrowserAccessibilityManager::Create(
208 MakeAXTreeUpdate(root
, text
),
209 NULL
, new CountedBrowserAccessibilityFactory()));
211 // Query for the text IAccessible and verify that it returns "old text" as its
213 base::win::ScopedVariant
one(1);
214 base::win::ScopedComPtr
<IDispatch
> text_dispatch
;
215 HRESULT hr
= manager
->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
216 one
, text_dispatch
.Receive());
219 base::win::ScopedComPtr
<IAccessible
> text_accessible
;
220 hr
= text_dispatch
.QueryInterface(text_accessible
.Receive());
223 base::win::ScopedVariant
childid_self(CHILDID_SELF
);
224 base::win::ScopedBstr name
;
225 hr
= text_accessible
->get_accName(childid_self
, name
.Receive());
227 EXPECT_EQ(L
"old text", base::string16(name
));
230 text_dispatch
.Release();
231 text_accessible
.Release();
233 // Notify the BrowserAccessibilityManager that the text child has changed.
234 ui::AXNodeData text2
;
236 text2
.role
= ui::AX_ROLE_STATIC_TEXT
;
237 text2
.SetName("new text");
238 text2
.SetName("old text");
239 AccessibilityHostMsg_EventParams param
;
240 param
.event_type
= ui::AX_EVENT_CHILDREN_CHANGED
;
241 param
.update
.nodes
.push_back(text2
);
243 std::vector
<AccessibilityHostMsg_EventParams
> events
;
244 events
.push_back(param
);
245 manager
->OnAccessibilityEvents(events
);
247 // Query for the text IAccessible and verify that it now returns "new text"
249 hr
= manager
->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
250 one
, text_dispatch
.Receive());
253 hr
= text_dispatch
.QueryInterface(text_accessible
.Receive());
256 hr
= text_accessible
->get_accName(childid_self
, name
.Receive());
258 EXPECT_EQ(L
"new text", base::string16(name
));
260 text_dispatch
.Release();
261 text_accessible
.Release();
263 // Delete the manager and test that all BrowserAccessibility instances are
266 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
269 TEST_F(BrowserAccessibilityTest
, TestChildrenChangeNoLeaks
) {
270 // Create ui::AXNodeData objects for a simple document tree,
271 // representing the accessibility information used to initialize
272 // BrowserAccessibilityManager.
275 div
.role
= ui::AX_ROLE_GROUP
;
278 ui::AXNodeData text3
;
280 text3
.role
= ui::AX_ROLE_STATIC_TEXT
;
283 ui::AXNodeData text4
;
285 text4
.role
= ui::AX_ROLE_STATIC_TEXT
;
288 div
.child_ids
.push_back(3);
289 div
.child_ids
.push_back(4);
293 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
295 root
.child_ids
.push_back(2);
297 // Construct a BrowserAccessibilityManager with this
298 // ui::AXNodeData tree and a factory for an instance-counting
299 // BrowserAccessibility and ensure that exactly 4 instances were
300 // created. Note that the manager takes ownership of the factory.
301 CountedBrowserAccessibility::reset();
302 scoped_ptr
<BrowserAccessibilityManager
> manager(
303 BrowserAccessibilityManager::Create(
304 MakeAXTreeUpdate(root
, div
, text3
, text4
),
305 NULL
, new CountedBrowserAccessibilityFactory()));
306 ASSERT_EQ(4, CountedBrowserAccessibility::num_instances());
308 // Notify the BrowserAccessibilityManager that the div node and its children
309 // were removed and ensure that only one BrowserAccessibility instance exists.
310 root
.child_ids
.clear();
311 AccessibilityHostMsg_EventParams param
;
312 param
.event_type
= ui::AX_EVENT_CHILDREN_CHANGED
;
313 param
.update
.nodes
.push_back(root
);
315 std::vector
<AccessibilityHostMsg_EventParams
> events
;
316 events
.push_back(param
);
317 manager
->OnAccessibilityEvents(events
);
318 ASSERT_EQ(1, CountedBrowserAccessibility::num_instances());
320 // Delete the manager and test that all BrowserAccessibility instances are
323 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
326 TEST_F(BrowserAccessibilityTest
, TestTextBoundaries
) {
327 std::string line1
= "One two three.";
328 std::string line2
= "Four five six.";
329 std::string text_value
= line1
+ '\n' + line2
;
333 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
334 root
.child_ids
.push_back(2);
336 ui::AXNodeData text_field
;
338 text_field
.role
= ui::AX_ROLE_TEXT_FIELD
;
339 text_field
.AddStringAttribute(ui::AX_ATTR_VALUE
, text_value
);
340 std::vector
<int32
> line_start_offsets
;
341 line_start_offsets
.push_back(15);
342 text_field
.AddIntListAttribute(
343 ui::AX_ATTR_LINE_BREAKS
, line_start_offsets
);
344 text_field
.child_ids
.push_back(3);
345 text_field
.child_ids
.push_back(5);
346 text_field
.child_ids
.push_back(6);
348 ui::AXNodeData static_text1
;
350 static_text1
.role
= ui::AX_ROLE_STATIC_TEXT
;
351 static_text1
.AddStringAttribute(ui::AX_ATTR_VALUE
, line1
);
352 static_text1
.child_ids
.push_back(4);
354 ui::AXNodeData inline_box1
;
356 inline_box1
.role
= ui::AX_ROLE_INLINE_TEXT_BOX
;
357 inline_box1
.AddStringAttribute(ui::AX_ATTR_VALUE
, line1
);
358 std::vector
<int32
> word_start_offsets1
;
359 word_start_offsets1
.push_back(0);
360 word_start_offsets1
.push_back(4);
361 word_start_offsets1
.push_back(8);
362 inline_box1
.AddIntListAttribute(
363 ui::AX_ATTR_WORD_STARTS
, word_start_offsets1
);
365 ui::AXNodeData line_break
;
367 line_break
.role
= ui::AX_ROLE_LINE_BREAK
;
368 line_break
.AddStringAttribute(ui::AX_ATTR_VALUE
, "\n");
370 ui::AXNodeData static_text2
;
372 static_text2
.role
= ui::AX_ROLE_STATIC_TEXT
;
373 static_text2
.AddStringAttribute(ui::AX_ATTR_VALUE
, line2
);
374 static_text2
.child_ids
.push_back(7);
376 ui::AXNodeData inline_box2
;
378 inline_box2
.role
= ui::AX_ROLE_INLINE_TEXT_BOX
;
379 inline_box2
.AddStringAttribute(ui::AX_ATTR_VALUE
, line2
);
380 std::vector
<int32
> word_start_offsets2
;
381 word_start_offsets2
.push_back(0);
382 word_start_offsets2
.push_back(5);
383 word_start_offsets2
.push_back(10);
384 inline_box2
.AddIntListAttribute(
385 ui::AX_ATTR_WORD_STARTS
, word_start_offsets2
);
387 CountedBrowserAccessibility::reset();
388 scoped_ptr
<BrowserAccessibilityManager
> manager(
389 BrowserAccessibilityManager::Create(
390 MakeAXTreeUpdate(root
, text_field
, static_text1
, inline_box1
,
391 line_break
, static_text2
, inline_box2
),
392 nullptr, new CountedBrowserAccessibilityFactory()));
393 ASSERT_EQ(7, CountedBrowserAccessibility::num_instances());
395 BrowserAccessibilityWin
* root_obj
=
396 manager
->GetRoot()->ToBrowserAccessibilityWin();
397 ASSERT_NE(nullptr, root_obj
);
398 ASSERT_EQ(1, root_obj
->PlatformChildCount());
400 BrowserAccessibilityWin
* text_field_obj
=
401 root_obj
->PlatformGetChild(0)->ToBrowserAccessibilityWin();
402 ASSERT_NE(nullptr, text_field_obj
);
405 EXPECT_EQ(S_OK
, text_field_obj
->get_nCharacters(&text_len
));
407 base::win::ScopedBstr text
;
408 EXPECT_EQ(S_OK
, text_field_obj
->get_text(0, text_len
, text
.Receive()));
409 EXPECT_EQ(text_value
, base::UTF16ToUTF8(base::string16(text
)));
412 EXPECT_EQ(S_OK
, text_field_obj
->get_text(0, 4, text
.Receive()));
413 EXPECT_STREQ(L
"One ", text
);
418 EXPECT_EQ(S_OK
, text_field_obj
->get_textAtOffset(
419 1, IA2_TEXT_BOUNDARY_CHAR
, &start
, &end
, text
.Receive()));
422 EXPECT_STREQ(L
"n", text
);
425 EXPECT_EQ(S_FALSE
, text_field_obj
->get_textAtOffset(
426 text_len
, IA2_TEXT_BOUNDARY_CHAR
, &start
, &end
, text
.Receive()));
429 EXPECT_EQ(nullptr, text
);
432 EXPECT_EQ(S_FALSE
, text_field_obj
->get_textAtOffset(
433 text_len
, IA2_TEXT_BOUNDARY_WORD
, &start
, &end
, text
.Receive()));
436 EXPECT_EQ(nullptr, text
);
439 EXPECT_EQ(S_OK
, text_field_obj
->get_textAtOffset(
440 1, IA2_TEXT_BOUNDARY_WORD
, &start
, &end
, text
.Receive()));
443 EXPECT_STREQ(L
"One ", text
);
446 EXPECT_EQ(S_OK
, text_field_obj
->get_textAtOffset(
447 6, IA2_TEXT_BOUNDARY_WORD
, &start
, &end
, text
.Receive()));
450 EXPECT_STREQ(L
"two ", text
);
453 EXPECT_EQ(S_OK
, text_field_obj
->get_textAtOffset(
454 text_len
- 1, IA2_TEXT_BOUNDARY_WORD
, &start
, &end
, text
.Receive()));
455 EXPECT_EQ(25, start
);
457 EXPECT_STREQ(L
"six.", text
);
460 EXPECT_EQ(S_OK
, text_field_obj
->get_textAtOffset(
461 1, IA2_TEXT_BOUNDARY_LINE
, &start
, &end
, text
.Receive()));
464 EXPECT_STREQ(L
"One two three.\n", text
);
467 EXPECT_EQ(S_OK
, text_field_obj
->get_textAtOffset(
468 text_len
, IA2_TEXT_BOUNDARY_LINE
, &start
, &end
, text
.Receive()));
469 EXPECT_EQ(15, start
);
470 EXPECT_EQ(text_len
, end
);
471 EXPECT_STREQ(L
"Four five six.", text
);
474 EXPECT_EQ(S_OK
, text_field_obj
->get_text(
475 0, IA2_TEXT_OFFSET_LENGTH
, text
.Receive()));
476 EXPECT_EQ(text_value
, base::UTF16ToUTF8(base::string16(text
)));
478 // Delete the manager and test that all BrowserAccessibility instances are
481 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
484 TEST_F(BrowserAccessibilityTest
, TestSimpleHypertext
) {
485 const std::string text1_name
= "One two three.";
486 const std::string text2_name
= " Four five six.";
488 ui::AXNodeData text1
;
490 text1
.role
= ui::AX_ROLE_STATIC_TEXT
;
491 text1
.state
= 1 << ui::AX_STATE_READ_ONLY
;
492 text1
.SetName(text1_name
);
494 ui::AXNodeData text2
;
496 text2
.role
= ui::AX_ROLE_STATIC_TEXT
;
497 text2
.state
= 1 << ui::AX_STATE_READ_ONLY
;
498 text2
.SetName(text2_name
);
502 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
503 root
.state
= 1 << ui::AX_STATE_READ_ONLY
;
504 root
.child_ids
.push_back(11);
505 root
.child_ids
.push_back(12);
507 CountedBrowserAccessibility::reset();
508 scoped_ptr
<BrowserAccessibilityManager
> manager(
509 BrowserAccessibilityManager::Create(
510 MakeAXTreeUpdate(root
, text1
, text2
),
511 NULL
, new CountedBrowserAccessibilityFactory()));
512 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
514 BrowserAccessibilityWin
* root_obj
=
515 manager
->GetRoot()->ToBrowserAccessibilityWin();
518 ASSERT_EQ(S_OK
, root_obj
->get_nCharacters(&text_len
));
520 base::win::ScopedBstr text
;
521 ASSERT_EQ(S_OK
, root_obj
->get_text(0, text_len
, text
.Receive()));
522 EXPECT_EQ(text1_name
+ text2_name
, base::UTF16ToUTF8(base::string16(text
)));
524 long hyperlink_count
;
525 ASSERT_EQ(S_OK
, root_obj
->get_nHyperlinks(&hyperlink_count
));
526 EXPECT_EQ(0, hyperlink_count
);
528 base::win::ScopedComPtr
<IAccessibleHyperlink
> hyperlink
;
529 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlink(-1, hyperlink
.Receive()));
530 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlink(0, hyperlink
.Receive()));
531 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlink(28, hyperlink
.Receive()));
532 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlink(29, hyperlink
.Receive()));
534 long hyperlink_index
;
535 EXPECT_EQ(E_FAIL
, root_obj
->get_hyperlinkIndex(0, &hyperlink_index
));
536 EXPECT_EQ(-1, hyperlink_index
);
537 EXPECT_EQ(E_FAIL
, root_obj
->get_hyperlinkIndex(28, &hyperlink_index
));
538 EXPECT_EQ(-1, hyperlink_index
);
539 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlinkIndex(-1, &hyperlink_index
));
540 EXPECT_EQ(-1, hyperlink_index
);
541 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlinkIndex(29, &hyperlink_index
));
542 EXPECT_EQ(-1, hyperlink_index
);
544 // Delete the manager and test that all BrowserAccessibility instances are
547 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
550 TEST_F(BrowserAccessibilityTest
, TestComplexHypertext
) {
551 const std::string text1_name
= "One two three.";
552 const std::string text2_name
= " Four five six.";
553 const std::string button1_text_name
= "red";
554 const std::string link1_text_name
= "blue";
556 ui::AXNodeData text1
;
558 text1
.role
= ui::AX_ROLE_STATIC_TEXT
;
559 text1
.state
= 1 << ui::AX_STATE_READ_ONLY
;
560 text1
.SetName(text1_name
);
562 ui::AXNodeData text2
;
564 text2
.role
= ui::AX_ROLE_STATIC_TEXT
;
565 text2
.state
= 1 << ui::AX_STATE_READ_ONLY
;
566 text2
.SetName(text2_name
);
568 ui::AXNodeData button1
, button1_text
;
570 button1_text
.id
= 15;
571 button1_text
.SetName(button1_text_name
);
572 button1
.role
= ui::AX_ROLE_BUTTON
;
573 button1_text
.role
= ui::AX_ROLE_STATIC_TEXT
;
574 button1
.state
= 1 << ui::AX_STATE_READ_ONLY
;
575 button1_text
.state
= 1 << ui::AX_STATE_READ_ONLY
;
576 button1
.child_ids
.push_back(15);
578 ui::AXNodeData link1
, link1_text
;
581 link1_text
.SetName(link1_text_name
);
582 link1
.role
= ui::AX_ROLE_LINK
;
583 link1_text
.role
= ui::AX_ROLE_STATIC_TEXT
;
584 link1
.state
= 1 << ui::AX_STATE_READ_ONLY
;
585 link1_text
.state
= 1 << ui::AX_STATE_READ_ONLY
;
586 link1
.child_ids
.push_back(16);
590 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
591 root
.state
= 1 << ui::AX_STATE_READ_ONLY
;
592 root
.child_ids
.push_back(11);
593 root
.child_ids
.push_back(13);
594 root
.child_ids
.push_back(12);
595 root
.child_ids
.push_back(14);
597 CountedBrowserAccessibility::reset();
598 scoped_ptr
<BrowserAccessibilityManager
> manager(
599 BrowserAccessibilityManager::Create(
600 MakeAXTreeUpdate(root
,
601 text1
, button1
, button1_text
,
602 text2
, link1
, link1_text
),
603 NULL
, new CountedBrowserAccessibilityFactory()));
604 ASSERT_EQ(7, CountedBrowserAccessibility::num_instances());
606 BrowserAccessibilityWin
* root_obj
=
607 manager
->GetRoot()->ToBrowserAccessibilityWin();
610 ASSERT_EQ(S_OK
, root_obj
->get_nCharacters(&text_len
));
612 base::win::ScopedBstr text
;
613 ASSERT_EQ(S_OK
, root_obj
->get_text(0, text_len
, text
.Receive()));
614 const std::string embed
= base::UTF16ToUTF8(
615 base::string16(1, BrowserAccessibilityWin::kEmbeddedCharacter
));
616 EXPECT_EQ(text1_name
+ embed
+ text2_name
+ embed
,
617 base::UTF16ToUTF8(base::string16(text
)));
620 long hyperlink_count
;
621 ASSERT_EQ(S_OK
, root_obj
->get_nHyperlinks(&hyperlink_count
));
622 EXPECT_EQ(2, hyperlink_count
);
624 base::win::ScopedComPtr
<IAccessibleHyperlink
> hyperlink
;
625 base::win::ScopedComPtr
<IAccessibleText
> hypertext
;
626 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlink(-1, hyperlink
.Receive()));
627 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlink(2, hyperlink
.Receive()));
628 EXPECT_EQ(E_INVALIDARG
, root_obj
->get_hyperlink(28, hyperlink
.Receive()));
630 EXPECT_EQ(S_OK
, root_obj
->get_hyperlink(0, hyperlink
.Receive()));
632 hyperlink
.QueryInterface
<IAccessibleText
>(hypertext
.Receive()));
633 EXPECT_EQ(S_OK
, hypertext
->get_text(0, 3, text
.Receive()));
634 EXPECT_STREQ(button1_text_name
.c_str(),
635 base::UTF16ToUTF8(base::string16(text
)).c_str());
640 EXPECT_EQ(S_OK
, root_obj
->get_hyperlink(1, hyperlink
.Receive()));
642 hyperlink
.QueryInterface
<IAccessibleText
>(hypertext
.Receive()));
643 EXPECT_EQ(S_OK
, hypertext
->get_text(0, 4, text
.Receive()));
644 EXPECT_STREQ(link1_text_name
.c_str(),
645 base::UTF16ToUTF8(base::string16(text
)).c_str());
650 long hyperlink_index
;
651 EXPECT_EQ(E_FAIL
, root_obj
->get_hyperlinkIndex(0, &hyperlink_index
));
652 EXPECT_EQ(-1, hyperlink_index
);
653 EXPECT_EQ(E_FAIL
, root_obj
->get_hyperlinkIndex(28, &hyperlink_index
));
654 EXPECT_EQ(-1, hyperlink_index
);
655 EXPECT_EQ(S_OK
, root_obj
->get_hyperlinkIndex(14, &hyperlink_index
));
656 EXPECT_EQ(0, hyperlink_index
);
657 EXPECT_EQ(S_OK
, root_obj
->get_hyperlinkIndex(30, &hyperlink_index
));
658 EXPECT_EQ(1, hyperlink_index
);
660 // Delete the manager and test that all BrowserAccessibility instances are
663 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
666 TEST_F(BrowserAccessibilityTest
, TestCreateEmptyDocument
) {
667 // Try creating an empty document with busy state. Readonly is
668 // set automatically.
669 CountedBrowserAccessibility::reset();
670 const int32 busy_state
= 1 << ui::AX_STATE_BUSY
;
671 const int32 readonly_state
= 1 << ui::AX_STATE_READ_ONLY
;
672 const int32 enabled_state
= 1 << ui::AX_STATE_ENABLED
;
673 scoped_ptr
<BrowserAccessibilityManager
> manager(
674 new BrowserAccessibilityManagerWin(
675 BrowserAccessibilityManagerWin::GetEmptyDocument(),
677 new CountedBrowserAccessibilityFactory()));
679 // Verify the root is as we expect by default.
680 BrowserAccessibility
* root
= manager
->GetRoot();
681 EXPECT_EQ(0, root
->GetId());
682 EXPECT_EQ(ui::AX_ROLE_ROOT_WEB_AREA
, root
->GetRole());
683 EXPECT_EQ(busy_state
| readonly_state
| enabled_state
, root
->GetState());
685 // Tree with a child textfield.
686 ui::AXNodeData tree1_1
;
688 tree1_1
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
689 tree1_1
.child_ids
.push_back(2);
691 ui::AXNodeData tree1_2
;
693 tree1_2
.role
= ui::AX_ROLE_TEXT_FIELD
;
695 // Process a load complete.
696 std::vector
<AccessibilityHostMsg_EventParams
> params
;
697 params
.push_back(AccessibilityHostMsg_EventParams());
698 AccessibilityHostMsg_EventParams
* msg
= ¶ms
[0];
699 msg
->event_type
= ui::AX_EVENT_LOAD_COMPLETE
;
700 msg
->update
.nodes
.push_back(tree1_1
);
701 msg
->update
.nodes
.push_back(tree1_2
);
702 msg
->id
= tree1_1
.id
;
703 manager
->OnAccessibilityEvents(params
);
705 // Save for later comparison.
706 BrowserAccessibility
* acc1_2
= manager
->GetFromID(2);
708 // Verify the root has changed.
709 EXPECT_NE(root
, manager
->GetRoot());
711 // And the proper child remains.
712 EXPECT_EQ(ui::AX_ROLE_TEXT_FIELD
, acc1_2
->GetRole());
713 EXPECT_EQ(2, acc1_2
->GetId());
715 // Tree with a child button.
716 ui::AXNodeData tree2_1
;
718 tree2_1
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
719 tree2_1
.child_ids
.push_back(3);
721 ui::AXNodeData tree2_2
;
723 tree2_2
.role
= ui::AX_ROLE_BUTTON
;
725 msg
->update
.nodes
.clear();
726 msg
->update
.nodes
.push_back(tree2_1
);
727 msg
->update
.nodes
.push_back(tree2_2
);
728 msg
->id
= tree2_1
.id
;
730 // Fire another load complete.
731 manager
->OnAccessibilityEvents(params
);
733 BrowserAccessibility
* acc2_2
= manager
->GetFromID(3);
735 // Verify the root has changed.
736 EXPECT_NE(root
, manager
->GetRoot());
738 // And the new child exists.
739 EXPECT_EQ(ui::AX_ROLE_BUTTON
, acc2_2
->GetRole());
740 EXPECT_EQ(3, acc2_2
->GetId());
742 // Ensure we properly cleaned up.
744 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
747 // This is a regression test for a bug where the initial empty document
748 // loaded by a BrowserAccessibilityManagerWin couldn't be looked up by
749 // its UniqueIDWin, because the AX Tree was loaded in
750 // BrowserAccessibilityManager code before BrowserAccessibilityManagerWin
752 TEST_F(BrowserAccessibilityTest
, EmptyDocHasUniqueIdWin
) {
753 scoped_ptr
<BrowserAccessibilityManagerWin
> manager(
754 new BrowserAccessibilityManagerWin(
755 BrowserAccessibilityManagerWin::GetEmptyDocument(),
757 new CountedBrowserAccessibilityFactory()));
759 // Verify the root is as we expect by default.
760 BrowserAccessibility
* root
= manager
->GetRoot();
761 EXPECT_EQ(0, root
->GetId());
762 EXPECT_EQ(ui::AX_ROLE_ROOT_WEB_AREA
, root
->GetRole());
763 EXPECT_EQ(1 << ui::AX_STATE_BUSY
|
764 1 << ui::AX_STATE_READ_ONLY
|
765 1 << ui::AX_STATE_ENABLED
,
768 LONG unique_id_win
= root
->ToBrowserAccessibilityWin()->unique_id_win();
769 ASSERT_EQ(root
, manager
->GetFromUniqueIdWin(unique_id_win
));
772 TEST_F(BrowserAccessibilityTest
, TestIA2Attributes
) {
773 ui::AXNodeData checkbox
;
775 checkbox
.SetName("Checkbox");
776 checkbox
.role
= ui::AX_ROLE_CHECK_BOX
;
777 checkbox
.state
= 1 << ui::AX_STATE_CHECKED
;
781 root
.SetName("Document");
782 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
783 root
.state
= (1 << ui::AX_STATE_READ_ONLY
) | (1 << ui::AX_STATE_FOCUSABLE
);
784 root
.child_ids
.push_back(2);
786 CountedBrowserAccessibility::reset();
787 scoped_ptr
<BrowserAccessibilityManager
> manager(
788 BrowserAccessibilityManager::Create(
789 MakeAXTreeUpdate(root
, checkbox
),
790 nullptr, new CountedBrowserAccessibilityFactory()));
791 ASSERT_EQ(2, CountedBrowserAccessibility::num_instances());
793 ASSERT_NE(nullptr, manager
->GetRoot());
794 BrowserAccessibilityWin
* root_accessible
=
795 manager
->GetRoot()->ToBrowserAccessibilityWin();
796 ASSERT_NE(nullptr, root_accessible
);
797 ASSERT_EQ(1, root_accessible
->PlatformChildCount());
798 BrowserAccessibilityWin
* checkbox_accessible
=
799 root_accessible
->PlatformGetChild(0)->ToBrowserAccessibilityWin();
800 ASSERT_NE(nullptr, checkbox_accessible
);
802 base::win::ScopedBstr attributes
;
803 HRESULT hr
= checkbox_accessible
->get_attributes(attributes
.Receive());
805 EXPECT_NE(nullptr, static_cast<BSTR
>(attributes
));
806 std::wstring
attributes_str(attributes
, attributes
.Length());
807 EXPECT_EQ(L
"checkable:true;", attributes_str
);
810 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
814 * Ensures that ui::AX_ATTR_TEXT_SEL_START/END attributes are correctly used to
815 * determine caret position and text selection in various types of editable
818 TEST_F(BrowserAccessibilityTest
, TestCaretAndTextSelection
) {
821 root
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
822 root
.state
= (1 << ui::AX_STATE_READ_ONLY
) | (1 << ui::AX_STATE_FOCUSABLE
);
824 ui::AXNodeData combo_box
;
826 combo_box
.role
= ui::AX_ROLE_COMBO_BOX
;
827 combo_box
.state
= (1 << ui::AX_STATE_FOCUSABLE
) | (1 << ui::AX_STATE_FOCUSED
);
828 combo_box
.SetValue("Test1");
829 // Place the caret between 't' and 'e'.
830 combo_box
.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START
, 1);
831 combo_box
.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END
, 1);
833 ui::AXNodeData text_field
;
835 text_field
.role
= ui::AX_ROLE_TEXT_FIELD
;
836 text_field
.state
= 1 << ui::AX_STATE_FOCUSABLE
;
837 text_field
.SetValue("Test2");
838 // Select the letter 'e'.
839 text_field
.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START
, 1);
840 text_field
.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END
, 2);
842 root
.child_ids
.push_back(2);
843 root
.child_ids
.push_back(3);
845 CountedBrowserAccessibility::reset();
846 scoped_ptr
<BrowserAccessibilityManager
> manager(
847 BrowserAccessibilityManager::Create(
848 MakeAXTreeUpdate(root
, combo_box
, text_field
),
849 nullptr, new CountedBrowserAccessibilityFactory()));
850 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
852 ASSERT_NE(nullptr, manager
->GetRoot());
853 BrowserAccessibilityWin
* root_accessible
=
854 manager
->GetRoot()->ToBrowserAccessibilityWin();
855 ASSERT_NE(nullptr, root_accessible
);
856 ASSERT_EQ(2, root_accessible
->PlatformChildCount());
858 BrowserAccessibilityWin
* combo_box_accessible
=
859 root_accessible
->PlatformGetChild(0)->ToBrowserAccessibilityWin();
860 ASSERT_NE(nullptr, combo_box_accessible
);
861 manager
->SetFocus(combo_box_accessible
, false /* notify */);
862 ASSERT_EQ(combo_box_accessible
,
863 manager
->GetFocus(root_accessible
)->ToBrowserAccessibilityWin());
864 BrowserAccessibilityWin
* text_field_accessible
=
865 root_accessible
->PlatformGetChild(1)->ToBrowserAccessibilityWin();
866 ASSERT_NE(nullptr, text_field_accessible
);
868 // -2 is never a valid offset.
869 LONG caret_offset
= -2;
870 LONG n_selections
= -2;
871 LONG selection_start
= -2;
872 LONG selection_end
= -2;
874 // Test get_caretOffset.
875 HRESULT hr
= combo_box_accessible
->get_caretOffset(&caret_offset
);;
877 EXPECT_EQ(1L, caret_offset
);
878 // caret_offset should be -1 when the object is not focused.
879 hr
= text_field_accessible
->get_caretOffset(&caret_offset
);;
880 EXPECT_EQ(S_FALSE
, hr
);
881 EXPECT_EQ(-1L, caret_offset
);
883 // Move the focus to the text field.
884 combo_box
.state
&= ~(1 << ui::AX_STATE_FOCUSED
);
885 text_field
.state
|= 1 << ui::AX_STATE_FOCUSED
;
886 manager
->SetFocus(text_field_accessible
, false /* notify */);
887 ASSERT_EQ(text_field_accessible
,
888 manager
->GetFocus(root_accessible
)->ToBrowserAccessibilityWin());
890 // The caret should be at the start of the selection.
891 hr
= text_field_accessible
->get_caretOffset(&caret_offset
);;
893 EXPECT_EQ(1L, caret_offset
);
895 // Test get_nSelections.
896 hr
= combo_box_accessible
->get_nSelections(&n_selections
);;
898 EXPECT_EQ(0L, n_selections
);
899 hr
= text_field_accessible
->get_nSelections(&n_selections
);;
901 EXPECT_EQ(1L, n_selections
);
903 // Test get_selection.
904 hr
= combo_box_accessible
->get_selection(
905 0L /* selection_index */, &selection_start
, &selection_end
);;
906 EXPECT_EQ(E_INVALIDARG
, hr
); // No selections available.
907 // Invalid in_args should not modify out_args.
908 EXPECT_EQ(-2L, selection_start
);
909 EXPECT_EQ(-2L, selection_end
);
910 hr
= text_field_accessible
->get_selection(
911 0L /* selection_index */, &selection_start
, &selection_end
);;
913 EXPECT_EQ(1L, selection_start
);
914 EXPECT_EQ(2L, selection_end
);
917 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
920 } // namespace content