Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Classes | Namespaces | Macros | Functions
ax_platform_node_textrangeprovider_win.cc File Reference
#include "ax/platform/ax_platform_node_textrangeprovider_win.h"
#include <UIAutomation.h>
#include <wrl/client.h>
#include <string_view>
#include "ax/ax_action_data.h"
#include "ax/ax_range.h"
#include "ax/platform/ax_platform_node_delegate.h"
#include "ax/platform/ax_platform_node_win.h"
#include "ax/platform/ax_platform_tree_manager.h"
#include "base/win/variant_vector.h"
#include "flutter/fml/platform/win/wstring_conversion.h"
#include "third_party/icu/source/i18n/unicode/usearch.h"

Go to the source code of this file.

Classes

class  ui::AXRangePhysicalPixelRectDelegate
 

Namespaces

namespace  ui
 

Macros

#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL()
 
#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN(in)
 
#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(out)
 
#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT(in, out)
 
#define UIA_VALIDATE_BOUNDS(bounds)
 

Functions

static bool ui::StringSearchBasic (const std::u16string_view search_string, const std::u16string_view find_in, size_t *find_start, size_t *find_length, bool backwards)
 
bool ui::StringSearch (std::u16string_view search_string, std::u16string_view find_in, size_t *find_start, size_t *find_length, bool ignore_case, bool backwards)
 

Macro Definition Documentation

◆ UIA_VALIDATE_BOUNDS

#define UIA_VALIDATE_BOUNDS (   bounds)
Value:
if (bounds.OffsetFromOrigin().IsZero() && bounds.IsEmpty()) \
return UIA_E_NOTSUPPORTED;

Definition at line 54 of file ax_platform_node_textrangeprovider_win.cc.

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

◆ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL

#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL ( )
Value:
if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
!start()->GetAnchor() || !end() || !end()->GetAnchor()) \
return UIA_E_ELEMENTNOTAVAILABLE; \
SetStart(start()->AsValidPosition()); \
SetEnd(end()->AsValidPosition());

Definition at line 20 of file ax_platform_node_textrangeprovider_win.cc.

◆ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN

#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN (   in)
Value:
if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
!start()->GetAnchor() || !end() || !end()->GetAnchor()) \
return UIA_E_ELEMENTNOTAVAILABLE; \
if (!in) \
return E_POINTER; \
SetStart(start()->AsValidPosition()); \
SetEnd(end()->AsValidPosition());

Definition at line 26 of file ax_platform_node_textrangeprovider_win.cc.

◆ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT

#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT (   in,
  out 
)
Value:
if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
!start()->GetAnchor() || !end() || !end()->GetAnchor()) \
return UIA_E_ELEMENTNOTAVAILABLE; \
if (!in || !out) \
return E_POINTER; \
*out = {}; \
SetStart(start()->AsValidPosition()); \
SetEnd(end()->AsValidPosition());

Definition at line 43 of file ax_platform_node_textrangeprovider_win.cc.

49 {}; \
50 SetStart(start()->AsValidPosition()); \
51 SetEnd(end()->AsValidPosition());

◆ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT

#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT (   out)
Value:
if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
!start()->GetAnchor() || !end() || !end()->GetAnchor()) \
return UIA_E_ELEMENTNOTAVAILABLE; \
if (!out) \
return E_POINTER; \
*out = {}; \
SetStart(start()->AsValidPosition()); \
SetEnd(end()->AsValidPosition());

Definition at line 34 of file ax_platform_node_textrangeprovider_win.cc.

40 {}; \
41 SetStart(start()->AsValidPosition()); \
42 SetEnd(end()->AsValidPosition());