Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_win_unittest.cc
blob501490e2117ea1e7b784801b0edcf802b07a6ca9
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 ui::AXNodeData text2;
235 text2.id = 2;
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);
242 param.id = text2.id;
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"
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 AccessibilityHostMsg_EventParams 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<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
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<AccessibilityHostMsg_EventParams> params;
697 params.push_back(AccessibilityHostMsg_EventParams());
698 AccessibilityHostMsg_EventParams* 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 various types of editable
816 * elements.
818 TEST_F(BrowserAccessibilityTest, TestCaretAndTextSelection) {
819 ui::AXNodeData root;
820 root.id = 1;
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;
825 combo_box.id = 2;
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;
834 text_field.id = 3;
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);;
876 EXPECT_EQ(S_OK, hr);
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);;
892 EXPECT_EQ(S_OK, hr);
893 EXPECT_EQ(1L, caret_offset);
895 // Test get_nSelections.
896 hr = combo_box_accessible->get_nSelections(&n_selections);;
897 EXPECT_EQ(S_OK, hr);
898 EXPECT_EQ(0L, n_selections);
899 hr = text_field_accessible->get_nSelections(&n_selections);;
900 EXPECT_EQ(S_OK, hr);
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);;
912 EXPECT_EQ(S_OK, hr);
913 EXPECT_EQ(1L, selection_start);
914 EXPECT_EQ(2L, selection_end);
916 manager.reset();
917 ASSERT_EQ(0, CountedBrowserAccessibility::num_instances());
920 } // namespace content