Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ax_platform_node_textprovider_win_unittest.cc
Go to the documentation of this file.
1// Copyright 2019 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
6
7#include <UIAutomationClient.h>
8#include <UIAutomationCoreApi.h>
9
10#include <vector>
11
12#include "ax/ax_action_data.h"
19
20#include "flutter/fml/logging.h"
21#include "flutter/fml/platform/win/wstring_conversion.h"
22
23using Microsoft::WRL::ComPtr;
24
25namespace ui {
26
27// Helper macros for UIAutomation HRESULT expectations
28#define EXPECT_UIA_INVALIDOPERATION(expr) \
29 EXPECT_EQ(static_cast<HRESULT>(UIA_E_INVALIDOPERATION), (expr))
30#define EXPECT_INVALIDARG(expr) \
31 EXPECT_EQ(static_cast<HRESULT>(E_INVALIDARG), (expr))
32
34 public:
36 ~AXPlatformNodeTextProviderTest() override = default;
38 delete;
40 const AXPlatformNodeTextProviderTest&) = delete;
41
42 protected:
43 void SetOwner(AXPlatformNodeWin* owner,
44 ITextRangeProvider* destination_range) {
45 ComPtr<ITextRangeProvider> destination_provider = destination_range;
46 ComPtr<AXPlatformNodeTextRangeProviderWin> destination_provider_interal;
47
48 destination_provider->QueryInterface(
49 IID_PPV_ARGS(&destination_provider_interal));
50 destination_provider_interal->SetOwnerForTesting(owner);
51 }
52 AXPlatformNodeWin* GetOwner(
53 const AXPlatformNodeTextProviderWin* text_provider) {
54 return text_provider->owner_.Get();
55 }
57 const AXPlatformNodeTextRangeProviderWin* text_range) {
58 return text_range->start();
59 }
61 const AXPlatformNodeTextRangeProviderWin* text_range) {
62 return text_range->end();
63 }
64};
65
66TEST_F(AXPlatformNodeTextProviderTest, CreateDegenerateRangeFromStart) {
67 AXNodeData text1_data;
68 text1_data.id = 3;
70 text1_data.SetName("some text");
71
72 AXNodeData text2_data;
73 text2_data.id = 4;
75 text2_data.SetName("more text");
76
77 AXNodeData link_data;
78 link_data.id = 2;
79 link_data.role = ax::mojom::Role::kLink;
80 link_data.child_ids = {3, 4};
81
82 AXNodeData root_data;
83 root_data.id = 1;
85 root_data.SetName("Document");
86 root_data.child_ids = {2};
87
89 AXTreeData tree_data;
91 update.tree_data = tree_data;
92 update.has_tree_data = true;
93 update.root_id = root_data.id;
94 update.nodes = {root_data, link_data, text1_data, text2_data};
95
96 Init(update);
97 AXNode* root_node = GetRootAsAXNode();
98 AXNode* link_node = root_node->children()[0];
99 AXNode* text2_node = link_node->children()[1];
100 AXPlatformNodeWin* owner =
101 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(root_node));
102 BASE_DCHECK(owner);
103
104 ComPtr<IRawElementProviderSimple> root_node_raw =
105 QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
106 ComPtr<IRawElementProviderSimple> link_node_raw =
107 QueryInterfaceFromNode<IRawElementProviderSimple>(link_node);
108 ComPtr<IRawElementProviderSimple> text2_node_raw =
109 QueryInterfaceFromNode<IRawElementProviderSimple>(text2_node);
110
111 ComPtr<AXPlatformNodeWin> root_platform_node;
112 EXPECT_HRESULT_SUCCEEDED(
113 root_node_raw->QueryInterface(IID_PPV_ARGS(&root_platform_node)));
114 ComPtr<AXPlatformNodeWin> link_platform_node;
115 EXPECT_HRESULT_SUCCEEDED(
116 link_node_raw->QueryInterface(IID_PPV_ARGS(&link_platform_node)));
117 ComPtr<AXPlatformNodeWin> text2_platform_node;
118 EXPECT_HRESULT_SUCCEEDED(
119 text2_node_raw->QueryInterface(IID_PPV_ARGS(&text2_platform_node)));
120
121 // Degenerate range created on root node should be:
122 // <>some textmore text
123 ComPtr<ITextRangeProvider> text_range_provider =
124 AXPlatformNodeTextProviderWin::CreateDegenerateRangeAtStart(
125 root_platform_node.Get());
126 SetOwner(owner, text_range_provider.Get());
127 base::win::ScopedBstr text_content;
128 EXPECT_HRESULT_SUCCEEDED(
129 text_range_provider->GetText(-1, text_content.Receive()));
130 EXPECT_EQ(0, wcscmp(text_content.Get(), L""));
131
132 ComPtr<AXPlatformNodeTextRangeProviderWin> actual_range;
133 text_range_provider->QueryInterface(IID_PPV_ARGS(&actual_range));
134 AXNodePosition::AXPositionInstance expected_start, expected_end;
135 expected_start = root_platform_node->GetDelegate()->CreateTextPositionAt(0);
136 expected_end = expected_start->Clone();
137 EXPECT_EQ(*GetStart(actual_range.Get()), *expected_start);
138 EXPECT_EQ(*GetEnd(actual_range.Get()), *expected_end);
139 text_content.Release();
140
141 // Degenerate range created on link node should be:
142 // <>some textmore text
143 text_range_provider =
144 AXPlatformNodeTextProviderWin::CreateDegenerateRangeAtStart(
145 link_platform_node.Get());
146 SetOwner(owner, text_range_provider.Get());
147 EXPECT_HRESULT_SUCCEEDED(
148 text_range_provider->GetText(-1, text_content.Receive()));
149 EXPECT_EQ(0, wcscmp(text_content.Get(), L""));
150 text_range_provider->QueryInterface(IID_PPV_ARGS(&actual_range));
151 EXPECT_EQ(*GetStart(actual_range.Get()), *expected_start);
152 EXPECT_EQ(*GetEnd(actual_range.Get()), *expected_end);
153 text_content.Release();
154
155 // Degenerate range created on more text node should be:
156 // some text<>more text
157 text_range_provider =
158 AXPlatformNodeTextProviderWin::CreateDegenerateRangeAtStart(
159 text2_platform_node.Get());
160 SetOwner(owner, text_range_provider.Get());
161 EXPECT_HRESULT_SUCCEEDED(
162 text_range_provider->GetText(-1, text_content.Receive()));
163 EXPECT_EQ(0, wcscmp(text_content.Get(), L""));
164 text_range_provider->QueryInterface(IID_PPV_ARGS(&actual_range));
165 expected_start = text2_platform_node->GetDelegate()->CreateTextPositionAt(0);
166 expected_end = expected_start->Clone();
167 EXPECT_EQ(*GetStart(actual_range.Get()), *expected_start);
168 EXPECT_EQ(*GetEnd(actual_range.Get()), *expected_end);
169 text_content.Release();
170}
171
172TEST_F(AXPlatformNodeTextProviderTest, ITextProviderRangeFromChild) {
173 AXNodeData text_data;
174 text_data.id = 2;
176 text_data.SetName("some text");
177
178 AXNodeData empty_text_data;
179 empty_text_data.id = 3;
180 empty_text_data.role = ax::mojom::Role::kStaticText;
181
182 AXNodeData root_data;
183 root_data.id = 1;
185 root_data.SetName("Document");
186 root_data.child_ids.push_back(2);
187 root_data.child_ids.push_back(3);
188
190 AXTreeData tree_data;
192 update.tree_data = tree_data;
193 update.has_tree_data = true;
194 update.root_id = root_data.id;
195 update.nodes.push_back(root_data);
196 update.nodes.push_back(text_data);
197 update.nodes.push_back(empty_text_data);
198
199 Init(update);
200
201 AXNode* root_node = GetRootAsAXNode();
202 AXNode* text_node = root_node->children()[0];
203 AXNode* empty_text_node = root_node->children()[1];
204 AXPlatformNodeWin* owner =
205 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(root_node));
206 BASE_DCHECK(owner);
207
208 ComPtr<IRawElementProviderSimple> root_node_raw =
209 QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
210 ComPtr<IRawElementProviderSimple> text_node_raw =
211 QueryInterfaceFromNode<IRawElementProviderSimple>(text_node);
212 ComPtr<IRawElementProviderSimple> empty_text_node_raw =
213 QueryInterfaceFromNode<IRawElementProviderSimple>(empty_text_node);
214
215 // Call RangeFromChild on the root with the text child passed in.
216 ComPtr<ITextProvider> text_provider;
217 EXPECT_HRESULT_SUCCEEDED(
218 root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
219
220 ComPtr<ITextRangeProvider> text_range_provider;
221 EXPECT_HRESULT_SUCCEEDED(
222 text_provider->RangeFromChild(text_node_raw.Get(), &text_range_provider));
223 SetOwner(owner, text_range_provider.Get());
224
225 base::win::ScopedBstr text_content;
226 EXPECT_HRESULT_SUCCEEDED(
227 text_range_provider->GetText(-1, text_content.Receive()));
228 EXPECT_EQ(0, wcscmp(text_content.Get(), L"some text"));
229
230 // Now test that the reverse relation doesn't return a valid
231 // ITextRangeProvider, and instead returns E_INVALIDARG.
232 EXPECT_HRESULT_SUCCEEDED(
233 text_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
234
236 text_provider->RangeFromChild(root_node_raw.Get(), &text_range_provider));
237
238 // Now test that a child with no text returns a degenerate range.
239 EXPECT_HRESULT_SUCCEEDED(
240 root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
241
242 EXPECT_HRESULT_SUCCEEDED(text_provider->RangeFromChild(
243 empty_text_node_raw.Get(), &text_range_provider));
244 SetOwner(owner, text_range_provider.Get());
245
246 base::win::ScopedBstr empty_text_content;
247 EXPECT_HRESULT_SUCCEEDED(
248 text_range_provider->GetText(-1, empty_text_content.Receive()));
249 EXPECT_EQ(0, wcscmp(empty_text_content.Get(), L""));
250
251 // Test that passing in an object from a different instance of
252 // IRawElementProviderSimple than that of the valid text provider
253 // returns UIA_E_INVALIDOPERATION.
254 ComPtr<IRawElementProviderSimple> other_root_node_raw;
256 &other_root_node_raw);
257
258 EXPECT_HRESULT_SUCCEEDED(
259 root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
260
261 EXPECT_UIA_INVALIDOPERATION(text_provider->RangeFromChild(
262 other_root_node_raw.Get(), &text_range_provider));
263}
264
266 ITextProviderRangeFromChildMultipleChildren) {
267 const int ROOT_ID = 1;
268 const int DIALOG_ID = 2;
269 const int DIALOG_LABEL_ID = 3;
270 const int DIALOG_DESCRIPTION_ID = 4;
271 const int BUTTON_ID = 5;
272 const int BUTTON_IMG_ID = 6;
273 const int BUTTON_TEXT_ID = 7;
274 const int DIALOG_DETAIL_ID = 8;
275
276 AXNodeData root;
277 root.id = ROOT_ID;
279 root.SetName("Document");
280 root.child_ids = {DIALOG_ID};
281
282 AXNodeData dialog;
283 dialog.id = DIALOG_ID;
285 dialog.child_ids = {DIALOG_LABEL_ID, DIALOG_DESCRIPTION_ID, BUTTON_ID,
286 DIALOG_DETAIL_ID};
287
288 AXNodeData dialog_label;
289 dialog_label.id = DIALOG_LABEL_ID;
290 dialog_label.role = ax::mojom::Role::kStaticText;
291 dialog_label.SetName("Dialog label.");
292
293 AXNodeData dialog_description;
294 dialog_description.id = DIALOG_DESCRIPTION_ID;
295 dialog_description.role = ax::mojom::Role::kStaticText;
296 dialog_description.SetName("Dialog description.");
297
298 AXNodeData button;
299 button.id = BUTTON_ID;
301 button.child_ids = {BUTTON_IMG_ID, BUTTON_TEXT_ID};
302
303 AXNodeData button_img;
304 button_img.id = BUTTON_IMG_ID;
305 button_img.role = ax::mojom::Role::kImage;
306
307 AXNodeData button_text;
308 button_text.id = BUTTON_TEXT_ID;
310 button_text.SetName("ok.");
311
312 AXNodeData dialog_detail;
313 dialog_detail.id = DIALOG_DETAIL_ID;
314 dialog_detail.role = ax::mojom::Role::kStaticText;
315 dialog_detail.SetName("Some more detail about dialog.");
316
318 AXTreeData tree_data;
320 update.tree_data = tree_data;
321 update.has_tree_data = true;
322 update.root_id = ROOT_ID;
323 update.nodes = {root, dialog, dialog_label, dialog_description,
324 button, button_img, button_text, dialog_detail};
325
326 Init(update);
327
328 AXNode* root_node = GetRootAsAXNode();
329 AXNode* dialog_node = root_node->children()[0];
330 AXPlatformNodeWin* owner =
331 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(root_node));
332 BASE_DCHECK(owner);
333
334 ComPtr<IRawElementProviderSimple> root_node_raw =
335 QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
336 ComPtr<IRawElementProviderSimple> dialog_node_raw =
337 QueryInterfaceFromNode<IRawElementProviderSimple>(dialog_node);
338
339 // Call RangeFromChild on the root with the dialog child passed in.
340 ComPtr<ITextProvider> text_provider;
341 EXPECT_HRESULT_SUCCEEDED(
342 root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
343
344 ComPtr<ITextRangeProvider> text_range_provider;
345 EXPECT_HRESULT_SUCCEEDED(text_provider->RangeFromChild(dialog_node_raw.Get(),
346 &text_range_provider));
347 SetOwner(owner, text_range_provider.Get());
348
349 base::win::ScopedBstr text_content;
350 EXPECT_HRESULT_SUCCEEDED(
351 text_range_provider->GetText(-1, text_content.Receive()));
352 EXPECT_EQ(fml::WideStringToUtf16(text_content.Get()),
353 u"Dialog label.Dialog description." + kEmbeddedCharacterAsString +
354 u"ok.Some more detail " + u"about dialog.");
355
356 // Check the reverse relationship that GetEnclosingElement on the text range
357 // gives back the dialog.
358 ComPtr<IRawElementProviderSimple> enclosing_element;
359 EXPECT_HRESULT_SUCCEEDED(
360 text_range_provider->GetEnclosingElement(&enclosing_element));
361 EXPECT_EQ(enclosing_element.Get(), dialog_node_raw.Get());
362}
363
364TEST_F(AXPlatformNodeTextProviderTest, NearestTextIndexToPoint) {
365 AXNodeData text_data;
366 text_data.id = 2;
368 text_data.SetName("text");
369 // spacing: "t-e-x---t-"
371 {2, 4, 8, 10});
372
373 AXNodeData root_data;
374 root_data.id = 1;
376 root_data.relative_bounds.bounds = gfx::RectF(1, 1, 2, 2);
377 root_data.child_ids.push_back(2);
378
379 Init(root_data, text_data);
380
381 AXNode* root_node = GetRootAsAXNode();
382 AXNode* text_node = root_node->children()[0];
383
384 struct NearestTextIndexTestData {
385 AXNode* node;
386 struct point_offset_expected_index_pair {
387 int point_offset_x;
388 int expected_index;
389 };
390 std::vector<point_offset_expected_index_pair> test_data;
391 };
392 NearestTextIndexTestData nodes[] = {
393 {text_node,
394 {{0, 0}, {2, 0}, {3, 1}, {4, 1}, {5, 2}, {8, 2}, {9, 3}, {10, 3}}},
395 {root_node,
396 {{0, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {8, 0}, {9, 0}, {10, 0}}}};
397 for (auto data : nodes) {
398 if (!data.node->IsText() && !data.node->data().IsTextField()) {
399 continue;
400 }
401 ComPtr<IRawElementProviderSimple> element_provider =
402 QueryInterfaceFromNode<IRawElementProviderSimple>(data.node);
403 ComPtr<ITextProvider> text_provider;
404 EXPECT_HRESULT_SUCCEEDED(element_provider->GetPatternProvider(
405 UIA_TextPatternId, &text_provider));
406 // get internal implementation to access helper for testing
407 ComPtr<AXPlatformNodeTextProviderWin> platform_text_provider;
408 EXPECT_HRESULT_SUCCEEDED(
409 text_provider->QueryInterface(IID_PPV_ARGS(&platform_text_provider)));
410
411 ComPtr<AXPlatformNodeWin> platform_node;
412 EXPECT_HRESULT_SUCCEEDED(
413 element_provider->QueryInterface(IID_PPV_ARGS(&platform_node)));
414
415 for (auto pair : data.test_data) {
416 EXPECT_EQ(pair.expected_index, platform_node->NearestTextIndexToPoint(
417 gfx::Point(pair.point_offset_x, 0)));
418 }
419 }
420}
421
422TEST_F(AXPlatformNodeTextProviderTest, ITextProviderDocumentRange) {
423 AXNodeData text_data;
424 text_data.id = 2;
426 text_data.SetName("some text");
427
428 AXNodeData root_data;
429 root_data.id = 1;
431 root_data.SetName("Document");
432 root_data.child_ids.push_back(2);
433
434 Init(root_data, text_data);
435
436 ComPtr<IRawElementProviderSimple> root_node =
437 GetRootIRawElementProviderSimple();
438
439 ComPtr<ITextProvider> text_provider;
440 EXPECT_HRESULT_SUCCEEDED(
441 root_node->GetPatternProvider(UIA_TextPatternId, &text_provider));
442
443 ComPtr<ITextRangeProvider> text_range_provider;
444 EXPECT_HRESULT_SUCCEEDED(
445 text_provider->get_DocumentRange(&text_range_provider));
446}
447
449 DISABLED_ITextProviderDocumentRangeTrailingIgnored) {
450 // ++1 root
451 // ++++2 kGenericContainer
452 // ++++++3 kStaticText "Hello"
453 // ++++4 kGenericContainer
454 // ++++++5 kGenericContainer
455 // ++++++++6 kStaticText "3.14"
456 // ++++7 kGenericContainer (ignored)
457 // ++++++8 kGenericContainer (ignored)
458 // ++++++++9 kStaticText "ignored"
459 AXNodeData root_1;
460 AXNodeData gc_2;
461 AXNodeData static_text_3;
462 AXNodeData gc_4;
463 AXNodeData gc_5;
464 AXNodeData static_text_6;
465 AXNodeData gc_7_ignored;
466 AXNodeData gc_8_ignored;
467 AXNodeData static_text_9_ignored;
468
469 root_1.id = 1;
470 gc_2.id = 2;
471 static_text_3.id = 3;
472 gc_4.id = 4;
473 gc_5.id = 5;
474 static_text_6.id = 6;
475 gc_7_ignored.id = 7;
476 gc_8_ignored.id = 8;
477 static_text_9_ignored.id = 9;
478
480 root_1.child_ids = {gc_2.id, gc_4.id, gc_7_ignored.id};
481 root_1.SetName("Document");
482
485 {static_text_3.id});
486 gc_2.child_ids = {static_text_3.id};
487
488 static_text_3.role = ax::mojom::Role::kStaticText;
489 static_text_3.SetName("Hello");
490
493 {gc_5.id});
494 gc_4.child_ids = {gc_5.id};
495
497 gc_5.child_ids = {static_text_6.id};
498
499 static_text_6.role = ax::mojom::Role::kStaticText;
500 static_text_6.SetName("3.14");
501
503 gc_7_ignored.child_ids = {gc_8_ignored.id};
505
507 gc_8_ignored.child_ids = {static_text_9_ignored.id};
509
510 static_text_9_ignored.role = ax::mojom::Role::kStaticText;
511 static_text_9_ignored.SetName("ignored");
512 static_text_9_ignored.AddState(ax::mojom::State::kIgnored);
513
515 AXTreeData tree_data;
517 update.tree_data = tree_data;
518 update.has_tree_data = true;
519 update.root_id = root_1.id;
520 update.nodes = {root_1, gc_2, static_text_3,
521 gc_4, gc_5, static_text_6,
522 gc_7_ignored, gc_8_ignored, static_text_9_ignored};
523
524 Init(update);
525
526 ComPtr<IRawElementProviderSimple> root_node =
527 GetRootIRawElementProviderSimple();
528
529 ComPtr<ITextProvider> text_provider;
530 EXPECT_HRESULT_SUCCEEDED(
531 root_node->GetPatternProvider(UIA_TextPatternId, &text_provider));
532
533 ComPtr<ITextRangeProvider> text_range_provider;
534 EXPECT_HRESULT_SUCCEEDED(
535 text_provider->get_DocumentRange(&text_range_provider));
536
537 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range;
538 text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range));
539
540 ComPtr<ITextProvider> root_text_provider;
541 EXPECT_HRESULT_SUCCEEDED(
542 root_node->GetPatternProvider(UIA_TextPatternId, &root_text_provider));
543 ComPtr<AXPlatformNodeTextProviderWin> root_platform_node;
544 root_text_provider->QueryInterface(IID_PPV_ARGS(&root_platform_node));
545 AXPlatformNodeWin* owner = GetOwner(root_platform_node.Get());
546
548 owner->GetDelegate()->CreateTextPositionAt(0)->AsLeafTextPosition();
550 owner->GetDelegate()
551 ->CreateTextPositionAt(0)
552 ->CreatePositionAtEndOfAnchor();
553 expected_end = expected_end->AsLeafTextPosition();
554 EXPECT_EQ(*GetStart(text_range.Get()), *expected_start);
555 EXPECT_EQ(*GetEnd(text_range.Get()), *expected_end);
556}
557
558TEST_F(AXPlatformNodeTextProviderTest, ITextProviderDocumentRangeNested) {
559 AXNodeData text_data;
560 text_data.id = 3;
562 text_data.SetName("some text");
563
564 AXNodeData paragraph_data;
565 paragraph_data.id = 2;
566 paragraph_data.role = ax::mojom::Role::kParagraph;
567 paragraph_data.child_ids.push_back(3);
568
569 AXNodeData root_data;
570 root_data.id = 1;
572 root_data.SetName("Document");
573 root_data.child_ids.push_back(2);
574
575 Init(root_data, paragraph_data, text_data);
576
577 ComPtr<IRawElementProviderSimple> root_node =
578 GetRootIRawElementProviderSimple();
579
580 ComPtr<ITextProvider> text_provider;
581 EXPECT_HRESULT_SUCCEEDED(
582 root_node->GetPatternProvider(UIA_TextPatternId, &text_provider));
583
584 ComPtr<ITextRangeProvider> text_range_provider;
585 EXPECT_HRESULT_SUCCEEDED(
586 text_provider->get_DocumentRange(&text_range_provider));
587}
588
589TEST_F(AXPlatformNodeTextProviderTest, ITextProviderSupportedSelection) {
590 AXNodeData text_data;
591 text_data.id = 2;
593 text_data.SetName("some text");
594
595 AXNodeData root_data;
596 root_data.id = 1;
598 root_data.SetName("Document");
599 root_data.child_ids.push_back(2);
600
601 Init(root_data, text_data);
602
603 ComPtr<IRawElementProviderSimple> root_node =
604 GetRootIRawElementProviderSimple();
605
606 ComPtr<ITextProvider> text_provider;
607 EXPECT_HRESULT_SUCCEEDED(
608 root_node->GetPatternProvider(UIA_TextPatternId, &text_provider));
609
610 SupportedTextSelection text_selection_mode;
611 EXPECT_HRESULT_SUCCEEDED(
612 text_provider->get_SupportedTextSelection(&text_selection_mode));
613 EXPECT_EQ(text_selection_mode, SupportedTextSelection_Single);
614}
615
616TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetSelection) {
617 AXNodeData text_data;
618 text_data.id = 2;
620 text_data.SetName("some text");
621
622 AXNodeData textbox_data;
623 textbox_data.id = 3;
625 textbox_data.SetName("textbox text");
627
628 AXNodeData nonatomic_textfield_data;
629 nonatomic_textfield_data.id = 4;
630 nonatomic_textfield_data.role = ax::mojom::Role::kGroup;
631 nonatomic_textfield_data.child_ids = {5};
632
633 AXNodeData text_child_data;
634 text_child_data.id = 5;
635 text_child_data.role = ax::mojom::Role::kStaticText;
636 text_child_data.SetName("text");
637
638 AXNodeData root_data;
639 root_data.id = 1;
641 root_data.SetName("Document");
642 root_data.child_ids = {2, 3, 4};
643
645 AXTreeData tree_data;
647 update.tree_data = tree_data;
648 update.has_tree_data = true;
649 update.root_id = root_data.id;
650 update.nodes = {root_data, text_data, textbox_data, nonatomic_textfield_data,
651 text_child_data};
652 Init(update);
653
654 ComPtr<IRawElementProviderSimple> root_node =
655 GetRootIRawElementProviderSimple();
656
657 ComPtr<ITextProvider> root_text_provider;
658 EXPECT_HRESULT_SUCCEEDED(
659 root_node->GetPatternProvider(UIA_TextPatternId, &root_text_provider));
660
662 root_text_provider->GetSelection(selections.Receive());
663 ASSERT_EQ(nullptr, selections.Get());
664
665 ComPtr<AXPlatformNodeTextProviderWin> root_platform_node;
666 root_text_provider->QueryInterface(IID_PPV_ARGS(&root_platform_node));
667
668 AXPlatformNodeWin* owner = GetOwner(root_platform_node.Get());
669 AXTreeData& selected_tree_data =
670 const_cast<AXTreeData&>(owner->GetDelegate()->GetTreeData());
671 selected_tree_data.sel_focus_object_id = 2;
672 selected_tree_data.sel_anchor_object_id = 2;
673 selected_tree_data.sel_anchor_offset = 0;
674 selected_tree_data.sel_focus_offset = 4;
675
676 root_text_provider->GetSelection(selections.Receive());
677 ASSERT_NE(nullptr, selections.Get());
678
679 LONG ubound;
680 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selections.Get(), 1, &ubound));
681 EXPECT_EQ(0, ubound);
682 LONG lbound;
683 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selections.Get(), 1, &lbound));
684 EXPECT_EQ(0, lbound);
685
686 LONG index = 0;
687 ComPtr<ITextRangeProvider> text_range_provider;
688 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
689 selections.Get(), &index, static_cast<void**>(&text_range_provider)));
690 SetOwner(owner, text_range_provider.Get());
691
692 base::win::ScopedBstr text_content;
693 EXPECT_HRESULT_SUCCEEDED(
694 text_range_provider->GetText(-1, text_content.Receive()));
695 EXPECT_EQ(0, wcscmp(text_content.Get(), L"some"));
696 text_content.Reset();
697 selections.Reset();
698 text_range_provider.Reset();
699
700 // Verify that start and end are appropriately swapped when sel_anchor_offset
701 // is greater than sel_focus_offset
702 selected_tree_data.sel_focus_object_id = 2;
703 selected_tree_data.sel_anchor_object_id = 2;
704 selected_tree_data.sel_anchor_offset = 4;
705 selected_tree_data.sel_focus_offset = 0;
706
707 root_text_provider->GetSelection(selections.Receive());
708 ASSERT_NE(nullptr, selections.Get());
709
710 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selections.Get(), 1, &ubound));
711 EXPECT_EQ(0, ubound);
712 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selections.Get(), 1, &lbound));
713 EXPECT_EQ(0, lbound);
714
715 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
716 selections.Get(), &index, static_cast<void**>(&text_range_provider)));
717 SetOwner(owner, text_range_provider.Get());
718
719 EXPECT_HRESULT_SUCCEEDED(
720 text_range_provider->GetText(-1, text_content.Receive()));
721 EXPECT_EQ(0, wcscmp(text_content.Get(), L"some"));
722 text_content.Reset();
723 selections.Reset();
724 text_range_provider.Reset();
725
726 // Verify that text ranges at an insertion point returns a degenerate (empty)
727 // text range via textbox with sel_anchor_offset equal to sel_focus_offset
728 selected_tree_data.sel_focus_object_id = 3;
729 selected_tree_data.sel_anchor_object_id = 3;
730 selected_tree_data.sel_anchor_offset = 1;
731 selected_tree_data.sel_focus_offset = 1;
732
733 AXNode* text_edit_node = GetRootAsAXNode()->children()[1];
734
735 ComPtr<IRawElementProviderSimple> text_edit_com =
736 QueryInterfaceFromNode<IRawElementProviderSimple>(text_edit_node);
737
738 ComPtr<ITextProvider> text_edit_provider;
739 EXPECT_HRESULT_SUCCEEDED(text_edit_com->GetPatternProvider(
740 UIA_TextPatternId, &text_edit_provider));
741
742 selections.Reset();
743 EXPECT_HRESULT_SUCCEEDED(
744 text_edit_provider->GetSelection(selections.Receive()));
745 EXPECT_NE(nullptr, selections.Get());
746
747 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selections.Get(), 1, &ubound));
748 EXPECT_EQ(0, ubound);
749 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selections.Get(), 1, &lbound));
750 EXPECT_EQ(0, lbound);
751
752 ComPtr<ITextRangeProvider> text_edit_range_provider;
753 EXPECT_HRESULT_SUCCEEDED(
754 SafeArrayGetElement(selections.Get(), &index,
755 static_cast<void**>(&text_edit_range_provider)));
756 SetOwner(owner, text_edit_range_provider.Get());
757 EXPECT_HRESULT_SUCCEEDED(
758 text_edit_range_provider->GetText(-1, text_content.Receive()));
759 EXPECT_EQ(0U, text_content.Length());
760 text_content.Reset();
761 selections.Reset();
762 text_edit_range_provider.Reset();
763
764 // Verify selections that span multiple nodes
765 selected_tree_data.sel_focus_object_id = 2;
766 selected_tree_data.sel_focus_offset = 0;
767 selected_tree_data.sel_anchor_object_id = 3;
768 selected_tree_data.sel_anchor_offset = 12;
769
770 root_text_provider->GetSelection(selections.Receive());
771 ASSERT_NE(nullptr, selections.Get());
772
773 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selections.Get(), 1, &ubound));
774 EXPECT_EQ(0, ubound);
775 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selections.Get(), 1, &lbound));
776 EXPECT_EQ(0, lbound);
777
778 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
779 selections.Get(), &index, static_cast<void**>(&text_range_provider)));
780
781 SetOwner(owner, text_range_provider.Get());
782 EXPECT_HRESULT_SUCCEEDED(
783 text_range_provider->GetText(-1, text_content.Receive()));
784 EXPECT_EQ(0, wcscmp(text_content.Get(), L"some texttextbox text"));
785 text_content.Reset();
786 selections.Reset();
787 text_range_provider.Reset();
788
789 // Verify SAFEARRAY value for degenerate selection.
790 selected_tree_data.sel_focus_object_id = 2;
791 selected_tree_data.sel_anchor_object_id = 2;
792 selected_tree_data.sel_anchor_offset = 1;
793 selected_tree_data.sel_focus_offset = 1;
794
795 root_text_provider->GetSelection(selections.Receive());
796 ASSERT_NE(nullptr, selections.Get());
797
798 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selections.Get(), 1, &ubound));
799 EXPECT_EQ(0, ubound);
800 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selections.Get(), 1, &lbound));
801 EXPECT_EQ(0, lbound);
802
803 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
804 selections.Get(), &index, static_cast<void**>(&text_range_provider)));
805
806 SetOwner(owner, text_range_provider.Get());
807 EXPECT_HRESULT_SUCCEEDED(
808 text_range_provider->GetText(-1, text_content.Receive()));
809 EXPECT_EQ(0, wcscmp(text_content.Get(), L""));
810 text_content.Reset();
811 selections.Reset();
812 text_range_provider.Reset();
813
814 // Removed testing logic for non-atomic text fields as we do not have this
815 // role.
816
817 // Now delete the tree (which will delete the associated elements) and verify
818 // that UIA_E_ELEMENTNOTAVAILABLE is returned when calling GetSelection on
819 // a dead element
820 DestroyTree();
821
822 EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
823 text_edit_provider->GetSelection(selections.Receive()));
824}
825
826TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetActiveComposition) {
827 AXNodeData text_data;
828 text_data.id = 2;
830 text_data.SetName("some text");
831
832 AXNodeData root_data;
833 root_data.id = 1;
835 root_data.SetName("Document");
836 root_data.child_ids.push_back(2);
837
839 AXTreeData tree_data;
841 update.tree_data = tree_data;
842 update.has_tree_data = true;
843 update.root_id = root_data.id;
844 update.nodes.push_back(root_data);
845 update.nodes.push_back(text_data);
846 Init(update);
847
848 ComPtr<IRawElementProviderSimple> root_node =
849 GetRootIRawElementProviderSimple();
850
851 ComPtr<ITextProvider> root_text_provider;
852 EXPECT_HRESULT_SUCCEEDED(
853 root_node->GetPatternProvider(UIA_TextPatternId, &root_text_provider));
854
855 ComPtr<ITextEditProvider> root_text_edit_provider;
856 EXPECT_HRESULT_SUCCEEDED(root_node->GetPatternProvider(
857 UIA_TextEditPatternId, &root_text_edit_provider));
858
859 ComPtr<ITextRangeProvider> text_range_provider;
860 root_text_edit_provider->GetActiveComposition(&text_range_provider);
861 ASSERT_EQ(nullptr, text_range_provider);
862
863 ComPtr<AXPlatformNodeTextProviderWin> root_platform_node;
864 root_text_provider->QueryInterface(IID_PPV_ARGS(&root_platform_node));
865
866 AXActionData action_data;
867 action_data.action = ax::mojom::Action::kFocus;
868 action_data.target_node_id = 1;
869 AXPlatformNodeWin* owner = GetOwner(root_platform_node.Get());
870 owner->GetDelegate()->AccessibilityPerformAction(action_data);
871 const std::u16string active_composition_text = u"a";
872 owner->OnActiveComposition(gfx::Range(0, 1), active_composition_text, false);
873
874 root_text_edit_provider->GetActiveComposition(&text_range_provider);
875 ASSERT_NE(nullptr, text_range_provider);
876 ComPtr<AXPlatformNodeTextRangeProviderWin> actual_range;
878 owner->GetDelegate()->CreateTextPositionAt(0);
880 owner->GetDelegate()->CreateTextPositionAt(1);
881 text_range_provider->QueryInterface(IID_PPV_ARGS(&actual_range));
882 EXPECT_EQ(*GetStart(actual_range.Get()), *expected_start);
883 EXPECT_EQ(*GetEnd(actual_range.Get()), *expected_end);
884}
885
886TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetConversionTarget) {
887 AXNodeData text_data;
888 text_data.id = 2;
890 text_data.SetName("some text");
891
892 AXNodeData root_data;
893 root_data.id = 1;
895 root_data.SetName("Document");
896 root_data.child_ids.push_back(2);
897
899 AXTreeData tree_data;
901 update.tree_data = tree_data;
902 update.has_tree_data = true;
903 update.root_id = root_data.id;
904 update.nodes.push_back(root_data);
905 update.nodes.push_back(text_data);
906 Init(update);
907
908 ComPtr<IRawElementProviderSimple> root_node =
909 GetRootIRawElementProviderSimple();
910
911 ComPtr<ITextProvider> root_text_provider;
912 EXPECT_HRESULT_SUCCEEDED(
913 root_node->GetPatternProvider(UIA_TextPatternId, &root_text_provider));
914
915 ComPtr<ITextEditProvider> root_text_edit_provider;
916 EXPECT_HRESULT_SUCCEEDED(root_node->GetPatternProvider(
917 UIA_TextEditPatternId, &root_text_edit_provider));
918
919 ComPtr<ITextRangeProvider> text_range_provider;
920 root_text_edit_provider->GetConversionTarget(&text_range_provider);
921 ASSERT_EQ(nullptr, text_range_provider);
922
923 ComPtr<AXPlatformNodeTextProviderWin> root_platform_node;
924 root_text_provider->QueryInterface(IID_PPV_ARGS(&root_platform_node));
925
926 AXActionData action_data;
927 action_data.action = ax::mojom::Action::kFocus;
928 action_data.target_node_id = 1;
929 AXPlatformNodeWin* owner = GetOwner(root_platform_node.Get());
930 owner->GetDelegate()->AccessibilityPerformAction(action_data);
931 const std::u16string active_composition_text = u"a";
932 owner->OnActiveComposition(gfx::Range(0, 1), active_composition_text, false);
933
934 root_text_edit_provider->GetConversionTarget(&text_range_provider);
935 ASSERT_NE(nullptr, text_range_provider);
936 ComPtr<AXPlatformNodeTextRangeProviderWin> actual_range;
938 owner->GetDelegate()->CreateTextPositionAt(0);
940 owner->GetDelegate()->CreateTextPositionAt(1);
941 text_range_provider->QueryInterface(IID_PPV_ARGS(&actual_range));
942 EXPECT_EQ(*GetStart(actual_range.Get()), *expected_start);
943 EXPECT_EQ(*GetEnd(actual_range.Get()), *expected_end);
944}
945
946} // namespace ui
#define EXPECT_UIA_INVALIDOPERATION(expr)
void Reset(BSTR bstr=nullptr)
size_t Length() const
void Reset(SAFEARRAY *safearray=nullptr)
const std::vector< AXNode * > & children() const
Definition ax_node.h:113
const AXNodePosition::AXPositionInstance & GetEnd(const AXPlatformNodeTextRangeProviderWin *text_range)
AXPlatformNodeTextProviderTest & operator=(const AXPlatformNodeTextProviderTest &)=delete
~AXPlatformNodeTextProviderTest() override=default
void SetOwner(AXPlatformNodeWin *owner, ITextRangeProvider *destination_range)
AXPlatformNodeTextProviderTest(const AXPlatformNodeTextProviderTest &)=delete
const AXNodePosition::AXPositionInstance & GetStart(const AXPlatformNodeTextRangeProviderWin *text_range)
AXPlatformNodeWin * GetOwner(const AXPlatformNodeTextProviderWin *text_provider)
std::unique_ptr< AXPosition< AXNodePosition, AXNode > > AXPositionInstance
static AXTreeID CreateNewAXTreeID()
Definition ax_tree_id.cc:47
static HRESULT CreateMockIRawElementProviderSimple(IRawElementProviderSimple **provider)
void Init()
std::u16string WideStringToUtf16(const std::wstring_view str)
TEST_F(AXPositionTest, Clone)
ax::mojom::Action action
AXRelativeBounds relative_bounds
void AddIntListAttribute(ax::mojom::IntListAttribute attribute, const std::vector< int32_t > &value)
void AddState(ax::mojom::State state)
std::vector< int32_t > child_ids
void SetName(const std::string &name)
ax::mojom::Role role
AXNode::AXID sel_anchor_object_id
AXNode::AXID sel_focus_object_id
int32_t sel_focus_offset
AXTreeID tree_id
int32_t sel_anchor_offset
#define BASE_DCHECK(condition)
Definition logging.h:63
long LONG