Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_win_unittest.cc
blob898e5dced782e214bfcf9203450cd1850477b74f
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"
20 namespace content {
21 namespace {
24 // CountedBrowserAccessibility ------------------------------------------------
26 // Subclass of BrowserAccessibilityWin that counts the number of instances.
27 class CountedBrowserAccessibility : public BrowserAccessibilityWin {
28 public:
29 CountedBrowserAccessibility();
30 ~CountedBrowserAccessibility() override;
32 static void reset() { num_instances_ = 0; }
33 static int num_instances() { return num_instances_; }
35 private:
36 static int num_instances_;
38 DISALLOW_COPY_AND_ASSIGN(CountedBrowserAccessibility);
41 // static
42 int CountedBrowserAccessibility::num_instances_ = 0;
44 CountedBrowserAccessibility::CountedBrowserAccessibility() {
45 ++num_instances_;
48 CountedBrowserAccessibility::~CountedBrowserAccessibility() {
49 --num_instances_;
53 // CountedBrowserAccessibilityFactory -----------------------------------------
55 // Factory that creates a CountedBrowserAccessibility.
56 class CountedBrowserAccessibilityFactory : public BrowserAccessibilityFactory {
57 public:
58 CountedBrowserAccessibilityFactory();
60 private:
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(
77 &instance);
78 DCHECK(SUCCEEDED(hr));
79 instance->AddRef();
80 return instance;
83 } // namespace
86 // BrowserAccessibilityTest ---------------------------------------------------
88 class BrowserAccessibilityTest : public testing::Test {
89 public:
90 BrowserAccessibilityTest();
91 ~BrowserAccessibilityTest() override;
93 private:
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;
121 button.id = 2;
122 button.SetName("Button");
123 button.role = ui::AX_ROLE_BUTTON;
124 button.state = 0;
126 ui::AXNodeData checkbox;
127 checkbox.id = 3;
128 checkbox.SetName("Checkbox");
129 checkbox.role = ui::AX_ROLE_CHECK_BOX;
130 checkbox.state = 0;
132 ui::AXNodeData root;
133 root.id = 1;
134 root.SetName("Document");
135 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
136 root.state = 0;
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.
152 manager.reset();
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);
167 ASSERT_EQ(S_OK, hr);
168 base::win::ScopedVariant one(1);
169 hr = root_accessible->get_accChild(one, &child1_iaccessible);
170 ASSERT_EQ(S_OK, hr);
172 // Now delete the manager, and only one of the three nodes in the tree
173 // should be released.
174 manager.reset();
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.
189 ui::AXNodeData text;
190 text.id = 2;
191 text.role = ui::AX_ROLE_STATIC_TEXT;
192 text.SetName("old text");
193 text.state = 0;
195 ui::AXNodeData root;
196 root.id = 1;
197 root.SetName("Document");
198 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
199 root.state = 0;
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
212 // value.
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());
217 ASSERT_EQ(S_OK, hr);
219 base::win::ScopedComPtr<IAccessible> text_accessible;
220 hr = text_dispatch.QueryInterface(text_accessible.Receive());
221 ASSERT_EQ(S_OK, hr);
223 base::win::ScopedVariant childid_self(CHILDID_SELF);
224 base::win::ScopedBstr name;
225 hr = text_accessible->get_accName(childid_self, name.Receive());
226 ASSERT_EQ(S_OK, hr);
227 EXPECT_EQ(L"old text", base::string16(name));
228 name.Reset();
230 text_dispatch.Release();
231 text_accessible.Release();
233 // Notify the BrowserAccessibilityManager that the text child has changed.
234 AXContentNodeData text2;
235 text2.id = 2;
236 text2.role = ui::AX_ROLE_STATIC_TEXT;
237 text2.SetName("new text");
238 text2.SetName("old text");
239 AXEventNotificationDetails param;
240 param.event_type = ui::AX_EVENT_CHILDREN_CHANGED;
241 param.update.nodes.push_back(text2);
242 param.id = text2.id;
243 std::vector<AXEventNotificationDetails> events;
244 events.push_back(param);
245 manager->OnAccessibilityEvents(events);
247 // Query for the text IAccessible and verify that it now returns "new text"
248 // as its value.
249 hr = manager->GetRoot()->ToBrowserAccessibilityWin()->get_accChild(
250 one, text_dispatch.Receive());
251 ASSERT_EQ(S_OK, hr);
253 hr = text_dispatch.QueryInterface(text_accessible.Receive());
254 ASSERT_EQ(S_OK, hr);
256 hr = text_accessible->get_accName(childid_self, name.Receive());
257 ASSERT_EQ(S_OK, hr);
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
264 // deleted.
265 manager.reset();
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.
273 ui::AXNodeData div;
274 div.id = 2;
275 div.role = ui::AX_ROLE_GROUP;
276 div.state = 0;
278 ui::AXNodeData text3;
279 text3.id = 3;
280 text3.role = ui::AX_ROLE_STATIC_TEXT;
281 text3.state = 0;
283 ui::AXNodeData text4;
284 text4.id = 4;
285 text4.role = ui::AX_ROLE_STATIC_TEXT;
286 text4.state = 0;
288 div.child_ids.push_back(3);
289 div.child_ids.push_back(4);
291 ui::AXNodeData root;
292 root.id = 1;
293 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
294 root.state = 0;
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 AXEventNotificationDetails param;
312 param.event_type = ui::AX_EVENT_CHILDREN_CHANGED;
313 param.update.nodes.push_back(root);
314 param.id = root.id;
315 std::vector<AXEventNotificationDetails> 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
321 // deleted.
322 manager.reset();
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;
331 ui::AXNodeData root;
332 root.id = 1;
333 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
334 root.child_ids.push_back(2);
336 ui::AXNodeData text_field;
337 text_field.id = 2;
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;
349 static_text1.id = 3;
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;
355 inline_box1.id = 4;
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;
366 line_break.id = 5;
367 line_break.role = ui::AX_ROLE_LINE_BREAK;
368 line_break.AddStringAttribute(ui::AX_ATTR_VALUE, "\n");
370 ui::AXNodeData static_text2;
371 static_text2.id = 6;
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;
377 inline_box2.id = 7;
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);
404 long text_len;
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)));
410 text.Reset();
412 EXPECT_EQ(S_OK, text_field_obj->get_text(0, 4, text.Receive()));
413 EXPECT_STREQ(L"One ", text);
414 text.Reset();
416 long start;
417 long end;
418 EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
419 1, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive()));
420 EXPECT_EQ(1, start);
421 EXPECT_EQ(2, end);
422 EXPECT_STREQ(L"n", text);
423 text.Reset();
425 EXPECT_EQ(S_FALSE, text_field_obj->get_textAtOffset(
426 text_len, IA2_TEXT_BOUNDARY_CHAR, &start, &end, text.Receive()));
427 EXPECT_EQ(0, start);
428 EXPECT_EQ(0, end);
429 EXPECT_EQ(nullptr, text);
430 text.Reset();
432 EXPECT_EQ(S_FALSE, text_field_obj->get_textAtOffset(
433 text_len, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
434 EXPECT_EQ(0, start);
435 EXPECT_EQ(0, end);
436 EXPECT_EQ(nullptr, text);
437 text.Reset();
439 EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
440 1, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
441 EXPECT_EQ(0, start);
442 EXPECT_EQ(4, end);
443 EXPECT_STREQ(L"One ", text);
444 text.Reset();
446 EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
447 6, IA2_TEXT_BOUNDARY_WORD, &start, &end, text.Receive()));
448 EXPECT_EQ(4, start);
449 EXPECT_EQ(8, end);
450 EXPECT_STREQ(L"two ", text);
451 text.Reset();
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);
456 EXPECT_EQ(29, end);
457 EXPECT_STREQ(L"six.", text);
458 text.Reset();
460 EXPECT_EQ(S_OK, text_field_obj->get_textAtOffset(
461 1, IA2_TEXT_BOUNDARY_LINE, &start, &end, text.Receive()));
462 EXPECT_EQ(0, start);
463 EXPECT_EQ(15, end);
464 EXPECT_STREQ(L"One two three.\n", text);
465 text.Reset();
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);
472 text.Reset();
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
479 // deleted.
480 manager.reset();
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;
489 text1.id = 11;
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;
495 text2.id = 12;
496 text2.role = ui::AX_ROLE_STATIC_TEXT;
497 text2.state = 1 << ui::AX_STATE_READ_ONLY;
498 text2.SetName(text2_name);
500 ui::AXNodeData root;
501 root.id = 1;
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();
517 long text_len;
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
545 // deleted.
546 manager.reset();
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;
557 text1.id = 11;
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;
563 text2.id = 12;
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;
569 button1.id = 13;
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;
579 link1.id = 14;
580 link1_text.id = 16;
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);
588 ui::AXNodeData root;
589 root.id = 1;
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();
609 long text_len;
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)));
618 text.Reset();
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()));
631 EXPECT_EQ(S_OK,
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());
636 text.Reset();
637 hyperlink.Release();
638 hypertext.Release();
640 EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, hyperlink.Receive()));
641 EXPECT_EQ(S_OK,
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());
646 text.Reset();
647 hyperlink.Release();
648 hypertext.Release();
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
661 // deleted.
662 manager.reset();
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(),
676 NULL,
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;
687 tree1_1.id = 1;
688 tree1_1.role = ui::AX_ROLE_ROOT_WEB_AREA;
689 tree1_1.child_ids.push_back(2);
691 ui::AXNodeData tree1_2;
692 tree1_2.id = 2;
693 tree1_2.role = ui::AX_ROLE_TEXT_FIELD;
695 // Process a load complete.
696 std::vector<AXEventNotificationDetails> params;
697 params.push_back(AXEventNotificationDetails());
698 AXEventNotificationDetails* msg = &params[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;
717 tree2_1.id = 1;
718 tree2_1.role = ui::AX_ROLE_ROOT_WEB_AREA;
719 tree2_1.child_ids.push_back(3);
721 ui::AXNodeData tree2_2;
722 tree2_2.id = 3;
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.
743 manager.reset();
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
751 // was initialized.
752 TEST_F(BrowserAccessibilityTest, EmptyDocHasUniqueIdWin) {
753 scoped_ptr<BrowserAccessibilityManagerWin> manager(
754 new BrowserAccessibilityManagerWin(
755 BrowserAccessibilityManagerWin::GetEmptyDocument(),
756 NULL,
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,
766 root->GetState());
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;
774 checkbox.id = 2;
775 checkbox.SetName("Checkbox");
776 checkbox.role = ui::AX_ROLE_CHECK_BOX;
777 checkbox.state = 1 << ui::AX_STATE_CHECKED;
779 ui::AXNodeData root;
780 root.id = 1;
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());
804 EXPECT_EQ(S_OK, hr);
805 EXPECT_NE(nullptr, static_cast<BSTR>(attributes));
806 std::wstring attributes_str(attributes, attributes.Length());
807 EXPECT_EQ(L"checkable:true;", attributes_str);
809 manager.reset();
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 simple form fields.
817 TEST_F(BrowserAccessibilityTest, TestCaretAndSelectionInSimpleFields) {
818 ui::AXNodeData root;
819 root.id = 1;
820 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
821 root.state = (1 << ui::AX_STATE_READ_ONLY) | (1 << ui::AX_STATE_FOCUSABLE);
823 ui::AXNodeData combo_box;
824 combo_box.id = 2;
825 combo_box.role = ui::AX_ROLE_COMBO_BOX;
826 combo_box.state = (1 << ui::AX_STATE_EDITABLE) |
827 (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;
834 text_field.id = 3;
835 text_field.role = ui::AX_ROLE_TEXT_FIELD;
836 text_field.state = (1 << ui::AX_STATE_EDITABLE) |
837 (1 << ui::AX_STATE_FOCUSABLE);
838 text_field.SetValue("Test2");
839 // Select the letter 'e'.
840 text_field.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, 1);
841 text_field.AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, 2);
843 root.child_ids.push_back(2);
844 root.child_ids.push_back(3);
846 CountedBrowserAccessibility::reset();
847 scoped_ptr<BrowserAccessibilityManager> manager(
848 BrowserAccessibilityManager::Create(
849 MakeAXTreeUpdate(root, combo_box, text_field),
850 nullptr, new CountedBrowserAccessibilityFactory()));
851 ASSERT_EQ(3, CountedBrowserAccessibility::num_instances());
853 ASSERT_NE(nullptr, manager->GetRoot());
854 BrowserAccessibilityWin* root_accessible =
855 manager->GetRoot()->ToBrowserAccessibilityWin();
856 ASSERT_NE(nullptr, root_accessible);
857 ASSERT_EQ(2, root_accessible->PlatformChildCount());
859 BrowserAccessibilityWin* combo_box_accessible =
860 root_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
861 ASSERT_NE(nullptr, combo_box_accessible);
862 manager->SetFocus(combo_box_accessible, false /* notify */);
863 ASSERT_EQ(combo_box_accessible,
864 manager->GetFocus(root_accessible)->ToBrowserAccessibilityWin());
865 BrowserAccessibilityWin* text_field_accessible =
866 root_accessible->PlatformGetChild(1)->ToBrowserAccessibilityWin();
867 ASSERT_NE(nullptr, text_field_accessible);
869 // -2 is never a valid offset.
870 LONG caret_offset = -2;
871 LONG n_selections = -2;
872 LONG selection_start = -2;
873 LONG selection_end = -2;
875 // Test get_caretOffset.
876 HRESULT hr = combo_box_accessible->get_caretOffset(&caret_offset);;
877 EXPECT_EQ(S_OK, hr);
878 EXPECT_EQ(1L, caret_offset);
879 // The caret should be at the start of the selection.
880 hr = text_field_accessible->get_caretOffset(&caret_offset);;
881 EXPECT_EQ(S_OK, hr);
882 EXPECT_EQ(1L, caret_offset);
884 // Move the focus to the text field.
885 combo_box.state &= ~(1 << ui::AX_STATE_FOCUSED);
886 text_field.state |= 1 << ui::AX_STATE_FOCUSED;
887 manager->SetFocus(text_field_accessible, false /* notify */);
888 ASSERT_EQ(text_field_accessible,
889 manager->GetFocus(root_accessible)->ToBrowserAccessibilityWin());
891 // The caret should not have moved.
892 hr = text_field_accessible->get_caretOffset(&caret_offset);;
893 EXPECT_EQ(S_OK, hr);
894 EXPECT_EQ(1L, caret_offset);
896 // Test get_nSelections.
897 hr = combo_box_accessible->get_nSelections(&n_selections);;
898 EXPECT_EQ(S_OK, hr);
899 EXPECT_EQ(0L, n_selections);
900 hr = text_field_accessible->get_nSelections(&n_selections);;
901 EXPECT_EQ(S_OK, hr);
902 EXPECT_EQ(1L, n_selections);
904 // Test get_selection.
905 hr = combo_box_accessible->get_selection(
906 0L /* selection_index */, &selection_start, &selection_end);;
907 EXPECT_EQ(E_INVALIDARG, hr); // No selections available.
908 // Invalid in_args should not modify out_args.
909 EXPECT_EQ(-2L, selection_start);
910 EXPECT_EQ(-2L, selection_end);
911 hr = text_field_accessible->get_selection(
912 0L /* selection_index */, &selection_start, &selection_end);;
913 EXPECT_EQ(S_OK, hr);
914 EXPECT_EQ(1L, selection_start);
915 EXPECT_EQ(2L, selection_end);
917 manager.reset();
918 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
921 TEST_F(BrowserAccessibilityTest, TestCaretInContentEditables) {
922 ui::AXNodeData root;
923 root.id = 1;
924 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
925 root.state = (1 << ui::AX_STATE_READ_ONLY) | (1 << ui::AX_STATE_FOCUSABLE);
927 ui::AXNodeData div_editable;
928 div_editable.id = 2;
929 div_editable.role = ui::AX_ROLE_DIV;
930 div_editable.state = (1 << ui::AX_STATE_EDITABLE) |
931 (1 << ui::AX_STATE_FOCUSABLE);
933 ui::AXNodeData text;
934 text.id = 3;
935 text.role = ui::AX_ROLE_STATIC_TEXT;
936 text.state = (1 << ui::AX_STATE_EDITABLE);
937 text.SetName("Click ");
939 ui::AXNodeData link;
940 link.id = 4;
941 link.role = ui::AX_ROLE_LINK;
942 link.state = (1 << ui::AX_STATE_EDITABLE) |
943 (1 << ui::AX_STATE_FOCUSABLE) | (1 << ui::AX_STATE_LINKED);
944 link.SetName("here");
946 ui::AXNodeData link_text;
947 link_text.id = 5;
948 link_text.role = ui::AX_ROLE_STATIC_TEXT;
949 link_text.state = (1 << ui::AX_STATE_EDITABLE) |
950 (1 << ui::AX_STATE_FOCUSABLE) | (1 << ui::AX_STATE_LINKED);
951 link_text.SetName("here");
953 // Place the caret between 'h' and 'e'.
954 root.AddIntAttribute(ui::AX_ATTR_ANCHOR_OBJECT_ID, 4);
955 root.AddIntAttribute(ui::AX_ATTR_ANCHOR_OFFSET, 1);
956 root.AddIntAttribute(ui::AX_ATTR_FOCUS_OBJECT_ID, 4);
957 root.AddIntAttribute(ui::AX_ATTR_FOCUS_OFFSET, 1);
959 root.child_ids.push_back(2);
960 div_editable.child_ids.push_back(3);
961 div_editable.child_ids.push_back(4);
962 link.child_ids.push_back(5);
964 CountedBrowserAccessibility::reset();
965 scoped_ptr<BrowserAccessibilityManager> manager(
966 BrowserAccessibilityManager::Create(
967 MakeAXTreeUpdate(root, div_editable, link, link_text, text),
968 nullptr, new CountedBrowserAccessibilityFactory()));
969 ASSERT_EQ(5, CountedBrowserAccessibility::num_instances());
971 ASSERT_NE(nullptr, manager->GetRoot());
972 BrowserAccessibilityWin* root_accessible =
973 manager->GetRoot()->ToBrowserAccessibilityWin();
974 ASSERT_NE(nullptr, root_accessible);
975 ASSERT_EQ(1, root_accessible->PlatformChildCount());
977 BrowserAccessibilityWin* div_editable_accessible =
978 root_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
979 ASSERT_NE(nullptr, div_editable_accessible);
980 ASSERT_EQ(2, div_editable_accessible->PlatformChildCount());
982 // -2 is never a valid offset.
983 LONG caret_offset = -2;
985 // The caret should be on the embedded object character.
986 HRESULT hr = div_editable_accessible->get_caretOffset(&caret_offset);;
987 EXPECT_EQ(S_OK, hr);
988 EXPECT_EQ(6L, caret_offset);
990 // Move the focus to the content editable.
991 div_editable.state |= 1 << ui::AX_STATE_FOCUSED;
992 manager->SetFocus(div_editable_accessible, false /* notify */);
993 ASSERT_EQ(div_editable_accessible,
994 manager->GetFocus(root_accessible)->ToBrowserAccessibilityWin());
996 BrowserAccessibilityWin* text_accessible =
997 div_editable_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
998 ASSERT_NE(nullptr, text_accessible);
999 BrowserAccessibilityWin* link_accessible =
1000 div_editable_accessible->PlatformGetChild(1)->ToBrowserAccessibilityWin();
1001 ASSERT_NE(nullptr, link_accessible);
1002 ASSERT_EQ(1, link_accessible->PlatformChildCount());
1004 BrowserAccessibilityWin* link_text_accessible =
1005 link_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
1006 ASSERT_NE(nullptr, link_text_accessible);
1008 // The caret should not have moved.
1009 hr = div_editable_accessible->get_caretOffset(&caret_offset);;
1010 EXPECT_EQ(S_OK, hr);
1011 EXPECT_EQ(6L, caret_offset);
1013 hr = link_accessible->get_caretOffset(&caret_offset);;
1014 EXPECT_EQ(S_OK, hr);
1015 EXPECT_EQ(1L, caret_offset);
1016 hr = link_text_accessible->get_caretOffset(&caret_offset);;
1017 EXPECT_EQ(S_OK, hr);
1018 EXPECT_EQ(1L, caret_offset);
1020 manager.reset();
1021 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
1024 TEST_F(BrowserAccessibilityTest, DISABLED_TestSelectionInContentEditables) {
1025 ui::AXNodeData root;
1026 root.id = 1;
1027 root.role = ui::AX_ROLE_ROOT_WEB_AREA;
1028 root.state = (1 << ui::AX_STATE_READ_ONLY) | (1 << ui::AX_STATE_FOCUSABLE);
1030 ui::AXNodeData div_editable;
1031 div_editable.id = 2;
1032 div_editable.role = ui::AX_ROLE_DIV;
1033 div_editable.state = (1 << ui::AX_STATE_FOCUSABLE);
1035 ui::AXNodeData text;
1036 text.id = 3;
1037 text.role = ui::AX_ROLE_STATIC_TEXT;
1038 text.SetName("Click ");
1040 ui::AXNodeData link;
1041 link.id = 4;
1042 link.role = ui::AX_ROLE_LINK;
1043 link.state = (1 << ui::AX_STATE_FOCUSABLE) | (1 << ui::AX_STATE_LINKED);
1044 link.SetName("here");
1046 ui::AXNodeData link_text;
1047 link_text.id = 5;
1048 link_text.role = ui::AX_ROLE_STATIC_TEXT;
1049 link_text.state = (1 << ui::AX_STATE_FOCUSABLE) | (1 << ui::AX_STATE_LINKED);
1050 link_text.SetName("here");
1052 // Select the part of the text "lick here".
1053 root.AddIntAttribute(ui::AX_ATTR_ANCHOR_OBJECT_ID, 3);
1054 root.AddIntAttribute(ui::AX_ATTR_ANCHOR_OFFSET, 1);
1055 root.AddIntAttribute(ui::AX_ATTR_FOCUS_OBJECT_ID, 5);
1056 root.AddIntAttribute(ui::AX_ATTR_FOCUS_OFFSET, 4);
1058 root.child_ids.push_back(2);
1059 div_editable.child_ids.push_back(3);
1060 div_editable.child_ids.push_back(4);
1061 link.child_ids.push_back(5);
1063 CountedBrowserAccessibility::reset();
1064 scoped_ptr<BrowserAccessibilityManager> manager(
1065 BrowserAccessibilityManager::Create(
1066 MakeAXTreeUpdate(root, div_editable, link, link_text, text),
1067 nullptr, new CountedBrowserAccessibilityFactory()));
1068 ASSERT_EQ(5, CountedBrowserAccessibility::num_instances());
1070 ASSERT_NE(nullptr, manager->GetRoot());
1071 BrowserAccessibilityWin* root_accessible =
1072 manager->GetRoot()->ToBrowserAccessibilityWin();
1073 ASSERT_NE(nullptr, root_accessible);
1074 ASSERT_EQ(1, root_accessible->PlatformChildCount());
1076 BrowserAccessibilityWin* div_editable_accessible =
1077 root_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
1078 ASSERT_NE(nullptr, div_editable_accessible);
1079 ASSERT_EQ(2, div_editable_accessible->PlatformChildCount());
1081 // -2 is never a valid offset.
1082 LONG caret_offset = -2;
1083 LONG n_selections = -2;
1084 LONG selection_start = -2;
1085 LONG selection_end = -2;
1087 BrowserAccessibilityWin* text_accessible =
1088 div_editable_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
1089 ASSERT_NE(nullptr, text_accessible);
1090 BrowserAccessibilityWin* link_accessible =
1091 div_editable_accessible->PlatformGetChild(1)->ToBrowserAccessibilityWin();
1092 ASSERT_NE(nullptr, link_accessible);
1093 ASSERT_EQ(1, link_accessible->PlatformChildCount());
1095 BrowserAccessibilityWin* link_text_accessible =
1096 link_accessible->PlatformGetChild(0)->ToBrowserAccessibilityWin();
1097 ASSERT_NE(nullptr, link_text_accessible);
1099 // get_nSelections should work on all objects.
1100 HRESULT hr = div_editable_accessible->get_nSelections(&n_selections);;
1101 EXPECT_EQ(S_OK, hr);
1102 EXPECT_EQ(1L, n_selections);
1103 hr = text_accessible->get_nSelections(&n_selections);;
1104 EXPECT_EQ(S_OK, hr);
1105 EXPECT_EQ(1L, n_selections);
1106 hr = link_accessible->get_nSelections(&n_selections);;
1107 EXPECT_EQ(S_OK, hr);
1108 EXPECT_EQ(1L, n_selections);
1109 hr = link_text_accessible->get_nSelections(&n_selections);;
1110 EXPECT_EQ(S_OK, hr);
1111 EXPECT_EQ(1L, n_selections);
1113 // get_selection should be unaffected by focus placement.
1114 hr = div_editable_accessible->get_selection(
1115 0L /* selection_index */, &selection_start, &selection_end);;
1116 EXPECT_EQ(S_OK, hr);
1117 EXPECT_EQ(1L, selection_start);
1118 // selection_end should be after embedded object character.
1119 EXPECT_EQ(7L, selection_end);
1121 hr = text_accessible->get_selection(
1122 0L /* selection_index */, &selection_start, &selection_end);;
1123 EXPECT_EQ(S_OK, hr);
1124 EXPECT_EQ(1L, selection_start);
1125 // No embedded character on this object, only the first part of the text.
1126 EXPECT_EQ(6L, selection_end);
1127 hr = link_accessible->get_selection(
1128 0L /* selection_index */, &selection_start, &selection_end);;
1129 EXPECT_EQ(S_OK, hr);
1130 EXPECT_EQ(0L, selection_start);
1131 EXPECT_EQ(4L, selection_end);
1132 hr = link_text_accessible->get_selection(
1133 0L /* selection_index */, &selection_start, &selection_end);;
1134 EXPECT_EQ(S_OK, hr);
1135 EXPECT_EQ(0L, selection_start);
1136 EXPECT_EQ(4L, selection_end);
1138 // The caret should be at the anchor (the start) of the selection.
1139 hr = div_editable_accessible->get_caretOffset(&caret_offset);;
1140 EXPECT_EQ(S_OK, hr);
1141 EXPECT_EQ(1L, caret_offset);
1143 // Move the focus to the content editable.
1144 div_editable.state |= 1 << ui::AX_STATE_FOCUSED;
1145 manager->SetFocus(div_editable_accessible, false /* notify */);
1146 ASSERT_EQ(div_editable_accessible,
1147 manager->GetFocus(root_accessible)->ToBrowserAccessibilityWin());
1149 // The caret should not have moved.
1150 hr = div_editable_accessible->get_caretOffset(&caret_offset);;
1151 EXPECT_EQ(S_OK, hr);
1152 EXPECT_EQ(1L, caret_offset);
1154 // The HRESULT should be S_FALSE if the caret is not in the given object.
1155 hr = link_accessible->get_caretOffset(&caret_offset);;
1156 EXPECT_EQ(S_FALSE, hr);
1157 EXPECT_EQ(-1L, caret_offset);
1158 hr = link_text_accessible->get_caretOffset(&caret_offset);;
1159 EXPECT_EQ(S_FALSE, hr);
1160 EXPECT_EQ(-1L, caret_offset);
1162 hr = div_editable_accessible->get_selection(
1163 0L /* selection_index */, &selection_start, &selection_end);;
1164 EXPECT_EQ(S_OK, hr);
1165 EXPECT_EQ(1L, selection_start);
1166 EXPECT_EQ(7L, selection_end);
1168 manager.reset();
1169 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
1172 } // namespace content