Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ax_platform_node_textrangeprovider_win.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 <UIAutomation.h>
8#include <wrl/client.h>
9#include <string_view>
10
11#include "ax/ax_action_data.h"
12#include "ax/ax_range.h"
17#include "flutter/fml/platform/win/wstring_conversion.h"
18#include "third_party/icu/source/i18n/unicode/usearch.h"
19
20#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL() \
21 if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
22 !start()->GetAnchor() || !end() || !end()->GetAnchor()) \
23 return UIA_E_ELEMENTNOTAVAILABLE; \
24 SetStart(start()->AsValidPosition()); \
25 SetEnd(end()->AsValidPosition());
26#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN(in) \
27 if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
28 !start()->GetAnchor() || !end() || !end()->GetAnchor()) \
29 return UIA_E_ELEMENTNOTAVAILABLE; \
30 if (!in) \
31 return E_POINTER; \
32 SetStart(start()->AsValidPosition()); \
33 SetEnd(end()->AsValidPosition());
34#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(out) \
35 if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
36 !start()->GetAnchor() || !end() || !end()->GetAnchor()) \
37 return UIA_E_ELEMENTNOTAVAILABLE; \
38 if (!out) \
39 return E_POINTER; \
40 *out = {}; \
41 SetStart(start()->AsValidPosition()); \
42 SetEnd(end()->AsValidPosition());
43#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT(in, out) \
44 if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
45 !start()->GetAnchor() || !end() || !end()->GetAnchor()) \
46 return UIA_E_ELEMENTNOTAVAILABLE; \
47 if (!in || !out) \
48 return E_POINTER; \
49 *out = {}; \
50 SetStart(start()->AsValidPosition()); \
51 SetEnd(end()->AsValidPosition());
52// Validate bounds calculated by AXPlatformNodeDelegate. Degenerate bounds
53// indicate the interface is not yet supported on the platform.
54#define UIA_VALIDATE_BOUNDS(bounds) \
55 if (bounds.OffsetFromOrigin().IsZero() && bounds.IsEmpty()) \
56 return UIA_E_NOTSUPPORTED;
57
58namespace ui {
59
61 public:
63 AXPlatformNodeTextRangeProviderWin* host)
64 : host_(host) {}
65
67 AXTreeID tree_id,
68 AXNode::AXID node_id,
69 int start_offset,
70 int end_offset,
71 ui::AXClippingBehavior clipping_behavior,
72 AXOffscreenResult* offscreen_result) override {
73 AXPlatformNodeDelegate* delegate = host_->GetDelegate(tree_id, node_id);
74 BASE_DCHECK(delegate);
75 return delegate->GetInnerTextRangeBoundsRect(
76 start_offset, end_offset, ui::AXCoordinateSystem::kScreenPhysicalPixels,
77 clipping_behavior, offscreen_result);
78 }
79
81 AXNode::AXID node_id,
82 AXOffscreenResult* offscreen_result) override {
83 AXPlatformNodeDelegate* delegate = host_->GetDelegate(tree_id, node_id);
84 BASE_DCHECK(delegate);
85 return delegate->GetBoundsRect(
87 ui::AXClippingBehavior::kClipped, offscreen_result);
88 }
89
90 private:
91 AXPlatformNodeTextRangeProviderWin* host_;
92};
93
94AXPlatformNodeTextRangeProviderWin::AXPlatformNodeTextRangeProviderWin() {}
95
96AXPlatformNodeTextRangeProviderWin::~AXPlatformNodeTextRangeProviderWin() {}
97
98ITextRangeProvider* AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
99 AXPositionInstance start,
100 AXPositionInstance end) {
101 CComObject<AXPlatformNodeTextRangeProviderWin>* text_range_provider = nullptr;
102 if (SUCCEEDED(CComObject<AXPlatformNodeTextRangeProviderWin>::CreateInstance(
103 &text_range_provider))) {
104 BASE_DCHECK(text_range_provider);
105 text_range_provider->SetStart(std::move(start));
106 text_range_provider->SetEnd(std::move(end));
107 text_range_provider->AddRef();
108 return text_range_provider;
109 }
110
111 return nullptr;
112}
113
114ITextRangeProvider*
115AXPlatformNodeTextRangeProviderWin::CreateTextRangeProviderForTesting(
116 AXPlatformNodeWin* owner,
117 AXPositionInstance start,
118 AXPositionInstance end) {
119 Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider =
120 CreateTextRangeProvider(start->Clone(), end->Clone());
121 Microsoft::WRL::ComPtr<AXPlatformNodeTextRangeProviderWin>
122 text_range_provider_win;
123 if (SUCCEEDED(text_range_provider->QueryInterface(
124 IID_PPV_ARGS(&text_range_provider_win)))) {
125 text_range_provider_win->SetOwnerForTesting(owner); // IN-TEST
126 return text_range_provider_win.Get();
127 }
128
129 return nullptr;
130}
131
132//
133// ITextRangeProvider methods.
134//
135HRESULT AXPlatformNodeTextRangeProviderWin::Clone(ITextRangeProvider** clone) {
137
138 *clone = CreateTextRangeProvider(start()->Clone(), end()->Clone());
139 return S_OK;
140}
141
142HRESULT AXPlatformNodeTextRangeProviderWin::Compare(ITextRangeProvider* other,
143 BOOL* result) {
145
146 Microsoft::WRL::ComPtr<AXPlatformNodeTextRangeProviderWin> other_provider;
147 if (other->QueryInterface(IID_PPV_ARGS(&other_provider)) != S_OK)
148 return UIA_E_INVALIDOPERATION;
149
150 if (*start() == *(other_provider->start()) &&
151 *end() == *(other_provider->end())) {
152 *result = TRUE;
153 }
154 return S_OK;
155}
156
157HRESULT AXPlatformNodeTextRangeProviderWin::CompareEndpoints(
158 TextPatternRangeEndpoint this_endpoint,
159 ITextRangeProvider* other,
160 TextPatternRangeEndpoint other_endpoint,
161 int* result) {
163
164 Microsoft::WRL::ComPtr<AXPlatformNodeTextRangeProviderWin> other_provider;
165 if (other->QueryInterface(IID_PPV_ARGS(&other_provider)) != S_OK)
166 return UIA_E_INVALIDOPERATION;
167
168 const AXPositionInstance& this_provider_endpoint =
169 (this_endpoint == TextPatternRangeEndpoint_Start) ? start() : end();
170 const AXPositionInstance& other_provider_endpoint =
171 (other_endpoint == TextPatternRangeEndpoint_Start)
172 ? other_provider->start()
173 : other_provider->end();
174
175 std::optional<int> comparison =
176 this_provider_endpoint->CompareTo(*other_provider_endpoint);
177 if (!comparison)
178 return UIA_E_INVALIDOPERATION;
179
180 if (comparison.value() < 0)
181 *result = -1;
182 else if (comparison.value() > 0)
183 *result = 1;
184 else
185 *result = 0;
186 return S_OK;
187}
188
189HRESULT AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit(
190 TextUnit unit) {
191 return ExpandToEnclosingUnitImpl(unit);
192}
193
194HRESULT AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnitImpl(
195 TextUnit unit) {
197 {
198 AXPositionInstance normalized_start = start()->Clone();
199 AXPositionInstance normalized_end = end()->Clone();
200 NormalizeTextRange(normalized_start, normalized_end);
201 SetStart(std::move(normalized_start));
202 SetEnd(std::move(normalized_end));
203 }
204
205 // Determine if start is on a boundary of the specified TextUnit, if it is
206 // not, move backwards until it is. Move the end forwards from start until it
207 // is on the next TextUnit boundary, if one exists.
208 switch (unit) {
209 case TextUnit_Character: {
210 // For characters, the start endpoint will always be on a TextUnit
211 // boundary, thus we only need to move the end position.
212 AXPositionInstance end_backup = end()->Clone();
213 SetEnd(start()->CreateNextCharacterPosition(
215
216 if (end()->IsNullPosition()) {
217 // The previous could fail if the start is at the end of the last anchor
218 // of the tree, try expanding to the previous character instead.
219 AXPositionInstance start_backup = start()->Clone();
220 SetStart(start()->CreatePreviousCharacterPosition(
222
223 if (start()->IsNullPosition()) {
224 // Text representation is empty, undo everything and exit.
225 SetStart(std::move(start_backup));
226 SetEnd(std::move(end_backup));
227 return S_OK;
228 }
229 SetEnd(start()->CreateNextCharacterPosition(
231 BASE_DCHECK(!end()->IsNullPosition());
232 }
233
234 AXPositionInstance normalized_start = start()->Clone();
235 AXPositionInstance normalized_end = end()->Clone();
236 NormalizeTextRange(normalized_start, normalized_end);
237 SetStart(std::move(normalized_start));
238 SetEnd(std::move(normalized_end));
239 break;
240 }
241 case TextUnit_Format:
242 SetStart(start()->CreatePreviousFormatStartPosition(
244 SetEnd(start()->CreateNextFormatEndPosition(
246 break;
247 case TextUnit_Word: {
248 AXPositionInstance start_backup = start()->Clone();
249 SetStart(start()->CreatePreviousWordStartPosition(
251
252 // Since start_ is already located at a word boundary, we need to cross it
253 // in order to move to the next one. Because Windows ATs behave
254 // undesirably when the start and end endpoints are not in the same anchor
255 // (for character and word navigation), stop at anchor boundary.
256 SetEnd(start()->CreateNextWordStartPosition(
258 break;
259 }
260 case TextUnit_Line:
261 // Walk backwards to the previous line start (but don't walk backwards
262 // if we're already at the start of a line). The previous line start can
263 // occur in a different node than where `start` is currently pointing, so
264 // use kStopAtLastAnchorBoundary, which will stop at the tree boundary if
265 // no previous line start is found.
266 SetStart(start()->CreateBoundaryStartPosition(
268 ax::mojom::MoveDirection::kBackward, &AtStartOfLinePredicate,
269 &AtEndOfLinePredicate));
270 // From the start we just walked backwards to, walk forwards to the line
271 // end position.
272 SetEnd(start()->CreateBoundaryEndPosition(
274 ax::mojom::MoveDirection::kForward, &AtStartOfLinePredicate,
275 &AtEndOfLinePredicate));
276 break;
277 case TextUnit_Paragraph:
278 SetStart(start()->CreatePreviousParagraphStartPosition(
280 SetEnd(start()->CreateNextParagraphStartPosition(
282 break;
283 case TextUnit_Page: {
284 // Per UIA spec, if the document containing the current range doesn't
285 // support pagination, default to document navigation.
286 const AXNode* common_anchor = start()->LowestCommonAnchor(*end());
287 if (common_anchor->tree()->HasPaginationSupport()) {
288 SetStart(start()->CreatePreviousPageStartPosition(
290 SetEnd(start()->CreateNextPageEndPosition(
292 break;
293 }
294 }
295 [[fallthrough]];
296 case TextUnit_Document:
297 SetStart(
298 start()->CreatePositionAtStartOfDocument()->AsLeafTextPosition());
299 SetEnd(start()->CreatePositionAtEndOfDocument());
300 break;
301 default:
302 return UIA_E_NOTSUPPORTED;
303 }
304 BASE_DCHECK(!start()->IsNullPosition());
305 BASE_DCHECK(!end()->IsNullPosition());
306 return S_OK;
307}
308
309HRESULT AXPlatformNodeTextRangeProviderWin::FindAttribute(
310 TEXTATTRIBUTEID text_attribute_id,
311 VARIANT attribute_val,
312 BOOL is_backward,
313 ITextRangeProvider** result) {
314 // Algorithm description:
315 // Performs linear search. Expand forward or backward to fetch the first
316 // instance of a sub text range that matches the attribute and its value.
317 // |is_backward| determines the direction of our search.
318 // |is_backward=true|, we search from the end of this text range to its
319 // beginning.
320 // |is_backward=false|, we search from the beginning of this text range to its
321 // end.
322 //
323 // 1. Iterate through the vector of AXRanges in this text range in the
324 // direction denoted by |is_backward|.
325 // 2. The |matched_range| is initially denoted as null since no range
326 // currently matches. We initialize |matched_range| to non-null value when
327 // we encounter the first AXRange instance that matches in attribute and
328 // value. We then set the |matched_range_start| to be the start (anchor) of
329 // the current AXRange, and |matched_range_end| to be the end (focus) of
330 // the current AXRange.
331 // 3. If the current AXRange we are iterating on continues to match attribute
332 // and value, we extend |matched_range| in one of the two following ways:
333 // - If |is_backward=true|, we extend the |matched_range| by moving
334 // |matched_range_start| backward. We do so by setting
335 // |matched_range_start| to the start (anchor) of the current AXRange.
336 // - If |is_backward=false|, we extend the |matched_range| by moving
337 // |matched_range_end| forward. We do so by setting |matched_range_end|
338 // to the end (focus) of the current AXRange.
339 // 4. We found a match when the current AXRange we are iterating on does not
340 // match the attribute and value and there is a previously matched range.
341 // The previously matched range is the final match we found.
343 // Use a cloned range so that FindAttribute does not introduce side-effects
344 // while normalizing the original range.
345 AXPositionInstance normalized_start = start()->Clone();
346 AXPositionInstance normalized_end = end()->Clone();
347 NormalizeTextRange(normalized_start, normalized_end);
348
349 *result = nullptr;
350 AXPositionInstance matched_range_start = nullptr;
351 AXPositionInstance matched_range_end = nullptr;
352
353 std::vector<AXNodeRange> anchors;
354 AXNodeRange range(normalized_start->Clone(), normalized_end->Clone());
355 for (AXNodeRange leaf_text_range : range)
356 anchors.emplace_back(std::move(leaf_text_range));
357
358 auto expand_match = [&matched_range_start, &matched_range_end, is_backward](
359 auto& current_start, auto& current_end) {
360 // The current AXRange has the attribute and its value that we are looking
361 // for, we expand the matched text range if a previously matched exists,
362 // otherwise initialize a newly matched text range.
363 if (matched_range_start != nullptr && matched_range_end != nullptr) {
364 // Continue expanding the matched text range forward/backward based on
365 // the search direction.
366 if (is_backward)
367 matched_range_start = current_start->Clone();
368 else
369 matched_range_end = current_end->Clone();
370 } else {
371 // Initialize the matched text range. The first AXRange instance that
372 // matches the attribute and its value encountered.
373 matched_range_start = current_start->Clone();
374 matched_range_end = current_end->Clone();
375 }
376 };
377
378 HRESULT hr_result =
379 is_backward
380 ? FindAttributeRange(text_attribute_id, attribute_val,
381 anchors.crbegin(), anchors.crend(), expand_match)
382 : FindAttributeRange(text_attribute_id, attribute_val,
383 anchors.cbegin(), anchors.cend(), expand_match);
384 if (FAILED(hr_result))
385 return E_FAIL;
386
387 if (matched_range_start != nullptr && matched_range_end != nullptr)
388 *result = CreateTextRangeProvider(std::move(matched_range_start),
389 std::move(matched_range_end));
390 return S_OK;
391}
392
393template <typename AnchorIterator, typename ExpandMatchLambda>
394HRESULT AXPlatformNodeTextRangeProviderWin::FindAttributeRange(
395 const TEXTATTRIBUTEID text_attribute_id,
396 VARIANT attribute_val,
397 const AnchorIterator first,
398 const AnchorIterator last,
399 ExpandMatchLambda expand_match) {
400 AXPlatformNodeWin* current_platform_node;
401 bool is_match_found = false;
402
403 for (auto it = first; it != last; ++it) {
404 const auto& current_start = it->anchor();
405 const auto& current_end = it->focus();
406
407 BASE_DCHECK(current_start->GetAnchor() == current_end->GetAnchor());
408
409 AXPlatformNodeDelegate* delegate = GetDelegate(current_start);
410 BASE_DCHECK(delegate);
411
412 current_platform_node = static_cast<AXPlatformNodeWin*>(
413 delegate->GetFromNodeID(current_start->GetAnchor()->id()));
414
415 base::win::VariantVector current_attribute_value;
416 if (FAILED(current_platform_node->GetTextAttributeValue(
417 text_attribute_id, current_start->text_offset(),
418 current_end->text_offset(), &current_attribute_value))) {
419 return E_FAIL;
420 }
421
422 if (!current_attribute_value.Compare(attribute_val)) {
423 // When we encounter an AXRange instance that matches the attribute
424 // and its value which we are looking for and no previously matched text
425 // range exists, we expand or initialize the matched range.
426 is_match_found = true;
427 expand_match(current_start, current_end);
428 } else if (is_match_found) {
429 // When we encounter an AXRange instance that does not match the attribute
430 // and its value which we are looking for and a previously matched text
431 // range exists, the previously matched text range is the result we found.
432 break;
433 }
434 }
435 return S_OK;
436}
437
438static bool StringSearchBasic(const std::u16string_view search_string,
439 const std::u16string_view find_in,
440 size_t* find_start,
441 size_t* find_length,
442 bool backwards) {
443 size_t index =
444 backwards ? find_in.rfind(search_string) : find_in.find(search_string);
445 if (index == std::u16string::npos) {
446 return false;
447 }
448 *find_start = index;
449 *find_length = search_string.size();
450 return true;
451}
452
453bool StringSearch(std::u16string_view search_string,
454 std::u16string_view find_in,
455 size_t* find_start,
456 size_t* find_length,
457 bool ignore_case,
458 bool backwards) {
459 UErrorCode status = U_ZERO_ERROR;
460 UCollator* col = ucol_open(uloc_getDefault(), &status);
461 UStringSearch* search = usearch_openFromCollator(
462 search_string.data(), search_string.size(), find_in.data(),
463 find_in.size(), col, nullptr, &status);
464 if (!U_SUCCESS(status)) {
465 if (search) {
466 usearch_close(search);
467 }
468 return StringSearchBasic(search_string, find_in, find_start, find_length,
469 backwards);
470 }
471 UCollator* collator = usearch_getCollator(search);
472 ucol_setStrength(collator, ignore_case ? UCOL_PRIMARY : UCOL_TERTIARY);
473 usearch_reset(search);
474 status = U_ZERO_ERROR;
475 usearch_setText(search, find_in.data(), find_in.size(), &status);
476 if (!U_SUCCESS(status)) {
477 if (search) {
478 usearch_close(search);
479 }
480 return StringSearchBasic(search_string, find_in, find_start, find_length,
481 backwards);
482 }
483 int32_t index = backwards ? usearch_last(search, &status)
484 : usearch_first(search, &status);
485 bool match = false;
486 if (U_SUCCESS(status) && index != USEARCH_DONE) {
487 match = true;
488 *find_start = static_cast<size_t>(index);
489 *find_length = static_cast<size_t>(usearch_getMatchedLength(search));
490 }
491 usearch_close(search);
492 return match;
493}
494
495HRESULT AXPlatformNodeTextRangeProviderWin::FindText(
496 BSTR string,
497 BOOL backwards,
498 BOOL ignore_case,
499 ITextRangeProvider** result) {
501 // On Windows, there's a dichotomy in the definition of a text offset in a
502 // text position between different APIs:
503 // - on UIA, a text offset translates to the offset in the text itself
504 // - on IA2, it translates to the offset in the hypertext
505 //
506 // All unignored non-text nodes are represented with an "embedded object
507 // character" in their parent's text representation on IA2, but aren't on UIA.
508 // This leads to different expected MaxTextOffset values for a same text
509 // position. If `string` is found in the text represented by the start/end
510 // endpoints, we'll create text positions in the least common ancestor, use
511 // the flat text representation's offsets of found string, then convert the
512 // positions to leaf. If 'embedded object characters' are considered, instead
513 // of the flat text representation, this falls apart.
514 //
515 // Whether we expose embedded object characters for nodes is managed by the
516 // |g_ax_embedded_object_behavior| global variable set in ax_node_position.cc.
517 // When on Windows, this variable is always set to kExposeCharacter... which
518 // is incorrect if we run UIA-specific code. To avoid problems caused by that,
519 // we use the following ScopedAXEmbeddedObjectBehaviorSetter to modify the
520 // value of the global variable to what is really expected on UIA.
521 ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
523
524 std::u16string search_string = fml::WideStringToUtf16(string);
525 if (search_string.length() <= 0)
526 return E_INVALIDARG;
527
528 size_t appended_newlines_count = 0;
529 std::u16string text_range = GetString(-1, &appended_newlines_count);
530 size_t find_start;
531 size_t find_length;
532 if (StringSearch(search_string, text_range, &find_start, &find_length,
533 ignore_case, backwards) &&
534 find_length > appended_newlines_count) {
535 // TODO(https://crbug.com/1023599): There is a known issue here related to
536 // text searches of a |string| starting and ending with a "\n", e.g.
537 // "\nsometext" or "sometext\n" if the newline is computed from a line
538 // breaking object. FindText() is rarely called, and when it is, it's not to
539 // look for a string starting or ending with a newline. This may change
540 // someday, and if so, we'll have to address this issue.
541 const AXNode* common_anchor = start()->LowestCommonAnchor(*end());
542 AXPositionInstance start_ancestor_position =
543 start()->CreateAncestorPosition(common_anchor,
545 BASE_DCHECK(!start_ancestor_position->IsNullPosition());
546 AXPositionInstance end_ancestor_position = end()->CreateAncestorPosition(
548 BASE_DCHECK(!end_ancestor_position->IsNullPosition());
549 const AXNode* anchor = start_ancestor_position->GetAnchor();
550 BASE_DCHECK(anchor);
551 const int start_offset =
552 start_ancestor_position->text_offset() + find_start;
553 const int end_offset = start_offset + find_length - appended_newlines_count;
554 const int max_end_offset = end_ancestor_position->text_offset();
555 BASE_DCHECK(start_offset <= end_offset && end_offset <= max_end_offset);
556
557 AXPositionInstance start =
559 anchor->tree()->GetAXTreeID(), anchor->id(), start_offset,
561 ->AsLeafTextPosition();
562 AXPositionInstance end =
564 anchor->tree()->GetAXTreeID(), anchor->id(), end_offset,
566 ->AsLeafTextPosition();
567
568 *result = CreateTextRangeProvider(start->Clone(), end->Clone());
569 }
570 return S_OK;
571}
572
573HRESULT AXPlatformNodeTextRangeProviderWin::GetAttributeValue(
574 TEXTATTRIBUTEID attribute_id,
575 VARIANT* value) {
577
578 base::win::VariantVector attribute_value;
579
580 // When the range spans only a generated newline (a generated newline is not
581 // part of a node, but rather introduced by AXRange::GetText when at a
582 // paragraph boundary), it doesn't make sense to return the readonly value of
583 // the start or end anchor since the newline character is not part of any of
584 // those nodes. Thus, this attribute value is independent from these nodes.
585 //
586 // Instead, we should return the readonly attribute value of the common anchor
587 // for these two endpoints since the newline character has more in common with
588 // its ancestor than its siblings. Important: This might not be true for all
589 // attributes, but it appears to be reasonable enough for the readonly one.
590 //
591 // To determine if the range encompasses *only* a generated newline, we need
592 // to validate that both the start and end endpoints are around the same
593 // paragraph boundary.
594 if (attribute_id == UIA_IsReadOnlyAttributeId &&
595 start()->anchor_id() != end()->anchor_id() &&
596 start()->AtEndOfParagraph() && end()->AtStartOfParagraph() &&
597 *start()->CreateNextCharacterPosition(
599 AXPlatformNodeWin* common_anchor = GetLowestAccessibleCommonPlatformNode();
600 BASE_DCHECK(common_anchor);
601
602 HRESULT hr = common_anchor->GetTextAttributeValue(
603 attribute_id, std::nullopt, std::nullopt, &attribute_value);
604
605 if (FAILED(hr))
606 return E_FAIL;
607
608 *value = attribute_value.ReleaseAsScalarVariant();
609 return S_OK;
610 }
611
612 // Use a cloned range so that GetAttributeValue does not introduce
613 // side-effects while normalizing the original range.
614 AXPositionInstance normalized_start = start()->Clone();
615 AXPositionInstance normalized_end = end()->Clone();
616 NormalizeTextRange(normalized_start, normalized_end);
617
618 // The range is inclusive, so advance our endpoint to the next position
619 const auto end_leaf_text_position = normalized_end->AsLeafTextPosition();
620 auto end = end_leaf_text_position->CreateNextAnchorPosition();
621
622 // Iterate over anchor positions
623 for (auto it = normalized_start->AsLeafTextPosition();
624 it->anchor_id() != end->anchor_id() || it->tree_id() != end->tree_id();
625 it = it->CreateNextAnchorPosition()) {
626 // If the iterator creates a null position, then it has likely overrun the
627 // range, return failure. This is unexpected but may happen if the range
628 // became inverted.
629 BASE_DCHECK(!it->IsNullPosition());
630 if (it->IsNullPosition())
631 return E_FAIL;
632
633 AXPlatformNodeDelegate* delegate = GetDelegate(it.get());
634 BASE_DCHECK(it && delegate);
635
636 AXPlatformNodeWin* platform_node = static_cast<AXPlatformNodeWin*>(
637 delegate->GetFromNodeID(it->anchor_id()));
638 BASE_DCHECK(platform_node);
639
640 // Only get attributes for nodes in the tree. Exclude descendants of leaves
641 // and ignored objects.
642 platform_node = static_cast<AXPlatformNodeWin*>(
644 platform_node->GetDelegate()->GetLowestPlatformAncestor()));
645 BASE_DCHECK(platform_node);
646
647 base::win::VariantVector current_value;
648 const bool at_end_leaf_text_anchor =
649 it->anchor_id() == end_leaf_text_position->anchor_id() &&
650 it->tree_id() == end_leaf_text_position->tree_id();
651 const std::optional<int> start_offset =
652 it->IsTextPosition() ? std::make_optional(it->text_offset())
653 : std::nullopt;
654 const std::optional<int> end_offset =
655 at_end_leaf_text_anchor
656 ? std::make_optional(end_leaf_text_position->text_offset())
657 : std::nullopt;
658 HRESULT hr = platform_node->GetTextAttributeValue(
659 attribute_id, start_offset, end_offset, &current_value);
660 if (FAILED(hr))
661 return E_FAIL;
662
663 if (attribute_value.Type() == VT_EMPTY) {
664 attribute_value = std::move(current_value);
665 } else if (attribute_value != current_value) {
666 V_VT(value) = VT_UNKNOWN;
667 return ::UiaGetReservedMixedAttributeValue(&V_UNKNOWN(value));
668 }
669 }
670
671 if (ShouldReleaseTextAttributeAsSafearray(attribute_id, attribute_value))
672 *value = attribute_value.ReleaseAsSafearrayVariant();
673 else
674 *value = attribute_value.ReleaseAsScalarVariant();
675 return S_OK;
676}
677
678HRESULT AXPlatformNodeTextRangeProviderWin::GetBoundingRectangles(
679 SAFEARRAY** screen_physical_pixel_rectangles) {
680 UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(screen_physical_pixel_rectangles);
681
682 *screen_physical_pixel_rectangles = nullptr;
683 AXNodeRange range(start()->Clone(), end()->Clone());
684 AXRangePhysicalPixelRectDelegate rect_delegate(this);
685 std::vector<gfx::Rect> rects = range.GetRects(&rect_delegate);
686
687 // 4 array items per rect: left, top, width, height
688 SAFEARRAY* safe_array = SafeArrayCreateVector(
689 VT_R8 /* element type */, 0 /* lower bound */, rects.size() * 4);
690
691 if (!safe_array)
692 return E_OUTOFMEMORY;
693
694 if (rects.size() > 0) {
695 double* double_array = nullptr;
696 HRESULT hr = SafeArrayAccessData(safe_array,
697 reinterpret_cast<void**>(&double_array));
698
699 if (SUCCEEDED(hr)) {
700 for (size_t rect_index = 0; rect_index < rects.size(); rect_index++) {
701 const gfx::Rect& rect = rects[rect_index];
702 double_array[rect_index * 4] = rect.x();
703 double_array[rect_index * 4 + 1] = rect.y();
704 double_array[rect_index * 4 + 2] = rect.width();
705 double_array[rect_index * 4 + 3] = rect.height();
706 }
707 hr = SafeArrayUnaccessData(safe_array);
708 }
709
710 if (FAILED(hr)) {
711 BASE_DCHECK(safe_array);
712 SafeArrayDestroy(safe_array);
713 return E_FAIL;
714 }
715 }
716
717 *screen_physical_pixel_rectangles = safe_array;
718 return S_OK;
719}
720
721HRESULT AXPlatformNodeTextRangeProviderWin::GetEnclosingElement(
722 IRawElementProviderSimple** element) {
724
725 AXPlatformNodeWin* enclosing_node = GetLowestAccessibleCommonPlatformNode();
726 if (!enclosing_node)
727 return UIA_E_ELEMENTNOTAVAILABLE;
728
729 enclosing_node->GetNativeViewAccessible()->QueryInterface(
730 IID_PPV_ARGS(element));
731
732 BASE_DCHECK(*element);
733 return S_OK;
734}
735
736HRESULT AXPlatformNodeTextRangeProviderWin::GetText(int max_count, BSTR* text) {
738
739 // -1 is a valid value that signifies that the caller wants complete text.
740 // Any other negative value is an invalid argument.
741 if (max_count < -1)
742 return E_INVALIDARG;
743
744 std::wstring full_text = fml::Utf16ToWideString(GetString(max_count));
745 if (!full_text.empty()) {
746 size_t length = full_text.length();
747
748 if (max_count != -1 && max_count < static_cast<int>(length))
749 *text = SysAllocStringLen(full_text.c_str(), max_count);
750 else
751 *text = SysAllocStringLen(full_text.c_str(), length);
752 } else {
753 *text = SysAllocString(L"");
754 }
755 return S_OK;
756}
757
758HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
759 int count,
760 int* units_moved) {
762
763 // Per MSDN, move with zero count has no effect.
764 if (count == 0)
765 return S_OK;
766
767 // Save a clone of start and end, in case one of the moves fails.
768 auto start_backup = start()->Clone();
769 auto end_backup = end()->Clone();
770 bool is_degenerate_range = (*start() == *end());
771
772 // Move the start of the text range forward or backward in the document by the
773 // requested number of text unit boundaries.
774 int start_units_moved = 0;
775 HRESULT hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_Start, unit,
776 count, &start_units_moved);
777
778 bool succeeded_move = SUCCEEDED(hr) && start_units_moved != 0;
779 if (succeeded_move) {
780 SetEnd(start()->Clone());
781 if (!is_degenerate_range) {
782 bool forwards = count > 0;
783 if (forwards && start()->AtEndOfDocument()) {
784 // The start is at the end of the document, so move the start backward
785 // by one text unit to expand the text range from the degenerate range
786 // state.
787 int current_start_units_moved = 0;
788 hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_Start, unit, -1,
789 &current_start_units_moved);
790 start_units_moved -= 1;
791 succeeded_move = SUCCEEDED(hr) && current_start_units_moved == -1 &&
792 start_units_moved > 0;
793 } else {
794 // The start is not at the end of the document, so move the endpoint
795 // forward by one text unit to expand the text range from the degenerate
796 // state.
797 int end_units_moved = 0;
798 hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_End, unit, 1,
799 &end_units_moved);
800 succeeded_move = SUCCEEDED(hr) && end_units_moved == 1;
801 }
802
803 // Because Windows ATs behave undesirably when the start and end endpoints
804 // are not in the same anchor (for character and word navigation), make
805 // sure to bring back the end endpoint to the end of the start's anchor.
806 if (start()->anchor_id() != end()->anchor_id() &&
807 (unit == TextUnit_Character || unit == TextUnit_Word)) {
808 ExpandToEnclosingUnitImpl(unit);
809 }
810 }
811 }
812
813 if (!succeeded_move) {
814 SetStart(std::move(start_backup));
815 SetEnd(std::move(end_backup));
816 start_units_moved = 0;
817 if (!SUCCEEDED(hr))
818 return hr;
819 }
820
821 *units_moved = start_units_moved;
822 return S_OK;
823}
824
825HRESULT AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnit(
826 TextPatternRangeEndpoint endpoint,
827 TextUnit unit,
828 int count,
829 int* units_moved) {
830 return MoveEndpointByUnitImpl(endpoint, unit, count, units_moved);
831}
832
833HRESULT AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnitImpl(
834 TextPatternRangeEndpoint endpoint,
835 TextUnit unit,
836 int count,
837 int* units_moved) {
839
840 // Per MSDN, MoveEndpointByUnit with zero count has no effect.
841 if (count == 0) {
842 *units_moved = 0;
843 return S_OK;
844 }
845
846 bool is_start_endpoint = endpoint == TextPatternRangeEndpoint_Start;
847 AXPositionInstance position_to_move =
848 is_start_endpoint ? start()->Clone() : end()->Clone();
849
850 AXPositionInstance new_position;
851 // TODO(schectman): TextUnit_Format implementation.
852 // https://github.com/flutter/flutter/issues/117792
853 switch (unit) {
854 case TextUnit_Character:
855 new_position =
856 MoveEndpointByCharacter(position_to_move, count, units_moved);
857 break;
858 case TextUnit_Word:
859 new_position = MoveEndpointByWord(position_to_move, count, units_moved);
860 break;
861 case TextUnit_Line:
862 new_position = MoveEndpointByLine(position_to_move, is_start_endpoint,
863 count, units_moved);
864 break;
865 case TextUnit_Paragraph:
866 new_position = MoveEndpointByParagraph(
867 position_to_move, is_start_endpoint, count, units_moved);
868 break;
869 case TextUnit_Page:
870 new_position = MoveEndpointByPage(position_to_move, is_start_endpoint,
871 count, units_moved);
872 break;
873 case TextUnit_Document:
874 new_position =
875 MoveEndpointByDocument(position_to_move, count, units_moved);
876 break;
877 default:
878 return UIA_E_NOTSUPPORTED;
879 }
880 if (is_start_endpoint)
881 SetStart(std::move(new_position));
882 else
883 SetEnd(std::move(new_position));
884
885 // If the start was moved past the end, create a degenerate range with the end
886 // equal to the start; do the equivalent if the end moved past the start.
887 std::optional<int> endpoint_comparison =
888 AXNodeRange::CompareEndpoints(start().get(), end().get());
889 BASE_DCHECK(endpoint_comparison.has_value());
890
891 if (endpoint_comparison.value_or(0) > 0) {
892 if (is_start_endpoint)
893 SetEnd(start()->Clone());
894 else
895 SetStart(end()->Clone());
896 }
897 return S_OK;
898}
899
900HRESULT AXPlatformNodeTextRangeProviderWin::MoveEndpointByRange(
901 TextPatternRangeEndpoint this_endpoint,
902 ITextRangeProvider* other,
903 TextPatternRangeEndpoint other_endpoint) {
905
906 Microsoft::WRL::ComPtr<AXPlatformNodeTextRangeProviderWin> other_provider;
907 if (other->QueryInterface(IID_PPV_ARGS(&other_provider)) != S_OK)
908 return UIA_E_INVALIDOPERATION;
909
910 const AXPositionInstance& other_provider_endpoint =
911 (other_endpoint == TextPatternRangeEndpoint_Start)
912 ? other_provider->start()
913 : other_provider->end();
914
915 if (this_endpoint == TextPatternRangeEndpoint_Start) {
916 SetStart(other_provider_endpoint->Clone());
917 if (*start() > *end())
918 SetEnd(start()->Clone());
919 } else {
920 SetEnd(other_provider_endpoint->Clone());
921 if (*start() > *end())
922 SetStart(end()->Clone());
923 }
924 return S_OK;
925}
926
927HRESULT AXPlatformNodeTextRangeProviderWin::Select() {
929
930 AXPositionInstance selection_start = start()->Clone();
931 AXPositionInstance selection_end = end()->Clone();
932
933 // Blink only supports selections within a single tree. So if start_ and end_
934 // are in different trees, we can't directly pass them to the render process
935 // for selection.
936 if (selection_start->tree_id() != selection_end->tree_id()) {
937 // Prioritize the end position's tree, as a selection's focus object is the
938 // end of a selection.
939 selection_start = selection_end->CreatePositionAtStartOfAXTree();
940 }
941
942 BASE_DCHECK(!selection_start->IsNullPosition());
943 BASE_DCHECK(!selection_end->IsNullPosition());
944 BASE_DCHECK(selection_start->tree_id() == selection_end->tree_id());
945
946 // TODO(crbug.com/1124051): Blink does not support selection on the list
947 // markers. So if |selection_start| or |selection_end| are in list markers, we
948 // don't perform selection and return success. Remove this check once this bug
949 // is fixed.
950 if (selection_start->GetAnchor()->IsInListMarker() ||
951 selection_end->GetAnchor()->IsInListMarker()) {
952 return S_OK;
953 }
954
955 AXPlatformNodeDelegate* delegate =
956 GetDelegate(selection_start->tree_id(), selection_start->anchor_id());
957 BASE_DCHECK(delegate);
958
959 AXNodeRange new_selection_range(std::move(selection_start),
960 std::move(selection_end));
961 RemoveFocusFromPreviousSelectionIfNeeded(new_selection_range);
962
963 AXActionData action_data;
964 action_data.anchor_node_id = new_selection_range.anchor()->anchor_id();
965 action_data.anchor_offset = new_selection_range.anchor()->text_offset();
966 action_data.focus_node_id = new_selection_range.focus()->anchor_id();
967 action_data.focus_offset = new_selection_range.focus()->text_offset();
968 action_data.action = ax::mojom::Action::kSetSelection;
969
970 delegate->AccessibilityPerformAction(action_data);
971 return S_OK;
972}
973
974HRESULT AXPlatformNodeTextRangeProviderWin::AddToSelection() {
975 // Blink does not support disjoint text selections.
976 return UIA_E_INVALIDOPERATION;
977}
978
979HRESULT
980AXPlatformNodeTextRangeProviderWin::RemoveFromSelection() {
981 // Blink does not support disjoint text selections.
982 return UIA_E_INVALIDOPERATION;
983}
984
985HRESULT AXPlatformNodeTextRangeProviderWin::ScrollIntoView(BOOL align_to_top) {
987
988 const AXPositionInstance start_common_ancestor =
989 start()->LowestCommonAncestor(*end());
990 const AXPositionInstance end_common_ancestor =
991 end()->LowestCommonAncestor(*start());
992 if (start_common_ancestor->IsNullPosition() ||
993 end_common_ancestor->IsNullPosition()) {
994 return E_INVALIDARG;
995 }
996
997 const AXNode* common_ancestor_anchor = start_common_ancestor->GetAnchor();
998 BASE_DCHECK(common_ancestor_anchor == end_common_ancestor->GetAnchor());
999
1000 const AXTreeID common_ancestor_tree_id = start_common_ancestor->tree_id();
1001 const AXPlatformNodeDelegate* root_delegate =
1002 GetRootDelegate(common_ancestor_tree_id);
1003 BASE_DCHECK(root_delegate);
1004 const gfx::Rect root_frame_bounds = root_delegate->GetBoundsRect(
1006 UIA_VALIDATE_BOUNDS(root_frame_bounds);
1007
1008 const AXPlatformNode* common_ancestor_platform_node =
1009 GetOwner()->GetDelegate()->GetFromTreeIDAndNodeID(
1010 common_ancestor_tree_id, common_ancestor_anchor->id());
1011 BASE_DCHECK(common_ancestor_platform_node);
1012 AXPlatformNodeDelegate* common_ancestor_delegate =
1013 common_ancestor_platform_node->GetDelegate();
1014 BASE_DCHECK(common_ancestor_delegate);
1015 const gfx::Rect text_range_container_frame_bounds =
1016 common_ancestor_delegate->GetBoundsRect(AXCoordinateSystem::kFrame,
1018 UIA_VALIDATE_BOUNDS(text_range_container_frame_bounds);
1019
1020 gfx::Point target_point;
1021 if (align_to_top) {
1022 target_point = gfx::Point(root_frame_bounds.x(), root_frame_bounds.y());
1023 } else {
1024 target_point =
1025 gfx::Point(root_frame_bounds.x(),
1026 root_frame_bounds.y() + root_frame_bounds.height());
1027 }
1028
1029 if ((align_to_top && start()->GetAnchor()->IsText()) ||
1030 (!align_to_top && end()->GetAnchor()->IsText())) {
1031 const gfx::Rect text_range_frame_bounds =
1032 common_ancestor_delegate->GetInnerTextRangeBoundsRect(
1033 start_common_ancestor->text_offset(),
1034 end_common_ancestor->text_offset(), AXCoordinateSystem::kFrame,
1036 UIA_VALIDATE_BOUNDS(text_range_frame_bounds);
1037
1038 if (align_to_top) {
1039 target_point.Offset(0, -(text_range_container_frame_bounds.height() -
1040 text_range_frame_bounds.height()));
1041 } else {
1042 target_point.Offset(0, -text_range_frame_bounds.height());
1043 }
1044 } else {
1045 if (!align_to_top)
1046 target_point.Offset(0, -text_range_container_frame_bounds.height());
1047 }
1048
1049 const gfx::Rect root_screen_bounds = root_delegate->GetBoundsRect(
1051 UIA_VALIDATE_BOUNDS(root_screen_bounds);
1052 target_point += root_screen_bounds.OffsetFromOrigin();
1053
1054 AXActionData action_data;
1055 action_data.action = ax::mojom::Action::kScrollToPoint;
1056 action_data.target_node_id = common_ancestor_anchor->id();
1057 action_data.target_point = target_point;
1058 if (!common_ancestor_delegate->AccessibilityPerformAction(action_data))
1059 return E_FAIL;
1060 return S_OK;
1061}
1062
1063// This function is expected to return a subset of the *direct* children of the
1064// common ancestor node. The subset should only include the direct children
1065// included - fully or partially - in the range.
1066HRESULT AXPlatformNodeTextRangeProviderWin::GetChildren(SAFEARRAY** children) {
1068 std::vector<gfx::NativeViewAccessible> descendants;
1069
1070 AXPlatformNodeWin* start_anchor =
1071 GetPlatformNodeFromAXNode(start()->GetAnchor());
1072 AXPlatformNodeWin* end_anchor = GetPlatformNodeFromAXNode(end()->GetAnchor());
1073 AXPlatformNodeWin* common_anchor = GetLowestAccessibleCommonPlatformNode();
1074 if (!common_anchor || !start_anchor || !end_anchor)
1075 return UIA_E_ELEMENTNOTAVAILABLE;
1076
1077 SAFEARRAY* safe_array = SafeArrayCreateVector(VT_UNKNOWN, 0, 0);
1078
1079 // TODO(schectman): Implement GetUIADirectChildrenInRange for
1080 // FlutterPlatformNodeDelegate
1081
1082 *children = safe_array;
1083 return S_OK;
1084}
1085
1086// static
1087bool AXPlatformNodeTextRangeProviderWin::AtStartOfLinePredicate(
1088 const AXPositionInstance& position) {
1089 return !position->IsIgnored() && position->AtStartOfAnchor() &&
1090 (position->AtStartOfLine() || position->AtStartOfInlineBlock());
1091}
1092
1093// static
1094bool AXPlatformNodeTextRangeProviderWin::AtEndOfLinePredicate(
1095 const AXPositionInstance& position) {
1096 return !position->IsIgnored() && position->AtEndOfAnchor() &&
1097 (position->AtEndOfLine() || position->AtStartOfInlineBlock());
1098}
1099
1100// static
1101AXPlatformNodeTextRangeProviderWin::AXPositionInstance
1102AXPlatformNodeTextRangeProviderWin::GetNextTextBoundaryPosition(
1103 const AXPositionInstance& position,
1104 ax::mojom::TextBoundary boundary_type,
1106 ax::mojom::MoveDirection boundary_direction) {
1107 // Override At[Start|End]OfLinePredicate for behavior specific to UIA.
1109 switch (boundary_type) {
1111 return position->CreateBoundaryStartPosition(options, boundary_direction,
1112 &AtStartOfLinePredicate,
1113 &AtEndOfLinePredicate);
1115 return position->CreateBoundaryEndPosition(options, boundary_direction,
1116 &AtStartOfLinePredicate,
1117 &AtEndOfLinePredicate);
1118 default:
1119 return position->CreatePositionAtTextBoundary(
1120 boundary_type, boundary_direction, options);
1121 }
1122}
1123
1124std::u16string AXPlatformNodeTextRangeProviderWin::GetString(
1125 int max_count,
1126 size_t* appended_newlines_count) {
1127 AXNodeRange range(start()->Clone(), end()->Clone());
1128 return range.GetText(AXTextConcatenationBehavior::kAsTextContent, max_count,
1129 false, appended_newlines_count);
1130}
1131
1132AXPlatformNodeWin* AXPlatformNodeTextRangeProviderWin::GetOwner() const {
1133 // Unit tests can't call |GetPlatformNodeFromTree|, so they must provide an
1134 // owner node.
1135 if (owner_for_test_.Get())
1136 return owner_for_test_.Get();
1137
1138 const AXPositionInstance& position =
1139 !start()->IsNullPosition() ? start() : end();
1140 // If start and end are both null, there's no owner.
1141 if (position->IsNullPosition())
1142 return nullptr;
1143
1144 const AXNode* anchor = position->GetAnchor();
1145 BASE_DCHECK(anchor);
1146 const AXTreeManager* tree_manager =
1147 AXTreeManagerMap::GetInstance().GetManager(anchor->tree()->GetAXTreeID());
1148 BASE_DCHECK(tree_manager);
1149 const AXPlatformTreeManager* platform_tree_manager =
1150 static_cast<const AXPlatformTreeManager*>(tree_manager);
1151 return static_cast<AXPlatformNodeWin*>(
1152 platform_tree_manager->GetPlatformNodeFromTree(*anchor));
1153}
1154
1155AXPlatformNodeDelegate* AXPlatformNodeTextRangeProviderWin::GetDelegate(
1156 const AXPositionInstanceType* position) const {
1157 return GetDelegate(position->tree_id(), position->anchor_id());
1158}
1159
1160AXPlatformNodeDelegate* AXPlatformNodeTextRangeProviderWin::GetDelegate(
1161 const AXTreeID tree_id,
1162 const AXNode::AXID node_id) const {
1163 AXPlatformNode* platform_node =
1164 GetOwner()->GetDelegate()->GetFromTreeIDAndNodeID(tree_id, node_id);
1165 if (!platform_node)
1166 return nullptr;
1167
1168 return platform_node->GetDelegate();
1169}
1170
1171AXPlatformNodeTextRangeProviderWin::AXPositionInstance
1172AXPlatformNodeTextRangeProviderWin::MoveEndpointByCharacter(
1173 const AXPositionInstance& endpoint,
1174 const int count,
1175 int* units_moved) {
1176 return MoveEndpointByUnitHelper(std::move(endpoint),
1178 units_moved);
1179}
1180
1181AXPlatformNodeTextRangeProviderWin::AXPositionInstance
1182AXPlatformNodeTextRangeProviderWin::MoveEndpointByWord(
1183 const AXPositionInstance& endpoint,
1184 const int count,
1185 int* units_moved) {
1186 return MoveEndpointByUnitHelper(std::move(endpoint),
1188 units_moved);
1189}
1190
1191AXPlatformNodeTextRangeProviderWin::AXPositionInstance
1192AXPlatformNodeTextRangeProviderWin::MoveEndpointByLine(
1193 const AXPositionInstance& endpoint,
1194 bool is_start_endpoint,
1195 const int count,
1196 int* units_moved) {
1197 return MoveEndpointByUnitHelper(std::move(endpoint),
1198 is_start_endpoint
1200 : ax::mojom::TextBoundary::kLineEnd,
1201 count, units_moved);
1202}
1203
1204AXPlatformNodeTextRangeProviderWin::AXPositionInstance
1205AXPlatformNodeTextRangeProviderWin::MoveEndpointByParagraph(
1206 const AXPositionInstance& endpoint,
1207 const bool is_start_endpoint,
1208 const int count,
1209 int* units_moved) {
1210 return MoveEndpointByUnitHelper(std::move(endpoint),
1211 is_start_endpoint
1213 : ax::mojom::TextBoundary::kParagraphEnd,
1214 count, units_moved);
1215}
1216
1217AXPlatformNodeTextRangeProviderWin::AXPositionInstance
1218AXPlatformNodeTextRangeProviderWin::MoveEndpointByPage(
1219 const AXPositionInstance& endpoint,
1220 const bool is_start_endpoint,
1221 const int count,
1222 int* units_moved) {
1223 // Per UIA spec, if the document containing the current endpoint doesn't
1224 // support pagination, default to document navigation.
1225 //
1226 // Note that the "ax::mojom::MoveDirection" should not matter when calculating
1227 // the ancestor position for use when navigating by page or document, so we
1228 // use a backward direction as the default.
1229 AXPositionInstance common_ancestor = start()->LowestCommonAncestor(*end());
1230 if (!common_ancestor->GetAnchor()->tree()->HasPaginationSupport())
1231 return MoveEndpointByDocument(std::move(endpoint), count, units_moved);
1232
1233 return MoveEndpointByUnitHelper(std::move(endpoint),
1234 is_start_endpoint
1236 : ax::mojom::TextBoundary::kPageEnd,
1237 count, units_moved);
1238}
1239
1240AXPlatformNodeTextRangeProviderWin::AXPositionInstance
1241AXPlatformNodeTextRangeProviderWin::MoveEndpointByDocument(
1242 const AXPositionInstance& endpoint,
1243 const int count,
1244 int* units_moved) {
1245 BASE_DCHECK(count != 0);
1246
1247 if (count < 0) {
1248 *units_moved = !endpoint->AtStartOfDocument() ? -1 : 0;
1249
1250 return endpoint->CreatePositionAtStartOfDocument();
1251 }
1252 *units_moved = !endpoint->AtEndOfDocument() ? 1 : 0;
1253 return endpoint->CreatePositionAtEndOfDocument();
1254}
1255
1256AXPlatformNodeTextRangeProviderWin::AXPositionInstance
1257AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnitHelper(
1258 const AXPositionInstance& endpoint,
1259 const ax::mojom::TextBoundary boundary_type,
1260 const int count,
1261 int* units_moved) {
1262 BASE_DCHECK(count != 0);
1263 const ax::mojom::MoveDirection boundary_direction =
1265 : ax::mojom::MoveDirection::kBackward;
1266
1267 const AXNode* initial_endpoint = endpoint->GetAnchor();
1268
1269 // Most of the methods used to create the next/previous position go back and
1270 // forth creating a leaf text position and rooting the result to the original
1271 // position's anchor; avoid this by normalizing to a leaf text position.
1272 AXPositionInstance current_endpoint = endpoint->AsLeafTextPosition();
1273 AXPositionInstance next_endpoint = GetNextTextBoundaryPosition(
1274 current_endpoint, boundary_type,
1276 BASE_DCHECK(next_endpoint->IsLeafTextPosition());
1277
1278 bool is_ignored_for_text_navigation = false;
1279 int iteration = 0;
1280 // Since AXBoundaryBehavior::kStopAtLastAnchorBoundary forces the next
1281 // text boundary position to be different than the input position, the
1282 // only case where these are equal is when they're already located at the
1283 // last anchor boundary. In such case, there is no next position to move
1284 // to.
1285 while (iteration < std::abs(count) &&
1286 !(next_endpoint->GetAnchor() == current_endpoint->GetAnchor() &&
1287 *next_endpoint == *current_endpoint)) {
1288 is_ignored_for_text_navigation = false;
1289 current_endpoint = std::move(next_endpoint);
1290
1291 next_endpoint = GetNextTextBoundaryPosition(
1292 current_endpoint, boundary_type,
1294 BASE_DCHECK(next_endpoint->IsLeafTextPosition());
1295
1296 // Loop until we're not on a position that is ignored for text navigation.
1297 // There is one exception for character navigation - since the ignored
1298 // anchor is represented by an embedded object character, we allow
1299 // navigation by character for consistency (i.e. you should be able to
1300 // move by character the same number of characters that are represented by
1301 // the ranges flat string buffer).
1302 is_ignored_for_text_navigation =
1303 boundary_type != ax::mojom::TextBoundary::kCharacter &&
1304 current_endpoint->GetAnchor()->data().role !=
1306 if (!is_ignored_for_text_navigation)
1307 iteration++;
1308 }
1309
1310 *units_moved = (count > 0) ? iteration : -iteration;
1311
1312 if (is_ignored_for_text_navigation &&
1313 initial_endpoint != current_endpoint->GetAnchor()) {
1314 // If the last node in the tree is ignored for text navigation, we
1315 // should still be able to return an endpoint located on that node. We
1316 // also need to ensure that the value of |units_moved| is accurate.
1317 *units_moved += (count > 0) ? 1 : -1;
1318 }
1319
1320 return current_endpoint;
1321}
1322
1323void AXPlatformNodeTextRangeProviderWin::NormalizeTextRange(
1324 AXPositionInstance& start,
1325 AXPositionInstance& end) {
1326 if (!start->IsValid() || !end->IsValid())
1327 return;
1328
1329 // If either endpoint is anchored to an ignored node,
1330 // first snap them both to be unignored positions.
1331 NormalizeAsUnignoredTextRange(start, end);
1332
1333 bool is_degenerate = *start == *end;
1334 AXPositionInstance normalized_start =
1335 is_degenerate ? start->Clone()
1336 : start->AsLeafTextPositionBeforeCharacter();
1337
1338 // For a degenerate range, the |end_| will always be the same as the
1339 // normalized start, so there's no need to compute the normalized end.
1340 // However, a degenerate range might go undetected if there's an ignored node
1341 // (or many) between the two endpoints. For this reason, we need to
1342 // compare the |end_| with both the |start_| and the |normalized_start|.
1343 is_degenerate = is_degenerate || *normalized_start == *end;
1344 AXPositionInstance normalized_end =
1345 is_degenerate ? normalized_start->Clone()
1346 : end->AsLeafTextPositionAfterCharacter();
1347
1348 if (!normalized_start->IsNullPosition() &&
1349 !normalized_end->IsNullPosition()) {
1350 start = std::move(normalized_start);
1351 end = std::move(normalized_end);
1352 }
1353
1354 BASE_DCHECK(*start <= *end);
1355}
1356
1357// static
1358void AXPlatformNodeTextRangeProviderWin::NormalizeAsUnignoredPosition(
1359 AXPositionInstance& position) {
1360 if (position->IsNullPosition() || !position->IsValid())
1361 return;
1362
1363 if (position->IsIgnored()) {
1364 AXPositionInstance normalized_position = position->AsUnignoredPosition(
1366 if (normalized_position->IsNullPosition()) {
1367 normalized_position = position->AsUnignoredPosition(
1369 }
1370
1371 if (!normalized_position->IsNullPosition())
1372 position = std::move(normalized_position);
1373 }
1374 BASE_DCHECK(!position->IsNullPosition());
1375}
1376
1377// static
1378void AXPlatformNodeTextRangeProviderWin::NormalizeAsUnignoredTextRange(
1379 AXPositionInstance& start,
1380 AXPositionInstance& end) {
1381 if (!start->IsValid() || !end->IsValid())
1382 return;
1383
1384 if (!start->IsIgnored() && !end->IsIgnored())
1385 return;
1386 NormalizeAsUnignoredPosition(start);
1387 NormalizeAsUnignoredPosition(end);
1388 BASE_DCHECK(*start <= *end);
1389}
1390
1391AXPlatformNodeDelegate* AXPlatformNodeTextRangeProviderWin::GetRootDelegate(
1392 const ui::AXTreeID tree_id) {
1393 const AXTreeManager* ax_tree_manager =
1395 BASE_DCHECK(ax_tree_manager);
1396 AXNode* root_node = ax_tree_manager->GetRootAsAXNode();
1397 const AXPlatformNode* root_platform_node =
1398 GetOwner()->GetDelegate()->GetFromTreeIDAndNodeID(tree_id,
1399 root_node->id());
1400 BASE_DCHECK(root_platform_node);
1401 return root_platform_node->GetDelegate();
1402}
1403
1404void AXPlatformNodeTextRangeProviderWin::SetStart(
1405 AXPositionInstance new_start) {
1406 endpoints_.SetStart(std::move(new_start));
1407}
1408
1409void AXPlatformNodeTextRangeProviderWin::SetEnd(AXPositionInstance new_end) {
1410 endpoints_.SetEnd(std::move(new_end));
1411}
1412
1413void AXPlatformNodeTextRangeProviderWin::SetOwnerForTesting(
1414 AXPlatformNodeWin* owner) {
1415 owner_for_test_ = owner;
1416}
1417
1418AXNode* AXPlatformNodeTextRangeProviderWin::GetSelectionCommonAnchor() {
1419 AXPlatformNodeDelegate* delegate = GetOwner()->GetDelegate();
1420 AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
1421 AXPlatformNode* anchor_object =
1422 delegate->GetFromNodeID(unignored_selection.anchor_object_id);
1423 AXPlatformNode* focus_object =
1424 delegate->GetFromNodeID(unignored_selection.focus_object_id);
1425
1426 if (!anchor_object || !focus_object)
1427 return nullptr;
1428
1430 anchor_object->GetDelegate()->CreateTextPositionAt(
1431 unignored_selection.anchor_offset);
1433 focus_object->GetDelegate()->CreateTextPositionAt(
1434 unignored_selection.focus_offset);
1435
1436 return start->LowestCommonAnchor(*end);
1437}
1438
1439// When the current selection is inside a focusable element, the DOM focused
1440// element will correspond to this element. When we update the selection to be
1441// on a different element that is not focusable, the new selection won't be
1442// applied unless we remove the DOM focused element. For example, with Narrator,
1443// if we move by word from a text field (focusable) to a static text (not
1444// focusable), the selection will stay on the text field because the DOM focused
1445// element will still be the text field. To avoid that, we need to remove the
1446// focus from this element. Since |ax::mojom::Action::kBlur| is not implemented,
1447// we perform a |ax::mojom::Action::focus| action on the root node. The result
1448// is the same.
1449void AXPlatformNodeTextRangeProviderWin::
1450 RemoveFocusFromPreviousSelectionIfNeeded(const AXNodeRange& new_selection) {
1451 const AXNode* old_selection_node = GetSelectionCommonAnchor();
1452 const AXNode* new_selection_node =
1453 new_selection.anchor()->LowestCommonAnchor(*new_selection.focus());
1454
1455 if (!old_selection_node)
1456 return;
1457
1458 if (!new_selection_node ||
1459 (old_selection_node->data().HasState(ax::mojom::State::kFocusable) &&
1460 !new_selection_node->data().HasState(ax::mojom::State::kFocusable))) {
1461 AXPlatformNodeDelegate* root_delegate =
1462 GetRootDelegate(old_selection_node->tree()->GetAXTreeID());
1463 BASE_DCHECK(root_delegate);
1464
1465 AXActionData focus_action;
1466 focus_action.action = ax::mojom::Action::kFocus;
1467 root_delegate->AccessibilityPerformAction(focus_action);
1468 }
1469}
1470
1471AXPlatformNodeWin*
1472AXPlatformNodeTextRangeProviderWin::GetPlatformNodeFromAXNode(
1473 const AXNode* node) const {
1474 if (!node)
1475 return nullptr;
1476
1477 // TODO(kschmi): Update to use AXTreeManager.
1478 AXPlatformNodeWin* platform_node =
1479 static_cast<AXPlatformNodeWin*>(AXPlatformNode::FromNativeViewAccessible(
1480 GetDelegate(node->tree()->GetAXTreeID(), node->id())
1481 ->GetNativeViewAccessible()));
1482 BASE_DCHECK(platform_node);
1483
1484 return platform_node;
1485}
1486
1487AXPlatformNodeWin*
1488AXPlatformNodeTextRangeProviderWin::GetLowestAccessibleCommonPlatformNode()
1489 const {
1490 AXNode* common_anchor = start()->LowestCommonAnchor(*end());
1491 if (!common_anchor)
1492 return nullptr;
1493
1494 return GetPlatformNodeFromAXNode(common_anchor)->GetLowestAccessibleElement();
1495}
1496
1497// static
1498bool AXPlatformNodeTextRangeProviderWin::TextAttributeIsArrayType(
1499 TEXTATTRIBUTEID attribute_id) {
1500 // https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids
1501 return attribute_id == UIA_AnnotationObjectsAttributeId ||
1502 attribute_id == UIA_AnnotationTypesAttributeId ||
1503 attribute_id == UIA_TabsAttributeId;
1504}
1505
1506// static
1507bool AXPlatformNodeTextRangeProviderWin::TextAttributeIsUiaReservedValue(
1508 const base::win::VariantVector& vector) {
1509 // Reserved values are always IUnknown.
1510 if (vector.Type() != VT_UNKNOWN)
1511 return false;
1512
1513 base::win::ScopedVariant mixed_attribute_value_variant;
1514 {
1515 Microsoft::WRL::ComPtr<IUnknown> mixed_attribute_value;
1516 HRESULT hr = ::UiaGetReservedMixedAttributeValue(&mixed_attribute_value);
1518 mixed_attribute_value_variant.Set(mixed_attribute_value.Get());
1519 }
1520
1521 base::win::ScopedVariant not_supported_value_variant;
1522 {
1523 Microsoft::WRL::ComPtr<IUnknown> not_supported_value;
1524 HRESULT hr = ::UiaGetReservedNotSupportedValue(&not_supported_value);
1526 not_supported_value_variant.Set(not_supported_value.Get());
1527 }
1528
1529 return !vector.Compare(mixed_attribute_value_variant) ||
1530 !vector.Compare(not_supported_value_variant);
1531}
1532
1533// static
1534bool AXPlatformNodeTextRangeProviderWin::ShouldReleaseTextAttributeAsSafearray(
1535 TEXTATTRIBUTEID attribute_id,
1536 const base::win::VariantVector& attribute_value) {
1537 // |vector| may be pre-populated with a UIA reserved value. In such a case, we
1538 // must release as a scalar variant.
1539 return TextAttributeIsArrayType(attribute_id) &&
1540 !TextAttributeIsUiaReservedValue(attribute_value);
1541}
1542
1543AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::TextRangeEndpoints() {
1546}
1547
1548AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::~TextRangeEndpoints() {
1551}
1552
1553void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::SetStart(
1554 AXPositionInstance new_start) {
1555 bool did_tree_change = start_->tree_id() != new_start->tree_id();
1556 // TODO(bebeaudr): We can't use IsNullPosition() here because of
1557 // https://crbug.com/1152939. Once this is fixed, we can go back to
1558 // IsNullPosition().
1559 if (did_tree_change && start_->kind() != AXPositionKind::NULL_POSITION &&
1560 start_->tree_id() != end_->tree_id()) {
1561 RemoveObserver(start_->tree_id());
1562 }
1563
1564 start_ = std::move(new_start);
1565
1566 if (did_tree_change && !start_->IsNullPosition() &&
1567 start_->tree_id() != end_->tree_id()) {
1568 AddObserver(start_->tree_id());
1569 }
1570}
1571
1572void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::SetEnd(
1573 AXPositionInstance new_end) {
1574 bool did_tree_change = end_->tree_id() != new_end->tree_id();
1575 // TODO(bebeaudr): We can't use IsNullPosition() here because of
1576 // https://crbug.com/1152939. Once this is fixed, we can go back to
1577 // IsNullPosition().
1578 if (did_tree_change && end_->kind() != AXPositionKind::NULL_POSITION &&
1579 end_->tree_id() != start_->tree_id()) {
1580 RemoveObserver(end_->tree_id());
1581 }
1582
1583 end_ = std::move(new_end);
1584
1585 if (did_tree_change && !end_->IsNullPosition() &&
1586 start_->tree_id() != end_->tree_id()) {
1587 AddObserver(end_->tree_id());
1588 }
1589}
1590
1591void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::AddObserver(
1592 const AXTreeID tree_id) {
1593 AXTreeManager* ax_tree_manager =
1595 BASE_DCHECK(ax_tree_manager);
1596 ax_tree_manager->GetTree()->AddObserver(this);
1597}
1598
1599void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::RemoveObserver(
1600 const AXTreeID tree_id) {
1601 AXTreeManager* ax_tree_manager =
1603 if (ax_tree_manager)
1604 ax_tree_manager->GetTree()->RemoveObserver(this);
1605}
1606
1607// Ensures that our endpoints are located on non-deleted nodes (step 1, case A
1608// and B). See comment in header file for more details.
1609void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::
1610 OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) {
1611 // If an endpoint is on a node that is included in a subtree that is about to
1612 // be deleted, move endpoint up to the parent of the deleted subtree's root
1613 // since we want to ensure that the endpoints of a text range provider are
1614 // always valid positions. Otherwise, the range will be stuck on nodes that
1615 // don't exist anymore.
1616 BASE_DCHECK(tree);
1617 BASE_DCHECK(node);
1618 BASE_DCHECK(tree->GetAXTreeID() == node->tree()->GetAXTreeID());
1619
1620 AdjustEndpointForSubtreeDeletion(tree, node, true /* is_start_endpoint */);
1621 AdjustEndpointForSubtreeDeletion(tree, node, false /* is_start_endpoint */);
1622}
1623
1624void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::
1625 AdjustEndpointForSubtreeDeletion(AXTree* tree,
1626 const AXNode* const node,
1627 bool is_start_endpoint) {
1628 AXPositionInstance endpoint =
1629 is_start_endpoint ? start_->Clone() : end_->Clone();
1630 if (tree->GetAXTreeID() != endpoint->tree_id())
1631 return;
1632
1633 // When the subtree of the root node will be deleted, we can be certain that
1634 // our endpoint should be invalidated. We know it's the root node when the
1635 // node doesn't have a parent.
1636 AXNode* endpoint_anchor = endpoint->GetAnchor();
1637 if (!node->parent() || !endpoint_anchor) {
1638 is_start_endpoint ? SetStart(AXNodePosition::CreateNullPosition())
1639 : SetEnd(AXNodePosition::CreateNullPosition());
1640 return;
1641 }
1642
1643 DeletionOfInterest deletion_of_interest = {tree->GetAXTreeID(), node->id()};
1644
1645 // If the root of subtree being deleted is a child of the anchor of the
1646 // endpoint, ensure `AXPosition::AsValidPosition` is called after the node is
1647 // deleted so that the index doesn't go out of bounds of the child array.
1648 if (endpoint->kind() == AXPositionKind::TREE_POSITION &&
1649 endpoint_anchor == node->parent()) {
1650 if (is_start_endpoint)
1651 validation_necessary_for_start_ = deletion_of_interest;
1652 else
1653 validation_necessary_for_end_ = deletion_of_interest;
1654 return;
1655 }
1656
1657 // Fast check for the common case - there are many tree updates and the
1658 // endpoints probably are not in the deleted subtree. Note that
1659 // CreateAncestorPosition/GetParentPosition can be expensive for text
1660 // positions.
1661 if (!endpoint_anchor->IsDescendantOfCrossingTreeBoundary(node))
1662 return;
1663
1664 AXPositionInstance new_endpoint = endpoint->CreateAncestorPosition(
1666
1667 // Obviously, we want the position to be on the parent of |node| and not on
1668 // |node| itself since it's about to be deleted.
1669 new_endpoint = new_endpoint->CreateParentPosition();
1670 AXPositionInstance other_endpoint =
1671 is_start_endpoint ? end_->Clone() : start_->Clone();
1672
1673 // Convert |new_endpoint| and |other_endpoint| to unignored positions to avoid
1674 // AXPosition::SlowCompareTo in the < operator below.
1675 NormalizeAsUnignoredPosition(new_endpoint);
1676 NormalizeAsUnignoredPosition(other_endpoint);
1677 BASE_DCHECK(!new_endpoint->IsIgnored());
1678 BASE_DCHECK(!other_endpoint->IsIgnored());
1679
1680 // If after all the above operations we're still left with a new endpoint that
1681 // is a descendant of the subtree root being deleted, just point at a null
1682 // position and don't crash later on. This can happen when the entire parent
1683 // chain of the subtree is ignored.
1684 endpoint_anchor = new_endpoint->GetAnchor();
1685 if (!endpoint_anchor ||
1686 endpoint_anchor->IsDescendantOfCrossingTreeBoundary(node))
1687 new_endpoint = AXNodePosition::CreateNullPosition();
1688
1689 // Create a degenerate range at the new position if we have an inverted range
1690 // - which occurs when the |end_| comes before the |start_|. This could have
1691 // happened due to the new endpoint walking forwards or backwards when
1692 // normalizing above. If we don't set the opposite endpoint to something that
1693 // we know will be safe (i.e. not in a deleted subtree) we'll crash later on
1694 // when trying to create a valid position.
1695 if (is_start_endpoint) {
1696 if (*other_endpoint < *new_endpoint)
1697 SetEnd(new_endpoint->Clone());
1698
1699 SetStart(std::move(new_endpoint));
1700 validation_necessary_for_start_ = deletion_of_interest;
1701 } else {
1702 if (*new_endpoint < *other_endpoint)
1703 SetStart(new_endpoint->Clone());
1704
1705 SetEnd(std::move(new_endpoint));
1706 validation_necessary_for_end_ = deletion_of_interest;
1707 }
1708}
1709
1710// Ensures that our endpoints are always valid (step 2, all scenarios). See
1711// comment in header file for more details.
1712void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::OnNodeDeleted(
1713 AXTree* tree,
1714 AXNode::AXID node_id) {
1715 BASE_DCHECK(tree);
1716
1717 if (validation_necessary_for_start_.has_value() &&
1718 validation_necessary_for_start_->tree_id == tree->GetAXTreeID() &&
1719 validation_necessary_for_start_->node_id == node_id) {
1720 if (!start_->IsNullPosition() && start_->GetAnchor()->data().id != 0)
1721 SetStart(start_->AsValidPosition());
1722 else
1724
1725 validation_necessary_for_start_ = std::nullopt;
1726 }
1727
1728 if (validation_necessary_for_end_.has_value() &&
1729 validation_necessary_for_end_->tree_id == tree->GetAXTreeID() &&
1730 validation_necessary_for_end_->node_id == node_id) {
1731 if (!end_->IsNullPosition() && end_->GetAnchor()->data().id != 0)
1732 SetEnd(end_->AsValidPosition());
1733 else
1735
1736 validation_necessary_for_end_ = std::nullopt;
1737 }
1738}
1739
1740} // namespace ui
const char * options
static bool match(const char *needle, const char *haystack)
Definition DM.cpp:1132
int count
static bool is_degenerate(const SkPath &path)
Definition SkPath.cpp:71
#define UIA_VALIDATE_BOUNDS(bounds)
#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL()
#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(out)
#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN(in)
#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT(in, out)
void Set(const wchar_t *str)
int Compare(const VARIANT &other, bool ignore_case=false) const
void Offset(int delta_x, int delta_y)
Definition point.h:58
constexpr int height() const
Definition rect.h:79
constexpr int y() const
Definition rect.h:69
Vector2d OffsetFromOrigin() const
Definition rect.h:108
constexpr int x() const
Definition rect.h:62
int32_t AXID
Definition ax_node.h:36
virtual gfx::Rect GetBoundsRect(const AXCoordinateSystem coordinate_system, const AXClippingBehavior clipping_behavior, AXOffscreenResult *offscreen_result=nullptr) const =0
virtual gfx::Rect GetInnerTextRangeBoundsRect(const int start_offset, const int end_offset, const AXCoordinateSystem coordinate_system, const AXClippingBehavior clipping_behavior, AXOffscreenResult *offscreen_result=nullptr) const =0
static AXPlatformNode * FromNativeViewAccessible(gfx::NativeViewAccessible accessible)
std::unique_ptr< AXPosition< AXNodePosition, AXNode > > AXPositionInstance
static AXPositionInstance CreateNullPosition()
static AXPositionInstance CreateTextPosition(AXTreeID tree_id, AXNode::AXID anchor_id, int text_offset, ax::mojom::TextAffinity affinity)
AXRangePhysicalPixelRectDelegate(AXPlatformNodeTextRangeProviderWin *host)
gfx::Rect GetBoundsRect(AXTreeID tree_id, AXNode::AXID node_id, AXOffscreenResult *offscreen_result) override
gfx::Rect GetInnerTextRangeBoundsRect(AXTreeID tree_id, AXNode::AXID node_id, int start_offset, int end_offset, ui::AXClippingBehavior clipping_behavior, AXOffscreenResult *offscreen_result) override
AXTreeManager * GetManager(AXTreeID tree_id)
static AXTreeManagerMap & GetInstance()
virtual AXTree * GetTree() const =0
void RemoveObserver(AXTreeObserver *observer)
Definition ax_tree.cc:717
glong glong end
uint8_t value
GAsyncResult * result
size_t length
std::u16string text
sk_sp< SkBlender > blender SkRect rect
Definition SkRecords.h:350
std::u16string WideStringToUtf16(const std::wstring_view str)
std::wstring Utf16ToWideString(const std::u16string_view str)
const myers::Point & get(const myers::Segment &)
Definition ref_ptr.h:256
static bool StringSearchBasic(const std::u16string_view search_string, const std::u16string_view find_in, size_t *find_start, size_t *find_length, bool backwards)
AXBoundaryBehavior
Definition ax_position.h:43
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)
bool IsText(ax::mojom::Role role)
#define BASE_DCHECK(condition)
Definition logging.h:63
int BOOL
#define SUCCEEDED(hr)
#define FAILED(hr)