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.
4
5 #include "content/browser/accessibility/browser_accessibility_win.h"
6
7 #include <string>
8 #include <vector>
9
10 #include <objbase.h>
11 #include <stdint.h>
12 #include <wrl/client.h>
13
14 #include <memory>
15 #include <utility>
16
17 #include "base/command_line.h"
18 #include "base/macros.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/test/task_environment.h"
21 #include "base/win/scoped_bstr.h"
22 #include "base/win/scoped_variant.h"
23 #include "content/browser/accessibility/browser_accessibility_manager.h"
24 #include "content/browser/accessibility/browser_accessibility_manager_win.h"
25 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
26 #include "content/browser/accessibility/test_browser_accessibility_delegate.h"
27 #include "content/browser/renderer_host/legacy_render_widget_host_win.h"
28 #include "content/public/test/browser_task_environment.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "ui/accessibility/accessibility_switches.h"
31 #include "ui/accessibility/ax_node_data.h"
32 #include "ui/accessibility/platform/ax_platform_node_win.h"
33 #include "ui/base/win/atl_module.h"
34
35 namespace content {
36
37 #define EXPECT_IA2_TEXT_AT_OFFSET(provider, index, text_boundary, expected_hr, \
38 start, end, text) \
39 { \
40 LONG actual_start; \
41 LONG actual_end; \
42 base::win::ScopedBstr actual_text; \
43 EXPECT_EQ(expected_hr, \
44 provider->get_textAtOffset(index, text_boundary, &actual_start, \
45 &actual_end, actual_text.Receive())); \
46 EXPECT_EQ(start, actual_start); \
47 EXPECT_EQ(end, actual_end); \
48 EXPECT_STREQ(text, actual_text.Get()); \
49 }
50
51 #define EXPECT_IA2_TEXT_BEFORE_OFFSET(provider, index, text_boundary, \
52 expected_hr, start, end, text) \
53 { \
54 LONG actual_start; \
55 LONG actual_end; \
56 base::win::ScopedBstr actual_text; \
57 EXPECT_EQ(expected_hr, provider->get_textBeforeOffset( \
58 index, text_boundary, &actual_start, \
59 &actual_end, actual_text.Receive())); \
60 EXPECT_EQ(start, actual_start); \
61 EXPECT_EQ(end, actual_end); \
62 EXPECT_STREQ(text, actual_text.Get()); \
63 }
64
65 #define EXPECT_IA2_TEXT_AFTER_OFFSET(provider, index, text_boundary, \
66 expected_hr, start, end, text) \
67 { \
68 LONG actual_start; \
69 LONG actual_end; \
70 base::win::ScopedBstr actual_text; \
71 EXPECT_EQ(expected_hr, provider->get_textAfterOffset( \
72 index, text_boundary, &actual_start, \
73 &actual_end, actual_text.Receive())); \
74 EXPECT_EQ(start, actual_start); \
75 EXPECT_EQ(end, actual_end); \
76 EXPECT_STREQ(text, actual_text.Get()); \
77 }
78
79 #define EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, \
80 expected_descendants) \
81 { \
82 size_t count = descendants.size(); \
83 EXPECT_EQ(count, expected_descendants.size()); \
84 for (size_t i = 0; i < count; ++i) { \
85 EXPECT_EQ(ui::AXPlatformNode::FromNativeViewAccessible(descendants[i]) \
86 ->GetDelegate() \
87 ->GetData() \
88 .ToString(), \
89 ui::AXPlatformNode::FromNativeViewAccessible( \
90 expected_descendants[i]) \
91 ->GetDelegate() \
92 ->GetData() \
93 .ToString()); \
94 } \
95 }
96
97 // BrowserAccessibilityWinTest ------------------------------------------------
98
99 class BrowserAccessibilityWinTest : public testing::Test {
100 public:
101 BrowserAccessibilityWinTest();
102 ~BrowserAccessibilityWinTest() override;
103
104 protected:
105 std::unique_ptr<TestBrowserAccessibilityDelegate>
106 test_browser_accessibility_delegate_;
107
108 private:
109 void SetUp() override;
110
111 content::BrowserTaskEnvironment task_environment_;
112
113 DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityWinTest);
114 };
115
BrowserAccessibilityWinTest()116 BrowserAccessibilityWinTest::BrowserAccessibilityWinTest() {}
117
~BrowserAccessibilityWinTest()118 BrowserAccessibilityWinTest::~BrowserAccessibilityWinTest() {}
119
SetUp()120 void BrowserAccessibilityWinTest::SetUp() {
121 ui::win::CreateATLModuleIfNeeded();
122 test_browser_accessibility_delegate_ =
123 std::make_unique<TestBrowserAccessibilityDelegate>();
124 }
125
126 // Actual tests ---------------------------------------------------------------
127
128 // Test that BrowserAccessibilityManager correctly releases the tree of
129 // BrowserAccessibility instances upon delete.
TEST_F(BrowserAccessibilityWinTest,TestNoLeaks)130 TEST_F(BrowserAccessibilityWinTest, TestNoLeaks) {
131 // Create ui::AXNodeData objects for a simple document tree,
132 // representing the accessibility information used to initialize
133 // BrowserAccessibilityManager.
134 ui::AXNodeData button;
135 button.id = 2;
136 button.role = ax::mojom::Role::kButton;
137 button.SetName("Button");
138
139 ui::AXNodeData checkbox;
140 checkbox.id = 3;
141 checkbox.role = ax::mojom::Role::kCheckBox;
142 checkbox.SetName("Checkbox");
143
144 ui::AXNodeData root;
145 root.id = 1;
146 root.role = ax::mojom::Role::kRootWebArea;
147 root.SetName("Document");
148 root.child_ids.push_back(2);
149 root.child_ids.push_back(3);
150
151 // Construct a BrowserAccessibilityManager with this
152 // ui::AXNodeData tree and a factory for an instance-counting
153 // BrowserAccessibility, and ensure that exactly 3 instances were
154 // created. Note that the manager takes ownership of the factory.
155 std::unique_ptr<BrowserAccessibilityManager> manager(
156 BrowserAccessibilityManager::Create(
157 MakeAXTreeUpdate(root, button, checkbox),
158 test_browser_accessibility_delegate_.get()));
159
160 // Delete the manager and test that all 3 instances are deleted.
161 manager.reset();
162
163 // Construct a manager again, and this time use the IAccessible interface
164 // to get new references to two of the three nodes in the tree.
165 manager.reset(BrowserAccessibilityManager::Create(
166 MakeAXTreeUpdate(root, button, checkbox),
167 test_browser_accessibility_delegate_.get()));
168 IAccessible* root_accessible =
169 ToBrowserAccessibilityWin(manager->GetRoot())->GetCOM();
170 IDispatch* root_iaccessible = NULL;
171 IDispatch* child1_iaccessible = NULL;
172 base::win::ScopedVariant childid_self(CHILDID_SELF);
173 HRESULT hr = root_accessible->get_accChild(childid_self, &root_iaccessible);
174 ASSERT_EQ(S_OK, hr);
175 base::win::ScopedVariant one(1);
176 hr = root_accessible->get_accChild(one, &child1_iaccessible);
177 ASSERT_EQ(S_OK, hr);
178
179 // Now delete the manager, and only one of the three nodes in the tree
180 // should be released.
181 manager.reset();
182
183 // Release each of our references and make sure that each one results in
184 // the instance being deleted as its reference count hits zero.
185 root_iaccessible->Release();
186 child1_iaccessible->Release();
187 }
188
TEST_F(BrowserAccessibilityWinTest,TestChildrenChange)189 TEST_F(BrowserAccessibilityWinTest, TestChildrenChange) {
190 // Create ui::AXNodeData objects for a simple document tree,
191 // representing the accessibility information used to initialize
192 // BrowserAccessibilityManager.
193 ui::AXNodeData text;
194 text.id = 2;
195 text.role = ax::mojom::Role::kStaticText;
196 text.SetName("old text");
197
198 ui::AXNodeData root;
199 root.id = 1;
200 root.role = ax::mojom::Role::kRootWebArea;
201 root.SetName("Document");
202 root.child_ids.push_back(2);
203
204 // Construct a BrowserAccessibilityManager with this
205 // ui::AXNodeData tree and a factory for an instance-counting
206 // BrowserAccessibility.
207 std::unique_ptr<BrowserAccessibilityManager> manager(
208 BrowserAccessibilityManager::Create(
209 MakeAXTreeUpdate(root, text),
210 test_browser_accessibility_delegate_.get()));
211
212 // Query for the text IAccessible and verify that it returns "old text" as its
213 // value.
214 base::win::ScopedVariant one(1);
215 Microsoft::WRL::ComPtr<IDispatch> text_dispatch;
216 HRESULT hr = ToBrowserAccessibilityWin(manager->GetRoot())
217 ->GetCOM()
218 ->get_accChild(one, &text_dispatch);
219 ASSERT_EQ(S_OK, hr);
220
221 Microsoft::WRL::ComPtr<IAccessible> text_accessible;
222 hr = text_dispatch.As(&text_accessible);
223 ASSERT_EQ(S_OK, hr);
224
225 base::win::ScopedVariant childid_self(CHILDID_SELF);
226 base::win::ScopedBstr name;
227 hr = text_accessible->get_accName(childid_self, name.Receive());
228 ASSERT_EQ(S_OK, hr);
229 EXPECT_EQ(L"old text", base::string16(name.Get()));
230 name.Reset();
231
232 text_dispatch.Reset();
233 text_accessible.Reset();
234
235 // Notify the BrowserAccessibilityManager that the text child has changed.
236 ui::AXNodeData text2;
237 text2.id = 2;
238 text2.role = ax::mojom::Role::kStaticText;
239 text2.SetName("new text");
240 AXEventNotificationDetails event_bundle;
241 event_bundle.updates.resize(1);
242 event_bundle.updates[0].nodes.push_back(text2);
243 ASSERT_TRUE(manager->OnAccessibilityEvents(event_bundle));
244
245 // Query for the text IAccessible and verify that it now returns "new text"
246 // as its value.
247 hr = ToBrowserAccessibilityWin(manager->GetRoot())
248 ->GetCOM()
249 ->get_accChild(one, &text_dispatch);
250 ASSERT_EQ(S_OK, hr);
251
252 hr = text_dispatch.As(&text_accessible);
253 ASSERT_EQ(S_OK, hr);
254
255 hr = text_accessible->get_accName(childid_self, name.Receive());
256 ASSERT_EQ(S_OK, hr);
257 EXPECT_EQ(L"new text", base::string16(name.Get()));
258
259 text_dispatch.Reset();
260 text_accessible.Reset();
261
262 // Delete the manager and test that all BrowserAccessibility instances are
263 // deleted.
264 manager.reset();
265 }
266
TEST_F(BrowserAccessibilityWinTest,TestChildrenChangeNoLeaks)267 TEST_F(BrowserAccessibilityWinTest, TestChildrenChangeNoLeaks) {
268 // Create ui::AXNodeData objects for a simple document tree,
269 // representing the accessibility information used to initialize
270 // BrowserAccessibilityManager.
271 ui::AXNodeData div;
272 div.id = 2;
273 div.role = ax::mojom::Role::kGroup;
274
275 ui::AXNodeData text3;
276 text3.id = 3;
277 text3.role = ax::mojom::Role::kStaticText;
278
279 ui::AXNodeData text4;
280 text4.id = 4;
281 text4.role = ax::mojom::Role::kStaticText;
282
283 div.child_ids.push_back(3);
284 div.child_ids.push_back(4);
285
286 ui::AXNodeData root;
287 root.id = 1;
288 root.role = ax::mojom::Role::kRootWebArea;
289 root.child_ids.push_back(2);
290
291 // Construct a BrowserAccessibilityManager with this
292 // ui::AXNodeData tree and a factory for an instance-counting
293 // BrowserAccessibility and ensure that exactly 4 instances were
294 // created. Note that the manager takes ownership of the factory.
295 std::unique_ptr<BrowserAccessibilityManager> manager(
296 BrowserAccessibilityManager::Create(
297 MakeAXTreeUpdate(root, div, text3, text4),
298 test_browser_accessibility_delegate_.get()));
299
300 // Notify the BrowserAccessibilityManager that the div node and its children
301 // were removed and ensure that only one BrowserAccessibility instance exists.
302 root.child_ids.clear();
303 AXEventNotificationDetails event_bundle;
304 event_bundle.updates.resize(1);
305 event_bundle.updates[0].nodes.push_back(root);
306 ASSERT_TRUE(manager->OnAccessibilityEvents(event_bundle));
307
308 // Delete the manager and test that all BrowserAccessibility instances are
309 // deleted.
310 manager.reset();
311 }
312
TEST_F(BrowserAccessibilityWinTest,TestTextBoundaries)313 TEST_F(BrowserAccessibilityWinTest, TestTextBoundaries) {
314 //
315 // +-1 root
316 // +-2 text_field
317 // +-3 static_text1 "One two three."
318 // | +-4 inline_box1 "One two three."
319 // +-5 line_break1 "\n"
320 // +-6 static_text2 "Four five six."
321 // | +-7 inline_box2 "Four five six."
322 // +-8 line_break2 "\n" kIsLineBreakingObject
323 // +-9 static_text3 "Seven eight nine."
324 // +-10 inline_box3 "Seven eight nine."
325 //
326 std::string line1 = "One two three.";
327 std::string line2 = "Four five six.";
328 std::string line3 = "Seven eight nine.";
329 std::string text_value = line1 + '\n' + line2 + '\n' + line3;
330
331 ui::AXNodeData root;
332 root.id = 1;
333 root.role = ax::mojom::Role::kRootWebArea;
334 root.child_ids.push_back(2);
335
336 ui::AXNodeData text_field;
337 text_field.id = 2;
338 text_field.role = ax::mojom::Role::kTextField;
339 text_field.AddState(ax::mojom::State::kEditable);
340 text_field.SetValue(text_value);
341 text_field.AddIntListAttribute(ax::mojom::IntListAttribute::kCachedLineStarts,
342 {15});
343 text_field.child_ids.push_back(3);
344 text_field.child_ids.push_back(5);
345 text_field.child_ids.push_back(6);
346 text_field.child_ids.push_back(8);
347 text_field.child_ids.push_back(9);
348
349 ui::AXNodeData static_text1;
350 static_text1.id = 3;
351 static_text1.role = ax::mojom::Role::kStaticText;
352 static_text1.AddState(ax::mojom::State::kEditable);
353 static_text1.SetName(line1);
354 static_text1.child_ids.push_back(4);
355
356 ui::AXNodeData inline_box1;
357 inline_box1.id = 4;
358 inline_box1.role = ax::mojom::Role::kInlineTextBox;
359 inline_box1.AddState(ax::mojom::State::kEditable);
360 inline_box1.SetName(line1);
361 inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
362 {0, 4, 8});
363
364 ui::AXNodeData line_break1;
365 line_break1.id = 5;
366 line_break1.role = ax::mojom::Role::kLineBreak;
367 line_break1.AddState(ax::mojom::State::kEditable);
368 line_break1.SetName("\n");
369
370 inline_box1.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
371 line_break1.id);
372 line_break1.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
373 inline_box1.id);
374
375 ui::AXNodeData static_text2;
376 static_text2.id = 6;
377 static_text2.role = ax::mojom::Role::kStaticText;
378 static_text2.AddState(ax::mojom::State::kEditable);
379 static_text2.SetName(line2);
380 static_text2.child_ids.push_back(7);
381
382 ui::AXNodeData inline_box2;
383 inline_box2.id = 7;
384 inline_box2.role = ax::mojom::Role::kInlineTextBox;
385 inline_box2.AddState(ax::mojom::State::kEditable);
386 inline_box2.SetName(line2);
387 inline_box2.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
388 {0, 5, 10});
389
390 ui::AXNodeData line_break2;
391 line_break2.id = 8;
392 line_break2.role = ax::mojom::Role::kLineBreak;
393 line_break2.AddState(ax::mojom::State::kEditable);
394 line_break2.SetName("\n");
395 line_break2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
396 true);
397
398 inline_box2.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
399 line_break2.id);
400 line_break2.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
401 inline_box2.id);
402
403 ui::AXNodeData static_text3;
404 static_text3.id = 9;
405 static_text3.role = ax::mojom::Role::kStaticText;
406 static_text3.AddState(ax::mojom::State::kEditable);
407 static_text3.SetName(line3);
408 static_text3.child_ids.push_back(10);
409
410 ui::AXNodeData inline_box3;
411 inline_box3.id = 10;
412 inline_box3.role = ax::mojom::Role::kInlineTextBox;
413 inline_box3.AddState(ax::mojom::State::kEditable);
414 inline_box3.SetName(line3);
415 inline_box3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
416 {0, 6, 12});
417
418 std::unique_ptr<BrowserAccessibilityManager> manager(
419 BrowserAccessibilityManager::Create(
420 MakeAXTreeUpdate(root, text_field, static_text1, inline_box1,
421 line_break1, static_text2, inline_box2, line_break2,
422 static_text3, inline_box3),
423 test_browser_accessibility_delegate_.get()));
424
425 BrowserAccessibilityWin* root_obj =
426 ToBrowserAccessibilityWin(manager->GetRoot());
427 ASSERT_NE(nullptr, root_obj);
428 ASSERT_EQ(1U, root_obj->PlatformChildCount());
429
430 BrowserAccessibilityComWin* text_field_obj =
431 ToBrowserAccessibilityComWin(root_obj->PlatformGetChild(0));
432 ASSERT_NE(nullptr, text_field_obj);
433
434 LONG text_len;
435 EXPECT_EQ(S_OK, text_field_obj->get_nCharacters(&text_len));
436
437 base::win::ScopedBstr text;
438 EXPECT_EQ(S_OK, text_field_obj->get_text(0, text_len, text.Receive()));
439 EXPECT_EQ(text_value, base::UTF16ToUTF8(base::string16(text.Get())));
440 text.Reset();
441
442 EXPECT_EQ(S_OK, text_field_obj->get_text(0, 4, text.Receive()));
443 EXPECT_STREQ(L"One ", text.Get());
444 text.Reset();
445
446 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, 1, IA2_TEXT_BOUNDARY_CHAR,
447 /*expected_hr=*/S_OK, /*start=*/1, /*end=*/2,
448 /*text=*/L"n");
449
450 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, text_len, IA2_TEXT_BOUNDARY_CHAR,
451 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
452 /*end=*/0,
453 /*text=*/nullptr);
454
455 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, text_len, IA2_TEXT_BOUNDARY_WORD,
456 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
457 /*end=*/0,
458 /*text=*/nullptr);
459
460 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, 1, IA2_TEXT_BOUNDARY_WORD,
461 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/4,
462 /*text=*/L"One ");
463
464 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, 6, IA2_TEXT_BOUNDARY_WORD,
465 /*expected_hr=*/S_OK, /*start=*/4, /*end=*/8,
466 /*text=*/L"two ");
467
468 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, text_len - 1,
469 IA2_TEXT_BOUNDARY_WORD,
470 /*expected_hr=*/S_OK, /*start=*/42, /*end=*/47,
471 /*text=*/L"nine.");
472
473 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, 1, IA2_TEXT_BOUNDARY_LINE,
474 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/15,
475 /*text=*/L"One two three.\n");
476
477 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, text_len, IA2_TEXT_BOUNDARY_LINE,
478 /*expected_hr=*/S_OK, /*start=*/30, /*end=*/47,
479 /*text=*/L"Seven eight nine.");
480
481 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, 1, IA2_TEXT_BOUNDARY_PARAGRAPH,
482 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/30,
483 /*text=*/L"One two three.\nFour five six.\n");
484
485 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, text_len - 1,
486 IA2_TEXT_BOUNDARY_PARAGRAPH,
487 /*expected_hr=*/S_OK, /*start=*/30, /*end=*/47,
488 /*text=*/L"Seven eight nine.");
489
490 EXPECT_IA2_TEXT_AT_OFFSET(text_field_obj, text_len,
491 IA2_TEXT_BOUNDARY_PARAGRAPH,
492 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
493 /*end=*/0,
494 /*text=*/nullptr);
495
496 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, 0, IA2_TEXT_BOUNDARY_CHAR,
497 /*expected_hr=*/S_FALSE, /*start=*/0, /*end=*/0,
498 /*text=*/nullptr);
499
500 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, 1, IA2_TEXT_BOUNDARY_CHAR,
501 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/1,
502 /*text=*/L"O");
503
504 EXPECT_IA2_TEXT_BEFORE_OFFSET(
505 text_field_obj, text_len, IA2_TEXT_BOUNDARY_CHAR,
506 /*expected_hr=*/E_INVALIDARG, /*start=*/0, /*end=*/0,
507 /*text=*/nullptr);
508
509 EXPECT_IA2_TEXT_BEFORE_OFFSET(
510 text_field_obj, text_len, IA2_TEXT_BOUNDARY_WORD,
511 /*expected_hr=*/E_INVALIDARG, /*start=*/0, /*end=*/0,
512 /*text=*/nullptr);
513
514 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, 1, IA2_TEXT_BOUNDARY_WORD,
515 /*expected_hr=*/S_FALSE, /*start=*/0, /*end=*/0,
516 /*text=*/nullptr);
517
518 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, 4, IA2_TEXT_BOUNDARY_WORD,
519 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/4,
520 /*text=*/L"One ");
521
522 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, 6, IA2_TEXT_BOUNDARY_WORD,
523 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/4,
524 /*text=*/L"One ");
525
526 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, text_len - 1,
527 IA2_TEXT_BOUNDARY_WORD,
528 /*expected_hr=*/S_OK, /*start=*/36, /*end=*/42,
529 /*text=*/L"eight ");
530
531 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, 0, IA2_TEXT_BOUNDARY_LINE,
532 /*expected_hr=*/S_FALSE, /*start=*/0, /*end=*/0,
533 /*text=*/nullptr);
534
535 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, text_len - 1,
536 IA2_TEXT_BOUNDARY_LINE,
537 /*expected_hr=*/S_OK, /*start=*/15, /*end=*/30,
538 /*text=*/L"Four five six.\n");
539
540 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, 18, IA2_TEXT_BOUNDARY_PARAGRAPH,
541 /*expected_hr=*/S_FALSE, /*start=*/0, /*end=*/0,
542 /*text=*/nullptr);
543
544 EXPECT_IA2_TEXT_BEFORE_OFFSET(text_field_obj, text_len - 1,
545 IA2_TEXT_BOUNDARY_PARAGRAPH,
546 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/30,
547 /*text=*/L"One two three.\nFour five six.\n");
548
549 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, 0, IA2_TEXT_BOUNDARY_CHAR,
550 /*expected_hr=*/S_OK, /*start=*/1, /*end=*/2,
551 /*text=*/L"n");
552
553 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, 1, IA2_TEXT_BOUNDARY_CHAR,
554 /*expected_hr=*/S_OK, /*start=*/2, /*end=*/3,
555 /*text=*/L"e");
556
557 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, text_len, IA2_TEXT_BOUNDARY_CHAR,
558 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
559 /*end=*/0,
560 /*text=*/nullptr);
561
562 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, 1, IA2_TEXT_BOUNDARY_WORD,
563 /*expected_hr=*/S_OK, /*start=*/4, /*end=*/8,
564 /*text=*/L"two ");
565
566 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, 6, IA2_TEXT_BOUNDARY_WORD,
567 /*expected_hr=*/S_OK, /*start=*/8, /*end=*/15,
568 /*text=*/L"three.\n");
569
570 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, text_len, IA2_TEXT_BOUNDARY_WORD,
571 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
572 /*end=*/0,
573 /*text=*/nullptr);
574
575 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, 0, IA2_TEXT_BOUNDARY_LINE,
576 /*expected_hr=*/S_OK, /*start=*/15, /*end=*/30,
577 /*text=*/L"Four five six.\n");
578
579 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, text_len - 1,
580 IA2_TEXT_BOUNDARY_LINE,
581 /*expected_hr=*/S_FALSE, /*start=*/0, /*end=*/0,
582 /*text=*/nullptr);
583
584 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, 18, IA2_TEXT_BOUNDARY_PARAGRAPH,
585 /*expected_hr=*/S_OK, /*start=*/30, /*end=*/47,
586 /*text=*/L"Seven eight nine.");
587
588 EXPECT_IA2_TEXT_AFTER_OFFSET(text_field_obj, text_len - 1,
589 IA2_TEXT_BOUNDARY_PARAGRAPH,
590 /*expected_hr=*/S_FALSE, /*start=*/0, /*end=*/0,
591 /*text=*/nullptr);
592
593 EXPECT_EQ(S_OK, text_field_obj->get_text(0, IA2_TEXT_OFFSET_LENGTH,
594 text.Receive()));
595 EXPECT_EQ(text_value, base::UTF16ToUTF8(base::string16(text.Get())));
596
597 // Delete the manager and test that all BrowserAccessibility instances are
598 // deleted.
599 manager.reset();
600 }
601
TEST_F(BrowserAccessibilityWinTest,TestSimpleHypertext)602 TEST_F(BrowserAccessibilityWinTest, TestSimpleHypertext) {
603 const std::string text1_name = "One two three.";
604 const std::string text2_name = " Four five six.";
605 const LONG text_name_len = text1_name.length() + text2_name.length();
606
607 ui::AXNodeData text1;
608 text1.id = 11;
609 text1.role = ax::mojom::Role::kStaticText;
610 text1.SetName(text1_name);
611
612 ui::AXNodeData text2;
613 text2.id = 12;
614 text2.role = ax::mojom::Role::kStaticText;
615 text2.SetName(text2_name);
616
617 ui::AXNodeData root;
618 root.id = 1;
619 root.role = ax::mojom::Role::kRootWebArea;
620 root.child_ids.push_back(text1.id);
621 root.child_ids.push_back(text2.id);
622
623 std::unique_ptr<BrowserAccessibilityManager> manager(
624 BrowserAccessibilityManager::Create(
625 MakeAXTreeUpdate(root, text1, text2),
626 test_browser_accessibility_delegate_.get()));
627
628 BrowserAccessibilityComWin* root_obj =
629 ToBrowserAccessibilityWin(manager->GetRoot())->GetCOM();
630
631 LONG text_len;
632 EXPECT_EQ(S_OK, root_obj->get_nCharacters(&text_len));
633 EXPECT_EQ(text_name_len, text_len);
634
635 base::win::ScopedBstr text;
636 EXPECT_EQ(S_OK, root_obj->get_text(0, text_name_len, text.Receive()));
637 EXPECT_EQ(text1_name + text2_name,
638 base::UTF16ToUTF8(base::string16(text.Get())));
639
640 LONG hyperlink_count;
641 EXPECT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
642 EXPECT_EQ(0, hyperlink_count);
643
644 Microsoft::WRL::ComPtr<IAccessibleHyperlink> hyperlink;
645 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, &hyperlink));
646 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(0, &hyperlink));
647 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(text_name_len, &hyperlink));
648 EXPECT_EQ(E_INVALIDARG,
649 root_obj->get_hyperlink(text_name_len + 1, &hyperlink));
650
651 LONG hyperlink_index;
652 EXPECT_EQ(S_FALSE, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
653 EXPECT_EQ(-1, hyperlink_index);
654 // Invalid arguments should not be modified.
655 hyperlink_index = -2;
656 EXPECT_EQ(E_INVALIDARG,
657 root_obj->get_hyperlinkIndex(text_name_len, &hyperlink_index));
658 EXPECT_EQ(-2, hyperlink_index);
659 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlinkIndex(-1, &hyperlink_index));
660 EXPECT_EQ(-2, hyperlink_index);
661 EXPECT_EQ(E_INVALIDARG,
662 root_obj->get_hyperlinkIndex(text_name_len + 1, &hyperlink_index));
663 EXPECT_EQ(-2, hyperlink_index);
664
665 manager.reset();
666 }
667
TEST_F(BrowserAccessibilityWinTest,TestComplexHypertext)668 TEST_F(BrowserAccessibilityWinTest, TestComplexHypertext) {
669 const base::string16 text1_name = L"One two three.";
670 const base::string16 combo_box_name = L"City:";
671 const base::string16 combo_box_value = L"Happyland";
672 const base::string16 text2_name = L" Four five six.";
673 const base::string16 check_box_name = L"I agree";
674 const base::string16 check_box_value = L"Checked";
675 const base::string16 button_text_name = L"Red";
676 const base::string16 link_text_name = L"Blue";
677 // Each control (combo / check box, button and link) will be represented by an
678 // embedded object character.
679 const base::string16 embed(1, BrowserAccessibilityComWin::kEmbeddedCharacter);
680 const base::string16 root_hypertext =
681 text1_name + embed + text2_name + embed + embed + embed;
682 const LONG root_hypertext_len = root_hypertext.length();
683
684 ui::AXNodeData text1;
685 text1.id = 11;
686 text1.role = ax::mojom::Role::kStaticText;
687 text1.SetName(base::UTF16ToUTF8(text1_name));
688
689 ui::AXNodeData combo_box;
690 combo_box.id = 12;
691 combo_box.role = ax::mojom::Role::kTextFieldWithComboBox;
692 combo_box.AddState(ax::mojom::State::kEditable);
693 combo_box.SetName(base::UTF16ToUTF8(combo_box_name));
694 combo_box.SetValue(base::UTF16ToUTF8(combo_box_value));
695
696 ui::AXNodeData text2;
697 text2.id = 13;
698 text2.role = ax::mojom::Role::kStaticText;
699 text2.SetName(base::UTF16ToUTF8(text2_name));
700
701 ui::AXNodeData check_box;
702 check_box.id = 14;
703 check_box.role = ax::mojom::Role::kCheckBox;
704 check_box.SetCheckedState(ax::mojom::CheckedState::kTrue);
705 check_box.SetName(base::UTF16ToUTF8(check_box_name));
706 // ARIA checkbox where the name is derived from its inner text.
707 check_box.SetNameFrom(ax::mojom::NameFrom::kContents);
708 check_box.SetValue(base::UTF16ToUTF8(check_box_value));
709
710 ui::AXNodeData button, button_text;
711 button.id = 15;
712 button_text.id = 17;
713 button.role = ax::mojom::Role::kButton;
714 button.SetName(base::UTF16ToUTF8(button_text_name));
715 button.SetNameFrom(ax::mojom::NameFrom::kContents);
716 // A single text child with the same name should be hidden from accessibility
717 // to prevent double speaking.
718 button_text.role = ax::mojom::Role::kStaticText;
719 button_text.SetName(base::UTF16ToUTF8(button_text_name));
720 button.child_ids.push_back(button_text.id);
721
722 ui::AXNodeData link, link_text;
723 link.id = 16;
724 link_text.id = 18;
725 link.role = ax::mojom::Role::kLink;
726 link_text.role = ax::mojom::Role::kStaticText;
727 link_text.SetName(base::UTF16ToUTF8(link_text_name));
728 link.child_ids.push_back(link_text.id);
729
730 ui::AXNodeData root;
731 root.id = 1;
732 root.role = ax::mojom::Role::kRootWebArea;
733 root.child_ids.push_back(text1.id);
734 root.child_ids.push_back(combo_box.id);
735 root.child_ids.push_back(text2.id);
736 root.child_ids.push_back(check_box.id);
737 root.child_ids.push_back(button.id);
738 root.child_ids.push_back(link.id);
739
740 std::unique_ptr<BrowserAccessibilityManager> manager(
741 BrowserAccessibilityManager::Create(
742 MakeAXTreeUpdate(root, text1, combo_box, text2, check_box, button,
743 button_text, link, link_text),
744 test_browser_accessibility_delegate_.get()));
745
746 BrowserAccessibilityComWin* root_obj =
747 ToBrowserAccessibilityWin(manager->GetRoot())->GetCOM();
748
749 LONG text_len;
750 EXPECT_EQ(S_OK, root_obj->get_nCharacters(&text_len));
751 EXPECT_EQ(root_hypertext_len, text_len);
752
753 base::win::ScopedBstr text;
754 EXPECT_EQ(S_OK, root_obj->get_text(0, root_hypertext_len, text.Receive()));
755 EXPECT_STREQ(root_hypertext.c_str(), text.Get());
756 text.Reset();
757
758 LONG hyperlink_count;
759 EXPECT_EQ(S_OK, root_obj->get_nHyperlinks(&hyperlink_count));
760 EXPECT_EQ(4, hyperlink_count);
761
762 Microsoft::WRL::ComPtr<IAccessibleHyperlink> hyperlink;
763 Microsoft::WRL::ComPtr<IAccessibleText> hypertext;
764 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(-1, &hyperlink));
765 EXPECT_EQ(E_INVALIDARG, root_obj->get_hyperlink(4, &hyperlink));
766
767 // Get the text of the combo box.
768 // It should be its value.
769 EXPECT_EQ(S_OK, root_obj->get_hyperlink(0, &hyperlink));
770 EXPECT_EQ(S_OK, hyperlink.As(&hypertext));
771 EXPECT_EQ(S_OK,
772 hypertext->get_text(0, IA2_TEXT_OFFSET_LENGTH, text.Receive()));
773 EXPECT_STREQ(combo_box_value.c_str(), text.Get());
774 text.Reset();
775 hyperlink.Reset();
776 hypertext.Reset();
777
778 // Get the text of the check box.
779 // It should be its name.
780 EXPECT_EQ(S_OK, root_obj->get_hyperlink(1, &hyperlink));
781 EXPECT_EQ(S_OK, hyperlink.As(&hypertext));
782 EXPECT_EQ(S_OK,
783 hypertext->get_text(0, IA2_TEXT_OFFSET_LENGTH, text.Receive()));
784 EXPECT_STREQ(check_box_name.c_str(), text.Get());
785 text.Reset();
786 hyperlink.Reset();
787 hypertext.Reset();
788
789 // Get the text of the button.
790 EXPECT_EQ(S_OK, root_obj->get_hyperlink(2, &hyperlink));
791 EXPECT_EQ(S_OK, hyperlink.As(&hypertext));
792 EXPECT_EQ(S_OK,
793 hypertext->get_text(0, IA2_TEXT_OFFSET_LENGTH, text.Receive()));
794 EXPECT_STREQ(button_text_name.c_str(), text.Get());
795 text.Reset();
796 hyperlink.Reset();
797 hypertext.Reset();
798
799 // Get the text of the link.
800 EXPECT_EQ(S_OK, root_obj->get_hyperlink(3, &hyperlink));
801 EXPECT_EQ(S_OK, hyperlink.As(&hypertext));
802 EXPECT_EQ(S_OK, hypertext->get_text(0, 4, text.Receive()));
803 EXPECT_STREQ(link_text_name.c_str(), text.Get());
804 text.Reset();
805 hyperlink.Reset();
806 hypertext.Reset();
807
808 LONG hyperlink_index;
809 EXPECT_EQ(S_FALSE, root_obj->get_hyperlinkIndex(0, &hyperlink_index));
810 EXPECT_EQ(-1, hyperlink_index);
811 // Invalid arguments should not be modified.
812 hyperlink_index = -2;
813 EXPECT_EQ(E_INVALIDARG,
814 root_obj->get_hyperlinkIndex(root_hypertext_len, &hyperlink_index));
815 EXPECT_EQ(-2, hyperlink_index);
816 EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(14, &hyperlink_index));
817 EXPECT_EQ(0, hyperlink_index);
818 EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(30, &hyperlink_index));
819 EXPECT_EQ(1, hyperlink_index);
820 EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(31, &hyperlink_index));
821 EXPECT_EQ(2, hyperlink_index);
822 EXPECT_EQ(S_OK, root_obj->get_hyperlinkIndex(32, &hyperlink_index));
823 EXPECT_EQ(3, hyperlink_index);
824
825 manager.reset();
826 }
827
TEST_F(BrowserAccessibilityWinTest,TestGetUIADescendants)828 TEST_F(BrowserAccessibilityWinTest, TestGetUIADescendants) {
829 // Set up ax tree with the following structure:
830 //
831 // root___________________________________________________
832 // | | | |
833 // para1____ text3 para2____ (hidden) button
834 // | | | | |
835 // text1 text2 text4 text5 (visible) image
836 ui::AXNodeData text1;
837 text1.id = 111;
838 text1.role = ax::mojom::Role::kStaticText;
839 text1.SetName("One two three.");
840
841 ui::AXNodeData text2;
842 text2.id = 112;
843 text2.role = ax::mojom::Role::kStaticText;
844 text2.SetName("Two three four.");
845
846 ui::AXNodeData text3;
847 text3.id = 113;
848 text3.role = ax::mojom::Role::kStaticText;
849 text3.SetName("Three four five.");
850
851 ui::AXNodeData text4;
852 text4.id = 114;
853 text4.role = ax::mojom::Role::kStaticText;
854 text4.SetName("four five six.");
855 text4.AddState(ax::mojom::State::kIgnored);
856
857 ui::AXNodeData text5;
858 text5.id = 115;
859 text5.role = ax::mojom::Role::kStaticText;
860 text5.SetName("five six seven.");
861
862 ui::AXNodeData image;
863 image.id = 116;
864 image.role = ax::mojom::Role::kImage;
865
866 ui::AXNodeData para1;
867 para1.id = 11;
868 para1.role = ax::mojom::Role::kParagraph;
869 para1.child_ids.push_back(text1.id);
870 para1.child_ids.push_back(text2.id);
871
872 ui::AXNodeData para2;
873 para2.id = 12;
874 para2.role = ax::mojom::Role::kParagraph;
875 para2.child_ids.push_back(text4.id);
876 para2.child_ids.push_back(text5.id);
877 para2.AddState(ax::mojom::State::kIgnored);
878
879 ui::AXNodeData button;
880 button.id = 13;
881 button.role = ax::mojom::Role::kButton;
882 button.child_ids.push_back(image.id);
883
884 ui::AXNodeData root;
885 root.id = 1;
886 root.role = ax::mojom::Role::kRootWebArea;
887 root.child_ids.push_back(para1.id);
888 root.child_ids.push_back(text3.id);
889 root.child_ids.push_back(para2.id);
890 root.child_ids.push_back(button.id);
891
892 std::unique_ptr<BrowserAccessibilityManager> manager(
893 BrowserAccessibilityManager::Create(
894 MakeAXTreeUpdate(root, para1, text1, text2, text3, para2, text4,
895 text5, button, image),
896 test_browser_accessibility_delegate_.get()));
897
898 BrowserAccessibility* root_obj = manager->GetRoot();
899 BrowserAccessibility* para_obj = root_obj->PlatformGetChild(0);
900 BrowserAccessibility* text1_obj = manager->GetFromID(111);
901 BrowserAccessibility* text2_obj = manager->GetFromID(112);
902 BrowserAccessibility* text3_obj = manager->GetFromID(113);
903 BrowserAccessibility* para2_obj = manager->GetFromID(12);
904 BrowserAccessibility* text4_obj = manager->GetFromID(114);
905 BrowserAccessibility* text5_obj = root_obj->PlatformGetChild(2);
906 BrowserAccessibility* button_obj = manager->GetFromID(13);
907 BrowserAccessibility* image_obj = manager->GetFromID(116);
908
909 // Leaf nodes should have no children.
910 std::vector<gfx::NativeViewAccessible> descendants =
911 text1_obj->GetUIADescendants();
912 std::vector<gfx::NativeViewAccessible> expected_descendants = {};
913 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
914
915 descendants = text2_obj->GetUIADescendants();
916 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
917
918 descendants = text3_obj->GetUIADescendants();
919 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
920
921 descendants = text4_obj->GetUIADescendants();
922 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
923
924 descendants = text5_obj->GetUIADescendants();
925 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
926
927 descendants = para2_obj->GetUIADescendants();
928 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
929
930 descendants = image_obj->GetUIADescendants();
931 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
932
933 // Verify that para1 has two children (text1 and tex2).
934 descendants = para_obj->GetUIADescendants();
935 expected_descendants = {text1_obj->GetNativeViewAccessible(),
936 text2_obj->GetNativeViewAccessible()};
937 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
938
939 // Verify that the button hides its child.
940 descendants = button_obj->GetUIADescendants();
941 expected_descendants = {};
942 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
943
944 // Calling GetChildNodeIds on the root should encompass the entire
945 // right and left subtrees (para1, text1, text2, and text3).
946 // para2 and its subtree should be ignored, except for text5. The image should
947 // be ignored, but not the button.
948 LOG(INFO) << "HERE";
949
950 descendants = root_obj->GetUIADescendants();
951 expected_descendants = {para_obj->GetNativeViewAccessible(),
952 text1_obj->GetNativeViewAccessible(),
953 text2_obj->GetNativeViewAccessible(),
954 text3_obj->GetNativeViewAccessible(),
955 text5_obj->GetNativeViewAccessible(),
956 button_obj->GetNativeViewAccessible()};
957 EXPECT_NATIVE_VIEW_ACCESSIBLE_VECTOR_EQ(descendants, expected_descendants);
958
959 manager.reset();
960 }
961
TEST_F(BrowserAccessibilityWinTest,TestCreateEmptyDocument)962 TEST_F(BrowserAccessibilityWinTest, TestCreateEmptyDocument) {
963 // Try creating an empty document with busy state. Readonly is
964 // set automatically.
965 std::unique_ptr<BrowserAccessibilityManager> manager(
966 new BrowserAccessibilityManagerWin(
967 BrowserAccessibilityManagerWin::GetEmptyDocument(),
968 test_browser_accessibility_delegate_.get()));
969
970 // Verify the root is as we expect by default.
971 BrowserAccessibility* root = manager->GetRoot();
972 EXPECT_EQ(1, root->GetId());
973 EXPECT_EQ(ax::mojom::Role::kRootWebArea, root->GetRole());
974 EXPECT_EQ(0, root->GetState());
975 EXPECT_EQ(true, root->GetBoolAttribute(ax::mojom::BoolAttribute::kBusy));
976
977 // Tree with a child textfield.
978 ui::AXNodeData tree1_1;
979 tree1_1.id = 1;
980 tree1_1.role = ax::mojom::Role::kRootWebArea;
981 tree1_1.child_ids.push_back(2);
982
983 ui::AXNodeData tree1_2;
984 tree1_2.id = 2;
985 tree1_2.role = ax::mojom::Role::kTextField;
986
987 // Process a load complete.
988 AXEventNotificationDetails event_bundle;
989 event_bundle.updates.resize(1);
990 event_bundle.updates[0].node_id_to_clear = root->GetId();
991 event_bundle.updates[0].root_id = tree1_1.id;
992 event_bundle.updates[0].nodes.push_back(tree1_1);
993 event_bundle.updates[0].nodes.push_back(tree1_2);
994 ASSERT_TRUE(manager->OnAccessibilityEvents(event_bundle));
995
996 // The root should have been cleared,not replaced, because in the former case
997 // this could cause multiple focus and load complete events.
998 EXPECT_EQ(root, manager->GetRoot());
999
1000 BrowserAccessibility* acc1_2 = manager->GetFromID(2);
1001 EXPECT_EQ(ax::mojom::Role::kTextField, acc1_2->GetRole());
1002 EXPECT_EQ(2, acc1_2->GetId());
1003
1004 // Tree with a child button.
1005 ui::AXNodeData tree2_1;
1006 tree2_1.id = 1;
1007 tree2_1.role = ax::mojom::Role::kRootWebArea;
1008 tree2_1.child_ids.push_back(3);
1009
1010 ui::AXNodeData tree2_2;
1011 tree2_2.id = 3;
1012 tree2_2.role = ax::mojom::Role::kButton;
1013
1014 event_bundle.updates[0].nodes.clear();
1015 event_bundle.updates[0].node_id_to_clear = tree1_1.id;
1016 event_bundle.updates[0].root_id = tree2_1.id;
1017 event_bundle.updates[0].nodes.push_back(tree2_1);
1018 event_bundle.updates[0].nodes.push_back(tree2_2);
1019
1020 // Unserialize the new tree update.
1021 ASSERT_TRUE(manager->OnAccessibilityEvents(event_bundle));
1022
1023 // Verify that the root has been cleared, not replaced.
1024 EXPECT_EQ(root, manager->GetRoot());
1025
1026 BrowserAccessibility* acc2_2 = manager->GetFromID(3);
1027 EXPECT_EQ(ax::mojom::Role::kButton, acc2_2->GetRole());
1028 EXPECT_EQ(3, acc2_2->GetId());
1029
1030 // Ensure we properly cleaned up.
1031 manager.reset();
1032 }
1033
GetUniqueId(BrowserAccessibility * accessibility)1034 int32_t GetUniqueId(BrowserAccessibility* accessibility) {
1035 BrowserAccessibilityWin* win_root = ToBrowserAccessibilityWin(accessibility);
1036 return win_root->GetCOM()->GetUniqueId();
1037 }
1038
1039 // This is a regression test for a bug where the initial empty document
1040 // loaded by a BrowserAccessibilityManagerWin couldn't be looked up by
1041 // its UniqueIDWin, because the AX Tree was loaded in
1042 // BrowserAccessibilityManager code before BrowserAccessibilityManagerWin
1043 // was initialized.
TEST_F(BrowserAccessibilityWinTest,EmptyDocHasUniqueIdWin)1044 TEST_F(BrowserAccessibilityWinTest, EmptyDocHasUniqueIdWin) {
1045 std::unique_ptr<BrowserAccessibilityManagerWin> manager(
1046 new BrowserAccessibilityManagerWin(
1047 BrowserAccessibilityManagerWin::GetEmptyDocument(),
1048 test_browser_accessibility_delegate_.get()));
1049
1050 // Verify the root is as we expect by default.
1051 BrowserAccessibility* root = manager->GetRoot();
1052 EXPECT_EQ(1, root->GetId());
1053 EXPECT_EQ(ax::mojom::Role::kRootWebArea, root->GetRole());
1054 EXPECT_EQ(0, root->GetState());
1055 EXPECT_EQ(true, root->GetBoolAttribute(ax::mojom::BoolAttribute::kBusy));
1056
1057 BrowserAccessibilityWin* win_root = ToBrowserAccessibilityWin(root);
1058
1059 ui::AXPlatformNode* node = static_cast<ui::AXPlatformNode*>(
1060 ui::AXPlatformNodeWin::GetFromUniqueId(GetUniqueId(win_root)));
1061
1062 ui::AXPlatformNode* other_node =
1063 static_cast<ui::AXPlatformNode*>(win_root->GetCOM());
1064 ASSERT_EQ(node, other_node);
1065 }
1066
TEST_F(BrowserAccessibilityWinTest,TestIA2Attributes)1067 TEST_F(BrowserAccessibilityWinTest, TestIA2Attributes) {
1068 ui::AXNodeData pseudo_before;
1069 pseudo_before.id = 2;
1070 pseudo_before.role = ax::mojom::Role::kGenericContainer;
1071 pseudo_before.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
1072 "<pseudo:before>");
1073 pseudo_before.AddStringAttribute(ax::mojom::StringAttribute::kDisplay,
1074 "none");
1075
1076 ui::AXNodeData checkbox;
1077 checkbox.id = 3;
1078 checkbox.role = ax::mojom::Role::kCheckBox;
1079 checkbox.SetCheckedState(ax::mojom::CheckedState::kTrue);
1080 checkbox.SetName("Checkbox");
1081
1082 ui::AXNodeData root;
1083 root.id = 1;
1084 root.role = ax::mojom::Role::kRootWebArea;
1085 root.AddState(ax::mojom::State::kFocusable);
1086 root.SetName("Document");
1087 root.child_ids.push_back(2);
1088 root.child_ids.push_back(3);
1089
1090 std::unique_ptr<BrowserAccessibilityManager> manager(
1091 BrowserAccessibilityManager::Create(
1092 MakeAXTreeUpdate(root, pseudo_before, checkbox),
1093 test_browser_accessibility_delegate_.get()));
1094
1095 ASSERT_NE(nullptr, manager->GetRoot());
1096 BrowserAccessibilityWin* root_accessible =
1097 ToBrowserAccessibilityWin(manager->GetRoot());
1098 ASSERT_NE(nullptr, root_accessible);
1099 ASSERT_EQ(2U, root_accessible->PlatformChildCount());
1100
1101 BrowserAccessibilityWin* pseudo_accessible =
1102 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(0));
1103 ASSERT_NE(nullptr, pseudo_accessible);
1104
1105 base::win::ScopedBstr attributes;
1106 HRESULT hr =
1107 pseudo_accessible->GetCOM()->get_attributes(attributes.Receive());
1108 EXPECT_EQ(S_OK, hr);
1109 EXPECT_NE(nullptr, attributes.Get());
1110 std::wstring attributes_str(attributes.Get(), attributes.Length());
1111 EXPECT_EQ(L"display:none;tag:<pseudo\\:before>;", attributes_str);
1112
1113 BrowserAccessibilityWin* checkbox_accessible =
1114 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(1));
1115 ASSERT_NE(nullptr, checkbox_accessible);
1116
1117 attributes.Reset();
1118 hr = checkbox_accessible->GetCOM()->get_attributes(attributes.Receive());
1119 EXPECT_EQ(S_OK, hr);
1120 EXPECT_NE(nullptr, attributes.Get());
1121 attributes_str = std::wstring(attributes.Get(), attributes.Length());
1122 EXPECT_EQ(L"checkable:true;explicit-name:true;", attributes_str);
1123
1124 manager.reset();
1125 }
1126
TEST_F(BrowserAccessibilityWinTest,TestValueAttributeInTextControls)1127 TEST_F(BrowserAccessibilityWinTest, TestValueAttributeInTextControls) {
1128 ui::AXNodeData root;
1129 root.id = 1;
1130 root.role = ax::mojom::Role::kRootWebArea;
1131 root.AddState(ax::mojom::State::kFocusable);
1132
1133 ui::AXNodeData combo_box, combo_box_text;
1134 combo_box.id = 2;
1135 combo_box_text.id = 3;
1136 combo_box.role = ax::mojom::Role::kTextFieldWithComboBox;
1137 combo_box_text.role = ax::mojom::Role::kStaticText;
1138 combo_box.SetName("Combo box:");
1139 combo_box_text.SetName("Combo box text");
1140 combo_box.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
1141 combo_box.AddState(ax::mojom::State::kEditable);
1142 combo_box.AddState(ax::mojom::State::kRichlyEditable);
1143 combo_box.AddState(ax::mojom::State::kFocusable);
1144 combo_box_text.AddState(ax::mojom::State::kEditable);
1145 combo_box_text.AddState(ax::mojom::State::kRichlyEditable);
1146 combo_box.child_ids.push_back(combo_box_text.id);
1147
1148 ui::AXNodeData search_box, search_box_text, new_line;
1149 search_box.id = 4;
1150 search_box_text.id = 5;
1151 new_line.id = 6;
1152 search_box.role = ax::mojom::Role::kSearchBox;
1153 search_box_text.role = ax::mojom::Role::kStaticText;
1154 new_line.role = ax::mojom::Role::kLineBreak;
1155 search_box.SetName("Search for:");
1156 search_box_text.SetName("Search box text");
1157 new_line.SetName("\n");
1158 search_box.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
1159 search_box.AddState(ax::mojom::State::kEditable);
1160 search_box.AddState(ax::mojom::State::kRichlyEditable);
1161 search_box.AddState(ax::mojom::State::kFocusable);
1162 search_box_text.AddState(ax::mojom::State::kEditable);
1163 search_box_text.AddState(ax::mojom::State::kRichlyEditable);
1164 new_line.AddState(ax::mojom::State::kEditable);
1165 new_line.AddState(ax::mojom::State::kRichlyEditable);
1166 search_box.child_ids.push_back(search_box_text.id);
1167 search_box.child_ids.push_back(new_line.id);
1168
1169 ui::AXNodeData text_field;
1170 text_field.id = 7;
1171 text_field.role = ax::mojom::Role::kTextField;
1172 text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
1173 text_field.AddState(ax::mojom::State::kEditable);
1174 text_field.AddState(ax::mojom::State::kFocusable);
1175 text_field.SetValue("Text field text");
1176
1177 ui::AXNodeData link, link_text;
1178 link.id = 8;
1179 link_text.id = 9;
1180 link.role = ax::mojom::Role::kLink;
1181 link_text.role = ax::mojom::Role::kStaticText;
1182 link_text.SetName("Link text");
1183 link.child_ids.push_back(link_text.id);
1184
1185 ui::AXNodeData slider, slider_text;
1186 slider.id = 10;
1187 slider_text.id = 11;
1188 slider.role = ax::mojom::Role::kSlider;
1189 slider_text.role = ax::mojom::Role::kStaticText;
1190 slider.AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, 5.0F);
1191 slider_text.SetName("Slider text");
1192 slider.child_ids.push_back(slider_text.id);
1193
1194 root.child_ids.push_back(2); // Combo box.
1195 root.child_ids.push_back(4); // Search box.
1196 root.child_ids.push_back(7); // Text field.
1197 root.child_ids.push_back(8); // Link.
1198 root.child_ids.push_back(10); // Slider.
1199
1200 std::unique_ptr<BrowserAccessibilityManager> manager(
1201 BrowserAccessibilityManager::Create(
1202 MakeAXTreeUpdate(root, combo_box, combo_box_text, search_box,
1203 search_box_text, new_line, text_field, link,
1204 link_text, slider, slider_text),
1205 test_browser_accessibility_delegate_.get()));
1206
1207 ASSERT_NE(nullptr, manager->GetRoot());
1208 BrowserAccessibilityWin* root_accessible =
1209 ToBrowserAccessibilityWin(manager->GetRoot());
1210 ASSERT_NE(nullptr, root_accessible);
1211 ASSERT_EQ(5U, root_accessible->PlatformChildCount());
1212
1213 BrowserAccessibilityWin* combo_box_accessible =
1214 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(0));
1215 ASSERT_NE(nullptr, combo_box_accessible);
1216 manager->SetFocusLocallyForTesting(combo_box_accessible);
1217 ASSERT_EQ(combo_box_accessible,
1218 ToBrowserAccessibilityWin(manager->GetFocus()));
1219 BrowserAccessibilityWin* search_box_accessible =
1220 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(1));
1221 ASSERT_NE(nullptr, search_box_accessible);
1222 BrowserAccessibilityWin* text_field_accessible =
1223 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(2));
1224 ASSERT_NE(nullptr, text_field_accessible);
1225 BrowserAccessibilityWin* link_accessible =
1226 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(3));
1227 ASSERT_NE(nullptr, link_accessible);
1228 BrowserAccessibilityWin* slider_accessible =
1229 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(4));
1230 ASSERT_NE(nullptr, slider_accessible);
1231
1232 base::win::ScopedVariant childid_self(CHILDID_SELF);
1233 base::win::ScopedVariant childid_slider(5);
1234 base::win::ScopedBstr value;
1235
1236 HRESULT hr = combo_box_accessible->GetCOM()->get_accValue(childid_self,
1237 value.Receive());
1238 EXPECT_EQ(S_OK, hr);
1239 EXPECT_STREQ(L"Combo box text", value.Get());
1240 value.Reset();
1241 hr = search_box_accessible->GetCOM()->get_accValue(childid_self,
1242 value.Receive());
1243 EXPECT_EQ(S_OK, hr);
1244 EXPECT_STREQ(L"Search box text\n", value.Get());
1245 value.Reset();
1246 hr = text_field_accessible->GetCOM()->get_accValue(childid_self,
1247 value.Receive());
1248 EXPECT_EQ(S_OK, hr);
1249 EXPECT_STREQ(L"Text field text", value.Get());
1250 value.Reset();
1251
1252 // Other controls, such as links, should not use their inner text as their
1253 // value. Only text entry controls.
1254 hr = link_accessible->GetCOM()->get_accValue(childid_self, value.Receive());
1255 EXPECT_EQ(S_OK, hr);
1256 EXPECT_EQ(0u, value.Length());
1257 value.Reset();
1258
1259 // Sliders and other range controls should expose their current value and not
1260 // their inner text.
1261 // Also, try accessing the slider via its child number instead of directly.
1262 hr = root_accessible->GetCOM()->get_accValue(childid_slider, value.Receive());
1263 EXPECT_EQ(S_OK, hr);
1264 EXPECT_STREQ(L"5", value.Get());
1265 value.Reset();
1266
1267 manager.reset();
1268 }
1269
TEST_F(BrowserAccessibilityWinTest,TestWordBoundariesInTextControls)1270 TEST_F(BrowserAccessibilityWinTest, TestWordBoundariesInTextControls) {
1271 const base::string16 line1(L"This is a very LONG line of text that ");
1272 const base::string16 line2(L"should wrap on more than one lines ");
1273 const base::string16 text(line1 + line2);
1274
1275 std::vector<int32_t> line1_word_starts;
1276 line1_word_starts.push_back(0);
1277 line1_word_starts.push_back(5);
1278 line1_word_starts.push_back(8);
1279 line1_word_starts.push_back(10);
1280 line1_word_starts.push_back(15);
1281 line1_word_starts.push_back(20);
1282 line1_word_starts.push_back(25);
1283 line1_word_starts.push_back(28);
1284 line1_word_starts.push_back(33);
1285
1286 std::vector<int32_t> line2_word_starts;
1287 line2_word_starts.push_back(0);
1288 line2_word_starts.push_back(7);
1289 line2_word_starts.push_back(12);
1290 line2_word_starts.push_back(15);
1291 line2_word_starts.push_back(20);
1292 line2_word_starts.push_back(25);
1293 line2_word_starts.push_back(29);
1294
1295 ui::AXNodeData root;
1296 root.id = 1;
1297 root.role = ax::mojom::Role::kRootWebArea;
1298 root.AddState(ax::mojom::State::kFocusable);
1299
1300 ui::AXNodeData textarea, textarea_div, textarea_text;
1301 textarea.id = 2;
1302 textarea_div.id = 3;
1303 textarea_text.id = 4;
1304 textarea.role = ax::mojom::Role::kTextField;
1305 textarea_div.role = ax::mojom::Role::kGenericContainer;
1306 textarea_text.role = ax::mojom::Role::kStaticText;
1307 textarea.AddState(ax::mojom::State::kEditable);
1308 textarea.AddState(ax::mojom::State::kFocusable);
1309 textarea.AddState(ax::mojom::State::kMultiline);
1310 textarea_div.AddState(ax::mojom::State::kEditable);
1311 textarea_text.AddState(ax::mojom::State::kEditable);
1312 textarea.SetValue(base::UTF16ToUTF8(text));
1313 textarea_text.SetName(base::UTF16ToUTF8(text));
1314 textarea.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "textarea");
1315 textarea.child_ids.push_back(textarea_div.id);
1316 textarea_div.child_ids.push_back(textarea_text.id);
1317
1318 ui::AXNodeData textarea_line1, textarea_line2;
1319 textarea_line1.id = 5;
1320 textarea_line2.id = 6;
1321 textarea_line1.role = ax::mojom::Role::kInlineTextBox;
1322 textarea_line2.role = ax::mojom::Role::kInlineTextBox;
1323 textarea_line1.AddState(ax::mojom::State::kEditable);
1324 textarea_line2.AddState(ax::mojom::State::kEditable);
1325 textarea_line1.SetName(base::UTF16ToUTF8(line1));
1326 textarea_line2.SetName(base::UTF16ToUTF8(line2));
1327 textarea_line1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
1328 line1_word_starts);
1329 textarea_line2.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
1330 line2_word_starts);
1331 textarea_text.child_ids.push_back(textarea_line1.id);
1332 textarea_text.child_ids.push_back(textarea_line2.id);
1333
1334 ui::AXNodeData text_field, text_field_div, text_field_text;
1335 text_field.id = 7;
1336 text_field_div.id = 8;
1337 text_field_text.id = 9;
1338 text_field.role = ax::mojom::Role::kTextField;
1339 text_field_div.role = ax::mojom::Role::kGenericContainer;
1340 text_field_text.role = ax::mojom::Role::kStaticText;
1341 text_field.AddState(ax::mojom::State::kEditable);
1342 text_field.AddState(ax::mojom::State::kFocusable);
1343 text_field_div.AddState(ax::mojom::State::kEditable);
1344 text_field_text.AddState(ax::mojom::State::kEditable);
1345 text_field.SetValue(base::UTF16ToUTF8(line1));
1346 text_field_text.SetName(base::UTF16ToUTF8(line1));
1347 text_field.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "input");
1348 text_field.html_attributes.push_back(std::make_pair("type", "text"));
1349 text_field.child_ids.push_back(text_field_div.id);
1350 text_field_div.child_ids.push_back(text_field_text.id);
1351
1352 ui::AXNodeData text_field_line;
1353 text_field_line.id = 10;
1354 text_field_line.role = ax::mojom::Role::kInlineTextBox;
1355 text_field_line.AddState(ax::mojom::State::kEditable);
1356 text_field_line.SetName(base::UTF16ToUTF8(line1));
1357 text_field_line.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
1358 line1_word_starts);
1359 text_field_text.child_ids.push_back(text_field_line.id);
1360
1361 root.child_ids.push_back(2); // Textarea.
1362 root.child_ids.push_back(7); // Text field.
1363
1364 std::unique_ptr<BrowserAccessibilityManager> manager(
1365 BrowserAccessibilityManager::Create(
1366 MakeAXTreeUpdate(root, textarea, textarea_div, textarea_text,
1367 textarea_line1, textarea_line2, text_field,
1368 text_field_div, text_field_text, text_field_line),
1369 test_browser_accessibility_delegate_.get()));
1370
1371 ASSERT_NE(nullptr, manager->GetRoot());
1372 BrowserAccessibilityWin* root_accessible =
1373 ToBrowserAccessibilityWin(manager->GetRoot());
1374 ASSERT_NE(nullptr, root_accessible);
1375 ASSERT_EQ(2U, root_accessible->PlatformChildCount());
1376
1377 BrowserAccessibilityWin* textarea_accessible =
1378 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(0));
1379 ASSERT_NE(nullptr, textarea_accessible);
1380 BrowserAccessibilityWin* text_field_accessible =
1381 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(1));
1382 ASSERT_NE(nullptr, text_field_accessible);
1383
1384 Microsoft::WRL::ComPtr<IAccessibleText> textarea_object;
1385 EXPECT_HRESULT_SUCCEEDED(textarea_accessible->GetCOM()->QueryInterface(
1386 IID_PPV_ARGS(&textarea_object)));
1387 Microsoft::WRL::ComPtr<IAccessibleText> text_field_object;
1388 EXPECT_HRESULT_SUCCEEDED(text_field_accessible->GetCOM()->QueryInterface(
1389 IID_PPV_ARGS(&text_field_object)));
1390
1391 LONG offset = 0;
1392 while (offset < static_cast<LONG>(text.length())) {
1393 LONG start, end;
1394 base::win::ScopedBstr word;
1395 EXPECT_EQ(S_OK,
1396 textarea_object->get_textAtOffset(offset, IA2_TEXT_BOUNDARY_WORD,
1397 &start, &end, word.Receive()));
1398 EXPECT_EQ(offset, start);
1399 EXPECT_LT(offset, end);
1400 LONG space_offset = static_cast<LONG>(text.find(' ', offset));
1401 EXPECT_EQ(space_offset + 1, end);
1402 LONG length = end - start;
1403 EXPECT_STREQ(text.substr(start, length).c_str(), word.Get());
1404 word.Reset();
1405 offset = end;
1406 }
1407
1408 offset = 0;
1409 while (offset < static_cast<LONG>(line1.length())) {
1410 LONG start, end;
1411 base::win::ScopedBstr word;
1412 EXPECT_EQ(S_OK, text_field_object->get_textAtOffset(
1413 offset, IA2_TEXT_BOUNDARY_WORD, &start, &end,
1414 word.Receive()));
1415 EXPECT_EQ(offset, start);
1416 EXPECT_LT(offset, end);
1417 LONG space_offset = static_cast<LONG>(line1.find(' ', offset));
1418 EXPECT_EQ(space_offset + 1, end);
1419 LONG length = end - start;
1420 EXPECT_STREQ(text.substr(start, length).c_str(), word.Get());
1421 word.Reset();
1422 offset = end;
1423 }
1424
1425 textarea_object.Reset();
1426 text_field_object.Reset();
1427
1428 manager.reset();
1429 }
1430
TEST_F(BrowserAccessibilityWinTest,TextBoundariesOnlyEmbeddedObjectsNoCrash)1431 TEST_F(BrowserAccessibilityWinTest, TextBoundariesOnlyEmbeddedObjectsNoCrash) {
1432 // Update the tree structure to test get_textAtOffset from an
1433 // embedded object that has no text, only an embedded object child.
1434 //
1435 // +-1 root_data
1436 // +-2 menu_data
1437 // | +-3 button_1_data
1438 // | +-4 button_2_data
1439 // +-5 static_text_data "after"
1440 //
1441 ui::AXNodeData root_data;
1442 root_data.id = 1;
1443 root_data.role = ax::mojom::Role::kRootWebArea;
1444
1445 ui::AXNodeData menu_data;
1446 menu_data.id = 2;
1447 menu_data.role = ax::mojom::Role::kMenu;
1448
1449 ui::AXNodeData button_1_data;
1450 button_1_data.id = 3;
1451 button_1_data.role = ax::mojom::Role::kButton;
1452
1453 ui::AXNodeData button_2_data;
1454 button_2_data.id = 4;
1455 button_2_data.role = ax::mojom::Role::kButton;
1456
1457 ui::AXNodeData static_text_data;
1458 static_text_data.id = 5;
1459 static_text_data.role = ax::mojom::Role::kStaticText;
1460 static_text_data.SetName("after");
1461
1462 root_data.child_ids = {menu_data.id, static_text_data.id};
1463 menu_data.child_ids = {button_1_data.id, button_2_data.id};
1464
1465 std::unique_ptr<BrowserAccessibilityManager> manager(
1466 BrowserAccessibilityManager::Create(
1467 MakeAXTreeUpdate(root_data, menu_data, button_1_data, button_2_data,
1468 static_text_data),
1469 test_browser_accessibility_delegate_.get()));
1470
1471 ASSERT_NE(nullptr, manager->GetRoot());
1472 BrowserAccessibilityWin* root_accessible =
1473 ToBrowserAccessibilityWin(manager->GetRoot());
1474 ASSERT_NE(nullptr, root_accessible);
1475 ASSERT_EQ(2U, root_accessible->PlatformChildCount());
1476
1477 BrowserAccessibilityComWin* menu_accessible_com =
1478 ToBrowserAccessibilityComWin(root_accessible->PlatformGetChild(0));
1479 ASSERT_NE(nullptr, menu_accessible_com);
1480 ASSERT_EQ(ax::mojom::Role::kMenu, menu_accessible_com->GetData().role);
1481
1482 // TODO(crbug.com/1039528): This should not have 2 embedded object characters.
1483 {
1484 const std::array<base::char16, 2> pieces = {
1485 ui::AXPlatformNodeBase::kEmbeddedCharacter,
1486 ui::AXPlatformNodeBase::kEmbeddedCharacter};
1487 const base::string16 expect(pieces.cbegin(), pieces.cend());
1488 EXPECT_IA2_TEXT_AT_OFFSET(menu_accessible_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1489 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/2,
1490 /*text=*/expect.c_str());
1491 }
1492 }
1493
TEST_F(BrowserAccessibilityWinTest,TestTextBoundariesEmbeddedCharacterText)1494 TEST_F(BrowserAccessibilityWinTest, TestTextBoundariesEmbeddedCharacterText) {
1495 // Update the tree structure to test empty leaf text positions.
1496 //
1497 // +-1 root_data
1498 // +-2 body_data
1499 // +-3 static_text_1_data "before"
1500 // | +-4 inline_text_1_data "before"
1501 // +-5 menu_data_1
1502 // | +-6 button_data
1503 // | +-7 button_leaf_container_data
1504 // | +-8 button_leaf_svg_data
1505 // +-9 menu_data_2
1506 // +-10 static_text_2_data "after"
1507 // | +-11 inline_text_2_data "after"
1508 // +-12 static_text_3_data "tail"
1509 // | +-13 inline_text_3_data "tail"
1510 //
1511 ui::AXNodeData root_data;
1512 root_data.id = 1;
1513 root_data.role = ax::mojom::Role::kRootWebArea;
1514
1515 ui::AXNodeData body_data;
1516 body_data.id = 2;
1517 body_data.role = ax::mojom::Role::kGenericContainer;
1518
1519 ui::AXNodeData static_text_1_data;
1520 static_text_1_data.id = 3;
1521 static_text_1_data.role = ax::mojom::Role::kStaticText;
1522 static_text_1_data.SetName("before");
1523
1524 ui::AXNodeData inline_text_1_data;
1525 inline_text_1_data.id = 4;
1526 inline_text_1_data.role = ax::mojom::Role::kInlineTextBox;
1527 inline_text_1_data.SetName("before");
1528
1529 ui::AXNodeData menu_data_1;
1530 menu_data_1.id = 5;
1531 menu_data_1.role = ax::mojom::Role::kMenu;
1532
1533 ui::AXNodeData button_data;
1534 button_data.id = 6;
1535 button_data.role = ax::mojom::Role::kButton;
1536
1537 ui::AXNodeData button_leaf_container_data;
1538 button_leaf_container_data.id = 7;
1539 button_leaf_container_data.role = ax::mojom::Role::kGenericContainer;
1540
1541 ui::AXNodeData button_leaf_svg_data;
1542 button_leaf_svg_data.id = 8;
1543 button_leaf_svg_data.role = ax::mojom::Role::kSvgRoot;
1544
1545 ui::AXNodeData menu_data_2;
1546 menu_data_2.id = 9;
1547 menu_data_2.role = ax::mojom::Role::kMenu;
1548
1549 ui::AXNodeData static_text_2_data;
1550 static_text_2_data.id = 10;
1551 static_text_2_data.role = ax::mojom::Role::kStaticText;
1552 static_text_2_data.SetName("after");
1553
1554 ui::AXNodeData inline_text_2_data;
1555 inline_text_2_data.id = 11;
1556 inline_text_2_data.role = ax::mojom::Role::kInlineTextBox;
1557 inline_text_2_data.SetName("after");
1558
1559 ui::AXNodeData static_text_3_data;
1560 static_text_3_data.id = 12;
1561 static_text_3_data.role = ax::mojom::Role::kStaticText;
1562 static_text_3_data.SetName("tail");
1563
1564 ui::AXNodeData inline_text_3_data;
1565 inline_text_3_data.id = 13;
1566 inline_text_3_data.role = ax::mojom::Role::kInlineTextBox;
1567 inline_text_3_data.SetName("tail");
1568
1569 root_data.child_ids = {body_data.id};
1570 body_data.child_ids = {static_text_1_data.id, menu_data_1.id, menu_data_2.id,
1571 static_text_2_data.id, static_text_3_data.id};
1572 menu_data_1.child_ids = {button_data.id};
1573 button_data.child_ids = {button_leaf_container_data.id,
1574 button_leaf_svg_data.id};
1575 static_text_1_data.child_ids = {inline_text_1_data.id};
1576 static_text_2_data.child_ids = {inline_text_2_data.id};
1577 static_text_3_data.child_ids = {inline_text_3_data.id};
1578
1579 ui::AXTreeUpdate update;
1580 ui::AXTreeData tree_data;
1581 tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
1582 tree_data.focused_tree_id = tree_data.tree_id;
1583 update.tree_data = tree_data;
1584 update.has_tree_data = true;
1585 update.root_id = root_data.id;
1586 update.nodes = {root_data,
1587 body_data,
1588 static_text_1_data,
1589 inline_text_1_data,
1590 menu_data_1,
1591 button_data,
1592 button_leaf_container_data,
1593 button_leaf_svg_data,
1594 menu_data_2,
1595 static_text_2_data,
1596 inline_text_2_data,
1597 static_text_3_data,
1598 inline_text_3_data};
1599
1600 std::unique_ptr<BrowserAccessibilityManager> manager(
1601 BrowserAccessibilityManager::Create(
1602 update, test_browser_accessibility_delegate_.get()));
1603
1604 ASSERT_NE(nullptr, manager->GetRoot());
1605 BrowserAccessibilityWin* root_accessible =
1606 ToBrowserAccessibilityWin(manager->GetRoot());
1607 ASSERT_NE(nullptr, root_accessible);
1608 ASSERT_EQ(1U, root_accessible->PlatformChildCount());
1609
1610 BrowserAccessibilityWin* body_accessible =
1611 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(0));
1612 ASSERT_NE(nullptr, body_accessible);
1613 ASSERT_EQ(5U, body_accessible->PlatformChildCount());
1614 BrowserAccessibilityComWin* body_accessible_com = body_accessible->GetCOM();
1615 ASSERT_NE(nullptr, body_accessible_com);
1616
1617 BrowserAccessibilityComWin* static_text_1_com =
1618 ToBrowserAccessibilityWin(body_accessible->PlatformGetChild(0))->GetCOM();
1619 ASSERT_NE(nullptr, static_text_1_com);
1620 ASSERT_EQ(ax::mojom::Role::kStaticText, static_text_1_com->GetData().role);
1621
1622 BrowserAccessibilityComWin* menu_1_accessible_com =
1623 ToBrowserAccessibilityWin(body_accessible->PlatformGetChild(1))->GetCOM();
1624 ASSERT_NE(nullptr, menu_1_accessible_com);
1625 ASSERT_EQ(ax::mojom::Role::kMenu, menu_1_accessible_com->GetData().role);
1626
1627 BrowserAccessibilityComWin* menu_2_accessible_com =
1628 ToBrowserAccessibilityWin(body_accessible->PlatformGetChild(2))->GetCOM();
1629 ASSERT_NE(nullptr, menu_2_accessible_com);
1630 ASSERT_EQ(ax::mojom::Role::kMenu, menu_2_accessible_com->GetData().role);
1631
1632 BrowserAccessibilityComWin* static_text_2_com =
1633 ToBrowserAccessibilityWin(body_accessible->PlatformGetChild(3))->GetCOM();
1634 ASSERT_NE(nullptr, static_text_2_com);
1635 ASSERT_EQ(ax::mojom::Role::kStaticText, static_text_2_com->GetData().role);
1636
1637 BrowserAccessibilityComWin* static_text_3_com =
1638 ToBrowserAccessibilityWin(body_accessible->PlatformGetChild(4))->GetCOM();
1639 ASSERT_NE(nullptr, static_text_3_com);
1640 ASSERT_EQ(ax::mojom::Role::kStaticText, static_text_3_com->GetData().role);
1641
1642 // L"<b>efore" [obj] [obj] L"after" L"tail"
1643 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1644 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/1,
1645 /*text=*/L"b");
1646
1647 // L"bef<o>re" [obj] [obj] L"after" L"tail"
1648 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 3, IA2_TEXT_BOUNDARY_CHAR,
1649 /*expected_hr=*/S_OK, /*start=*/3, /*end=*/4,
1650 /*text=*/L"o");
1651
1652 // L"befor<e>" [obj] [obj] L"after" L"tail"
1653 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 5, IA2_TEXT_BOUNDARY_CHAR,
1654 /*expected_hr=*/S_OK, /*start=*/5, /*end=*/6,
1655 /*text=*/L"e");
1656
1657 // L"before" <[obj]> [obj] L"after" L"tail"
1658 // TODO(crbug.com/1039528): This should not include multiple characters.
1659 {
1660 const std::array<base::char16, 3> pieces = {
1661 ui::AXPlatformNodeBase::kEmbeddedCharacter,
1662 ui::AXPlatformNodeBase::kEmbeddedCharacter, L'a'};
1663 const base::string16 expect(pieces.cbegin(), pieces.cend());
1664 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 6, IA2_TEXT_BOUNDARY_CHAR,
1665 /*expected_hr=*/S_OK, /*start=*/6, /*end=*/9,
1666 /*text=*/
1667 expect.c_str());
1668 }
1669
1670 // L"before" [obj] <[obj]> L"after" L"tail"
1671 // TODO(crbug.com/1039528): This should not include multiple characters.
1672 {
1673 const std::array<base::char16, 2> pieces = {
1674 ui::AXPlatformNodeBase::kEmbeddedCharacter, L'a'};
1675 const base::string16 expect(pieces.cbegin(), pieces.cend());
1676 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 7, IA2_TEXT_BOUNDARY_CHAR,
1677 /*expected_hr=*/S_OK, /*start=*/7, /*end=*/9,
1678 /*text=*/
1679 expect.c_str());
1680 }
1681
1682 // L"before" [obj] [obj] L"<a>fter" L"tail"
1683 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 8, IA2_TEXT_BOUNDARY_CHAR,
1684 /*expected_hr=*/S_OK, /*start=*/8, /*end=*/9,
1685 /*text=*/L"a");
1686
1687 // L"before" [obj] [obj] L"a<f>ter" L"tail"
1688 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 9, IA2_TEXT_BOUNDARY_CHAR,
1689 /*expected_hr=*/S_OK, /*start=*/9, /*end=*/10,
1690 /*text=*/L"f");
1691
1692 // L"before" [obj] [obj] L"afte<r>" L"tail"
1693 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 12, IA2_TEXT_BOUNDARY_CHAR,
1694 /*expected_hr=*/S_OK, /*start=*/12, /*end=*/13,
1695 /*text=*/L"r");
1696
1697 // L"before" [obj] [obj] L"after" L"<t>ail"
1698 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 13, IA2_TEXT_BOUNDARY_CHAR,
1699 /*expected_hr=*/S_OK, /*start=*/13, /*end=*/14,
1700 /*text=*/L"t");
1701
1702 // L"before" [obj] [obj] L"after" L"ta<i>l"
1703 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 15, IA2_TEXT_BOUNDARY_CHAR,
1704 /*expected_hr=*/S_OK, /*start=*/15, /*end=*/16,
1705 /*text=*/L"i");
1706
1707 // L"before" [obj] [obj] L"after" L"tai<l>"
1708 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 16, IA2_TEXT_BOUNDARY_CHAR,
1709 /*expected_hr=*/S_OK, /*start=*/16, /*end=*/17,
1710 /*text=*/L"l");
1711
1712 // L"before" [obj] [obj] L"after" L"tail<>"
1713 EXPECT_IA2_TEXT_AT_OFFSET(body_accessible_com, 17, IA2_TEXT_BOUNDARY_CHAR,
1714 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
1715 /*end=*/0,
1716 /*text=*/nullptr);
1717
1718 // L"<b>efore"
1719 EXPECT_IA2_TEXT_AT_OFFSET(static_text_1_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1720 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/1,
1721 /*text=*/L"b");
1722
1723 // L"be<f>ore"
1724 EXPECT_IA2_TEXT_AT_OFFSET(static_text_1_com, 2, IA2_TEXT_BOUNDARY_CHAR,
1725 /*expected_hr=*/S_OK, /*start=*/2, /*end=*/3,
1726 /*text=*/L"f");
1727
1728 // L"before<>"
1729 EXPECT_IA2_TEXT_AT_OFFSET(static_text_1_com, 6, IA2_TEXT_BOUNDARY_CHAR,
1730 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
1731 /*end=*/0,
1732 /*text=*/nullptr);
1733
1734 // <[obj]>
1735 EXPECT_IA2_TEXT_AT_OFFSET(
1736 menu_1_accessible_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1737 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/1,
1738 /*text=*/
1739 base::string16{ui::AXPlatformNodeBase::kEmbeddedCharacter}.c_str());
1740
1741 // [obj]<>
1742 EXPECT_IA2_TEXT_AT_OFFSET(menu_1_accessible_com, 1, IA2_TEXT_BOUNDARY_CHAR,
1743 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
1744 /*end=*/0,
1745 /*text=*/nullptr);
1746
1747 // L"<>"
1748 EXPECT_IA2_TEXT_AT_OFFSET(menu_2_accessible_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1749 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
1750 /*end=*/0,
1751 /*text=*/nullptr);
1752
1753 // L"<a>fter"
1754 EXPECT_IA2_TEXT_AT_OFFSET(static_text_2_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1755 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/1,
1756 /*text=*/L"a");
1757
1758 // L"af<t>er"
1759 EXPECT_IA2_TEXT_AT_OFFSET(static_text_2_com, 2, IA2_TEXT_BOUNDARY_CHAR,
1760 /*expected_hr=*/S_OK, /*start=*/2, /*end=*/3,
1761 /*text=*/L"t");
1762
1763 // L"after<>"
1764 EXPECT_IA2_TEXT_AT_OFFSET(static_text_2_com, 5, IA2_TEXT_BOUNDARY_CHAR,
1765 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
1766 /*end=*/0,
1767 /*text=*/nullptr);
1768
1769 // L"<t>ail"
1770 EXPECT_IA2_TEXT_AT_OFFSET(static_text_3_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1771 /*expected_hr=*/S_OK, /*start=*/0, /*end=*/1,
1772 /*text=*/L"t");
1773
1774 // L"ta<i>l"
1775 EXPECT_IA2_TEXT_AT_OFFSET(static_text_3_com, 2, IA2_TEXT_BOUNDARY_CHAR,
1776 /*expected_hr=*/S_OK, /*start=*/2, /*end=*/3,
1777 /*text=*/L"i");
1778
1779 // L"tail<>"
1780 EXPECT_IA2_TEXT_AT_OFFSET(static_text_3_com, 4, IA2_TEXT_BOUNDARY_CHAR,
1781 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
1782 /*end=*/0,
1783 /*text=*/nullptr);
1784
1785 // L"before" [obj] <[obj]> L"<a>fter" L"tail"
1786 EXPECT_IA2_TEXT_BEFORE_OFFSET(
1787 body_accessible_com, 7, IA2_TEXT_BOUNDARY_CHAR,
1788 /*expected_hr=*/S_OK, /*start=*/6, /*end=*/7,
1789 /*text=*/
1790 base::string16{ui::AXPlatformNodeBase::kEmbeddedCharacter}.c_str());
1791
1792 // L"before" <[obj]> [obj] L"after" L"tail"
1793 EXPECT_IA2_TEXT_BEFORE_OFFSET(body_accessible_com, 6, IA2_TEXT_BOUNDARY_CHAR,
1794 /*expected_hr=*/S_OK, /*start=*/5, /*end=*/6,
1795 /*text=*/L"e");
1796
1797 // <[obj]>
1798 EXPECT_IA2_TEXT_BEFORE_OFFSET(menu_1_accessible_com, 0,
1799 IA2_TEXT_BOUNDARY_CHAR,
1800 /*expected_hr=*/S_FALSE, /*start=*/0, /*end=*/0,
1801 /*text=*/nullptr);
1802
1803 // L"<>"
1804 EXPECT_IA2_TEXT_BEFORE_OFFSET(menu_2_accessible_com, 0,
1805 IA2_TEXT_BOUNDARY_CHAR,
1806 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
1807 /*end=*/0,
1808 /*text=*/nullptr);
1809
1810 // L"befor<e>" [obj] <[obj]> L"after" L"tail"
1811 // TODO(crbug.com/1039528): This should not include multiple characters.
1812 {
1813 const std::array<base::char16, 3> pieces = {
1814 ui::AXPlatformNodeBase::kEmbeddedCharacter,
1815 ui::AXPlatformNodeBase::kEmbeddedCharacter, L'a'};
1816 const base::string16 expect(pieces.cbegin(), pieces.cend());
1817 EXPECT_IA2_TEXT_AFTER_OFFSET(body_accessible_com, 5, IA2_TEXT_BOUNDARY_CHAR,
1818 /*expected_hr=*/S_OK, /*start=*/6, /*end=*/9,
1819 /*text=*/expect.c_str());
1820 }
1821
1822 // L"before" <[obj]> [obj] L"after" L"tail"
1823 // TODO(crbug.com/1039528): This should probably not skip over L"a"
1824 EXPECT_IA2_TEXT_AFTER_OFFSET(body_accessible_com, 6, IA2_TEXT_BOUNDARY_CHAR,
1825 /*expected_hr=*/S_OK, /*start=*/9, /*end=*/10,
1826 /*text=*/L"f");
1827
1828 // <[obj]>
1829 EXPECT_IA2_TEXT_AFTER_OFFSET(menu_1_accessible_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1830 /*expected_hr=*/S_FALSE, /*start=*/0, /*end=*/0,
1831 /*text=*/nullptr);
1832
1833 // L"<>"
1834 EXPECT_IA2_TEXT_AFTER_OFFSET(menu_2_accessible_com, 0, IA2_TEXT_BOUNDARY_CHAR,
1835 /*expected_hr=*/E_INVALIDARG, /*start=*/0,
1836 /*end=*/0,
1837 /*text=*/nullptr);
1838 }
1839
TEST_F(BrowserAccessibilityWinTest,TestCaretAndSelectionInSimpleFields)1840 TEST_F(BrowserAccessibilityWinTest, TestCaretAndSelectionInSimpleFields) {
1841 ui::AXNodeData root;
1842 root.id = 1;
1843 root.role = ax::mojom::Role::kRootWebArea;
1844 root.AddState(ax::mojom::State::kFocusable);
1845
1846 ui::AXNodeData combo_box;
1847 combo_box.id = 2;
1848 combo_box.role = ax::mojom::Role::kTextFieldWithComboBox;
1849 combo_box.AddState(ax::mojom::State::kEditable);
1850 combo_box.AddState(ax::mojom::State::kFocusable);
1851 combo_box.SetValue("Test1");
1852 // Place the caret between 't' and 'e'.
1853 combo_box.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, 1);
1854 combo_box.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, 1);
1855
1856 ui::AXNodeData text_field;
1857 text_field.id = 3;
1858 text_field.role = ax::mojom::Role::kTextField;
1859 text_field.AddState(ax::mojom::State::kEditable);
1860 text_field.AddState(ax::mojom::State::kFocusable);
1861 text_field.SetValue("Test2");
1862 // Select the letter 'e'.
1863 text_field.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, 1);
1864 text_field.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, 2);
1865
1866 root.child_ids.push_back(2);
1867 root.child_ids.push_back(3);
1868
1869 std::unique_ptr<BrowserAccessibilityManager> manager(
1870 BrowserAccessibilityManager::Create(
1871 MakeAXTreeUpdate(root, combo_box, text_field),
1872 test_browser_accessibility_delegate_.get()));
1873
1874 ASSERT_NE(nullptr, manager->GetRoot());
1875 BrowserAccessibilityWin* root_accessible =
1876 ToBrowserAccessibilityWin(manager->GetRoot());
1877 ASSERT_NE(nullptr, root_accessible);
1878 ASSERT_EQ(2U, root_accessible->PlatformChildCount());
1879
1880 BrowserAccessibilityWin* combo_box_accessible =
1881 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(0));
1882 ASSERT_NE(nullptr, combo_box_accessible);
1883 manager->SetFocusLocallyForTesting(combo_box_accessible);
1884 ASSERT_EQ(combo_box_accessible,
1885 ToBrowserAccessibilityWin(manager->GetFocus()));
1886 BrowserAccessibilityWin* text_field_accessible =
1887 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(1));
1888 ASSERT_NE(nullptr, text_field_accessible);
1889
1890 // -2 is never a valid offset.
1891 LONG caret_offset = -2;
1892 LONG n_selections = -2;
1893 LONG selection_start = -2;
1894 LONG selection_end = -2;
1895
1896 // Test get_caretOffset.
1897 HRESULT hr = combo_box_accessible->GetCOM()->get_caretOffset(&caret_offset);
1898 EXPECT_EQ(S_OK, hr);
1899 EXPECT_EQ(1, caret_offset);
1900 // The caret should be at the end of the selection.
1901 hr = text_field_accessible->GetCOM()->get_caretOffset(&caret_offset);
1902 EXPECT_EQ(S_OK, hr);
1903 EXPECT_EQ(2, caret_offset);
1904
1905 // Move the focus to the text field.
1906 manager->SetFocusLocallyForTesting(text_field_accessible);
1907 ASSERT_EQ(text_field_accessible,
1908 ToBrowserAccessibilityWin(manager->GetFocus()));
1909
1910 // The caret should not have moved.
1911 hr = text_field_accessible->GetCOM()->get_caretOffset(&caret_offset);
1912 EXPECT_EQ(S_OK, hr);
1913 EXPECT_EQ(2, caret_offset);
1914
1915 // Test get_nSelections.
1916 hr = combo_box_accessible->GetCOM()->get_nSelections(&n_selections);
1917 EXPECT_EQ(S_OK, hr);
1918 EXPECT_EQ(0, n_selections);
1919 hr = text_field_accessible->GetCOM()->get_nSelections(&n_selections);
1920 EXPECT_EQ(S_OK, hr);
1921 EXPECT_EQ(1, n_selections);
1922
1923 // Test get_selection.
1924 hr = combo_box_accessible->GetCOM()->get_selection(
1925 0L /* selection_index */, &selection_start, &selection_end);
1926 EXPECT_EQ(E_INVALIDARG, hr); // No selections available.
1927 hr = text_field_accessible->GetCOM()->get_selection(
1928 0L /* selection_index */, &selection_start, &selection_end);
1929 EXPECT_EQ(S_OK, hr);
1930 EXPECT_EQ(1, selection_start);
1931 EXPECT_EQ(2, selection_end);
1932
1933 manager.reset();
1934 }
1935
TEST_F(BrowserAccessibilityWinTest,TestCaretInContentEditables)1936 TEST_F(BrowserAccessibilityWinTest, TestCaretInContentEditables) {
1937 ui::AXNodeData root;
1938 root.id = 1;
1939 root.role = ax::mojom::Role::kRootWebArea;
1940 root.AddState(ax::mojom::State::kFocusable);
1941
1942 ui::AXNodeData div_editable;
1943 div_editable.id = 2;
1944 div_editable.role = ax::mojom::Role::kGenericContainer;
1945 div_editable.AddState(ax::mojom::State::kEditable);
1946 div_editable.AddState(ax::mojom::State::kFocusable);
1947
1948 ui::AXNodeData text;
1949 text.id = 3;
1950 text.role = ax::mojom::Role::kStaticText;
1951 text.AddState(ax::mojom::State::kEditable);
1952 text.SetName("Click ");
1953
1954 ui::AXNodeData link;
1955 link.id = 4;
1956 link.role = ax::mojom::Role::kLink;
1957 link.AddState(ax::mojom::State::kEditable);
1958 link.AddState(ax::mojom::State::kFocusable);
1959 link.AddState(ax::mojom::State::kLinked);
1960 link.SetName("here");
1961
1962 ui::AXNodeData link_text;
1963 link_text.id = 5;
1964 link_text.role = ax::mojom::Role::kStaticText;
1965 link_text.AddState(ax::mojom::State::kEditable);
1966 link_text.AddState(ax::mojom::State::kFocusable);
1967 link_text.AddState(ax::mojom::State::kLinked);
1968 link_text.SetName("here");
1969
1970 root.child_ids.push_back(2);
1971 div_editable.child_ids.push_back(3);
1972 div_editable.child_ids.push_back(4);
1973 link.child_ids.push_back(5);
1974
1975 ui::AXTreeUpdate update =
1976 MakeAXTreeUpdate(root, div_editable, link, link_text, text);
1977
1978 // Place the caret between 'h' and 'e'.
1979 update.has_tree_data = true;
1980 update.tree_data.sel_anchor_object_id = 5;
1981 update.tree_data.sel_anchor_offset = 1;
1982 update.tree_data.sel_focus_object_id = 5;
1983 update.tree_data.sel_focus_offset = 1;
1984
1985 std::unique_ptr<BrowserAccessibilityManager> manager(
1986 BrowserAccessibilityManager::Create(
1987 update, test_browser_accessibility_delegate_.get()));
1988
1989 ASSERT_NE(nullptr, manager->GetRoot());
1990 BrowserAccessibilityWin* root_accessible =
1991 ToBrowserAccessibilityWin(manager->GetRoot());
1992 ASSERT_NE(nullptr, root_accessible);
1993 ASSERT_EQ(1U, root_accessible->PlatformChildCount());
1994
1995 BrowserAccessibilityWin* div_editable_accessible =
1996 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(0));
1997 ASSERT_NE(nullptr, div_editable_accessible);
1998 ASSERT_EQ(2U, div_editable_accessible->PlatformChildCount());
1999
2000 // -2 is never a valid offset.
2001 LONG caret_offset = -2;
2002 LONG n_selections = -2;
2003
2004 // No selection should be present.
2005 HRESULT hr =
2006 div_editable_accessible->GetCOM()->get_nSelections(&n_selections);
2007 EXPECT_EQ(S_OK, hr);
2008 EXPECT_EQ(0, n_selections);
2009
2010 // The caret should be on the embedded object character.
2011 hr = div_editable_accessible->GetCOM()->get_caretOffset(&caret_offset);
2012 EXPECT_EQ(S_OK, hr);
2013 EXPECT_EQ(6, caret_offset);
2014
2015 // Move the focus to the content editable.
2016 manager->SetFocusLocallyForTesting(div_editable_accessible);
2017 ASSERT_EQ(div_editable_accessible,
2018 ToBrowserAccessibilityWin(manager->GetFocus()));
2019
2020 BrowserAccessibilityWin* text_accessible =
2021 ToBrowserAccessibilityWin(div_editable_accessible->PlatformGetChild(0));
2022 ASSERT_NE(nullptr, text_accessible);
2023 BrowserAccessibilityWin* link_accessible =
2024 ToBrowserAccessibilityWin(div_editable_accessible->PlatformGetChild(1));
2025 ASSERT_NE(nullptr, link_accessible);
2026 ASSERT_EQ(1U, link_accessible->PlatformChildCount());
2027
2028 BrowserAccessibilityWin* link_text_accessible =
2029 ToBrowserAccessibilityWin(link_accessible->PlatformGetChild(0));
2030 ASSERT_NE(nullptr, link_text_accessible);
2031
2032 // The caret should not have moved.
2033 hr = div_editable_accessible->GetCOM()->get_nSelections(&n_selections);
2034 EXPECT_EQ(S_OK, hr);
2035 EXPECT_EQ(0, n_selections);
2036 hr = div_editable_accessible->GetCOM()->get_caretOffset(&caret_offset);
2037 EXPECT_EQ(S_OK, hr);
2038 EXPECT_EQ(6, caret_offset);
2039
2040 hr = link_accessible->GetCOM()->get_nSelections(&n_selections);
2041 EXPECT_EQ(S_OK, hr);
2042 EXPECT_EQ(0, n_selections);
2043 hr = link_text_accessible->GetCOM()->get_nSelections(&n_selections);
2044 EXPECT_EQ(S_OK, hr);
2045 EXPECT_EQ(0, n_selections);
2046
2047 hr = link_accessible->GetCOM()->get_caretOffset(&caret_offset);
2048 EXPECT_EQ(S_OK, hr);
2049 EXPECT_EQ(1, caret_offset);
2050 hr = link_text_accessible->GetCOM()->get_caretOffset(&caret_offset);
2051 EXPECT_EQ(S_OK, hr);
2052 EXPECT_EQ(1, caret_offset);
2053
2054 manager.reset();
2055 }
2056
TEST_F(BrowserAccessibilityWinTest,TestSelectionInContentEditables)2057 TEST_F(BrowserAccessibilityWinTest, TestSelectionInContentEditables) {
2058 ui::AXNodeData root;
2059 root.id = 1;
2060 root.role = ax::mojom::Role::kRootWebArea;
2061 root.AddState(ax::mojom::State::kFocusable);
2062
2063 ui::AXNodeData div_editable;
2064 div_editable.id = 2;
2065 div_editable.role = ax::mojom::Role::kGenericContainer;
2066 div_editable.AddState(ax::mojom::State::kFocusable);
2067 div_editable.AddState(ax::mojom::State::kEditable);
2068
2069 ui::AXNodeData text;
2070 text.id = 3;
2071 text.role = ax::mojom::Role::kStaticText;
2072 text.AddState(ax::mojom::State::kFocusable);
2073 text.AddState(ax::mojom::State::kEditable);
2074 text.SetName("Click ");
2075
2076 ui::AXNodeData link;
2077 link.id = 4;
2078 link.role = ax::mojom::Role::kLink;
2079 link.AddState(ax::mojom::State::kFocusable);
2080 link.AddState(ax::mojom::State::kEditable);
2081 link.AddState(ax::mojom::State::kLinked);
2082 link.SetName("here");
2083
2084 ui::AXNodeData link_text;
2085 link_text.id = 5;
2086 link_text.role = ax::mojom::Role::kStaticText;
2087 link_text.AddState(ax::mojom::State::kFocusable);
2088 link_text.AddState(ax::mojom::State::kEditable);
2089 link_text.AddState(ax::mojom::State::kLinked);
2090 link_text.SetName("here");
2091
2092 root.child_ids.push_back(2);
2093 div_editable.child_ids.push_back(3);
2094 div_editable.child_ids.push_back(4);
2095 link.child_ids.push_back(5);
2096
2097 ui::AXTreeUpdate update =
2098 MakeAXTreeUpdate(root, div_editable, link, link_text, text);
2099
2100 // Select the following part of the text: "lick here".
2101 update.has_tree_data = true;
2102 update.tree_data.sel_anchor_object_id = 3;
2103 update.tree_data.sel_anchor_offset = 1;
2104 update.tree_data.sel_focus_object_id = 5;
2105 update.tree_data.sel_focus_offset = 4;
2106
2107 std::unique_ptr<BrowserAccessibilityManager> manager(
2108 BrowserAccessibilityManager::Create(
2109 update, test_browser_accessibility_delegate_.get()));
2110
2111 ASSERT_NE(nullptr, manager->GetRoot());
2112 BrowserAccessibilityWin* root_accessible =
2113 ToBrowserAccessibilityWin(manager->GetRoot());
2114 ASSERT_NE(nullptr, root_accessible);
2115 ASSERT_EQ(1U, root_accessible->PlatformChildCount());
2116
2117 BrowserAccessibilityWin* div_editable_accessible =
2118 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(0));
2119 ASSERT_NE(nullptr, div_editable_accessible);
2120 ASSERT_EQ(2U, div_editable_accessible->PlatformChildCount());
2121
2122 // -2 is never a valid offset.
2123 LONG caret_offset = -2;
2124 LONG n_selections = -2;
2125 LONG selection_start = -2;
2126 LONG selection_end = -2;
2127
2128 BrowserAccessibilityWin* text_accessible =
2129 ToBrowserAccessibilityWin(div_editable_accessible->PlatformGetChild(0));
2130 ASSERT_NE(nullptr, text_accessible);
2131 BrowserAccessibilityWin* link_accessible =
2132 ToBrowserAccessibilityWin(div_editable_accessible->PlatformGetChild(1));
2133 ASSERT_NE(nullptr, link_accessible);
2134 ASSERT_EQ(1U, link_accessible->PlatformChildCount());
2135
2136 BrowserAccessibilityWin* link_text_accessible =
2137 ToBrowserAccessibilityWin(link_accessible->PlatformGetChild(0));
2138 ASSERT_NE(nullptr, link_text_accessible);
2139
2140 // get_nSelections should work on all objects.
2141 HRESULT hr =
2142 div_editable_accessible->GetCOM()->get_nSelections(&n_selections);
2143 EXPECT_EQ(S_OK, hr);
2144 EXPECT_EQ(1, n_selections);
2145 hr = text_accessible->GetCOM()->get_nSelections(&n_selections);
2146 EXPECT_EQ(S_OK, hr);
2147 EXPECT_EQ(1, n_selections);
2148 hr = link_accessible->GetCOM()->get_nSelections(&n_selections);
2149 EXPECT_EQ(S_OK, hr);
2150 EXPECT_EQ(1, n_selections);
2151 hr = link_text_accessible->GetCOM()->get_nSelections(&n_selections);
2152 EXPECT_EQ(S_OK, hr);
2153 EXPECT_EQ(1, n_selections);
2154
2155 // get_selection should be unaffected by focus placement.
2156 hr = div_editable_accessible->GetCOM()->get_selection(
2157 0L /* selection_index */, &selection_start, &selection_end);
2158 EXPECT_EQ(S_OK, hr);
2159 EXPECT_EQ(1, selection_start);
2160 // selection_end should be after embedded object character.
2161 EXPECT_EQ(7, selection_end);
2162
2163 hr = text_accessible->GetCOM()->get_selection(
2164 0L /* selection_index */, &selection_start, &selection_end);
2165 EXPECT_EQ(S_OK, hr);
2166 EXPECT_EQ(1, selection_start);
2167 // No embedded character on this object, only the first part of the text.
2168 EXPECT_EQ(6, selection_end);
2169 hr = link_accessible->GetCOM()->get_selection(
2170 0L /* selection_index */, &selection_start, &selection_end);
2171 EXPECT_EQ(S_OK, hr);
2172 EXPECT_EQ(0, selection_start);
2173 EXPECT_EQ(4, selection_end);
2174 hr = link_text_accessible->GetCOM()->get_selection(
2175 0L /* selection_index */, &selection_start, &selection_end);
2176 EXPECT_EQ(S_OK, hr);
2177 EXPECT_EQ(0, selection_start);
2178 EXPECT_EQ(4, selection_end);
2179
2180 // The caret should be at the focus (the end) of the selection.
2181 hr = div_editable_accessible->GetCOM()->get_caretOffset(&caret_offset);
2182 EXPECT_EQ(S_OK, hr);
2183 EXPECT_EQ(7, caret_offset);
2184
2185 // Move the focus to the content editable.
2186 manager->SetFocusLocallyForTesting(div_editable_accessible);
2187 ASSERT_EQ(div_editable_accessible,
2188 ToBrowserAccessibilityWin(manager->GetFocus()));
2189
2190 // The caret should not have moved.
2191 hr = div_editable_accessible->GetCOM()->get_caretOffset(&caret_offset);
2192 EXPECT_EQ(S_OK, hr);
2193 EXPECT_EQ(7, caret_offset);
2194
2195 // The caret offset should reflect the position of the selection's focus in
2196 // any given object.
2197 hr = link_accessible->GetCOM()->get_caretOffset(&caret_offset);
2198 EXPECT_EQ(S_OK, hr);
2199 EXPECT_EQ(4, caret_offset);
2200 hr = link_text_accessible->GetCOM()->get_caretOffset(&caret_offset);
2201 EXPECT_EQ(S_OK, hr);
2202 EXPECT_EQ(4, caret_offset);
2203
2204 hr = div_editable_accessible->GetCOM()->get_selection(
2205 0L /* selection_index */, &selection_start, &selection_end);
2206 EXPECT_EQ(S_OK, hr);
2207 EXPECT_EQ(1, selection_start);
2208 EXPECT_EQ(7, selection_end);
2209
2210 manager.reset();
2211 }
2212
TEST_F(BrowserAccessibilityWinTest,TestIAccessibleHyperlink)2213 TEST_F(BrowserAccessibilityWinTest, TestIAccessibleHyperlink) {
2214 ui::AXNodeData root;
2215 root.id = 1;
2216 root.role = ax::mojom::Role::kRootWebArea;
2217 root.AddState(ax::mojom::State::kFocusable);
2218
2219 ui::AXNodeData div;
2220 div.id = 2;
2221 div.role = ax::mojom::Role::kGenericContainer;
2222 div.AddState(ax::mojom::State::kFocusable);
2223
2224 ui::AXNodeData text;
2225 text.id = 3;
2226 text.role = ax::mojom::Role::kStaticText;
2227 text.SetName("Click ");
2228
2229 ui::AXNodeData link;
2230 link.id = 4;
2231 link.role = ax::mojom::Role::kLink;
2232 link.AddState(ax::mojom::State::kFocusable);
2233 link.AddState(ax::mojom::State::kLinked);
2234 link.SetName("here");
2235 link.SetNameFrom(ax::mojom::NameFrom::kContents);
2236 link.AddStringAttribute(ax::mojom::StringAttribute::kUrl, "example.com");
2237
2238 root.child_ids.push_back(div.id);
2239 div.child_ids = {text.id, link.id};
2240
2241 std::unique_ptr<BrowserAccessibilityManager> manager(
2242 BrowserAccessibilityManager::Create(
2243 MakeAXTreeUpdate(root, div, link, text),
2244 test_browser_accessibility_delegate_.get()));
2245
2246 ASSERT_NE(nullptr, manager->GetRoot());
2247 BrowserAccessibilityWin* root_accessible =
2248 ToBrowserAccessibilityWin(manager->GetRoot());
2249 ASSERT_NE(nullptr, root_accessible);
2250 ASSERT_EQ(1U, root_accessible->PlatformChildCount());
2251
2252 BrowserAccessibilityWin* div_accessible =
2253 ToBrowserAccessibilityWin(root_accessible->PlatformGetChild(0));
2254 ASSERT_NE(nullptr, div_accessible);
2255 ASSERT_EQ(2U, div_accessible->PlatformChildCount());
2256
2257 BrowserAccessibilityWin* text_accessible =
2258 ToBrowserAccessibilityWin(div_accessible->PlatformGetChild(0));
2259 ASSERT_NE(nullptr, text_accessible);
2260 BrowserAccessibilityWin* link_accessible =
2261 ToBrowserAccessibilityWin(div_accessible->PlatformGetChild(1));
2262 ASSERT_NE(nullptr, link_accessible);
2263
2264 // -1 is never a valid value.
2265 LONG n_actions = -1;
2266 LONG start_index = -1;
2267 LONG end_index = -1;
2268
2269 Microsoft::WRL::ComPtr<IAccessibleHyperlink> hyperlink;
2270 base::win::ScopedVariant anchor;
2271 base::win::ScopedVariant anchor_target;
2272 base::win::ScopedBstr bstr;
2273
2274 base::string16 div_hypertext(L"Click ");
2275 div_hypertext.push_back(BrowserAccessibilityComWin::kEmbeddedCharacter);
2276
2277 // div_accessible and link_accessible are the only IA2 hyperlinks.
2278 EXPECT_HRESULT_FAILED(
2279 root_accessible->GetCOM()->QueryInterface(IID_PPV_ARGS(&hyperlink)));
2280 hyperlink.Reset();
2281 EXPECT_HRESULT_SUCCEEDED(
2282 div_accessible->GetCOM()->QueryInterface(IID_PPV_ARGS(&hyperlink)));
2283 hyperlink.Reset();
2284 EXPECT_HRESULT_FAILED(
2285 text_accessible->GetCOM()->QueryInterface(IID_PPV_ARGS(&hyperlink)));
2286 hyperlink.Reset();
2287 EXPECT_HRESULT_SUCCEEDED(
2288 link_accessible->GetCOM()->QueryInterface(IID_PPV_ARGS(&hyperlink)));
2289 hyperlink.Reset();
2290
2291 EXPECT_HRESULT_SUCCEEDED(root_accessible->GetCOM()->nActions(&n_actions));
2292 EXPECT_EQ(0, n_actions);
2293 EXPECT_HRESULT_SUCCEEDED(div_accessible->GetCOM()->nActions(&n_actions));
2294 EXPECT_EQ(1, n_actions);
2295 EXPECT_HRESULT_SUCCEEDED(text_accessible->GetCOM()->nActions(&n_actions));
2296 EXPECT_EQ(0, n_actions);
2297 EXPECT_HRESULT_SUCCEEDED(link_accessible->GetCOM()->nActions(&n_actions));
2298 EXPECT_EQ(1, n_actions);
2299
2300 EXPECT_HRESULT_FAILED(
2301 root_accessible->GetCOM()->get_anchor(0, anchor.Receive()));
2302 anchor.Reset();
2303 HRESULT hr = div_accessible->GetCOM()->get_anchor(0, anchor.Receive());
2304 EXPECT_EQ(S_OK, hr);
2305 EXPECT_EQ(VT_BSTR, anchor.type());
2306 bstr.Reset(V_BSTR(anchor.ptr()));
2307 EXPECT_STREQ(div_hypertext.c_str(), bstr.Get());
2308 bstr.Reset();
2309 anchor.Reset();
2310 EXPECT_HRESULT_FAILED(
2311 text_accessible->GetCOM()->get_anchor(0, anchor.Receive()));
2312 anchor.Reset();
2313 hr = link_accessible->GetCOM()->get_anchor(0, anchor.Receive());
2314 EXPECT_EQ(S_OK, hr);
2315 EXPECT_EQ(VT_BSTR, anchor.type());
2316 bstr.Reset(V_BSTR(anchor.ptr()));
2317 EXPECT_STREQ(L"here", bstr.Get());
2318 bstr.Reset();
2319 anchor.Reset();
2320 EXPECT_HRESULT_FAILED(
2321 div_accessible->GetCOM()->get_anchor(1, anchor.Receive()));
2322 anchor.Reset();
2323 EXPECT_HRESULT_FAILED(
2324 link_accessible->GetCOM()->get_anchor(1, anchor.Receive()));
2325 anchor.Reset();
2326
2327 EXPECT_HRESULT_FAILED(
2328 root_accessible->GetCOM()->get_anchorTarget(0, anchor_target.Receive()));
2329 anchor_target.Reset();
2330 hr = div_accessible->GetCOM()->get_anchorTarget(0, anchor_target.Receive());
2331 EXPECT_EQ(S_FALSE, hr);
2332 EXPECT_EQ(VT_BSTR, anchor_target.type());
2333 bstr.Reset(V_BSTR(anchor_target.ptr()));
2334 // Target should be empty.
2335 EXPECT_STREQ(L"", bstr.Get());
2336 bstr.Reset();
2337 anchor_target.Reset();
2338 EXPECT_HRESULT_FAILED(
2339 text_accessible->GetCOM()->get_anchorTarget(0, anchor_target.Receive()));
2340 anchor_target.Reset();
2341 hr = link_accessible->GetCOM()->get_anchorTarget(0, anchor_target.Receive());
2342 EXPECT_EQ(S_OK, hr);
2343 EXPECT_EQ(VT_BSTR, anchor_target.type());
2344 bstr.Reset(V_BSTR(anchor_target.ptr()));
2345 EXPECT_STREQ(L"example.com", bstr.Get());
2346 bstr.Reset();
2347 anchor_target.Reset();
2348 EXPECT_HRESULT_FAILED(
2349 div_accessible->GetCOM()->get_anchorTarget(1, anchor_target.Receive()));
2350 anchor_target.Reset();
2351 EXPECT_HRESULT_FAILED(
2352 link_accessible->GetCOM()->get_anchorTarget(1, anchor_target.Receive()));
2353 anchor_target.Reset();
2354
2355 EXPECT_HRESULT_FAILED(
2356 root_accessible->GetCOM()->get_startIndex(&start_index));
2357 EXPECT_HRESULT_SUCCEEDED(
2358 div_accessible->GetCOM()->get_startIndex(&start_index));
2359 EXPECT_EQ(0, start_index);
2360 EXPECT_HRESULT_FAILED(
2361 text_accessible->GetCOM()->get_startIndex(&start_index));
2362 EXPECT_HRESULT_SUCCEEDED(
2363 link_accessible->GetCOM()->get_startIndex(&start_index));
2364 EXPECT_EQ(6, start_index);
2365
2366 EXPECT_HRESULT_FAILED(root_accessible->GetCOM()->get_endIndex(&end_index));
2367 EXPECT_HRESULT_SUCCEEDED(div_accessible->GetCOM()->get_endIndex(&end_index));
2368 EXPECT_EQ(1, end_index);
2369 EXPECT_HRESULT_FAILED(text_accessible->GetCOM()->get_endIndex(&end_index));
2370 EXPECT_HRESULT_SUCCEEDED(link_accessible->GetCOM()->get_endIndex(&end_index));
2371 EXPECT_EQ(7, end_index);
2372 }
2373
TEST_F(BrowserAccessibilityWinTest,TestTextAttributesInContentEditables)2374 TEST_F(BrowserAccessibilityWinTest, TestTextAttributesInContentEditables) {
2375 ui::AXNodeData root;
2376 root.id = 1;
2377 root.role = ax::mojom::Role::kRootWebArea;
2378 root.AddState(ax::mojom::State::kFocusable);
2379
2380 ui::AXNodeData div_editable;
2381 div_editable.id = 2;
2382 div_editable.role = ax::mojom::Role::kGenericContainer;
2383 div_editable.AddState(ax::mojom::State::kEditable);
2384 div_editable.AddState(ax::mojom::State::kFocusable);
2385 div_editable.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily,
2386 "Helvetica");
2387
2388 ui::AXNodeData text_before;
2389 text_before.id = 3;
2390 text_before.role = ax::mojom::Role::kStaticText;
2391 text_before.AddState(ax::mojom::State::kEditable);
2392 text_before.SetName("Before ");
2393 text_before.AddTextStyle(ax::mojom::TextStyle::kBold);
2394 text_before.AddTextStyle(ax::mojom::TextStyle::kItalic);
2395
2396 ui::AXNodeData link;
2397 link.id = 4;
2398 link.role = ax::mojom::Role::kLink;
2399 link.AddState(ax::mojom::State::kEditable);
2400 link.AddState(ax::mojom::State::kFocusable);
2401 link.AddState(ax::mojom::State::kLinked);
2402 link.SetName("lnk");
2403 link.AddTextStyle(ax::mojom::TextStyle::kUnderline);
2404
2405 ui::AXNodeData link_text;
2406 link_text.id = 5;
2407 link_text.role = ax::mojom::Role::kStaticText;
2408 link_text.AddState(ax::mojom::State::kEditable);
2409 link_text.AddState(ax::mojom::State::kFocusable);
2410 link_text.AddState(ax::mojom::State::kLinked);
2411 link_text.SetName("lnk");
2412 link_text.AddTextStyle(ax::mojom::TextStyle::kUnderline);
2413
2414 // The name "lnk" is misspelled.
2415 std::vector<int32_t> marker_types{
2416 static_cast<int32_t>(ax::mojom::MarkerType::kSpelling)};
2417 std::vector<int32_t> marker_starts{0};
2418 std::vector<int32_t> marker_ends{3};
2419 link_text.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes,
2420 marker_types);
2421 link_text.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts,
2422 marker_starts);
2423 link_text.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds,
2424 marker_ends);
2425
2426 ui::AXNodeData text_after;
2427 text_after.id = 6;
2428 text_after.role = ax::mojom::Role::kStaticText;
2429 text_after.AddState(ax::mojom::State::kEditable);
2430 text_after.SetName(" after.");
2431 // Leave text style as normal.
2432
2433 root.child_ids.push_back(div_editable.id);
2434 div_editable.child_ids.push_back(text_before.id);
2435 div_editable.child_ids.push_back(link.id);
2436 div_editable.child_ids.push_back(text_after.id);
2437 link.child_ids.push_back(link_text.id);
2438
2439 ui::AXTreeUpdate update = MakeAXTreeUpdate(root, div_editable, text_before,
2440 link, link_text, text_after);
2441
2442 std::unique_ptr<BrowserAccessibilityManager> manager(
2443 BrowserAccessibilityManager::Create(
2444 update, test_browser_accessibility_delegate_.get()));
2445
2446 ASSERT_NE(nullptr, manager->GetRoot());
2447 BrowserAccessibilityWin* ax_root =
2448 ToBrowserAccessibilityWin(manager->GetRoot());
2449 ASSERT_NE(nullptr, ax_root);
2450 ASSERT_EQ(1U, ax_root->PlatformChildCount());
2451
2452 BrowserAccessibilityWin* ax_div =
2453 ToBrowserAccessibilityWin(ax_root->PlatformGetChild(0));
2454 ASSERT_NE(nullptr, ax_div);
2455 ASSERT_EQ(3U, ax_div->PlatformChildCount());
2456
2457 BrowserAccessibilityWin* ax_before =
2458 ToBrowserAccessibilityWin(ax_div->PlatformGetChild(0));
2459 ASSERT_NE(nullptr, ax_before);
2460 BrowserAccessibilityWin* ax_link =
2461 ToBrowserAccessibilityWin(ax_div->PlatformGetChild(1));
2462 ASSERT_NE(nullptr, ax_link);
2463 ASSERT_EQ(1U, ax_link->PlatformChildCount());
2464 BrowserAccessibilityWin* ax_after =
2465 ToBrowserAccessibilityWin(ax_div->PlatformGetChild(2));
2466 ASSERT_NE(nullptr, ax_after);
2467
2468 BrowserAccessibilityWin* ax_link_text =
2469 ToBrowserAccessibilityWin(ax_link->PlatformGetChild(0));
2470 ASSERT_NE(nullptr, ax_link_text);
2471
2472 HRESULT hr;
2473 LONG n_characters, start_offset, end_offset;
2474 base::win::ScopedBstr text_attributes;
2475
2476 ASSERT_HRESULT_SUCCEEDED(ax_root->GetCOM()->get_nCharacters(&n_characters));
2477 ASSERT_EQ(1, n_characters);
2478 ASSERT_HRESULT_SUCCEEDED(ax_div->GetCOM()->get_nCharacters(&n_characters));
2479 ASSERT_EQ(15, n_characters);
2480
2481 // Test the style of the root.
2482 hr = ax_root->GetCOM()->get_attributes(0, &start_offset, &end_offset,
2483 text_attributes.Receive());
2484 EXPECT_EQ(S_OK, hr);
2485 EXPECT_EQ(0, start_offset);
2486 EXPECT_EQ(1, end_offset);
2487 EXPECT_NE(
2488 base::string16::npos,
2489 base::string16(text_attributes.Get()).find(L"font-family:Helvetica"));
2490 text_attributes.Reset();
2491
2492 // Test the style of text_before.
2493 for (LONG offset = 0; offset < 7; ++offset) {
2494 hr = ax_div->GetCOM()->get_attributes(0, &start_offset, &end_offset,
2495 text_attributes.Receive());
2496 EXPECT_EQ(S_OK, hr);
2497 EXPECT_EQ(0, start_offset);
2498 EXPECT_EQ(7, end_offset);
2499 base::string16 attributes(text_attributes.Get());
2500 EXPECT_NE(base::string16::npos, attributes.find(L"font-family:Helvetica"));
2501 EXPECT_NE(base::string16::npos, attributes.find(L"font-weight:bold"));
2502 EXPECT_NE(base::string16::npos, attributes.find(L"font-style:italic"));
2503 text_attributes.Reset();
2504 }
2505
2506 // Test the style of the link.
2507 hr = ax_link->GetCOM()->get_attributes(0, &start_offset, &end_offset,
2508 text_attributes.Receive());
2509 EXPECT_EQ(S_OK, hr);
2510 EXPECT_EQ(0, start_offset);
2511 EXPECT_EQ(3, end_offset);
2512 EXPECT_NE(
2513 base::string16::npos,
2514 base::string16(text_attributes.Get()).find(L"font-family:Helvetica"));
2515 EXPECT_EQ(base::string16::npos,
2516 base::string16(text_attributes.Get()).find(L"font-weight:"));
2517 EXPECT_EQ(base::string16::npos,
2518 base::string16(text_attributes.Get()).find(L"font-style:"));
2519 EXPECT_NE(base::string16::npos, base::string16(text_attributes.Get())
2520 .find(L"text-underline-style:solid"));
2521 EXPECT_EQ(
2522 base::string16::npos,
2523 base::string16(text_attributes.Get()).find(L"text-underline-type:"));
2524 // For compatibility with Firefox, spelling attributes should also be
2525 // propagated to the parent of static text leaves.
2526 EXPECT_NE(base::string16::npos,
2527 base::string16(text_attributes.Get()).find(L"invalid:spelling"));
2528 text_attributes.Reset();
2529
2530 hr = ax_link_text->GetCOM()->get_attributes(2, &start_offset, &end_offset,
2531 text_attributes.Receive());
2532 EXPECT_EQ(S_OK, hr);
2533 EXPECT_EQ(0, start_offset);
2534 EXPECT_EQ(3, end_offset);
2535 EXPECT_NE(
2536 base::string16::npos,
2537 base::string16(text_attributes.Get()).find(L"font-family:Helvetica"));
2538 EXPECT_EQ(base::string16::npos,
2539 base::string16(text_attributes.Get()).find(L"font-weight:"));
2540 EXPECT_EQ(base::string16::npos,
2541 base::string16(text_attributes.Get()).find(L"font-style:"));
2542 EXPECT_NE(base::string16::npos, base::string16(text_attributes.Get())
2543 .find(L"text-underline-style:solid"));
2544 EXPECT_EQ(
2545 base::string16::npos,
2546 base::string16(text_attributes.Get()).find(L"text-underline-type:"));
2547 EXPECT_NE(base::string16::npos,
2548 base::string16(text_attributes.Get()).find(L"invalid:spelling"));
2549 text_attributes.Reset();
2550
2551 // Test the style of text_after.
2552 for (LONG offset = 8; offset < 15; ++offset) {
2553 hr = ax_div->GetCOM()->get_attributes(offset, &start_offset, &end_offset,
2554 text_attributes.Receive());
2555 EXPECT_EQ(S_OK, hr);
2556 EXPECT_EQ(8, start_offset);
2557 EXPECT_EQ(15, end_offset);
2558 base::string16 attributes(text_attributes.Get());
2559 EXPECT_NE(base::string16::npos, attributes.find(L"font-family:Helvetica"));
2560 EXPECT_EQ(base::string16::npos, attributes.find(L"font-weight:"));
2561 EXPECT_EQ(base::string16::npos, attributes.find(L"font-style:"));
2562 EXPECT_EQ(base::string16::npos, base::string16(text_attributes.Get())
2563 .find(L"text-underline-style:solid"));
2564 EXPECT_EQ(
2565 base::string16::npos,
2566 base::string16(text_attributes.Get()).find(L"text-underline-type:"));
2567 EXPECT_EQ(base::string16::npos, attributes.find(L"invalid:spelling"));
2568 text_attributes.Reset();
2569 }
2570
2571 // Test the style of the static text nodes.
2572 hr = ax_before->GetCOM()->get_attributes(6, &start_offset, &end_offset,
2573 text_attributes.Receive());
2574 EXPECT_EQ(S_OK, hr);
2575 EXPECT_EQ(0, start_offset);
2576 EXPECT_EQ(7, end_offset);
2577 EXPECT_NE(
2578 base::string16::npos,
2579 base::string16(text_attributes.Get()).find(L"font-family:Helvetica"));
2580 EXPECT_NE(base::string16::npos,
2581 base::string16(text_attributes.Get()).find(L"font-weight:bold"));
2582 EXPECT_NE(base::string16::npos,
2583 base::string16(text_attributes.Get()).find(L"font-style:italic"));
2584 EXPECT_EQ(base::string16::npos,
2585 base::string16(text_attributes.Get()).find(L"invalid:spelling"));
2586 text_attributes.Reset();
2587
2588 hr = ax_after->GetCOM()->get_attributes(6, &start_offset, &end_offset,
2589 text_attributes.Receive());
2590 EXPECT_EQ(S_OK, hr);
2591 EXPECT_EQ(0, start_offset);
2592 EXPECT_EQ(7, end_offset);
2593 EXPECT_NE(
2594 base::string16::npos,
2595 base::string16(text_attributes.Get()).find(L"font-family:Helvetica"));
2596 EXPECT_EQ(base::string16::npos,
2597 base::string16(text_attributes.Get()).find(L"font-weight:"));
2598 EXPECT_EQ(base::string16::npos,
2599 base::string16(text_attributes.Get()).find(L"font-style:"));
2600 EXPECT_EQ(
2601 base::string16::npos,
2602 base::string16(text_attributes.Get()).find(L"text-underline-style:"));
2603 EXPECT_EQ(
2604 base::string16::npos,
2605 base::string16(text_attributes.Get()).find(L"text-underline-type:"));
2606 EXPECT_EQ(base::string16::npos,
2607 base::string16(text_attributes.Get()).find(L"invalid:spelling"));
2608 text_attributes.Reset();
2609
2610 manager.reset();
2611 }
2612
TEST_F(BrowserAccessibilityWinTest,TestExistingMisspellingsInSimpleTextFields)2613 TEST_F(BrowserAccessibilityWinTest,
2614 TestExistingMisspellingsInSimpleTextFields) {
2615 std::string value1("Testing .");
2616 // The word "helo" is misspelled.
2617 std::string value2("Helo there.");
2618
2619 LONG value1_length = static_cast<LONG>(value1.length());
2620 LONG value2_length = static_cast<LONG>(value2.length());
2621 LONG combo_box_value_length = value1_length + value2_length;
2622
2623 ui::AXNodeData root;
2624 root.id = 1;
2625 root.role = ax::mojom::Role::kRootWebArea;
2626 root.AddState(ax::mojom::State::kFocusable);
2627
2628 ui::AXNodeData combo_box;
2629 combo_box.id = 2;
2630 combo_box.role = ax::mojom::Role::kTextFieldWithComboBox;
2631 combo_box.AddState(ax::mojom::State::kEditable);
2632 combo_box.AddState(ax::mojom::State::kFocusable);
2633 combo_box.SetValue(value1 + value2);
2634
2635 ui::AXNodeData combo_box_div;
2636 combo_box_div.id = 3;
2637 combo_box_div.role = ax::mojom::Role::kGenericContainer;
2638 combo_box_div.AddState(ax::mojom::State::kEditable);
2639
2640 ui::AXNodeData static_text1;
2641 static_text1.id = 4;
2642 static_text1.role = ax::mojom::Role::kStaticText;
2643 static_text1.AddState(ax::mojom::State::kEditable);
2644 static_text1.SetName(value1);
2645
2646 ui::AXNodeData static_text2;
2647 static_text2.id = 5;
2648 static_text2.role = ax::mojom::Role::kStaticText;
2649 static_text2.AddState(ax::mojom::State::kEditable);
2650 static_text2.SetName(value2);
2651
2652 std::vector<int32_t> marker_types;
2653 marker_types.push_back(
2654 static_cast<int32_t>(ax::mojom::MarkerType::kSpelling));
2655 std::vector<int32_t> marker_starts;
2656 marker_starts.push_back(0);
2657 std::vector<int32_t> marker_ends;
2658 marker_ends.push_back(4);
2659 static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes,
2660 marker_types);
2661 static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts,
2662 marker_starts);
2663 static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds,
2664 marker_ends);
2665
2666 root.child_ids.push_back(combo_box.id);
2667 combo_box.child_ids.push_back(combo_box_div.id);
2668 combo_box_div.child_ids.push_back(static_text1.id);
2669 combo_box_div.child_ids.push_back(static_text2.id);
2670
2671 std::unique_ptr<BrowserAccessibilityManager> manager(
2672 BrowserAccessibilityManager::Create(
2673 MakeAXTreeUpdate(root, combo_box, combo_box_div, static_text1,
2674 static_text2),
2675 test_browser_accessibility_delegate_.get()));
2676
2677 ASSERT_NE(nullptr, manager->GetRoot());
2678 BrowserAccessibilityWin* ax_root =
2679 ToBrowserAccessibilityWin(manager->GetRoot());
2680 ASSERT_NE(nullptr, ax_root);
2681 ASSERT_EQ(1U, ax_root->PlatformChildCount());
2682
2683 BrowserAccessibilityWin* ax_combo_box =
2684 ToBrowserAccessibilityWin(ax_root->PlatformGetChild(0));
2685 ASSERT_NE(nullptr, ax_combo_box);
2686
2687 HRESULT hr;
2688 LONG start_offset, end_offset;
2689 base::win::ScopedBstr text_attributes;
2690
2691 // Ensure that the first part of the value is not marked misspelled.
2692 for (LONG offset = 0; offset < value1_length; ++offset) {
2693 hr = ax_combo_box->GetCOM()->get_attributes(
2694 offset, &start_offset, &end_offset, text_attributes.Receive());
2695 EXPECT_TRUE(base::string16(text_attributes.Get()).empty());
2696 EXPECT_EQ(0, start_offset);
2697 EXPECT_EQ(value1_length, end_offset);
2698 text_attributes.Reset();
2699 }
2700
2701 // Ensure that "helo" is marked misspelled.
2702 for (LONG offset = value1_length; offset < value1_length + 4; ++offset) {
2703 hr = ax_combo_box->GetCOM()->get_attributes(
2704 offset, &start_offset, &end_offset, text_attributes.Receive());
2705 EXPECT_EQ(S_OK, hr);
2706 EXPECT_EQ(value1_length, start_offset);
2707 EXPECT_EQ(value1_length + 4, end_offset);
2708 EXPECT_NE(base::string16::npos,
2709 base::string16(text_attributes.Get()).find(L"invalid:spelling"));
2710 text_attributes.Reset();
2711 }
2712
2713 // Ensure that the last part of the value is not marked misspelled.
2714 for (LONG offset = value1_length + 4; offset < combo_box_value_length;
2715 ++offset) {
2716 hr = ax_combo_box->GetCOM()->get_attributes(
2717 offset, &start_offset, &end_offset, text_attributes.Receive());
2718 EXPECT_TRUE(base::string16(text_attributes.Get()).empty());
2719 EXPECT_EQ(value1_length + 4, start_offset);
2720 EXPECT_EQ(combo_box_value_length, end_offset);
2721 text_attributes.Reset();
2722 }
2723
2724 manager.reset();
2725 }
2726
TEST_F(BrowserAccessibilityWinTest,TestNewMisspellingsInSimpleTextFields)2727 TEST_F(BrowserAccessibilityWinTest, TestNewMisspellingsInSimpleTextFields) {
2728 std::string value1("Testing .");
2729 // The word "helo" is misspelled.
2730 std::string value2("Helo there.");
2731
2732 LONG value1_length = static_cast<LONG>(value1.length());
2733 LONG value2_length = static_cast<LONG>(value2.length());
2734 LONG combo_box_value_length = value1_length + value2_length;
2735
2736 ui::AXNodeData root;
2737 root.id = 1;
2738 root.role = ax::mojom::Role::kRootWebArea;
2739 root.AddState(ax::mojom::State::kFocusable);
2740
2741 ui::AXNodeData combo_box;
2742 combo_box.id = 2;
2743 combo_box.role = ax::mojom::Role::kTextFieldWithComboBox;
2744 combo_box.AddState(ax::mojom::State::kEditable);
2745 combo_box.AddState(ax::mojom::State::kFocusable);
2746 combo_box.SetValue(value1 + value2);
2747
2748 ui::AXNodeData combo_box_div;
2749 combo_box_div.id = 3;
2750 combo_box_div.role = ax::mojom::Role::kGenericContainer;
2751 combo_box_div.AddState(ax::mojom::State::kEditable);
2752
2753 ui::AXNodeData static_text1;
2754 static_text1.id = 4;
2755 static_text1.role = ax::mojom::Role::kStaticText;
2756 static_text1.AddState(ax::mojom::State::kEditable);
2757 static_text1.SetName(value1);
2758
2759 ui::AXNodeData static_text2;
2760 static_text2.id = 5;
2761 static_text2.role = ax::mojom::Role::kStaticText;
2762 static_text2.AddState(ax::mojom::State::kEditable);
2763 static_text2.SetName(value2);
2764
2765 root.child_ids.push_back(combo_box.id);
2766 combo_box.child_ids.push_back(combo_box_div.id);
2767 combo_box_div.child_ids.push_back(static_text1.id);
2768 combo_box_div.child_ids.push_back(static_text2.id);
2769
2770 std::unique_ptr<BrowserAccessibilityManager> manager(
2771 BrowserAccessibilityManager::Create(
2772 MakeAXTreeUpdate(root, combo_box, combo_box_div, static_text1,
2773 static_text2),
2774 test_browser_accessibility_delegate_.get()));
2775
2776 ASSERT_NE(nullptr, manager->GetRoot());
2777 BrowserAccessibilityWin* ax_root =
2778 ToBrowserAccessibilityWin(manager->GetRoot());
2779 ASSERT_NE(nullptr, ax_root);
2780 ASSERT_EQ(1U, ax_root->PlatformChildCount());
2781
2782 BrowserAccessibilityWin* ax_combo_box =
2783 ToBrowserAccessibilityWin(ax_root->PlatformGetChild(0));
2784 ASSERT_NE(nullptr, ax_combo_box);
2785
2786 HRESULT hr;
2787 LONG start_offset, end_offset;
2788 base::win::ScopedBstr text_attributes;
2789
2790 // Ensure that nothing is marked misspelled.
2791 for (LONG offset = 0; offset < combo_box_value_length; ++offset) {
2792 hr = ax_combo_box->GetCOM()->get_attributes(
2793 offset, &start_offset, &end_offset, text_attributes.Receive());
2794 EXPECT_TRUE(base::string16(text_attributes.Get()).empty());
2795 EXPECT_EQ(0, start_offset);
2796 EXPECT_EQ(combo_box_value_length, end_offset);
2797 text_attributes.Reset();
2798 }
2799
2800 // Add the spelling markers on "helo".
2801 std::vector<int32_t> marker_types{
2802 static_cast<int32_t>(ax::mojom::MarkerType::kSpelling)};
2803 std::vector<int32_t> marker_starts{0};
2804 std::vector<int32_t> marker_ends{4};
2805 static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes,
2806 marker_types);
2807 static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts,
2808 marker_starts);
2809 static_text2.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds,
2810 marker_ends);
2811 ui::AXTree* tree = const_cast<ui::AXTree*>(manager->ax_tree());
2812 ASSERT_NE(nullptr, tree);
2813 ASSERT_TRUE(tree->Unserialize(MakeAXTreeUpdate(static_text2)));
2814
2815 // Ensure that value1 is still not marked misspelled.
2816 for (LONG offset = 0; offset < value1_length; ++offset) {
2817 hr = ax_combo_box->GetCOM()->get_attributes(
2818 offset, &start_offset, &end_offset, text_attributes.Receive());
2819 EXPECT_TRUE(base::string16(text_attributes.Get()).empty());
2820 EXPECT_EQ(0, start_offset);
2821 EXPECT_EQ(value1_length, end_offset);
2822 text_attributes.Reset();
2823 }
2824
2825 // Ensure that "helo" is now marked misspelled.
2826 for (LONG offset = value1_length; offset < value1_length + 4; ++offset) {
2827 hr = ax_combo_box->GetCOM()->get_attributes(
2828 offset, &start_offset, &end_offset, text_attributes.Receive());
2829 EXPECT_EQ(S_OK, hr);
2830 EXPECT_EQ(value1_length, start_offset);
2831 EXPECT_EQ(value1_length + 4, end_offset);
2832 EXPECT_NE(base::string16::npos,
2833 base::string16(text_attributes.Get()).find(L"invalid:spelling"));
2834 text_attributes.Reset();
2835 }
2836
2837 // Ensure that the last part of the value is not marked misspelled.
2838 for (LONG offset = value1_length + 4; offset < combo_box_value_length;
2839 ++offset) {
2840 hr = ax_combo_box->GetCOM()->get_attributes(
2841 offset, &start_offset, &end_offset, text_attributes.Receive());
2842 EXPECT_TRUE(base::string16(text_attributes.Get()).empty());
2843 EXPECT_EQ(value1_length + 4, start_offset);
2844 EXPECT_EQ(combo_box_value_length, end_offset);
2845 text_attributes.Reset();
2846 }
2847
2848 manager.reset();
2849 }
2850
TEST_F(BrowserAccessibilityWinTest,TestDeepestFirstLastChild)2851 TEST_F(BrowserAccessibilityWinTest, TestDeepestFirstLastChild) {
2852 ui::AXNodeData root;
2853 root.id = 1;
2854 root.role = ax::mojom::Role::kRootWebArea;
2855
2856 ui::AXNodeData child1;
2857 child1.id = 2;
2858 child1.role = ax::mojom::Role::kStaticText;
2859 root.child_ids.push_back(2);
2860
2861 ui::AXNodeData child2;
2862 child2.id = 3;
2863 child2.role = ax::mojom::Role::kStaticText;
2864 root.child_ids.push_back(3);
2865
2866 ui::AXNodeData child2_child1;
2867 child2_child1.id = 4;
2868 child2_child1.role = ax::mojom::Role::kInlineTextBox;
2869 child2.child_ids.push_back(4);
2870
2871 ui::AXNodeData child2_child2;
2872 child2_child2.id = 5;
2873 child2_child2.role = ax::mojom::Role::kInlineTextBox;
2874 child2.child_ids.push_back(5);
2875
2876 std::unique_ptr<BrowserAccessibilityManager> manager(
2877 BrowserAccessibilityManager::Create(
2878 MakeAXTreeUpdate(root, child1, child2, child2_child1, child2_child2),
2879 test_browser_accessibility_delegate_.get()));
2880
2881 BrowserAccessibility* root_accessible = manager->GetRoot();
2882 ASSERT_NE(nullptr, root_accessible);
2883 ASSERT_EQ(2U, root_accessible->PlatformChildCount());
2884 BrowserAccessibility* child1_accessible =
2885 root_accessible->PlatformGetChild(0);
2886 ASSERT_NE(nullptr, child1_accessible);
2887 BrowserAccessibility* child2_accessible =
2888 root_accessible->PlatformGetChild(1);
2889 ASSERT_NE(nullptr, child2_accessible);
2890 ASSERT_EQ(0U, child2_accessible->PlatformChildCount());
2891 ASSERT_EQ(2U, child2_accessible->InternalChildCount());
2892 BrowserAccessibility* child2_child1_accessible =
2893 child2_accessible->InternalGetChild(0);
2894 ASSERT_NE(nullptr, child2_child1_accessible);
2895 BrowserAccessibility* child2_child2_accessible =
2896 child2_accessible->InternalGetChild(1);
2897 ASSERT_NE(nullptr, child2_child2_accessible);
2898
2899 EXPECT_EQ(child1_accessible, root_accessible->PlatformDeepestFirstChild());
2900 EXPECT_EQ(child1_accessible, root_accessible->InternalDeepestFirstChild());
2901
2902 EXPECT_EQ(child2_accessible, root_accessible->PlatformDeepestLastChild());
2903 EXPECT_EQ(child2_child2_accessible,
2904 root_accessible->InternalDeepestLastChild());
2905
2906 EXPECT_EQ(nullptr, child1_accessible->PlatformDeepestFirstChild());
2907 EXPECT_EQ(nullptr, child1_accessible->InternalDeepestFirstChild());
2908
2909 EXPECT_EQ(nullptr, child1_accessible->PlatformDeepestLastChild());
2910 EXPECT_EQ(nullptr, child1_accessible->InternalDeepestLastChild());
2911
2912 EXPECT_EQ(nullptr, child2_accessible->PlatformDeepestFirstChild());
2913 EXPECT_EQ(child2_child1_accessible,
2914 child2_accessible->InternalDeepestFirstChild());
2915
2916 EXPECT_EQ(nullptr, child2_accessible->PlatformDeepestLastChild());
2917 EXPECT_EQ(child2_child2_accessible,
2918 child2_accessible->InternalDeepestLastChild());
2919
2920 EXPECT_EQ(nullptr, child2_child1_accessible->PlatformDeepestFirstChild());
2921 EXPECT_EQ(nullptr, child2_child1_accessible->InternalDeepestFirstChild());
2922 EXPECT_EQ(nullptr, child2_child1_accessible->PlatformDeepestLastChild());
2923 EXPECT_EQ(nullptr, child2_child1_accessible->InternalDeepestLastChild());
2924 EXPECT_EQ(nullptr, child2_child2_accessible->PlatformDeepestFirstChild());
2925 EXPECT_EQ(nullptr, child2_child2_accessible->InternalDeepestFirstChild());
2926 EXPECT_EQ(nullptr, child2_child2_accessible->PlatformDeepestLastChild());
2927 EXPECT_EQ(nullptr, child2_child2_accessible->InternalDeepestLastChild());
2928 }
2929
TEST_F(BrowserAccessibilityWinTest,TestInheritedStringAttributes)2930 TEST_F(BrowserAccessibilityWinTest, TestInheritedStringAttributes) {
2931 ui::AXNodeData root;
2932 root.id = 1;
2933 root.role = ax::mojom::Role::kRootWebArea;
2934 root.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "en-US");
2935 root.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, "Helvetica");
2936
2937 ui::AXNodeData child1;
2938 child1.id = 2;
2939 child1.role = ax::mojom::Role::kStaticText;
2940 root.child_ids.push_back(2);
2941
2942 ui::AXNodeData child2;
2943 child2.id = 3;
2944 child2.role = ax::mojom::Role::kStaticText;
2945 child2.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr");
2946 child2.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, "Arial");
2947 root.child_ids.push_back(3);
2948
2949 ui::AXNodeData child2_child1;
2950 child2_child1.id = 4;
2951 child2_child1.role = ax::mojom::Role::kInlineTextBox;
2952 child2.child_ids.push_back(4);
2953
2954 ui::AXNodeData child2_child2;
2955 child2_child2.id = 5;
2956 child2_child2.role = ax::mojom::Role::kInlineTextBox;
2957 child2.child_ids.push_back(5);
2958
2959 std::unique_ptr<BrowserAccessibilityManager> manager(
2960 BrowserAccessibilityManager::Create(
2961 MakeAXTreeUpdate(root, child1, child2, child2_child1, child2_child2),
2962 test_browser_accessibility_delegate_.get()));
2963
2964 BrowserAccessibility* root_accessible = manager->GetRoot();
2965 ASSERT_NE(nullptr, root_accessible);
2966 BrowserAccessibility* child1_accessible =
2967 root_accessible->PlatformGetChild(0);
2968 ASSERT_NE(nullptr, child1_accessible);
2969 BrowserAccessibility* child2_accessible =
2970 root_accessible->PlatformGetChild(1);
2971 ASSERT_NE(nullptr, child2_accessible);
2972 BrowserAccessibility* child2_child1_accessible =
2973 child2_accessible->InternalGetChild(0);
2974 ASSERT_NE(nullptr, child2_child1_accessible);
2975 BrowserAccessibility* child2_child2_accessible =
2976 child2_accessible->InternalGetChild(1);
2977 ASSERT_NE(nullptr, child2_child2_accessible);
2978
2979 // Test GetInheritedString16Attribute(attribute).
2980 EXPECT_EQ(base::UTF8ToUTF16("en-US"),
2981 root_accessible->GetInheritedString16Attribute(
2982 ax::mojom::StringAttribute::kLanguage));
2983 EXPECT_EQ(base::UTF8ToUTF16("en-US"),
2984 child1_accessible->GetInheritedString16Attribute(
2985 ax::mojom::StringAttribute::kLanguage));
2986 EXPECT_EQ(base::UTF8ToUTF16("fr"),
2987 child2_accessible->GetInheritedString16Attribute(
2988 ax::mojom::StringAttribute::kLanguage));
2989 EXPECT_EQ(base::UTF8ToUTF16("fr"),
2990 child2_child1_accessible->GetInheritedString16Attribute(
2991 ax::mojom::StringAttribute::kLanguage));
2992 EXPECT_EQ(base::UTF8ToUTF16("fr"),
2993 child2_child2_accessible->GetInheritedString16Attribute(
2994 ax::mojom::StringAttribute::kLanguage));
2995
2996 // Test GetInheritedStringAttribute(attribute).
2997 EXPECT_EQ("Helvetica", root_accessible->GetInheritedStringAttribute(
2998 ax::mojom::StringAttribute::kFontFamily));
2999 EXPECT_EQ("Helvetica", child1_accessible->GetInheritedStringAttribute(
3000 ax::mojom::StringAttribute::kFontFamily));
3001 EXPECT_EQ("Arial", child2_accessible->GetInheritedStringAttribute(
3002 ax::mojom::StringAttribute::kFontFamily));
3003 EXPECT_EQ("Arial", child2_child1_accessible->GetInheritedStringAttribute(
3004 ax::mojom::StringAttribute::kFontFamily));
3005 EXPECT_EQ("Arial", child2_child2_accessible->GetInheritedStringAttribute(
3006 ax::mojom::StringAttribute::kFontFamily));
3007 }
3008
TEST_F(BrowserAccessibilityWinTest,UniqueIdWinInvalidAfterDeletingTree)3009 TEST_F(BrowserAccessibilityWinTest, UniqueIdWinInvalidAfterDeletingTree) {
3010 ui::AXNodeData root_node;
3011 root_node.id = 1;
3012 root_node.role = ax::mojom::Role::kRootWebArea;
3013
3014 ui::AXNodeData child_node;
3015 child_node.id = 2;
3016 root_node.child_ids.push_back(2);
3017
3018 std::unique_ptr<BrowserAccessibilityManagerWin> manager(
3019 new BrowserAccessibilityManagerWin(
3020 MakeAXTreeUpdate(root_node, child_node),
3021 test_browser_accessibility_delegate_.get()));
3022
3023 BrowserAccessibility* root = manager->GetRoot();
3024 int32_t root_unique_id = GetUniqueId(root);
3025 BrowserAccessibility* child = root->PlatformGetChild(0);
3026 int32_t child_unique_id = GetUniqueId(child);
3027
3028 // Now destroy that original tree and create a new tree.
3029 manager.reset(new BrowserAccessibilityManagerWin(
3030 MakeAXTreeUpdate(root_node, child_node),
3031 test_browser_accessibility_delegate_.get()));
3032 root = manager->GetRoot();
3033 int32_t root_unique_id_2 = GetUniqueId(root);
3034 child = root->PlatformGetChild(0);
3035 int32_t child_unique_id_2 = GetUniqueId(child);
3036
3037 // The nodes in the new tree should not have the same ids.
3038 EXPECT_NE(root_unique_id, root_unique_id_2);
3039 EXPECT_NE(child_unique_id, child_unique_id_2);
3040
3041 // Trying to access the unique IDs of the old, deleted objects should fail.
3042 base::win::ScopedVariant old_root_variant(-root_unique_id);
3043 Microsoft::WRL::ComPtr<IDispatch> old_root_dispatch;
3044 HRESULT hr = ToBrowserAccessibilityWin(root)->GetCOM()->get_accChild(
3045 old_root_variant, &old_root_dispatch);
3046 EXPECT_EQ(E_INVALIDARG, hr);
3047
3048 base::win::ScopedVariant old_child_variant(-child_unique_id);
3049 Microsoft::WRL::ComPtr<IDispatch> old_child_dispatch;
3050 hr = ToBrowserAccessibilityWin(root)->GetCOM()->get_accChild(
3051 old_child_variant, &old_child_dispatch);
3052 EXPECT_EQ(E_INVALIDARG, hr);
3053
3054 // Trying to access the unique IDs of the new objects should succeed.
3055 base::win::ScopedVariant new_root_variant(-root_unique_id_2);
3056 Microsoft::WRL::ComPtr<IDispatch> new_root_dispatch;
3057 hr = ToBrowserAccessibilityWin(root)->GetCOM()->get_accChild(
3058 new_root_variant, &new_root_dispatch);
3059 EXPECT_EQ(S_OK, hr);
3060
3061 base::win::ScopedVariant new_child_variant(-child_unique_id_2);
3062 Microsoft::WRL::ComPtr<IDispatch> new_child_dispatch;
3063 hr = ToBrowserAccessibilityWin(root)->GetCOM()->get_accChild(
3064 new_child_variant, &new_child_dispatch);
3065 EXPECT_EQ(S_OK, hr);
3066 }
3067
TEST_F(BrowserAccessibilityWinTest,AccChildOnlyReturnsDescendants)3068 TEST_F(BrowserAccessibilityWinTest, AccChildOnlyReturnsDescendants) {
3069 ui::AXNodeData root_node;
3070 root_node.id = 1;
3071 root_node.role = ax::mojom::Role::kRootWebArea;
3072
3073 ui::AXNodeData child_node;
3074 child_node.id = 2;
3075 root_node.child_ids.push_back(2);
3076
3077 std::unique_ptr<BrowserAccessibilityManagerWin> manager(
3078 new BrowserAccessibilityManagerWin(
3079 MakeAXTreeUpdate(root_node, child_node),
3080 test_browser_accessibility_delegate_.get()));
3081
3082 BrowserAccessibility* root = manager->GetRoot();
3083 BrowserAccessibility* child = root->PlatformGetChild(0);
3084
3085 base::win::ScopedVariant root_unique_id_variant(-GetUniqueId(root));
3086 Microsoft::WRL::ComPtr<IDispatch> result;
3087 EXPECT_EQ(E_INVALIDARG,
3088 ToBrowserAccessibilityWin(child)->GetCOM()->get_accChild(
3089 root_unique_id_variant, &result));
3090
3091 base::win::ScopedVariant child_unique_id_variant(-GetUniqueId(child));
3092 EXPECT_EQ(S_OK, ToBrowserAccessibilityWin(root)->GetCOM()->get_accChild(
3093 child_unique_id_variant, &result));
3094 }
3095
3096 // TODO(crbug.com/929563): Disabled due to flakiness.
TEST_F(BrowserAccessibilityWinTest,DISABLED_TestIAccessible2Relations)3097 TEST_F(BrowserAccessibilityWinTest, DISABLED_TestIAccessible2Relations) {
3098 ui::AXNodeData root;
3099 root.id = 1;
3100 root.role = ax::mojom::Role::kRootWebArea;
3101 // Reflexive relations should be ignored.
3102 std::vector<int32_t> describedby_ids = {1, 2, 3};
3103 root.AddIntListAttribute(ax::mojom::IntListAttribute::kDescribedbyIds,
3104 describedby_ids);
3105
3106 ui::AXNodeData child1;
3107 child1.id = 2;
3108 child1.role = ax::mojom::Role::kStaticText;
3109 root.child_ids.push_back(2);
3110
3111 ui::AXNodeData child2;
3112 child2.id = 3;
3113 child2.role = ax::mojom::Role::kStaticText;
3114 root.child_ids.push_back(3);
3115
3116 std::unique_ptr<BrowserAccessibilityManager> manager(
3117 BrowserAccessibilityManager::Create(
3118 MakeAXTreeUpdate(root, child1, child2),
3119 test_browser_accessibility_delegate_.get()));
3120
3121 BrowserAccessibilityWin* ax_root =
3122 ToBrowserAccessibilityWin(manager->GetRoot());
3123 ASSERT_NE(nullptr, ax_root);
3124 BrowserAccessibilityWin* ax_child1 =
3125 ToBrowserAccessibilityWin(ax_root->PlatformGetChild(0));
3126 ASSERT_NE(nullptr, ax_child1);
3127 BrowserAccessibilityWin* ax_child2 =
3128 ToBrowserAccessibilityWin(ax_root->PlatformGetChild(1));
3129 ASSERT_NE(nullptr, ax_child2);
3130
3131 LONG n_relations = 0;
3132 LONG n_targets = 0;
3133 LONG unique_id = 0;
3134 base::win::ScopedBstr relation_type;
3135 Microsoft::WRL::ComPtr<IAccessibleRelation> describedby_relation;
3136 Microsoft::WRL::ComPtr<IAccessibleRelation> description_for_relation;
3137 Microsoft::WRL::ComPtr<IUnknown> target;
3138 Microsoft::WRL::ComPtr<IAccessible2> ax_target;
3139
3140 EXPECT_HRESULT_SUCCEEDED(ax_root->GetCOM()->get_nRelations(&n_relations));
3141 EXPECT_EQ(1, n_relations);
3142
3143 EXPECT_HRESULT_SUCCEEDED(
3144 ax_root->GetCOM()->get_relation(0, &describedby_relation));
3145 EXPECT_HRESULT_SUCCEEDED(
3146 describedby_relation->get_relationType(relation_type.Receive()));
3147 EXPECT_EQ(L"describedBy", base::string16(relation_type.Get()));
3148 relation_type.Reset();
3149
3150 EXPECT_HRESULT_SUCCEEDED(describedby_relation->get_nTargets(&n_targets));
3151 EXPECT_EQ(2, n_targets);
3152
3153 EXPECT_HRESULT_SUCCEEDED(describedby_relation->get_target(0, &target));
3154 target.As(&ax_target);
3155 EXPECT_HRESULT_SUCCEEDED(ax_target->get_uniqueID(&unique_id));
3156 EXPECT_EQ(-GetUniqueId(ax_child1), unique_id);
3157 ax_target.Reset();
3158 target.Reset();
3159
3160 EXPECT_HRESULT_SUCCEEDED(describedby_relation->get_target(1, &target));
3161 target.As(&ax_target);
3162 EXPECT_HRESULT_SUCCEEDED(ax_target->get_uniqueID(&unique_id));
3163 EXPECT_EQ(-GetUniqueId(ax_child2), unique_id);
3164 ax_target.Reset();
3165 target.Reset();
3166 describedby_relation.Reset();
3167
3168 // Test the reverse relations.
3169 EXPECT_HRESULT_SUCCEEDED(ax_child1->GetCOM()->get_nRelations(&n_relations));
3170 EXPECT_EQ(1, n_relations);
3171
3172 EXPECT_HRESULT_SUCCEEDED(
3173 ax_child1->GetCOM()->get_relation(0, &description_for_relation));
3174 EXPECT_HRESULT_SUCCEEDED(
3175 description_for_relation->get_relationType(relation_type.Receive()));
3176 EXPECT_EQ(L"descriptionFor", base::string16(relation_type.Get()));
3177 relation_type.Reset();
3178
3179 EXPECT_HRESULT_SUCCEEDED(description_for_relation->get_nTargets(&n_targets));
3180 EXPECT_EQ(1, n_targets);
3181
3182 EXPECT_HRESULT_SUCCEEDED(description_for_relation->get_target(0, &target));
3183 target.As(&ax_target);
3184 EXPECT_HRESULT_SUCCEEDED(ax_target->get_uniqueID(&unique_id));
3185 EXPECT_EQ(-GetUniqueId(ax_root), unique_id);
3186 ax_target.Reset();
3187 target.Reset();
3188 description_for_relation.Reset();
3189
3190 EXPECT_HRESULT_SUCCEEDED(ax_child2->GetCOM()->get_nRelations(&n_relations));
3191 EXPECT_EQ(1, n_relations);
3192
3193 EXPECT_HRESULT_SUCCEEDED(
3194 ax_child2->GetCOM()->get_relation(0, &description_for_relation));
3195 EXPECT_HRESULT_SUCCEEDED(
3196 description_for_relation->get_relationType(relation_type.Receive()));
3197 EXPECT_EQ(L"descriptionFor", base::string16(relation_type.Get()));
3198 relation_type.Reset();
3199
3200 EXPECT_HRESULT_SUCCEEDED(description_for_relation->get_nTargets(&n_targets));
3201 EXPECT_EQ(1, n_targets);
3202
3203 EXPECT_HRESULT_SUCCEEDED(description_for_relation->get_target(0, &target));
3204 target.As(&ax_target);
3205 EXPECT_HRESULT_SUCCEEDED(ax_target->get_uniqueID(&unique_id));
3206 EXPECT_EQ(-GetUniqueId(ax_root), unique_id);
3207 ax_target.Reset();
3208 target.Reset();
3209
3210 // Try adding one more relation.
3211 std::vector<int32_t> labelledby_ids = {3};
3212 child1.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
3213 labelledby_ids);
3214 AXEventNotificationDetails event_bundle;
3215 event_bundle.updates.resize(1);
3216 event_bundle.updates[0].nodes.push_back(child1);
3217 ASSERT_TRUE(manager->OnAccessibilityEvents(event_bundle));
3218
3219 EXPECT_HRESULT_SUCCEEDED(ax_child1->GetCOM()->get_nRelations(&n_relations));
3220 EXPECT_EQ(2, n_relations);
3221 EXPECT_HRESULT_SUCCEEDED(ax_child2->GetCOM()->get_nRelations(&n_relations));
3222 EXPECT_EQ(2, n_relations);
3223 }
3224
TEST_F(BrowserAccessibilityWinTest,TestUIASwitch)3225 TEST_F(BrowserAccessibilityWinTest, TestUIASwitch) {
3226 EXPECT_FALSE(::switches::IsExperimentalAccessibilityPlatformUIAEnabled());
3227
3228 base::CommandLine::ForCurrentProcess()->AppendSwitch(
3229 ::switches::kEnableExperimentalUIAutomation);
3230
3231 EXPECT_TRUE(::switches::IsExperimentalAccessibilityPlatformUIAEnabled());
3232 }
3233
3234 } // namespace content
3235