Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
411 ui::AXNodeData root;
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
3471 ui::AXNodeData root;
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
4461 AXNodeData root;
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
4610 AXNodeData root;
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]);
5162 base::win::ScopedBstr find_string(L"more text");
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
static int find_string(const TArray< SkString > &strings, const char ext[])
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
Type::kYUV Type::kRGBA() int(0.7 *637)
#define EXPECT_UIA_INVALIDOPERATION(expr)
#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_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)
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
static AXPositionInstance CreateTreePosition(AXTreeID tree_id, AXNode::AXID anchor_id, int child_index)
static AXPositionInstance CreateTextPosition(AXTreeID tree_id, AXNode::AXID anchor_id, int text_offset, ax::mojom::TextAffinity affinity)
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 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
static bool b
struct MyStruct a[10]
glong glong end
static const uint8_t buffer[]
GAsyncResult * result
void Init()
size_t length
std::u16string text
return FALSE
std::string UTF16ToUTF8(std::u16string src)
void InitializeICU(const std::string &icu_data_path)
Definition icu_util.cc:102
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)
AXRelativeBounds relative_bounds
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
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
void SetTextPosition(ax::mojom::TextPosition text_position)
void SetCheckedState(ax::mojom::CheckedState checked_state)
AXTreeID tree_id
std::vector< AXNodeData > nodes
#define EXPECT_TRUE(handle)
Definition unit_test.h:685
int BOOL
#define SUCCEEDED(hr)
long LONG
#define MAX_PATH