Flutter Engine
The Flutter Engine
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
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
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
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 =
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
int count
Definition: FontMgrTest.cpp:50
static bool is_degenerate(const SkPath &path)
Definition: SkPath.cpp:73
#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
Definition: rect.h:36
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
Definition: ax_position.h:163
static AXPositionInstance CreateNullPosition()
Definition: ax_position.h:183
static AXPositionInstance CreateTextPosition(AXTreeID tree_id, AXNode::AXID anchor_id, int text_offset, ax::mojom::TextAffinity affinity)
Definition: ax_position.h:201
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
def match(bench, filt)
Definition: benchmark.py:23
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)
TPoint< Scalar > Point
Definition: point.h:322
const myers::Point & get(const myers::Segment &)
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition: SkVx.h:707
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
Definition: windows_types.h:37
#define SUCCEEDED(hr)
#define FAILED(hr)