Flutter Engine
The Flutter Engine
ax_platform_node_textrangeprovider_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 <filesystem>
11#include <memory>
12#include <utility>
13
14#include "ax/ax_tree.h"
17#include "base/win/atl.h"
21#include "flutter/fml/icu_util.h"
22#include "third_party/icu/source/common/unicode/putil.h"
23
24using Microsoft::WRL::ComPtr;
25
26namespace ui {
27
28// Helper macros for UIAutomation HRESULT expectations
29#define EXPECT_UIA_ELEMENTNOTAVAILABLE(expr) \
30 EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), (expr))
31#define EXPECT_UIA_INVALIDOPERATION(expr) \
32 EXPECT_EQ(static_cast<HRESULT>(UIA_E_INVALIDOPERATION), (expr))
33#define EXPECT_UIA_ELEMENTNOTENABLED(expr) \
34 EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTENABLED), (expr))
35#define EXPECT_UIA_NOTSUPPORTED(expr) \
36 EXPECT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED), (expr))
37
38#define ASSERT_UIA_ELEMENTNOTAVAILABLE(expr) \
39 ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), (expr))
40#define ASSERT_UIA_INVALIDOPERATION(expr) \
41 ASSERT_EQ(static_cast<HRESULT>(UIA_E_INVALIDOPERATION), (expr))
42#define ASSERT_UIA_ELEMENTNOTENABLED(expr) \
43 ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTENABLED), (expr))
44#define ASSERT_UIA_NOTSUPPORTED(expr) \
45 ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED), (expr))
46
47#define EXPECT_UIA_GETPROPERTYVALUE_EQ(node, property_id, expected) \
48 { \
49 base::win::ScopedVariant expectedVariant(expected); \
50 ASSERT_EQ(VT_BSTR, expectedVariant.type()); \
51 ASSERT_NE(nullptr, expectedVariant.ptr()->bstrVal); \
52 base::win::ScopedVariant actual; \
53 ASSERT_HRESULT_SUCCEEDED( \
54 node->GetPropertyValue(property_id, actual.Receive())); \
55 ASSERT_EQ(VT_BSTR, actual.type()); \
56 ASSERT_NE(nullptr, actual.ptr()->bstrVal); \
57 EXPECT_STREQ(expectedVariant.ptr()->bstrVal, actual.ptr()->bstrVal); \
58 }
59
60#define EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(array, element_test_property_id, \
61 expected_property_values) \
62 { \
63 ASSERT_EQ(1u, SafeArrayGetDim(array)); \
64 LONG array_lower_bound; \
65 ASSERT_HRESULT_SUCCEEDED( \
66 SafeArrayGetLBound(array, 1, &array_lower_bound)); \
67 LONG array_upper_bound; \
68 ASSERT_HRESULT_SUCCEEDED( \
69 SafeArrayGetUBound(array, 1, &array_upper_bound)); \
70 IUnknown** array_data; \
71 ASSERT_HRESULT_SUCCEEDED( \
72 ::SafeArrayAccessData(array, reinterpret_cast<void**>(&array_data))); \
73 size_t count = array_upper_bound - array_lower_bound + 1; \
74 ASSERT_EQ(expected_property_values.size(), count); \
75 for (size_t i = 0; i < count; ++i) { \
76 ComPtr<IRawElementProviderSimple> element; \
77 ASSERT_HRESULT_SUCCEEDED( \
78 array_data[i]->QueryInterface(IID_PPV_ARGS(&element))); \
79 EXPECT_UIA_GETPROPERTYVALUE_EQ(element, element_test_property_id, \
80 expected_property_values[i].c_str()); \
81 } \
82 ASSERT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(array)); \
83 }
84
85#define EXPECT_UIA_SAFEARRAY_EQ(safearray, expected_property_values) \
86 { \
87 using T = typename decltype(expected_property_values)::value_type; \
88 EXPECT_EQ(sizeof(T), ::SafeArrayGetElemsize(safearray)); \
89 EXPECT_EQ(1u, SafeArrayGetDim(safearray)); \
90 LONG array_lower_bound; \
91 EXPECT_HRESULT_SUCCEEDED( \
92 SafeArrayGetLBound(safearray, 1, &array_lower_bound)); \
93 LONG array_upper_bound; \
94 EXPECT_HRESULT_SUCCEEDED( \
95 SafeArrayGetUBound(safearray, 1, &array_upper_bound)); \
96 const size_t count = array_upper_bound - array_lower_bound + 1; \
97 EXPECT_EQ(expected_property_values.size(), count); \
98 if (sizeof(T) == ::SafeArrayGetElemsize(safearray) && \
99 count == expected_property_values.size()) { \
100 T* array_data; \
101 EXPECT_HRESULT_SUCCEEDED(::SafeArrayAccessData( \
102 safearray, reinterpret_cast<void**>(&array_data))); \
103 for (size_t i = 0; i < count; ++i) { \
104 EXPECT_EQ(array_data[i], expected_property_values[i]); \
105 } \
106 EXPECT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(safearray)); \
107 } \
108 }
109
110#define EXPECT_UIA_TEXTATTRIBUTE_EQ(provider, attribute, variant) \
111 { \
112 base::win::ScopedVariant scoped_variant; \
113 EXPECT_HRESULT_SUCCEEDED( \
114 provider->GetAttributeValue(attribute, scoped_variant.Receive())); \
115 EXPECT_EQ(0, scoped_variant.Compare(variant)); \
116 }
117
118#define EXPECT_UIA_TEXTATTRIBUTE_MIXED(provider, attribute) \
119 { \
120 ComPtr<IUnknown> expected_mixed; \
121 EXPECT_HRESULT_SUCCEEDED( \
122 ::UiaGetReservedMixedAttributeValue(&expected_mixed)); \
123 base::win::ScopedVariant scoped_variant; \
124 EXPECT_HRESULT_SUCCEEDED( \
125 provider->GetAttributeValue(attribute, scoped_variant.Receive())); \
126 EXPECT_EQ(VT_UNKNOWN, scoped_variant.type()); \
127 EXPECT_EQ(expected_mixed.Get(), V_UNKNOWN(scoped_variant.ptr())); \
128 }
129
130#define EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(provider, attribute) \
131 { \
132 ComPtr<IUnknown> expected_notsupported; \
133 EXPECT_HRESULT_SUCCEEDED( \
134 ::UiaGetReservedNotSupportedValue(&expected_notsupported)); \
135 base::win::ScopedVariant scoped_variant; \
136 EXPECT_HRESULT_SUCCEEDED( \
137 provider->GetAttributeValue(attribute, scoped_variant.Receive())); \
138 EXPECT_EQ(VT_UNKNOWN, scoped_variant.type()); \
139 EXPECT_EQ(expected_notsupported.Get(), V_UNKNOWN(scoped_variant.ptr())); \
140 }
141
142#define EXPECT_UIA_TEXTRANGE_EQ(provider, expected_content) \
143 { \
144 base::win::ScopedBstr provider_content; \
145 EXPECT_HRESULT_SUCCEEDED( \
146 provider->GetText(-1, provider_content.Receive())); \
147 EXPECT_STREQ(expected_content, provider_content.Get()); \
148 }
149
150#define EXPECT_UIA_FIND_TEXT(text_range_provider, search_term, ignore_case, \
151 owner) \
152 { \
153 base::win::ScopedBstr find_string(search_term); \
154 ComPtr<ITextRangeProvider> text_range_provider_found; \
155 EXPECT_HRESULT_SUCCEEDED(text_range_provider->FindText( \
156 find_string.Get(), false, ignore_case, &text_range_provider_found)); \
157 if (text_range_provider_found == nullptr) { \
158 EXPECT_TRUE(false); \
159 } else { \
160 SetOwner(owner, text_range_provider_found.Get()); \
161 base::win::ScopedBstr found_content; \
162 EXPECT_HRESULT_SUCCEEDED( \
163 text_range_provider_found->GetText(-1, found_content.Receive())); \
164 if (ignore_case) \
165 EXPECT_TRUE(StringCompareICU(found_content.Get(), find_string.Get())); \
166 else \
167 EXPECT_EQ(0, wcscmp(found_content.Get(), find_string.Get())); \
168 } \
169 }
170
171#define EXPECT_UIA_FIND_TEXT_NO_MATCH(text_range_provider, search_term, \
172 ignore_case, owner) \
173 { \
174 base::win::ScopedBstr find_string(search_term); \
175 ComPtr<ITextRangeProvider> text_range_provider_found; \
176 EXPECT_HRESULT_SUCCEEDED(text_range_provider->FindText( \
177 find_string.Get(), false, ignore_case, &text_range_provider_found)); \
178 EXPECT_EQ(nullptr, text_range_provider_found); \
179 }
180
181#define EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, endpoint, unit, \
182 count, expected_text, expected_count) \
183 { \
184 int result_count; \
185 EXPECT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit( \
186 endpoint, unit, count, &result_count)); \
187 EXPECT_EQ(expected_count, result_count); \
188 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, expected_text); \
189 }
190
191#define EXPECT_UIA_MOVE(text_range_provider, unit, count, expected_text, \
192 expected_count) \
193 { \
194 int result_count; \
195 EXPECT_HRESULT_SUCCEEDED( \
196 text_range_provider->Move(unit, count, &result_count)); \
197 EXPECT_EQ(expected_count, result_count); \
198 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, expected_text); \
199 }
200
201#define EXPECT_ENCLOSING_ELEMENT(ax_node_given, ax_node_expected) \
202 { \
203 ComPtr<ITextRangeProvider> text_range_provider; \
204 GetTextRangeProviderFromTextNode(text_range_provider, ax_node_given); \
205 ComPtr<IRawElementProviderSimple> enclosing_element; \
206 ASSERT_HRESULT_SUCCEEDED( \
207 text_range_provider->GetEnclosingElement(&enclosing_element)); \
208 ComPtr<IRawElementProviderSimple> expected_text_provider = \
209 QueryInterfaceFromNode<IRawElementProviderSimple>(ax_node_expected); \
210 EXPECT_EQ(expected_text_provider.Get(), enclosing_element.Get()); \
211 }
212
213#define DCHECK_EQ(a, b) BASE_DCHECK((a) == (b))
214
215static bool StringCompareICU(BSTR left, BSTR right) {
216 size_t start, length;
217 if (!StringSearch(reinterpret_cast<char16_t*>(left),
218 reinterpret_cast<char16_t*>(right), &start, &length, true,
219 false)) {
220 return false;
221 }
222 return start == 0 && length == wcslen(left);
223}
224
226 const AXNode& anchor,
227 int text_offset,
228 ax::mojom::TextAffinity affinity) {
230 anchor.id(), text_offset, affinity);
231}
232
234 public:
236 const AXPlatformNodeTextRangeProviderWin* text_range) {
237 return text_range->start();
238 }
239
241 const AXPlatformNodeTextRangeProviderWin* text_range) {
242 return text_range->end();
243 }
244
245 ui::AXPlatformNodeWin* GetOwner(
246 const AXPlatformNodeTextRangeProviderWin* text_range) {
247 return text_range->GetOwner();
248 }
249
250 void CopyOwnerToClone(ITextRangeProvider* source_range,
251 ITextRangeProvider* destination_range) {
252 ComPtr<ITextRangeProvider> source_provider = source_range;
253 ComPtr<ITextRangeProvider> destination_provider = destination_range;
254
255 ComPtr<AXPlatformNodeTextRangeProviderWin> source_provider_internal;
256 ComPtr<AXPlatformNodeTextRangeProviderWin> destination_provider_internal;
257
258 source_provider->QueryInterface(IID_PPV_ARGS(&source_provider_internal));
259 destination_provider->QueryInterface(
260 IID_PPV_ARGS(&destination_provider_internal));
261 destination_provider_internal->SetOwnerForTesting(
262 source_provider_internal->GetOwner());
263 }
264
265 void SetOwner(AXPlatformNodeWin* owner,
266 ITextRangeProvider* destination_range) {
267 ComPtr<AXPlatformNodeTextRangeProviderWin> destination_provider_internal;
268 auto as =
269 static_cast<AXPlatformNodeTextRangeProviderWin*>(destination_range);
270 destination_range->QueryInterface(
271 IID_PPV_ARGS(&destination_provider_internal));
272 destination_provider_internal->SetOwnerForTesting(owner);
273 }
274
275 void NormalizeTextRange(AXPlatformNodeTextRangeProviderWin* text_range,
278 DCHECK_EQ(*GetStart(text_range), *start);
279 DCHECK_EQ(*GetEnd(text_range), *end);
280 text_range->NormalizeTextRange(start, end);
281 }
282
284 ComPtr<ITextRangeProvider>& text_range_provider,
285 ui::AXNode* text_node) {
286 ComPtr<IRawElementProviderSimple> provider_simple =
287 QueryInterfaceFromNode<IRawElementProviderSimple>(text_node);
288 ASSERT_NE(nullptr, provider_simple.Get());
289
290 ComPtr<ITextProvider> text_provider;
291 EXPECT_HRESULT_SUCCEEDED(
292 provider_simple->GetPatternProvider(UIA_TextPatternId, &text_provider));
293 ASSERT_NE(nullptr, text_provider.Get());
294
295 EXPECT_HRESULT_SUCCEEDED(
296 text_provider->get_DocumentRange(&text_range_provider));
297 ASSERT_NE(nullptr, text_range_provider.Get());
298
299 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider_interal;
300 EXPECT_HRESULT_SUCCEEDED(text_range_provider->QueryInterface(
301 IID_PPV_ARGS(&text_range_provider_interal)));
302 AXPlatformNode* ax_platform_node = AXPlatformNodeFromNode(text_node);
303 ASSERT_NE(ax_platform_node, nullptr);
304 text_range_provider_interal->SetOwnerForTesting(
305 static_cast<AXPlatformNodeWin*>(ax_platform_node));
306 }
307
309 ComPtr<AXPlatformNodeTextRangeProviderWin>& text_range_provider_win,
310 AXPlatformNodeWin* owner,
311 const AXNode* start_anchor,
312 int start_offset,
313 ax::mojom::TextAffinity start_affinity,
314 const AXNode* end_anchor,
315 int end_offset,
316 ax::mojom::TextAffinity end_affinity) {
318 CreateTextPosition(*start_anchor, start_offset, start_affinity);
320 CreateTextPosition(*end_anchor, end_offset, end_affinity);
321
322 ComPtr<ITextRangeProvider> text_range_provider =
323 AXPlatformNodeTextRangeProviderWin::CreateTextRangeProviderForTesting(
324 owner, std::move(range_start), std::move(range_end));
325
326 text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range_provider_win));
327 }
328
329 void ComputeWordBoundariesOffsets(const std::string& text,
330 std::vector<int>& word_start_offsets,
331 std::vector<int>& word_end_offsets) {
332 char previous_char = ' ';
333 word_start_offsets = std::vector<int>();
334 for (size_t i = 0; i < text.size(); ++i) {
335 if (previous_char == ' ' && text[i] != ' ')
336 word_start_offsets.push_back(i);
337 previous_char = text[i];
338 }
339
340 previous_char = ' ';
341 word_end_offsets = std::vector<int>();
342 for (size_t i = text.size(); i > 0; --i) {
343 if (previous_char == ' ' && text[i - 1] != ' ')
344 word_end_offsets.push_back(i);
345 previous_char = text[i - 1];
346 }
347 std::reverse(word_end_offsets.begin(), word_end_offsets.end());
348 }
349
351 const std::vector<std::string>& text_nodes_content,
352 bool build_word_boundaries_offsets = false,
353 bool place_text_on_one_line = false) {
354 int current_id = 0;
355 AXNodeData root_data;
356 root_data.id = ++current_id;
358
360 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
361 update.has_tree_data = true;
362
363 for (const std::string& text_content : text_nodes_content) {
364 AXNodeData static_text_data;
365 static_text_data.id = ++current_id;
366 static_text_data.role = ax::mojom::Role::kStaticText;
367 static_text_data.SetName(text_content);
368 root_data.child_ids.push_back(static_text_data.id);
369
370 AXNodeData inline_box_data;
371 inline_box_data.id = ++current_id;
372 inline_box_data.role = ax::mojom::Role::kInlineTextBox;
373 inline_box_data.SetName(text_content);
374 static_text_data.child_ids = {inline_box_data.id};
375
376 if (build_word_boundaries_offsets) {
377 std::vector<int> word_end_offsets;
378 std::vector<int> word_start_offsets;
379 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
380 word_end_offsets);
381 inline_box_data.AddIntListAttribute(
382 ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
383 inline_box_data.AddIntListAttribute(
385 }
386
387 if (place_text_on_one_line && !update.nodes.empty()) {
388 AXNodeData* previous_inline_box_data = &update.nodes.back();
389 static_text_data.AddIntAttribute(
391 previous_inline_box_data->id);
392 inline_box_data.AddIntAttribute(
394 previous_inline_box_data->id);
395 previous_inline_box_data->AddIntAttribute(
397 }
398
399 update.nodes.push_back(static_text_data);
400 update.nodes.push_back(inline_box_data);
401 }
402
403 update.nodes.insert(update.nodes.begin(), root_data);
404 update.root_id = root_data.id;
405 return update;
406 }
407
409 // AXTree content:
410 // <button>Button</button><input type="checkbox">Line 1<br>Line 2
412 ui::AXNodeData button;
413 ui::AXNodeData check_box;
414 ui::AXNodeData text_field;
415 ui::AXNodeData static_text1;
416 ui::AXNodeData line_break;
417 ui::AXNodeData static_text2;
418 ui::AXNodeData inline_box1;
419 ui::AXNodeData inline_box2;
420 ui::AXNodeData inline_box_line_break;
421
422 const int ROOT_ID = 1;
423 const int BUTTON_ID = 2;
424 const int CHECK_BOX_ID = 3;
425 const int TEXT_FIELD_ID = 4;
426 const int STATIC_TEXT1_ID = 5;
427 const int INLINE_BOX1_ID = 6;
428 const int LINE_BREAK_ID = 7;
429 const int INLINE_BOX_LINE_BREAK_ID = 8;
430 const int STATIC_TEXT2_ID = 9;
431 const int INLINE_BOX2_ID = 10;
432
433 root.id = ROOT_ID;
434 button.id = BUTTON_ID;
435 check_box.id = CHECK_BOX_ID;
436 text_field.id = TEXT_FIELD_ID;
437 static_text1.id = STATIC_TEXT1_ID;
438 inline_box1.id = INLINE_BOX1_ID;
439 line_break.id = LINE_BREAK_ID;
440 inline_box_line_break.id = INLINE_BOX_LINE_BREAK_ID;
441 static_text2.id = STATIC_TEXT2_ID;
442 inline_box2.id = INLINE_BOX2_ID;
443
444 std::string LINE_1_TEXT = "Line 1";
445 std::string LINE_2_TEXT = "Line 2";
446 std::string LINE_BREAK_TEXT = "\n";
447 std::string ALL_TEXT = LINE_1_TEXT + LINE_BREAK_TEXT + LINE_2_TEXT;
448 std::string BUTTON_TEXT = "Button";
449 std::string CHECKBOX_TEXT = "Check box";
450
452
455 button.SetName(BUTTON_TEXT);
456 button.SetValue(BUTTON_TEXT);
457 button.relative_bounds.bounds = gfx::RectF(20, 20, 200, 30);
459 check_box.id);
460 root.child_ids.push_back(button.id);
461
464 check_box.SetName(CHECKBOX_TEXT);
465 check_box.relative_bounds.bounds = gfx::RectF(20, 50, 200, 30);
467 button.id);
468 root.child_ids.push_back(check_box.id);
469
473 "input");
475 "text");
476 text_field.SetValue(ALL_TEXT);
477 text_field.AddIntListAttribute(
479 std::vector<int32_t>{0, 7});
480 text_field.child_ids.push_back(static_text1.id);
481 text_field.child_ids.push_back(line_break.id);
482 text_field.child_ids.push_back(static_text2.id);
483 root.child_ids.push_back(text_field.id);
484
485 static_text1.role = ax::mojom::Role::kStaticText;
487 static_text1.SetName(LINE_1_TEXT);
488 static_text1.child_ids.push_back(inline_box1.id);
489
492 inline_box1.SetName(LINE_1_TEXT);
493 inline_box1.relative_bounds.bounds = gfx::RectF(220, 20, 100, 30);
494 std::vector<int32_t> character_offsets1;
495 // The width of each character is 5px.
496 character_offsets1.push_back(225); // "L" {220, 20, 5x30}
497 character_offsets1.push_back(230); // "i" {225, 20, 5x30}
498 character_offsets1.push_back(235); // "n" {230, 20, 5x30}
499 character_offsets1.push_back(240); // "e" {235, 20, 5x30}
500 character_offsets1.push_back(245); // " " {240, 20, 5x30}
501 character_offsets1.push_back(250); // "1" {245, 20, 5x30}
502 inline_box1.AddIntListAttribute(
505 std::vector<int32_t>{0, 5});
507 std::vector<int32_t>{4, 6});
509 line_break.id);
510
513 line_break.SetName(LINE_BREAK_TEXT);
514 line_break.relative_bounds.bounds = gfx::RectF(250, 20, 0, 30);
516 inline_box1.id);
517 line_break.child_ids.push_back(inline_box_line_break.id);
518
519 inline_box_line_break.role = ax::mojom::Role::kInlineTextBox;
520 inline_box_line_break.AddBoolAttribute(
522 inline_box_line_break.SetName(LINE_BREAK_TEXT);
523 inline_box_line_break.relative_bounds.bounds = gfx::RectF(250, 20, 0, 30);
524 inline_box_line_break.AddIntListAttribute(
526 inline_box_line_break.AddIntListAttribute(
527 ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
528 inline_box_line_break.AddIntListAttribute(
529 ax::mojom::IntListAttribute::kWordEnds, std::vector<int32_t>{0});
530
531 static_text2.role = ax::mojom::Role::kStaticText;
533 static_text2.SetName(LINE_2_TEXT);
534 static_text2.child_ids.push_back(inline_box2.id);
535
538 inline_box2.SetName(LINE_2_TEXT);
539 inline_box2.relative_bounds.bounds = gfx::RectF(220, 50, 100, 30);
540 std::vector<int32_t> character_offsets2;
541 // The width of each character is 7 px.
542 character_offsets2.push_back(227); // "L" {220, 50, 7x30}
543 character_offsets2.push_back(234); // "i" {227, 50, 7x30}
544 character_offsets2.push_back(241); // "n" {234, 50, 7x30}
545 character_offsets2.push_back(248); // "e" {241, 50, 7x30}
546 character_offsets2.push_back(255); // " " {248, 50, 7x30}
547 character_offsets2.push_back(262); // "2" {255, 50, 7x30}
548 inline_box2.AddIntListAttribute(
551 std::vector<int32_t>{0, 5});
553 std::vector<int32_t>{4, 6});
554
556 update.has_tree_data = true;
557 update.root_id = ROOT_ID;
558 update.nodes = {
559 root, button, check_box, text_field,
560 static_text1, inline_box1, line_break, inline_box_line_break,
561 static_text2, inline_box2};
562 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
563 return update;
564 }
565
566 const std::wstring tree_for_move_full_text =
567 L"First line of text\nStandalone line\n"
568 L"bold text\nParagraph 1\nParagraph 2";
569
571 ui::AXNodeData group1_data;
572 group1_data.id = 2;
574 group1_data.AddBoolAttribute(
576
577 ui::AXNodeData text_data;
578 text_data.id = 3;
580 std::string text_content = "First line of text";
581 text_data.SetName(text_content);
582 std::vector<int> word_end_offsets;
583 std::vector<int> word_start_offsets;
584 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
585 word_end_offsets);
587 word_start_offsets);
589 word_end_offsets);
590 group1_data.child_ids = {text_data.id};
591
592 ui::AXNodeData group2_data;
593 group2_data.id = 4;
595
596 ui::AXNodeData line_break1_data;
597 line_break1_data.id = 5;
598 line_break1_data.role = ax::mojom::Role::kLineBreak;
599 line_break1_data.AddBoolAttribute(
601 line_break1_data.SetName("\n");
602
603 ui::AXNodeData standalone_text_data;
604 standalone_text_data.id = 6;
605 standalone_text_data.role = ax::mojom::Role::kStaticText;
606 text_content = "Standalone line";
607 standalone_text_data.SetName(text_content);
608 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
609 word_end_offsets);
610 standalone_text_data.AddIntListAttribute(
611 ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
612 standalone_text_data.AddIntListAttribute(
614
615 ui::AXNodeData line_break2_data;
616 line_break2_data.id = 7;
617 line_break2_data.role = ax::mojom::Role::kLineBreak;
618 line_break2_data.AddBoolAttribute(
620 line_break2_data.SetName("\n");
621
622 group2_data.child_ids = {line_break1_data.id, standalone_text_data.id,
623 line_break2_data.id};
625 line_break2_data.id);
627 standalone_text_data.id);
628
629 ui::AXNodeData bold_text_data;
630 bold_text_data.id = 8;
631 bold_text_data.role = ax::mojom::Role::kStaticText;
632 bold_text_data.AddIntAttribute(
634 static_cast<int32_t>(ax::mojom::TextStyle::kBold));
635 text_content = "bold text";
636 bold_text_data.SetName(text_content);
637 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
638 word_end_offsets);
640 word_start_offsets);
642 word_end_offsets);
643
644 ui::AXNodeData paragraph1_data;
645 paragraph1_data.id = 9;
646 paragraph1_data.role = ax::mojom::Role::kParagraph;
647 paragraph1_data.AddBoolAttribute(
649
650 ui::AXNodeData paragraph1_text_data;
651 paragraph1_text_data.id = 10;
652 paragraph1_text_data.role = ax::mojom::Role::kStaticText;
653 text_content = "Paragraph 1";
654 paragraph1_text_data.SetName(text_content);
655 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
656 word_end_offsets);
657 paragraph1_text_data.AddIntListAttribute(
658 ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
659 paragraph1_text_data.AddIntListAttribute(
661 paragraph1_data.AddBoolAttribute(
663
664 ui::AXNodeData ignored_text_data;
665 ignored_text_data.id = 11;
666 ignored_text_data.role = ax::mojom::Role::kStaticText;
667 ignored_text_data.AddState(ax::mojom::State::kIgnored);
668 text_content = "ignored text";
669 ignored_text_data.SetName(text_content);
670
671 paragraph1_data.child_ids = {paragraph1_text_data.id, ignored_text_data.id};
672
673 ui::AXNodeData paragraph2_data;
674 paragraph2_data.id = 12;
675 paragraph2_data.role = ax::mojom::Role::kParagraph;
676 paragraph2_data.AddBoolAttribute(
678
679 ui::AXNodeData paragraph2_text_data;
680 paragraph2_text_data.id = 13;
681 paragraph2_text_data.role = ax::mojom::Role::kStaticText;
682 text_content = "Paragraph 2";
683 paragraph2_text_data.SetName(text_content);
684 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
685 word_end_offsets);
686 paragraph2_text_data.AddIntListAttribute(
687 ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
688 paragraph2_text_data.AddIntListAttribute(
690 paragraph1_data.AddBoolAttribute(
692 paragraph2_data.child_ids = {paragraph2_text_data.id};
693
694 ui::AXNodeData root_data;
695 root_data.id = 1;
697 root_data.child_ids = {group1_data.id, group2_data.id, bold_text_data.id,
698 paragraph1_data.id, paragraph2_data.id};
699
701 update.has_tree_data = true;
702 update.root_id = root_data.id;
703 update.nodes = {root_data, group1_data,
704 text_data, group2_data,
705 line_break1_data, standalone_text_data,
706 line_break2_data, bold_text_data,
707 paragraph1_data, paragraph1_text_data,
708 ignored_text_data, paragraph2_data,
709 paragraph2_text_data};
710 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
711 return update;
712 }
713
715 // 1
716 // |
717 // -------------------------------------
718 // | | | | | | |
719 // 2 4 8 10 12 14 16
720 // | | | | | | |
721 // | --------- | | | | |
722 // | | | | | | | | |
723 // 3 5 6 7 9 11 13 15 17
724
725 AXNodeData group1_data;
726 group1_data.id = 2;
729 "test font");
730 group1_data.AddBoolAttribute(
732
733 AXNodeData text_data;
734 text_data.id = 3;
736 text_data.SetName("Text with formatting");
737 group1_data.child_ids = {text_data.id};
738
739 AXNodeData group2_data;
740 group2_data.id = 4;
742 group2_data.AddBoolAttribute(
744
745 AXNodeData line_break1_data;
746 line_break1_data.id = 5;
747 line_break1_data.role = ax::mojom::Role::kLineBreak;
748 line_break1_data.SetName("\n");
749
750 AXNodeData standalone_text_data;
751 standalone_text_data.id = 6;
752 standalone_text_data.role = ax::mojom::Role::kStaticText;
753 standalone_text_data.SetName("Standalone line with no formatting");
754
755 AXNodeData line_break2_data;
756 line_break2_data.id = 7;
757 line_break2_data.role = ax::mojom::Role::kLineBreak;
758 line_break2_data.SetName("\n");
759
760 group2_data.child_ids = {line_break1_data.id, standalone_text_data.id,
761 line_break2_data.id};
762
763 AXNodeData group3_data;
764 group3_data.id = 8;
766 group3_data.AddIntAttribute(
768 static_cast<int32_t>(ax::mojom::TextStyle::kBold));
769 group3_data.AddBoolAttribute(
771
772 AXNodeData bold_text_data;
773 bold_text_data.id = 9;
774 bold_text_data.role = ax::mojom::Role::kStaticText;
775 bold_text_data.SetName("bold text");
776 group3_data.child_ids = {bold_text_data.id};
777
778 AXNodeData paragraph1_data;
779 paragraph1_data.id = 10;
780 paragraph1_data.role = ax::mojom::Role::kParagraph;
782 paragraph1_data.AddBoolAttribute(
784
785 AXNodeData paragraph1_text_data;
786 paragraph1_text_data.id = 11;
787 paragraph1_text_data.role = ax::mojom::Role::kStaticText;
788 paragraph1_text_data.SetName("Paragraph 1");
789 paragraph1_data.child_ids = {paragraph1_text_data.id};
790
791 AXNodeData paragraph2_data;
792 paragraph2_data.id = 12;
793 paragraph2_data.role = ax::mojom::Role::kParagraph;
795 1.0f);
796 paragraph2_data.AddBoolAttribute(
798
799 AXNodeData paragraph2_text_data;
800 paragraph2_text_data.id = 13;
801 paragraph2_text_data.role = ax::mojom::Role::kStaticText;
802 paragraph2_text_data.SetName("Paragraph 2");
803 paragraph2_data.child_ids = {paragraph2_text_data.id};
804
805 AXNodeData paragraph3_data;
806 paragraph3_data.id = 14;
807 paragraph3_data.role = ax::mojom::Role::kParagraph;
809 1.0f);
810 paragraph3_data.AddBoolAttribute(
812
813 AXNodeData paragraph3_text_data;
814 paragraph3_text_data.id = 15;
815 paragraph3_text_data.role = ax::mojom::Role::kStaticText;
816 paragraph3_text_data.SetName("Paragraph 3");
817 paragraph3_data.child_ids = {paragraph3_text_data.id};
818
819 AXNodeData paragraph4_data;
820 paragraph4_data.id = 16;
821 paragraph4_data.role = ax::mojom::Role::kParagraph;
823 2.0f);
824 paragraph4_data.AddBoolAttribute(
826
827 AXNodeData paragraph4_text_data;
828 paragraph4_text_data.id = 17;
829 paragraph4_text_data.role = ax::mojom::Role::kStaticText;
830 paragraph4_text_data.SetName("Paragraph 4");
831 paragraph4_data.child_ids = {paragraph4_text_data.id};
832
833 AXNodeData root_data;
834 root_data.id = 1;
836 root_data.child_ids = {group1_data.id, group2_data.id,
837 group3_data.id, paragraph1_data.id,
838 paragraph2_data.id, paragraph3_data.id,
839 paragraph4_data.id};
840
842 update.has_tree_data = true;
843 update.root_id = root_data.id;
844 update.nodes = {root_data,
845 group1_data,
846 text_data,
847 group2_data,
848 line_break1_data,
849 standalone_text_data,
850 line_break2_data,
851 group3_data,
852 bold_text_data,
853 paragraph1_data,
854 paragraph1_text_data,
855 paragraph2_data,
856 paragraph2_text_data,
857 paragraph3_data,
858 paragraph3_text_data,
859 paragraph4_data,
860 paragraph4_text_data};
861 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
862 return update;
863 }
864
867 EXPECT_EQ(*a, *b);
868 EXPECT_EQ(a->anchor_id(), b->anchor_id());
869 EXPECT_EQ(a->text_offset(), b->text_offset());
870 }
871};
872
874 : public CComObjectRootEx<CComMultiThreadModel>,
875 public ITextRangeProvider {
876 public:
878 COM_INTERFACE_ENTRY(ITextRangeProvider)
879 END_COM_MAP()
880
883
884 static HRESULT CreateMockTextRangeProvider(ITextRangeProvider** provider) {
885 CComObject<MockAXPlatformNodeTextRangeProviderWin>* text_range_provider =
886 nullptr;
887 HRESULT hr =
888 CComObject<MockAXPlatformNodeTextRangeProviderWin>::CreateInstance(
889 &text_range_provider);
890 if (SUCCEEDED(hr)) {
891 *provider = text_range_provider;
892 }
893
894 return hr;
895 }
896
897 //
898 // ITextRangeProvider methods.
899 //
900 IFACEMETHODIMP Clone(ITextRangeProvider** clone) override {
901 return E_NOTIMPL;
902 }
903
904 IFACEMETHODIMP Compare(ITextRangeProvider* other, BOOL* result) override {
905 return E_NOTIMPL;
906 }
907
908 IFACEMETHODIMP CompareEndpoints(TextPatternRangeEndpoint this_endpoint,
909 ITextRangeProvider* other,
910 TextPatternRangeEndpoint other_endpoint,
911 int* result) override {
912 return E_NOTIMPL;
913 }
914
915 IFACEMETHODIMP ExpandToEnclosingUnit(TextUnit unit) override {
916 return E_NOTIMPL;
917 }
918
919 IFACEMETHODIMP FindAttribute(TEXTATTRIBUTEID attribute_id,
920 VARIANT val,
921 BOOL backward,
922 ITextRangeProvider** result) override {
923 return E_NOTIMPL;
924 }
925
926 IFACEMETHODIMP FindText(BSTR string,
927 BOOL backwards,
928 BOOL ignore_case,
929 ITextRangeProvider** result) override {
930 return E_NOTIMPL;
931 }
932
933 IFACEMETHODIMP GetAttributeValue(TEXTATTRIBUTEID attribute_id,
934 VARIANT* value) override {
935 return E_NOTIMPL;
936 }
937
938 IFACEMETHODIMP GetBoundingRectangles(SAFEARRAY** rectangles) override {
939 return E_NOTIMPL;
940 }
941
942 IFACEMETHODIMP GetEnclosingElement(
943 IRawElementProviderSimple** element) override {
944 return E_NOTIMPL;
945 }
946
947 IFACEMETHODIMP GetText(int max_count, BSTR* text) override {
948 return E_NOTIMPL;
949 }
950
951 IFACEMETHODIMP Move(TextUnit unit, int count, int* units_moved) override {
952 return E_NOTIMPL;
953 }
954
955 IFACEMETHODIMP MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,
956 TextUnit unit,
957 int count,
958 int* units_moved) override {
959 return E_NOTIMPL;
960 }
961
962 IFACEMETHODIMP MoveEndpointByRange(
963 TextPatternRangeEndpoint this_endpoint,
964 ITextRangeProvider* other,
965 TextPatternRangeEndpoint other_endpoint) override {
966 return E_NOTIMPL;
967 }
968
969 IFACEMETHODIMP Select() override { return E_NOTIMPL; }
970
971 IFACEMETHODIMP AddToSelection() override { return E_NOTIMPL; }
972
973 IFACEMETHODIMP RemoveFromSelection() override { return E_NOTIMPL; }
974
975 IFACEMETHODIMP ScrollIntoView(BOOL align_to_top) override {
976 return E_NOTIMPL;
977 }
978
979 IFACEMETHODIMP GetChildren(SAFEARRAY** children) override {
980 return E_NOTIMPL;
981 }
982};
983
984TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderClone) {
985 Init(BuildTextDocument({"some text"}));
986
987 ComPtr<ITextRangeProvider> text_range_provider;
988 GetTextRangeProviderFromTextNode(text_range_provider,
989 GetRootAsAXNode()->children()[0]);
990 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
991
992 ComPtr<ITextRangeProvider> text_range_provider_clone;
993 text_range_provider->Clone(&text_range_provider_clone);
994 CopyOwnerToClone(text_range_provider.Get(), text_range_provider_clone.Get());
995 ComPtr<AXPlatformNodeTextRangeProviderWin> original_range;
996 ComPtr<AXPlatformNodeTextRangeProviderWin> clone_range;
997
998 text_range_provider->QueryInterface(IID_PPV_ARGS(&original_range));
999 text_range_provider_clone->QueryInterface(IID_PPV_ARGS(&clone_range));
1000
1001 EXPECT_EQ(*GetStart(original_range.Get()), *GetStart(clone_range.Get()));
1002 EXPECT_EQ(*GetEnd(original_range.Get()), *GetEnd(clone_range.Get()));
1003 EXPECT_EQ(GetOwner(original_range.Get()), GetOwner(clone_range.Get()));
1004
1005 // Clear original text range provider.
1006 text_range_provider.Reset();
1007 EXPECT_EQ(nullptr, text_range_provider.Get());
1008
1009 // Ensure the clone still works correctly.
1010 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider_clone, L"some text");
1011}
1012
1014 TestITextRangeProviderCompareEndpoints) {
1015 Init(BuildTextDocument({"some text", "more text"},
1016 false /* build_word_boundaries_offsets */,
1017 true /* place_text_on_one_line */));
1018
1019 AXNode* root_node = GetRootAsAXNode();
1020
1021 // Get the textRangeProvider for the document,
1022 // which contains text "some textmore text".
1023 ComPtr<ITextRangeProvider> document_text_range_provider;
1024 GetTextRangeProviderFromTextNode(document_text_range_provider, root_node);
1025
1026 // Get the textRangeProvider for "some text".
1027 ComPtr<ITextRangeProvider> text_range_provider;
1028 GetTextRangeProviderFromTextNode(text_range_provider,
1029 root_node->children()[0]);
1030
1031 // Get the textRangeProvider for "more text".
1032 ComPtr<ITextRangeProvider> more_text_range_provider;
1033 GetTextRangeProviderFromTextNode(more_text_range_provider,
1034 root_node->children()[1]);
1035
1036 // Compare the endpoints of the document which contains "some textmore text".
1037 int result;
1038 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->CompareEndpoints(
1039 TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
1040 TextPatternRangeEndpoint_Start, &result));
1041 EXPECT_EQ(0, result);
1042
1043 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->CompareEndpoints(
1044 TextPatternRangeEndpoint_End, document_text_range_provider.Get(),
1045 TextPatternRangeEndpoint_End, &result));
1046 EXPECT_EQ(0, result);
1047
1048 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->CompareEndpoints(
1049 TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
1050 TextPatternRangeEndpoint_End, &result));
1051 EXPECT_EQ(-1, result);
1052
1053 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->CompareEndpoints(
1054 TextPatternRangeEndpoint_End, document_text_range_provider.Get(),
1055 TextPatternRangeEndpoint_Start, &result));
1056 EXPECT_EQ(1, result);
1057
1058 // Compare the endpoints of "some text" and "more text". The position at the
1059 // end of "some text" is logically equivalent to the position at the start of
1060 // "more text".
1061 EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
1062 TextPatternRangeEndpoint_Start, more_text_range_provider.Get(),
1063 TextPatternRangeEndpoint_Start, &result));
1064 EXPECT_EQ(-1, result);
1065
1066 EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
1067 TextPatternRangeEndpoint_End, more_text_range_provider.Get(),
1068 TextPatternRangeEndpoint_Start, &result));
1069 EXPECT_EQ(0, result);
1070
1071 // Compare the endpoints of "some text" with those of the entire document. The
1072 // position at the start of "some text" is logically equivalent to the
1073 // position at the start of the document.
1074 EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
1075 TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
1076 TextPatternRangeEndpoint_Start, &result));
1077 EXPECT_EQ(0, result);
1078
1079 EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
1080 TextPatternRangeEndpoint_End, document_text_range_provider.Get(),
1081 TextPatternRangeEndpoint_End, &result));
1082 EXPECT_EQ(-1, result);
1083
1084 // Compare the endpoints of "more text" with those of the entire document.
1085 EXPECT_HRESULT_SUCCEEDED(more_text_range_provider->CompareEndpoints(
1086 TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
1087 TextPatternRangeEndpoint_Start, &result));
1088 EXPECT_EQ(1, result);
1089
1090 EXPECT_HRESULT_SUCCEEDED(more_text_range_provider->CompareEndpoints(
1091 TextPatternRangeEndpoint_End, document_text_range_provider.Get(),
1092 TextPatternRangeEndpoint_End, &result));
1093 EXPECT_EQ(0, result);
1094}
1095
1097 TestITextRangeProviderExpandToEnclosingCharacter) {
1098 ui::AXTreeUpdate update = BuildTextDocument({"some text", "more text"});
1099 Init(update);
1100 AXNode* root_node = GetRootAsAXNode();
1101
1102 ComPtr<ITextRangeProvider> text_range_provider;
1103 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
1104 ASSERT_HRESULT_SUCCEEDED(
1105 text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
1106 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"s");
1107
1108 int count;
1109 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1110 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 2, &count));
1111 ASSERT_EQ(2, count);
1112 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1113 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 1, &count));
1114 ASSERT_EQ(1, count);
1115 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"om");
1116
1117 ASSERT_HRESULT_SUCCEEDED(
1118 text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
1119 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"o");
1120
1121 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1122 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 9, &count));
1123 ASSERT_EQ(9, count);
1124 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1125 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 8, &count));
1126 ASSERT_EQ(8, count);
1127 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"mo");
1128
1129 ASSERT_HRESULT_SUCCEEDED(
1130 text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
1131 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"m");
1132
1133 // Move the start and end to the end of the document.
1134 // Expand to enclosing unit should never return a null position.
1135 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1136 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 9, &count));
1137 ASSERT_EQ(8, count);
1138 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1139 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 9, &count));
1140 ASSERT_EQ(9, count);
1141
1142 ASSERT_HRESULT_SUCCEEDED(
1143 text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
1144 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"t");
1145
1146 // Move both endpoints to the position before the start of the "more text"
1147 // anchor. Then, force the start to be on the position after the end of
1148 // "some text" by moving one character backward and one forward.
1149 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1150 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -9, &count));
1151 ASSERT_EQ(-9, count);
1152 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1153 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ -1,
1154 &count));
1155 ASSERT_EQ(-1, count);
1156 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1157 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 1, &count));
1158 ASSERT_EQ(1, count);
1159 ASSERT_HRESULT_SUCCEEDED(
1160 text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
1161 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"m");
1162
1163 // Check that the enclosing element of the range matches ATs expectations.
1164 ComPtr<IRawElementProviderSimple> more_text_provider =
1165 QueryInterfaceFromNode<IRawElementProviderSimple>(
1166 root_node->children()[1]->children()[0]);
1167 ComPtr<IRawElementProviderSimple> enclosing_element;
1168 ASSERT_HRESULT_SUCCEEDED(
1169 text_range_provider->GetEnclosingElement(&enclosing_element));
1170 EXPECT_EQ(more_text_provider.Get(), enclosing_element.Get());
1171}
1172
1174 TestITextRangeProviderExpandToEnclosingWord) {
1175 Init(BuildTextDocument({"some text", "definitely not text"},
1176 /*build_word_boundaries_offsets*/ true));
1177
1178 ComPtr<ITextRangeProvider> text_range_provider;
1179 GetTextRangeProviderFromTextNode(text_range_provider,
1180 GetRootAsAXNode()->children()[1]);
1181 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"definitely not text");
1182
1183 // Start endpoint is already on a word's start boundary.
1184 ASSERT_HRESULT_SUCCEEDED(
1185 text_range_provider->ExpandToEnclosingUnit(TextUnit_Word));
1186 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"definitely ");
1187
1188 // Start endpoint is between a word's start and end boundaries.
1189 int count;
1190 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1191 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ -2,
1192 &count));
1193 ASSERT_EQ(-2, count);
1194 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"xtdefinitely ");
1195
1196 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1197 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 4, &count));
1198 ASSERT_EQ(4, count);
1199 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"xtdefinitely not ");
1200
1201 ASSERT_HRESULT_SUCCEEDED(
1202 text_range_provider->ExpandToEnclosingUnit(TextUnit_Word));
1203 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"text");
1204
1205 // Start endpoint is on a word's end boundary.
1206 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1207 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 18,
1208 &count));
1209 ASSERT_EQ(18, count);
1210 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1211
1212 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1213 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 1, &count));
1214 ASSERT_EQ(1, count);
1215 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L" ");
1216
1217 ASSERT_HRESULT_SUCCEEDED(
1218 text_range_provider->ExpandToEnclosingUnit(TextUnit_Word));
1219 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"not ");
1220}
1221
1223 TestITextRangeProviderExpandToEnclosingLine) {
1224 Init(BuildTextDocument({"line #1", "maybe line #1?", "not line #1"}));
1225
1226 ComPtr<ITextRangeProvider> text_range_provider;
1227 GetTextRangeProviderFromTextNode(text_range_provider,
1228 GetRootAsAXNode()->children()[0]);
1229 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"line #1");
1230
1231 // Start endpoint is already on a line's start boundary.
1232 int count;
1233 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1234 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -11, &count));
1235 ASSERT_EQ(-7, count);
1236 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1237
1238 ASSERT_HRESULT_SUCCEEDED(
1239 text_range_provider->ExpandToEnclosingUnit(TextUnit_Line));
1240 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"line #1");
1241
1242 // Start endpoint is between a line's start and end boundaries.
1243 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1244 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 13,
1245 &count));
1246 ASSERT_EQ(13, count);
1247 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1248
1249 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1250 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 4, &count));
1251 ASSERT_EQ(4, count);
1252 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"line");
1253
1254 ASSERT_HRESULT_SUCCEEDED(
1255 text_range_provider->ExpandToEnclosingUnit(TextUnit_Line));
1256 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"maybe line #1?");
1257
1258 // Start endpoint is on a line's end boundary.
1259 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1260 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 29,
1261 &count));
1262 ASSERT_EQ(25, count);
1263 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1264
1265 ASSERT_HRESULT_SUCCEEDED(
1266 text_range_provider->ExpandToEnclosingUnit(TextUnit_Line));
1267 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"not line #1");
1268}
1269
1270// TOOD(schectman) https://github.com/flutter/flutter/issues/117012
1272 DISABLED_TestITextRangeProviderExpandToEnclosingParagraph) {
1273 Init(BuildAXTreeForMove());
1274 AXNode* root_node = GetRootAsAXNode();
1275
1276 ComPtr<ITextRangeProvider> text_range_provider;
1277 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
1278
1279 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
1280 /*expected_text*/ tree_for_move_full_text.data());
1281
1282 // Start endpoint is already on a paragraph's start boundary.
1283 //
1284 // Note that there are 5 paragraphs, not 6, because the line break element
1285 // between the first and second paragraph is merged in the text of the first
1286 // paragraph. This is standard UIA behavior which merges any trailing
1287 // whitespace with the previous paragraph.
1288 int count;
1289 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1290 TextPatternRangeEndpoint_End, TextUnit_Paragraph, /*count*/ -5, &count));
1291 EXPECT_EQ(-5, count);
1292 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1293
1294 ASSERT_HRESULT_SUCCEEDED(
1295 text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
1296 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"First line of text\n");
1297
1298 // Moving the start by two lines will create a degenerate range positioned
1299 // at the next paragraph (skipping the newline).
1300 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1301 TextPatternRangeEndpoint_Start, TextUnit_Line, /*count*/ 2, &count));
1302 EXPECT_EQ(2, count);
1303 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1304
1305 ASSERT_HRESULT_SUCCEEDED(
1306 text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
1307 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Standalone line\n");
1308
1309 // Move to the next paragraph via MoveEndpointByUnit (line), then move to
1310 // the middle of the paragraph via Move (word), then expand by paragraph.
1311 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1312 TextPatternRangeEndpoint_Start, TextUnit_Line, /*count*/ 1, &count));
1313 EXPECT_EQ(1, count);
1314 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1315 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
1316 /*count*/ 1,
1317 /*expected_text*/
1318 L"",
1319 /*expected_count*/ 1);
1320 ASSERT_HRESULT_SUCCEEDED(
1321 text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
1322 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"bold text\n");
1323
1324 // Create a degenerate range at the end of the document, then expand by
1325 // paragraph.
1326 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1327 TextPatternRangeEndpoint_Start, TextUnit_Document, /*count*/ 1, &count));
1328 EXPECT_EQ(1, count);
1329 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1330 ASSERT_HRESULT_SUCCEEDED(
1331 text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
1332 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Paragraph 2");
1333}
1334
1335// TOOD(schectman) https://github.com/flutter/flutter/issues/117012
1337 DISABLED_TestITextRangeProviderExpandToEnclosingFormat) {
1338 Init(BuildAXTreeForMoveByFormat());
1339 AXNode* root_node = GetRootAsAXNode();
1340 ComPtr<ITextRangeProvider> text_range_provider;
1341 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
1342 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider_internal;
1343 ASSERT_HRESULT_SUCCEEDED(text_range_provider->QueryInterface(
1344 IID_PPV_ARGS(&text_range_provider_internal)));
1345
1347 text_range_provider,
1348 L"Text with formatting\nStandalone line with no formatting\nbold "
1349 L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParagraph 4");
1350
1351 // https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationtextrange-expandtoenclosingunit
1352 // Consider two consecutive text units A and B.
1353 // The documentation illustrates 9 cases, but cases 1 and 9 are equivalent.
1354 // In each case, the expected output is a range from start of A to end of A.
1355
1356 // Create a range encompassing nodes 11-15 which will serve as text units A
1357 // and B for this test.
1358 ComPtr<ITextRangeProvider> units_a_b_provider;
1359 ASSERT_HRESULT_SUCCEEDED(text_range_provider->Clone(&units_a_b_provider));
1360 CopyOwnerToClone(text_range_provider.Get(), units_a_b_provider.Get());
1361
1362 int count;
1363 ASSERT_HRESULT_SUCCEEDED(units_a_b_provider->MoveEndpointByUnit(
1364 TextPatternRangeEndpoint_Start, TextUnit_Line, /*count*/ 5, &count));
1365 ASSERT_EQ(5, count);
1366 ASSERT_HRESULT_SUCCEEDED(units_a_b_provider->MoveEndpointByUnit(
1367 TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ -1, &count));
1368 ASSERT_EQ(-1, count);
1369 EXPECT_UIA_TEXTRANGE_EQ(units_a_b_provider,
1370 L"Paragraph 1\nParagraph 2\nParagraph 3");
1371
1372 // Create a range encompassing node 11 which will serve as our expected
1373 // value of a range from start of A to end of A.
1374 ComPtr<ITextRangeProvider> unit_a_provider;
1375 ASSERT_HRESULT_SUCCEEDED(units_a_b_provider->Clone(&unit_a_provider));
1376 CopyOwnerToClone(units_a_b_provider.Get(), unit_a_provider.Get());
1377 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->MoveEndpointByUnit(
1378 TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ -2, &count));
1379 ASSERT_EQ(-2, count);
1380 EXPECT_UIA_TEXTRANGE_EQ(unit_a_provider, L"Paragraph 1");
1381
1382 // Case 1: Degenerate range at start of A.
1383 {
1384 SCOPED_TRACE("Case 1: Degenerate range at start of A.");
1385 ComPtr<ITextRangeProvider> test_case_provider;
1386 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
1387 CopyOwnerToClone(unit_a_provider.Get(), test_case_provider.Get());
1388 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByRange(
1389 TextPatternRangeEndpoint_End, test_case_provider.Get(),
1390 TextPatternRangeEndpoint_Start));
1391 EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"");
1392
1393 ASSERT_HRESULT_SUCCEEDED(
1394 test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
1395 BOOL are_same;
1396 ASSERT_HRESULT_SUCCEEDED(
1397 test_case_provider->Compare(unit_a_provider.Get(), &are_same));
1398 EXPECT_TRUE(are_same);
1399 }
1400
1401 // Case 2: Range from start of A to middle of A.
1402 {
1403 SCOPED_TRACE("Case 2: Range from start of A to middle of A.");
1404 ComPtr<ITextRangeProvider> test_case_provider;
1405 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
1406 CopyOwnerToClone(unit_a_provider.Get(), test_case_provider.Get());
1407 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
1408 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -7,
1409 &count));
1410 ASSERT_EQ(-7, count);
1411 EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"Para");
1412
1413 ASSERT_HRESULT_SUCCEEDED(
1414 test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
1415 BOOL are_same;
1416 ASSERT_HRESULT_SUCCEEDED(
1417 test_case_provider->Compare(unit_a_provider.Get(), &are_same));
1418 EXPECT_TRUE(are_same);
1419 }
1420
1421 // Case 3: Range from start of A to end of A.
1422 {
1423 SCOPED_TRACE("Case 3: Range from start of A to end of A.");
1424 ComPtr<ITextRangeProvider> test_case_provider;
1425 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
1426 CopyOwnerToClone(unit_a_provider.Get(), test_case_provider.Get());
1427 EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"Paragraph 1");
1428
1429 ASSERT_HRESULT_SUCCEEDED(
1430 test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
1431 BOOL are_same;
1432 ASSERT_HRESULT_SUCCEEDED(
1433 test_case_provider->Compare(unit_a_provider.Get(), &are_same));
1434 EXPECT_TRUE(are_same);
1435 }
1436
1437 // Case 4: Range from start of A to middle of B.
1438 {
1439 SCOPED_TRACE("Case 4: Range from start of A to middle of B.");
1440 ComPtr<ITextRangeProvider> test_case_provider;
1441 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
1442 CopyOwnerToClone(unit_a_provider.Get(), test_case_provider.Get());
1443 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
1444 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 5, &count));
1445 ASSERT_EQ(5, count);
1446 EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"Paragraph 1\nPara");
1447
1448 ASSERT_HRESULT_SUCCEEDED(
1449 test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
1450 BOOL are_same;
1451 ASSERT_HRESULT_SUCCEEDED(
1452 test_case_provider->Compare(unit_a_provider.Get(), &are_same));
1453 EXPECT_TRUE(are_same);
1454 }
1455
1456 // Case 5: Degenerate range in middle of A.
1457 {
1458 SCOPED_TRACE("Case 5: Degenerate range in middle of A.");
1459 ComPtr<ITextRangeProvider> test_case_provider;
1460 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
1461 CopyOwnerToClone(unit_a_provider.Get(), test_case_provider.Get());
1462 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
1463 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 4,
1464 &count));
1465 ASSERT_EQ(4, count);
1466 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByRange(
1467 TextPatternRangeEndpoint_End, test_case_provider.Get(),
1468 TextPatternRangeEndpoint_Start));
1469 EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"");
1470
1471 ASSERT_HRESULT_SUCCEEDED(
1472 test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
1473 BOOL are_same;
1474 ASSERT_HRESULT_SUCCEEDED(
1475 test_case_provider->Compare(unit_a_provider.Get(), &are_same));
1476 EXPECT_TRUE(are_same);
1477 }
1478
1479 // Case 6: Range from middle of A to middle of A.
1480 {
1481 SCOPED_TRACE("Case 6: Range from middle of A to middle of A.");
1482 ComPtr<ITextRangeProvider> test_case_provider;
1483 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
1484 CopyOwnerToClone(unit_a_provider.Get(), test_case_provider.Get());
1485 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
1486 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 4,
1487 &count));
1488 ASSERT_EQ(4, count);
1489 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
1490 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -2,
1491 &count));
1492 ASSERT_EQ(-2, count);
1493 EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"graph");
1494
1495 ASSERT_HRESULT_SUCCEEDED(
1496 test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
1497 BOOL are_same;
1498 ASSERT_HRESULT_SUCCEEDED(
1499 test_case_provider->Compare(unit_a_provider.Get(), &are_same));
1500 EXPECT_TRUE(are_same);
1501 }
1502
1503 // Case 7: Range from middle of A to end of A.
1504 {
1505 SCOPED_TRACE("Case 7: Range from middle of A to end of A.");
1506 ComPtr<ITextRangeProvider> test_case_provider;
1507 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
1508 CopyOwnerToClone(unit_a_provider.Get(), test_case_provider.Get());
1509 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
1510 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 4,
1511 &count));
1512 ASSERT_EQ(4, count);
1513 EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"graph 1");
1514
1515 ASSERT_HRESULT_SUCCEEDED(
1516 test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
1517 BOOL are_same;
1518 ASSERT_HRESULT_SUCCEEDED(
1519 test_case_provider->Compare(unit_a_provider.Get(), &are_same));
1520 EXPECT_TRUE(are_same);
1521 }
1522
1523 // Case 8: Range from middle of A to middle of B.
1524 {
1525 SCOPED_TRACE("Case 8: Range from middle of A to middle of B.");
1526 ComPtr<ITextRangeProvider> test_case_provider;
1527 ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
1528 CopyOwnerToClone(unit_a_provider.Get(), test_case_provider.Get());
1529 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
1530 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 5,
1531 &count));
1532 ASSERT_EQ(5, count);
1533 ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
1534 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 5, &count));
1535 ASSERT_EQ(5, count);
1536 EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"raph 1\nPara");
1537
1538 ASSERT_HRESULT_SUCCEEDED(
1539 test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
1540 BOOL are_same;
1541 ASSERT_HRESULT_SUCCEEDED(
1542 test_case_provider->Compare(unit_a_provider.Get(), &are_same));
1543 EXPECT_TRUE(are_same);
1544 }
1545}
1546
1547// TOOD(schectman) https://github.com/flutter/flutter/issues/117012
1549 DISABLED_TestITextRangeProviderExpandToEnclosingFormatWithEmptyObjects) {
1550 // This test updates the tree structure to test a specific edge case.
1551 //
1552 // When using heading navigation, the empty objects (see
1553 // AXPosition::IsEmptyObjectReplacedByCharacter for information about empty
1554 // objects) sometimes cause a problem with
1555 // AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit.
1556 // With some specific AXTree (like the one used below), the empty object
1557 // causes ExpandToEnclosingUnit to move the range back on the heading that it
1558 // previously was instead of moving it forward/backward to the next heading.
1559 // To avoid this, empty objects are always marked as format boundaries.
1560 //
1561 // The issue normally occurs when a heading is directly followed by an ignored
1562 // empty object, itself followed by an unignored empty object.
1563 //
1564 // ++1 kRootWebArea
1565 // ++++2 kHeading
1566 // ++++++3 kStaticText
1567 // ++++++++4 kInlineTextBox
1568 // ++++5 kGenericContainer ignored
1569 // ++++6 kButton
1570 ui::AXNodeData root_1;
1571 ui::AXNodeData heading_2;
1572 ui::AXNodeData static_text_3;
1573 ui::AXNodeData inline_box_4;
1574 ui::AXNodeData generic_container_5;
1575 ui::AXNodeData button_6;
1576
1577 root_1.id = 1;
1578 heading_2.id = 2;
1579 static_text_3.id = 3;
1580 inline_box_4.id = 4;
1581 generic_container_5.id = 5;
1582 button_6.id = 6;
1583
1585 root_1.child_ids = {heading_2.id, generic_container_5.id, button_6.id};
1586
1587 heading_2.role = ax::mojom::Role::kHeading;
1588 heading_2.child_ids = {static_text_3.id};
1589
1590 static_text_3.role = ax::mojom::Role::kStaticText;
1591 static_text_3.child_ids = {inline_box_4.id};
1592 static_text_3.SetName("3.14");
1593
1595 inline_box_4.SetName("3.14");
1596
1597 generic_container_5.role = ax::mojom::Role::kGenericContainer;
1598 generic_container_5.AddBoolAttribute(
1600 generic_container_5.AddState(ax::mojom::State::kIgnored);
1601
1602 button_6.role = ax::mojom::Role::kButton;
1603
1605 ui::AXTreeData tree_data;
1607 update.tree_data = tree_data;
1608 update.has_tree_data = true;
1609 update.root_id = root_1.id;
1610 update.nodes.push_back(root_1);
1611 update.nodes.push_back(heading_2);
1612 update.nodes.push_back(static_text_3);
1613 update.nodes.push_back(inline_box_4);
1614 update.nodes.push_back(generic_container_5);
1615 update.nodes.push_back(button_6);
1616
1617 Init(update);
1618
1619 AXNode* root_node = GetRootAsAXNode();
1620 ComPtr<ITextRangeProvider> text_range_provider;
1621 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
1622
1623 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"3.14\xFFFC");
1624
1625 // Create a degenerate range positioned at the boundary between nodes 4 and 6,
1626 // e.g., "3.14<>" and "<\xFFFC>" (because node 5 is ignored).
1627 int count;
1628 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1629 TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 5, &count));
1630 ASSERT_EQ(5, count);
1631 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1632 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -1, &count));
1633 ASSERT_EQ(-1, count);
1634 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
1635
1636 // ExpandToEnclosingUnit should move the range to the next non-ignored empty
1637 // object (i.e, node 6), and not at the beginning of node 4.
1638 ASSERT_HRESULT_SUCCEEDED(
1639 text_range_provider->ExpandToEnclosingUnit(TextUnit_Format));
1640 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"\xFFFC");
1641}
1642
1644 TestITextRangeProviderExpandToEnclosingDocument) {
1645 Init(BuildTextDocument({"some text", "more text", "even more text"}));
1646
1647 AXNode* root_node = GetRootAsAXNode();
1648 AXNode* text_node = root_node->children()[0];
1649 AXNode* more_text_node = root_node->children()[1];
1650 AXNode* even_more_text_node = root_node->children()[2];
1651
1652 // Run the test twice, one for TextUnit_Document and once for TextUnit_Page,
1653 // since they should have identical behavior.
1654 const TextUnit textunit_types[] = {TextUnit_Document, TextUnit_Page};
1655 ComPtr<ITextRangeProvider> text_range_provider;
1656
1657 for (auto& textunit : textunit_types) {
1658 GetTextRangeProviderFromTextNode(text_range_provider, text_node);
1659 ASSERT_HRESULT_SUCCEEDED(
1660 text_range_provider->ExpandToEnclosingUnit(textunit));
1661 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
1662 L"some textmore texteven more text");
1663
1664 GetTextRangeProviderFromTextNode(text_range_provider, more_text_node);
1665 ASSERT_HRESULT_SUCCEEDED(
1666 text_range_provider->ExpandToEnclosingUnit(textunit));
1667 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
1668 L"some textmore texteven more text");
1669
1670 GetTextRangeProviderFromTextNode(text_range_provider, even_more_text_node);
1671 ASSERT_HRESULT_SUCCEEDED(
1672 text_range_provider->ExpandToEnclosingUnit(textunit));
1673 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
1674 L"some textmore texteven more text");
1675 }
1676}
1677
1678// TOOD(schectman) Why should this be ignored?
1679// https://github.com/flutter/flutter/issues/117012
1681 DISABLED_TestITextRangeProviderIgnoredForTextNavigation) {
1682 // ++1 kRootWebArea
1683 // ++++2 kStaticText
1684 // ++++++3 kInlineTextBox foo
1685 // ++++4 kSplitter
1686 // ++++5 kStaticText
1687 // ++++++6 kInlineTextBox bar
1688 // ++++7 genericContainer
1689 // ++++8 kStaticText
1690 // ++++++9 kInlineTextBox baz
1691 ui::AXNodeData root_1;
1692 ui::AXNodeData static_text_2;
1693 ui::AXNodeData inline_box_3;
1694 ui::AXNodeData splitter_4;
1695 ui::AXNodeData static_text_5;
1696 ui::AXNodeData inline_box_6;
1697 ui::AXNodeData generic_container_7;
1698 ui::AXNodeData static_text_8;
1699 ui::AXNodeData inline_box_9;
1700
1701 root_1.id = 1;
1702 static_text_2.id = 2;
1703 inline_box_3.id = 3;
1704 splitter_4.id = 4;
1705 static_text_5.id = 5;
1706 inline_box_6.id = 6;
1707 generic_container_7.id = 7;
1708 static_text_8.id = 8;
1709 inline_box_9.id = 9;
1710
1712 root_1.child_ids = {static_text_2.id, splitter_4.id, static_text_5.id,
1713 generic_container_7.id, static_text_8.id};
1714
1715 static_text_2.role = ax::mojom::Role::kStaticText;
1716 static_text_2.child_ids = {inline_box_3.id};
1717 static_text_2.SetName("foo");
1718
1720 inline_box_3.SetName("foo");
1721
1722 splitter_4.role = ax::mojom::Role::kSplitter;
1724 true);
1725
1726 static_text_5.role = ax::mojom::Role::kStaticText;
1727 static_text_5.child_ids = {inline_box_6.id};
1728 static_text_5.SetName("bar");
1729
1731 inline_box_6.SetName("bar");
1732
1733 generic_container_7.role = ax::mojom::Role::kGenericContainer;
1734 generic_container_7.AddBoolAttribute(
1736
1737 static_text_8.role = ax::mojom::Role::kStaticText;
1738 static_text_8.child_ids = {inline_box_9.id};
1739 static_text_8.SetName("bar");
1740
1742 inline_box_9.SetName("baz");
1743
1745 ui::AXTreeData tree_data;
1747 update.tree_data = tree_data;
1748 update.has_tree_data = true;
1749 update.root_id = root_1.id;
1750 update.nodes = {
1751 root_1, static_text_2, inline_box_3, splitter_4,
1752 static_text_5, inline_box_6, generic_container_7, static_text_8,
1753 inline_box_9};
1754
1755 Init(update);
1756
1757 AXNode* root_node = GetRootAsAXNode();
1758 ComPtr<ITextRangeProvider> text_range_provider;
1759 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
1760
1761 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
1762 L"foo\n\xFFFC\nbar\n\xFFFC\nbaz");
1763
1764 int count;
1765 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1766 TextPatternRangeEndpoint_Start, TextUnit_Paragraph, /*count*/ 1, &count));
1767 ASSERT_EQ(1, count);
1768 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"bar\n\xFFFC\nbaz");
1769
1770 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
1771 TextPatternRangeEndpoint_Start, TextUnit_Paragraph, /*count*/ 1, &count));
1772 ASSERT_EQ(1, count);
1773 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"baz");
1774}
1775
1776// TODO(schectman) Segfault after test completes.
1777// Why? https://github.com/flutter/flutter/issues/117012
1779 DISABLED_TestITextRangeProviderInvalidCalls) {
1780 // Test for when a text range provider is invalid. Because no ax tree is
1781 // available, the anchor is invalid, so the text range provider fails the
1782 // validate call.
1783 {
1784 Init(BuildTextDocument({}));
1785
1786 ComPtr<ITextRangeProvider> text_range_provider;
1787 GetTextRangeProviderFromTextNode(text_range_provider, GetRootAsAXNode());
1788
1789 DestroyTree();
1790 ComPtr<ITextRangeProvider> text_range_provider_clone;
1792 text_range_provider->Clone(&text_range_provider_clone));
1793
1794 BOOL compare_result;
1795 EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->Compare(
1796 text_range_provider.Get(), &compare_result));
1797
1798 int compare_endpoints_result;
1799 EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->CompareEndpoints(
1800 TextPatternRangeEndpoint_Start, text_range_provider.Get(),
1801 TextPatternRangeEndpoint_Start, &compare_endpoints_result));
1802
1803 VARIANT attr_val;
1804 V_VT(&attr_val) = VT_BOOL;
1805 V_BOOL(&attr_val) = VARIANT_TRUE;
1806 ComPtr<ITextRangeProvider> matched_range_provider;
1807 EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->FindAttribute(
1808 UIA_IsHiddenAttributeId, attr_val, true, &matched_range_provider));
1809
1810 EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->MoveEndpointByRange(
1811 TextPatternRangeEndpoint_Start, text_range_provider.Get(),
1812 TextPatternRangeEndpoint_Start));
1813
1814 EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->Select());
1815 }
1816
1817 // Test for when this provider is valid, but the other provider is not an
1818 // instance of AXPlatformNodeTextRangeProviderWin, so no operation can be
1819 // performed on the other provider.
1820 {
1821 Init(BuildTextDocument({}));
1822
1823 ComPtr<ITextRangeProvider> this_provider;
1824 GetTextRangeProviderFromTextNode(this_provider, GetRootAsAXNode());
1825
1826 ComPtr<ITextRangeProvider> other_provider_different_type;
1828 &other_provider_different_type);
1829
1830 BOOL compare_result;
1831 EXPECT_UIA_INVALIDOPERATION(this_provider->Compare(
1832 other_provider_different_type.Get(), &compare_result));
1833
1834 int compare_endpoints_result;
1835 EXPECT_UIA_INVALIDOPERATION(this_provider->CompareEndpoints(
1836 TextPatternRangeEndpoint_Start, other_provider_different_type.Get(),
1837 TextPatternRangeEndpoint_Start, &compare_endpoints_result));
1838
1839 EXPECT_UIA_INVALIDOPERATION(this_provider->MoveEndpointByRange(
1840 TextPatternRangeEndpoint_Start, other_provider_different_type.Get(),
1841 TextPatternRangeEndpoint_Start));
1842 }
1843}
1844
1845TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetText) {
1846 Init(BuildTextDocument({"some text", "more text"}));
1847
1848 AXNode* root_node = GetRootAsAXNode();
1849 AXNode* text_node = root_node->children()[0];
1850
1851 ComPtr<ITextRangeProvider> text_range_provider;
1852 GetTextRangeProviderFromTextNode(text_range_provider, text_node);
1853
1854 base::win::ScopedBstr text_content;
1855 EXPECT_HRESULT_SUCCEEDED(
1856 text_range_provider->GetText(-1, text_content.Receive()));
1857 EXPECT_STREQ(text_content.Get(), L"some text");
1858 text_content.Reset();
1859
1860 EXPECT_HRESULT_SUCCEEDED(
1861 text_range_provider->GetText(4, text_content.Receive()));
1862 EXPECT_STREQ(text_content.Get(), L"some");
1863 text_content.Reset();
1864
1865 EXPECT_HRESULT_SUCCEEDED(
1866 text_range_provider->GetText(0, text_content.Receive()));
1867 EXPECT_STREQ(text_content.Get(), L"");
1868 text_content.Reset();
1869
1870 EXPECT_HRESULT_SUCCEEDED(
1871 text_range_provider->GetText(9, text_content.Receive()));
1872 EXPECT_STREQ(text_content.Get(), L"some text");
1873 text_content.Reset();
1874
1875 EXPECT_HRESULT_SUCCEEDED(
1876 text_range_provider->GetText(10, text_content.Receive()));
1877 EXPECT_STREQ(text_content.Get(), L"some text");
1878 text_content.Reset();
1879
1880 EXPECT_HRESULT_FAILED(text_range_provider->GetText(-1, nullptr));
1881
1882 EXPECT_HRESULT_FAILED(
1883 text_range_provider->GetText(-2, text_content.Receive()));
1884 text_content.Reset();
1885
1886 ComPtr<ITextRangeProvider> document_textrange;
1887 GetTextRangeProviderFromTextNode(document_textrange, root_node);
1888
1889 EXPECT_HRESULT_SUCCEEDED(
1890 document_textrange->GetText(-1, text_content.Receive()));
1891 EXPECT_STREQ(text_content.Get(), L"some textmore text");
1892 text_content.Reset();
1893}
1894
1896 TestITextRangeProviderMoveCharacter) {
1897 Init(BuildAXTreeForMove());
1898 AXNode* root_node = GetRootAsAXNode();
1899
1900 ComPtr<ITextRangeProvider> text_range_provider;
1901 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
1902
1903 // Moving by 0 should have no effect.
1904 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, /*count*/ 0,
1905 /*expected_text*/
1906 L"First line of text\nStandalone line\n"
1907 L"bold textParagraph 1Paragraph 2",
1908 /*expected_count*/ 0);
1909
1910 // Move forward.
1911 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1912 /*count*/ 1,
1913 /*expected_text*/ L"i",
1914 /*expected_count*/ 1);
1915 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1916 /*count*/ 18,
1917 /*expected_text*/ L"S",
1918 /*expected_count*/ 18);
1919 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1920 /*count*/ 16,
1921 /*expected_text*/ L"b",
1922 /*expected_count*/ 16);
1923 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1924 /*count*/ 60,
1925 /*expected_text*/ L"2",
1926 /*expected_count*/ 30);
1927
1928 // Trying to move past the last character should have no effect.
1929 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1930 /*count*/ 1,
1931 /*expected_text*/ L"2",
1932 /*expected_count*/ 0);
1933
1934 // Move backward.
1935 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1936 /*count*/ -2,
1937 /*expected_text*/ L"h",
1938 /*expected_count*/ -2);
1939 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1940 /*count*/ -9,
1941 /*expected_text*/ L"1",
1942 /*expected_count*/ -9);
1943 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1944 /*count*/ -60,
1945 /*expected_text*/ L"F",
1946 /*expected_count*/ -54);
1947
1948 // Moving backward by any number of characters at the start of document
1949 // should have no effect.
1950 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1951 /*count*/ -1,
1952 /*expected_text*/
1953 L"F",
1954 /*expected_count*/ 0);
1955
1956 // Degenerate range moves.
1958 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
1959 /*count*/ -1,
1960 /*expected_text*/ L"",
1961 /*expected_count*/ -1);
1962 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1963 /*count*/ 4,
1964 /*expected_text*/ L"",
1965 /*expected_count*/ 4);
1966 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1967 /*count*/ 70,
1968 /*expected_text*/ L"",
1969 /*expected_count*/ 62);
1970
1971 // Trying to move past the last character should have no effect.
1972 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1973 /*count*/ 70,
1974 /*expected_text*/ L"",
1975 /*expected_count*/ 0);
1976 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
1977 /*count*/ -2,
1978 /*expected_text*/ L"",
1979 /*expected_count*/ -2);
1980}
1981
1982// TODO(schectman) https://github.com/flutter/flutter/issues/117012
1984 DISABLED_TestITextRangeProviderMoveFormat) {
1985 Init(BuildAXTreeForMoveByFormat());
1986 AXNode* root_node = GetRootAsAXNode();
1987
1988 ComPtr<ITextRangeProvider> text_range_provider;
1989 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
1990
1991 // Moving by 0 should have no effect.
1993 text_range_provider, TextUnit_Format,
1994 /*count*/ 0,
1995 /*expected_text*/
1996 L"Text with formatting\nStandalone line with no formatting\nbold "
1997 L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParagraph 4",
1998 /*expected_count*/ 0);
1999
2000 // Move forward.
2001 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2002 /*count*/ 1,
2003 /*expected_text*/ L"\nStandalone line with no formatting\n",
2004 /*expected_count*/ 1);
2005 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2006 /*count*/ 2,
2007 /*expected_text*/ L"Paragraph 1",
2008 /*expected_count*/ 2);
2009 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2010 /*count*/ 1,
2011 /*expected_text*/ L"Paragraph 2\nParagraph 3",
2012 /*expected_count*/ 1);
2013 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2014 /*count*/ 1,
2015 /*expected_text*/ L"Paragraph 4",
2016 /*expected_count*/ 1);
2017
2018 // Trying to move past the last format should have no effect.
2019 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2020 /*count*/ 1,
2021 /*expected_text*/ L"Paragraph 4",
2022 /*expected_count*/ 0);
2023
2024 // Move backward.
2025 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2026 /*count*/ -3,
2027 /*expected_text*/ L"bold text",
2028 /*expected_count*/ -3);
2029 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2030 /*count*/ -1,
2031 /*expected_text*/ L"\nStandalone line with no formatting\n",
2032 /*expected_count*/ -1);
2033 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2034 /*count*/ -1,
2035 /*expected_text*/ L"Text with formatting",
2036 /*expected_count*/ -1);
2037
2038 // Moving backward by any number of formats at the start of document
2039 // should have no effect.
2040 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2041 /*count*/ -1,
2042 /*expected_text*/
2043 L"Text with formatting",
2044 /*expected_count*/ 0);
2045
2046 // Test degenerate range creation at the beginning of the document.
2048 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
2049 /*count*/ -1,
2050 /*expected_text*/ L"",
2051 /*expected_count*/ -1);
2053 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
2054 /*count*/ 1,
2055 /*expected_text*/ L"Text with formatting",
2056 /*expected_count*/ 1);
2057
2058 // Test degenerate range creation at the end of the document.
2059 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2060 /*count*/ 5,
2061 /*expected_text*/ L"Paragraph 4",
2062 /*expected_count*/ 5);
2064 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
2065 /*count*/ 1,
2066 /*expected_text*/ L"",
2067 /*expected_count*/ 1);
2069 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
2070 /*count*/ -1,
2071 /*expected_text*/ L"Paragraph 4",
2072 /*expected_count*/ -1);
2074 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
2075 /*count*/ 1,
2076 /*expected_text*/ L"",
2077 /*expected_count*/ 1);
2079 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
2080 /*count*/ -1,
2081 /*expected_text*/ L"Paragraph 4",
2082 /*expected_count*/ -1);
2083
2084 // Degenerate range moves.
2085 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2086 /*count*/ -5,
2087 /*expected_text*/ L"Text with formatting",
2088 /*expected_count*/ -5);
2090 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
2091 /*count*/ -1,
2092 /*expected_text*/ L"",
2093 /*expected_count*/ -1);
2094 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2095 /*count*/ 3,
2096 /*expected_text*/ L"",
2097 /*expected_count*/ 3);
2098 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2099 /*count*/ 70,
2100 /*expected_text*/ L"",
2101 /*expected_count*/ 3);
2102
2103 // Trying to move past the last format should have no effect.
2104 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2105 /*count*/ 70,
2106 /*expected_text*/ L"",
2107 /*expected_count*/ 0);
2108 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
2109 /*count*/ -2,
2110 /*expected_text*/ L"",
2111 /*expected_count*/ -2);
2112}
2113
2114// TODO(schectman) https://github.com/flutter/flutter/issues/117012
2116 DISABLED_TestITextRangeProviderMoveWord) {
2117 Init(BuildAXTreeForMove());
2118 AXNode* root_node = GetRootAsAXNode();
2119
2120 ComPtr<ITextRangeProvider> text_range_provider;
2121 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
2122
2123 // Moving by 0 should have no effect.
2124 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word, /*count*/ 0,
2125 /*expected_text*/ tree_for_move_full_text.data(),
2126 /*expected_count*/ 0);
2127
2128 // Move forward.
2129 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2130 /*count*/ 1,
2131 /*expected_text*/ L"line ",
2132 /*expected_count*/ 1);
2133 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2134 /*count*/ 2,
2135 /*expected_text*/ L"text",
2136 /*expected_count*/ 2);
2137 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2138 /*count*/ 2,
2139 /*expected_text*/ L"line",
2140 /*expected_count*/ 2);
2141 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2142 /*count*/ 3,
2143 /*expected_text*/ L"Paragraph ",
2144 /*expected_count*/ 3);
2145 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2146 /*count*/ 6,
2147 /*expected_text*/ L"2",
2148 /*expected_count*/ 3);
2149
2150 // Trying to move past the last word should have no effect.
2151 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2152 /*count*/ 1,
2153 /*expected_text*/ L"2",
2154 /*expected_count*/ 0);
2155
2156 // Move backward.
2157 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2158 /*count*/ -3,
2159 /*expected_text*/ L"Paragraph ",
2160 /*expected_count*/ -3);
2161 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2162 /*count*/ -3,
2163 /*expected_text*/ L"line",
2164 /*expected_count*/ -3);
2165 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2166 /*count*/ -2,
2167 /*expected_text*/ L"text",
2168 /*expected_count*/ -2);
2169 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2170 /*count*/ -6,
2171 /*expected_text*/ L"First ",
2172 /*expected_count*/ -3);
2173
2174 // Moving backward by any number of words at the start of document
2175 // should have no effect.
2176 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2177 /*count*/ -20,
2178 /*expected_text*/ L"First ",
2179 /*expected_count*/ 0);
2180
2181 // Degenerate range moves.
2182 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2183 TextPatternRangeEndpoint_End, TextUnit_Word,
2184 /*count*/ -1,
2185 /*expected_text*/ L"",
2186 /*expected_count*/ -1);
2187 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2188 /*count*/ 4,
2189 /*expected_text*/ L"",
2190 /*expected_count*/ 4);
2191 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2192 /*count*/ 70,
2193 /*expected_text*/ L"",
2194 /*expected_count*/ 8);
2195
2196 // Trying to move past the last word should have no effect.
2197 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2198 /*count*/ 70,
2199 /*expected_text*/ L"",
2200 /*expected_count*/ 0);
2201 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
2202 /*count*/ -2,
2203 /*expected_text*/ L"",
2204 /*expected_count*/ -2);
2205}
2206
2207// TODO(schectman) https://github.com/flutter/flutter/issues/117012
2209 DISABLED_TestITextRangeProviderMoveLine) {
2210 Init(BuildAXTreeForMove());
2211 AXNode* root_node = GetRootAsAXNode();
2212
2213 ComPtr<ITextRangeProvider> text_range_provider;
2214 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
2215
2216 // Moving by 0 should have no effect.
2217 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line, /*count*/ 0,
2218 /*expected_text*/ tree_for_move_full_text.data(),
2219 /*expected_count*/ 0);
2220
2221 // Move forward.
2222 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2223 /*count*/ 2,
2224 /*expected_text*/ L"Standalone line",
2225 /*expected_count*/ 2);
2226 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2227 /*count*/ 1,
2228 /*expected_text*/ L"bold text",
2229 /*expected_count*/ 1);
2230 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2231 /*count*/ 10,
2232 /*expected_text*/ L"Paragraph 2",
2233 /*expected_count*/ 2);
2234
2235 // Trying to move past the last line should have no effect.
2236 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2237 /*count*/ 1,
2238 /*expected_text*/ L"Paragraph 2",
2239 /*expected_count*/ 0);
2240
2241 // Move backward.
2242 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2243 /*count*/ -1,
2244 /*expected_text*/ L"Paragraph 1",
2245 /*expected_count*/ -1);
2246 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2247 /*count*/ -5,
2248 /*expected_text*/ L"First line of text",
2249 /*expected_count*/ -4);
2250
2251 // Moving backward by any number of lines at the start of document
2252 // should have no effect.
2253 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2254 /*count*/ -20,
2255 /*expected_text*/ L"First line of text",
2256 /*expected_count*/ 0);
2257
2258 // Degenerate range moves.
2259 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2260 TextPatternRangeEndpoint_End, TextUnit_Line,
2261 /*count*/ -1,
2262 /*expected_text*/ L"",
2263 /*expected_count*/ -1);
2264 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2265 /*count*/ 4,
2266 /*expected_text*/ L"",
2267 /*expected_count*/ 4);
2268 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2269 /*count*/ 70,
2270 /*expected_text*/ L"",
2271 /*expected_count*/ 2);
2272
2273 // Trying to move past the last line should have no effect.
2274 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2275 /*count*/ 70,
2276 /*expected_text*/ L"",
2277 /*expected_count*/ 0);
2278 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
2279 /*count*/ -2,
2280 /*expected_text*/ L"",
2281 /*expected_count*/ -2);
2282}
2283
2284// TODO(schectman) https://github.com/flutter/flutter/issues/117012
2286 DISABLED_TestITextRangeProviderMoveParagraph) {
2287 Init(BuildAXTreeForMove());
2288 AXNode* root_node = GetRootAsAXNode();
2289
2290 ComPtr<ITextRangeProvider> text_range_provider;
2291 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
2292
2293 // Moving by 0 should have no effect.
2294 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph, /*count*/ 0,
2295 /*expected_text*/ tree_for_move_full_text.data(),
2296 /*expected_count*/ 0);
2297
2299 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
2300 /*count*/ -4,
2301 /*expected_text*/ L"First line of text\n",
2302 /*expected_count*/ -4);
2303
2304 // The first line break does not create an empty paragraph because even though
2305 // it is in a block element (i.e. a kGenericContainer) of its own which is a
2306 // line breaking object, it merges with the previous paragraph. This is
2307 // standard UIA behavior which merges any trailing whitespace with the
2308 // previous paragraph.
2310 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
2311 /*count*/ -1,
2312 /*expected_text*/ L"",
2313 /*expected_count*/ -1);
2315 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
2316 /*count*/ 1,
2317 /*expected_text*/ L"First line of text\n",
2318 /*expected_count*/ 1);
2319
2320 //
2321 // Move forward.
2322 //
2323
2324 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2325 /*count*/ 1,
2326 /*expected_text*/ L"Standalone line\n",
2327 /*expected_count*/ 1);
2328 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2329 /*count*/ 1,
2330 /*expected_text*/ L"bold text\n",
2331 /*expected_count*/ 1);
2332 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2333 /*count*/ 1,
2334 /*expected_text*/ L"Paragraph 1\n",
2335 /*expected_count*/ 1);
2336 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2337 /*count*/ 1,
2338 /*expected_text*/ L"Paragraph 2",
2339 /*expected_count*/ 1);
2340 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2341 /*count*/ 2,
2342 /*expected_text*/ L"Paragraph 2",
2343 /*expected_count*/ 0);
2344
2345 // Trying to move past the last paragraph should have no effect.
2346 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2347 /*count*/ 1,
2348 /*expected_text*/ L"Paragraph 2",
2349 /*expected_count*/ 0);
2350
2351 //
2352 // Move backward.
2353 //
2354
2355 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2356 /*count*/ -1,
2357 /*expected_text*/ L"Paragraph 1\n",
2358 /*expected_count*/ -1);
2359 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2360 /*count*/ -1,
2361 /*expected_text*/ L"bold text\n",
2362 /*expected_count*/ -1);
2363 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2364 /*count*/ -1,
2365 /*expected_text*/ L"Standalone line\n",
2366 /*expected_count*/ -1);
2367 // The first line break creates an empty paragraph because it is in a block
2368 // element (i.e. a kGenericContainer) of its own which is a line breaking
2369 // object. It's like having a <br> element wrapped inside a <div>.
2370 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2371 /*count*/ -1,
2372 /*expected_text*/ L"First line of text\n",
2373 /*expected_count*/ -1);
2374 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2375 /*count*/ -1,
2376 /*expected_text*/ L"First line of text\n",
2377 /*expected_count*/ 0);
2378
2379 // Moving backward by any number of paragraphs at the start of document
2380 // should have no effect.
2381 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2382 /*count*/ -1,
2383 /*expected_text*/ L"First line of text\n",
2384 /*expected_count*/ 0);
2385
2386 // Test degenerate range creation at the beginning of the document.
2388 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
2389 /*count*/ -1,
2390 /*expected_text*/ L"",
2391 /*expected_count*/ -1);
2393 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
2394 /*count*/ 1,
2395 /*expected_text*/ L"First line of text\n",
2396 /*expected_count*/ 1);
2397
2398 // Test degenerate range creation at the end of the document.
2399 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2400 /*count*/ 5,
2401 /*expected_text*/ L"Paragraph 2",
2402 /*expected_count*/ 4);
2404 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
2405 /*count*/ 1,
2406 /*expected_text*/ L"",
2407 /*expected_count*/ 1);
2409 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
2410 /*count*/ -1,
2411 /*expected_text*/ L"Paragraph 2",
2412 /*expected_count*/ -1);
2414 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
2415 /*count*/ 1,
2416 /*expected_text*/ L"",
2417 /*expected_count*/ 1);
2419 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
2420 /*count*/ -1,
2421 /*expected_text*/ L"Paragraph 2",
2422 /*expected_count*/ -1);
2423
2424 //
2425 // Degenerate range moves.
2426 //
2427
2428 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2429 /*count*/ -6,
2430 /*expected_text*/ L"First line of text\n",
2431 /*expected_count*/ -4);
2433 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
2434 /*count*/ -1,
2435 /*expected_text*/ L"",
2436 /*expected_count*/ -1);
2437 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2438 /*count*/ 3,
2439 /*expected_text*/ L"",
2440 /*expected_count*/ 3);
2441 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2442 /*count*/ 70,
2443 /*expected_text*/ L"",
2444 /*expected_count*/ 2);
2445
2446 // Trying to move past the last paragraph should have no effect.
2447 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2448 /*count*/ 70,
2449 /*expected_text*/ L"",
2450 /*expected_count*/ 0);
2451 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
2452 /*count*/ -2,
2453 /*expected_text*/ L"",
2454 /*expected_count*/ -2);
2455}
2456
2457// TODO(schectman) https://github.com/flutter/flutter/issues/117012
2459 DISABLED_TestITextRangeProviderMoveDocument) {
2460 Init(BuildAXTreeForMove());
2461 AXNode* root_node = GetRootAsAXNode();
2462
2463 ComPtr<ITextRangeProvider> text_range_provider;
2464 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
2465
2466 // Moving by 0 should have no effect.
2467 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document, /*count*/ 0,
2468 /*expected_text*/ tree_for_move_full_text.data(),
2469 /*expected_count*/ 0);
2470
2471 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document, /*count*/ -1,
2472 /*expected_text*/ tree_for_move_full_text.data(),
2473 /*expected_count*/ 0);
2474 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document, /*count*/ 2,
2475 /*expected_text*/ tree_for_move_full_text.data(),
2476 /*expected_count*/ 0);
2477 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page, /*count*/ 1,
2478 /*expected_text*/ tree_for_move_full_text.data(),
2479 /*expected_count*/ 0);
2480 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page, /*count*/ -1,
2481 /*expected_text*/ tree_for_move_full_text.data(),
2482 /*expected_count*/ 0);
2483
2484 // Degenerate range moves.
2486 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Document,
2487 /*count*/ -2,
2488 /*expected_text*/ L"",
2489 /*expected_count*/ -1);
2490 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
2491 /*count*/ 4,
2492 /*expected_text*/ L"",
2493 /*expected_count*/ 1);
2494
2495 // Trying to move past the last character should have no effect.
2496 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document,
2497 /*count*/ 1,
2498 /*expected_text*/ L"",
2499 /*expected_count*/ 0);
2500 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
2501 /*count*/ -2,
2502 /*expected_text*/ L"",
2503 /*expected_count*/ -1);
2504 EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document,
2505 /*count*/ -1,
2506 /*expected_text*/ L"",
2507 /*expected_count*/ 0);
2508}
2509
2510TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMove) {
2511 Init(BuildAXTreeForMove());
2512 AXNode* root_node = GetRootAsAXNode();
2513
2514 ComPtr<ITextRangeProvider> text_range_provider;
2515 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
2516
2517 // TODO(https://crbug.com/928948): test intermixed unit types
2518}
2519
2521 TestITextRangeProviderMoveEndpointByDocument) {
2522 Init(BuildTextDocument({"some text", "more text", "even more text"}));
2523 AXNode* text_node = GetRootAsAXNode()->children()[1];
2524
2525 // Run the test twice, one for TextUnit_Document and once for TextUnit_Page,
2526 // since they should have identical behavior.
2527 const TextUnit textunit_types[] = {TextUnit_Document, TextUnit_Page};
2528 ComPtr<ITextRangeProvider> text_range_provider;
2529
2530 for (auto& textunit : textunit_types) {
2531 GetTextRangeProviderFromTextNode(text_range_provider, text_node);
2532
2533 // Verify MoveEndpointByUnit with zero count has no effect
2534 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2535 TextPatternRangeEndpoint_End, textunit,
2536 /*count*/ 0,
2537 /*expected_text*/ L"more text",
2538 /*expected_count*/ 0);
2539
2540 // Move the endpoint to the end of the document. Verify all text content.
2542 text_range_provider, TextPatternRangeEndpoint_End, textunit,
2543 /*count*/ 1,
2544 /*expected_text*/ L"more texteven more text",
2545 /*expected_count*/ 1);
2546
2547 // Verify no moves occur since the end is already at the end of the document
2549 text_range_provider, TextPatternRangeEndpoint_End, textunit,
2550 /*count*/ 5,
2551 /*expected_text*/ L"more texteven more text",
2552 /*expected_count*/ 0);
2553
2554 // Move the end before the start
2555 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2556 TextPatternRangeEndpoint_End, textunit,
2557 /*count*/ -4,
2558 /*expected_text*/ L"",
2559 /*expected_count*/ -1);
2560
2561 // Move the end back to the end of the document. The text content
2562 // should now include the entire document since end was previously
2563 // moved before start.
2565 text_range_provider, TextPatternRangeEndpoint_End, textunit,
2566 /*count*/ 1,
2567 /*expected_text*/ L"some textmore texteven more text",
2568 /*expected_count*/ 1);
2569
2570 // Move the start point to the end
2571 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2572 TextPatternRangeEndpoint_Start, textunit,
2573 /*count*/ 3,
2574 /*expected_text*/ L"",
2575 /*expected_count*/ 1);
2576
2577 // Move the start point back to the beginning
2579 text_range_provider, TextPatternRangeEndpoint_Start, textunit,
2580 /*count*/ -3,
2581 /*expected_text*/ L"some textmore texteven more text",
2582 /*expected_count*/ -1);
2583 }
2584}
2585
2586// TODO(schectman) We are probably not accounting for multibyte characters
2587// properly yet. https://github.com/flutter/flutter/issues/117012
2589 DISABLED_TestITextRangeProviderMoveEndpointByCharacterMultilingual) {
2590 // The English string has three characters, each 8 bits in length.
2591 const std::string english = "hey";
2592
2593 // The Hindi string has two characters, the first one 32 bits and the second
2594 // 64 bits in length. It is formatted in UTF16.
2595 const std::string hindi =
2596 base::UTF16ToUTF8(u"\x0939\x093F\x0928\x094D\x0926\x0940");
2597
2598 // The Thai string has three characters, the first one 48, the second 32 and
2599 // the last one 16 bits in length. It is formatted in UTF16.
2600 const std::string thai =
2601 base::UTF16ToUTF8(u"\x0E23\x0E39\x0E49\x0E2A\x0E36\x0E01");
2602
2603 Init(BuildTextDocument({english, hindi, thai}));
2604
2605 ComPtr<ITextRangeProvider> text_range_provider;
2606 GetTextRangeProviderFromTextNode(text_range_provider,
2607 GetRootAsAXNode()->children()[0]);
2608
2609 // Verify MoveEndpointByUnit with zero count has no effect
2610 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"hey");
2612 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
2613 /*count*/ 0,
2614 /*expected_text*/ L"hey",
2615 /*expected_count*/ 0);
2616
2618 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
2619 /*count*/ 1,
2620 /*expected_text*/ L"ey",
2621 /*expected_count*/ 1);
2622
2624 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
2625 /*count*/ -1,
2626 /*expected_text*/ L"e",
2627 /*expected_count*/ -1);
2628
2629 // Move end into the adjacent node.
2630 //
2631 // The first character of the second node is 32 bits in length.
2633 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
2634 /*count*/ 2,
2635 /*expected_text*/ L"ey\x0939\x093F",
2636 /*expected_count*/ 2);
2637
2638 // The second character of the second node is 64 bits in length.
2640 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
2641 /*count*/ 1,
2642 /*expected_text*/ L"ey\x939\x93F\x928\x94D\x926\x940",
2643 /*expected_count*/ 1);
2644
2645 // Move start into the adjacent node as well.
2647 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
2648 /*count*/ 2,
2649 /*expected_text*/ L"\x939\x93F\x928\x94D\x926\x940",
2650 /*expected_count*/ 2);
2651
2652 // Move end into the last node.
2653 //
2654 // The first character of the last node is 48 bits in length.
2656 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
2657 /*count*/ 1,
2658 /*expected_text*/ L"\x939\x93F\x928\x94D\x926\x940\xE23\xE39\xE49",
2659 /*expected_count*/ 1);
2660
2661 // Move end back into the second node and then into the last node again.
2663 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
2664 /*count*/ -2,
2665 /*expected_text*/ L"\x939\x93F",
2666 /*expected_count*/ -2);
2667
2669 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
2670 /*count*/ 3,
2671 /*expected_text*/
2672 L"\x939\x93F\x928\x94D\x926\x940\xE23\xE39\xE49\xE2A\xE36",
2673 /*expected_count*/ 3);
2674
2675 // The last character of the last node is only 16 bits in length.
2677 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
2678 /*count*/ 1,
2679 /*expected_text*/
2680 L"\x939\x93F\x928\x94D\x926\x940\xE23\xE39\xE49\xE2A\xE36\xE01",
2681 /*expected_count*/ 1);
2682
2683 // Move start into the last node.
2685 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
2686 /*count*/ 3,
2687 /*expected_text*/ L"\x0E2A\x0E36\x0E01",
2688 /*expected_count*/ 3);
2689
2691 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
2692 /*count*/ -1,
2693 /*expected_text*/ L"\x0E23\x0E39\x0E49\x0E2A\x0E36\x0E01",
2694 /*expected_count*/ -1);
2695}
2696
2697// TODO(schectman) https://github.com/flutter/flutter/issues/117012
2699 DISABLED_TestITextRangeProviderMoveEndpointByWord) {
2700 Init(BuildTextDocument({"some text", "more text", "even more text"},
2701 /*build_word_boundaries_offsets*/ true));
2702
2703 ComPtr<ITextRangeProvider> text_range_provider;
2704 GetTextRangeProviderFromTextNode(text_range_provider,
2705 GetRootAsAXNode()->children()[1]);
2706 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"more text");
2707
2708 // Moving with zero count does not alter the range.
2709 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2710 TextPatternRangeEndpoint_End, TextUnit_Word,
2711 /*count*/ 0,
2712 /*expected_text*/ L"more text",
2713 /*expected_count*/ 0);
2714
2715 // Moving the start forward and backward.
2717 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
2718 /*count*/ 1,
2719 /*expected_text*/ L"text",
2720 /*expected_count*/ 1);
2722 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
2723 /*count*/ -1,
2724 /*expected_text*/ L"more text",
2725 /*expected_count*/ -1);
2726
2727 // Moving the end backward and forward.
2728 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2729 TextPatternRangeEndpoint_End, TextUnit_Word,
2730 /*count*/ -1,
2731 /*expected_text*/ L"more ",
2732 /*expected_count*/ -1);
2733 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2734 TextPatternRangeEndpoint_End, TextUnit_Word,
2735 /*count*/ 1,
2736 /*expected_text*/ L"more text",
2737 /*expected_count*/ 1);
2738
2739 // Moving the start past the end, then reverting.
2741 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
2742 /*count*/ 3,
2743 /*expected_text*/ L"",
2744 /*expected_count*/ 3);
2746 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
2747 /*count*/ -3,
2748 /*expected_text*/ L"more texteven ",
2749 /*expected_count*/ -3);
2750 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2751 TextPatternRangeEndpoint_End, TextUnit_Word,
2752 /*count*/ -1,
2753 /*expected_text*/ L"more text",
2754 /*expected_count*/ -1);
2755
2756 // Moving the end past the start, then reverting.
2757 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2758 TextPatternRangeEndpoint_End, TextUnit_Word,
2759 /*count*/ -3,
2760 /*expected_text*/ L"",
2761 /*expected_count*/ -3);
2762 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2763 TextPatternRangeEndpoint_End, TextUnit_Word,
2764 /*count*/ 3,
2765 /*expected_text*/ L"textmore text",
2766 /*expected_count*/ 3);
2768 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
2769 /*count*/ 1,
2770 /*expected_text*/ L"more text",
2771 /*expected_count*/ 1);
2772
2773 // Moving the endpoints further than both ends of the document.
2774 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2775 TextPatternRangeEndpoint_End, TextUnit_Word,
2776 /*count*/ 5,
2777 /*expected_text*/ L"more texteven more text",
2778 /*expected_count*/ 3);
2780 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
2781 /*count*/ 6,
2782 /*expected_text*/ L"",
2783 /*expected_count*/ 5);
2785 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
2786 /*count*/ -8,
2787 /*expected_text*/ L"some textmore texteven more text",
2788 /*expected_count*/ -7);
2789 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2790 TextPatternRangeEndpoint_End, TextUnit_Word,
2791 /*count*/ -8,
2792 /*expected_text*/ L"",
2793 /*expected_count*/ -7);
2794}
2795
2796// TODO(schectman) https://github.com/flutter/flutter/issues/117012
2798 DISABLED_TestITextRangeProviderMoveEndpointByLine) {
2799 Init(BuildTextDocument({"0", "1", "2", "3", "4", "5", "6"}));
2800
2801 ComPtr<ITextRangeProvider> text_range_provider;
2802 GetTextRangeProviderFromTextNode(text_range_provider,
2803 GetRootAsAXNode()->children()[3]);
2804 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"3");
2805
2806 // Moving with zero count does not alter the range.
2807 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2808 TextPatternRangeEndpoint_End, TextUnit_Line,
2809 /*count*/ 0,
2810 /*expected_text*/ L"3",
2811 /*expected_count*/ 0);
2812
2813 // Moving the start backward and forward.
2815 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
2816 /*count*/ -2,
2817 /*expected_text*/ L"123",
2818 /*expected_count*/ -2);
2820 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
2821 /*count*/ 1,
2822 /*expected_text*/ L"23",
2823 /*expected_count*/ 1);
2824
2825 // Moving the end forward and backward.
2826 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2827 TextPatternRangeEndpoint_End, TextUnit_Line,
2828 /*count*/ 3,
2829 /*expected_text*/ L"23456",
2830 /*expected_count*/ 3);
2831 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2832 TextPatternRangeEndpoint_End, TextUnit_Line,
2833 /*count*/ -2,
2834 /*expected_text*/ L"234",
2835 /*expected_count*/ -2);
2836
2837 // Moving the end past the start and vice versa.
2838 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2839 TextPatternRangeEndpoint_End, TextUnit_Line,
2840 /*count*/ -4,
2841 /*expected_text*/ L"",
2842 /*expected_count*/ -4);
2844 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
2845 /*count*/ -1,
2846 /*expected_text*/ L"0",
2847 /*expected_count*/ -1);
2849 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
2850 /*count*/ 6,
2851 /*expected_text*/ L"",
2852 /*expected_count*/ 6);
2854 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
2855 /*count*/ -6,
2856 /*expected_text*/ L"012345",
2857 /*expected_count*/ -6);
2858
2859 // Moving the endpoints further than both ends of the document.
2860 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2861 TextPatternRangeEndpoint_End, TextUnit_Line,
2862 /*count*/ -13,
2863 /*expected_text*/ L"",
2864 /*expected_count*/ -6);
2865 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
2866 TextPatternRangeEndpoint_End, TextUnit_Line,
2867 /*count*/ 11,
2868 /*expected_text*/ L"0123456",
2869 /*expected_count*/ 7);
2871 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
2872 /*count*/ 9,
2873 /*expected_text*/ L"",
2874 /*expected_count*/ 7);
2876 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
2877 /*count*/ -7,
2878 /*expected_text*/ L"0123456",
2879 /*expected_count*/ -7);
2880}
2881
2882// TODO(schectman) https://github.com/flutter/flutter/issues/117012
2883// Verify that the endpoint can move past an empty text field.
2885 DISABLED_TestITextRangeProviderMoveEndpointByUnitTextField) {
2886 // An empty text field should also be a character, word, and line boundary.
2887 ui::AXNodeData root_data;
2888 root_data.id = 1;
2890
2891 ui::AXNodeData group1_data;
2892 group1_data.id = 2;
2894
2895 ui::AXNodeData text_data;
2896 text_data.id = 3;
2898 std::string text_content = "some text";
2899 text_data.SetName(text_content);
2900 std::vector<int> word_start_offsets, word_end_offsets;
2901 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
2902 word_end_offsets);
2904 word_start_offsets);
2906 word_end_offsets);
2907
2908 ui::AXNodeData text_input_data;
2909 text_input_data.id = 4;
2910 text_input_data.role = ax::mojom::Role::kTextField;
2911 text_input_data.AddState(ax::mojom::State::kEditable);
2913 "input");
2915 "text");
2916
2917 ui::AXNodeData group2_data;
2918 group2_data.id = 5;
2920
2921 ui::AXNodeData more_text_data;
2922 more_text_data.id = 6;
2923 more_text_data.role = ax::mojom::Role::kStaticText;
2924 text_content = "more text";
2925 more_text_data.SetName(text_content);
2926 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
2927 word_end_offsets);
2929 word_start_offsets);
2931 word_end_offsets);
2932
2933 ui::AXNodeData empty_text_data;
2934 empty_text_data.id = 7;
2935 empty_text_data.role = ax::mojom::Role::kStaticText;
2936 empty_text_data.AddState(ax::mojom::State::kEditable);
2937 text_content = "";
2938 empty_text_data.SetNameExplicitlyEmpty();
2939 ComputeWordBoundariesOffsets(text_content, word_start_offsets,
2940 word_end_offsets);
2942 word_start_offsets);
2944 word_end_offsets);
2945
2946 root_data.child_ids = {group1_data.id, text_input_data.id, group2_data.id};
2947 group1_data.child_ids = {text_data.id};
2948 text_input_data.child_ids = {empty_text_data.id};
2949 group2_data.child_ids = {more_text_data.id};
2950
2952 ui::AXTreeData tree_data;
2954 update.tree_data = tree_data;
2955 update.has_tree_data = true;
2956 update.root_id = root_data.id;
2957 update.nodes = {root_data, group1_data, text_data, text_input_data,
2958 group2_data, more_text_data, empty_text_data};
2959
2960 Init(update);
2961
2962 // Set up variables from the tree for testing.
2963 AXNode* root_node = GetRootAsAXNode();
2964 AXNode* text_node = root_node->children()[0]->children()[0];
2965
2966 ComPtr<ITextRangeProvider> text_range_provider;
2967 GetTextRangeProviderFromTextNode(text_range_provider, text_node);
2968
2969 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
2970
2971 int count;
2972 // Tests for TextUnit_Character.
2973 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
2974 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 2, &count));
2975 ASSERT_EQ(2, count);
2976 // Note that by design, empty objects such as empty text fields, are placed in
2977 // their own paragraph for easier screen reader navigation.
2978 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFc");
2979
2980 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
2981 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 2, &count));
2982 ASSERT_EQ(2, count);
2983 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFc\nm");
2984
2985 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
2986 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -1, &count));
2987 ASSERT_EQ(-1, count);
2988 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFC\n");
2989
2990 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
2991 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -2, &count));
2992 ASSERT_EQ(-2, count);
2993 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n");
2994
2995 // Tests for TextUnit_Word.
2996 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
2997 TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ 1, &count));
2998 ASSERT_EQ(1, count);
2999 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFC\n");
3000
3001 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3002 TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ 1, &count));
3003 ASSERT_EQ(1, count);
3004 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFC\nmore ");
3005
3006 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3007 TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ -1, &count));
3008 ASSERT_EQ(-1, count);
3009 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFC\n");
3010
3011 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3012 TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ -1, &count));
3013 ASSERT_EQ(-1, count);
3014 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n");
3015
3016 // Tests for TextUnit_Line.
3017 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3018 TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ 1, &count));
3019 ASSERT_EQ(1, count);
3020 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFC");
3021
3022 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3023 TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ 1, &count));
3024 ASSERT_EQ(1, count);
3025 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFC\nmore text");
3026
3027 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3028 TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ -1, &count));
3029 ASSERT_EQ(-1, count);
3030 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text\n\xFFFC");
3031
3032 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3033 TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ -1, &count));
3034 ASSERT_EQ(-1, count);
3035 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
3036}
3037
3038// TODO(schectman) https://github.com/flutter/flutter/issues/117012
3040 DISABLED_TestITextRangeProviderMoveEndpointByFormat) {
3041 Init(BuildAXTreeForMoveByFormat());
3042 AXNode* root_node = GetRootAsAXNode();
3043
3044 ComPtr<ITextRangeProvider> text_range_provider;
3045 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
3046
3048 text_range_provider,
3049 L"Text with formatting\nStandalone line with no formatting\nbold "
3050 L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParagraph 4");
3052 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
3053 /*count*/ -2,
3054 /*expected_text*/
3055 L"Text with formatting\nStandalone line with no formatting\nbold "
3056 L"text\nParagraph 1",
3057 /*expected_count*/ -2);
3058
3060 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
3061 /*count*/ -1,
3062 /*expected_text*/
3063 L"Text with formatting\nStandalone line with no formatting\nbold text",
3064 /*expected_count*/ -1);
3065
3067 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
3068 /*count*/ -1,
3069 /*expected_text*/
3070 L"Text with formatting\nStandalone line with no formatting\n",
3071 /*expected_count*/ -1);
3072
3074 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
3075 /*count*/ -1,
3076 /*expected_text*/ L"Text with formatting",
3077 /*expected_count*/ -1);
3078
3080 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
3081 /*count*/ -1,
3082 /*expected_text*/ L"",
3083 /*expected_count*/ -1);
3084
3086 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
3087 /*count*/ 7,
3088 /*expected_text*/
3089 L"Text with formatting\nStandalone line with no formatting\nbold "
3090 L"text\nParagraph 1\nParagraph 2\nParagraph 3\nParagraph 4",
3091 /*expected_count*/ 6);
3092
3094 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
3095 /*count*/ -8,
3096 /*expected_text*/ L"",
3097 /*expected_count*/ -6);
3098}
3099
3100TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderCompare) {
3101 Init(BuildTextDocument({"some text", "some text"}));
3102 AXNode* root_node = GetRootAsAXNode();
3103
3104 // Get the textRangeProvider for the document,
3105 // which contains text "some textsome text".
3106 ComPtr<ITextRangeProvider> document_text_range_provider;
3107 GetTextRangeProviderFromTextNode(document_text_range_provider, root_node);
3108
3109 // Get the textRangeProvider for the first text node.
3110 ComPtr<ITextRangeProvider> text_range_provider;
3111 GetTextRangeProviderFromTextNode(text_range_provider,
3112 root_node->children()[0]);
3113
3114 // Get the textRangeProvider for the second text node.
3115 ComPtr<ITextRangeProvider> more_text_range_provider;
3116 GetTextRangeProviderFromTextNode(more_text_range_provider,
3117 root_node->children()[1]);
3118
3119 // Compare text range of the entire document with itself, which should return
3120 // that they are equal.
3121 BOOL result;
3122 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->Compare(
3123 document_text_range_provider.Get(), &result));
3125
3126 // Compare the text range of the entire document with one of its child, which
3127 // should return that they are not equal.
3128 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->Compare(
3129 text_range_provider.Get(), &result));
3130 EXPECT_FALSE(result);
3131
3132 // Compare the text range of text_node which contains "some text" with
3133 // text range of more_text_node which also contains "some text". Those two
3134 // text ranges should not equal, because their endpoints are different, even
3135 // though their contents are the same.
3136 EXPECT_HRESULT_SUCCEEDED(
3137 text_range_provider->Compare(more_text_range_provider.Get(), &result));
3138 EXPECT_FALSE(result);
3139}
3140
3141TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderSelection) {
3142 Init(BuildTextDocument({"some text"}));
3143
3144 ComPtr<ITextRangeProvider> text_range_provider;
3145 GetTextRangeProviderFromTextNode(text_range_provider, GetRootAsAXNode());
3146
3147 ASSERT_UIA_INVALIDOPERATION(text_range_provider->AddToSelection());
3148 ASSERT_UIA_INVALIDOPERATION(text_range_provider->RemoveFromSelection());
3149}
3150
3151// TODO(schectman) Rectangles not implemented as in Chromium.
3152// https://github.com/flutter/flutter/issues/117012
3154 DISABLED_TestITextRangeProviderGetBoundingRectangles) {
3155 ui::AXTreeUpdate update = BuildAXTreeForBoundingRectangles();
3156 Init(update);
3157 ComPtr<ITextRangeProvider> text_range_provider;
3158 base::win::ScopedSafearray rectangles;
3159 int units_moved;
3160
3161 // Expected bounding rects:
3162 // <button>Button</button><input type="checkbox">Line 1<br>Line 2
3163 // |---------------------||---------------------||----| |------|
3164 GetTextRangeProviderFromTextNode(text_range_provider, GetRootAsAXNode());
3165 EXPECT_HRESULT_SUCCEEDED(
3166 text_range_provider->GetBoundingRectangles(rectangles.Receive()));
3167 std::vector<double> expected_values = {20, 20, 200, 30, /* button */
3168 20, 50, 200, 30, /* check box */
3169 220, 20, 30, 30, /* line 1 */
3170 220, 50, 42, 30 /* line 2 */};
3171 EXPECT_UIA_SAFEARRAY_EQ(rectangles.Get(), expected_values);
3172 rectangles.Reset();
3173
3174 // Move the text range end back by one character.
3175 // Expected bounding rects:
3176 // <button>Button</button><input type="checkbox">Line 1<br>Line 2
3177 // |---------------------||---------------------||----| |----|
3178 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3179 TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -1,
3180 &units_moved));
3181 ASSERT_EQ(-1, units_moved);
3182 EXPECT_HRESULT_SUCCEEDED(
3183 text_range_provider->GetBoundingRectangles(rectangles.Receive()));
3184 expected_values = {20, 20, 200, 30, /* button */
3185 20, 50, 200, 30, /* check box */
3186 220, 20, 30, 30, /* line 1 */
3187 220, 50, 35, 30 /* line 2 */};
3188 EXPECT_UIA_SAFEARRAY_EQ(rectangles.Get(), expected_values);
3189 rectangles.Reset();
3190
3191 // Move the text range end back by one line.
3192 // Expected bounding rects:
3193 // <button>Button</button><input type="checkbox">Line 1<br>Line 2
3194 // |---------------------||---------------------||--------|
3195 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3196 TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ -1, &units_moved));
3197 ASSERT_EQ(-1, units_moved);
3198 EXPECT_HRESULT_SUCCEEDED(
3199 text_range_provider->GetBoundingRectangles(rectangles.Receive()));
3200 expected_values = {20, 20, 200, 30, /* button */
3201 20, 50, 200, 30, /* check box */
3202 220, 20, 30, 30 /* line 1 */};
3203 EXPECT_UIA_SAFEARRAY_EQ(rectangles.Get(), expected_values);
3204 rectangles.Reset();
3205
3206 // Move the text range end back by one line.
3207 // Expected bounding rects:
3208 // <button>Button</button><input type="checkbox">Line 1<br>Line 2
3209 // |---------------------||---------------------|
3210 ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
3211 TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ -3, &units_moved));
3212 ASSERT_EQ(-3, units_moved);
3213 EXPECT_HRESULT_SUCCEEDED(
3214 text_range_provider->GetBoundingRectangles(rectangles.Receive()));
3215 expected_values = {20, 20, 200, 30, /* button */
3216 20, 50, 200, 30 /* check box */};
3217 EXPECT_UIA_SAFEARRAY_EQ(rectangles.Get(), expected_values);
3218}
3219
3221 TestITextRangeProviderGetEnclosingElement) {
3222 // Set up ax tree with the following structure:
3223 //
3224 // root
3225 // |
3226 // paragraph______________________________________________
3227 // | | | | |
3228 // static_text link link search input pdf_highlight
3229 // | | | | |
3230 // text_node static_text ul text_node static_text
3231 // | | |
3232 // text_node li text_node
3233 // |
3234 // static_text
3235 // |
3236 // text_node
3237
3238 ui::AXNodeData root_data;
3239 root_data.id = 1;
3241
3242 ui::AXNodeData paragraph_data;
3243 paragraph_data.id = 2;
3244 paragraph_data.role = ax::mojom::Role::kParagraph;
3245 root_data.child_ids.push_back(paragraph_data.id);
3246
3247 ui::AXNodeData static_text_data1;
3248 static_text_data1.id = 3;
3249 static_text_data1.role = ax::mojom::Role::kStaticText;
3250 paragraph_data.child_ids.push_back(static_text_data1.id);
3251
3252 ui::AXNodeData inline_text_data1;
3253 inline_text_data1.id = 4;
3254 inline_text_data1.role = ax::mojom::Role::kInlineTextBox;
3255 static_text_data1.child_ids.push_back(inline_text_data1.id);
3256
3257 ui::AXNodeData link_data;
3258 link_data.id = 5;
3259 link_data.role = ax::mojom::Role::kLink;
3260 paragraph_data.child_ids.push_back(link_data.id);
3261
3262 ui::AXNodeData static_text_data2;
3263 static_text_data2.id = 6;
3264 static_text_data2.role = ax::mojom::Role::kStaticText;
3265 link_data.child_ids.push_back(static_text_data2.id);
3266
3267 ui::AXNodeData inline_text_data2;
3268 inline_text_data2.id = 7;
3269 inline_text_data2.role = ax::mojom::Role::kInlineTextBox;
3270 static_text_data2.child_ids.push_back(inline_text_data2.id);
3271
3272 ui::AXNodeData link_data2;
3273 link_data2.id = 8;
3274 link_data2.role = ax::mojom::Role::kLink;
3275 paragraph_data.child_ids.push_back(link_data2.id);
3276
3277 ui::AXNodeData list_data;
3278 list_data.id = 9;
3279 list_data.role = ax::mojom::Role::kList;
3280 link_data2.child_ids.push_back(list_data.id);
3281
3282 ui::AXNodeData list_item_data;
3283 list_item_data.id = 10;
3284 list_item_data.role = ax::mojom::Role::kListItem;
3285 list_data.child_ids.push_back(list_item_data.id);
3286
3287 ui::AXNodeData static_text_data3;
3288 static_text_data3.id = 11;
3289 static_text_data3.role = ax::mojom::Role::kStaticText;
3290 list_item_data.child_ids.push_back(static_text_data3.id);
3291
3292 ui::AXNodeData inline_text_data3;
3293 inline_text_data3.id = 12;
3294 inline_text_data3.role = ax::mojom::Role::kInlineTextBox;
3295 static_text_data3.child_ids.push_back(inline_text_data3.id);
3296
3297 ui::AXNodeData search_box;
3298 search_box.id = 13;
3299 search_box.role = ax::mojom::Role::kSearchBox;
3303 "search");
3304 paragraph_data.child_ids.push_back(search_box.id);
3305
3306 ui::AXNodeData search_text;
3307 search_text.id = 14;
3308 search_text.role = ax::mojom::Role::kStaticText;
3310 search_text.SetName("placeholder");
3311 search_box.child_ids.push_back(search_text.id);
3312
3313 ui::AXNodeData pdf_highlight_data;
3314 pdf_highlight_data.id = 15;
3316 paragraph_data.child_ids.push_back(pdf_highlight_data.id);
3317
3318 ui::AXNodeData static_text_data4;
3319 static_text_data4.id = 16;
3320 static_text_data4.role = ax::mojom::Role::kStaticText;
3321 pdf_highlight_data.child_ids.push_back(static_text_data4.id);
3322
3323 ui::AXNodeData inline_text_data4;
3324 inline_text_data4.id = 17;
3325 inline_text_data4.role = ax::mojom::Role::kInlineTextBox;
3326 static_text_data4.child_ids.push_back(inline_text_data4.id);
3327
3329 ui::AXTreeData tree_data;
3331 update.tree_data = tree_data;
3332 update.has_tree_data = true;
3333 update.root_id = root_data.id;
3334 update.nodes = {root_data, paragraph_data, static_text_data1,
3335 inline_text_data1, link_data, static_text_data2,
3336 inline_text_data2, link_data2, list_data,
3337 list_item_data, static_text_data3, inline_text_data3,
3338 search_box, search_text, pdf_highlight_data,
3339 static_text_data4, inline_text_data4};
3340 Init(update);
3341
3342 // Set up variables from the tree for testing.
3343 AXNode* paragraph_node = GetRootAsAXNode()->children()[0];
3344 AXNode* static_text_node1 = paragraph_node->children()[0];
3345 AXNode* link_node = paragraph_node->children()[1];
3346 AXNode* inline_text_node1 = static_text_node1->children()[0];
3347 AXNode* static_text_node2 = link_node->children()[0];
3348 AXNode* inline_text_node2 = static_text_node2->children()[0];
3349 AXNode* link_node2 = paragraph_node->children()[2];
3350 AXNode* list_node = link_node2->children()[0];
3351 AXNode* list_item_node = list_node->children()[0];
3352 AXNode* static_text_node3 = list_item_node->children()[0];
3353 AXNode* inline_text_node3 = static_text_node3->children()[0];
3354 AXNode* search_box_node = paragraph_node->children()[3];
3355 AXNode* search_text_node = search_box_node->children()[0];
3356 AXNode* pdf_highlight_node = paragraph_node->children()[4];
3357 AXNode* static_text_node4 = pdf_highlight_node->children()[0];
3358 AXNode* inline_text_node4 = static_text_node4->children()[0];
3359 AXPlatformNodeWin* owner =
3360 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(paragraph_node));
3361 ASSERT_NE(owner, nullptr);
3362
3363 ComPtr<IRawElementProviderSimple> link_node_raw =
3364 QueryInterfaceFromNode<IRawElementProviderSimple>(link_node);
3365 ComPtr<IRawElementProviderSimple> static_text_node_raw1 =
3366 QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_node1);
3367 ComPtr<IRawElementProviderSimple> static_text_node_raw2 =
3368 QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_node2);
3369 ComPtr<IRawElementProviderSimple> static_text_node_raw3 =
3370 QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_node3);
3371 ComPtr<IRawElementProviderSimple> inline_text_node_raw1 =
3372 QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node1);
3373 ComPtr<IRawElementProviderSimple> inline_text_node_raw2 =
3374 QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node2);
3375 ComPtr<IRawElementProviderSimple> inline_text_node_raw3 =
3376 QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node3);
3377 ComPtr<IRawElementProviderSimple> search_box_node_raw =
3378 QueryInterfaceFromNode<IRawElementProviderSimple>(search_box_node);
3379 ComPtr<IRawElementProviderSimple> search_text_node_raw =
3380 QueryInterfaceFromNode<IRawElementProviderSimple>(search_text_node);
3381 ComPtr<IRawElementProviderSimple> pdf_highlight_node_raw =
3382 QueryInterfaceFromNode<IRawElementProviderSimple>(pdf_highlight_node);
3383 ComPtr<IRawElementProviderSimple> inline_text_node_raw4 =
3384 QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node4);
3385
3386 // Test GetEnclosingElement for the two leaves text nodes. The enclosing
3387 // element of the first one should be its static text parent (because inline
3388 // text boxes shouldn't be exposed) and the enclosing element for the text
3389 // node that is grandchild of the link node should return the link node.
3390 // The text node in the link node with a complex subtree should behave
3391 // normally and return the static text parent.
3392 ComPtr<ITextProvider> text_provider;
3393 EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw1->GetPatternProvider(
3394 UIA_TextPatternId, &text_provider));
3395
3396 ComPtr<ITextRangeProvider> text_range_provider;
3397 EXPECT_HRESULT_SUCCEEDED(
3398 text_provider->get_DocumentRange(&text_range_provider));
3399 SetOwner(owner, text_range_provider.Get());
3400
3401 ComPtr<IRawElementProviderSimple> enclosing_element;
3402 EXPECT_HRESULT_SUCCEEDED(
3403 text_range_provider->GetEnclosingElement(&enclosing_element));
3404 EXPECT_EQ(inline_text_node_raw1.Get(), enclosing_element.Get());
3405
3406 EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw2->GetPatternProvider(
3407 UIA_TextPatternId, &text_provider));
3408
3409 EXPECT_HRESULT_SUCCEEDED(
3410 text_provider->get_DocumentRange(&text_range_provider));
3411 SetOwner(owner, text_range_provider.Get());
3412
3413 EXPECT_HRESULT_SUCCEEDED(
3414 text_range_provider->GetEnclosingElement(&enclosing_element));
3415 EXPECT_EQ(link_node_raw.Get(), enclosing_element.Get());
3416
3417 EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw3->GetPatternProvider(
3418 UIA_TextPatternId, &text_provider));
3419
3420 EXPECT_HRESULT_SUCCEEDED(
3421 text_provider->get_DocumentRange(&text_range_provider));
3422 SetOwner(owner, text_range_provider.Get());
3423
3424 EXPECT_HRESULT_SUCCEEDED(
3425 text_range_provider->GetEnclosingElement(&enclosing_element));
3426 EXPECT_EQ(inline_text_node_raw3.Get(), enclosing_element.Get());
3427
3428 // The enclosing element of a text range in the search text should give the
3429 // search box
3430 EXPECT_HRESULT_SUCCEEDED(search_text_node_raw->GetPatternProvider(
3431 UIA_TextPatternId, &text_provider));
3432
3433 EXPECT_HRESULT_SUCCEEDED(
3434 text_provider->get_DocumentRange(&text_range_provider));
3435 SetOwner(owner, text_range_provider.Get());
3436
3437 EXPECT_HRESULT_SUCCEEDED(
3438 text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
3439
3440 EXPECT_HRESULT_SUCCEEDED(
3441 text_range_provider->GetEnclosingElement(&enclosing_element));
3442 EXPECT_EQ(search_box_node_raw.Get(), enclosing_element.Get());
3443
3444 // The enclosing element for the text node that is grandchild of the
3445 // pdf_highlight node should return the pdf_highlight node.
3446 EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw4->GetPatternProvider(
3447 UIA_TextPatternId, &text_provider));
3448
3449 EXPECT_HRESULT_SUCCEEDED(
3450 text_provider->get_DocumentRange(&text_range_provider));
3451 SetOwner(owner, text_range_provider.Get());
3452
3453 EXPECT_HRESULT_SUCCEEDED(
3454 text_range_provider->GetEnclosingElement(&enclosing_element));
3455 EXPECT_EQ(pdf_highlight_node_raw.Get(), enclosing_element.Get());
3456}
3457
3459 TestITextRangeProviderGetEnclosingElementRichButton) {
3460 // Set up ax tree with the following structure:
3461 //
3462 // root
3463 // ++button_1
3464 // ++++static_text_1
3465 // ++++++inline_text_1
3466 // ++button_2
3467 // ++++heading
3468 // ++++++statix_text_2
3469 // ++++++++inline_text_2
3470
3472 ui::AXNodeData button_1;
3473 ui::AXNodeData static_text_1;
3474 ui::AXNodeData inline_text_1;
3475 ui::AXNodeData button_2;
3476 ui::AXNodeData heading;
3477 ui::AXNodeData static_text_2;
3478 ui::AXNodeData inline_text_2;
3479
3480 root.id = 1;
3481 button_1.id = 2;
3482 static_text_1.id = 3;
3483 inline_text_1.id = 4;
3484 button_2.id = 5;
3485 heading.id = 6;
3486 static_text_2.id = 7;
3487 inline_text_2.id = 8;
3488
3490 root.child_ids = {button_1.id, button_2.id};
3491
3492 button_1.role = ax::mojom::Role::kButton;
3493 button_1.child_ids.push_back(static_text_1.id);
3494
3495 static_text_1.role = ax::mojom::Role::kStaticText;
3496 static_text_1.child_ids.push_back(inline_text_1.id);
3497
3498 inline_text_1.role = ax::mojom::Role::kInlineTextBox;
3499
3500 button_2.role = ax::mojom::Role::kButton;
3501 button_2.child_ids.push_back(heading.id);
3502
3504 heading.child_ids.push_back(static_text_2.id);
3505
3506 static_text_2.role = ax::mojom::Role::kStaticText;
3507 static_text_2.child_ids.push_back(inline_text_2.id);
3508
3509 inline_text_2.role = ax::mojom::Role::kInlineTextBox;
3510
3512 ui::AXTreeData tree_data;
3514 update.tree_data = tree_data;
3515 update.has_tree_data = true;
3516 update.root_id = root.id;
3517 update.nodes = {root, button_1, static_text_1, inline_text_1,
3518 button_2, heading, static_text_2, inline_text_2};
3519 Init(update);
3520
3521 // Set up variables from the tree for testing.
3522 AXNode* button_1_node = GetRootAsAXNode()->children()[0];
3523 AXNode* static_text_1_node = button_1_node->children()[0];
3524 AXNode* inline_text_1_node = static_text_1_node->children()[0];
3525 AXNode* button_2_node = GetRootAsAXNode()->children()[1];
3526 AXNode* heading_node = button_2_node->children()[0];
3527 AXNode* static_text_2_node = heading_node->children()[0];
3528 AXNode* inline_text_2_node = static_text_2_node->children()[0];
3529 AXPlatformNodeWin* owner =
3530 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(button_1_node));
3531 ASSERT_NE(owner, nullptr);
3532
3533 ComPtr<IRawElementProviderSimple> button_1_node_raw =
3534 QueryInterfaceFromNode<IRawElementProviderSimple>(button_1_node);
3535 ComPtr<IRawElementProviderSimple> inline_text_1_node_raw =
3536 QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_1_node);
3537
3538 ComPtr<IRawElementProviderSimple> button_2_node_raw =
3539 QueryInterfaceFromNode<IRawElementProviderSimple>(button_2_node);
3540 ComPtr<IRawElementProviderSimple> static_text_2_node_raw =
3541 QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_2_node);
3542 ComPtr<IRawElementProviderSimple> inline_text_2_node_raw =
3543 QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_2_node);
3544
3545 // 1. The first button should hide its children since it contains a single
3546 // text node. Thus, calling GetEnclosingElement on a descendant inline text
3547 // box should return the button itself.
3548 ComPtr<ITextProvider> text_provider;
3549 EXPECT_HRESULT_SUCCEEDED(inline_text_1_node_raw->GetPatternProvider(
3550 UIA_TextPatternId, &text_provider));
3551
3552 ComPtr<ITextRangeProvider> text_range_provider;
3553 EXPECT_HRESULT_SUCCEEDED(
3554 text_provider->get_DocumentRange(&text_range_provider));
3555 SetOwner(owner, text_range_provider.Get());
3556
3557 ComPtr<IRawElementProviderSimple> enclosing_element;
3558 EXPECT_HRESULT_SUCCEEDED(
3559 text_range_provider->GetEnclosingElement(&enclosing_element));
3560 EXPECT_EQ(button_1_node_raw.Get(), enclosing_element.Get());
3561
3562 // 2. The second button shouldn't hide its children since it doesn't contain a
3563 // single text node (it contains a heading node). Thus, calling
3564 // GetEnclosingElement on a descendant inline text box should return the
3565 // parent node.
3566 EXPECT_HRESULT_SUCCEEDED(inline_text_2_node_raw->GetPatternProvider(
3567 UIA_TextPatternId, &text_provider));
3568
3569 EXPECT_HRESULT_SUCCEEDED(
3570 text_provider->get_DocumentRange(&text_range_provider));
3571 SetOwner(owner, text_range_provider.Get());
3572
3573 EXPECT_HRESULT_SUCCEEDED(
3574 text_range_provider->GetEnclosingElement(&enclosing_element));
3575 EXPECT_EQ(button_2_node_raw.Get(), enclosing_element.Get());
3576}
3577
3579 TestITextRangeProviderMoveEndpointByRange) {
3580 Init(BuildTextDocument({"some text", "more text"}));
3581
3582 AXNode* root_node = GetRootAsAXNode();
3583 AXNode* text_node = root_node->children()[0];
3584 AXNode* more_text_node = root_node->children()[1];
3585 AXPlatformNodeWin* owner =
3586 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(root_node));
3587 ASSERT_NE(owner, nullptr);
3588
3589 // Text range for the document, which contains text "some textmore text".
3590 ComPtr<IRawElementProviderSimple> root_node_raw =
3591 QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
3592 ComPtr<ITextProvider> document_provider;
3593 EXPECT_HRESULT_SUCCEEDED(
3594 root_node_raw->GetPatternProvider(UIA_TextPatternId, &document_provider));
3595 ComPtr<ITextRangeProvider> document_text_range_provider;
3596 ComPtr<AXPlatformNodeTextRangeProviderWin> document_text_range;
3597
3598 // Text range related to "some text".
3599 ComPtr<IRawElementProviderSimple> text_node_raw =
3600 QueryInterfaceFromNode<IRawElementProviderSimple>(text_node);
3601 ComPtr<ITextProvider> text_provider;
3602 EXPECT_HRESULT_SUCCEEDED(
3603 text_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
3604 ComPtr<ITextRangeProvider> text_range_provider;
3605 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range;
3606
3607 // Text range related to "more text".
3608 ComPtr<IRawElementProviderSimple> more_text_node_raw =
3609 QueryInterfaceFromNode<IRawElementProviderSimple>(more_text_node);
3610 ComPtr<ITextProvider> more_text_provider;
3611 EXPECT_HRESULT_SUCCEEDED(more_text_node_raw->GetPatternProvider(
3612 UIA_TextPatternId, &more_text_provider));
3613 ComPtr<ITextRangeProvider> more_text_range_provider;
3614 ComPtr<AXPlatformNodeTextRangeProviderWin> more_text_range;
3615
3616 // Move the start of document text range "some textmore text" to the end of
3617 // itself.
3618 // The start of document text range "some textmore text" is at the end of
3619 // itself.
3620 //
3621 // Before:
3622 // |s e|
3623 // "some textmore text"
3624 // After:
3625 // |s
3626 // e|
3627 // "some textmore text"
3628
3629 // Get the textRangeProvider for the document, which contains text
3630 // "some textmore text".
3631 EXPECT_HRESULT_SUCCEEDED(
3632 document_provider->get_DocumentRange(&document_text_range_provider));
3633 SetOwner(owner, document_text_range_provider.Get());
3634
3635 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->MoveEndpointByRange(
3636 TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
3637 TextPatternRangeEndpoint_End));
3638
3639 document_text_range_provider->QueryInterface(
3640 IID_PPV_ARGS(&document_text_range));
3641 EXPECT_EQ(*GetStart(document_text_range.Get()),
3642 *GetEnd(document_text_range.Get()));
3643
3644 // Move the end of document text range "some textmore text" to the start of
3645 // itself.
3646 // The end of document text range "some textmore text" is at the start of
3647 // itself.
3648 //
3649 // Before:
3650 // |s e|
3651 // "some textmore text"
3652 // After:
3653 // |s
3654 // e|
3655 // "some textmore text"
3656
3657 // Get the textRangeProvider for the document, which contains text
3658 // "some textmore text".
3659 EXPECT_HRESULT_SUCCEEDED(
3660 document_provider->get_DocumentRange(&document_text_range_provider));
3661 SetOwner(owner, document_text_range_provider.Get());
3662
3663 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->MoveEndpointByRange(
3664 TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
3665 TextPatternRangeEndpoint_End));
3666
3667 document_text_range_provider->QueryInterface(
3668 IID_PPV_ARGS(&document_text_range));
3669 EXPECT_EQ(*GetStart(document_text_range.Get()),
3670 *GetEnd(document_text_range.Get()));
3671
3672 // Move the start of document text range "some textmore text" to the start
3673 // of text range "more text". The start of document text range "some
3674 // textmore text" is at the start of text range "more text". The end of
3675 // document range does not change.
3676 //
3677 // Before:
3678 // |s e|
3679 // "some textmore text"
3680 // After:
3681 // |s e|
3682 // "some textmore text"
3683
3684 // Get the textRangeProvider for the document, which contains text
3685 // "some textmore text".
3686 EXPECT_HRESULT_SUCCEEDED(
3687 document_provider->get_DocumentRange(&document_text_range_provider));
3688 SetOwner(owner, document_text_range_provider.Get());
3689 // Get the textRangeProvider for more_text_node which contains "more text".
3690 EXPECT_HRESULT_SUCCEEDED(
3691 more_text_provider->get_DocumentRange(&more_text_range_provider));
3692 SetOwner(owner, more_text_range_provider.Get());
3693
3694 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->MoveEndpointByRange(
3695 TextPatternRangeEndpoint_Start, more_text_range_provider.Get(),
3696 TextPatternRangeEndpoint_Start));
3697
3698 document_text_range_provider->QueryInterface(
3699 IID_PPV_ARGS(&document_text_range));
3700 more_text_range_provider->QueryInterface(IID_PPV_ARGS(&more_text_range));
3701 EXPECT_EQ(*GetStart(document_text_range.Get()),
3702 *GetStart(more_text_range.Get()));
3703
3704 // Move the end of document text range "some textmore text" to the end of
3705 // text range "some text".
3706 // The end of document text range "some textmore text" is at the end of text
3707 // range "some text". The start of document range does not change.
3708 //
3709 // Before:
3710 // |s e|
3711 // "some textmore text"
3712 // After:
3713 // |s e|
3714 // "some textmore text"
3715
3716 // Get the textRangeProvider for the document, which contains text
3717 // "some textmore text".
3718 EXPECT_HRESULT_SUCCEEDED(
3719 document_provider->get_DocumentRange(&document_text_range_provider));
3720 SetOwner(owner, document_text_range_provider.Get());
3721 // Get the textRangeProvider for text_node which contains "some text".
3722 EXPECT_HRESULT_SUCCEEDED(
3723 text_provider->get_DocumentRange(&text_range_provider));
3724 SetOwner(owner, text_range_provider.Get());
3725
3726 EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->MoveEndpointByRange(
3727 TextPatternRangeEndpoint_End, text_range_provider.Get(),
3728 TextPatternRangeEndpoint_End));
3729
3730 document_text_range_provider->QueryInterface(
3731 IID_PPV_ARGS(&document_text_range));
3732 text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range));
3733 EXPECT_EQ(*GetEnd(document_text_range.Get()), *GetEnd(text_range.Get()));
3734
3735 // Move the end of text range "more text" to the start of
3736 // text range "some text". Since the order of the endpoints being moved
3737 // (those of "more text") have to be ensured, both endpoints of "more text"
3738 // is at the start of "some text".
3739 //
3740 // Before:
3741 // |s e|
3742 // "some textmore text"
3743 // After:
3744 // e|
3745 // |s
3746 // "some textmore text"
3747
3748 // Get the textRangeProvider for text_node which contains "some text".
3749 EXPECT_HRESULT_SUCCEEDED(
3750 text_provider->get_DocumentRange(&text_range_provider));
3751 SetOwner(owner, document_text_range_provider.Get());
3752 // Get the textRangeProvider for more_text_node which contains "more text".
3753 EXPECT_HRESULT_SUCCEEDED(
3754 more_text_provider->get_DocumentRange(&more_text_range_provider));
3755 SetOwner(owner, more_text_range_provider.Get());
3756
3757 EXPECT_HRESULT_SUCCEEDED(more_text_range_provider->MoveEndpointByRange(
3758 TextPatternRangeEndpoint_End, text_range_provider.Get(),
3759 TextPatternRangeEndpoint_Start));
3760
3761 text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range));
3762 more_text_range_provider->QueryInterface(IID_PPV_ARGS(&more_text_range));
3763 EXPECT_EQ(*GetEnd(more_text_range.Get()), *GetStart(text_range.Get()));
3764 EXPECT_EQ(*GetStart(more_text_range.Get()), *GetStart(text_range.Get()));
3765
3766 // Move the start of text range "some text" to the end of text range
3767 // "more text". Since the order of the endpoints being moved (those
3768 // of "some text") have to be ensured, both endpoints of "some text" is at
3769 // the end of "more text".
3770 //
3771 // Before:
3772 // |s e|
3773 // "some textmore text"
3774 // After:
3775 // |s
3776 // e|
3777 // "some textmore text"
3778
3779 // Get the textRangeProvider for text_node which contains "some text".
3780 EXPECT_HRESULT_SUCCEEDED(
3781 text_provider->get_DocumentRange(&text_range_provider));
3782 SetOwner(owner, text_range_provider.Get());
3783 // Get the textRangeProvider for more_text_node which contains "more text".
3784 EXPECT_HRESULT_SUCCEEDED(
3785 more_text_provider->get_DocumentRange(&more_text_range_provider));
3786 SetOwner(owner, more_text_range_provider.Get());
3787
3788 EXPECT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByRange(
3789 TextPatternRangeEndpoint_Start, more_text_range_provider.Get(),
3790 TextPatternRangeEndpoint_End));
3791
3792 text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range));
3793 more_text_range_provider->QueryInterface(IID_PPV_ARGS(&more_text_range));
3794 EXPECT_EQ(*GetStart(text_range.Get()), *GetEnd(more_text_range.Get()));
3795 EXPECT_EQ(*GetEnd(text_range.Get()), *GetEnd(more_text_range.Get()));
3796}
3797
3798// TODO(schectman) https://github.com/flutter/flutter/issues/117012
3800 DISABLED_TestITextRangeProviderGetAttributeValue) {
3801 ui::AXNodeData text_data;
3802 text_data.id = 2;
3809 2);
3812 0xFFADBEEFU);
3813 text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3824 {0, 5, 0, 14, 19});
3826 {9, 9, 4, 18, 24});
3827 text_data.SetName("some text and some other text");
3828
3829 ui::AXNodeData heading_data;
3830 heading_data.id = 3;
3831 heading_data.role = ax::mojom::Role::kHeading;
3834 0xFFADBEEFU);
3835 heading_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3839 heading_data.child_ids = {4};
3840
3841 ui::AXNodeData heading_text_data;
3842 heading_text_data.id = 4;
3843 heading_text_data.role = ax::mojom::Role::kStaticText;
3844 heading_text_data.AddState(ax::mojom::State::kInvisible);
3846 0xFFADBEEFU);
3848 0xFFADC0DEU);
3851 heading_text_data.AddState(ax::mojom::State::kEditable);
3853 heading_text_data.AddIntListAttribute(
3856 heading_text_data.AddIntListAttribute(
3858 heading_text_data.AddIntListAttribute(
3860 heading_text_data.SetName("more text");
3861
3862 ui::AXNodeData mark_data;
3863 mark_data.id = 5;
3864 mark_data.role = ax::mojom::Role::kMark;
3866 0xFFADBEEFU);
3867 mark_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3869 mark_data.child_ids = {6};
3870
3871 ui::AXNodeData mark_text_data;
3872 mark_text_data.id = 6;
3873 mark_text_data.role = ax::mojom::Role::kStaticText;
3875 0xFFADBEEFU);
3876 mark_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3879 mark_text_data.SetName("marked text");
3880
3881 ui::AXNodeData list_data;
3882 list_data.id = 7;
3883 list_data.role = ax::mojom::Role::kList;
3884 list_data.child_ids = {8, 10};
3886 0xFFADBEEFU);
3887 list_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3888
3889 ui::AXNodeData list_item_data;
3890 list_item_data.id = 8;
3891 list_item_data.role = ax::mojom::Role::kListItem;
3892 list_item_data.child_ids = {9};
3893 list_item_data.AddIntAttribute(
3895 static_cast<int>(ax::mojom::ListStyle::kOther));
3897 0xFFADBEEFU);
3898 list_item_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3899
3900 ui::AXNodeData list_item_text_data;
3901 list_item_text_data.id = 9;
3902 list_item_text_data.role = ax::mojom::Role::kStaticText;
3904 0xFFADBEEFU);
3906 0xFFADC0DEU);
3907 list_item_text_data.SetName("list item");
3908
3909 ui::AXNodeData list_item2_data;
3910 list_item2_data.id = 10;
3911 list_item2_data.role = ax::mojom::Role::kListItem;
3912 list_item2_data.child_ids = {11};
3913 list_item2_data.AddIntAttribute(
3915 static_cast<int>(ax::mojom::ListStyle::kDisc));
3917 0xFFADBEEFU);
3918 list_item2_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3919
3920 ui::AXNodeData list_item2_text_data;
3921 list_item2_text_data.id = 11;
3922 list_item2_text_data.role = ax::mojom::Role::kStaticText;
3923 list_item2_text_data.AddIntAttribute(
3926 0xFFADC0DEU);
3927 list_item2_text_data.SetName("list item 2");
3928
3929 ui::AXNodeData input_text_data;
3930 input_text_data.id = 12;
3931 input_text_data.role = ax::mojom::Role::kTextField;
3932 input_text_data.AddState(ax::mojom::State::kEditable);
3933 input_text_data.AddIntAttribute(
3935 static_cast<int>(ax::mojom::NameFrom::kPlaceholder));
3937 "placeholder2");
3939 0xFFADBEEFU);
3940 input_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3942 "input");
3944 "text");
3945 input_text_data.SetName("placeholder");
3946 input_text_data.child_ids = {13};
3947
3948 ui::AXNodeData placeholder_text_data;
3949 placeholder_text_data.id = 13;
3950 placeholder_text_data.role = ax::mojom::Role::kStaticText;
3951 placeholder_text_data.AddIntAttribute(
3954 0xFFADC0DEU);
3955 placeholder_text_data.SetName("placeholder");
3956
3957 ui::AXNodeData input_text_data2;
3958 input_text_data2.id = 14;
3959 input_text_data2.role = ax::mojom::Role::kTextField;
3960 input_text_data2.AddState(ax::mojom::State::kEditable);
3963 "placeholder2");
3965 0xFFADBEEFU);
3967 0xFFADC0DEU);
3969 "input");
3971 "text");
3972 input_text_data2.SetName("foo");
3973 input_text_data2.child_ids = {15};
3974
3975 ui::AXNodeData placeholder_text_data2;
3976 placeholder_text_data2.id = 15;
3977 placeholder_text_data2.role = ax::mojom::Role::kStaticText;
3978 placeholder_text_data2.AddIntAttribute(
3980 placeholder_text_data2.AddIntAttribute(ax::mojom::IntAttribute::kColor,
3981 0xFFADC0DEU);
3982 placeholder_text_data2.SetName("placeholder2");
3983
3984 ui::AXNodeData link_data;
3985 link_data.id = 16;
3986 link_data.role = ax::mojom::Role::kLink;
3988 0xFFADBEEFU);
3989 link_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3990
3991 ui::AXNodeData link_text_data;
3992 link_text_data.id = 17;
3993 link_text_data.role = ax::mojom::Role::kStaticText;
3995 0xFFADBEEFU);
3996 link_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
3997 link_data.child_ids = {17};
3998
3999 ui::AXNodeData root_data;
4000 root_data.id = 1;
4002 root_data.child_ids = {2, 3, 5, 7, 12, 14, 16};
4003
4005 ui::AXTreeData tree_data;
4007 update.tree_data = tree_data;
4008 update.has_tree_data = true;
4009 update.root_id = root_data.id;
4010 update.nodes.push_back(root_data);
4011 update.nodes.push_back(text_data);
4012 update.nodes.push_back(heading_data);
4013 update.nodes.push_back(heading_text_data);
4014 update.nodes.push_back(mark_data);
4015 update.nodes.push_back(mark_text_data);
4016 update.nodes.push_back(list_data);
4017 update.nodes.push_back(list_item_data);
4018 update.nodes.push_back(list_item_text_data);
4019 update.nodes.push_back(list_item2_data);
4020 update.nodes.push_back(list_item2_text_data);
4021 update.nodes.push_back(input_text_data);
4022 update.nodes.push_back(placeholder_text_data);
4023 update.nodes.push_back(input_text_data2);
4024 update.nodes.push_back(placeholder_text_data2);
4025 update.nodes.push_back(link_data);
4026 update.nodes.push_back(link_text_data);
4027
4028 Init(update);
4029
4030 AXNode* root_node = GetRootAsAXNode();
4031 AXNode* text_node = root_node->children()[0];
4032 AXNode* heading_node = root_node->children()[1];
4033 AXNode* heading_text_node = heading_node->children()[0];
4034 AXNode* mark_node = root_node->children()[2];
4035 AXNode* mark_text_node = mark_node->children()[0];
4036 AXNode* list_node = root_node->children()[3];
4037 AXNode* list_item_node = list_node->children()[0];
4038 AXNode* list_item_text_node = list_item_node->children()[0];
4039 AXNode* list_item2_node = list_node->children()[1];
4040 AXNode* list_item2_text_node = list_item2_node->children()[0];
4041 AXNode* input_text_node = root_node->children()[4];
4042 AXNode* placeholder_text_node = input_text_node->children()[0];
4043 AXNode* input_text_node2 = root_node->children()[5];
4044 AXNode* placeholder_text_node2 = input_text_node2->children()[0];
4045 AXNode* link_node = root_node->children()[6];
4046 AXNode* link_text_node = link_node->children()[0];
4047
4048 ComPtr<ITextRangeProvider> document_range_provider;
4049 GetTextRangeProviderFromTextNode(document_range_provider, root_node);
4050 ComPtr<ITextRangeProvider> text_range_provider;
4051 GetTextRangeProviderFromTextNode(text_range_provider, text_node);
4052 ComPtr<ITextRangeProvider> heading_text_range_provider;
4053 GetTextRangeProviderFromTextNode(heading_text_range_provider,
4054 heading_text_node);
4055 ComPtr<ITextRangeProvider> mark_text_range_provider;
4056 GetTextRangeProviderFromTextNode(mark_text_range_provider, mark_text_node);
4057 ComPtr<ITextRangeProvider> list_item_text_range_provider;
4058 GetTextRangeProviderFromTextNode(list_item_text_range_provider,
4059 list_item_text_node);
4060 ComPtr<ITextRangeProvider> list_item2_text_range_provider;
4061 GetTextRangeProviderFromTextNode(list_item2_text_range_provider,
4062 list_item2_text_node);
4063
4064 ComPtr<ITextRangeProvider> placeholder_text_range_provider;
4065 GetTextRangeProviderFromTextNode(placeholder_text_range_provider,
4066 placeholder_text_node);
4067
4068 ComPtr<ITextRangeProvider> placeholder_text_range_provider2;
4069 GetTextRangeProviderFromTextNode(placeholder_text_range_provider2,
4070 placeholder_text_node2);
4071
4072 ComPtr<ITextRangeProvider> link_text_range_provider;
4073 GetTextRangeProviderFromTextNode(link_text_range_provider, link_text_node);
4074
4075 base::win::ScopedVariant expected_variant;
4076
4077 // SkColor is ARGB, COLORREF is 0BGR
4078 expected_variant.Set(static_cast<int32_t>(0x00EFBEADU));
4079 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
4080 UIA_BackgroundColorAttributeId, expected_variant);
4081 // Important: all nodes need to have the kColor and kBackgroundColor attribute
4082 // set for this test, otherwise the following assert will fail.
4083 EXPECT_UIA_TEXTATTRIBUTE_EQ(document_range_provider,
4084 UIA_BackgroundColorAttributeId, expected_variant);
4085 expected_variant.Reset();
4086
4087 expected_variant.Set(static_cast<int32_t>(BulletStyle::BulletStyle_None));
4088 EXPECT_UIA_TEXTATTRIBUTE_EQ(list_item_text_range_provider,
4089 UIA_BulletStyleAttributeId, expected_variant);
4090 expected_variant.Reset();
4091
4092 expected_variant.Set(
4093 static_cast<int32_t>(BulletStyle::BulletStyle_FilledRoundBullet));
4094 EXPECT_UIA_TEXTATTRIBUTE_EQ(list_item2_text_range_provider,
4095 UIA_BulletStyleAttributeId, expected_variant);
4096 expected_variant.Reset();
4097
4098 {
4099 base::win::ScopedVariant lang_variant;
4100 EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue(
4101 UIA_CultureAttributeId, lang_variant.Receive()));
4102
4103 EXPECT_EQ(lang_variant.type(), VT_I4);
4104 const LCID lcid = V_I4(lang_variant.ptr());
4105 EXPECT_EQ(LANG_FRENCH, PRIMARYLANGID(lcid));
4106 EXPECT_EQ(SUBLANG_FRENCH_CANADIAN, SUBLANGID(lcid));
4107 EXPECT_EQ(SORT_DEFAULT, SORTIDFROMLCID(lcid));
4108 }
4109
4110 std::wstring font_name = L"sans";
4111 expected_variant.Set(SysAllocString(font_name.c_str()));
4112 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontNameAttributeId,
4113 expected_variant);
4114 expected_variant.Reset();
4115
4116 expected_variant.Set(12.0);
4117 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontSizeAttributeId,
4118 expected_variant);
4119 expected_variant.Reset();
4120
4121 expected_variant.Set(300);
4122 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontWeightAttributeId,
4123 expected_variant);
4124 expected_variant.Reset();
4125
4126 // SkColor is ARGB, COLORREF is 0BGR
4127 expected_variant.Set(static_cast<int32_t>(0x00DEC0ADU));
4128 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
4129 UIA_ForegroundColorAttributeId, expected_variant);
4130 EXPECT_UIA_TEXTATTRIBUTE_EQ(document_range_provider,
4131 UIA_ForegroundColorAttributeId, expected_variant);
4132 expected_variant.Reset();
4133
4134 expected_variant.Set(false);
4135 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsHiddenAttributeId,
4136 expected_variant);
4137 expected_variant.Reset();
4138
4139 EXPECT_UIA_TEXTATTRIBUTE_MIXED(document_range_provider,
4140 UIA_IsHiddenAttributeId);
4141
4142 expected_variant.Set(true);
4143 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsItalicAttributeId,
4144 expected_variant);
4145 expected_variant.Reset();
4146
4147 expected_variant.Set(false);
4148 EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
4149 UIA_IsItalicAttributeId, expected_variant);
4150 expected_variant.Reset();
4151
4152 expected_variant.Set(true);
4153 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsReadOnlyAttributeId,
4154 expected_variant);
4155 expected_variant.Reset();
4156
4157 expected_variant.Set(false);
4158 EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
4159 UIA_IsReadOnlyAttributeId, expected_variant);
4160 expected_variant.Reset();
4161
4162 expected_variant.Set(false);
4163 EXPECT_UIA_TEXTATTRIBUTE_EQ(placeholder_text_range_provider,
4164 UIA_IsReadOnlyAttributeId, expected_variant);
4165 expected_variant.Reset();
4166
4167 expected_variant.Set(true);
4168 EXPECT_UIA_TEXTATTRIBUTE_EQ(placeholder_text_range_provider2,
4169 UIA_IsReadOnlyAttributeId, expected_variant);
4170 expected_variant.Reset();
4171
4172 expected_variant.Set(true);
4173 EXPECT_UIA_TEXTATTRIBUTE_EQ(link_text_range_provider,
4174 UIA_IsReadOnlyAttributeId, expected_variant);
4175 expected_variant.Reset();
4176
4177 expected_variant.Set(HorizontalTextAlignment_Centered);
4178 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
4179 UIA_HorizontalTextAlignmentAttributeId,
4180 expected_variant);
4181 expected_variant.Reset();
4182
4183 expected_variant.Set(HorizontalTextAlignment_Justified);
4184 EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
4185 UIA_HorizontalTextAlignmentAttributeId,
4186 expected_variant);
4187 expected_variant.Reset();
4188
4189 expected_variant.Set(true);
4190 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsSubscriptAttributeId,
4191 expected_variant);
4192 expected_variant.Reset();
4193
4194 expected_variant.Set(false);
4195 EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
4196 UIA_IsSubscriptAttributeId, expected_variant);
4197 expected_variant.Reset();
4198
4199 expected_variant.Set(false);
4200 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsSuperscriptAttributeId,
4201 expected_variant);
4202 expected_variant.Reset();
4203
4204 expected_variant.Set(true);
4205 EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
4206 UIA_IsSuperscriptAttributeId, expected_variant);
4207 expected_variant.Reset();
4208
4209 expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Dot);
4210 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_OverlineStyleAttributeId,
4211 expected_variant);
4212 expected_variant.Reset();
4213
4214 expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Dash);
4216 text_range_provider, UIA_StrikethroughStyleAttributeId, expected_variant);
4217 expected_variant.Reset();
4218
4219 expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Single);
4220 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
4221 UIA_UnderlineStyleAttributeId, expected_variant);
4222 expected_variant.Reset();
4223
4224 std::wstring style_name;
4225 expected_variant.Set(SysAllocString(style_name.c_str()));
4226 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_StyleNameAttributeId,
4227 expected_variant);
4228 expected_variant.Reset();
4229
4230 expected_variant.Set(static_cast<int32_t>(StyleId_Heading6));
4231 EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
4232 UIA_StyleIdAttributeId, expected_variant);
4233 expected_variant.Reset();
4234
4235 style_name = L"mark";
4236 expected_variant.Set(SysAllocString(style_name.c_str()));
4237 EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
4238 UIA_StyleNameAttributeId, expected_variant);
4239 expected_variant.Reset();
4240
4241 expected_variant.Set(static_cast<int32_t>(StyleId_NumberedList));
4242 EXPECT_UIA_TEXTATTRIBUTE_EQ(list_item_text_range_provider,
4243 UIA_StyleIdAttributeId, expected_variant);
4244 expected_variant.Reset();
4245
4246 expected_variant.Set(static_cast<int32_t>(StyleId_BulletedList));
4247 EXPECT_UIA_TEXTATTRIBUTE_EQ(list_item2_text_range_provider,
4248 UIA_StyleIdAttributeId, expected_variant);
4249 expected_variant.Reset();
4250
4251 expected_variant.Set(
4252 static_cast<int32_t>(FlowDirections::FlowDirections_RightToLeft));
4254 text_range_provider, UIA_TextFlowDirectionsAttributeId, expected_variant);
4255 EXPECT_UIA_TEXTATTRIBUTE_MIXED(document_range_provider,
4256 UIA_TextFlowDirectionsAttributeId);
4257 expected_variant.Reset();
4258
4259 // Move the start endpoint back and forth one character to force such endpoint
4260 // to be located at the end of the previous anchor, this shouldn't cause
4261 // GetAttributeValue to include the previous anchor's attributes.
4262 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
4263 TextPatternRangeEndpoint_Start,
4264 TextUnit_Character,
4265 /*count*/ -1,
4266 /*expected_text*/ L"tmarked text",
4267 /*expected_count*/ -1);
4268 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
4269 TextPatternRangeEndpoint_Start,
4270 TextUnit_Character,
4271 /*count*/ 1,
4272 /*expected_text*/ L"marked text",
4273 /*expected_count*/ 1);
4274 expected_variant.Set(false);
4275 EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
4276 UIA_IsSuperscriptAttributeId, expected_variant);
4277 expected_variant.Reset();
4278
4279 // Same idea as above, but moving forth and back the end endpoint to force it
4280 // to be located at the start of the next anchor.
4281 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
4282 TextPatternRangeEndpoint_End,
4283 TextUnit_Character,
4284 /*count*/ 1,
4285 /*expected_text*/ L"marked textl",
4286 /*expected_count*/ 1);
4287 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
4288 TextPatternRangeEndpoint_End,
4289 TextUnit_Character,
4290 /*count*/ -1,
4291 /*expected_text*/ L"marked text",
4292 /*expected_count*/ -1);
4293 expected_variant.Set(
4294 static_cast<int32_t>(FlowDirections::FlowDirections_RightToLeft));
4295 EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
4296 UIA_TextFlowDirectionsAttributeId,
4297 expected_variant);
4298 expected_variant.Reset();
4299
4300 {
4301 // |text_node| has a grammar error on "some text", a highlight for the
4302 // first word, a spelling error for the second word, a "spelling-error"
4303 // highlight for the fourth word, and a "grammar-error" highlight for the
4304 // fifth word. So the range has mixed annotations.
4305 EXPECT_UIA_TEXTATTRIBUTE_MIXED(text_range_provider,
4306 UIA_AnnotationTypesAttributeId);
4307
4308 // Testing annotations in range [5,9)
4309 // start: TextPosition, anchor_id=2, text_offset=5,
4310 // annotated_text=some <t>ext and some other text
4311 // end : TextPosition, anchor_id=2, text_offset=9,
4312 // annotated_text=some text<> and some other text
4313 AXPlatformNodeWin* owner =
4314 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(text_node));
4315 ComPtr<AXPlatformNodeTextRangeProviderWin> range_with_annotations;
4316 CreateTextRangeProviderWin(
4317 range_with_annotations, owner,
4318 /*start_anchor=*/text_node, /*start_offset=*/5,
4319 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4320 /*end_anchor=*/text_node, /*end_offset=*/9,
4321 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4322
4323 base::win::ScopedVariant annotation_types_variant;
4324 EXPECT_HRESULT_SUCCEEDED(range_with_annotations->GetAttributeValue(
4325 UIA_AnnotationTypesAttributeId, annotation_types_variant.Receive()));
4326
4327 EXPECT_EQ(annotation_types_variant.type(), VT_ARRAY | VT_I4);
4328 std::vector<int> expected_annotations = {AnnotationType_SpellingError,
4329 AnnotationType_GrammarError};
4330 EXPECT_UIA_SAFEARRAY_EQ(V_ARRAY(annotation_types_variant.ptr()),
4331 expected_annotations);
4332 }
4333
4334 {
4335 // Testing annotations in range [0,4)
4336 // start: TextPosition, anchor_id=2, text_offset=0,
4337 // annotated_text=<s>ome text and some other text
4338 // end : TextPosition, anchor_id=2, text_offset=4,
4339 // annotated_text=some<> text and some other text
4340 AXPlatformNodeWin* owner =
4341 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(text_node));
4342 ComPtr<AXPlatformNodeTextRangeProviderWin> range_with_annotations;
4343 CreateTextRangeProviderWin(
4344 range_with_annotations, owner,
4345 /*start_anchor=*/text_node, /*start_offset=*/0,
4346 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4347 /*end_anchor=*/text_node, /*end_offset=*/4,
4348 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4349
4350 base::win::ScopedVariant annotation_types_variant;
4351 EXPECT_HRESULT_SUCCEEDED(range_with_annotations->GetAttributeValue(
4352 UIA_AnnotationTypesAttributeId, annotation_types_variant.Receive()));
4353
4354 EXPECT_EQ(annotation_types_variant.type(), VT_ARRAY | VT_I4);
4355 std::vector<int> expected_annotations = {AnnotationType_GrammarError,
4356 AnnotationType_Highlighted};
4357 EXPECT_UIA_SAFEARRAY_EQ(V_ARRAY(annotation_types_variant.ptr()),
4358 expected_annotations);
4359 }
4360
4361 {
4362 // Testing annotations in range [14,18)
4363 // start: TextPosition, anchor_id=2, text_offset=14,
4364 // annotated_text=some text and <s>ome other text
4365 // end : TextPosition, anchor_id=2, text_offset=18,
4366 // annotated_text=some text and some<> other text
4367 AXPlatformNodeWin* owner =
4368 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(text_node));
4369 ComPtr<AXPlatformNodeTextRangeProviderWin> range_with_annotations;
4370 CreateTextRangeProviderWin(
4371 range_with_annotations, owner,
4372 /*start_anchor=*/text_node, /*start_offset=*/14,
4373 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4374 /*end_anchor=*/text_node, /*end_offset=*/18,
4375 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4376
4377 base::win::ScopedVariant annotation_types_variant;
4378 EXPECT_HRESULT_SUCCEEDED(range_with_annotations->GetAttributeValue(
4379 UIA_AnnotationTypesAttributeId, annotation_types_variant.Receive()));
4380
4381 EXPECT_EQ(annotation_types_variant.type(), VT_ARRAY | VT_I4);
4382 std::vector<int> expected_annotations = {AnnotationType_SpellingError};
4383 EXPECT_UIA_SAFEARRAY_EQ(V_ARRAY(annotation_types_variant.ptr()),
4384 expected_annotations);
4385 }
4386
4387 {
4388 // Testing annotations in range [19,24)
4389 // start: TextPosition, anchor_id=2, text_offset=19,
4390 // annotated_text=some text and some <o>ther text
4391 // end : TextPosition, anchor_id=2, text_offset=24,
4392 // annotated_text=some text and some other<> text
4393 AXPlatformNodeWin* owner =
4394 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(text_node));
4395 ComPtr<AXPlatformNodeTextRangeProviderWin> range_with_annotations;
4396 CreateTextRangeProviderWin(
4397 range_with_annotations, owner,
4398 /*start_anchor=*/text_node, /*start_offset=*/19,
4399 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4400 /*end_anchor=*/text_node, /*end_offset=*/24,
4401 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4402
4403 base::win::ScopedVariant annotation_types_variant;
4404 EXPECT_HRESULT_SUCCEEDED(range_with_annotations->GetAttributeValue(
4405 UIA_AnnotationTypesAttributeId, annotation_types_variant.Receive()));
4406
4407 EXPECT_EQ(annotation_types_variant.type(), VT_ARRAY | VT_I4);
4408 std::vector<int> expected_annotations = {AnnotationType_GrammarError};
4409 EXPECT_UIA_SAFEARRAY_EQ(V_ARRAY(annotation_types_variant.ptr()),
4410 expected_annotations);
4411 }
4412
4413 {
4414 // |heading_text_node| has a spelling error for one word, and no
4415 // annotations for the remaining text, so the range has mixed annotations.
4416 EXPECT_UIA_TEXTATTRIBUTE_MIXED(heading_text_range_provider,
4417 UIA_AnnotationTypesAttributeId);
4418
4419 // start: TextPosition, anchor_id=4, text_offset=5,
4420 // annotated_text=more <t>ext
4421 // end : TextPosition, anchor_id=4, text_offset=9,
4422 // annotated_text=more text<>
4423 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
4424 AXPlatformNodeFromNode(heading_text_node));
4425 ComPtr<AXPlatformNodeTextRangeProviderWin> range_with_annotations;
4426 CreateTextRangeProviderWin(
4427 range_with_annotations, owner,
4428 /*start_anchor=*/heading_text_node, /*start_offset=*/5,
4429 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4430 /*end_anchor=*/heading_text_node, /*end_offset=*/9,
4431 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4432
4433 base::win::ScopedVariant annotation_types_variant;
4434 EXPECT_HRESULT_SUCCEEDED(range_with_annotations->GetAttributeValue(
4435 UIA_AnnotationTypesAttributeId, annotation_types_variant.Receive()));
4436
4437 std::vector<int> expected_annotations = {AnnotationType_SpellingError};
4438 EXPECT_UIA_SAFEARRAY_EQ(V_ARRAY(annotation_types_variant.ptr()),
4439 expected_annotations);
4440 }
4441
4442 {
4443 base::win::ScopedVariant empty_variant;
4444 EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
4445 UIA_AnnotationTypesAttributeId, empty_variant);
4446 }
4447}
4448
4450 DISABLED_TestITextRangeProviderGetAttributeValueAnnotationObjects) {
4451 // rootWebArea id=1
4452 // ++mark id=2 detailsIds=comment1 comment2 highlighted
4453 // ++++staticText id=3 name="some text"
4454 // ++comment id=4 name="comment 1"
4455 // ++++staticText id=5 name="comment 1"
4456 // ++comment id=6 name="comment 2"
4457 // ++++staticText id=7 name="comment 2"
4458 // ++mark id=8 name="highlighted"
4459 // ++++staticText id=9 name="highlighted"
4460
4462 AXNodeData annotation_target;
4463 AXNodeData some_text;
4464 AXNodeData comment1;
4465 AXNodeData comment1_text;
4466 AXNodeData comment2;
4467 AXNodeData comment2_text;
4468 AXNodeData highlighted;
4469 AXNodeData highlighted_text;
4470
4471 root.id = 1;
4472 annotation_target.id = 2;
4473 some_text.id = 3;
4474 comment1.id = 4;
4475 comment1_text.id = 5;
4476 comment2.id = 6;
4477 comment2_text.id = 7;
4478 highlighted.id = 8;
4479 highlighted_text.id = 9;
4480
4482 root.SetName("root");
4483 root.child_ids = {annotation_target.id, comment1.id, comment2.id,
4484 highlighted.id};
4485
4486 annotation_target.role = ax::mojom::Role::kMark;
4487 annotation_target.child_ids = {some_text.id};
4488 annotation_target.AddIntListAttribute(
4490 {comment1.id, comment2.id, highlighted.id});
4491
4493 some_text.SetName("some text");
4494
4496 comment1.SetName("comment 1");
4497 comment1.child_ids = {comment1_text.id};
4498
4499 comment1_text.role = ax::mojom::Role::kStaticText;
4500 comment1_text.SetName("comment 1");
4501
4503 comment2.SetName("comment 2");
4504 comment2.child_ids = {comment2_text.id};
4505
4506 comment2_text.role = ax::mojom::Role::kStaticText;
4507 comment2_text.SetName("comment 2");
4508
4509 highlighted.role = ax::mojom::Role::kMark;
4510 highlighted.SetName("highlighted");
4511 highlighted.child_ids = {highlighted_text.id};
4512
4513 highlighted_text.role = ax::mojom::Role::kStaticText;
4514 highlighted_text.SetName("highlighted");
4515
4517 update.has_tree_data = true;
4518 update.root_id = root.id;
4519 update.nodes = {root, annotation_target, some_text,
4520 comment1, comment1_text, comment2,
4521 comment2_text, highlighted, highlighted_text};
4522 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
4523
4524 Init(update);
4525
4526 AXNode* root_node = GetRootAsAXNode();
4527 AXNode* annotation_target_node = root_node->children()[0];
4528 AXNode* comment1_node = root_node->children()[1];
4529 AXNode* comment2_node = root_node->children()[2];
4530 AXNode* highlighted_node = root_node->children()[3];
4531
4532 ComPtr<AXPlatformNodeTextRangeProviderWin> some_text_range_provider;
4533
4534 // Create a text range encapsulates |annotation_target_node| with content
4535 // "some text".
4536 // start: TextPosition, anchor_id=2, text_offset=0, annotated_text=<s>ome text
4537 // end : TextPosition, anchor_id=2, text_offset=9, annotated_text=some text<>
4538 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
4539 AXPlatformNodeFromNode(annotation_target_node));
4540 CreateTextRangeProviderWin(
4541 some_text_range_provider, owner,
4542 /*start_anchor=*/annotation_target_node, /*start_offset=*/0,
4543 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4544 /*end_anchor=*/annotation_target_node, /*end_offset=*/9,
4545 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4546 ASSERT_NE(nullptr, some_text_range_provider.Get());
4547 EXPECT_UIA_TEXTRANGE_EQ(some_text_range_provider, L"some text");
4548
4549 ComPtr<IRawElementProviderSimple> comment1_provider =
4550 QueryInterfaceFromNode<IRawElementProviderSimple>(comment1_node);
4551 ASSERT_NE(nullptr, comment1_provider.Get());
4552 ComPtr<IRawElementProviderSimple> comment2_provider =
4553 QueryInterfaceFromNode<IRawElementProviderSimple>(comment2_node);
4554 ASSERT_NE(nullptr, comment2_provider.Get());
4555 ComPtr<IRawElementProviderSimple> highlighted_provider =
4556 QueryInterfaceFromNode<IRawElementProviderSimple>(highlighted_node);
4557 ASSERT_NE(nullptr, highlighted_provider.Get());
4558
4559 ComPtr<IAnnotationProvider> annotation_provider;
4560 int annotation_type;
4561
4562 // Validate |comment1_node| with Role::kComment supports IAnnotationProvider.
4563 EXPECT_HRESULT_SUCCEEDED(comment1_provider->GetPatternProvider(
4564 UIA_AnnotationPatternId, &annotation_provider));
4565 ASSERT_NE(nullptr, annotation_provider.Get());
4566 EXPECT_HRESULT_SUCCEEDED(
4567 annotation_provider->get_AnnotationTypeId(&annotation_type));
4568 EXPECT_EQ(AnnotationType_Comment, annotation_type);
4569 annotation_provider.Reset();
4570
4571 // Validate |comment2_node| with Role::kComment supports IAnnotationProvider.
4572 EXPECT_HRESULT_SUCCEEDED(comment2_provider->GetPatternProvider(
4573 UIA_AnnotationPatternId, &annotation_provider));
4574 ASSERT_NE(nullptr, annotation_provider.Get());
4575 EXPECT_HRESULT_SUCCEEDED(
4576 annotation_provider->get_AnnotationTypeId(&annotation_type));
4577 EXPECT_EQ(AnnotationType_Comment, annotation_type);
4578 annotation_provider.Reset();
4579
4580 // Validate |highlighted_node| with Role::kMark supports
4581 // IAnnotationProvider.
4582 EXPECT_HRESULT_SUCCEEDED(highlighted_provider->GetPatternProvider(
4583 UIA_AnnotationPatternId, &annotation_provider));
4584 ASSERT_NE(nullptr, annotation_provider.Get());
4585 EXPECT_HRESULT_SUCCEEDED(
4586 annotation_provider->get_AnnotationTypeId(&annotation_type));
4587 EXPECT_EQ(AnnotationType_Highlighted, annotation_type);
4588 annotation_provider.Reset();
4589
4590 base::win::ScopedVariant annotation_objects_variant;
4591 EXPECT_HRESULT_SUCCEEDED(some_text_range_provider->GetAttributeValue(
4592 UIA_AnnotationObjectsAttributeId, annotation_objects_variant.Receive()));
4593 EXPECT_EQ(VT_UNKNOWN | VT_ARRAY, annotation_objects_variant.type());
4594
4595 std::vector<std::wstring> expected_names = {L"comment 1", L"comment 2",
4596 L"highlighted"};
4597 EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(V_ARRAY(annotation_objects_variant.ptr()),
4598 UIA_NamePropertyId, expected_names);
4599}
4600
4602 DISABLED_TestITextRangeProviderGetAttributeValueAnnotationObjectsMixed) {
4603 // rootWebArea id=1
4604 // ++mark id=2 detailsIds=comment
4605 // ++++staticText id=3 name="some text"
4606 // ++staticText id=4 name="read only" restriction=readOnly
4607 // ++comment id=5 name="comment 1"
4608 // ++++staticText id=6 name="comment 1"
4609
4611 AXNodeData highlighted;
4612 AXNodeData some_text;
4613 AXNodeData readonly_text;
4614 AXNodeData comment1;
4615 AXNodeData comment1_text;
4616
4617 root.id = 1;
4618 highlighted.id = 2;
4619 some_text.id = 3;
4620 readonly_text.id = 4;
4621 comment1.id = 5;
4622 comment1_text.id = 6;
4623
4625 root.SetName("root");
4626 root.child_ids = {highlighted.id, readonly_text.id, comment1.id};
4627
4628 highlighted.role = ax::mojom::Role::kMark;
4629 highlighted.child_ids = {some_text.id};
4631 {comment1.id});
4632
4634 some_text.SetName("some text");
4635
4636 readonly_text.role = ax::mojom::Role::kStaticText;
4638 readonly_text.SetName("read only");
4639
4641 comment1.SetName("comment 1");
4642 comment1.child_ids = {comment1_text.id};
4643
4644 comment1_text.role = ax::mojom::Role::kStaticText;
4645 comment1_text.SetName("comment 1");
4646
4648 update.has_tree_data = true;
4649 update.root_id = root.id;
4650 update.nodes = {root, highlighted, some_text,
4651 readonly_text, comment1, comment1_text};
4652 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
4653
4654 Init(update);
4655
4656 AXNode* root_node = GetRootAsAXNode();
4657 AXNode* highlighted_node = root_node->children()[0];
4658 AXNode* some_text_node = highlighted_node->children()[0];
4659 AXNode* readonly_text_node = root_node->children()[1];
4660 AXNode* comment1_node = root_node->children()[2];
4661
4662 // Create a text range encapsulates |highlighted_node| with content
4663 // "some text".
4664 // start: TextPosition, anchor_id=2, text_offset=0, annotated_text=<s>ome text
4665 // end : TextPosition, anchor_id=2, text_offset=9, annotated_text=some text<>
4666 ComPtr<AXPlatformNodeTextRangeProviderWin> some_text_range_provider;
4667 AXPlatformNodeWin* owner =
4668 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(highlighted_node));
4669 CreateTextRangeProviderWin(
4670 some_text_range_provider, owner,
4671 /*start_anchor=*/highlighted_node, /*start_offset=*/0,
4672 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4673 /*end_anchor=*/highlighted_node, /*end_offset=*/9,
4674 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4675 ASSERT_NE(nullptr, some_text_range_provider.Get());
4676 EXPECT_UIA_TEXTRANGE_EQ(some_text_range_provider, L"some text");
4677
4678 ComPtr<ITextRangeProvider> readonly_text_range_provider;
4679 GetTextRangeProviderFromTextNode(readonly_text_range_provider,
4680 readonly_text_node);
4681 ASSERT_NE(nullptr, readonly_text_range_provider.Get());
4682
4683 ComPtr<IRawElementProviderSimple> comment1_provider =
4684 QueryInterfaceFromNode<IRawElementProviderSimple>(comment1_node);
4685 ASSERT_NE(nullptr, comment1_provider.Get());
4686
4687 ComPtr<IAnnotationProvider> annotation_provider;
4688 int annotation_type;
4689 base::win::ScopedVariant expected_variant;
4690
4691 // Validate |comment1_node| with Role::kComment supports IAnnotationProvider.
4692 EXPECT_HRESULT_SUCCEEDED(comment1_provider->GetPatternProvider(
4693 UIA_AnnotationPatternId, &annotation_provider));
4694 ASSERT_NE(nullptr, annotation_provider.Get());
4695 EXPECT_HRESULT_SUCCEEDED(
4696 annotation_provider->get_AnnotationTypeId(&annotation_type));
4697 EXPECT_EQ(AnnotationType_Comment, annotation_type);
4698 annotation_provider.Reset();
4699
4700 // Validate text range "some text" supports AnnotationObjectsAttribute.
4701 EXPECT_HRESULT_SUCCEEDED(some_text_range_provider->GetAttributeValue(
4702 UIA_AnnotationObjectsAttributeId, expected_variant.Receive()));
4703 EXPECT_EQ(VT_UNKNOWN | VT_ARRAY, expected_variant.type());
4704
4705 std::vector<std::wstring> expected_names = {L"comment 1"};
4706 EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(V_ARRAY(expected_variant.ptr()),
4707 UIA_NamePropertyId, expected_names);
4708 expected_variant.Reset();
4709
4710 // Validate text range "read only" supports IsReadOnlyAttribute.
4711 // Use IsReadOnly on text range "read only" as a second property in order to
4712 // test the "mixed" property in the following section.
4713 expected_variant.Set(true);
4714 EXPECT_UIA_TEXTATTRIBUTE_EQ(readonly_text_range_provider,
4715 UIA_IsReadOnlyAttributeId, expected_variant);
4716
4717 // Validate text range "some textread only" returns mixed attribute.
4718 // start: TextPosition, anchor_id=2, text_offset=0, annotated_text=<s>ome text
4719 // end : TextPosition, anchor_id=3, text_offset=9, annotated_text=read only<>
4720 ComPtr<AXPlatformNodeTextRangeProviderWin> mixed_text_range_provider;
4721 CreateTextRangeProviderWin(
4722 mixed_text_range_provider, owner,
4723 /*start_anchor=*/some_text_node, /*start_offset=*/0,
4724 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4725 /*end_anchor=*/readonly_text_node, /*end_offset=*/9,
4726 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4727
4728 EXPECT_UIA_TEXTRANGE_EQ(mixed_text_range_provider, L"some textread only");
4729 EXPECT_UIA_TEXTATTRIBUTE_MIXED(mixed_text_range_provider,
4730 UIA_AnnotationObjectsAttributeId);
4731}
4732
4734 TestITextRangeProviderGetAttributeValueNotSupported) {
4735 ui::AXNodeData root_data;
4736 root_data.id = 1;
4738
4739 ui::AXNodeData text_data_first;
4740 text_data_first.id = 2;
4741 text_data_first.role = ax::mojom::Role::kStaticText;
4742 text_data_first.SetName("first");
4743 root_data.child_ids.push_back(text_data_first.id);
4744
4745 ui::AXNodeData text_data_second;
4746 text_data_second.id = 3;
4747 text_data_second.role = ax::mojom::Role::kStaticText;
4748 text_data_second.SetName("second");
4749 root_data.child_ids.push_back(text_data_second.id);
4750
4752 ui::AXTreeData tree_data;
4754 update.tree_data = tree_data;
4755 update.has_tree_data = true;
4756 update.root_id = root_data.id;
4757 update.nodes.push_back(root_data);
4758 update.nodes.push_back(text_data_first);
4759 update.nodes.push_back(text_data_second);
4760
4761 Init(update);
4762
4763 ComPtr<ITextRangeProvider> document_range_provider;
4764 GetTextRangeProviderFromTextNode(document_range_provider, GetRootAsAXNode());
4765
4766 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4767 UIA_AfterParagraphSpacingAttributeId);
4768 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4769 UIA_AnimationStyleAttributeId);
4770 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4771 UIA_BeforeParagraphSpacingAttributeId);
4772 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4773 UIA_CapStyleAttributeId);
4774 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4775 UIA_CaretBidiModeAttributeId);
4776 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4777 UIA_CaretPositionAttributeId);
4778 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4779 UIA_IndentationFirstLineAttributeId);
4780 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4781 UIA_IndentationLeadingAttributeId);
4782 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4783 UIA_IndentationTrailingAttributeId);
4784 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4785 UIA_IsActiveAttributeId);
4786 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4787 UIA_LineSpacingAttributeId);
4788 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4789 UIA_LinkAttributeId);
4790 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4791 UIA_MarginBottomAttributeId);
4792 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4793 UIA_MarginLeadingAttributeId);
4794 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4795 UIA_MarginTopAttributeId);
4796 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4797 UIA_MarginTrailingAttributeId);
4798 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4799 UIA_OutlineStylesAttributeId);
4800 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4801 UIA_OverlineColorAttributeId);
4802 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4803 UIA_SelectionActiveEndAttributeId);
4804 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4805 UIA_StrikethroughColorAttributeId);
4806 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4807 UIA_TabsAttributeId);
4808 EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
4809 UIA_UnderlineColorAttributeId);
4810}
4811
4813 TestITextRangeProviderGetAttributeValueWithAncestorTextPosition) {
4814 ui::AXTreeUpdate initial_state;
4816 initial_state.tree_data.tree_id = tree_id;
4817 initial_state.has_tree_data = true;
4818 initial_state.root_id = 1;
4819 initial_state.nodes.resize(5);
4820 initial_state.nodes[0].id = 1;
4821 initial_state.nodes[0].child_ids = {2};
4822 initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
4823 initial_state.nodes[1].id = 2;
4824 initial_state.nodes[1].child_ids = {3};
4825 initial_state.nodes[1].role = ax::mojom::Role::kGenericContainer;
4826 initial_state.nodes[2].id = 3;
4827 initial_state.nodes[2].child_ids = {4, 5};
4828 initial_state.nodes[2].role = ax::mojom::Role::kGenericContainer;
4829 initial_state.nodes[3].id = 4;
4830 initial_state.nodes[3].role = ax::mojom::Role::kStaticText;
4831 initial_state.nodes[3].SetName("some text");
4832 initial_state.nodes[3].AddIntAttribute(
4834 initial_state.nodes[4].id = 5;
4835 initial_state.nodes[4].role = ax::mojom::Role::kStaticText;
4836 initial_state.nodes[4].SetName("more text");
4837 initial_state.nodes[4].AddIntAttribute(
4839
4840 Init(initial_state);
4841 const AXTree* tree = GetTree();
4842 const AXNode* some_text_node = tree->GetFromId(4);
4843 const AXNode* more_text_node = tree->GetFromId(5);
4844
4845 // Making |owner| AXID:2 so that |TestAXNodeWrapper::BuildAllWrappers|
4846 // will build the entire subtree, and not only AXID:3 for example.
4847 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
4848 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 2)));
4849
4850 // start: TextPosition, anchor_id=4, text_offset=0, annotated_text=<s>ome text
4851 // end : TextPosition, anchor_id=5, text_offset=8,
4852 // annotated_text=more tex<t>
4853 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider_win;
4854 CreateTextRangeProviderWin(
4855 text_range_provider_win, owner,
4856 /*start_anchor=*/some_text_node, /*start_offset=*/0,
4857 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
4858 /*end_anchor=*/more_text_node, /*end_offset=*/8,
4859 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
4860
4861 ASSERT_TRUE(GetStart(text_range_provider_win.Get())->IsTextPosition());
4862 ASSERT_EQ(4, GetStart(text_range_provider_win.Get())->anchor_id());
4863 ASSERT_EQ(0, GetStart(text_range_provider_win.Get())->text_offset());
4864 ASSERT_TRUE(GetEnd(text_range_provider_win.Get())->IsTextPosition());
4865 ASSERT_EQ(5, GetEnd(text_range_provider_win.Get())->anchor_id());
4866 ASSERT_EQ(8, GetEnd(text_range_provider_win.Get())->text_offset());
4867
4868 base::win::ScopedVariant expected_variant;
4869 // SkColor is ARGB, COLORREF is 0BGR
4870 expected_variant.Set(static_cast<int32_t>(0x00EFBEADU));
4871 EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider_win,
4872 UIA_BackgroundColorAttributeId, expected_variant);
4873}
4874
4875TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderSelect) {
4876 Init(BuildTextDocument({"some text", "more text2"}));
4877 AXNode* root_node = GetRootAsAXNode();
4878
4879 // Text range for the document, which contains text "some textmore text2".
4880 ComPtr<IRawElementProviderSimple> root_node_raw =
4881 QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
4882 ComPtr<ITextProvider> document_provider;
4883 ComPtr<ITextRangeProvider> document_text_range_provider;
4884 ComPtr<AXPlatformNodeTextRangeProviderWin> document_text_range;
4885 EXPECT_HRESULT_SUCCEEDED(
4886 root_node_raw->GetPatternProvider(UIA_TextPatternId, &document_provider));
4887 EXPECT_HRESULT_SUCCEEDED(
4888 document_provider->get_DocumentRange(&document_text_range_provider));
4889 document_text_range_provider->QueryInterface(
4890 IID_PPV_ARGS(&document_text_range));
4891 AXPlatformNodeWin* owner_platform =
4892 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(root_node));
4893 ASSERT_NE(owner_platform, nullptr);
4894 SetOwner(owner_platform, document_text_range_provider.Get());
4895
4896 // Text range related to "some text".
4897 ComPtr<ITextRangeProvider> text_range_provider;
4898 GetTextRangeProviderFromTextNode(text_range_provider,
4899 root_node->children()[0]);
4900 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range;
4901 EXPECT_HRESULT_SUCCEEDED(
4902 text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range)));
4903
4904 // Text range related to "more text2".
4905 ComPtr<ITextRangeProvider> more_text_range_provider;
4906 GetTextRangeProviderFromTextNode(more_text_range_provider,
4907 root_node->children()[1]);
4908 SetOwner(owner_platform, more_text_range_provider.Get());
4909 ComPtr<AXPlatformNodeTextRangeProviderWin> more_text_range;
4910 more_text_range_provider->QueryInterface(IID_PPV_ARGS(&more_text_range));
4911
4912 AXPlatformNodeDelegate* delegate =
4913 GetOwner(document_text_range.Get())->GetDelegate();
4914
4915 ComPtr<ITextRangeProvider> selected_text_range_provider;
4917 LONG index = 0;
4918 LONG ubound;
4919 LONG lbound;
4920
4921 // Text range "some text" performs select.
4922 {
4923 text_range_provider->Select();
4924
4925 // Verify selection.
4926 AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
4927 EXPECT_EQ(3, unignored_selection.anchor_object_id);
4928 EXPECT_EQ(3, unignored_selection.focus_object_id);
4929 EXPECT_EQ(0, unignored_selection.anchor_offset);
4930 EXPECT_EQ(9, unignored_selection.focus_offset);
4931
4932 // Verify the content of the selection.
4933 document_provider->GetSelection(selection.Receive());
4934 ASSERT_NE(nullptr, selection.Get());
4935
4936 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selection.Get(), 1, &ubound));
4937 EXPECT_EQ(0, ubound);
4938 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selection.Get(), 1, &lbound));
4939 EXPECT_EQ(0, lbound);
4940 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
4941 selection.Get(), &index,
4942 static_cast<void**>(&selected_text_range_provider)));
4943 SetOwner(owner_platform, selected_text_range_provider.Get());
4944 EXPECT_UIA_TEXTRANGE_EQ(selected_text_range_provider, L"some text");
4945
4946 selected_text_range_provider.Reset();
4947 selection.Reset();
4948 }
4949
4950 // Text range "more text2" performs select.
4951 {
4952 more_text_range_provider->Select();
4953
4954 // Verify selection
4955 AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
4956 EXPECT_EQ(5, unignored_selection.anchor_object_id);
4957 EXPECT_EQ(5, unignored_selection.focus_object_id);
4958 EXPECT_EQ(0, unignored_selection.anchor_offset);
4959 EXPECT_EQ(10, unignored_selection.focus_offset);
4960
4961 // Verify the content of the selection.
4962 document_provider->GetSelection(selection.Receive());
4963 ASSERT_NE(nullptr, selection.Get());
4964
4965 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selection.Get(), 1, &ubound));
4966 EXPECT_EQ(0, ubound);
4967 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selection.Get(), 1, &lbound));
4968 EXPECT_EQ(0, lbound);
4969 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
4970 selection.Get(), &index,
4971 static_cast<void**>(&selected_text_range_provider)));
4972 SetOwner(owner_platform, selected_text_range_provider.Get());
4973 EXPECT_UIA_TEXTRANGE_EQ(selected_text_range_provider, L"more text2");
4974
4975 selected_text_range_provider.Reset();
4976 selection.Reset();
4977 }
4978
4979 // Document text range "some textmore text2" performs select.
4980 {
4981 document_text_range_provider->Select();
4982
4983 // Verify selection.
4984 AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
4985 EXPECT_EQ(3, unignored_selection.anchor_object_id);
4986 EXPECT_EQ(5, unignored_selection.focus_object_id);
4987 EXPECT_EQ(0, unignored_selection.anchor_offset);
4988 EXPECT_EQ(10, unignored_selection.focus_offset);
4989
4990 // Verify the content of the selection.
4991 document_provider->GetSelection(selection.Receive());
4992 ASSERT_NE(nullptr, selection.Get());
4993
4994 document_provider->GetSelection(selection.Receive());
4995 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selection.Get(), 1, &ubound));
4996 EXPECT_EQ(0, ubound);
4997 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selection.Get(), 1, &lbound));
4998 EXPECT_EQ(0, lbound);
4999 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
5000 selection.Get(), &index,
5001 static_cast<void**>(&selected_text_range_provider)));
5002 SetOwner(owner_platform, selected_text_range_provider.Get());
5003 EXPECT_UIA_TEXTRANGE_EQ(selected_text_range_provider,
5004 L"some textmore text2");
5005 }
5006
5007 // A degenerate text range performs select.
5008 {
5009 // Move the endpoint of text range so it becomes degenerate, then select.
5010 text_range_provider->MoveEndpointByRange(TextPatternRangeEndpoint_Start,
5011 text_range_provider.Get(),
5012 TextPatternRangeEndpoint_End);
5013 text_range_provider->Select();
5014
5015 // Verify selection.
5016 AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
5017 EXPECT_EQ(3, unignored_selection.anchor_object_id);
5018 EXPECT_EQ(3, unignored_selection.focus_object_id);
5019 EXPECT_EQ(9, unignored_selection.anchor_offset);
5020 EXPECT_EQ(9, unignored_selection.focus_offset);
5021
5022 // Verify selection on degenerate range.
5023 document_provider->GetSelection(selection.Receive());
5024 ASSERT_NE(nullptr, selection.Get());
5025
5026 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selection.Get(), 1, &ubound));
5027 EXPECT_EQ(0, ubound);
5028 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selection.Get(), 1, &lbound));
5029 EXPECT_EQ(0, lbound);
5030 EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
5031 selection.Get(), &index,
5032 static_cast<void**>(&selected_text_range_provider)));
5033 SetOwner(owner_platform, selected_text_range_provider.Get());
5034 EXPECT_UIA_TEXTRANGE_EQ(selected_text_range_provider, L"");
5035
5036 selected_text_range_provider.Reset();
5037 selection.Reset();
5038 }
5039}
5040
5041// TODO(crbug.com/1124051): Remove this test once this crbug is fixed.
5043 TestITextRangeProviderSelectListMarker) {
5044 ui::AXNodeData root_data;
5045 root_data.id = 1;
5047
5048 ui::AXNodeData list_data;
5049 list_data.id = 2;
5050 list_data.role = ax::mojom::Role::kList;
5051 root_data.child_ids.push_back(list_data.id);
5052
5053 ui::AXNodeData list_item_data;
5054 list_item_data.id = 3;
5055 list_item_data.role = ax::mojom::Role::kListItem;
5056 list_data.child_ids.push_back(list_item_data.id);
5057
5058 ui::AXNodeData list_marker;
5059 list_marker.id = 4;
5060 list_marker.role = ax::mojom::Role::kListMarker;
5061 list_item_data.child_ids.push_back(list_marker.id);
5062
5063 ui::AXNodeData static_text_data;
5064 static_text_data.id = 5;
5065 static_text_data.role = ax::mojom::Role::kStaticText;
5066 static_text_data.SetName("1. ");
5067 list_marker.child_ids.push_back(static_text_data.id);
5068
5069 ui::AXNodeData list_item_text_data;
5070 list_item_text_data.id = 6;
5071 list_item_text_data.role = ax::mojom::Role::kStaticText;
5072 list_item_text_data.SetName("First Item");
5073 list_item_data.child_ids.push_back(list_item_text_data.id);
5074
5076 ui::AXTreeData tree_data;
5078 update.tree_data = tree_data;
5079 update.has_tree_data = true;
5080 update.root_id = root_data.id;
5081 update.nodes = {root_data, list_data, list_item_data,
5082 list_marker, static_text_data, list_item_text_data};
5083 Init(update);
5084 AXNode* root_node = GetRootAsAXNode();
5085
5086 // Text range related to "1. ".
5087 AXNode* list_node = root_node->children()[0];
5088 AXNode* list_item_node = list_node->children()[0];
5089 AXNode* list_marker_node = list_item_node->children()[0];
5090 ComPtr<ITextRangeProvider> list_marker_text_range_provider;
5091 GetTextRangeProviderFromTextNode(list_marker_text_range_provider,
5092 list_marker_node->children()[0]);
5093
5094 // A list marker text range performs select.
5095 EXPECT_HRESULT_SUCCEEDED(list_marker_text_range_provider->Select());
5096
5097 // Verify selection was not performed on list marker range.
5099 ComPtr<IRawElementProviderSimple> root_node_raw =
5100 QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
5101 ComPtr<ITextProvider> document_provider;
5102 EXPECT_HRESULT_SUCCEEDED(
5103 root_node_raw->GetPatternProvider(UIA_TextPatternId, &document_provider));
5104 EXPECT_HRESULT_SUCCEEDED(
5105 document_provider->GetSelection(selection.Receive()));
5106 ASSERT_EQ(nullptr, selection.Get());
5107 selection.Reset();
5108}
5109
5110TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderFindText) {
5111 // Initialize the ICU data from the icudtl.dat file, if it exists.
5112 wchar_t buffer[MAX_PATH];
5113 GetModuleFileName(nullptr, buffer, MAX_PATH);
5114 std::filesystem::path exec_path(buffer);
5115 exec_path.remove_filename();
5116 exec_path.append("icudtl.dat");
5117 const std::string icudtl_path = exec_path.string();
5118 if (std::filesystem::exists(icudtl_path)) {
5119 fml::icu::InitializeICU(icudtl_path);
5120 }
5121
5122 // \xC3\xA9 are the UTF8 bytes for codepoint 0xE9 - accented lowercase e.
5123 Init(BuildTextDocument({"some text", "more text", "resum\xC3\xA9"},
5124 false /* build_word_boundaries_offsets */,
5125 true /* place_text_on_one_line */));
5126
5127 AXNode* root_node = GetRootAsAXNode();
5128 AXPlatformNodeWin* owner =
5129 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(root_node));
5130 ASSERT_NE(owner, nullptr);
5131 ComPtr<ITextRangeProvider> range;
5132
5133 // Test Leaf kStaticText search.
5134 GetTextRangeProviderFromTextNode(range, root_node->children()[0]);
5135 EXPECT_UIA_FIND_TEXT(range, L"some text", false, owner);
5136 EXPECT_UIA_FIND_TEXT(range, L"SoMe TeXt", true, owner);
5137 GetTextRangeProviderFromTextNode(range, root_node->children()[1]);
5138 EXPECT_UIA_FIND_TEXT(range, L"more", false, owner);
5139 EXPECT_UIA_FIND_TEXT(range, L"MoRe", true, owner);
5140
5141 // Test searching for leaf content from ancestor.
5142 GetTextRangeProviderFromTextNode(range, root_node);
5143 EXPECT_UIA_FIND_TEXT(range, L"some text", false, owner);
5144 EXPECT_UIA_FIND_TEXT(range, L"SoMe TeXt", true, owner);
5145 EXPECT_UIA_FIND_TEXT(range, L"more text", false, owner);
5146 EXPECT_UIA_FIND_TEXT(range, L"MoRe TeXt", true, owner);
5147 EXPECT_UIA_FIND_TEXT(range, L"more", false, owner);
5148 // Accented lowercase e.
5149 EXPECT_UIA_FIND_TEXT(range, L"resum\xE9", false, owner);
5150 // Accented uppercase +e.
5151 EXPECT_UIA_FIND_TEXT(range, L"resum\xC9", true, owner);
5152 EXPECT_UIA_FIND_TEXT(range, L"resume", true, owner);
5153 EXPECT_UIA_FIND_TEXT(range, L"resumE", true, owner);
5154 // Test finding text that crosses a node boundary.
5155 EXPECT_UIA_FIND_TEXT(range, L"textmore", false, owner);
5156 // Test no match.
5157 EXPECT_UIA_FIND_TEXT_NO_MATCH(range, L"no match", false, owner);
5158 EXPECT_UIA_FIND_TEXT_NO_MATCH(range, L"resume", false, owner);
5159
5160 // Test if range returned is in expected anchor node.
5161 GetTextRangeProviderFromTextNode(range, root_node->children()[1]);
5163 Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider_found;
5164 EXPECT_HRESULT_SUCCEEDED(range->FindText(find_string.Get(), false, false,
5165 &text_range_provider_found));
5166 Microsoft::WRL::ComPtr<AXPlatformNodeTextRangeProviderWin>
5167 text_range_provider_win;
5168 text_range_provider_found->QueryInterface(
5169 IID_PPV_ARGS(&text_range_provider_win));
5170 ASSERT_TRUE(GetStart(text_range_provider_win.Get())->IsTextPosition());
5171 EXPECT_EQ(5, GetStart(text_range_provider_win.Get())->anchor_id());
5172 EXPECT_EQ(0, GetStart(text_range_provider_win.Get())->text_offset());
5173 ASSERT_TRUE(GetEnd(text_range_provider_win.Get())->IsTextPosition());
5174 EXPECT_EQ(5, GetEnd(text_range_provider_win.Get())->anchor_id());
5175 EXPECT_EQ(9, GetEnd(text_range_provider_win.Get())->text_offset());
5176}
5177
5179 FindTextWithEmbeddedObjectCharacter) {
5180 // ++1 kRootWebArea
5181 // ++++2 kList
5182 // ++++++3 kListItem
5183 // ++++++++4 kStaticText
5184 // ++++++++++5 kInlineTextBox
5185 // ++++++6 kListItem
5186 // ++++++++7 kStaticText
5187 // ++++++++++8 kInlineTextBox
5188 ui::AXNodeData root_1;
5189 ui::AXNodeData list_2;
5190 ui::AXNodeData list_item_3;
5191 ui::AXNodeData static_text_4;
5192 ui::AXNodeData inline_box_5;
5193 ui::AXNodeData list_item_6;
5194 ui::AXNodeData static_text_7;
5195 ui::AXNodeData inline_box_8;
5196
5197 root_1.id = 1;
5198 list_2.id = 2;
5199 list_item_3.id = 3;
5200 static_text_4.id = 4;
5201 inline_box_5.id = 5;
5202 list_item_6.id = 6;
5203 static_text_7.id = 7;
5204 inline_box_8.id = 8;
5205
5207 root_1.child_ids = {list_2.id};
5208
5210 list_2.child_ids = {list_item_3.id, list_item_6.id};
5211
5212 list_item_3.role = ax::mojom::Role::kListItem;
5213 list_item_3.child_ids = {static_text_4.id};
5214
5215 static_text_4.role = ax::mojom::Role::kStaticText;
5216 static_text_4.SetName("foo");
5217 static_text_4.child_ids = {inline_box_5.id};
5218
5220 inline_box_5.SetName("foo");
5221
5222 list_item_6.role = ax::mojom::Role::kListItem;
5223 list_item_6.child_ids = {static_text_7.id};
5224
5225 static_text_7.role = ax::mojom::Role::kStaticText;
5226 static_text_7.child_ids = {inline_box_8.id};
5227 static_text_7.SetName("bar");
5228
5230 inline_box_8.SetName("bar");
5231
5233 ui::AXTreeData tree_data;
5235 update.tree_data = tree_data;
5236 update.has_tree_data = true;
5237 update.root_id = root_1.id;
5238 update.nodes = {root_1, list_2, list_item_3, static_text_4,
5239 inline_box_5, list_item_6, static_text_7, inline_box_8};
5240
5241 Init(update);
5242
5243 AXNode* root_node = GetRootAsAXNode();
5244 ComPtr<ITextRangeProvider> text_range_provider;
5245 GetTextRangeProviderFromTextNode(text_range_provider, root_node);
5246
5248 Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider_found;
5249 EXPECT_HRESULT_SUCCEEDED(text_range_provider->FindText(
5250 find_string.Get(), false, false, &text_range_provider_found));
5251 ASSERT_TRUE(text_range_provider_found.Get());
5252 Microsoft::WRL::ComPtr<AXPlatformNodeTextRangeProviderWin>
5253 text_range_provider_win;
5254 text_range_provider_found->QueryInterface(
5255 IID_PPV_ARGS(&text_range_provider_win));
5256 ASSERT_TRUE(GetStart(text_range_provider_win.Get())->IsTextPosition());
5257 EXPECT_EQ(5, GetStart(text_range_provider_win.Get())->anchor_id());
5258 EXPECT_EQ(1, GetStart(text_range_provider_win.Get())->text_offset());
5259 ASSERT_TRUE(GetEnd(text_range_provider_win.Get())->IsTextPosition());
5260 EXPECT_EQ(8, GetEnd(text_range_provider_win.Get())->anchor_id());
5261 EXPECT_EQ(3, GetEnd(text_range_provider_win.Get())->text_offset());
5262}
5263
5265 TestITextRangeProviderFindTextBackwards) {
5266 Init(BuildTextDocument({"text", "some", "text"},
5267 false /* build_word_boundaries_offsets */,
5268 true /* place_text_on_one_line */));
5269 AXNode* root_node = GetRootAsAXNode();
5270
5271 ComPtr<ITextRangeProvider> root_range_provider;
5272 GetTextRangeProviderFromTextNode(root_range_provider, root_node);
5273 ComPtr<ITextRangeProvider> text_node1_range;
5274 GetTextRangeProviderFromTextNode(text_node1_range, root_node->children()[0]);
5275 ComPtr<ITextRangeProvider> text_node3_range;
5276 GetTextRangeProviderFromTextNode(text_node3_range, root_node->children()[2]);
5277
5278 ComPtr<ITextRangeProvider> text_range_provider_found;
5280 BOOL range_equal;
5281
5282 // Forward search finds the text_node1.
5283 EXPECT_HRESULT_SUCCEEDED(root_range_provider->FindText(
5284 find_string.Get(), false, false, &text_range_provider_found));
5285 CopyOwnerToClone(root_range_provider.Get(), text_range_provider_found.Get());
5286 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider_found, find_string.Get());
5287
5288 range_equal = false;
5289 EXPECT_HRESULT_SUCCEEDED(
5290 text_range_provider_found->Compare(text_node1_range.Get(), &range_equal));
5291 EXPECT_TRUE(range_equal);
5292
5293 // Backwards search finds the text_node3.
5294 EXPECT_HRESULT_SUCCEEDED(root_range_provider->FindText(
5295 find_string.Get(), true, false, &text_range_provider_found));
5296 CopyOwnerToClone(root_range_provider.Get(), text_range_provider_found.Get());
5297 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider_found, find_string.Get());
5298
5299 range_equal = false;
5300 EXPECT_HRESULT_SUCCEEDED(
5301 text_range_provider_found->Compare(text_node3_range.Get(), &range_equal));
5302 EXPECT_TRUE(range_equal);
5303}
5304
5306 TestITextRangeProviderFindAttribute) {
5307 // document - visible
5308 // [empty]
5309 //
5310 // Search forward, look for IsHidden=true.
5311 // Expected: nullptr
5312 // Search forward, look for IsHidden=false.
5313 // Expected: ""
5314 // Note: returns "" rather than nullptr here because document root web area by
5315 // default set to visible. So the text range represents document matches
5316 // our searching criteria. And we return a degenerate range.
5317 //
5318 // Search backward, look for IsHidden=true.
5319 // Expected: nullptr
5320 // Search backward, look for IsHidden=false.
5321 // Expected: ""
5322 // Note: returns "" rather than nullptr here because document root web area by
5323 // default set to visible. So the text range represents document matches
5324 // our searching criteria. And we return a degenerate range.
5325 {
5326 ui::AXNodeData root_data;
5327 root_data.id = 1;
5329
5331 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
5332 update.has_tree_data = true;
5333 update.root_id = root_data.id;
5334 update.nodes = {root_data};
5335
5336 Init(update);
5337
5338 bool is_search_backward;
5339 VARIANT is_hidden_attr_val;
5340 V_VT(&is_hidden_attr_val) = VT_BOOL;
5341 ComPtr<ITextRangeProvider> matched_range_provider;
5342 ComPtr<ITextRangeProvider> document_range_provider;
5343 GetTextRangeProviderFromTextNode(document_range_provider,
5344 GetRootAsAXNode());
5345
5346 // Search forward, look for IsHidden=true.
5347 // Expected: nullptr
5348 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5349 is_search_backward = false;
5350 document_range_provider->FindAttribute(
5351 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5352 &matched_range_provider);
5353 ASSERT_EQ(nullptr, matched_range_provider.Get());
5354
5355 // Search forward, look for IsHidden=false.
5356 // Expected: ""
5357 // Note: returns "" rather than nullptr here because document root web area
5358 // by default set to visible. So the text range represents document
5359 // matches our searching criteria. And we return a degenerate range.
5360 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5361 is_search_backward = false;
5362 document_range_provider->FindAttribute(
5363 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5364 &matched_range_provider);
5365 ASSERT_NE(nullptr, matched_range_provider.Get());
5366 CopyOwnerToClone(document_range_provider.Get(),
5367 matched_range_provider.Get());
5368 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"");
5369 matched_range_provider.Reset();
5370
5371 // Search backward, look for IsHidden=true.
5372 // Expected: nullptr
5373 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5374 is_search_backward = true;
5375 document_range_provider->FindAttribute(
5376 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5377 &matched_range_provider);
5378 ASSERT_EQ(nullptr, matched_range_provider.Get());
5379
5380 // Search backward, look for IsHidden=false.
5381 // Expected: ""
5382 // Note: returns "" rather than nullptr here because document root web area
5383 // by default set to visible. So the text range represents document
5384 // matches our searching criteria. And we return a degenerate range.
5385 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5386 is_search_backward = true;
5387 document_range_provider->FindAttribute(
5388 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5389 &matched_range_provider);
5390 ASSERT_NE(nullptr, matched_range_provider.Get());
5391 CopyOwnerToClone(document_range_provider.Get(),
5392 matched_range_provider.Get());
5393 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"");
5394 }
5395
5396 // document - visible
5397 // text1 - invisible
5398 //
5399 // Search forward, look for IsHidden=true.
5400 // Expected: "text1"
5401 // Search forward, look for IsHidden=false.
5402 // Expected: nullptr
5403 // Search backward, look for IsHidden=true.
5404 // Expected: "text1"
5405 // Search backward, look for IsHidden=false.
5406 // Expected: nullptr
5407 {
5408 ui::AXNodeData text_data1;
5409 text_data1.id = 2;
5412 text_data1.SetName("text1");
5413
5414 ui::AXNodeData root_data;
5415 root_data.id = 1;
5417 root_data.child_ids = {2};
5418
5420 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
5421 update.has_tree_data = true;
5422 update.root_id = root_data.id;
5423 update.nodes = {root_data, text_data1};
5424
5425 Init(update);
5426
5427 bool is_search_backward;
5428 VARIANT is_hidden_attr_val;
5429 V_VT(&is_hidden_attr_val) = VT_BOOL;
5430 ComPtr<ITextRangeProvider> matched_range_provider;
5431 ComPtr<ITextRangeProvider> document_range_provider;
5432 GetTextRangeProviderFromTextNode(document_range_provider,
5433 GetRootAsAXNode());
5434
5435 // Search forward, look for IsHidden=true.
5436 // Expected: "text1"
5437 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5438 is_search_backward = false;
5439 document_range_provider->FindAttribute(
5440 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5441 &matched_range_provider);
5442 ASSERT_NE(nullptr, matched_range_provider.Get());
5443 CopyOwnerToClone(document_range_provider.Get(),
5444 matched_range_provider.Get());
5445 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1");
5446 matched_range_provider.Reset();
5447
5448 // Search forward, look for IsHidden=false.
5449 // Expected: nullptr
5450 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5451 is_search_backward = false;
5452 document_range_provider->FindAttribute(
5453 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5454 &matched_range_provider);
5455 ASSERT_EQ(nullptr, matched_range_provider.Get());
5456
5457 // Search backward, look for IsHidden=true.
5458 // Expected: "text1"
5459 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5460 is_search_backward = true;
5461 document_range_provider->FindAttribute(
5462 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5463 &matched_range_provider);
5464 ASSERT_NE(nullptr, matched_range_provider.Get());
5465 CopyOwnerToClone(document_range_provider.Get(),
5466 matched_range_provider.Get());
5467 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1");
5468 matched_range_provider.Reset();
5469
5470 // Search backward, look for IsHidden=false.
5471 // Expected: nullptr
5472 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5473 is_search_backward = true;
5474 document_range_provider->FindAttribute(
5475 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5476 &matched_range_provider);
5477 ASSERT_EQ(nullptr, matched_range_provider.Get());
5478 }
5479
5480 // document - visible
5481 // text1 - visible
5482 // text2 - visible
5483 //
5484 // Search forward, look for IsHidden=true.
5485 // Expected: nullptr
5486 // Search forward, look for IsHidden=false.
5487 // Expected: "text1text2"
5488 // Search backward, look for IsHidden=true.
5489 // Expected: nullptr
5490 // Search backward, look for IsHidden=false.
5491 // Expected: "text1text2"
5492 {
5493 ui::AXNodeData text_data1;
5494 text_data1.id = 2;
5496 text_data1.SetName("text1");
5497
5498 ui::AXNodeData text_data2;
5499 text_data2.id = 3;
5501 text_data2.SetName("text2");
5502
5503 ui::AXNodeData root_data;
5504 root_data.id = 1;
5506 root_data.child_ids = {2, 3};
5507
5509 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
5510 update.has_tree_data = true;
5511 update.root_id = root_data.id;
5512 update.nodes = {root_data, text_data1, text_data2};
5513
5514 Init(update);
5515
5516 bool is_search_backward;
5517 VARIANT is_hidden_attr_val;
5518 V_VT(&is_hidden_attr_val) = VT_BOOL;
5519 ComPtr<ITextRangeProvider> matched_range_provider;
5520 ComPtr<ITextRangeProvider> document_range_provider;
5521 GetTextRangeProviderFromTextNode(document_range_provider,
5522 GetRootAsAXNode());
5523
5524 // Search forward, look for IsHidden=true.
5525 // Expected: nullptr
5526 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5527 is_search_backward = false;
5528 document_range_provider->FindAttribute(
5529 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5530 &matched_range_provider);
5531 ASSERT_EQ(nullptr, matched_range_provider.Get());
5532
5533 // Search forward, look for IsHidden=false.
5534 // Expected: "text1text2"
5535 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5536 is_search_backward = false;
5537 document_range_provider->FindAttribute(
5538 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5539 &matched_range_provider);
5540 ASSERT_NE(nullptr, matched_range_provider.Get());
5541 CopyOwnerToClone(document_range_provider.Get(),
5542 matched_range_provider.Get());
5543 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1text2");
5544 matched_range_provider.Reset();
5545
5546 // Search backward, look for IsHidden=true.
5547 // Expected: nullptr
5548 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5549 is_search_backward = true;
5550 document_range_provider->FindAttribute(
5551 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5552 &matched_range_provider);
5553 ASSERT_EQ(nullptr, matched_range_provider.Get());
5554
5555 // Search backward, look for IsHidden=false.
5556 // Expected: "text1text2"
5557 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5558 is_search_backward = true;
5559 document_range_provider->FindAttribute(
5560 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5561 &matched_range_provider);
5562 ASSERT_NE(nullptr, matched_range_provider.Get());
5563 CopyOwnerToClone(document_range_provider.Get(),
5564 matched_range_provider.Get());
5565 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1text2");
5566 }
5567
5568 // document - visible
5569 // text1 - visible
5570 // text2 - invisible
5571 // text3 - invisible
5572 // text4 - visible
5573 // text5 - invisible
5574 //
5575 // Search forward, look for IsHidden=true.
5576 // Expected: "text2text3"
5577 // Search forward, look for IsHidden=false.
5578 // Expected: "text1"
5579 // Search backward, look for IsHidden=true.
5580 // Expected: "text5"
5581 // Search backward, look for IsHidden=false.
5582 // Expected: "text4"
5583 {
5584 ui::AXNodeData text_data1;
5585 text_data1.id = 2;
5587 text_data1.SetName("text1");
5588
5589 ui::AXNodeData text_data2;
5590 text_data2.id = 3;
5593 text_data2.SetName("text2");
5594
5595 ui::AXNodeData text_data3;
5596 text_data3.id = 4;
5599 text_data3.SetName("text3");
5600
5601 ui::AXNodeData text_data4;
5602 text_data4.id = 5;
5604 text_data4.SetName("text4");
5605
5606 ui::AXNodeData text_data5;
5607 text_data5.id = 6;
5610 text_data5.SetName("text5");
5611
5612 ui::AXNodeData root_data;
5613 root_data.id = 1;
5615 root_data.child_ids = {2, 3, 4, 5, 6};
5616
5618 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
5619 update.has_tree_data = true;
5620 update.root_id = root_data.id;
5621 update.nodes = {root_data, text_data1, text_data2,
5622 text_data3, text_data4, text_data5};
5623
5624 Init(update);
5625
5626 bool is_search_backward;
5627 VARIANT is_hidden_attr_val;
5628 V_VT(&is_hidden_attr_val) = VT_BOOL;
5629 ComPtr<ITextRangeProvider> matched_range_provider;
5630 ComPtr<ITextRangeProvider> document_range_provider;
5631 GetTextRangeProviderFromTextNode(document_range_provider,
5632 GetRootAsAXNode());
5633
5634 // Search forward, look for IsHidden=true.
5635 // Expected: "text2text3"
5636 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5637 is_search_backward = false;
5638 document_range_provider->FindAttribute(
5639 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5640 &matched_range_provider);
5641 ASSERT_NE(nullptr, matched_range_provider.Get());
5642 CopyOwnerToClone(document_range_provider.Get(),
5643 matched_range_provider.Get());
5644 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text2text3");
5645 matched_range_provider.Reset();
5646
5647 // Search forward, look for IsHidden=false.
5648 // Expected: "text1"
5649 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5650 is_search_backward = false;
5651 document_range_provider->FindAttribute(
5652 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5653 &matched_range_provider);
5654 ASSERT_NE(nullptr, matched_range_provider.Get());
5655 CopyOwnerToClone(document_range_provider.Get(),
5656 matched_range_provider.Get());
5657 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1");
5658 matched_range_provider.Reset();
5659
5660 // Search backward, look for IsHidden=true.
5661 // Expected: "text5"
5662 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5663 is_search_backward = true;
5664 document_range_provider->FindAttribute(
5665 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5666 &matched_range_provider);
5667 ASSERT_NE(nullptr, matched_range_provider.Get());
5668 CopyOwnerToClone(document_range_provider.Get(),
5669 matched_range_provider.Get());
5670 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text5");
5671 matched_range_provider.Reset();
5672
5673 // Search backward, look for IsHidden=false.
5674 // Expected: "text4"
5675 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5676 is_search_backward = true;
5677 document_range_provider->FindAttribute(
5678 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5679 &matched_range_provider);
5680 ASSERT_NE(nullptr, matched_range_provider.Get());
5681 CopyOwnerToClone(document_range_provider.Get(),
5682 matched_range_provider.Get());
5683 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text4");
5684 }
5685
5686 // document - visible
5687 // text1 - visible
5688 // text2 - invisible
5689 // text3 - invisible
5690 // text4 - invisible
5691 // text5 - visible
5692 //
5693 // Search forward, look for IsHidden=true.
5694 // Expected: "text2text3text4"
5695 // Search forward, look for IsHidden=false.
5696 // Expected: "text1"
5697 // Search backward, look for IsHidden=true.
5698 // Expected: "text2text3text4"
5699 // Search backward, look for IsHidden=false.
5700 // Expected: "text5"
5701 {
5702 ui::AXNodeData text_data1;
5703 text_data1.id = 2;
5705 text_data1.SetName("text1");
5706
5707 ui::AXNodeData text_data2;
5708 text_data2.id = 3;
5711 text_data2.SetName("text2");
5712
5713 ui::AXNodeData text_data3;
5714 text_data3.id = 4;
5717 text_data3.SetName("text3");
5718
5719 ui::AXNodeData text_data4;
5720 text_data4.id = 5;
5723 text_data4.SetName("text4");
5724
5725 ui::AXNodeData text_data5;
5726 text_data5.id = 6;
5728 text_data5.SetName("text5");
5729
5730 ui::AXNodeData root_data;
5731 root_data.id = 1;
5733 root_data.child_ids = {2, 3, 4, 5, 6};
5734
5736 update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
5737 update.has_tree_data = true;
5738 update.root_id = root_data.id;
5739 update.nodes = {root_data, text_data1, text_data2,
5740 text_data3, text_data4, text_data5};
5741
5742 Init(update);
5743
5744 bool is_search_backward;
5745 VARIANT is_hidden_attr_val;
5746 V_VT(&is_hidden_attr_val) = VT_BOOL;
5747 ComPtr<ITextRangeProvider> matched_range_provider;
5748 ComPtr<ITextRangeProvider> document_range_provider;
5749 GetTextRangeProviderFromTextNode(document_range_provider,
5750 GetRootAsAXNode());
5751
5752 // Search forward, look for IsHidden=true.
5753 // Expected: "text2text3text4"
5754 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5755 is_search_backward = false;
5756 document_range_provider->FindAttribute(
5757 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5758 &matched_range_provider);
5759 ASSERT_NE(nullptr, matched_range_provider.Get());
5760 CopyOwnerToClone(document_range_provider.Get(),
5761 matched_range_provider.Get());
5762 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text2text3text4");
5763 matched_range_provider.Reset();
5764
5765 // Search forward, look for IsHidden=false.
5766 // Expected: "text1"
5767 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5768 is_search_backward = false;
5769 document_range_provider->FindAttribute(
5770 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5771 &matched_range_provider);
5772 ASSERT_NE(nullptr, matched_range_provider.Get());
5773 CopyOwnerToClone(document_range_provider.Get(),
5774 matched_range_provider.Get());
5775 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1");
5776 matched_range_provider.Reset();
5777
5778 // Search backward, look for IsHidden=true.
5779 // Expected: "text2text3text4"
5780 V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
5781 is_search_backward = true;
5782 document_range_provider->FindAttribute(
5783 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5784 &matched_range_provider);
5785 ASSERT_NE(nullptr, matched_range_provider.Get());
5786 CopyOwnerToClone(document_range_provider.Get(),
5787 matched_range_provider.Get());
5788 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text2text3text4");
5789 matched_range_provider.Reset();
5790
5791 // Search backward, look for IsHidden=false.
5792 // Expected: "text5"
5793 V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
5794 is_search_backward = true;
5795 document_range_provider->FindAttribute(
5796 UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
5797 &matched_range_provider);
5798 ASSERT_NE(nullptr, matched_range_provider.Get());
5799 CopyOwnerToClone(document_range_provider.Get(),
5800 matched_range_provider.Get());
5801 EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text5");
5802 }
5803}
5804
5806 AXNodeData root_ax_node_data;
5807 root_ax_node_data.id = 1;
5808 root_ax_node_data.role = ax::mojom::Role::kRootWebArea;
5809
5810 Init(root_ax_node_data);
5811
5812 ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
5813 QueryInterfaceFromNode<IRawElementProviderSimple>(GetRootAsAXNode());
5814 ASSERT_NE(nullptr, raw_element_provider_simple.Get());
5815
5816 ComPtr<ITextProvider> text_provider;
5817 ASSERT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
5818 UIA_TextPatternId, &text_provider));
5819 ASSERT_NE(nullptr, text_provider.Get());
5820
5821 ComPtr<ITextRangeProvider> text_range_provider;
5822 ASSERT_HRESULT_SUCCEEDED(
5823 text_provider->get_DocumentRange(&text_range_provider));
5824 ASSERT_NE(nullptr, text_range_provider.Get());
5825
5826 // An empty tree.
5827 SetTree(std::make_unique<AXTree>());
5828
5829 BOOL bool_arg = FALSE;
5830 ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
5831 text_range_provider->ScrollIntoView(bool_arg));
5832}
5833
5834// TODO(schectman) Non-empty ignored nodes are not used by Flutter.
5835// https://github.com/flutter/flutter/issues/117012
5837 DISABLED_TestITextRangeProviderIgnoredNodes) {
5838 // Parent Tree
5839 // 1
5840 // |
5841 // 2(i)
5842 // |________________________________
5843 // | | | | | |
5844 // 3 4 5 6 7(i) 8(i)
5845 // | |________
5846 // | | |
5847 // 9(i) 10(i) 11
5848 // | |____
5849 // | | |
5850 // 12 13 14
5851
5852 ui::AXTreeUpdate tree_update;
5854 tree_update.tree_data.tree_id = tree_id;
5855 tree_update.has_tree_data = true;
5856 tree_update.root_id = 1;
5857 tree_update.nodes.resize(14);
5858 tree_update.nodes[0].id = 1;
5859 tree_update.nodes[0].child_ids = {2};
5860 tree_update.nodes[0].role = ax::mojom::Role::kRootWebArea;
5861
5862 tree_update.nodes[1].id = 2;
5863 tree_update.nodes[1].child_ids = {3, 4, 5, 6, 7, 8};
5864 // According to the existing Blink code, editable roots are never ignored.
5865 // However, we can still create this tree structure only for test purposes.
5866 tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
5867 tree_update.nodes[1].AddState(ax::mojom::State::kEditable);
5868 tree_update.nodes[1].AddState(ax::mojom::State::kRichlyEditable);
5869 // tree_update.nodes[1].AddBoolAttribute(
5870 // ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot, true);
5871 tree_update.nodes[1].role = ax::mojom::Role::kGenericContainer;
5872
5873 tree_update.nodes[2].id = 3;
5874 tree_update.nodes[2].role = ax::mojom::Role::kStaticText;
5875 tree_update.nodes[2].SetName(".3.");
5876
5877 tree_update.nodes[3].id = 4;
5878 tree_update.nodes[3].role = ax::mojom::Role::kStaticText;
5879 tree_update.nodes[3].SetName(".4.");
5880
5881 tree_update.nodes[4].id = 5;
5882 tree_update.nodes[4].role = ax::mojom::Role::kStaticText;
5883 tree_update.nodes[4].SetName(".5.");
5884
5885 tree_update.nodes[5].id = 6;
5886 tree_update.nodes[5].role = ax::mojom::Role::kButton;
5887 tree_update.nodes[5].child_ids = {9};
5888
5889 tree_update.nodes[6].id = 7;
5890 tree_update.nodes[6].child_ids = {10, 11};
5891 tree_update.nodes[6].AddState(ax::mojom::State::kIgnored);
5892 tree_update.nodes[6].role = ax::mojom::Role::kGenericContainer;
5893
5894 tree_update.nodes[7].id = 8;
5895 tree_update.nodes[7].AddState(ax::mojom::State::kIgnored);
5896 tree_update.nodes[7].role = ax::mojom::Role::kStaticText;
5897 tree_update.nodes[7].SetName(".8.");
5898
5899 tree_update.nodes[8].id = 9;
5900 tree_update.nodes[8].child_ids = {12};
5901 tree_update.nodes[8].AddState(ax::mojom::State::kIgnored);
5902 tree_update.nodes[8].role = ax::mojom::Role::kGenericContainer;
5903
5904 tree_update.nodes[9].id = 10;
5905 tree_update.nodes[9].child_ids = {13, 14};
5906 tree_update.nodes[9].AddState(ax::mojom::State::kIgnored);
5907 tree_update.nodes[8].role = ax::mojom::Role::kGenericContainer;
5908
5909 tree_update.nodes[10].id = 11;
5910 tree_update.nodes[10].role = ax::mojom::Role::kStaticText;
5911 tree_update.nodes[10].SetName(".11.");
5912
5913 tree_update.nodes[11].id = 12;
5914 tree_update.nodes[11].role = ax::mojom::Role::kStaticText;
5915 tree_update.nodes[11].AddState(ax::mojom::State::kIgnored);
5916 tree_update.nodes[11].SetName(".12.");
5917
5918 tree_update.nodes[12].id = 13;
5919 tree_update.nodes[12].role = ax::mojom::Role::kStaticText;
5920 tree_update.nodes[12].SetName(".13.");
5921
5922 tree_update.nodes[13].id = 14;
5923 tree_update.nodes[13].role = ax::mojom::Role::kStaticText;
5924 tree_update.nodes[13].SetName(".14.");
5925
5926 Init(tree_update);
5927 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 1),
5928 GetNodeFromTree(tree_id, 1));
5929 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 2),
5930 GetNodeFromTree(tree_id, 1));
5931 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 3),
5932 GetNodeFromTree(tree_id, 3));
5933 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 4),
5934 GetNodeFromTree(tree_id, 4));
5935 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 5),
5936 GetNodeFromTree(tree_id, 5));
5937 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 8),
5938 GetNodeFromTree(tree_id, 1));
5939 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 11),
5940 GetNodeFromTree(tree_id, 11));
5941 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 13),
5942 GetNodeFromTree(tree_id, 13));
5943 EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 14),
5944 GetNodeFromTree(tree_id, 14));
5945
5946 // Test movement and GetText()
5947 ComPtr<ITextRangeProvider> text_range_provider;
5948 GetTextRangeProviderFromTextNode(text_range_provider,
5949 GetNodeFromTree(tree_id, 1));
5950
5951 ASSERT_HRESULT_SUCCEEDED(
5952 text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
5953 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L".");
5954
5956 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
5957 /*count*/ 2,
5958 /*expected_text*/ L".3.",
5959 /*expected_count*/ 2);
5960
5962 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
5963 /*count*/ 6,
5964 /*expected_text*/ L".3..4..5.",
5965 /*expected_count*/ 6);
5966
5967 // By design, empty objects, such as the unlabelled button in this case, are
5968 // placed in their own paragraph for easier screen reader navigation.
5970 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
5971 /*count*/ 15,
5972 /*expected_text*/ L".3..4..5.\n\xFFFC\n.13..14..11.",
5973 /*expected_count*/ 15);
5974}
5975
5977 TestNormalizeTextRangePastEndOfDocument) {
5978 ui::AXTreeUpdate initial_state;
5980 initial_state.tree_data.tree_id = tree_id;
5981 initial_state.has_tree_data = true;
5982 initial_state.root_id = 1;
5983 initial_state.nodes.resize(3);
5984 initial_state.nodes[0].id = 1;
5985 initial_state.nodes[0].child_ids = {2};
5986 initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
5987 initial_state.nodes[1].id = 2;
5988 initial_state.nodes[1].child_ids = {3};
5989 initial_state.nodes[1].role = ax::mojom::Role::kStaticText;
5990 initial_state.nodes[1].SetName("aaa");
5991 initial_state.nodes[2].id = 3;
5992 initial_state.nodes[2].role = ax::mojom::Role::kInlineTextBox;
5993 initial_state.nodes[2].SetName("aaa");
5994
5995 Init(initial_state);
5996
5997 ComPtr<ITextRangeProvider> text_range_provider;
5998 GetTextRangeProviderFromTextNode(text_range_provider,
5999 GetNodeFromTree(tree_id, 3));
6000
6001 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"aaa");
6003 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
6004 /*count*/ 2,
6005 /*expected_text*/ L"a",
6006 /*expected_count*/ 2);
6007
6008 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider_win;
6009 text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range_provider_win));
6010
6011 const AXNodePosition::AXPositionInstance start_after_move =
6012 GetStart(text_range_provider_win.Get())->Clone();
6013 const AXNodePosition::AXPositionInstance end_after_move =
6014 GetEnd(text_range_provider_win.Get())->Clone();
6015 EXPECT_LT(*start_after_move, *end_after_move);
6016
6018 update.nodes.resize(2);
6019 update.nodes[0] = initial_state.nodes[1];
6020 update.nodes[0].SetName("aa");
6021 update.nodes[1] = initial_state.nodes[2];
6022 update.nodes[1].SetName("aa");
6023 ASSERT_TRUE(GetTree()->Unserialize(update));
6024
6025 auto* text_range = text_range_provider_win.Get();
6026
6027 auto original_start = GetStart(text_range)->Clone();
6028 auto original_end = GetEnd(text_range)->Clone();
6029
6030 auto normalized_start = GetStart(text_range)->Clone();
6031 auto normalized_end = GetEnd(text_range)->Clone();
6032 NormalizeTextRange(text_range, normalized_start, normalized_end);
6033 // Verify that the original range was not changed by normalization.
6034 ExpectPositionsEqual(original_start, GetStart(text_range));
6035 ExpectPositionsEqual(original_end, GetEnd(text_range));
6036
6037 EXPECT_EQ(*start_after_move, *normalized_start);
6038 EXPECT_EQ(*end_after_move, *normalized_end);
6039}
6040
6042 TestNormalizeTextRangePastEndOfDocumentWithIgnoredNodes) {
6043 ui::AXTreeUpdate initial_state;
6045 initial_state.tree_data.tree_id = tree_id;
6046 initial_state.has_tree_data = true;
6047 initial_state.root_id = 1;
6048 initial_state.nodes.resize(4);
6049 initial_state.nodes[0].id = 1;
6050 initial_state.nodes[0].child_ids = {2};
6051 initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
6052 initial_state.nodes[1].id = 2;
6053 initial_state.nodes[1].child_ids = {3, 4};
6054 initial_state.nodes[1].role = ax::mojom::Role::kStaticText;
6055 initial_state.nodes[1].SetName("aaa");
6056 initial_state.nodes[2].id = 3;
6057 initial_state.nodes[2].role = ax::mojom::Role::kInlineTextBox;
6058 initial_state.nodes[2].SetName("aaa");
6059 initial_state.nodes[3].id = 4;
6060 initial_state.nodes[3].role = ax::mojom::Role::kInlineTextBox;
6061 initial_state.nodes[3].AddState(ax::mojom::State::kIgnored);
6062 initial_state.nodes[3].SetName("ignored");
6063
6064 Init(initial_state);
6065
6066 ComPtr<ITextRangeProvider> text_range_provider;
6067 GetTextRangeProviderFromTextNode(text_range_provider,
6068 GetNodeFromTree(tree_id, 3));
6069
6070 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"aaa");
6072 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
6073 /*count*/ 2,
6074 /*expected_text*/ L"a",
6075 /*expected_count*/ 2);
6076
6077 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider_win;
6078 text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range_provider_win));
6079
6080 const AXNodePosition::AXPositionInstance start_after_move =
6081 GetStart(text_range_provider_win.Get())->Clone();
6082 const AXNodePosition::AXPositionInstance end_after_move =
6083 GetEnd(text_range_provider_win.Get())->Clone();
6084 EXPECT_LT(*start_after_move, *end_after_move);
6085
6087 update.nodes.resize(2);
6088 update.nodes[0] = initial_state.nodes[1];
6089 update.nodes[0].SetName("aa");
6090 update.nodes[1] = initial_state.nodes[2];
6091 update.nodes[1].SetName("aa");
6092 ASSERT_TRUE(GetTree()->Unserialize(update));
6093
6094 auto* text_range = text_range_provider_win.Get();
6095
6096 auto original_start = GetStart(text_range)->Clone();
6097 auto original_end = GetEnd(text_range)->Clone();
6098
6099 auto normalized_start = GetStart(text_range)->Clone();
6100 auto normalized_end = GetEnd(text_range)->Clone();
6101 NormalizeTextRange(text_range, normalized_start, normalized_end);
6102 // Verify that the original range was not changed by normalization.
6103 ExpectPositionsEqual(original_start, GetStart(text_range));
6104 ExpectPositionsEqual(original_end, GetEnd(text_range));
6105
6106 EXPECT_EQ(*start_after_move, *normalized_start);
6107 EXPECT_EQ(*end_after_move, *normalized_end);
6108}
6109
6111 TestNormalizeTextRangeInsideIgnoredNodes) {
6112 ui::AXTreeUpdate initial_state;
6114 initial_state.tree_data.tree_id = tree_id;
6115 initial_state.has_tree_data = true;
6116 initial_state.root_id = 1;
6117 initial_state.nodes.resize(4);
6118 initial_state.nodes[0].id = 1;
6119 initial_state.nodes[0].child_ids = {2, 3, 4};
6120 initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
6121 initial_state.nodes[1].id = 2;
6122 initial_state.nodes[1].role = ax::mojom::Role::kStaticText;
6123 initial_state.nodes[1].SetName("before");
6124 initial_state.nodes[2].id = 3;
6125 initial_state.nodes[2].role = ax::mojom::Role::kStaticText;
6126 initial_state.nodes[2].AddState(ax::mojom::State::kIgnored);
6127 initial_state.nodes[2].SetName("ignored");
6128 initial_state.nodes[3].id = 4;
6129 initial_state.nodes[3].role = ax::mojom::Role::kStaticText;
6130 initial_state.nodes[3].SetName("after");
6131
6132 Init(initial_state);
6133 const AXTree* tree = GetTree();
6134 const AXNode* ignored_node = tree->GetFromId(3);
6135
6136 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
6137 // will build the entire tree.
6138 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
6139 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
6140
6141 // start: TextPosition, anchor_id=3, text_offset=1, annotated_text=i<g>nored
6142 // end : TextPosition, anchor_id=3, text_offset=6, annotated_text=ignore<d>
6143 ComPtr<AXPlatformNodeTextRangeProviderWin> ignored_range_win;
6144 CreateTextRangeProviderWin(
6145 ignored_range_win, owner,
6146 /*start_anchor=*/ignored_node, /*start_offset=*/0,
6147 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
6148 /*end_anchor=*/ignored_node, /*end_offset=*/0,
6149 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
6150
6151 EXPECT_TRUE(GetStart(ignored_range_win.Get())->IsIgnored());
6152 EXPECT_TRUE(GetEnd(ignored_range_win.Get())->IsIgnored());
6153
6154 auto original_start = GetStart(ignored_range_win.Get())->Clone();
6155 auto original_end = GetEnd(ignored_range_win.Get())->Clone();
6156
6157 auto normalized_start = GetStart(ignored_range_win.Get())->Clone();
6158 auto normalized_end = GetEnd(ignored_range_win.Get())->Clone();
6159 NormalizeTextRange(ignored_range_win.Get(), normalized_start, normalized_end);
6160 // Verify that the original range was not changed by normalization.
6161 ExpectPositionsEqual(original_start, GetStart(ignored_range_win.Get()));
6162 ExpectPositionsEqual(original_end, GetEnd(ignored_range_win.Get()));
6163
6164 EXPECT_FALSE(normalized_start->IsIgnored());
6165 EXPECT_FALSE(normalized_end->IsIgnored());
6166 EXPECT_LE(*GetStart(ignored_range_win.Get()), *normalized_start);
6167 EXPECT_LE(*GetEnd(ignored_range_win.Get()), *normalized_end);
6168 EXPECT_LE(*normalized_start, *normalized_end);
6169
6170 // Remove the last node, forcing |NormalizeTextRange| to normalize
6171 // using the opposite AdjustmentBehavior.
6173 update.nodes.resize(1);
6174 update.nodes[0] = initial_state.nodes[0];
6175 update.nodes[0].child_ids = {2, 3};
6176 ASSERT_TRUE(GetTree()->Unserialize(update));
6177
6178 original_start = GetStart(ignored_range_win.Get())->Clone();
6179 original_end = GetEnd(ignored_range_win.Get())->Clone();
6180
6181 normalized_start = GetStart(ignored_range_win.Get())->Clone();
6182 normalized_end = GetEnd(ignored_range_win.Get())->Clone();
6183 NormalizeTextRange(ignored_range_win.Get(), normalized_start, normalized_end);
6184 // Verify that the original range was not changed by normalization.
6185 ExpectPositionsEqual(original_start, GetStart(ignored_range_win.Get()));
6186 ExpectPositionsEqual(original_end, GetEnd(ignored_range_win.Get()));
6187
6188 EXPECT_FALSE(normalized_start->IsIgnored());
6189 EXPECT_FALSE(normalized_end->IsIgnored());
6190 EXPECT_GE(*GetStart(ignored_range_win.Get()), *normalized_start);
6191 EXPECT_GE(*GetEnd(ignored_range_win.Get()), *normalized_end);
6192 EXPECT_LE(*normalized_start, *normalized_end);
6193}
6194
6196 TestNormalizeTextRangeSpanIgnoredNodes) {
6197 ui::AXNodeData root_data;
6198 root_data.id = 1;
6200
6201 ui::AXNodeData before_text;
6202 before_text.id = 2;
6203 before_text.role = ax::mojom::Role::kStaticText;
6204 before_text.SetName("before");
6205 root_data.child_ids.push_back(before_text.id);
6206
6207 ui::AXNodeData ignored_text1;
6208 ignored_text1.id = 3;
6209 ignored_text1.role = ax::mojom::Role::kStaticText;
6210 ignored_text1.AddState(ax::mojom::State::kIgnored);
6211 ignored_text1.SetName("ignored1");
6212 root_data.child_ids.push_back(ignored_text1.id);
6213
6214 ui::AXNodeData ignored_text2;
6215 ignored_text2.id = 4;
6216 ignored_text2.role = ax::mojom::Role::kStaticText;
6217 ignored_text2.AddState(ax::mojom::State::kIgnored);
6218 ignored_text2.SetName("ignored2");
6219 root_data.child_ids.push_back(ignored_text2.id);
6220
6221 ui::AXNodeData after_text;
6222 after_text.id = 5;
6224 after_text.SetName("after");
6225 root_data.child_ids.push_back(after_text.id);
6226
6229 update.root_id = root_data.id;
6230 update.tree_data.tree_id = tree_id;
6231 update.has_tree_data = true;
6232 update.nodes = {root_data, before_text, ignored_text1, ignored_text2,
6233 after_text};
6234
6235 Init(update);
6236 const AXTree* tree = GetTree();
6237
6238 const AXNode* before_text_node = tree->GetFromId(before_text.id);
6239 const AXNode* after_text_node = tree->GetFromId(after_text.id);
6240
6241 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
6242 // will build the entire tree.
6243 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
6244 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
6245
6246 // Original range before NormalizeTextRange()
6247 // |before<>||ignored1||ignored2||<a>fter|
6248 // |-----------------------|
6249 // start: TextPosition, anchor_id=2, text_offset=6, annotated_text=before<>
6250 // end : TextPosition, anchor_id=5, text_offset=0, annotated_text=<a>fter
6251 ComPtr<AXPlatformNodeTextRangeProviderWin> range_span_ignored_nodes;
6252 CreateTextRangeProviderWin(
6253 range_span_ignored_nodes, owner,
6254 /*start_anchor=*/before_text_node, /*start_offset=*/6,
6255 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
6256 /*end_anchor=*/after_text_node, /*end_offset=*/0,
6257 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
6258
6259 auto original_start = GetStart(range_span_ignored_nodes.Get())->Clone();
6260 auto original_end = GetEnd(range_span_ignored_nodes.Get())->Clone();
6261
6262 // Normalized range after NormalizeTextRange()
6263 // |before||ignored1||ignored2||<a>fter|
6264 // |-|
6265 AXNodePosition::AXPositionInstance normalized_start =
6266 GetStart(range_span_ignored_nodes.Get())->Clone();
6267 AXNodePosition::AXPositionInstance normalized_end =
6268 GetEnd(range_span_ignored_nodes.Get())->Clone();
6269 NormalizeTextRange(range_span_ignored_nodes.Get(), normalized_start,
6270 normalized_end);
6271 // Verify that the original range was not changed by normalization.
6272 ExpectPositionsEqual(original_start,
6273 GetStart(range_span_ignored_nodes.Get()));
6274 ExpectPositionsEqual(original_end, GetEnd(range_span_ignored_nodes.Get()));
6275
6276 EXPECT_EQ(*normalized_start, *normalized_end);
6277
6278 EXPECT_TRUE(normalized_start->IsTextPosition());
6279 EXPECT_TRUE(normalized_start->AtStartOfAnchor());
6280 EXPECT_EQ(5, normalized_start->anchor_id());
6281 EXPECT_EQ(0, normalized_start->text_offset());
6282
6283 EXPECT_TRUE(normalized_end->IsTextPosition());
6284 EXPECT_TRUE(normalized_end->AtStartOfAnchor());
6285 EXPECT_EQ(5, normalized_end->anchor_id());
6286 EXPECT_EQ(0, normalized_end->text_offset());
6287}
6288
6289// TODO(schectman) Non-zero text offset in position into an empty node.
6290// Why? https://github.com/flutter/flutter/issues/117012
6292 DISABLED_TestNormalizeTextRangeForceSameAnchorOnDegenerateRange) {
6293 // ++1 kRootWebArea
6294 // ++++2 kGenericContainer
6295 // ++++++3 kImage
6296 // ++++4 kTextField
6297 // ++++++5 kGenericContainer
6298 // ++++++++6 kStaticText
6299 // ++++++++++7 kInlineTextBox
6300 ui::AXNodeData root_1;
6301 ui::AXNodeData generic_container_2;
6302 ui::AXNodeData line_break_3;
6303 ui::AXNodeData text_field_4;
6304 ui::AXNodeData generic_container_5;
6305 ui::AXNodeData static_text_6;
6306 ui::AXNodeData inline_box_7;
6307
6308 root_1.id = 1;
6309 generic_container_2.id = 2;
6310 line_break_3.id = 3;
6311 text_field_4.id = 4;
6312 generic_container_5.id = 5;
6313 static_text_6.id = 6;
6314 inline_box_7.id = 7;
6315
6317 root_1.child_ids = {generic_container_2.id, text_field_4.id};
6318
6319 generic_container_2.role = ax::mojom::Role::kGenericContainer;
6320 generic_container_2.AddBoolAttribute(
6322 generic_container_2.child_ids = {line_break_3.id};
6323
6324 line_break_3.role = ax::mojom::Role::kLineBreak;
6325
6326 text_field_4.role = ax::mojom::Role::kTextField;
6328 text_field_4.child_ids = {generic_container_5.id};
6329 text_field_4.SetValue("3.14");
6330
6331 generic_container_5.role = ax::mojom::Role::kGenericContainer;
6332 generic_container_5.child_ids = {static_text_6.id};
6333
6334 static_text_6.role = ax::mojom::Role::kStaticText;
6335 static_text_6.child_ids = {inline_box_7.id};
6336 static_text_6.SetName("3.14");
6337
6339 inline_box_7.SetName("3.14");
6340
6342 ui::AXTreeData tree_data;
6344 update.tree_data = tree_data;
6345 update.has_tree_data = true;
6346 update.root_id = root_1.id;
6347 update.nodes.push_back(root_1);
6348 update.nodes.push_back(generic_container_2);
6349 update.nodes.push_back(line_break_3);
6350 update.nodes.push_back(text_field_4);
6351 update.nodes.push_back(generic_container_5);
6352 update.nodes.push_back(static_text_6);
6353 update.nodes.push_back(inline_box_7);
6354
6355 Init(update);
6356 const AXTree* tree = GetTree();
6357
6358 const AXNode* line_break_3_node = tree->GetFromId(line_break_3.id);
6359 const AXNode* inline_box_7_node = tree->GetFromId(inline_box_7.id);
6360
6361 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
6362 AXPlatformNodeFromNode(GetNodeFromTree(tree_data.tree_id, 1)));
6363
6364 // start: TextPosition, anchor_id=3, text_offset=1, annotated_text=/xFFFC<>
6365 // end : TextPosition, anchor_id=7, text_offset=0, annotated_text=<p>i
6366 ComPtr<AXPlatformNodeTextRangeProviderWin> range;
6367 CreateTextRangeProviderWin(
6368 range, owner,
6369 /*start_anchor=*/line_break_3_node, /*start_offset=*/1,
6370 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
6371 /*end_anchor=*/inline_box_7_node, /*end_offset=*/0,
6372 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
6373
6374 auto original_start = GetStart(range.Get())->Clone();
6375 auto original_end = GetEnd(range.Get())->Clone();
6376
6377 AXNodePosition::AXPositionInstance normalized_start =
6378 GetStart(range.Get())->Clone();
6379 AXNodePosition::AXPositionInstance normalized_end =
6380 GetEnd(range.Get())->Clone();
6381 NormalizeTextRange(range.Get(), normalized_start, normalized_end);
6382 // Verify that the original range was not changed by normalization.
6383 ExpectPositionsEqual(original_start, GetStart(range.Get()));
6384 ExpectPositionsEqual(original_end, GetEnd(range.Get()));
6385
6386 EXPECT_EQ(*normalized_start, *normalized_start);
6387
6388 EXPECT_TRUE(normalized_start->AtStartOfAnchor());
6389 EXPECT_TRUE(normalized_end->AtStartOfAnchor());
6390 EXPECT_EQ(7, normalized_start->anchor_id());
6391 EXPECT_EQ(7, normalized_end->anchor_id());
6392}
6393
6394// TODO(schectman) https://github.com/flutter/flutter/issues/117012
6395TEST_F(AXPlatformNodeTextRangeProviderTest, DISABLED_TestValidateStartAndEnd) {
6396 // This test updates the tree structure to test a specific edge case -
6397 // CreatePositionAtFormatBoundary when text lies at the beginning and end
6398 // of the AX tree.
6399 AXNodeData root_data;
6400 root_data.id = 1;
6402
6403 AXNodeData text_data;
6404 text_data.id = 2;
6406 text_data.SetName("some text");
6407
6408 AXNodeData more_text_data;
6409 more_text_data.id = 3;
6410 more_text_data.role = ax::mojom::Role::kStaticText;
6411 more_text_data.SetName("more text");
6412
6413 root_data.child_ids = {text_data.id, more_text_data.id};
6414
6417 update.root_id = root_data.id;
6418 update.tree_data.tree_id = tree_id;
6419 update.has_tree_data = true;
6420 update.nodes = {root_data, text_data, more_text_data};
6421
6422 Init(update);
6423 const AXTree* tree = GetTree();
6424
6425 const AXNode* root_node = tree->GetFromId(root_data.id);
6426 const AXNode* more_text_node = tree->GetFromId(more_text_data.id);
6427
6428 // Create a position at MaxTextOffset
6429 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
6430 // will build the entire tree.
6431 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
6432 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
6433
6434 // start: TextPosition, anchor_id=1, text_offset=0, annotated_text=<s>ome text
6435 // end : TextPosition, anchor_id=3, text_offset=9, annotated_text=more text<>
6436 ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider;
6437 CreateTextRangeProviderWin(
6438 text_range_provider, owner,
6439 /*start_anchor=*/root_node, /*start_offset=*/0,
6440 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
6441 /*end_anchor=*/more_text_node, /*end_offset=*/9,
6442 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
6443
6444 // Since the end of the range is at MaxTextOffset, moving it by 1 character
6445 // should have an expected_count of 0.
6447 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
6448 /*count*/ 1,
6449 /*expected_text*/ L"some textmore text",
6450 /*expected_count*/ 0);
6451
6452 // Now make a change to shorten MaxTextOffset. Ensure that this position is
6453 // invalid, then call SnapToMaxTextOffsetIfBeyond and ensure that it is now
6454 // valid.
6455 more_text_data.SetName("ore tex");
6456 AXTreeUpdate test_update;
6457 test_update.nodes = {more_text_data};
6458 ASSERT_TRUE(GetTree()->Unserialize(test_update));
6459
6461 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
6462 /*count*/ 1,
6463 /*expected_text*/ L"some textore tex",
6464 /*expected_count*/ 0);
6465
6466 // Now modify the tree so that start_ is pointing to a node that has been
6467 // removed from the tree.
6468 text_data.SetNameExplicitlyEmpty();
6469 AXTreeUpdate test_update2;
6470 test_update2.nodes = {text_data};
6471 ASSERT_TRUE(GetTree()->Unserialize(test_update2));
6472
6474 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
6475 /*count*/ 1,
6476 /*expected_text*/ L"re tex",
6477 /*expected_count*/ 1);
6478
6479 // Now adjust a node that's not the final node in the tree to point past
6480 // MaxTextOffset. First move the range endpoints so that they're pointing to
6481 // MaxTextOffset on the first node.
6482 text_data.SetName("some text");
6483 AXTreeUpdate test_update3;
6484 test_update3.nodes = {text_data};
6485 ASSERT_TRUE(GetTree()->Unserialize(test_update3));
6486
6488 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
6489 /*count*/ -10,
6490 /*expected_text*/ L"some textore tex",
6491 /*expected_count*/ -10);
6492
6493 // Ensure that we're at MaxTextOffset on the first node by first
6494 // overshooting a negative move...
6496 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
6497 /*count*/ -8,
6498 /*expected_text*/ L"some tex",
6499 /*expected_count*/ -8);
6500
6501 // ...followed by a positive move
6503 text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
6504 /*count*/ 1,
6505 /*expected_text*/ L"some text",
6506 /*expected_count*/ 1);
6507
6508 // Now our range's start_ is pointing to offset 0 on the first node and end_
6509 // is pointing to MaxTextOffset on the first node. Now modify the tree so
6510 // that MaxTextOffset is invalid on the first node and ensure that we can
6511 // still move
6512 text_data.SetName("some tex");
6513 AXTreeUpdate test_update4;
6514 test_update4.nodes = {text_data};
6515 ASSERT_TRUE(GetTree()->Unserialize(test_update4));
6516
6518 text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
6519 /*count*/ 1,
6520 /*expected_text*/ L"ome tex",
6521 /*expected_count*/ 1);
6522}
6523
6525 TestReplaceStartAndEndEndpointNode) {
6526 // This test updates the tree structure to ensure that the text range is still
6527 // valid after a text node gets replaced by another one. This case occurs
6528 // every time an AT's focus moves to a node whose style is affected by focus,
6529 // thus generating a tree update.
6530 //
6531 // ++1 kRootWebArea
6532 // ++++2 kGroup (ignored)
6533 // ++++++3 kStaticText/++++4 kStaticText (replacement node)
6534 // ++++5 kStaticText/++++6 kStaticText (replacement node)
6535 AXNodeData root_1;
6536 AXNodeData group_2;
6537 AXNodeData text_3;
6538 AXNodeData text_4;
6539 AXNodeData text_5;
6540 AXNodeData text_6;
6541
6542 root_1.id = 1;
6543 group_2.id = 2;
6544 text_3.id = 3;
6545 text_4.id = 4;
6546 text_5.id = 5;
6547 text_6.id = 6;
6548
6550 root_1.child_ids = {text_3.id, text_5.id};
6551
6552 group_2.role = ax::mojom::Role::kGroup;
6554 group_2.child_ids = {text_3.id};
6555
6557 text_3.SetName("some text");
6558
6559 // Replacement node of |text_3|.
6561 text_4.SetName("some text");
6562
6564 text_5.SetName("more text");
6565
6566 // Replacement node of |text_5|.
6568 text_6.SetName("more text");
6569
6572 update.root_id = root_1.id;
6573 update.tree_data.tree_id = tree_id;
6574 update.has_tree_data = true;
6575 update.nodes = {root_1, text_3, text_5};
6576
6577 Init(update);
6578 const AXTree* tree = GetTree();
6579
6580 const AXNode* text_3_node = tree->GetFromId(text_3.id);
6581 const AXNode* text_5_node = tree->GetFromId(text_5.id);
6582
6583 // Create a position at MaxTextOffset.
6584 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
6585 // will build the entire tree.
6586 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
6587 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
6588
6589 // start: TextPosition, anchor_id=3, text_offset=0, annotated_text=<s>ome text
6590 // end : TextPosition, anchor_id=5, text_offset=9, annotated_text=more text<>
6591 ComPtr<AXPlatformNodeTextRangeProviderWin> range;
6592 CreateTextRangeProviderWin(
6593 range, owner,
6594 /*start_anchor*/ text_3_node, /*start_offset*/ 0,
6595 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
6596 /*end_anchor*/ text_5_node, /*end_offset*/ 9,
6597 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
6598
6599 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"some textmore text");
6600
6601 // 1. Replace the node on which |start_| is.
6602 {
6603 // Replace node |text_3| with |text_4|.
6604 root_1.child_ids = {text_4.id, text_5.id};
6605 AXTreeUpdate test_update;
6606 test_update.nodes = {root_1, text_4};
6607 ASSERT_TRUE(GetTree()->Unserialize(test_update));
6608
6609 // Replacing that node shouldn't impact the range.
6611 range->GetChildren(children.Receive());
6612 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"some textmore text");
6613
6614 // The |start_| endpoint should have moved to the root, skipping its ignored
6615 // parent.
6616 EXPECT_EQ(root_1.id, GetStart(range.Get())->anchor_id());
6617 EXPECT_EQ(0, GetStart(range.Get())->text_offset());
6618
6619 // The |end_| endpoint should not have moved.
6620 EXPECT_EQ(text_5.id, GetEnd(range.Get())->anchor_id());
6621 EXPECT_EQ(9, GetEnd(range.Get())->text_offset());
6622 }
6623
6624 // 2. Replace the node on which |end_| is.
6625 {
6626 // Replace node |text_4| with |text_5|.
6627 root_1.child_ids = {text_4.id, text_6.id};
6628 AXTreeUpdate test_update;
6629 test_update.nodes = {root_1, text_6};
6630 ASSERT_TRUE(GetTree()->Unserialize(test_update));
6631
6632 // Replacing that node shouldn't impact the range.
6634 range->GetChildren(children.Receive());
6635 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"some textmore text");
6636
6637 // The |start_| endpoint should still be on its parent.
6638 EXPECT_EQ(root_1.id, GetStart(range.Get())->anchor_id());
6639 EXPECT_EQ(0, GetStart(range.Get())->text_offset());
6640
6641 // The |end_| endpoint should have moved to its parent.
6642 EXPECT_EQ(root_1.id, GetEnd(range.Get())->anchor_id());
6643 EXPECT_EQ(18, GetEnd(range.Get())->text_offset());
6644 }
6645
6646 // 3. Replace the node on which |start_| and |end_| is.
6647 {
6648 // start: TextPosition, anchor_id=4, text_offset=0, annotated_text=<s>ome
6649 // end : TextPosition, anchor_id=4, text_offset=4, annotated_text=some<>
6650 const AXNode* text_4_node = tree->GetFromId(text_4.id);
6651 ComPtr<AXPlatformNodeTextRangeProviderWin> range_2;
6652 CreateTextRangeProviderWin(
6653 range_2, owner,
6654 /*start_anchor*/ text_4_node, /*start_offset*/ 0,
6655 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
6656 /*end_anchor*/ text_4_node, /*end_offset*/ 4,
6657 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
6658
6659 EXPECT_UIA_TEXTRANGE_EQ(range_2, /*expected_text*/ L"some");
6660
6661 // Replace node |text_4| with |text_3|.
6662 root_1.child_ids = {text_3.id, text_6.id};
6663 AXTreeUpdate test_update;
6664 test_update.nodes = {root_1, text_3};
6665 ASSERT_TRUE(GetTree()->Unserialize(test_update));
6666
6667 // Replacing that node shouldn't impact the range.
6669 range_2->GetChildren(children.Receive());
6670 EXPECT_UIA_TEXTRANGE_EQ(range_2, /*expected_text*/ L"some");
6671
6672 // The |start_| endpoint should have moved to its parent.
6673 EXPECT_EQ(root_1.id, GetStart(range_2.Get())->anchor_id());
6674 EXPECT_EQ(0, GetStart(range_2.Get())->text_offset());
6675
6676 // The |end_| endpoint should have moved to its parent.
6677 EXPECT_EQ(root_1.id, GetEnd(range_2.Get())->anchor_id());
6678 EXPECT_EQ(4, GetEnd(range_2.Get())->text_offset());
6679 }
6680}
6681
6683 TestDeleteSubtreeThatIncludesEndpoints) {
6684 // This test updates the tree structure to ensure that the text range is still
6685 // valid after a subtree that includes the text range is deleted, resulting in
6686 // a change to the range.
6687 //
6688 // ++1 kRootWebArea
6689 // ++++2 kStaticText "one"
6690 // ++++3 kGenericContainer
6691 // ++++++4 kGenericContainer
6692 // ++++++++5 kStaticText " two"
6693 // ++++++6 kGenericContainer
6694 // ++++++++7 kStaticText " three"
6695 AXNodeData root_1;
6696 AXNodeData text_2;
6697 AXNodeData gc_3;
6698 AXNodeData gc_4;
6699 AXNodeData text_5;
6700 AXNodeData gc_6;
6701 AXNodeData text_7;
6702
6703 root_1.id = 1;
6704 text_2.id = 2;
6705 gc_3.id = 3;
6706 gc_4.id = 4;
6707 text_5.id = 5;
6708 gc_6.id = 6;
6709 text_7.id = 7;
6710
6712 root_1.child_ids = {text_2.id, gc_3.id};
6713
6715 text_2.SetName("one");
6716
6718 gc_3.child_ids = {gc_4.id, gc_6.id};
6719
6721 gc_4.child_ids = {text_5.id};
6722
6724 text_5.SetName(" two");
6725
6727 gc_6.child_ids = {text_7.id};
6728
6730 text_7.SetName(" three");
6731
6734 update.root_id = root_1.id;
6735 update.tree_data.tree_id = tree_id;
6736 update.has_tree_data = true;
6737 update.nodes = {root_1, text_2, gc_3, gc_4, text_5, gc_6, text_7};
6738
6739 Init(update);
6740 const AXTree* tree = GetTree();
6741
6742 const AXNode* text_5_node = tree->GetFromId(text_5.id);
6743 const AXNode* text_7_node = tree->GetFromId(text_7.id);
6744
6745 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
6746 // will build the entire tree.
6747 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
6748 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
6749
6750 // Create a range that spans " two three" located on the leaf nodes.
6751
6752 // start: TextPosition, anchor_id=5, text_offset=0
6753 // end : TextPosition, anchor_id=7, text_offset=6
6754 ComPtr<AXPlatformNodeTextRangeProviderWin> range;
6755 CreateTextRangeProviderWin(
6756 range, owner,
6757 /*start_anchor*/ text_5_node, /*start_offset*/ 0,
6758 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
6759 /*end_anchor*/ text_7_node, /*end_offset*/ 6,
6760 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
6761
6762 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L" two three");
6763
6764 // Delete |gc_3|, which will delete the entire subtree where both of our
6765 // endpoints are.
6766 AXTreeUpdate test_update;
6767 root_1.child_ids = {text_2.id};
6768 test_update.nodes = {root_1};
6769 ASSERT_TRUE(GetTree()->Unserialize(test_update));
6770
6771 // The text range should now be a degenerate range positioned at the end of
6772 // root, the parent of |gc_3|, since |gc_3| has been deleted.
6773 EXPECT_EQ(root_1.id, GetStart(range.Get())->anchor_id());
6774 EXPECT_EQ(3, GetStart(range.Get())->text_offset());
6775
6776 EXPECT_EQ(root_1.id, GetEnd(range.Get())->anchor_id());
6777 EXPECT_EQ(3, GetEnd(range.Get())->text_offset());
6778}
6779
6780// TODO(schectman) https://github.com/flutter/flutter/issues/117012
6782 DISABLED_TestDeleteSubtreeWithIgnoredAncestors) {
6783 // This test updates the tree structure to ensure that the text range doesn't
6784 // crash and points to null positions after a subtree that includes the text
6785 // range is deleted and all ancestors are ignored.
6786 //
6787 // ++1 kRootWebArea ignored
6788 // ++++2 kStaticText "one"
6789 // ++++3 kGenericContainer ignored
6790 // ++++++4 kGenericContainer
6791 // ++++++++5 kGenericContainer
6792 // ++++++++++6 kStaticText " two"
6793 // ++++++++7 kGenericContainer ignored
6794 // ++++++++++8 kStaticText " ignored" ignored
6795 // ++++++++9 kGenericContainer
6796 // ++++++++++10 kStaticText " three"
6797 // ++++11 kGenericContainer
6798 // ++++++12 kStaticText "four"
6799 AXNodeData root_1;
6800 AXNodeData text_2;
6801 AXNodeData gc_3;
6802 AXNodeData gc_4;
6803 AXNodeData gc_5;
6804 AXNodeData text_6;
6805 AXNodeData gc_7;
6806 AXNodeData text_8;
6807 AXNodeData gc_9;
6808 AXNodeData text_10;
6809 AXNodeData gc_11;
6810 AXNodeData text_12;
6811
6812 root_1.id = 1;
6813 text_2.id = 2;
6814 gc_3.id = 3;
6815 gc_4.id = 4;
6816 gc_5.id = 5;
6817 text_6.id = 6;
6818 gc_7.id = 7;
6819 text_8.id = 8;
6820 gc_9.id = 9;
6821 text_10.id = 10;
6822 gc_11.id = 11;
6823 text_12.id = 12;
6824
6826 root_1.child_ids = {text_2.id, gc_3.id, gc_11.id};
6828
6830 text_2.SetName("one");
6831
6834 gc_3.child_ids = {gc_4.id};
6835
6837 gc_4.child_ids = {gc_5.id, gc_7.id, gc_9.id};
6838
6840 gc_5.child_ids = {text_6.id};
6841
6843 text_6.SetName(" two");
6844
6847 gc_7.child_ids = {text_8.id};
6848
6851 text_8.SetName(" ignored");
6852
6854 gc_9.child_ids = {text_10.id};
6855
6857 text_10.SetName(" three");
6858
6860 gc_11.child_ids = {text_12.id};
6861
6863 text_12.SetName("four");
6864
6867 update.root_id = root_1.id;
6868 update.tree_data.tree_id = tree_id;
6869 update.has_tree_data = true;
6870 update.nodes = {root_1, text_2, gc_3, gc_4, gc_5, text_6,
6871 gc_7, text_8, gc_9, text_10, gc_11, text_12};
6872
6873 Init(update);
6874 const AXTree* tree = GetTree();
6875
6876 const AXNode* text_6_node = tree->GetFromId(text_6.id);
6877 const AXNode* text_10_node = tree->GetFromId(text_10.id);
6878
6879 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
6880 // will build the entire tree.
6881 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
6882 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
6883
6884 // Create a range that spans " two three" located on the leaf nodes.
6885
6886 // start: TextPosition, anchor_id=5, text_offset=0
6887 // end : TextPosition, anchor_id=7, text_offset=6
6888 ComPtr<AXPlatformNodeTextRangeProviderWin> range;
6889 CreateTextRangeProviderWin(
6890 range, owner,
6891 /*start_anchor*/ text_6_node, /*start_offset*/ 2,
6892 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
6893 /*end_anchor*/ text_10_node, /*end_offset*/ 6,
6894 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
6895
6896 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"wo three");
6897
6898 // Delete |gc_3|, which will delete the entire subtree where both of our
6899 // endpoints are.
6900 AXTreeUpdate test_update;
6901 gc_3.child_ids = {};
6902 test_update.nodes = {gc_3};
6903 ASSERT_TRUE(GetTree()->Unserialize(test_update));
6904
6905 // There was no unignored position in which to place the start and end - they
6906 // should now be null positions.
6907 EXPECT_TRUE(GetStart(range.Get())->IsNullPosition());
6908 EXPECT_TRUE(GetEnd(range.Get())->IsNullPosition());
6909}
6910
6911// TODO(schectman) https://github.com/flutter/flutter/issues/117012
6913 DISABLED_TestDeleteSubtreeThatIncludesEndpointsNormalizeMoves) {
6914 // This test updates the tree structure to ensure that the text range is still
6915 // valid after a subtree that includes the text range is deleted, resulting in
6916 // a change to the range that is adjusted forwards due to an ignored node.
6917 //
6918 // ++1 kRootWebArea
6919 // ++++2 kStaticText "one"
6920 // ++++3 kGenericContainer ignored
6921 // ++++++4 kGenericContainer
6922 // ++++++++5 kGenericContainer
6923 // ++++++++++6 kStaticText " two"
6924 // ++++++++7 kGenericContainer
6925 // ++++++++++8 kStaticText " three"
6926 // ++++++++9 kGenericContainer ignored
6927 // ++++++++++10 kStaticText " ignored" ignored
6928 // ++++11 kGenericContainer
6929 // ++++++12 kStaticText "four"
6930 AXNodeData root_1;
6931 AXNodeData text_2;
6932 AXNodeData gc_3;
6933 AXNodeData gc_4;
6934 AXNodeData gc_5;
6935 AXNodeData text_6;
6936 AXNodeData gc_7;
6937 AXNodeData text_8;
6938 AXNodeData gc_9;
6939 AXNodeData text_10;
6940 AXNodeData gc_11;
6941 AXNodeData text_12;
6942
6943 root_1.id = 1;
6944 text_2.id = 2;
6945 gc_3.id = 3;
6946 gc_4.id = 4;
6947 gc_5.id = 5;
6948 text_6.id = 6;
6949 gc_7.id = 7;
6950 text_8.id = 8;
6951 gc_9.id = 9;
6952 text_10.id = 10;
6953 gc_11.id = 11;
6954 text_12.id = 12;
6955
6957 root_1.child_ids = {text_2.id, gc_3.id, gc_11.id};
6958
6960 text_2.SetName("one");
6961
6964 gc_3.child_ids = {gc_4.id};
6965
6967 gc_4.child_ids = {gc_5.id, gc_7.id, gc_9.id};
6968
6970 gc_5.child_ids = {text_6.id};
6971
6973 text_6.SetName(" two");
6974
6976 gc_7.child_ids = {text_8.id};
6977
6979 text_8.SetName(" three");
6980
6983 gc_9.child_ids = {text_10.id};
6984
6987 text_10.SetName(" ignored");
6988
6990 gc_11.child_ids = {text_12.id};
6991
6993 text_12.SetName("four");
6994
6997 update.root_id = root_1.id;
6998 update.tree_data.tree_id = tree_id;
6999 update.has_tree_data = true;
7000 update.nodes = {root_1, text_2, gc_3, gc_4, gc_5, text_6,
7001 gc_7, text_8, gc_9, text_10, gc_11, text_12};
7002
7003 Init(update);
7004 const AXTree* tree = GetTree();
7005
7006 const AXNode* text_6_node = tree->GetFromId(text_6.id);
7007 const AXNode* text_8_node = tree->GetFromId(text_8.id);
7008
7009 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
7010 // will build the entire tree.
7011 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
7012 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
7013
7014 // Create a range that spans " two three" located on the leaf nodes.
7015
7016 // start: TextPosition, anchor_id=5, text_offset=0
7017 // end : TextPosition, anchor_id=7, text_offset=6
7018 ComPtr<AXPlatformNodeTextRangeProviderWin> range;
7019 CreateTextRangeProviderWin(
7020 range, owner,
7021 /*start_anchor*/ text_6_node, /*start_offset*/ 2,
7022 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
7023 /*end_anchor*/ text_8_node, /*end_offset*/ 6,
7024 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
7025
7026 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"wo three");
7027
7028 // Delete |gc_3|, which will delete the entire subtree where both of our
7029 // endpoints are.
7030 AXTreeUpdate test_update;
7031 gc_3.child_ids = {};
7032 test_update.nodes = {gc_3};
7033 ASSERT_TRUE(GetTree()->Unserialize(test_update));
7034
7035 // The text range should now be a degenerate range positioned at the end of
7036 // root, the parent of |gc_3|, since |gc_3| has been deleted.
7037 EXPECT_EQ(text_12.id, GetStart(range.Get())->anchor_id());
7038 EXPECT_EQ(0, GetStart(range.Get())->text_offset());
7039
7040 EXPECT_EQ(text_12.id, GetEnd(range.Get())->anchor_id());
7041 EXPECT_EQ(0, GetEnd(range.Get())->text_offset());
7042}
7043
7045 TestDeleteTreePositionPreviousSibling) {
7046 // This test creates a degenerate range with endpoints pointing after the last
7047 // child of the 2 generic container. It then deletes a previous sibling and
7048 // ensures that we don't crash with an out of bounds index that causes null
7049 // child positions to be created.
7050 //
7051 // ++1 kRootWebArea
7052 // ++++2 kGenericContainer
7053 // ++++++3 kHeading
7054 // ++++++++4 kStaticText
7055 // ++++++++++5 kInlineTextBox
7056 // ++++++6 kGenericContainer
7057 // ++++++7 kButton
7058 ui::AXNodeData root_1;
7059 ui::AXNodeData generic_container_2;
7060 ui::AXNodeData heading_3;
7061 ui::AXNodeData static_text_4;
7062 ui::AXNodeData inline_box_5;
7063 ui::AXNodeData generic_container_6;
7064 ui::AXNodeData button_7;
7065
7066 root_1.id = 1;
7067 generic_container_2.id = 2;
7068 heading_3.id = 3;
7069 static_text_4.id = 4;
7070 inline_box_5.id = 5;
7071 generic_container_6.id = 6;
7072 button_7.id = 7;
7073
7075 root_1.child_ids = {generic_container_2.id};
7076
7077 generic_container_2.role = ax::mojom::Role::kGenericContainer;
7078 generic_container_2.child_ids = {heading_3.id, generic_container_6.id,
7079 button_7.id};
7080
7081 heading_3.role = ax::mojom::Role::kHeading;
7082 heading_3.child_ids = {static_text_4.id};
7083
7084 static_text_4.role = ax::mojom::Role::kStaticText;
7085 static_text_4.child_ids = {inline_box_5.id};
7086 static_text_4.SetName("3.14");
7087
7089 inline_box_5.SetName("3.14");
7090
7091 generic_container_6.role = ax::mojom::Role::kGenericContainer;
7092 generic_container_6.AddBoolAttribute(
7094
7095 button_7.role = ax::mojom::Role::kButton;
7096
7098 ui::AXTreeData tree_data;
7100 update.tree_data = tree_data;
7101 update.has_tree_data = true;
7102 update.root_id = root_1.id;
7103 update.nodes = {root_1, generic_container_2, heading_3, static_text_4,
7104 inline_box_5, generic_container_6, button_7};
7105
7106 Init(update);
7107 AXTree* tree = GetTree();
7108
7109 AXNode* root_node = GetRootAsAXNode();
7112 generic_container_2.id,
7113 /*child_index*/ 3);
7114 AXNodePosition::AXPositionInstance range_end = range_start->Clone();
7115
7116 AXPlatformNodeWin* owner =
7117 static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(root_node));
7118 ComPtr<ITextRangeProvider> text_range_provider =
7119 AXPlatformNodeTextRangeProviderWin::CreateTextRangeProviderForTesting(
7120 owner, std::move(range_start), std::move(range_end));
7121 EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
7122
7123 generic_container_2.child_ids = {heading_3.id, button_7.id};
7124 AXTreeUpdate test_update;
7125 test_update.nodes = {generic_container_2};
7126 ASSERT_TRUE(tree->Unserialize(test_update));
7127
7128 root_1.child_ids = {};
7129 test_update.nodes = {root_1};
7130 ASSERT_TRUE(tree->Unserialize(test_update));
7131}
7132
7134 TestReplaceStartAndEndEndpointRepeatRemoval) {
7135 // This test updates the tree structure to ensure that the text range is still
7136 // valid after text nodes get removed repeatedly.
7137 //
7138 // ++1 kRootWebArea
7139 // ++++2 kStaticText
7140 // ++++3 kGroup (ignored)
7141 // ++++++4 kStaticText
7142 // ++++5 kStaticText
7143 AXNodeData root_1;
7144 AXNodeData text_2;
7145 AXNodeData group_3;
7146 AXNodeData text_4;
7147 AXNodeData text_5;
7148
7149 root_1.id = 1;
7150 text_2.id = 2;
7151 group_3.id = 3;
7152 text_4.id = 4;
7153 text_5.id = 5;
7154
7156 root_1.child_ids = {text_2.id, group_3.id, text_5.id};
7157
7159 text_2.SetName("text 2");
7160
7161 group_3.role = ax::mojom::Role::kGroup;
7163 group_3.child_ids = {text_4.id};
7164
7166 text_4.SetName("text 4");
7167
7169 text_5.SetName("text 5");
7170
7173 update.root_id = root_1.id;
7174 update.tree_data.tree_id = tree_id;
7175 update.has_tree_data = true;
7176 update.nodes = {root_1, text_2, group_3, text_4, text_5};
7177
7178 Init(update);
7179 const AXTree* tree = GetTree();
7180
7181 const AXNode* text_2_node = tree->GetFromId(text_2.id);
7182 const AXNode* text_4_node = tree->GetFromId(text_4.id);
7183
7184 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
7185 // will build the entire tree.
7186 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
7187 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
7188
7189 ComPtr<AXPlatformNodeTextRangeProviderWin> range;
7190 CreateTextRangeProviderWin(
7191 range, owner,
7192 /*start_anchor*/ text_2_node, /*start_offset*/ 0,
7193 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
7194 /*end_anchor*/ text_4_node, /*end_offset*/ 0,
7195 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
7196
7197 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"text 2");
7198
7199 // start: TextPosition, anchor_id=2, text_offset=0, annotated_text=<t>ext2
7200 // end : TextPosition, anchor_id=4, text_offset=0, annotated_text=<>text4
7201 // 1. Remove |text_4| which |end_| is anchored on.
7202 {
7203 // Remove node |text_4|.
7204 group_3.child_ids = {};
7205 AXTreeUpdate test_update;
7206 test_update.nodes = {root_1, group_3};
7207 ASSERT_TRUE(GetTree()->Unserialize(test_update));
7208
7209 // Replacing that node should not impact the range.
7210 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"text 2");
7211 }
7212
7213 // start: TextPosition, anchor_id=2, text_offset=0, annotated_text=<>text2
7214 // end : TextPosition, anchor_id=2, text_offset=5, annotated_text=text2<>
7215 // 2. Remove |text_2|, which both |start_| and |end_| are anchored to and
7216 // replace with |text_5|.
7217 {
7218 root_1.child_ids = {group_3.id, text_5.id};
7219 AXTreeUpdate test_update;
7220 test_update.nodes = {root_1, group_3};
7221 ASSERT_TRUE(GetTree()->Unserialize(test_update));
7222
7223 // Removing that node should adjust the range to the |text_5|, as it took
7224 // |text_2|'s position.
7225 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"text 5");
7226 }
7227
7228 // start: TextPosition, anchor_id=5, text_offset=0, annotated_text=<>text5
7229 // end : TextPosition, anchor_id=5, text_offset=5, annotated_text=text5<>
7230 // 3. Remove |text_5|, which both |start_| and |end_| are pointing to.
7231 {
7232 root_1.child_ids = {group_3.id};
7233 AXTreeUpdate test_update;
7234 test_update.nodes = {root_1, group_3};
7235 ASSERT_TRUE(GetTree()->Unserialize(test_update));
7236
7237 // Removing the last text node should leave a degenerate range.
7238 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"");
7239 }
7240}
7241
7242TEST_F(AXPlatformNodeTextRangeProviderTest, CaretAtEndOfTextFieldReadOnly) {
7243 // This test places a degenerate range at end of text field, and it should not
7244 // normalize to other positions, so we should expect the
7245 // 'UIA_IsReadOnlyAttributeId' attribute queried at this position to return
7246 // false.
7247 // ++1 kRootWebArea
7248 // ++++2 kTextField editable value="hello"
7249 // ++++++3 kGenericContainer editable isLineBreakingObject=true
7250 // ++++++++4 kStaticText editable name="hello"
7251 // ++++++++++5 kInlineTextBox editable name="hello"
7252 // ++++6 kStaticText name="abc"
7253 // ++++++7 kInlineTextBox name="abc"
7254 AXNodeData root_1;
7255 AXNodeData text_field_2;
7256 AXNodeData generic_container_3;
7257 AXNodeData static_text_4;
7258 AXNodeData inline_text_5;
7259 AXNodeData static_text_6;
7260 AXNodeData inline_text_7;
7261
7262 root_1.id = 1;
7263 text_field_2.id = 2;
7264 generic_container_3.id = 3;
7265 static_text_4.id = 4;
7266 inline_text_5.id = 5;
7267 static_text_6.id = 6;
7268 inline_text_7.id = 7;
7269
7271 root_1.child_ids = {text_field_2.id, static_text_6.id};
7272
7273 text_field_2.role = ax::mojom::Role::kTextField;
7275 text_field_2.SetValue("hello");
7276 text_field_2.child_ids = {generic_container_3.id};
7277
7278 generic_container_3.role = ax::mojom::Role::kGenericContainer;
7279 generic_container_3.AddState(ax::mojom::State::kEditable);
7280 generic_container_3.AddBoolAttribute(
7282 generic_container_3.child_ids = {static_text_4.id};
7283
7284 static_text_4.role = ax::mojom::Role::kStaticText;
7285 static_text_4.SetName("hello");
7287 static_text_4.child_ids = {inline_text_5.id};
7288
7289 inline_text_5.role = ax::mojom::Role::kInlineTextBox;
7290 inline_text_5.SetName("hello");
7292
7293 static_text_6.role = ax::mojom::Role::kStaticText;
7294 static_text_6.SetName("abc");
7295 static_text_6.child_ids = {inline_text_7.id};
7296
7297 inline_text_7.role = ax::mojom::Role::kInlineTextBox;
7298 inline_text_7.SetName("abc");
7299
7302 update.root_id = root_1.id;
7303 update.tree_data.tree_id = tree_id;
7304 update.has_tree_data = true;
7305 update.nodes = {root_1, text_field_2, generic_container_3,
7306 static_text_4, inline_text_5, static_text_6,
7307 inline_text_7};
7308
7309 Init(update);
7310 const AXTree* tree = GetTree();
7311 const AXNode* inline_text_5_node = tree->GetFromId(inline_text_5.id);
7312
7313 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
7314 // will build the entire tree.
7315 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
7316 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
7317
7318 ComPtr<AXPlatformNodeTextRangeProviderWin> range;
7319 base::win::ScopedVariant expected_variant;
7320
7321 CreateTextRangeProviderWin(
7322 range, owner,
7323 /*start_anchor*/ inline_text_5_node, /*start_offset*/ 3,
7324 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
7325 /*end_anchor*/ inline_text_5_node, /*end_offset*/ 4,
7326 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
7327
7328 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"l");
7329
7330 expected_variant.Set(false);
7331 EXPECT_UIA_TEXTATTRIBUTE_EQ(range, UIA_IsReadOnlyAttributeId,
7332 expected_variant);
7333
7334 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(range, TextPatternRangeEndpoint_Start,
7335 TextUnit_Character,
7336 /*count*/ 1,
7337 /*expected_text*/ L"",
7338 /*expected_count*/ 1);
7339 expected_variant.Set(false);
7340 EXPECT_UIA_TEXTATTRIBUTE_EQ(range, UIA_IsReadOnlyAttributeId,
7341 expected_variant);
7342
7343 EXPECT_UIA_MOVE(range, TextUnit_Character,
7344 /*count*/ 1,
7345 /*expected_text*/
7346 L"",
7347 /*expected_count*/ 1);
7348 expected_variant.Set(false);
7349 EXPECT_UIA_TEXTATTRIBUTE_EQ(range, UIA_IsReadOnlyAttributeId,
7350 expected_variant);
7351 const AXNodePosition::AXPositionInstance& start = GetStart(range.Get());
7352 const AXNodePosition::AXPositionInstance& end = GetEnd(range.Get());
7353 EXPECT_TRUE(start->AtEndOfAnchor());
7354 EXPECT_EQ(5, start->anchor_id());
7355 EXPECT_EQ(5, start->text_offset());
7356
7357 EXPECT_TRUE(end->AtEndOfAnchor());
7358 EXPECT_EQ(5, end->anchor_id());
7359 EXPECT_EQ(5, end->text_offset());
7360}
7361
7362// TODO(schectman) Not all attributes treated as in Chromium.
7363// https://github.com/flutter/flutter/issues/117012
7365 DISABLED_GeneratedNewlineReturnsCommonAnchorReadonly) {
7366 // This test places a range that starts at the end of a paragraph and
7367 // ends at the beginning of the next paragraph. The range only contains the
7368 // generated newline character. The readonly attribute value returned should
7369 // be the one of the common anchor of the start and end endpoint.
7370
7371 // ++1 kRootWebArea
7372 // ++++2 kGenericContainer
7373 // ++++++3 kImage
7374 // ++++++4 kTextField editable
7375 // ++++5 kGenericContainer editable
7376 // ++++++6 kImage
7377 // ++++++7 kTextField editable
7378 // ++++8 kGenericContainer
7379 // ++++++9 kTextField editable
7380 // ++++++10 kTextField editable
7381 AXNodeData root_1;
7382 AXNodeData generic_container_2;
7383 AXNodeData image_3;
7384 AXNodeData text_field_4;
7385 AXNodeData generic_container_5;
7386 AXNodeData image_6;
7387 AXNodeData text_field_7;
7388 AXNodeData generic_container_8;
7389 AXNodeData text_field_9;
7390 AXNodeData text_field_10;
7391
7392 root_1.id = 1;
7393 generic_container_2.id = 2;
7394 image_3.id = 3;
7395 text_field_4.id = 4;
7396 generic_container_5.id = 5;
7397 image_6.id = 6;
7398 text_field_7.id = 7;
7399 generic_container_8.id = 8;
7400 text_field_9.id = 9;
7401 text_field_10.id = 10;
7402
7404 root_1.child_ids = {generic_container_2.id, generic_container_5.id,
7405 generic_container_8.id};
7406
7407 generic_container_2.role = ax::mojom::Role::kGenericContainer;
7408 generic_container_2.child_ids = {image_3.id, text_field_4.id};
7409
7410 image_3.role = ax::mojom::Role::kImage;
7412 true);
7413
7414 text_field_4.role = ax::mojom::Role::kTextField;
7416
7417 generic_container_5.role = ax::mojom::Role::kGenericContainer;
7418 generic_container_5.AddState(ax::mojom::State::kEditable);
7419 generic_container_5.child_ids = {image_6.id, text_field_7.id};
7420
7421 image_6.role = ax::mojom::Role::kImage;
7423 true);
7424
7425 text_field_7.role = ax::mojom::Role::kTextField;
7427
7428 generic_container_8.role = ax::mojom::Role::kGenericContainer;
7429 generic_container_8.child_ids = {text_field_9.id, text_field_10.id};
7430
7431 text_field_9.role = ax::mojom::Role::kTextField;
7434 true);
7435
7436 text_field_10.role = ax::mojom::Role::kTextField;
7438
7441 update.root_id = root_1.id;
7442 update.tree_data.tree_id = tree_id;
7443 update.has_tree_data = true;
7444 update.nodes = {root_1, generic_container_2, image_3,
7445 text_field_4, generic_container_5, image_6,
7446 text_field_7, generic_container_8, text_field_9,
7447 text_field_10};
7448
7449 Init(update);
7450 const AXTree* tree = GetTree();
7451
7452 const AXNode* image_3_node = tree->GetFromId(image_3.id);
7453 const AXNode* image_6_node = tree->GetFromId(image_6.id);
7454 const AXNode* text_field_4_node = tree->GetFromId(text_field_4.id);
7455 const AXNode* text_field_7_node = tree->GetFromId(text_field_7.id);
7456 const AXNode* text_field_9_node = tree->GetFromId(text_field_9.id);
7457 const AXNode* text_field_10_node = tree->GetFromId(text_field_10.id);
7458
7459 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
7460 // will build the entire tree.
7461 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
7462 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
7463
7464 base::win::ScopedVariant expected_variant;
7465
7466 ComPtr<AXPlatformNodeTextRangeProviderWin> range_1;
7467 CreateTextRangeProviderWin(
7468 range_1, owner,
7469 /*start_anchor*/ image_3_node, /*start_offset*/ 1,
7470 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
7471 /*end_anchor*/ text_field_4_node, /*end_offset*/ 0,
7472 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
7473
7474 EXPECT_UIA_TEXTRANGE_EQ(range_1, /*expected_text*/ L"");
7475
7476 expected_variant.Set(true);
7477 EXPECT_UIA_TEXTATTRIBUTE_EQ(range_1, UIA_IsReadOnlyAttributeId,
7478 expected_variant);
7479 expected_variant.Reset();
7480
7481 ComPtr<AXPlatformNodeTextRangeProviderWin> range_2;
7482 CreateTextRangeProviderWin(
7483 range_2, owner,
7484 /*start_anchor*/ image_6_node, /*start_offset*/ 1,
7485 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
7486 /*end_anchor*/ text_field_7_node, /*end_offset*/ 0,
7487 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
7488
7489 EXPECT_UIA_TEXTRANGE_EQ(range_2, /*expected_text*/ L"");
7490
7491 expected_variant.Set(false);
7492 EXPECT_UIA_TEXTATTRIBUTE_EQ(range_2, UIA_IsReadOnlyAttributeId,
7493 expected_variant);
7494 expected_variant.Reset();
7495
7496 // This is testing a corner case when the range spans two text fields
7497 // separated by a paragraph boundary. This case used to not work because we
7498 // were relying on NormalizeTextRange to handle generated newlines and
7499 // normalization doesn't work when the range spans text fields.
7500 ComPtr<AXPlatformNodeTextRangeProviderWin> range_3;
7501 CreateTextRangeProviderWin(
7502 range_3, owner,
7503 /*start_anchor*/ text_field_9_node, /*start_offset*/ 1,
7504 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
7505 /*end_anchor*/ text_field_10_node, /*end_offset*/ 0,
7506 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
7507
7508 EXPECT_UIA_TEXTRANGE_EQ(range_3, /*expected_text*/ L"");
7509
7510 expected_variant.Set(true);
7511 EXPECT_UIA_TEXTATTRIBUTE_EQ(range_3, UIA_IsReadOnlyAttributeId,
7512 expected_variant);
7513 expected_variant.Reset();
7514}
7515
7516// TODO(schectman) https://github.com/flutter/flutter/issues/117012
7518 DISABLED_MoveEndpointToLastIgnoredForTextNavigationNode) {
7519 // This test moves the end endpoint of a range by one paragraph unit forward
7520 // to the last node of the tree. That last node happens to be a node that is
7521 // ignored for text navigation, but since it's the last node in the tree, it
7522 // should successfully move the endpoint to that node and keep the units_moved
7523 // value in sync.
7524 // ++1 kRootWebArea
7525 // ++++2 kStaticText name="abc"
7526 // ++++++3 kInlineTextBox name="abc"
7527 // ++++4 kGenericContainer
7528 AXNodeData root_1;
7529 AXNodeData static_text_2;
7530 AXNodeData inline_text_3;
7531 AXNodeData generic_container_4;
7532
7533 root_1.id = 1;
7534 static_text_2.id = 2;
7535 inline_text_3.id = 3;
7536 generic_container_4.id = 4;
7537
7539 root_1.child_ids = {static_text_2.id, generic_container_4.id};
7540
7541 static_text_2.role = ax::mojom::Role::kStaticText;
7542 static_text_2.SetName("abc");
7543 static_text_2.child_ids = {inline_text_3.id};
7544
7545 inline_text_3.role = ax::mojom::Role::kInlineTextBox;
7546 inline_text_3.SetName("abc");
7547
7548 generic_container_4.role = ax::mojom::Role::kGenericContainer;
7549
7552 update.root_id = root_1.id;
7553 update.tree_data.tree_id = tree_id;
7554 update.has_tree_data = true;
7555 update.nodes = {root_1, static_text_2, inline_text_3, generic_container_4};
7556
7557 Init(update);
7558 const AXTree* tree = GetTree();
7559 const AXNode* inline_text_3_node = tree->GetFromId(inline_text_3.id);
7560
7561 // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
7562 // will build the entire tree.
7563 AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
7564 AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
7565
7566 ComPtr<AXPlatformNodeTextRangeProviderWin> range;
7567 base::win::ScopedVariant expected_variant;
7568
7569 CreateTextRangeProviderWin(
7570 range, owner,
7571 /*start_anchor*/ inline_text_3_node, /*start_offset*/ 0,
7572 /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
7573 /*end_anchor*/ inline_text_3_node, /*end_offset*/ 3,
7574 /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
7575
7576 EXPECT_UIA_TEXTRANGE_EQ(range, /*expected_text*/ L"abc");
7577
7578 EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(range, TextPatternRangeEndpoint_End,
7579 TextUnit_Paragraph,
7580 /*count*/ 1,
7581 /*expected_text*/ L"abc\xFFFC",
7582 /*expected_count*/ 1);
7583}
7584
7585} // namespace ui
int count
Definition: FontMgrTest.cpp:50
static int find_string(const TArray< SkString > &strings, const char ext[])
#define EXPECT_UIA_FIND_TEXT(text_range_provider, search_term, ignore_case, owner)
#define EXPECT_ENCLOSING_ELEMENT(ax_node_given, ax_node_expected)
#define EXPECT_UIA_ELEMENTNOTAVAILABLE(expr)
#define EXPECT_UIA_TEXTATTRIBUTE_MIXED(provider, attribute)
#define EXPECT_UIA_MOVE(text_range_provider, unit, count, expected_text, expected_count)
#define EXPECT_UIA_TEXTATTRIBUTE_EQ(provider, attribute, variant)
#define EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, endpoint, unit, count, expected_text, expected_count)
#define EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(array, element_test_property_id, expected_property_values)
#define EXPECT_UIA_FIND_TEXT_NO_MATCH(text_range_provider, search_term, ignore_case, owner)
#define EXPECT_UIA_TEXTRANGE_EQ(provider, expected_content)
#define EXPECT_UIA_SAFEARRAY_EQ(safearray, expected_property_values)
#define EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(provider, attribute)
void Reset(BSTR bstr=nullptr)
Definition: scoped_bstr.cc:42
BSTR Get() const
Definition: scoped_bstr.h:33
void Reset(SAFEARRAY *safearray=nullptr)
const VARIANT * ptr() const
void Set(const wchar_t *str)
void Reset(const VARIANT &var=kEmptyVariant)
virtual AXTreeID GetAXTreeID() const =0
AXID id() const
Definition: ax_node.h:110
OwnerTree * tree() const
Definition: ax_node.h:109
const std::vector< AXNode * > & children() const
Definition: ax_node.h:113
virtual const AXTree::Selection GetUnignoredSelection() const =0
void NormalizeTextRange(AXPlatformNodeTextRangeProviderWin *text_range, AXNodePosition::AXPositionInstance &start, AXNodePosition::AXPositionInstance &end)
void GetTextRangeProviderFromTextNode(ComPtr< ITextRangeProvider > &text_range_provider, ui::AXNode *text_node)
AXTreeUpdate BuildTextDocument(const std::vector< std::string > &text_nodes_content, bool build_word_boundaries_offsets=false, bool place_text_on_one_line=false)
ui::AXPlatformNodeWin * GetOwner(const AXPlatformNodeTextRangeProviderWin *text_range)
const AXNodePosition::AXPositionInstance & GetEnd(const AXPlatformNodeTextRangeProviderWin *text_range)
void ComputeWordBoundariesOffsets(const std::string &text, std::vector< int > &word_start_offsets, std::vector< int > &word_end_offsets)
void CopyOwnerToClone(ITextRangeProvider *source_range, ITextRangeProvider *destination_range)
void ExpectPositionsEqual(const AXNodePosition::AXPositionInstance &a, const AXNodePosition::AXPositionInstance &b)
const AXNodePosition::AXPositionInstance & GetStart(const AXPlatformNodeTextRangeProviderWin *text_range)
void SetOwner(AXPlatformNodeWin *owner, ITextRangeProvider *destination_range)
void CreateTextRangeProviderWin(ComPtr< AXPlatformNodeTextRangeProviderWin > &text_range_provider_win, AXPlatformNodeWin *owner, const AXNode *start_anchor, int start_offset, ax::mojom::TextAffinity start_affinity, const AXNode *end_anchor, int end_offset, ax::mojom::TextAffinity end_affinity)
AXPlatformNode * AXPlatformNodeFromNode(AXNode *node)
std::unique_ptr< AXPosition< AXNodePosition, AXNode > > AXPositionInstance
Definition: ax_position.h:163
static AXPositionInstance CreateTreePosition(AXTreeID tree_id, AXNode::AXID anchor_id, int child_index)
Definition: ax_position.h:191
static AXPositionInstance CreateTextPosition(AXTreeID tree_id, AXNode::AXID anchor_id, int text_offset, ax::mojom::TextAffinity affinity)
Definition: ax_position.h:201
static AXTreeID CreateNewAXTreeID()
Definition: ax_tree_id.cc:47
AXTreeID GetAXTreeID() const override
Definition: ax_tree.cc:724
virtual bool Unserialize(const AXTreeUpdate &update)
Definition: ax_tree.cc:969
AXNode * GetFromId(int32_t id) const override
Definition: ax_tree.cc:728
IFACEMETHODIMP CompareEndpoints(TextPatternRangeEndpoint this_endpoint, ITextRangeProvider *other, TextPatternRangeEndpoint other_endpoint, int *result) override
IFACEMETHODIMP Clone(ITextRangeProvider **clone) override
IFACEMETHODIMP MoveEndpointByRange(TextPatternRangeEndpoint this_endpoint, ITextRangeProvider *other, TextPatternRangeEndpoint other_endpoint) override
IFACEMETHODIMP GetBoundingRectangles(SAFEARRAY **rectangles) override
IFACEMETHODIMP FindText(BSTR string, BOOL backwards, BOOL ignore_case, ITextRangeProvider **result) override
IFACEMETHODIMP MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int *units_moved) override
IFACEMETHODIMP FindAttribute(TEXTATTRIBUTEID attribute_id, VARIANT val, BOOL backward, ITextRangeProvider **result) override
IFACEMETHODIMP Move(TextUnit unit, int count, int *units_moved) override
static HRESULT CreateMockTextRangeProvider(ITextRangeProvider **provider)
IFACEMETHODIMP Compare(ITextRangeProvider *other, BOOL *result) override
IFACEMETHODIMP GetEnclosingElement(IRawElementProviderSimple **element) override
IFACEMETHODIMP GetAttributeValue(TEXTATTRIBUTEID attribute_id, VARIANT *value) override
IFACEMETHODIMP GetText(int max_count, BSTR *text) override
static bool b
struct MyStruct a[10]
glong glong end
uint8_t value
GAsyncResult * result
void Init()
size_t length
std::u16string text
return FALSE
std::string UTF16ToUTF8(std::u16string src)
Definition: string_utils.cc:71
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
void InitializeICU(const std::string &icu_data_path)
Definition: icu_util.cc:102
string root
Definition: scale_cpu.py:20
static bool StringCompareICU(BSTR left, BSTR right)
bool StringSearch(std::u16string_view search_string, std::u16string_view find_in, size_t *find_start, size_t *find_length, bool ignore_case, bool backwards)
TEST_F(AXPositionTest, Clone)
static AXNodePosition::AXPositionInstance CreateTextPosition(const AXNode &anchor, int text_offset, ax::mojom::TextAffinity affinity)
Definition: update.py:1
AXRelativeBounds relative_bounds
Definition: ax_node_data.h:293
void AddFloatAttribute(ax::mojom::FloatAttribute attribute, float value)
void SetRestriction(ax::mojom::Restriction restriction)
void AddIntListAttribute(ax::mojom::IntListAttribute attribute, const std::vector< int32_t > &value)
void AddState(ax::mojom::State state)
void AddIntAttribute(ax::mojom::IntAttribute attribute, int32_t value)
void SetTextAlign(ax::mojom::TextAlign text_align)
std::vector< int32_t > child_ids
Definition: ax_node_data.h:291
void SetValue(const std::string &value)
void SetHasPopup(ax::mojom::HasPopup has_popup)
void SetTextDirection(ax::mojom::WritingDirection text_direction)
void SetName(const std::string &name)
void AddStringAttribute(ax::mojom::StringAttribute attribute, const std::string &value)
void SetNameExplicitlyEmpty()
void AddTextStyle(ax::mojom::TextStyle text_style)
void AddBoolAttribute(ax::mojom::BoolAttribute attribute, bool value)
ax::mojom::Role role
Definition: ax_node_data.h:277
void SetTextPosition(ax::mojom::TextPosition text_position)
void SetCheckedState(ax::mojom::CheckedState checked_state)
AXTreeID tree_id
Definition: ax_tree_data.h:34
std::vector< AXNodeData > nodes
#define EXPECT_TRUE(handle)
Definition: unit_test.h:678
int BOOL
Definition: windows_types.h:37
#define SUCCEEDED(hr)
long LONG
Definition: windows_types.h:23
#define MAX_PATH