Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ax_position.h
Go to the documentation of this file.
1// Copyright 2016 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
5#ifndef UI_ACCESSIBILITY_AX_POSITION_H_
6#define UI_ACCESSIBILITY_AX_POSITION_H_
7
8#include <cmath>
9#include <cstdint>
10#include <functional>
11#include <memory>
12#include <ostream>
13#include <stack>
14#include <string>
15#include <type_traits>
16#include <utility>
17#include <vector>
18
19#include "ax_enum_util.h"
20#include "ax_enums.h"
21#include "ax_node.h"
22#include "ax_node_text_styles.h"
23#include "ax_role_properties.h"
24#include "ax_tree_id.h"
26#include "base/logging.h"
27#include "base/string_utils.h"
28
29namespace ui {
30
31// Defines the type of position in the accessibility tree.
32// A tree position is used when referring to a specific child of a node in the
33// accessibility tree.
34// A text position is used when referring to a specific character of text inside
35// a particular node.
36// A null position is used to signify that the provided data is invalid or that
37// a boundary has been reached.
39
40// Defines how creating the next or previous position should behave whenever we
41// are at or are crossing a boundary, such as at the start of an anchor, a word
42// or a line.
49
50// Describes in further detail what type of boundary a current position is on.
51// For complex boundaries such as format boundaries, it can be useful to know
52// why a particular boundary was chosen.
53enum class AXBoundaryType {
54 // Not at a unit boundary.
55 kNone,
56 // At a unit boundary (e.g. a format boundary).
58 // At the start of a document.
60 // At the end of a document.
62};
63
64// When converting to an unignored position, determines how to adjust the new
65// position in order to make it valid, either moving backward or forward in
66// the accessibility tree.
68
69// Specifies how AXPosition::ExpandToEnclosingTextBoundary behaves.
70//
71// As an example, imagine we have the text "hello world" and a position before
72// the space character. We want to expand to the surrounding word boundary.
73// Since we are right at the end of the first word, we could either expand to
74// the left first, find the start of the first word and then use that to find
75// the corresponding word end, resulting in the word "Hello". Another
76// possibility is to expand to the right first, find the end of the next word
77// and use that as our starting point to find the previous word start, resulting
78// in the word "world".
80 // Expands to the left boundary first and then uses that position as the
81 // starting point to find the boundary to the right.
83 // Expands to the right boundary first and then uses that position as the
84 // starting point to find the boundary to the left.
86};
87
88// Some platforms require empty objects to be represented by a replacement
89// character in order for text navigation to work correctly. This enum controls
90// whether a replacement character will be exposed for such objects.
91//
92// When an embedded object is replaced by a real character, the expectations
93// are the same with this character as with other ordinary characters.
94// For example, with UIA on Windows, we need to be able to navigate inside and
95// outside of this character as if it was an ordinary character, using the
96// AXPlatformNodeTextRangeProvider methods. Since an embedded object character
97// is the only character in a node, we also treat this character as a word.
102
103// Controls whether embedded objects are represented by a replacement
104// character. This is initialized to a per-platform default but can be
105// overridden for testing.
107
117
118// Forward declarations.
119template <class AXPositionType, class AXNodeType>
120class AXPosition;
121template <class AXPositionType>
122class AXRange;
123template <class AXPositionType, class AXNodeType>
126template <class AXPositionType, class AXNodeType>
129
130// A position in the accessibility tree.
131//
132// This class could either represent a tree position or a text position.
133// Tree positions point to either a child of a specific node or at the end of a
134// node (i.e. an "after children" position).
135// Text positions point to either a character offset in the text inside a
136// particular node including text from all its children, or to the end of the
137// node's text, (i.e. an "after text" position).
138// On tree positions that have a leaf node as their anchor, we also need to
139// distinguish between "before text" and "after text" positions. To do this, if
140// the child index is 0 and the anchor is a leaf node, then it's an "after text"
141// position. If the child index is |BEFORE_TEXT| and the anchor is a leaf node,
142// then this is a "before text" position.
143// It doesn't make sense to have a "before text" position on a text position,
144// because it is identical to setting its offset to the first character.
145//
146// To avoid re-computing either the text offset or the child index when
147// converting between the two types of positions, both values are saved after
148// the first conversion.
149//
150// This class template uses static polymorphism in order to allow sub-classes to
151// be created from the base class without the base class knowing the type of the
152// sub-class in advance.
153// The template argument |AXPositionType| should always be set to the type of
154// any class that inherits from this template, making this a
155// "curiously recursive template".
156//
157// This class can be copied using the |Clone| method. It is designed to be
158// immutable.
159template <class AXPositionType, class AXNodeType>
161 public:
163 std::unique_ptr<AXPosition<AXPositionType, AXNodeType>>;
164
166
168
169 typedef std::vector<int32_t> BoundaryTextOffsetsFunc(
170 const AXPositionInstance&);
171
172 static const int BEFORE_TEXT = -1;
173 static const int INVALID_INDEX = -2;
174 static const int INVALID_OFFSET = -1;
175
176 // Replacement character used to represent an empty object. See
177 // AXEmbeddedObjectBehavior for more information.
178 //
179 // Duplicate of AXPlatformNodeBase::kEmbeddedCharacter because we don't want
180 // to include platform specific code in here.
181 static constexpr char16_t kEmbeddedCharacter = L'\xfffc';
182
184 AXPositionInstance new_position(new AXPositionType());
185 new_position->Initialize(
188 return new_position;
189 }
190
193 int child_index) {
194 AXPositionInstance new_position(new AXPositionType());
195 new_position->Initialize(AXPositionKind::TREE_POSITION, tree_id, anchor_id,
198 return new_position;
199 }
200
204 int text_offset,
206 AXPositionInstance new_position(new AXPositionType());
207 new_position->Initialize(AXPositionKind::TEXT_POSITION, tree_id, anchor_id,
209 return new_position;
210 }
211
212 virtual ~AXPosition() = default;
213
214 // Implemented based on the copy and swap idiom.
216 AXPositionInstance clone = other.Clone();
217 swap(*clone);
218 return *this;
219 }
220
221 virtual AXPositionInstance Clone() const = 0;
222
223 // A serialization of a position as POD. Not for sharing on disk or sharing
224 // across thread or process boundaries, just for passing a position to an
225 // API that works with positions as opaque objects.
234
235 static_assert(std::is_trivially_copyable<SerializedPosition>::value,
236 "SerializedPosition must be POD");
237
240 result.kind = kind_;
241
242 // A tree ID can be serialized as a 32-byte string.
243 std::string tree_id_string = tree_id_.ToString();
244 BASE_DCHECK(tree_id_string.size() <= 32U);
245 strncpy(result.tree_id, tree_id_string.c_str(), 32);
246 result.tree_id[32] = 0;
247
248 result.anchor_id = anchor_id_;
249 result.child_index = child_index_;
250 result.text_offset = text_offset_;
251 result.affinity = affinity_;
252 return result;
253 }
254
256 const SerializedPosition& serialization) {
257 AXPositionInstance new_position(new AXPositionType());
258 new_position->Initialize(serialization.kind,
259 ui::AXTreeID::FromString(serialization.tree_id),
260 serialization.anchor_id, serialization.child_index,
261 serialization.text_offset, serialization.affinity);
262 return new_position;
263 }
264
265 std::string ToString() const {
266 std::string str;
267 switch (kind_) {
269 return "NullPosition";
271 std::string str_child_index;
272 if (child_index_ == BEFORE_TEXT) {
273 str_child_index = "before_text";
274 } else if (child_index_ == INVALID_INDEX) {
275 str_child_index = "invalid";
276 } else {
277 str_child_index = base::NumberToString(child_index_);
278 }
279 str = "TreePosition tree_id=" + tree_id_.ToString() +
280 " anchor_id=" + base::NumberToString(anchor_id_) +
281 " child_index=" + str_child_index;
282 break;
283 }
285 std::string str_text_offset;
286 if (text_offset_ == INVALID_OFFSET) {
287 str_text_offset = "invalid";
288 } else {
289 str_text_offset = base::NumberToString(text_offset_);
290 }
291 str = "TextPosition anchor_id=" + base::NumberToString(anchor_id_) +
292 " text_offset=" + str_text_offset + " affinity=" +
293 ui::ToString(static_cast<ax::mojom::TextAffinity>(affinity_));
294 break;
295 }
296 }
297
298 if (!IsTextPosition() || text_offset_ > MaxTextOffset())
299 return str;
300
301 std::u16string text = GetText();
302 BASE_DCHECK(text_offset_ >= 0);
303 int max_text_offset = MaxTextOffset();
304 BASE_DCHECK(text_offset_ <= max_text_offset);
305 std::u16string annotated_text;
306 if (text_offset_ == max_text_offset) {
307 annotated_text = text + u"<>";
308 } else {
309 annotated_text = text.substr(0, text_offset_) + u"<" +
310 text[text_offset_] + u">" +
311 text.substr(text_offset_ + 1);
312 }
313 return str + " annotated_text=" + base::UTF16ToUTF8(annotated_text);
314 }
315
316 AXTreeID tree_id() const { return tree_id_; }
317 AXNode::AXID anchor_id() const { return anchor_id_; }
318
319 AXNodeType* GetAnchor() const {
320 if (tree_id_ == AXTreeIDUnknown() || anchor_id_ == AXNode::kInvalidAXID)
321 return nullptr;
322 return GetNodeInTree(tree_id_, anchor_id_);
323 }
324
325 AXPositionKind kind() const { return kind_; }
326 int child_index() const { return child_index_; }
327 int text_offset() const { return text_offset_; }
328 ax::mojom::TextAffinity affinity() const { return affinity_; }
329
330 bool IsIgnored() const {
331 if (IsNullPosition())
332 return false;
333
335 // If this position is anchored to an ignored node, then consider this
336 // position to be ignored.
337 if (GetAnchor()->IsIgnored()) {
338 return true;
339 }
340
341 switch (kind_) {
344 return false;
346 // If this is a "before text" or an "after text" tree position, it's
347 // pointing to the anchor itself, which we've determined to be
348 // unignored.
349 BASE_DCHECK(!IsLeaf() || child_index_ == BEFORE_TEXT ||
350 child_index_ == 0)
351 << "\"Before text\" and \"after text\" tree positions are only "
352 "valid on leaf nodes.";
353 if (child_index_ == BEFORE_TEXT || IsLeaf())
354 return false;
355
356 // If this position is an "after children" position, consider the
357 // position to be ignored if the last child is ignored. This is because
358 // the last child will not be visible in the unignored tree. If the
359 // position is not adjusted, the resulting position would erroneously
360 // point before the second child in the unignored subtree rooted at the
361 // last child.
362 //
363 // 1 kRootWebArea
364 // ++2 kGenericContainer ignored
365 // ++++3 kStaticText "Line 1."
366 // ++++4 kStaticText "Line 2."
367 //
368 // Tree position anchor=kGenericContainer, child_index=1.
369 //
370 // Alternatively, if there is a node at the position pointed to by
371 // "child_index_", i.e. this position is neither a leaf position nor an
372 // "after children" position, consider this tree position to be ignored
373 // if the child node is ignored.
374 int adjusted_child_index = child_index_ != AnchorChildCount()
375 ? child_index_
376 : child_index_ - 1;
377 AXPositionInstance child_position =
378 CreateChildPositionAt(adjusted_child_index);
379 BASE_DCHECK(child_position && !child_position->IsNullPosition());
380 return child_position->GetAnchor()->IsIgnored();
381 }
383 // If the corresponding leaf position is ignored, the current text
384 // offset will point to ignored text. Therefore, consider this position
385 // to be ignored.
386 if (!IsLeaf()) {
387 return AsLeafTreePosition()->IsIgnored();
388 }
389 return false;
390 }
391 }
392
393 bool IsNullPosition() const {
394 return kind_ == AXPositionKind::NULL_POSITION || !GetAnchor();
395 }
396
397 bool IsTreePosition() const {
398 return GetAnchor() && kind_ == AXPositionKind::TREE_POSITION;
399 }
400
401 bool IsLeafTreePosition() const { return IsTreePosition() && IsLeaf(); }
402
403 bool IsTextPosition() const {
404 return GetAnchor() && kind_ == AXPositionKind::TEXT_POSITION;
405 }
406
407 bool IsLeafTextPosition() const { return IsTextPosition() && IsLeaf(); }
408
409 bool IsLeaf() const {
410 if (IsNullPosition())
411 return false;
412
414 }
415
416 // Returns true if this is a valid position, e.g. the child_index_ or
417 // text_offset_ is within a valid range.
418 bool IsValid() const {
419 switch (kind_) {
421 return tree_id_ == AXTreeIDUnknown() &&
422 anchor_id_ == AXNode::kInvalidAXID &&
423 child_index_ == INVALID_INDEX &&
424 text_offset_ == INVALID_OFFSET &&
427 return GetAnchor() &&
428 (child_index_ == BEFORE_TEXT ||
429 (child_index_ >= 0 && child_index_ <= AnchorChildCount())) &&
433 return false;
434 }
435
436 // For performance reasons we skip any validation of the text offset
437 // that involves retrieving the anchor's text, if the offset is set to
438 // 0, because 0 is frequently used and always valid regardless of the
439 // actual text.
440 return text_offset_ == 0 ||
441 (text_offset_ > 0 && text_offset_ <= MaxTextOffset());
442 }
443 }
444
445 // TODO(nektar): Update logic of AtStartOfAnchor() for text_offset_ == 0 and
446 // fix related bug.
447 bool AtStartOfAnchor() const {
448 if (!GetAnchor())
449 return false;
450 switch (kind_) {
452 return false;
454 if (text_offset_ > 0)
455 return false;
456 if (!IsLeaf() || text_offset_ == 0)
457 return child_index_ == 0;
458 return child_index_ == BEFORE_TEXT;
460 return text_offset_ == 0;
461 }
462 }
463
464 bool AtEndOfAnchor() const {
465 if (!GetAnchor())
466 return false;
467 switch (kind_) {
469 return false;
471 return child_index_ == AnchorChildCount();
473 return text_offset_ == MaxTextOffset();
474 }
475 }
476
477 bool AtStartOfWord() const {
478 AXPositionInstance text_position = AsLeafTextPosition();
479 switch (text_position->kind_) {
481 return false;
484 return false;
486 const std::vector<int32_t> word_starts =
487 text_position->GetWordStartOffsets();
488 return base::Contains(word_starts,
489 int32_t{text_position->text_offset_});
490 }
491 }
492 }
493
494 bool AtEndOfWord() const {
495 AXPositionInstance text_position = AsLeafTextPosition();
496 switch (text_position->kind_) {
498 return false;
501 return false;
503 const std::vector<int32_t> word_ends =
504 text_position->GetWordEndOffsets();
505 return base::Contains(word_ends, int32_t{text_position->text_offset_});
506 }
507 }
508 }
509
510 bool AtStartOfLine() const {
511 AXPositionInstance text_position = AsLeafTextPosition();
512 switch (text_position->kind_) {
514 return false;
517 return false;
519 // We treat a position after some white space that is not connected to
520 // any node after it via "next on line ID", to be equivalent to a
521 // position before the next line, and therefore as being at start of
522 // line.
523 //
524 // We assume that white space, including but not limited to hard line
525 // breaks, might be used to separate lines. For example, an inline text
526 // box with just a single space character inside it can be used to
527 // represent a soft line break. If an inline text box containing white
528 // space separates two lines, it should always be connected to the first
529 // line via "kPreviousOnLineId". This is guaranteed by the renderer. If
530 // there are multiple line breaks separating the two lines, then only
531 // the first line break is connected to the first line via
532 // "kPreviousOnLineId".
533 //
534 // Sometimes there might be an inline text box with a single space in it
535 // at the end of a text field. We should not mark positions that are at
536 // the end of text fields, or in general at the end of their anchor, as
537 // being at the start of line, except when that anchor is an inline text
538 // box that is in the middle of a text span. Note that in most but not
539 // all cases, the parent of an inline text box is a static text object,
540 // whose end signifies the end of the text span. One exception is line
541 // breaks.
542 if (text_position->AtEndOfAnchor() &&
543 !text_position->AtEndOfTextSpan() &&
544 text_position->IsInWhiteSpace() &&
545 GetNextOnLineID(text_position->anchor_id_) ==
547 return true;
548 }
549
550 return GetPreviousOnLineID(text_position->anchor_id_) ==
552 text_position->AtStartOfAnchor();
553 }
554 }
555
556 bool AtEndOfLine() const {
557 AXPositionInstance text_position = AsLeafTextPosition();
558 switch (text_position->kind_) {
560 return false;
563 return false;
565 // Text positions on objects with no text should not be considered at
566 // end of line because the empty position may share a text offset with
567 // a non-empty text position in which case the end of line iterators
568 // must move to the line end of the non-empty content. Specified next
569 // line IDs are ignored.
570 if (!text_position->MaxTextOffset())
571 return false;
572
573 // If affinity has been used to specify whether the caret is at the end
574 // of a line or at the start of the next one, this should have been
575 // reflected in the leaf text position we got via "AsLeafTextPosition".
576 // If affinity had been set to upstream, the leaf text position should
577 // be pointing to the end of the inline text box that ends the first
578 // line. If it had been set to downstream, the leaf text position should
579 // be pointing to the start of the inline text box that starts the
580 // second line.
581 //
582 // In other cases, we assume that white space, including but not limited
583 // to hard line breaks, might be used to separate lines. For example, an
584 // inline text box with just a single space character inside it can be
585 // used to represent a soft line break. If an inline text box containing
586 // white space separates two lines, it should always be connected to the
587 // first line via "kPreviousOnLineId". This is guaranteed by the
588 // renderer. If there are multiple line breaks separating the two lines,
589 // then only the first line break is connected to the first line via
590 // "kPreviousOnLineId".
591 //
592 // We don't treat a position that is at the start of white space that is
593 // on a line by itself as being at the end of the line. This is in order
594 // to enable screen readers to recognize and announce blank lines
595 // correctly. However, we do treat positions at the start of white space
596 // that end a line of text as being at the end of that line. We also
597 // treat positions at the end of white space that is on a line by
598 // itself, i.e. on a blank line, as being at the end of that line.
599 //
600 // Sometimes there might be an inline text box with a single space in it
601 // at the end of a text field. We should mark positions that are at the
602 // end of text fields, or in general at the end of an anchor with no
603 // "kNextOnLineId", as being at end of line, except when that anchor is
604 // an inline text box that is in the middle of a text span. Note that
605 // in most but not all cases, the parent of an inline text box is a
606 // static text object, whose end signifies the end of the text span. One
607 // exception is line breaks.
608 if (GetNextOnLineID(text_position->anchor_id_) ==
610 return (!text_position->AtEndOfTextSpan() &&
611 text_position->IsInWhiteSpace() &&
612 GetPreviousOnLineID(text_position->anchor_id_) !=
614 ? text_position->AtStartOfAnchor()
615 : text_position->AtEndOfAnchor();
616 }
617
618 // The current anchor might be followed by a soft line break.
619 return text_position->AtEndOfAnchor() &&
620 text_position->CreateNextLeafTextPosition()->AtEndOfLine();
621 }
622 }
623
624 // |AtStartOfParagraph| is asymmetric from |AtEndOfParagraph| because of
625 // trailing whitespace collapse rules.
626 // The start of a paragraph should be a leaf text position (or equivalent),
627 // either at the start of the document, or at the start of the next leaf text
628 // position from the one representing the end of the previous paragraph.
629 // A position |AsLeafTextPosition| is the start of a paragraph if all of the
630 // following are true :
631 // 1. The current leaf text position must be an unignored position at
632 // the start of an anchor.
633 // 2. The current position is not whitespace only, unless it is also
634 // the first leaf text position within the document.
635 // 3. Either (a) the current leaf text position is the first leaf text
636 // position in the document, or (b) there are no line breaking
637 // objects between it and the previous non-whitespace leaf text
638 // position.
639 bool AtStartOfParagraph() const {
640 AXPositionInstance text_position = AsLeafTextPosition();
641 switch (text_position->kind_) {
643 return false;
646 return false;
648 // 1. The current leaf text position must be an unignored position at
649 // the start of an anchor.
650 if (text_position->IsIgnored() || !text_position->AtStartOfAnchor())
651 return false;
652
653 // 2. The current position is not whitespace only, unless it is also
654 // the first leaf text position within the document.
655 if (text_position->IsInWhiteSpace()) {
656 return text_position->CreatePreviousLeafTextPosition()
657 ->IsNullPosition();
658 }
659
660 // 3. Either (a) the current leaf text position is the first leaf text
661 // position in the document, or (b) there are no line breaking
662 // objects between it and the previous non-whitespace leaf text
663 // position.
664 //
665 // Search for the previous text position within the current paragraph,
666 // using the paragraph boundary abort predicate.
667 // If a valid position was found, then this position cannot be
668 // the start of a paragraph.
669 // This will return a null position when an anchor movement would
670 // cross a paragraph boundary, or the start of document was reached.
671 bool crossed_line_breaking_object_token = false;
672 auto abort_move_predicate =
673 [&crossed_line_breaking_object_token](
674 const AXPosition& move_from, const AXPosition& move_to,
675 const AXMoveType type, const AXMoveDirection direction) {
676 return AbortMoveAtParagraphBoundary(
677 crossed_line_breaking_object_token, move_from, move_to, type,
678 direction);
679 };
680
681 AXPositionInstance previous_text_position = text_position->Clone();
682 do {
683 previous_text_position =
684 previous_text_position->CreatePreviousTextAnchorPosition(
685 abort_move_predicate);
686 // If the previous position is whitespace, then continue searching
687 // until a non-whitespace leaf text position is found within the
688 // current paragraph because whitespace is supposed to be collapsed.
689 // There's a chance that |CreatePreviousTextAnchorPosition| will
690 // return whitespace that should be appended to a previous paragraph
691 // rather than separating two pieces of the current paragraph.
692 } while (previous_text_position->IsInWhiteSpace() ||
693 previous_text_position->IsIgnored());
694 return previous_text_position->IsNullPosition();
695 }
696 }
697 }
698
699 // |AtEndOfParagraph| is asymmetric from |AtStartOfParagraph| because of
700 // trailing whitespace collapse rules.
701 // The end of a paragraph should be a leaf text position (or equivalent),
702 // either at the end of the document, or at the end of the previous leaf text
703 // position from the one representing the start of the next paragraph.
704 // A position |AsLeafTextPosition| is the end of a paragraph if all of the
705 // following are true :
706 // 1. The current leaf text position must be an unignored position at
707 // the end of an anchor.
708 // 2. Either (a) the current leaf text position is the last leaf text
709 // position in the document, or (b) there are no line breaking
710 // objects between it and the next leaf text position except when
711 // the next leaf text position is whitespace only since whitespace
712 // must be collapsed.
713 // 3. If there is a next leaf text position then it must not be
714 // whitespace only.
715 // 4. If there is a next leaf text position and it is not whitespace
716 // only, it must also be the start of a paragraph for the current
717 // position to be the end of a paragraph.
718 bool AtEndOfParagraph() const {
719 AXPositionInstance text_position = AsLeafTextPosition();
720 switch (text_position->kind_) {
722 return false;
725 return false;
727 // 1. The current leaf text position must be an unignored position at
728 // the end of an anchor.
729 if (text_position->IsIgnored() || !text_position->AtEndOfAnchor())
730 return false;
731
732 // 2. Either (a) the current leaf text position is the last leaf text
733 // position in the document, or (b) there are no line breaking
734 // objects between it and the next leaf text position except when
735 // the next leaf text position is whitespace only since whitespace
736 // must be collapsed.
737 //
738 // Search for the next text position within the current paragraph,
739 // using the paragraph boundary abort predicate.
740 // If a null position was found, then this position must be the end of
741 // a paragraph.
742 // |CreateNextTextAnchorPosition| + |AbortMoveAtParagraphBoundary|
743 // will return a null position when an anchor movement would
744 // cross a paragraph boundary and there is no doubt that it is the end
745 // of a paragraph, or the end of document was reached.
746 // There are some fringe cases related to whitespace collapse that
747 // cannot be handled easily with only |AbortMoveAtParagraphBoundary|.
748 bool crossed_line_breaking_object_token = false;
749 auto abort_move_predicate =
750 [&crossed_line_breaking_object_token](
751 const AXPosition& move_from, const AXPosition& move_to,
752 const AXMoveType type, const AXMoveDirection direction) {
753 return AbortMoveAtParagraphBoundary(
754 crossed_line_breaking_object_token, move_from, move_to, type,
755 direction);
756 };
757
758 AXPositionInstance next_text_position = text_position->Clone();
759 do {
760 next_text_position = next_text_position->CreateNextTextAnchorPosition(
761 abort_move_predicate);
762 } while (next_text_position->IsIgnored());
763 if (next_text_position->IsNullPosition())
764 return true;
765
766 // 3. If there is a next leaf text position then it must not be
767 // whitespace only.
768 if (next_text_position->IsInWhiteSpace())
769 return false;
770
771 // 4. If there is a next leaf text position and it is not whitespace
772 // only, it must also be the start of a paragraph for the current
773 // position to be the end of a paragraph.
774 //
775 // Consider the following example :
776 // ++{1} kStaticText "First Paragraph"
777 // ++++{2} kInlineTextBox "First Paragraph"
778 // ++{3} kStaticText "\n Second Paragraph"
779 // ++++{4} kInlineTextBox "\n" kIsLineBreakingObject
780 // ++++{5} kInlineTextBox " "
781 // ++++{6} kInlineTextBox "Second Paragraph"
782 // A position at the end of {5} is the end of a paragraph, because
783 // the first paragraph must collapse trailing whitespace and contain
784 // leaf text anchors {2, 4, 5}. The second paragraph is only {6}.
785 return next_text_position->CreatePositionAtStartOfAnchor()
786 ->AtStartOfParagraph();
787 }
788 }
789 }
790
791 bool AtStartOfPage() const {
792 AXPositionInstance text_position = AsLeafTextPosition();
793 switch (text_position->kind_) {
795 return false;
798 return false;
800 if (!text_position->AtStartOfAnchor())
801 return false;
802
803 // Search for the previous text position within the current page,
804 // using the page boundary abort predicate.
805 // If a valid position was found, then this position cannot be
806 // the start of a page.
807 // This will return a null position when an anchor movement would
808 // cross a page boundary, or the start of document was reached.
809 AXPositionInstance previous_text_position =
810 text_position->CreatePreviousTextAnchorPosition(
811 AbortMoveAtPageBoundary);
812 return previous_text_position->IsNullPosition();
813 }
814 }
815 }
816
817 bool AtEndOfPage() const {
818 AXPositionInstance text_position = AsLeafTextPosition();
819 switch (text_position->kind_) {
821 return false;
824 return false;
826 if (!text_position->AtEndOfAnchor())
827 return false;
828
829 // Search for the next text position within the current page,
830 // using the page boundary abort predicate.
831 // If a valid position was found, then this position cannot be
832 // the end of a page.
833 // This will return a null position when an anchor movement would
834 // cross a page boundary, or the end of document was reached.
835 AXPositionInstance next_text_position =
836 text_position->CreateNextTextAnchorPosition(
837 AbortMoveAtPageBoundary);
838 return next_text_position->IsNullPosition();
839 }
840 }
841 }
842
843 bool AtStartOfAXTree() const {
844 if (IsNullPosition())
845 return false;
846
847 if (AtStartOfAnchor()) {
849
850 // Consider the start of the document as the start of an AXTree.
851 if (previous_anchor->IsNullPosition())
852 return true;
853 else
854 return previous_anchor->tree_id() != tree_id();
855 }
856 return false;
857 }
858
859 bool AtEndOfAXTree() const {
860 if (IsNullPosition())
861 return false;
862
863 if (AtEndOfAnchor()) {
865
866 // Consider the end of the document as the end of an AXTree.
867 if (next_anchor->IsNullPosition())
868 return true;
869 else
870 return next_anchor->tree_id() != tree_id();
871 }
872 return false;
873 }
874
876 // Since formats are stored on text anchors, the start of a format boundary
877 // must be at the start of an anchor.
880
881 // Treat the first iterable node as a format boundary.
884
885 // Ignored positions cannot be format boundaries.
886 if (IsIgnored())
888
889 // Iterate over anchors until a format boundary is found. This will return a
890 // null position upon crossing a boundary. Make sure the previous position
891 // is not on an ignored node.
892 AXPositionInstance previous_position = Clone();
893 do {
894 previous_position = previous_position->CreatePreviousLeafTreePosition(
895 AbortMoveAtFormatBoundary);
896 } while (previous_position->IsIgnored());
897
898 if (previous_position->IsNullPosition())
900
902 }
903
907
909 // Since formats are stored on text anchors, the end of a format break must
910 // be at the end of an anchor.
911 if (IsNullPosition() || !AtEndOfAnchor())
913
914 // Treat the last iterable node as a format boundary
917
918 // Ignored positions cannot be format boundaries.
919 if (IsIgnored())
921
922 // Iterate over anchors until a format boundary is found. This will return a
923 // null position upon crossing a boundary. Make sure the next position is
924 // not on an ignored node.
925 AXPositionInstance next_position = Clone();
926 do {
927 next_position =
928 next_position->CreateNextLeafTreePosition(AbortMoveAtFormatBoundary);
929 } while (next_position->IsIgnored());
930
931 if (next_position->IsNullPosition())
933
935 }
936
937 bool AtEndOfFormat() const {
939 }
940
941 bool AtStartOfInlineBlock() const {
942 AXPositionInstance text_position = AsLeafTextPosition();
943 switch (text_position->kind_) {
945 return false;
948 return false;
950 if (text_position->AtStartOfAnchor()) {
951 AXPositionInstance previous_position =
952 text_position->CreatePreviousLeafTreePosition();
953
954 // Check that this position is not the start of the first anchor.
955 if (!previous_position->IsNullPosition()) {
956 previous_position = text_position->CreatePreviousLeafTreePosition(
957 &AbortMoveAtStartOfInlineBlock);
958
959 // If we get a null position here it means we have crossed an inline
960 // block's start, thus this position is located at such start.
961 if (previous_position->IsNullPosition())
962 return true;
963 }
964 }
965 if (text_position->AtEndOfAnchor()) {
966 AXPositionInstance next_position =
967 text_position->CreateNextLeafTreePosition();
968
969 // Check that this position is not the end of the last anchor.
970 if (!next_position->IsNullPosition()) {
971 next_position = text_position->CreateNextLeafTreePosition(
972 &AbortMoveAtStartOfInlineBlock);
973
974 // If we get a null position here it means we have crossed an inline
975 // block's start, thus this position is located at such start.
976 if (next_position->IsNullPosition())
977 return true;
978 }
979 }
980 return false;
981 }
982 }
983 }
984
985 bool AtStartOfDocument() const {
986 if (IsNullPosition())
987 return false;
989 }
990
991 bool AtEndOfDocument() const {
992 if (IsNullPosition())
993 return false;
994 return AtLastNodeInTree() && AtEndOfAnchor();
995 }
996
997 bool AtLastNodeInTree() const {
998 if (IsNullPosition())
999 return false;
1000
1001 // Avoid a potentionally expensive MaxTextOffset call by only using tree
1002 // positions. The only thing that matters is whether our anchor_id_ is at
1003 // the last anchor of the document, so we're free to ignore text_offset_.
1004 AXPositionInstance tree_position =
1005 CreateTreePosition(tree_id_, anchor_id_, 0);
1006 return tree_position->CreateNextAnchorPosition()->IsNullPosition();
1007 }
1008
1009 // This method finds the lowest common AXNodeType of |this| and |second|.
1010 AXNodeType* LowestCommonAnchor(const AXPosition& second) const {
1011 if (IsNullPosition() || second.IsNullPosition())
1012 return nullptr;
1013 if (GetAnchor() == second.GetAnchor())
1014 return GetAnchor();
1015
1016 std::stack<AXNodeType*> our_ancestors = GetAncestorAnchors();
1017 std::stack<AXNodeType*> other_ancestors = second.GetAncestorAnchors();
1018
1019 AXNodeType* common_anchor = nullptr;
1020 while (!our_ancestors.empty() && !other_ancestors.empty() &&
1021 our_ancestors.top() == other_ancestors.top()) {
1022 common_anchor = our_ancestors.top();
1023 our_ancestors.pop();
1024 other_ancestors.pop();
1025 }
1026 return common_anchor;
1027 }
1028
1029 // This method returns a position instead of a node because this allows us to
1030 // return the corresponding text offset or child index in the ancestor that
1031 // relates to the current position.
1032 // Also, this method uses position instead of tree logic to traverse the tree,
1033 // because positions can handle moving across multiple trees, while trees
1034 // cannot.
1038
1039 // See "CreateParentPosition" for an explanation of the use of
1040 // |move_direction|.
1042 const AXNodeType* ancestor_anchor,
1043 ax::mojom::MoveDirection move_direction =
1045 if (!ancestor_anchor) {
1046 return CreateNullPosition();
1047 }
1048
1049 AXPositionInstance ancestor_position = Clone();
1050 while (!ancestor_position->IsNullPosition() &&
1051 ancestor_position->GetAnchor() != ancestor_anchor) {
1052 ancestor_position =
1053 ancestor_position->CreateParentPosition(move_direction);
1054 }
1055 return ancestor_position;
1056 }
1057
1058 // If the position is not valid, we return a new valid position that is
1059 // closest to the original position if possible, or a null position otherwise.
1061 AXPositionInstance position = Clone();
1062 switch (position->kind_) {
1064 // We avoid cloning to ensure that all fields will be valid.
1065 return CreateNullPosition();
1067 if (!position->GetAnchor())
1068 return CreateNullPosition();
1069
1070 if (AXNodeType* empty_object_node = GetEmptyObjectAncestorNode()) {
1071 // In this class and on certain platforms, we define the empty object
1072 // as one that doesn't expose its underlying content. Its content is
1073 // replaced by the empty object character (string of length 1). A
1074 // position on a descendant of an empty object is invalid. To make it
1075 // valid we move the position from the descendant to the empty object
1076 // node itself.
1077 return CreateTreePosition(
1078 position->tree_id(), GetAnchorID(empty_object_node),
1079 position->child_index() == BEFORE_TEXT ? BEFORE_TEXT : 0);
1080 }
1081
1082 if (position->child_index_ == BEFORE_TEXT)
1083 return position;
1084
1085 if (position->child_index_ < 0)
1086 position->child_index_ = 0;
1087 else if (position->child_index_ > position->AnchorChildCount())
1088 position->child_index_ = position->AnchorChildCount();
1089 break;
1090 }
1092 if (!position->GetAnchor())
1093 return CreateNullPosition();
1094
1095 if (AXNodeType* empty_object_node = GetEmptyObjectAncestorNode()) {
1096 // This is needed because an empty object as defined in this class and
1097 // on certain platforms can have descendants that should not be
1098 // exposed. See comment above in similar implementation for
1099 // AXPositionKind::TREE_POSITION.
1100 //
1101 // We set the |text_offset_| to either 0 or 1 here because the
1102 // MaxTextOffset of an empty object is 1 (the empty object character,
1103 // a string of length 1). If the invalid position was already at the
1104 // start of the node, we set it to 0.
1105 return CreateTextPosition(position->tree_id(),
1106 GetAnchorID(empty_object_node),
1107 position->text_offset() > 0 ? 1 : 0,
1109 }
1110
1111 if (position->text_offset_ <= 0) {
1112 // 0 is always a valid offset, so skip calling MaxTextOffset in that
1113 // case.
1114 position->text_offset_ = 0;
1115 position->affinity_ = ax::mojom::TextAffinity::kDownstream;
1116 } else {
1117 int max_text_offset = position->MaxTextOffset();
1118 if (position->text_offset_ > max_text_offset) {
1119 position->text_offset_ = max_text_offset;
1120 position->affinity_ = ax::mojom::TextAffinity::kDownstream;
1121 }
1122 }
1123 break;
1124 }
1125 }
1126 BASE_DCHECK(position->IsValid());
1127 return position;
1128 }
1129
1131 if (IsNullPosition() || IsTreePosition())
1132 return Clone();
1133
1136 BASE_DCHECK(copy->text_offset_ >= 0);
1137 if (copy->IsLeaf()) {
1138 const int max_text_offset = copy->MaxTextOffset();
1139 copy->child_index_ =
1140 (max_text_offset != 0 && copy->text_offset_ != max_text_offset)
1141 ? BEFORE_TEXT
1142 : 0;
1144 return copy;
1145 }
1146
1147 // We stop at the last child that we can reach with the current text offset
1148 // and ignore any remaining children. This is for defensive programming
1149 // purposes, in case "MaxTextOffset" doesn't match the total length of all
1150 // our children. This may happen if, for example, there is a bug in the
1151 // internal accessibility tree we get from the renderer. In contrast, the
1152 // current offset could not be greater than the length of all our children
1153 // because the position would have been invalid.
1154 int current_offset = 0;
1155 int child_index = 0;
1156 for (; child_index < copy->AnchorChildCount(); ++child_index) {
1157 AXPositionInstance child = copy->CreateChildPositionAt(child_index);
1158 BASE_DCHECK(child);
1159 int child_length = child->MaxTextOffsetInParent();
1160 // If the text offset falls on the boundary between two adjacent children,
1161 // we look at the affinity to decide whether to place the tree position on
1162 // the first child vs. the second child. Upstream affinity would always
1163 // choose the first child, whilst downstream affinity the second. This
1164 // also has implications when converting the resulting tree position back
1165 // to a text position. In that case, maintaining an upstream affinity
1166 // would place the text position at the end of the first child, whilst
1167 // maintaining a downstream affinity will place the text position at the
1168 // beginning of the second child.
1169 //
1170 // This is vital for text positions on soft line breaks, as well as text
1171 // positions before and after character, to work properly.
1172 //
1173 // See also `CreateLeafTextPositionBeforeCharacter` and
1174 // `CreateLeafTextPositionAfterCharacter`.
1175 if (copy->text_offset_ >= current_offset &&
1176 (copy->text_offset_ < (current_offset + child_length) ||
1178 copy->text_offset_ == (current_offset + child_length)))) {
1179 break;
1180 }
1181
1182 current_offset += child_length;
1183 }
1184
1185 copy->child_index_ = child_index;
1187 return copy;
1188 }
1189
1190 // This is an optimization over "AsLeafTextPosition", in cases when computing
1191 // the corresponding text offset on the leaf node is not needed. If this
1192 // method is called on a text position, it will conservatively fall back to
1193 // the non-optimized "AsLeafTextPosition", if the current text offset is
1194 // greater than 0, or the affinity is upstream, since converting to a tree
1195 // position at any point before reaching the leaf node could potentially lose
1196 // information.
1198 if (IsNullPosition() || IsLeaf())
1199 return AsTreePosition();
1200
1201 // If our text offset is greater than 0, or if our affinity is set to
1202 // upstream, we need to ensure that text offset and affinity will be taken
1203 // into consideration during our descend to the leaves. Switching to a tree
1204 // position early in this case will potentially lose information, so we
1205 // descend using a text position instead.
1206 //
1207 // We purposely don't check whether this position is a text position, to
1208 // allow for the possibility that this position has recently been converted
1209 // from a text to a tree position and text offset or affinity information
1210 // has been left intact.
1211 if (text_offset_ > 0 || affinity_ == ax::mojom::TextAffinity::kUpstream)
1212 return AsLeafTextPosition()->AsTreePosition();
1213
1214 AXPositionInstance tree_position = AsTreePosition();
1215 do {
1216 if (tree_position->child_index_ == tree_position->AnchorChildCount()) {
1217 tree_position =
1218 tree_position
1219 ->CreateChildPositionAt(tree_position->child_index_ - 1)
1220 ->CreatePositionAtEndOfAnchor();
1221 } else {
1222 tree_position =
1223 tree_position->CreateChildPositionAt(tree_position->child_index_);
1224 }
1225 BASE_DCHECK(tree_position && !tree_position->IsNullPosition());
1226 } while (!tree_position->IsLeaf());
1227
1228 BASE_DCHECK(tree_position && tree_position->IsLeafTreePosition());
1229 return tree_position;
1230 }
1231
1233 if (IsNullPosition() || IsTextPosition())
1234 return Clone();
1235
1238 // Check if it is a "before text" position.
1239 if (copy->child_index_ == BEFORE_TEXT) {
1240 // "Before text" positions can only appear on leaf nodes.
1241 BASE_DCHECK(copy->IsLeaf());
1242 // If the current text offset is valid, we don't touch it to potentially
1243 // allow converting from a text position to a tree position and back
1244 // without losing information.
1245 //
1246 // We test for INVALID_OFFSET first, due to the possible performance
1247 // implications of calling MaxTextOffset().
1248 BASE_DCHECK(copy->text_offset_ >= INVALID_OFFSET);
1249 if (copy->text_offset_ == INVALID_OFFSET ||
1250 (copy->text_offset_ > 0 &&
1251 copy->text_offset_ >= copy->MaxTextOffset())) {
1252 copy->text_offset_ = 0;
1253 }
1254 } else if (copy->child_index_ == copy->AnchorChildCount()) {
1255 copy->text_offset_ = copy->MaxTextOffset();
1256 } else {
1257 BASE_DCHECK(copy->child_index_ >= 0);
1258 BASE_DCHECK(copy->child_index_ < copy->AnchorChildCount());
1259 int new_offset = 0;
1260 for (int i = 0; i <= child_index_; ++i) {
1261 AXPositionInstance child = copy->CreateChildPositionAt(i);
1262 BASE_DCHECK(child);
1263 // If the current text offset is valid, we don't touch it to
1264 // potentially allow converting from a text position to a tree
1265 // position and back without losing information. Otherwise, if the
1266 // text_offset is invalid, equals to 0 or is smaller than
1267 // |new_offset|, we reset it to the beginning of the current child
1268 // node.
1269 if (i == child_index_ && copy->text_offset_ <= new_offset) {
1270 copy->text_offset_ = new_offset;
1271 break;
1272 }
1273
1274 int child_length = child->MaxTextOffsetInParent();
1275 // Same comment as above: we don't touch the text offset if it's
1276 // already valid.
1277 if (i == child_index_ &&
1278 (copy->text_offset_ > (new_offset + child_length) ||
1279 // When the text offset is equal to the text's length but this is
1280 // not an "after text" position.
1281 (!copy->AtEndOfAnchor() &&
1282 copy->text_offset_ == (new_offset + child_length)))) {
1283 copy->text_offset_ = new_offset;
1284 break;
1285 }
1286
1287 new_offset += child_length;
1288 }
1289 }
1290
1291 // Affinity should always be left as downstream. The only case when the
1292 // resulting text position is at the end of the line is when we get an
1293 // "after text" leaf position, but even in this case downstream is
1294 // appropriate because there is no ambiguity whetehr the position is at the
1295 // end of the current line vs. the start of the next line. It would always
1296 // be the former.
1298 return copy;
1299 }
1300
1302 if (IsNullPosition() || IsLeaf()) {
1303 return AsTextPosition();
1304 }
1305
1306 // Adjust the text offset.
1307 // No need to check for "before text" positions here because they are only
1308 // present on leaf anchor nodes.
1309 AXPositionInstance text_position = AsTextPosition();
1310 int adjusted_offset = text_position->text_offset_;
1311 do {
1312 AXPositionInstance child_position =
1313 text_position->CreateChildPositionAt(0);
1314 BASE_DCHECK(child_position);
1315
1316 // If the text offset corresponds to multiple child positions because some
1317 // of the children have empty text, the condition "adjusted_offset > 0"
1318 // below ensures that the first child will be chosen.
1319 for (int i = 1;
1320 i < text_position->AnchorChildCount() && adjusted_offset > 0; ++i) {
1321 const int max_text_offset_in_parent =
1322 child_position->MaxTextOffsetInParent();
1323 if (adjusted_offset < max_text_offset_in_parent) {
1324 break;
1325 }
1326 if (affinity_ == ax::mojom::TextAffinity::kUpstream &&
1327 adjusted_offset == max_text_offset_in_parent) {
1328 // Maintain upstream affinity so that we'll be able to choose the
1329 // correct leaf anchor if the text offset is right on the boundary
1330 // between two leaves.
1331 child_position->affinity_ = ax::mojom::TextAffinity::kUpstream;
1332 break;
1333 }
1334 child_position = std::move(text_position->CreateChildPositionAt(i));
1335 adjusted_offset -= max_text_offset_in_parent;
1336 }
1337
1338 text_position = std::move(child_position);
1339 } while (!text_position->IsLeaf());
1340
1341 BASE_DCHECK(text_position);
1342 BASE_DCHECK(text_position->IsLeafTextPosition());
1343 text_position->text_offset_ = adjusted_offset;
1344 // A leaf Text position is always downstream since there is no ambiguity as
1345 // to whether it refers to the end of the current or the start of the next
1346 // line.
1347 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
1348 return text_position;
1349 }
1350
1351 // We deploy three strategies in order to find the best match for an ignored
1352 // position in the accessibility tree:
1353 //
1354 // 1. In the case of a text position, we move up the parent positions until we
1355 // find the next unignored equivalent parent position. We don't do this for
1356 // tree positions because, unlike text positions which maintain the
1357 // corresponding text offset in the inner text of the parent node, tree
1358 // positions would lose some information every time a parent position is
1359 // computed. In other words, the parent position of a tree position is, in
1360 // most cases, non-equivalent to the child position.
1361 // 2. If no equivalent and unignored parent position can be computed, we try
1362 // computing the leaf equivalent position. If this is unignored, we return it.
1363 // This can happen both for tree and text positions, provided that the leaf
1364 // node and its inner text is visible to platform APIs, i.e. it's unignored.
1365 // 3. As a last resort, we move either to the next or previous unignored
1366 // position in the accessibility tree, based on the "adjustment_behavior".
1368 AXPositionAdjustmentBehavior adjustment_behavior) const {
1369 if (IsNullPosition() || !IsIgnored())
1370 return Clone();
1371
1372 AXPositionInstance leaf_tree_position = AsLeafTreePosition();
1373
1374 // If this is a text position, first try moving up to a parent equivalent
1375 // position and check if the resulting position is still ignored. This
1376 // won't result in the loss of any information. We can't do that in the
1377 // case of tree positions, because we would be better off to move to the
1378 // next or previous position within the same anchor, as this would lose
1379 // less information than moving to a parent equivalent position.
1380 //
1381 // Text positions are considered ignored if either the current anchor is
1382 // ignored, or if the equivalent leaf tree position is ignored.
1383 // If this position is a leaf text position, or the equivalent leaf tree
1384 // position is ignored, then it's not possible to create an ancestor text
1385 // position that is unignored.
1386 if (IsTextPosition() && !IsLeafTextPosition() &&
1387 !leaf_tree_position->IsIgnored()) {
1388 AXPositionInstance unignored_position = CreateParentPosition();
1389 while (!unignored_position->IsNullPosition()) {
1390 // Since the equivalent leaf tree position is unignored, search for the
1391 // first unignored ancestor anchor and return that text position.
1392 if (!unignored_position->GetAnchor()->IsIgnored()) {
1393 BASE_DCHECK(!unignored_position->IsIgnored());
1394 return unignored_position;
1395 }
1396 unignored_position = unignored_position->CreateParentPosition();
1397 }
1398 }
1399
1400 // There is a possibility that the position became unignored by moving to a
1401 // leaf equivalent position. Otherwise, we have no choice but to move to the
1402 // next or previous position and lose some information in the process.
1403 while (leaf_tree_position->IsIgnored()) {
1404 switch (adjustment_behavior) {
1406 leaf_tree_position = leaf_tree_position->CreateNextLeafTreePosition();
1407 break;
1409 leaf_tree_position =
1410 leaf_tree_position->CreatePreviousLeafTreePosition();
1411 // in case the unignored leaf node contains some text, ensure that the
1412 // resulting position is an "after text" position, as such a position
1413 // would be the closest to the ignored one, given the fact that we are
1414 // moving backwards through the tree.
1415 leaf_tree_position =
1416 leaf_tree_position->CreatePositionAtEndOfAnchor();
1417 break;
1418 }
1419 }
1420
1421 if (IsTextPosition())
1422 return leaf_tree_position->AsTextPosition();
1423 return leaf_tree_position;
1424 }
1425
1426 // Searches backward and forward from this position until it finds the given
1427 // text boundary, and creates an AXRange that spans from the former to the
1428 // latter. The resulting AXRange is always a forward range: its anchor always
1429 // comes before its focus in document order. The resulting AXRange is bounded
1430 // by the anchor of this position, i.e. the AXBoundaryBehavior is set to
1431 // StopAtAnchorBoundary. The exception is ax::mojom::TextBoundary::kWebPage,
1432 // where this behavior won't make sense. This behavior is based on current
1433 // platform needs and might be relaxed if necessary in the future.
1434 //
1435 // Please note that |expand_behavior| should have no effect for
1436 // ax::mojom::TextBoundary::kObject and ax::mojom::TextBoundary::kWebPage
1437 // because the range should be the same regardless if we first move left or
1438 // right.
1440 ax::mojom::TextBoundary boundary,
1441 AXRangeExpandBehavior expand_behavior) const {
1442 AXBoundaryBehavior boundary_behavior =
1444 if (boundary == ax::mojom::TextBoundary::kWebPage)
1445 boundary_behavior = AXBoundaryBehavior::CrossBoundary;
1446
1447 switch (expand_behavior) {
1450 boundary, ax::mojom::MoveDirection::kBackward, boundary_behavior);
1451 AXPositionInstance right_position =
1452 left_position->CreatePositionAtTextBoundary(
1454 boundary_behavior);
1455 return AXRangeType(std::move(left_position), std::move(right_position));
1456 }
1459 boundary, ax::mojom::MoveDirection::kForward, boundary_behavior);
1460 AXPositionInstance left_position =
1461 right_position->CreatePositionAtTextBoundary(
1463 boundary_behavior);
1464 return AXRangeType(std::move(left_position), std::move(right_position));
1465 }
1466 }
1467 }
1468
1469 // Starting from this position, moves in the given direction until it finds
1470 // the given text boundary, and creates a new position at that location.
1471 //
1472 // When a boundary has the "StartOrEnd" suffix, it means that this method will
1473 // find the start boundary when moving in the backward direction, and the end
1474 // boundary when moving in the forward direction.
1476 ax::mojom::TextBoundary boundary,
1477 ax::mojom::MoveDirection direction,
1478 AXBoundaryBehavior boundary_behavior) const {
1479 AXPositionInstance resulting_position = CreateNullPosition();
1480 switch (boundary) {
1482 switch (direction) {
1484 resulting_position =
1485 CreatePreviousCharacterPosition(boundary_behavior);
1486 break;
1488 resulting_position = CreateNextCharacterPosition(boundary_behavior);
1489 break;
1490 }
1491 break;
1492
1494 switch (direction) {
1496 resulting_position =
1497 CreatePreviousFormatStartPosition(boundary_behavior);
1498 break;
1500 resulting_position = CreateNextFormatEndPosition(boundary_behavior);
1501 break;
1502 }
1503 break;
1504
1506 switch (direction) {
1508 resulting_position =
1509 CreatePreviousLineEndPosition(boundary_behavior);
1510 break;
1512 resulting_position = CreateNextLineEndPosition(boundary_behavior);
1513 break;
1514 }
1515 break;
1516
1518 switch (direction) {
1520 resulting_position =
1521 CreatePreviousLineStartPosition(boundary_behavior);
1522 break;
1524 resulting_position = CreateNextLineStartPosition(boundary_behavior);
1525 break;
1526 }
1527 break;
1528
1530 switch (direction) {
1532 resulting_position =
1533 CreatePreviousLineStartPosition(boundary_behavior);
1534 break;
1536 resulting_position = CreateNextLineEndPosition(boundary_behavior);
1537 break;
1538 }
1539 break;
1540
1542 switch (direction) {
1544 resulting_position = CreatePositionAtStartOfAnchor();
1545 break;
1547 resulting_position = CreatePositionAtEndOfAnchor();
1548 break;
1549 }
1550 break;
1551
1553 switch (direction) {
1555 resulting_position =
1556 CreatePreviousPageEndPosition(boundary_behavior);
1557 break;
1559 resulting_position = CreateNextPageEndPosition(boundary_behavior);
1560 break;
1561 }
1562 break;
1563
1565 switch (direction) {
1567 resulting_position =
1568 CreatePreviousPageStartPosition(boundary_behavior);
1569 break;
1571 resulting_position = CreateNextPageStartPosition(boundary_behavior);
1572 break;
1573 }
1574 break;
1575
1577 switch (direction) {
1579 resulting_position =
1580 CreatePreviousPageStartPosition(boundary_behavior);
1581 break;
1583 resulting_position = CreateNextPageEndPosition(boundary_behavior);
1584 break;
1585 }
1586 break;
1587
1589 switch (direction) {
1591 resulting_position =
1592 CreatePreviousParagraphEndPosition(boundary_behavior);
1593 break;
1595 resulting_position =
1596 CreateNextParagraphEndPosition(boundary_behavior);
1597 break;
1598 }
1599 break;
1600
1602 switch (direction) {
1604 resulting_position =
1605 CreatePreviousParagraphStartPosition(boundary_behavior);
1606 break;
1608 resulting_position =
1609 CreateNextParagraphStartPosition(boundary_behavior);
1610 break;
1611 }
1612 break;
1613
1615 switch (direction) {
1617 resulting_position =
1618 CreatePreviousParagraphStartPosition(boundary_behavior);
1619 break;
1621 resulting_position =
1622 CreateNextParagraphEndPosition(boundary_behavior);
1623 break;
1624 }
1625 break;
1626
1628 BASE_LOG() << "Sentence boundaries are not yet supported.";
1630 return CreateNullPosition();
1631
1633 BASE_LOG() << "Sentence boundaries are not yet supported.";
1635 return CreateNullPosition();
1636
1638 BASE_LOG() << "Sentence boundaries are not yet supported.";
1640 return CreateNullPosition();
1641
1643 if (boundary_behavior != AXBoundaryBehavior::CrossBoundary) {
1644 BASE_LOG() << "We can't reach the start of the document if we "
1645 "are disallowed "
1646 "from crossing boundaries.";
1648 }
1649 switch (direction) {
1651 resulting_position = CreatePositionAtStartOfDocument();
1652 break;
1654 resulting_position = CreatePositionAtEndOfDocument();
1655 break;
1656 }
1657 break;
1658
1660 switch (direction) {
1662 resulting_position =
1663 CreatePreviousWordEndPosition(boundary_behavior);
1664 break;
1666 resulting_position = CreateNextWordEndPosition(boundary_behavior);
1667 break;
1668 }
1669 break;
1670
1672 switch (direction) {
1674 resulting_position =
1675 CreatePreviousWordStartPosition(boundary_behavior);
1676 break;
1678 resulting_position = CreateNextWordStartPosition(boundary_behavior);
1679 break;
1680 }
1681 break;
1682
1684 switch (direction) {
1686 resulting_position =
1687 CreatePreviousWordStartPosition(boundary_behavior);
1688 break;
1690 resulting_position = CreateNextWordEndPosition(boundary_behavior);
1691 break;
1692 }
1693 break;
1694 }
1695 return resulting_position;
1696 }
1697
1699 switch (kind_) {
1701 return CreateNullPosition();
1703 if (IsLeaf())
1704 return CreateTreePosition(tree_id_, anchor_id_, BEFORE_TEXT);
1705 return CreateTreePosition(tree_id_, anchor_id_, 0 /* child_index */);
1707 return CreateTextPosition(tree_id_, anchor_id_, 0 /* text_offset */,
1709 }
1710 return CreateNullPosition();
1711 }
1712
1714 switch (kind_) {
1716 return CreateNullPosition();
1718 return CreateTreePosition(
1719 tree_id_, anchor_id_,
1722 return CreateTextPosition(tree_id_, anchor_id_, MaxTextOffset(),
1724 }
1725 return CreateNullPosition();
1726 }
1727
1730 return Clone();
1731
1732 // First check for positions on nodes which are AXTree boundaries, but where
1733 // the text position on that node is not at the start of the anchor.
1736
1737 // Iterate over tree positions until a boundary is reached.
1738 AXPositionInstance previous_position = AsTreePosition();
1739 do {
1740 previous_position = previous_position->CreatePreviousAnchorPosition();
1741 } while (!previous_position->AtStartOfAXTree());
1742
1743 // This method should not cross tree boundaries.
1744 BASE_DCHECK(previous_position->tree_id() == tree_id());
1745
1746 if (IsTextPosition())
1747 previous_position = previous_position->AsTextPosition();
1748 return previous_position;
1749 }
1750
1752 if (IsNullPosition() || AtEndOfAXTree())
1753 return Clone();
1754
1755 // First check for positions on nodes which are AXTree boundaries, but where
1756 // the text position on that node is not at the end of the anchor.
1759
1760 // Iterate over tree positions until a boundary is reached.
1761 AXPositionInstance next_position = AsTreePosition();
1762 do {
1763 next_position = next_position->CreateNextAnchorPosition()
1764 ->CreatePositionAtEndOfAnchor();
1765 } while (!next_position->AtEndOfAXTree());
1766
1767 // This method should not cross tree boundaries.
1768 BASE_DCHECK(next_position->tree_id() == tree_id());
1769
1770 if (IsTextPosition())
1771 next_position = next_position->AsTextPosition();
1772 return next_position->CreatePositionAtEndOfAnchor();
1773 }
1774
1775 // "document" is defined here as a single, top-level, navigatable unit from
1776 // a user's perspective. This means that all iframes are part of a single
1777 // "document" that contains the top-level navigatable page. So this method
1778 // will break out of an iframe and return a position at the start of the
1779 // top-level document.
1780 //
1781 // Note that this definition is different than HTML's definition of
1782 // "document", where each iframe has its own document object. For a similar
1783 // method that stops at iframe boundaries, see
1784 // CreatePositionAtStartOfAXTree().
1786 AXPositionInstance position =
1787 AsTreePosition()->CreateDocumentAncestorPosition();
1788 if (!position->IsNullPosition()) {
1789 position = position->CreatePositionAtStartOfAnchor();
1790 if (IsTextPosition())
1791 position = position->AsTextPosition();
1792 }
1793 return position;
1794 }
1795
1796 // "document" is defined here as a single, top-level, navigatable unit from
1797 // a user's perspective. This means that all iframes are part of a single
1798 // "document" that contains the top-level navigatable page. So this method
1799 // will break out of an iframe and return a position at the end of the
1800 // top-level document.
1801 //
1802 // Note that this definition is different than HTML's definition of
1803 // "document", where each iframe has its own document object. For a similar
1804 // method that stops at iframe boundaries, see CreatePositionAtEndOfAXTree().
1806 AXPositionInstance position =
1807 AsTreePosition()->CreateDocumentAncestorPosition();
1808 if (!position->IsNullPosition()) {
1809 while (!position->IsLeaf()) {
1810 position =
1811 position->CreateChildPositionAt(position->AnchorChildCount() - 1);
1812 }
1813 position = position->CreatePositionAtEndOfAnchor();
1814 if (IsTextPosition())
1815 position = position->AsTextPosition();
1816 }
1817 return position;
1818 }
1819
1821 if (IsNullPosition() || IsLeaf())
1822 return CreateNullPosition();
1823
1824 if (child_index < 0 || child_index >= AnchorChildCount())
1825 return CreateNullPosition();
1826
1829 AnchorChild(child_index, &tree_id, &child_id);
1831 BASE_DCHECK(child_id != AXNode::kInvalidAXID);
1832 switch (kind_) {
1835 return CreateNullPosition();
1837 AXPositionInstance child_position =
1838 CreateTreePosition(tree_id, child_id, 0 /* child_index */);
1839 // If the child's anchor is a leaf node, make this a "before text"
1840 // position.
1841 if (child_position->IsLeaf())
1842 child_position->child_index_ = BEFORE_TEXT;
1843 return child_position;
1844 }
1846 return CreateTextPosition(tree_id, child_id, 0 /* text_offset */,
1848 }
1849
1850 return CreateNullPosition();
1851 }
1852
1853 // Creates a parent equivalent position.
1854 //
1855 // "move_direction" is used only in the case of a text position, when in
1856 // the process of searching for a text boundary, and on platforms where child
1857 // nodes are represented by embedded object characters. On such platforms, the
1858 // "IsEmbeddedObjectInParent" method returns true. We need to decide whether
1859 // to create a parent equivalent position that is before or after the child
1860 // node, since moving to a parent position would always cause us to lose some
1861 // information. We can't simply re-use the text offset of the child position
1862 // because by definition the parent node doesn't include all the text of the
1863 // child node, but only a single embedded object character.
1864 //
1865 // staticText name='Line one' IA2-hypertext='<embedded_object>'
1866 // ++inlineTextBox name='Line one'
1867 //
1868 // If we are given a text position pointing to somewhere inside the
1869 // inlineTextBox, and we move to the parent equivalent position, we need to
1870 // decide whether the parent position would be set to point to before the
1871 // embedded object character or after it. Both are valid, depending on the
1872 // direction on motion, e.g. if we are trying to find the start of the line
1873 // vs. the end of the line.
1875 ax::mojom::MoveDirection move_direction =
1877 if (IsNullPosition())
1878 return CreateNullPosition();
1879
1882 AnchorParent(&tree_id, &parent_id);
1883 if (tree_id == AXTreeIDUnknown() || parent_id == AXNode::kInvalidAXID)
1884 return CreateNullPosition();
1885
1886 switch (kind_) {
1889 return CreateNullPosition();
1891 return CreateTreePosition(tree_id, parent_id, AnchorIndexInParent());
1893 // On some platforms, such as Android, Mac and Chrome OS, the inner text
1894 // of a node is made up by concatenating the text of child nodes. On
1895 // other platforms, such as Windows IA2 and Linux ATK, child nodes are
1896 // represented by a single embedded object character.
1897 //
1898 // If our parent's inner text is a concatenation of all its children's
1899 // text, we need to maintain the affinity and compute the corresponding
1900 // text offset. Otherwise, we have no choice but to return a position
1901 // that is either before or after this child, losing some information in
1902 // the process. Regardless to whether our parent contains all our text,
1903 // we always recompute the affinity when the position is after the
1904 // child.
1905 //
1906 // Recomputing the affinity in the latter situation is important because
1907 // even though a text position might unambiguously be at the end of a
1908 // line, its parent position might be the same as the parent position of
1909 // a position that represents the start of the next line. For example:
1910 //
1911 // staticText name='Line oneLine two'
1912 // ++inlineTextBox name='Line one'
1913 // ++inlineTextBox name='Line two'
1914 //
1915 // If the original position is at the end of the inline text box for
1916 // "Line one", then the resulting parent equivalent position would be
1917 // the same as the one that would have been computed if the original
1918 // position were at the start of the inline text box for "Line two".
1919 const int max_text_offset = MaxTextOffset();
1920 int max_text_offset_in_parent =
1921 IsEmbeddedObjectInParent() ? 1 : max_text_offset;
1922 int parent_offset = AnchorTextOffsetInParent();
1923 ax::mojom::TextAffinity parent_affinity = affinity_;
1924 if (max_text_offset == max_text_offset_in_parent) {
1925 // Our parent contains all our text. No information would be lost when
1926 // moving to a parent equivalent position.
1927 parent_offset += text_offset_;
1928 } else if (text_offset_ > 0) {
1929 // If "text_offset_" == 0, then the child position is clearly before
1930 // any embedded object character. No information would be lost when
1931 // moving to a parent equivalent position, including affinity
1932 // information. Otherwise, we should decide whether to set the parent
1933 // position to be before or after the child, based on the direction of
1934 // motion, and also reset the affinity.
1935 switch (move_direction) {
1937 // Keep the offset to be right before the embedded object
1938 // character.
1939 break;
1941 // Set the offset to be after the embedded object character.
1942 parent_offset += max_text_offset_in_parent;
1943 break;
1944 }
1945
1946 // The original affinity doesn't apply any more. In most cases, it
1947 // should be downstream, unless there is an ambiguity as to whether
1948 // the parent position is between the end of one line and the start of
1949 // the next. We perform this check below.
1950 parent_affinity = ax::mojom::TextAffinity::kDownstream;
1951 }
1952
1953 // This dummy position serves to retrieve the max text offset of the
1954 // anchor-node in which we want to create the parent position.
1955 AXPositionInstance dummy_position =
1956 CreateTextPosition(tree_id, parent_id, 0, parent_affinity);
1957 max_text_offset_in_parent = dummy_position->MaxTextOffset();
1958 if (parent_offset > max_text_offset_in_parent) {
1959 parent_offset = max_text_offset_in_parent;
1960 }
1961 AXPositionInstance parent_position = CreateTextPosition(
1962 tree_id, parent_id, parent_offset, parent_affinity);
1963
1964 // If the current position is pointing at the end of its anchor, we need
1965 // to check if the parent position has introduced ambiguity as to
1966 // whether it refers to the end of a line or the start of the next.
1967 // Ambiguity is only present when the parent position points to a text
1968 // offset that is neither at the start nor at the end of its anchor. We
1969 // check for ambiguity by creating the parent position and testing if it
1970 // is erroneously at the start of the next line. Given that the current
1971 // position, by the nature of being at the end of its anchor, could only
1972 // be at end of line, the fact that the parent position is also
1973 // determined to be at start of line demonstrates the presence of
1974 // ambiguity which is resolved by setting its affinity to upstream.
1975 //
1976 // We could not have
1977 // checked if the child was at the end of the line, because our
1978 // "AtEndOfLine" predicate takes into account trailing line breaks,
1979 // which would create false positives.
1980 if (text_offset_ == max_text_offset &&
1981 !parent_position->AtEndOfAnchor() &&
1982 parent_position->AtStartOfLine()) {
1983 parent_position->affinity_ = ax::mojom::TextAffinity::kUpstream;
1984 }
1985 return parent_position;
1986 }
1987 }
1988
1989 return CreateNullPosition();
1990 }
1991
1992 // Creates a tree position using the next text-only node as its anchor.
1993 // Assumes that text-only nodes are leaf nodes.
1995 return CreateNextLeafTreePosition(&DefaultAbortMovePredicate);
1996 }
1997
1998 // Creates a tree position using the previous text-only node as its anchor.
1999 // Assumes that text-only nodes are leaf nodes.
2001 return CreatePreviousLeafTreePosition(&DefaultAbortMovePredicate);
2002 }
2003
2004 // Creates the next text position anchored at a leaf node of the AXTree.
2005 //
2006 // If a pointer |crossed_line_breaking_object| is provided, it'll be set to
2007 // |true| if any line breaking object boundary was crossed by moving from this
2008 // leaf text position to the next (if it exists), |false| otherwise.
2010 bool* crossed_line_breaking_object = nullptr) const {
2011 if (crossed_line_breaking_object)
2012 *crossed_line_breaking_object = false;
2013
2014 // If this is an ancestor text position, resolve to its leaf text position.
2015 if (IsTextPosition() && !IsLeaf())
2016 return AsLeafTextPosition();
2017 std::function<AbortMovePredicate> abort_move_predicate;
2018 if (crossed_line_breaking_object) {
2019 abort_move_predicate = [crossed_line_breaking_object](
2020 const AXPosition& move_from,
2021 const AXPosition& move_to,
2022 const AXMoveType type,
2023 const AXMoveDirection direction) {
2024 return UpdateCrossedLineBreakingObjectToken(
2025 *crossed_line_breaking_object, move_from, move_to, type, direction);
2026 };
2027 } else {
2028 abort_move_predicate =
2029 [](const AXPosition& move_from, const AXPosition& move_to,
2030 const AXMoveType type, const AXMoveDirection direction) {
2031 return AXPosition::DefaultAbortMovePredicate(move_from, move_to,
2032 type, direction);
2033 };
2034 }
2035 return CreateNextLeafTreePosition(abort_move_predicate)->AsTextPosition();
2036 }
2037
2038 // Creates a text position using the previous text-only node as its anchor.
2039 // Assumes that text-only nodes are leaf nodes.
2041 return CreatePreviousTextAnchorPosition(DefaultAbortMovePredicate);
2042 }
2043
2044 // Returns a text position located right before the next character (from this
2045 // position) in the tree's text representation, following these conditions:
2046 //
2047 // - If this position is at the end of its anchor, normalize it to the start
2048 // of the next text anchor, regardless of the position's affinity.
2049 // Both text positions are equal when compared, but we consider the start of
2050 // an anchor to be a position BEFORE its first character and the end of the
2051 // previous to be AFTER its last character.
2052 //
2053 // - Skip any empty text anchors; they're "invisible" to the text
2054 // representation and the next character could be ahead.
2055 //
2056 // - Return a null position if there is no next character forward.
2057 //
2058 // If possible, return a position anchored at the current position's anchor;
2059 // this is necessary because we don't want to return any position that might
2060 // be located in the shadow DOM or in a position anchored at a node that is
2061 // not visible to a specific platform's APIs.
2062 //
2063 // Also, |text_offset| is adjusted to point to a valid character offset, i.e.
2064 // it cannot be pointing to a low surrogate pair or to the middle of a
2065 // grapheme cluster.
2067 if (IsNullPosition())
2068 return Clone();
2069
2070 AXPositionInstance text_position = AsTextPosition();
2071 // In case the input affinity is upstream, reset it to downstream.
2072 //
2073 // This is to ensure that when we find the equivalent leaf text position, it
2074 // will be at the start of anchor if the original position is anchored to a
2075 // node higher up in the tree and pointing to a text offset that falls on
2076 // the boundary between two leaf nodes. In other words, the returned
2077 // position will always be "before character".
2078 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
2079 text_position = text_position->AsLeafTextPosition();
2080 BASE_DCHECK(!text_position->IsNullPosition())
2081 << "Adjusting to a leaf position should never turn a non-null position "
2082 "into a null one.";
2083 if (!text_position->IsIgnored() && !text_position->AtEndOfAnchor()) {
2084 BASE_DCHECK(text_position->text_offset_ >= 0);
2085 return text_position;
2086 }
2087
2088 text_position = text_position->CreateNextLeafTextPosition();
2089 while (!text_position->IsNullPosition() &&
2090 (text_position->IsIgnored() || !text_position->MaxTextOffset())) {
2091 text_position = text_position->CreateNextLeafTextPosition();
2092 }
2093 return text_position;
2094 }
2095
2096 // Returns a text position located right after the previous character (from
2097 // this position) in the tree's text representation.
2098 //
2099 // See `AsLeafTextPositionBeforeCharacter`, as this is its "reversed" version.
2101 if (IsNullPosition())
2102 return Clone();
2103
2104 AXPositionInstance text_position = AsTextPosition();
2105 // Temporarily set the affinity to upstream.
2106 //
2107 // This is to ensure that when we find the equivalent leaf text position, it
2108 // will be at the end of anchor if the original position is anchored to a
2109 // node higher up in the tree and pointing to a text offset that falls on
2110 // the boundary between two leaf nodes. In other words, the returned
2111 // position will always be "after character".
2112 text_position->affinity_ = ax::mojom::TextAffinity::kUpstream;
2113 text_position = text_position->AsLeafTextPosition();
2114 BASE_DCHECK(!text_position->IsNullPosition())
2115 << "Adjusting to a leaf position should never turn a non-null position "
2116 "into a null one.";
2117 if (!text_position->IsIgnored() && !text_position->AtStartOfAnchor()) {
2118 // The following situation should not be possible but there are existing
2119 // crashes in the field.
2120 //
2121 // TODO(nektar): Remove this workaround as soon as the source of the bug
2122 // is identified.
2123 BASE_DCHECK(text_position->text_offset_ >= 0);
2124 // TODO(chunhtai): handles grapheme.
2125
2126 // Reset the affinity to downstream, because an upstream affinity doesn't
2127 // make sense on a leaf anchor.
2128 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
2129 return text_position;
2130 }
2131 text_position = text_position->CreatePreviousLeafTextPosition();
2132 while (!text_position->IsNullPosition() &&
2133 (text_position->IsIgnored() || !text_position->MaxTextOffset())) {
2134 text_position = text_position->CreatePreviousLeafTextPosition();
2135 }
2136 return text_position->CreatePositionAtEndOfAnchor();
2137 }
2138
2139 // Creates a position pointing to before the next character, which is defined
2140 // as the start of the next grapheme cluster. Also, ensures that the created
2141 // position will not point to a low surrogate pair.
2142 //
2143 // A grapheme cluster is what an end-user would consider a character and it
2144 // could include a letter with additional diacritics. It could be more than
2145 // one Unicode code unit in length.
2146 //
2147 // See also http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
2149 AXBoundaryBehavior boundary_behavior) const {
2150 if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary &&
2151 AtEndOfAnchor()) {
2152 return Clone();
2153 }
2154
2155 // There is no next character position.
2157 if (text_position->IsNullPosition()) {
2158 if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary ||
2159 boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary) {
2160 text_position = Clone();
2161 }
2162 return text_position;
2163 }
2164
2165 if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary &&
2166 *text_position == *this) {
2167 return Clone();
2168 }
2169 BASE_DCHECK(text_position->text_offset_ < text_position->MaxTextOffset());
2170 // TODO(chunhtai): Need to consider grapheme cluster.
2171 ++text_position->text_offset_;
2172 BASE_DCHECK(text_position->text_offset_ > 0);
2173 BASE_DCHECK(text_position->text_offset_ <= text_position->MaxTextOffset());
2174 // If the character boundary is in the same subtree, return a position
2175 // rooted at this position's anchor. This is necessary because we don't want
2176 // to return a position that might be in the shadow DOM when this position
2177 // is not.
2178 const AXNodeType* common_anchor = text_position->LowestCommonAnchor(*this);
2179 if (GetAnchor() == common_anchor) {
2180 text_position = text_position->CreateAncestorPosition(
2181 common_anchor, ax::mojom::MoveDirection::kForward);
2182 } else if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary) {
2183 // If the next character position crosses the current anchor boundary
2184 // with StopAtAnchorBoundary, snap to the end of the current anchor.
2186 }
2187 // Even if the resulting position is right on a soft line break, affinity is
2188 // defaulted to downstream so that this method will always produce the same
2189 // result regardless of the direction of motion or the input affinity.
2190 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
2191 if (IsTreePosition())
2192 return text_position->AsTreePosition();
2193 return text_position;
2194 }
2195
2196 // Creates a position pointing to before the previous character, which is
2197 // defined as the start of the previous grapheme cluster. Also, ensures that
2198 // the created position will not point to a low surrogate pair.
2199 //
2200 // See the comment above `CreateNextCharacterPosition` for the definition of a
2201 // grapheme cluster.
2203 AXBoundaryBehavior boundary_behavior) const {
2204 if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary &&
2205 AtStartOfAnchor()) {
2206 return Clone();
2207 }
2208
2209 // There is no previous character position.
2211 if (text_position->IsNullPosition()) {
2212 if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary ||
2213 boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary) {
2214 text_position = Clone();
2215 }
2216 return text_position;
2217 }
2218
2219 if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary &&
2220 *text_position == *this) {
2221 return Clone();
2222 }
2223 BASE_DCHECK(text_position->text_offset_ > 0);
2224 // TODO(chunhtai): Need to consider grapheme cluster.
2225 --text_position->text_offset_;
2226 BASE_DCHECK(text_position->text_offset_ >= 0);
2227 BASE_DCHECK(text_position->text_offset_ < text_position->MaxTextOffset());
2228 // The character boundary should be in the same subtree. Return a position
2229 // rooted at this position's anchor. This is necessary because we don't want
2230 // to return a position that might be in the shadow DOM when this position
2231 // is not.
2232 const AXNodeType* common_anchor = text_position->LowestCommonAnchor(*this);
2233 if (GetAnchor() == common_anchor) {
2234 text_position = text_position->CreateAncestorPosition(
2236 } else if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary) {
2237 // If the previous character position crosses the current anchor boundary
2238 // with StopAtAnchorBoundary, snap to the start of the current anchor.
2240 }
2241 // Even if the resulting position is right on a soft line break, affinity is
2242 // defaulted to downstream so that this method will always produce the same
2243 // result regardless of the direction of motion or the input affinity.
2244 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
2245 if (IsTreePosition())
2246 return text_position->AsTreePosition();
2247 return text_position;
2248 }
2249
2251 AXBoundaryBehavior boundary_behavior) const {
2253 boundary_behavior, ax::mojom::MoveDirection::kForward,
2254 &AtStartOfWordPredicate, &AtEndOfWordPredicate,
2255 &GetWordStartOffsetsFunc);
2256 }
2257
2259 AXBoundaryBehavior boundary_behavior) const {
2261 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2262 &AtStartOfWordPredicate, &AtEndOfWordPredicate,
2263 &GetWordStartOffsetsFunc);
2264 }
2265
2266 // Word end positions are one past the last character of the word.
2268 AXBoundaryBehavior boundary_behavior) const {
2270 boundary_behavior, ax::mojom::MoveDirection::kForward,
2271 &AtStartOfWordPredicate, &AtEndOfWordPredicate, &GetWordEndOffsetsFunc);
2272 }
2273
2274 // Word end positions are one past the last character of the word.
2276 AXBoundaryBehavior boundary_behavior) const {
2278 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2279 &AtStartOfWordPredicate, &AtEndOfWordPredicate, &GetWordEndOffsetsFunc);
2280 }
2281
2283 AXBoundaryBehavior boundary_behavior) const {
2285 boundary_behavior, ax::mojom::MoveDirection::kForward,
2286 &AtStartOfLinePredicate, &AtEndOfLinePredicate);
2287 }
2288
2290 AXBoundaryBehavior boundary_behavior) const {
2292 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2293 &AtStartOfLinePredicate, &AtEndOfLinePredicate);
2294 }
2295
2296 // Line end positions are one past the last character of the line, excluding
2297 // any white space or newline characters that separate the lines.
2299 AXBoundaryBehavior boundary_behavior) const {
2301 boundary_behavior, ax::mojom::MoveDirection::kForward,
2302 &AtStartOfLinePredicate, &AtEndOfLinePredicate);
2303 }
2304
2305 // Line end positions are one past the last character of the line, excluding
2306 // any white space or newline characters separating the lines.
2308 AXBoundaryBehavior boundary_behavior) const {
2310 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2311 &AtStartOfLinePredicate, &AtEndOfLinePredicate);
2312 }
2313
2315 AXBoundaryBehavior boundary_behavior) const {
2316 if (IsNullPosition())
2317 return Clone();
2318
2320 if (boundary_type != AXBoundaryType::kNone) {
2321 if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary ||
2322 (boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary &&
2323 boundary_type == AXBoundaryType::kDocumentStart)) {
2324 AXPositionInstance clone = Clone();
2325 // In order to make equality checks simpler, affinity should be reset so
2326 // that we would get consistent output from this function regardless of
2327 // input affinity.
2328 clone->affinity_ = ax::mojom::TextAffinity::kDownstream;
2329 return clone;
2330 } else if (boundary_behavior == AXBoundaryBehavior::CrossBoundary &&
2331 boundary_type == AXBoundaryType::kDocumentStart) {
2332 // If we're at a format boundary and there are no more text positions
2333 // to traverse, return a null position for cross-boundary moves.
2334 return CreateNullPosition();
2335 }
2336 }
2337
2338 AXPositionInstance tree_position =
2339 AsTreePosition()->CreatePositionAtStartOfAnchor();
2340 AXPositionInstance previous_tree_position =
2341 tree_position->CreatePreviousLeafTreePosition();
2342
2343 // If moving to the start of the current anchor hasn't changed our position
2344 // from the original position, we need to test the previous leaf tree
2345 // position.
2346 if (AtStartOfAnchor() &&
2347 boundary_behavior != AXBoundaryBehavior::StopIfAlreadyAtBoundary) {
2348 tree_position = std::move(previous_tree_position);
2349 previous_tree_position = tree_position->CreatePreviousLeafTreePosition();
2350 }
2351
2352 // The first position in the document is also a format start boundary, so we
2353 // should not return NullPosition unless we started from that location.
2354 while (boundary_type != AXBoundaryType::kDocumentStart &&
2355 !previous_tree_position->IsNullPosition() &&
2356 !tree_position->AtStartOfFormat()) {
2357 tree_position = std::move(previous_tree_position);
2358 previous_tree_position = tree_position->CreatePreviousLeafTreePosition();
2359 }
2360
2361 // If the format boundary is in the same subtree, return a position rooted
2362 // at the current position.
2363 // This is necessary because we don't want to return any position that might
2364 // be in the shadow DOM if the original position was not.
2365 const AXNodeType* common_anchor = tree_position->LowestCommonAnchor(*this);
2366 if (GetAnchor() == common_anchor) {
2367 tree_position = tree_position->CreateAncestorPosition(
2369 } else if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary) {
2371 }
2372
2373 if (IsTextPosition())
2374 return tree_position->AsTextPosition();
2375 return tree_position;
2376 }
2377
2379 AXBoundaryBehavior boundary_behavior) const {
2380 if (IsNullPosition())
2381 return Clone();
2382
2383 AXBoundaryType boundary_type = GetFormatEndBoundaryType();
2384 if (boundary_type != AXBoundaryType::kNone) {
2385 if (boundary_behavior == AXBoundaryBehavior::StopIfAlreadyAtBoundary ||
2386 (boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary &&
2387 boundary_type == AXBoundaryType::kDocumentEnd)) {
2388 AXPositionInstance clone = Clone();
2389 // In order to make equality checks simpler, affinity should be reset so
2390 // that we would get consistent output from this function regardless of
2391 // input affinity.
2392 clone->affinity_ = ax::mojom::TextAffinity::kDownstream;
2393 return clone;
2394 } else if (boundary_behavior == AXBoundaryBehavior::CrossBoundary &&
2395 boundary_type == AXBoundaryType::kDocumentEnd) {
2396 // If we're at a format boundary and there are no more text positions
2397 // to traverse, return a null position for cross-boundary moves.
2398 return CreateNullPosition();
2399 }
2400 }
2401
2402 AXPositionInstance tree_position =
2403 AsTreePosition()->CreatePositionAtEndOfAnchor();
2404 AXPositionInstance next_tree_position =
2405 tree_position->CreateNextLeafTreePosition()
2406 ->CreatePositionAtEndOfAnchor();
2407
2408 // If moving to the end of the current anchor hasn't changed our original
2409 // position, we need to test the next leaf tree position.
2410 if (AtEndOfAnchor() &&
2411 boundary_behavior != AXBoundaryBehavior::StopIfAlreadyAtBoundary) {
2412 tree_position = std::move(next_tree_position);
2413 next_tree_position = tree_position->CreateNextLeafTreePosition()
2414 ->CreatePositionAtEndOfAnchor();
2415 }
2416
2417 // The last position in the document is also a format end boundary, so we
2418 // should not return NullPosition unless we started from that location.
2419 while (boundary_type != AXBoundaryType::kDocumentEnd &&
2420 !next_tree_position->IsNullPosition() &&
2421 !tree_position->AtEndOfFormat()) {
2422 tree_position = std::move(next_tree_position);
2423 next_tree_position = tree_position->CreateNextLeafTreePosition()
2424 ->CreatePositionAtEndOfAnchor();
2425 }
2426
2427 // If the format boundary is in the same subtree, return a position
2428 // rooted at the current position.
2429 // This is necessary because we don't want to return any position that might
2430 // be in the shadow DOM if the original position was not.
2431 const AXNodeType* common_anchor = tree_position->LowestCommonAnchor(*this);
2432 if (GetAnchor() == common_anchor) {
2433 tree_position = tree_position->CreateAncestorPosition(
2434 common_anchor, ax::mojom::MoveDirection::kForward);
2435 } else if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary) {
2437 }
2438
2439 if (IsTextPosition())
2440 return tree_position->AsTextPosition();
2441 return tree_position;
2442 }
2443
2445 AXBoundaryBehavior boundary_behavior) const {
2447 boundary_behavior, ax::mojom::MoveDirection::kForward,
2448 &AtStartOfParagraphPredicate, &AtEndOfParagraphPredicate);
2449 }
2450
2452 AXBoundaryBehavior boundary_behavior) const {
2454 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2455 &AtStartOfParagraphPredicate, &AtEndOfParagraphPredicate);
2456 }
2457
2459 AXBoundaryBehavior boundary_behavior) const {
2461 boundary_behavior, ax::mojom::MoveDirection::kForward,
2462 &AtStartOfParagraphPredicate, &AtEndOfParagraphPredicate);
2463 }
2464
2466 AXBoundaryBehavior boundary_behavior) const {
2468 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2469 &AtStartOfParagraphPredicate, &AtEndOfParagraphPredicate);
2470 if (boundary_behavior == AXBoundaryBehavior::CrossBoundary ||
2471 boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary) {
2472 // This is asymmetric with CreateNextParagraphEndPosition due to
2473 // asymmetries in text anchor movement. Consider:
2474 //
2475 // ++1 rootWebArea
2476 // ++++2 staticText name="FIRST"
2477 // ++++3 genericContainer isLineBreakingObject=true
2478 // ++++++4 genericContainer isLineBreakingObject=true
2479 // ++++++5 staticText name="SECOND"
2480 //
2481 // Node 2 offset 5 FIRST<> is a paragraph end since node 3 is a line-
2482 // breaking object that's not collapsible (since it's not a leaf). When
2483 // looking for the next text anchor position from there, we advance to
2484 // sibling node 3, then since that node has descendants, we convert to a
2485 // tree position to find the leaf node that maps to "node 3 offset 0".
2486 // Since node 4 has no text, we skip it and land on node 5. We end up at
2487 // node 5 offset 6 SECOND<> as our next paragraph end.
2488 //
2489 // The set of paragraph ends should be consistent when moving in the
2490 // reverse direction. But starting from node 5 offset 6, the previous text
2491 // anchor position is previous sibling node 4. We'll consider that a
2492 // paragraph end since it's a leaf line-breaking object and stop.
2493 //
2494 // Essentially, we have two consecutive line-breaking objects, each of
2495 // which stops movement in the "outward" direction, for different reasons.
2496 //
2497 // We handle this by looking back one more step after finding a candidate
2498 // for previous paragraph end, then testing a forward step from the look-
2499 // back position. That will land us on the candidate position if it's a
2500 // valid paragraph boundary.
2501 //
2502 while (!previous_position->IsNullPosition()) {
2503 AXPositionInstance look_back_position =
2504 previous_position->AsLeafTextPosition()
2505 ->CreatePreviousLeafTextPosition()
2506 ->CreatePositionAtEndOfAnchor();
2507 if (look_back_position->IsNullPosition()) {
2508 // Nowhere to look back to, so our candidate must be a valid paragraph
2509 // boundary.
2510 break;
2511 }
2512 AXPositionInstance forward_step_position =
2513 look_back_position->CreateNextLeafTextPosition()
2514 ->CreatePositionAtEndOfAnchor();
2515 if (*forward_step_position == *previous_position)
2516 break;
2517
2518 previous_position = previous_position->CreateBoundaryEndPosition(
2519 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2520 &AtStartOfParagraphPredicate, &AtEndOfParagraphPredicate);
2521 }
2522 }
2523
2524 return previous_position;
2525 }
2526
2528 AXBoundaryBehavior boundary_behavior) const {
2530 boundary_behavior, ax::mojom::MoveDirection::kForward,
2531 &AtStartOfPagePredicate, &AtEndOfPagePredicate);
2532 }
2533
2535 AXBoundaryBehavior boundary_behavior) const {
2537 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2538 &AtStartOfPagePredicate, &AtEndOfPagePredicate);
2539 }
2540
2542 AXBoundaryBehavior boundary_behavior) const {
2544 boundary_behavior, ax::mojom::MoveDirection::kForward,
2545 &AtStartOfPagePredicate, &AtEndOfPagePredicate);
2546 }
2547
2549 AXBoundaryBehavior boundary_behavior) const {
2551 boundary_behavior, ax::mojom::MoveDirection::kBackward,
2552 &AtStartOfPagePredicate, &AtEndOfPagePredicate);
2553 }
2554
2556 AXBoundaryBehavior boundary_behavior,
2557 ax::mojom::MoveDirection move_direction,
2558 BoundaryConditionPredicate at_start_condition,
2559 BoundaryConditionPredicate at_end_condition,
2560 BoundaryTextOffsetsFunc get_start_offsets = {}) const {
2561 AXPositionInstance text_position = AsLeafTextPosition();
2562 if (text_position->IsNullPosition())
2563 return text_position;
2564
2565 if (boundary_behavior != AXBoundaryBehavior::StopIfAlreadyAtBoundary) {
2566 text_position =
2567 text_position->CreateAdjacentLeafTextPosition(move_direction);
2568 if (text_position->IsNullPosition()) {
2569 // There is no adjacent position to move to; in such case, CrossBoundary
2570 // behavior shall return a null position, while any other behavior shall
2571 // fallback to return the initial position.
2572 if (boundary_behavior == AXBoundaryBehavior::CrossBoundary)
2573 return text_position;
2574 return Clone();
2575 }
2576 }
2577
2578 if (!at_start_condition(text_position)) {
2579 text_position = text_position->CreatePositionAtNextOffsetBoundary(
2580 move_direction, get_start_offsets);
2581
2582 while (!at_start_condition(text_position)) {
2583 AXPositionInstance next_position;
2584 if (move_direction == ax::mojom::MoveDirection::kForward) {
2585 next_position = text_position->CreateNextLeafTextPosition();
2586 } else {
2587 if (text_position->AtStartOfAnchor()) {
2588 next_position = text_position->CreatePreviousLeafTextPosition();
2589 } else {
2590 text_position = text_position->CreatePositionAtStartOfAnchor();
2591 BASE_DCHECK(!text_position->IsNullPosition());
2592 continue;
2593 }
2594 }
2595
2596 if (next_position->IsNullPosition()) {
2597 if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary) {
2598 switch (move_direction) {
2600 return CreatePositionAtEndOfAnchor()->AsUnignoredPosition(
2603 return CreatePositionAtStartOfAnchor()->AsUnignoredPosition(
2605 }
2606 }
2607
2608 if (boundary_behavior ==
2610 // We can't simply return the following position; break and after
2611 // this loop we'll try to do some adjustments to text_position.
2612 switch (move_direction) {
2614 text_position = text_position->CreatePositionAtEndOfAnchor();
2615 break;
2617 text_position = text_position->CreatePositionAtStartOfAnchor();
2618 break;
2619 }
2620
2621 break;
2622 }
2623
2624 return next_position->AsUnignoredPosition(
2625 AdjustmentBehaviorFromBoundaryDirection(move_direction));
2626 }
2627
2628 // Continue searching for the next boundary start in the specified
2629 // direction until the next logical text position is reached.
2630 text_position = next_position->CreatePositionAtFirstOffsetBoundary(
2631 move_direction, get_start_offsets);
2632 }
2633 }
2634
2635 // If the boundary is in the same subtree, return a position rooted at this
2636 // position's anchor. This is necessary because we don't want to return a
2637 // position that might be in the shadow DOM when this position is not.
2638 const AXNodeType* common_anchor = text_position->LowestCommonAnchor(*this);
2639 if (GetAnchor() == common_anchor) {
2640 text_position =
2641 text_position->CreateAncestorPosition(common_anchor, move_direction);
2642 } else if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary) {
2643 switch (move_direction) {
2645 return CreatePositionAtEndOfAnchor()->AsUnignoredPosition(
2648 return CreatePositionAtStartOfAnchor()->AsUnignoredPosition(
2650 }
2651 }
2652
2653 // Affinity is only upstream at the end of a line, and so a start boundary
2654 // will never have an upstream affinity.
2655 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
2656 if (IsTreePosition())
2657 text_position = text_position->AsTreePosition();
2658 AXPositionInstance unignored_position = text_position->AsUnignoredPosition(
2659 AdjustmentBehaviorFromBoundaryDirection(move_direction));
2660 // If there are no unignored positions in |move_direction| then
2661 // |text_position| is anchored in ignored content at the start or end
2662 // of the document.
2663 // For StopAtLastAnchorBoundary, try to adjust in the opposite direction
2664 // to return a position within the document just before crossing into
2665 // the ignored content. This will be the last unignored anchor boundary.
2666 if (unignored_position->IsNullPosition() &&
2667 boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary) {
2668 unignored_position =
2669 text_position->AsUnignoredPosition(OppositeAdjustmentBehavior(
2670 AdjustmentBehaviorFromBoundaryDirection(move_direction)));
2671 }
2672 return unignored_position;
2673 }
2674
2676 AXBoundaryBehavior boundary_behavior,
2677 ax::mojom::MoveDirection move_direction,
2678 BoundaryConditionPredicate at_start_condition,
2679 BoundaryConditionPredicate at_end_condition,
2680 BoundaryTextOffsetsFunc get_end_offsets = {}) const {
2681 AXPositionInstance text_position = AsLeafTextPosition();
2682 if (text_position->IsNullPosition())
2683 return text_position;
2684
2685 if (boundary_behavior != AXBoundaryBehavior::StopIfAlreadyAtBoundary) {
2686 text_position =
2687 text_position->CreateAdjacentLeafTextPosition(move_direction);
2688 if (text_position->IsNullPosition()) {
2689 // There is no adjacent position to move to; in such case, CrossBoundary
2690 // behavior shall return a null position, while any other behavior shall
2691 // fallback to return the initial position.
2692 if (boundary_behavior == AXBoundaryBehavior::CrossBoundary)
2693 return text_position;
2694 return Clone();
2695 }
2696 }
2697
2698 if (!at_end_condition(text_position)) {
2699 text_position = text_position->CreatePositionAtNextOffsetBoundary(
2700 move_direction, get_end_offsets);
2701
2702 while (!at_end_condition(text_position)) {
2703 AXPositionInstance next_position;
2704 if (move_direction == ax::mojom::MoveDirection::kForward) {
2705 if (text_position->AtEndOfAnchor()) {
2706 next_position = text_position->CreateNextLeafTextPosition();
2707 } else {
2708 text_position = text_position->CreatePositionAtEndOfAnchor();
2709 BASE_DCHECK(!text_position->IsNullPosition());
2710 continue;
2711 }
2712 } else {
2713 next_position = text_position->CreatePreviousLeafTextPosition()
2714 ->CreatePositionAtEndOfAnchor();
2715 }
2716
2717 if (next_position->IsNullPosition()) {
2718 if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary) {
2719 switch (move_direction) {
2721 return CreatePositionAtEndOfAnchor()->AsUnignoredPosition(
2724 return CreatePositionAtStartOfAnchor()->AsUnignoredPosition(
2726 }
2727 }
2728
2729 if (boundary_behavior ==
2731 // We can't simply return the following position; break and after
2732 // this loop we'll try to do some adjustments to text_position.
2733 switch (move_direction) {
2735 text_position = text_position->CreatePositionAtEndOfAnchor();
2736 break;
2738 text_position = text_position->CreatePositionAtStartOfAnchor();
2739 break;
2740 }
2741
2742 break;
2743 }
2744
2745 return next_position->AsUnignoredPosition(
2746 AdjustmentBehaviorFromBoundaryDirection(move_direction));
2747 }
2748
2749 // Continue searching for the next boundary end in the specified
2750 // direction until the next logical text position is reached.
2751 text_position = next_position->CreatePositionAtFirstOffsetBoundary(
2752 move_direction, get_end_offsets);
2753 }
2754 }
2755
2756 // If the boundary is in the same subtree, return a position rooted at this
2757 // position's anchor. This is necessary because we don't want to return a
2758 // position that might be in the shadow DOM when this position is not.
2759 const AXNodeType* common_anchor = text_position->LowestCommonAnchor(*this);
2760 if (GetAnchor() == common_anchor) {
2761 text_position =
2762 text_position->CreateAncestorPosition(common_anchor, move_direction);
2763 } else if (boundary_behavior == AXBoundaryBehavior::StopAtAnchorBoundary) {
2764 switch (move_direction) {
2766 return CreatePositionAtEndOfAnchor()->AsUnignoredPosition(
2769 return CreatePositionAtStartOfAnchor()->AsUnignoredPosition(
2771 }
2772 }
2773
2774 // If there is no ambiguity as to whether the position is at the end of
2775 // the current boundary or the start of the next boundary, an upstream
2776 // affinity should be reset to downstream in order to get consistent output
2777 // from this method, regardless of input affinity.
2778 //
2779 // Note that there could be no ambiguity if the boundary is either at the
2780 // start or the end of the current anchor, so we should always reset to
2781 // downstream affinity in those cases.
2782 if (text_position->affinity_ == ax::mojom::TextAffinity::kUpstream) {
2783 AXPositionInstance downstream_position = text_position->Clone();
2784 downstream_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
2785 if (downstream_position->AtStartOfAnchor() ||
2786 downstream_position->AtEndOfAnchor() ||
2787 !at_start_condition(downstream_position)) {
2788 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
2789 }
2790 }
2791
2792 if (IsTreePosition())
2793 text_position = text_position->AsTreePosition();
2794 AXPositionInstance unignored_position = text_position->AsUnignoredPosition(
2795 AdjustmentBehaviorFromBoundaryDirection(move_direction));
2796 // If there are no unignored positions in |move_direction| then
2797 // |text_position| is anchored in ignored content at the start or end
2798 // of the document.
2799 // For StopAtLastAnchorBoundary, try to adjust in the opposite direction
2800 // to return a position within the document just before crossing into
2801 // the ignored content. This will be the last unignored anchor boundary.
2802 if (unignored_position->IsNullPosition() &&
2803 boundary_behavior == AXBoundaryBehavior::StopAtLastAnchorBoundary) {
2804 unignored_position =
2805 text_position->AsUnignoredPosition(OppositeAdjustmentBehavior(
2806 AdjustmentBehaviorFromBoundaryDirection(move_direction)));
2807 }
2808 return unignored_position;
2809 }
2810
2811 // TODO(nektar): Add sentence navigation methods.
2812
2813 // Uses depth-first pre-order traversal.
2815 return CreateNextAnchorPosition(&DefaultAbortMovePredicate);
2816 }
2817
2818 // Uses depth-first pre-order traversal.
2820 return CreatePreviousAnchorPosition(&DefaultAbortMovePredicate);
2821 }
2822
2823 // Returns an optional integer indicating the logical order of this position
2824 // compared to another position or returns an empty optional if the positions
2825 // are not comparable. Any text position at the same character location is
2826 // logically equivalent although they may be on different anchors or have
2827 // different text offsets. Positions are not comparable when one position is
2828 // null and the other is not or if the positions do not have any common
2829 // ancestor.
2830 // 0: if this position is logically equivalent to the other position
2831 // <0: if this position is logically less than the other position
2832 // >0: if this position is logically greater than the other position
2833 std::optional<int> CompareTo(const AXPosition& other) const {
2834 if (this->IsNullPosition() && other.IsNullPosition())
2835 return std::optional<int>(0);
2836 if (this->IsNullPosition() || other.IsNullPosition())
2837 return std::optional<int>(std::nullopt);
2838
2839 // If both positions share an anchor and are of the same type, we can do a
2840 // straight compare of text offsets or child indices.
2841 if (GetAnchor() == other.GetAnchor()) {
2842 if (IsTextPosition() && other.IsTextPosition())
2843 return text_offset() - other.text_offset();
2844 if (IsTreePosition() && other.IsTreePosition())
2845 return child_index() - other.child_index();
2846 }
2847
2848 // Ancestor positions are expensive to compute. If possible, we will avoid
2849 // doing so by computing the ancestor chain of the two positions' anchors.
2850 // If the lowest common ancestor is neither position's anchor, we can use
2851 // the order of the first uncommon ancestors as a proxy for the order of the
2852 // positions.
2853 //
2854 // In order to do that, we need to normalize text positions at the end of an
2855 // anchor to equivalent positions at the start of the next anchor. Ignored
2856 // positions are a special case in that they need to be shifted to the
2857 // nearest unignored position in order to be normalized. That shifting can
2858 // change the comparison result, so if we have an ignored position, we must
2859 // use the slow path.
2860 if (IsIgnored() || other.IsIgnored())
2861 return SlowCompareTo(other);
2862
2863 // Normalize any text positions at the end of an anchor to equivalent
2864 // positions at the start of the next anchor.
2865 AXPositionInstance normalized_this_position = Clone();
2866 if (normalized_this_position->IsTextPosition()) {
2867 normalized_this_position =
2868 normalized_this_position->AsLeafTextPositionBeforeCharacter();
2869 }
2870
2871 AXPositionInstance normalized_other_position = other.Clone();
2872 if (normalized_other_position->IsTextPosition()) {
2873 normalized_other_position =
2874 normalized_other_position->AsLeafTextPositionBeforeCharacter();
2875 }
2876
2877 if (normalized_this_position->IsNullPosition()) {
2878 if (normalized_other_position->IsNullPosition()) {
2879 // Both positions normalized to a position past the end of the document.
2880 BASE_DCHECK(SlowCompareTo(other).value() == 0);
2881 return 0;
2882 }
2883 // |this| normalized to a position past the end of the document.
2884 BASE_DCHECK(SlowCompareTo(other).value() > 0);
2885 return 1;
2886 } else if (normalized_other_position->IsNullPosition()) {
2887 // |other| normalized to a position past the end of the document.
2888 BASE_DCHECK(SlowCompareTo(other).value() < 0);
2889 return -1;
2890 }
2891
2892 // Compute the ancestor stacks of both positions and walk them ourselves
2893 // rather than calling LowestCommonAnchor(). That way, we can discover the
2894 // first uncommon ancestors.
2895 const AXNodeType* common_anchor = nullptr;
2896 std::stack<AXNodeType*> our_ancestors =
2897 normalized_this_position->GetAncestorAnchors();
2898 std::stack<AXNodeType*> other_ancestors =
2899 normalized_other_position->GetAncestorAnchors();
2900 while (!our_ancestors.empty() && !other_ancestors.empty() &&
2901 our_ancestors.top() == other_ancestors.top()) {
2902 common_anchor = our_ancestors.top();
2903 our_ancestors.pop();
2904 other_ancestors.pop();
2905 }
2906
2907 if (!common_anchor)
2908 return std::optional<int>(std::nullopt);
2909
2910 // If each position has an uncommon ancestor node, we can compare those
2911 // instead of needing to compute ancestor positions.
2912 if (!our_ancestors.empty() && !other_ancestors.empty()) {
2913 AXPositionInstance this_uncommon_tree_position = CreateTreePosition(
2914 GetTreeID(our_ancestors.top()), GetAnchorID(our_ancestors.top()),
2915 0 /*child_index*/);
2916 int this_uncommon_ancestor_index =
2917 this_uncommon_tree_position->AnchorIndexInParent();
2918 AXPositionInstance other_uncommon_tree_position = CreateTreePosition(
2919 GetTreeID(other_ancestors.top()), GetAnchorID(other_ancestors.top()),
2920 0 /*child_index*/);
2921 int other_uncommon_ancestor_index =
2922 other_uncommon_tree_position->AnchorIndexInParent();
2923 BASE_DCHECK(this_uncommon_ancestor_index !=
2924 other_uncommon_ancestor_index);
2925 int result = this_uncommon_ancestor_index - other_uncommon_ancestor_index;
2926
2927 // On platforms that support embedded objects, if a text position is
2928 // within an embedded object and if it is not at the start of that object,
2929 // the resulting ancestor position should be adjusted to point after the
2930 // embedded object. Otherwise, assistive software will not be able to get
2931 // out of the embedded object if its text is not editable when navigating
2932 // by character.
2933 //
2934 // For example, look at the following accessibility tree and the two
2935 // example text positions together with their equivalent ancestor
2936 // positions.
2937 // ++1 kRootWebArea
2938 // ++++2 kTextField "Before<embedded_object>after"
2939 // ++++++3 kStaticText "Before"
2940 // ++++++++4 kInlineTextBox "Before"
2941 // ++++++5 kImage "Test image"
2942 // ++++++6 kStaticText "after"
2943 // ++++++++7 kInlineTextBox "after"
2944 //
2945 // Note that the alt text of an image cannot be navigated with cursor
2946 // left/right, even when the rest of the contents are in a
2947 // contenteditable.
2948 //
2949 // Ancestor position should not be adjusted:
2950 // TextPosition anchor_id=kImage text_offset=0 affinity=downstream
2951 // annotated_text=<T>est image AncestorTextPosition anchor_id=kTextField
2952 // text_offset=6 affinity=downstream
2953 // annotated_text=Before<embedded_object>after
2954 //
2955 // Ancestor position should be adjusted:
2956 // TextPosition anchor_id=kImage text_offset=1 affinity=downstream
2957 // annotated_text=T<e>st image AncestorTextPosition anchor_id=kTextField
2958 // text_offset=7 affinity=downstream
2959 // annotated_text=Beforeembedded_object<a>fter
2960 //
2961 // Note that since the adjustment to the distance between the ancestor
2962 // positions could at most be by one, we skip doing this check if the
2963 // ancestor positions have a distance of more than one since it can never
2964 // change the outcome of the comparison. Note too that if both ancestor
2965 // positions need to be adjusted, the adjustments will cancel out.
2966 if (abs(result) == 1) {
2967 if (!normalized_this_position->AtStartOfAnchor() &&
2968 this_uncommon_tree_position->IsEmbeddedObjectInParent()) {
2969 result += 1;
2970 }
2971 if (!normalized_other_position->AtStartOfAnchor() &&
2972 other_uncommon_tree_position->IsEmbeddedObjectInParent()) {
2973 result -= 1;
2974 }
2975 }
2976
2977#ifndef NDEBUG
2978 // Validate the optimization.
2979 int slow_result = SlowCompareTo(other).value();
2980 BASE_DCHECK((result == 0 && slow_result == 0) ||
2981 (result < 0 && slow_result < 0) ||
2982 (result > 0 && slow_result > 0));
2983#endif
2984
2985 return result;
2986 }
2987
2988 return SlowCompareTo(other);
2989 }
2990
2991 std::optional<int> SlowCompareTo(const AXPosition& other) const {
2992 // It is potentially costly to compute the parent position of a text
2993 // position, whilst computing the parent position of a tree position is
2994 // really inexpensive. In order to find the lowest common ancestor,
2995 // especially if that ancestor is all the way up to the root of the tree,
2996 // this will need to be done repeatedly. We avoid the performance hit by
2997 // converting both positions to tree positions and only falling back to text
2998 // positions if both are text positions and the lowest common ancestor is
2999 // not one of their anchors. Essentially, the question we need to answer is:
3000 // "When are two non equivalent positions going to have the same lowest
3001 // common ancestor position when converted to tree positions?" The answer is
3002 // when they are both text positions and they either have the same anchor,
3003 // or one is the ancestor of the other.
3004 const AXNodeType* common_anchor = this->LowestCommonAnchor(other);
3005 if (!common_anchor)
3006 return std::optional<int>(std::nullopt);
3007
3008 // Attempt to avoid recomputing the lowest common ancestor because we may
3009 // already have its anchor in which case just find the text offset.
3010 if (this->IsTextPosition() && other.IsTextPosition()) {
3011 // This text position's anchor is the common ancestor of the other text
3012 // position's anchor.
3013 if (this->GetAnchor() == common_anchor) {
3014 AXPositionInstance other_text_position =
3015 other.CreateAncestorPosition(common_anchor);
3016 return std::optional<int>(this->text_offset_ -
3017 other_text_position->text_offset_);
3018 }
3019
3020 // The other text position's anchor is the common ancestor of this text
3021 // position's anchor.
3022 if (other.GetAnchor() == common_anchor) {
3023 AXPositionInstance this_text_position =
3024 this->CreateAncestorPosition(common_anchor);
3025 return std::optional<int>(this_text_position->text_offset_ -
3026 other.text_offset_);
3027 }
3028
3029 // All optimizations failed. Fall back to comparing text positions with
3030 // the common text position ancestor.
3031 AXPositionInstance this_text_position_ancestor =
3032 this->CreateAncestorPosition(common_anchor);
3033 AXPositionInstance other_text_position_ancestor =
3034 other.CreateAncestorPosition(common_anchor);
3035 BASE_DCHECK(this_text_position_ancestor->IsTextPosition());
3036 BASE_DCHECK(other_text_position_ancestor->IsTextPosition());
3037 BASE_DCHECK(common_anchor == this_text_position_ancestor->GetAnchor());
3038 BASE_DCHECK(common_anchor == other_text_position_ancestor->GetAnchor());
3039
3040 // TODO - This does not take into account |affinity_|, so we may return
3041 // a false positive when comparing at the end of a line.
3042 // For example :
3043 // ++1 kRootWebArea
3044 // ++++2 kTextField "Line 1\nLine 2"
3045 // ++++++3 kStaticText "Line 1"
3046 // ++++++++4 kInlineTextBox "Line 1"
3047 // ++++++5 kLineBreak "\n"
3048 // ++++++6 kStaticText "Line 2"
3049 // ++++++++7 kInlineTextBox "Line 2"
3050 //
3051 // TextPosition anchor_id=5 text_offset=1
3052 // affinity=downstream annotated_text=\n<>
3053 //
3054 // TextPosition anchor_id=7 text_offset=0
3055 // affinity=downstream annotated_text=<L>ine 2
3056 //
3057 // |LowestCommonAncestor| for both will be :
3058 // TextPosition anchor_id=2 text_offset=7
3059 // ... except anchor_id=5 creates a kUpstream position, while
3060 // anchor_id=7 creates a kDownstream position.
3061 return std::optional<int>(this_text_position_ancestor->text_offset_ -
3062 other_text_position_ancestor->text_offset_);
3063 }
3064
3065 // All optimizations failed. Fall back to comparing child index with
3066 // the common tree position ancestor.
3067 AXPositionInstance this_tree_position_ancestor =
3068 this->AsTreePosition()->CreateAncestorPosition(common_anchor);
3069 AXPositionInstance other_tree_position_ancestor =
3070 other.AsTreePosition()->CreateAncestorPosition(common_anchor);
3071 BASE_DCHECK(this_tree_position_ancestor->IsTreePosition());
3072 BASE_DCHECK(other_tree_position_ancestor->IsTreePosition());
3073 BASE_DCHECK(common_anchor == this_tree_position_ancestor->GetAnchor());
3074 BASE_DCHECK(common_anchor == other_tree_position_ancestor->GetAnchor());
3075
3076 return std::optional<int>(this_tree_position_ancestor->child_index() -
3077 other_tree_position_ancestor->child_index());
3078 }
3079
3080 // A valid position can become invalid if the underlying tree structure
3081 // changes. This is expected behavior, but it is sometimes necessary to
3082 // maintain valid positions. This method modifies an invalid position that is
3083 // beyond MaxTextOffset to snap to MaxTextOffset.
3085 int max_text_offset = MaxTextOffset();
3086 if (text_offset_ > max_text_offset)
3087 text_offset_ = max_text_offset;
3088 }
3089
3090 // Returns true if this position is on an empty object node that needs to
3091 // be represented by an empty object replacement character. It does when the
3092 // node is a collapsed menu list popup button or has no unignored child and is
3093 // not a text object. This feature is only enabled on some platforms.
3097 IsNullPosition()) {
3098 return false;
3099 }
3100
3101 // A collapsed popup button that contains a menu list popup (i.e, the exact
3102 // subtree representation we get from a collapsed <select> element on
3103 // Windows) should not expose its children even though they are not ignored.
3104 if (GetAnchor()->IsCollapsedMenuListPopUpButton())
3105 return true;
3106
3107 // All other elements that have unignored descendants should not be treated
3108 // as empty objects.
3110 return false;
3111
3112 // All unignored leaf nodes in the AXTree except document and text
3113 // nodes should be replaced by the embedded object character. Also, nodes
3114 // that only have ignored children (e.g., a button that contains only an
3115 // empty div) need to be treated as leaf nodes.
3116 //
3117 // Calling AXPosition::IsIgnored here is not possible as it would create an
3118 // infinite loop. However, GetAnchor()->IsIgnored() is sufficient here
3119 // because we know that the anchor at this position doesn't have an
3120 // unignored child, making this a leaf tree or text position.
3121 return !GetAnchor()->IsIgnored() && !IsDocument(GetAnchorRole()) &&
3123 }
3124
3128 IsNullPosition()) {
3129 return false;
3130 }
3131
3132 // Empty objects are only possible on a collapsed popup button parent of a
3133 // menu list popup or a node that only has ignored descendants. If it has no
3134 // empty object ancestor, it can't be inside of an empty object.
3136 }
3137
3138 AXNodeType* GetEmptyObjectAncestorNode() const {
3141 !GetAnchor()) {
3142 return nullptr;
3143 }
3144
3145 if (!GetAnchor()->IsIgnored()) {
3146 // The only case where a descendant of an empty object can be unignored is
3147 // when we are inside of a collapsed popup button parent of a menu list
3148 // popup.
3149 if (AXNodeType* popup_button =
3150 GetAnchor()->GetCollapsedMenuListPopUpButtonAncestor()) {
3151 return popup_button;
3152 }
3153 return nullptr;
3154 }
3155
3156 // The first unignored ancestor is necessarily the empty object if this node
3157 // is the descendant of an empty object.
3158 AXNodeType* ancestor_node = GetLowestUnignoredAncestor();
3159 if (!ancestor_node)
3160 return nullptr;
3161
3163 tree_id_, GetAnchorID(ancestor_node), 0 /* text_offset */,
3165 if (position && position->IsEmptyObjectReplacedByCharacter())
3166 return ancestor_node;
3167
3168 return nullptr;
3169 }
3170
3171 void swap(AXPosition& other) {
3172 std::swap(kind_, other.kind_);
3173 std::swap(tree_id_, other.tree_id_);
3174 std::swap(anchor_id_, other.anchor_id_);
3175 std::swap(child_index_, other.child_index_);
3176 std::swap(text_offset_, other.text_offset_);
3177 std::swap(affinity_, other.affinity_);
3178 }
3179
3180 // Abstract methods.
3181
3182 // Returns the text that is present inside the anchor node, including any text
3183 // found in descendant text nodes, based on the platform's text
3184 // representation. Some platforms use an embedded object replacement character
3185 // that replaces the text coming from each child node.
3186 virtual std::u16string GetText() const = 0;
3187
3188 // Determines if the anchor containing this position is a <br> or a text
3189 // object whose parent's anchor is an enclosing <br>.
3190 virtual bool IsInLineBreak() const = 0;
3191
3192 // Determines if the anchor containing this position is a text object.
3193 virtual bool IsInTextObject() const = 0;
3194
3195 // Determines if the text representation of this position's anchor contains
3196 // only whitespace characters; <br> objects span a single '\n' character, so
3197 // positions inside line breaks are also considered "in whitespace".
3198 virtual bool IsInWhiteSpace() const = 0;
3199
3200 // Returns the length of the text that is present inside the anchor node,
3201 // including any text found in descendant text nodes. This is based on the
3202 // platform's text representation. Some platforms use an embedded object
3203 // character that replaces the text coming from each child node.
3204 //
3205 // Similar to "text_offset_", the length of the text is in UTF16 code units,
3206 // not in grapheme clusters.
3207 virtual int MaxTextOffset() const {
3208 if (IsNullPosition())
3209 return INVALID_OFFSET;
3210 return static_cast<int>(GetText().length());
3211 }
3212
3213 protected:
3215 : kind_(AXPositionKind::NULL_POSITION),
3216 tree_id_(AXTreeIDUnknown()),
3217 anchor_id_(AXNode::kInvalidAXID),
3218 child_index_(INVALID_INDEX),
3219 text_offset_(INVALID_OFFSET),
3220 affinity_(ax::mojom::TextAffinity::kDownstream) {}
3221
3222 // We explicitly don't copy any cached members.
3224 : kind_(other.kind_),
3225 tree_id_(other.tree_id_),
3226 anchor_id_(other.anchor_id_),
3227 child_index_(other.child_index_),
3228 text_offset_(other.text_offset_),
3229 affinity_(other.affinity_) {}
3230
3231 // Returns the character offset inside our anchor's parent at which our text
3232 // starts.
3234 if (IsNullPosition())
3235 return INVALID_OFFSET;
3236
3237 // Calculate how much text there is to the left of this anchor.
3238 AXPositionInstance tree_position = AsTreePosition();
3239 BASE_DCHECK(tree_position);
3240 AXPositionInstance parent_position = tree_position->CreateParentPosition();
3241 BASE_DCHECK(parent_position);
3242 if (parent_position->IsNullPosition())
3243 return 0;
3244
3245 int offset_in_parent = 0;
3246 for (int i = 0; i < parent_position->child_index(); ++i) {
3247 AXPositionInstance child = parent_position->CreateChildPositionAt(i);
3248 BASE_DCHECK(child);
3249 offset_in_parent += child->MaxTextOffsetInParent();
3250 }
3251 return offset_in_parent;
3252 }
3253
3256 int32_t anchor_id,
3257 int child_index,
3258 int text_offset,
3260 kind_ = kind;
3261 tree_id_ = tree_id;
3262 anchor_id_ = anchor_id;
3263 child_index_ = child_index;
3264 text_offset_ = text_offset;
3265 affinity_ = affinity;
3266
3267 if (!IsValid()) {
3268 // Reset to the null position.
3270 tree_id_ = AXTreeIDUnknown();
3271 anchor_id_ = AXNode::kInvalidAXID;
3272 child_index_ = INVALID_INDEX;
3273 text_offset_ = INVALID_OFFSET;
3275 }
3276 }
3277
3278 // Abstract methods.
3279 virtual void AnchorChild(int child_index,
3281 int32_t* child_id) const = 0;
3282 virtual int AnchorChildCount() const = 0;
3283 // When a child is ignored, it looks for unignored nodes of that child's
3284 // children until there are no more descendants.
3285 //
3286 // E.g.
3287 // ++TextField
3288 // ++++GenericContainer ignored
3289 // ++++++StaticText "Hello"
3290 // When we call the following method on TextField, it would return 1.
3291 virtual int AnchorUnignoredChildCount() const = 0;
3292 virtual int AnchorIndexInParent() const = 0;
3293 virtual int AnchorSiblingCount() const = 0;
3294 virtual std::stack<AXNodeType*> GetAncestorAnchors() const = 0;
3295 virtual AXNodeType* GetLowestUnignoredAncestor() const = 0;
3296 virtual void AnchorParent(AXTreeID* tree_id, int32_t* parent_id) const = 0;
3297 virtual AXNodeType* GetNodeInTree(AXTreeID tree_id,
3298 int32_t node_id) const = 0;
3299 virtual int32_t GetAnchorID(AXNodeType* node) const = 0;
3300 virtual AXTreeID GetTreeID(AXNodeType* node) const = 0;
3301
3302 // Returns the length of text that this anchor node takes up in its parent.
3303 // On some platforms, embedded objects are represented in their parent with a
3304 // single embedded object character.
3306 return IsEmbeddedObjectInParent() ? 1 : MaxTextOffset();
3307 }
3308
3309 // Returns whether or not this anchor is represented in their parent with a
3310 // single embedded object character.
3311 virtual bool IsEmbeddedObjectInParent() const = 0;
3312
3313 // Determines if the anchor containing this position produces a hard line
3314 // break in the text representation, e.g. a block level element or a <br>.
3315 virtual bool IsInLineBreakingObject() const = 0;
3316
3317 virtual ax::mojom::Role GetAnchorRole() const = 0;
3318 virtual ax::mojom::Role GetRole(AXNodeType* node) const = 0;
3319 virtual AXNodeTextStyles GetTextStyles() const = 0;
3320 virtual std::vector<int32_t> GetWordStartOffsets() const = 0;
3321 virtual std::vector<int32_t> GetWordEndOffsets() const = 0;
3322 virtual int32_t GetNextOnLineID(int32_t node_id) const = 0;
3323 virtual int32_t GetPreviousOnLineID(int32_t node_id) const = 0;
3324
3325 private:
3326 // Defines the relationship between positions during traversal.
3327 // For example, moving from a descendant to an ancestor, is a kAncestor move.
3328 enum class AXMoveType {
3329 kAncestor,
3330 kDescendant,
3331 kSibling,
3332 };
3333
3334 // Defines the direction of position movement, either next / previous in tree.
3335 enum class AXMoveDirection {
3336 kNextInTree,
3337 kPreviousInTree,
3338 };
3339
3340 // Type of predicate function called during anchor navigation.
3341 // When the predicate returns |true|, the navigation stops and returns a
3342 // null position object.
3343 typedef bool AbortMovePredicate(const AXPosition& move_from,
3344 const AXPosition& move_to,
3345 const AXMoveType type,
3346 const AXMoveDirection direction);
3347
3348 // A text span is defined by a series of inline text boxes that make up a
3349 // single static text object.
3350 bool AtEndOfTextSpan() const {
3352 return false;
3353
3354 // We are at the end of text span if |this| position has
3355 // role::kInlineTextBox, the parent of |this| has role::kStaticText, and the
3356 // anchor node of |this| is the last child of parent's children.
3357 const bool is_last_child =
3359
3360 return is_last_child && GetRole(GetLowestUnignoredAncestor()) ==
3362 }
3363
3364 // Uses depth-first pre-order traversal.
3366 std::function<AbortMovePredicate> abort_predicate) const {
3367 if (IsNullPosition())
3368 return Clone();
3369
3370 AXPositionInstance current_position = AsTreePosition();
3371 BASE_DCHECK(!current_position->IsNullPosition());
3372
3373 if (!IsLeaf()) {
3374 const int child_index = current_position->child_index_;
3375 if (child_index < current_position->AnchorChildCount()) {
3376 AXPositionInstance child_position =
3377 current_position->CreateChildPositionAt(child_index);
3378
3379 if (abort_predicate(*current_position, *child_position,
3380 AXMoveType::kDescendant,
3381 AXMoveDirection::kNextInTree)) {
3382 return CreateNullPosition();
3383 }
3384 return child_position;
3385 }
3386 }
3387
3388 AXPositionInstance parent_position =
3389 current_position->CreateParentPosition();
3390
3391 // Get the next sibling if it exists, otherwise move up the AXTree to the
3392 // lowest next sibling of this position's ancestors.
3393 while (!parent_position->IsNullPosition()) {
3394 const int index_in_parent = current_position->AnchorIndexInParent();
3395 if (index_in_parent + 1 < parent_position->AnchorChildCount()) {
3396 AXPositionInstance next_sibling =
3397 parent_position->CreateChildPositionAt(index_in_parent + 1);
3398 BASE_DCHECK(!next_sibling->IsNullPosition());
3399
3400 if (abort_predicate(*current_position, *next_sibling,
3401 AXMoveType::kSibling,
3402 AXMoveDirection::kNextInTree)) {
3403 return CreateNullPosition();
3404 }
3405 return next_sibling;
3406 }
3407
3408 if (abort_predicate(*current_position, *parent_position,
3409 AXMoveType::kAncestor,
3410 AXMoveDirection::kNextInTree)) {
3411 return CreateNullPosition();
3412 }
3413
3414 current_position = std::move(parent_position);
3415 parent_position = current_position->CreateParentPosition();
3416 }
3417 return CreateNullPosition();
3418 }
3419
3420 // Uses depth-first pre-order traversal.
3422 std::function<AbortMovePredicate> abort_predicate) const {
3423 if (IsNullPosition())
3424 return Clone();
3425
3426 AXPositionInstance current_position = AsTreePosition();
3427 BASE_DCHECK(!current_position->IsNullPosition());
3428
3429 AXPositionInstance parent_position =
3430 current_position->CreateParentPosition();
3431 if (parent_position->IsNullPosition())
3432 return parent_position;
3433
3434 // If there is no previous sibling, move up to the parent.
3435 const int index_in_parent = current_position->AnchorIndexInParent();
3436 if (index_in_parent <= 0) {
3437 if (abort_predicate(*current_position, *parent_position,
3438 AXMoveType::kAncestor,
3439 AXMoveDirection::kPreviousInTree)) {
3440 return CreateNullPosition();
3441 }
3442 return parent_position;
3443 }
3444
3445 // Get the previous sibling's deepest last child.
3446 AXPositionInstance rightmost_leaf =
3447 parent_position->CreateChildPositionAt(index_in_parent - 1);
3448 BASE_DCHECK(!rightmost_leaf->IsNullPosition());
3449
3450 if (abort_predicate(*current_position, *rightmost_leaf,
3451 AXMoveType::kSibling,
3452 AXMoveDirection::kPreviousInTree)) {
3453 return CreateNullPosition();
3454 }
3455
3456 while (!rightmost_leaf->IsLeaf()) {
3457 parent_position = std::move(rightmost_leaf);
3458 rightmost_leaf = parent_position->CreateChildPositionAt(
3459 parent_position->AnchorChildCount() - 1);
3460 BASE_DCHECK(!rightmost_leaf->IsNullPosition());
3461
3462 if (abort_predicate(*parent_position, *rightmost_leaf,
3463 AXMoveType::kDescendant,
3464 AXMoveDirection::kPreviousInTree)) {
3465 return CreateNullPosition();
3466 }
3467 }
3468 return rightmost_leaf;
3469 }
3470
3471 // Creates a position using the next text-only node as its anchor.
3472 // Assumes that text-only nodes are leaf nodes.
3473 AXPositionInstance CreateNextTextAnchorPosition(
3474 std::function<AbortMovePredicate> abort_predicate) const {
3475 // If this is an ancestor text position, resolve to its leaf text position.
3476 if (IsTextPosition() && !IsLeaf())
3477 return AsLeafTextPosition();
3478
3479 AXPositionInstance next_leaf = CreateNextAnchorPosition(abort_predicate);
3480 while (!next_leaf->IsNullPosition() && !next_leaf->IsLeaf())
3481 next_leaf = next_leaf->CreateNextAnchorPosition(abort_predicate);
3482
3483 BASE_DCHECK(next_leaf);
3484 return next_leaf->AsLeafTextPosition();
3485 }
3486
3487 // Creates a position using the previous text-only node as its anchor.
3488 // Assumes that text-only nodes are leaf nodes.
3489 AXPositionInstance CreatePreviousTextAnchorPosition(
3490 std::function<AbortMovePredicate> abort_predicate) const {
3491 // If this is an ancestor text position, resolve to its leaf text position.
3492 if (IsTextPosition() && !IsLeaf())
3493 return AsLeafTextPosition();
3494
3495 AXPositionInstance previous_leaf =
3496 CreatePreviousAnchorPosition(abort_predicate);
3497 while (!previous_leaf->IsNullPosition() && !previous_leaf->IsLeaf()) {
3498 previous_leaf =
3499 previous_leaf->CreatePreviousAnchorPosition(abort_predicate);
3500 }
3501
3502 BASE_DCHECK(previous_leaf);
3503 return previous_leaf->AsLeafTextPosition();
3504 }
3505
3506 // Creates a tree position using the next text-only node as its anchor.
3507 // Assumes that text-only nodes are leaf nodes.
3509 std::function<AbortMovePredicate> abort_predicate) const {
3510 AXPositionInstance next_leaf =
3511 AsTreePosition()->CreateNextAnchorPosition(abort_predicate);
3512 while (!next_leaf->IsNullPosition() && !next_leaf->IsLeaf())
3513 next_leaf = next_leaf->CreateNextAnchorPosition(abort_predicate);
3514
3515 BASE_DCHECK(next_leaf);
3516 return next_leaf;
3517 }
3518
3519 // Creates a tree position using the previous text-only node as its anchor.
3520 // Assumes that text-only nodes are leaf nodes.
3522 std::function<AbortMovePredicate> abort_predicate) const {
3523 AXPositionInstance previous_leaf =
3524 AsTreePosition()->CreatePreviousAnchorPosition(abort_predicate);
3525 while (!previous_leaf->IsNullPosition() && !previous_leaf->IsLeaf()) {
3526 previous_leaf =
3527 previous_leaf->CreatePreviousAnchorPosition(abort_predicate);
3528 }
3529
3530 BASE_DCHECK(previous_leaf);
3531 return previous_leaf;
3532 }
3533
3534 //
3535 // Static helpers for lambda usage.
3536 //
3537
3538 static bool AtStartOfPagePredicate(const AXPositionInstance& position) {
3539 // If a page boundary is ignored, then it should not be exposed to assistive
3540 // software.
3541 return !position->IsIgnored() && position->AtStartOfPage();
3542 }
3543
3544 static bool AtEndOfPagePredicate(const AXPositionInstance& position) {
3545 // If a page boundary is ignored, then it should not be exposed to assistive
3546 // software.
3547 return !position->IsIgnored() && position->AtEndOfPage();
3548 }
3549
3550 static bool AtStartOfParagraphPredicate(const AXPositionInstance& position) {
3551 // The "AtStartOfParagraph" method already excludes ignored nodes.
3552 return position->AtStartOfParagraph();
3553 }
3554
3555 static bool AtEndOfParagraphPredicate(const AXPositionInstance& position) {
3556 // The "AtEndOfParagraph" method already excludes ignored nodes.
3557 return position->AtEndOfParagraph();
3558 }
3559
3560 static bool AtStartOfLinePredicate(const AXPositionInstance& position) {
3561 // Sometimes, nodes that are used to signify line boundaries are ignored.
3562 return position->AtStartOfLine();
3563 }
3564
3565 static bool AtEndOfLinePredicate(const AXPositionInstance& position) {
3566 // Sometimes, nodes that are used to signify line boundaries are ignored.
3567 return position->AtEndOfLine();
3568 }
3569
3570 static bool AtStartOfWordPredicate(const AXPositionInstance& position) {
3571 // Word boundaries should be at specific text offsets that are "visible" to
3572 // assistive software, hence not ignored. Ignored nodes are often used for
3573 // additional layout information, such as line and paragraph boundaries.
3574 // Their text is not currently processed.
3575 return !position->IsIgnored() && position->AtStartOfWord();
3576 }
3577
3578 static bool AtEndOfWordPredicate(const AXPositionInstance& position) {
3579 // Word boundaries should be at specific text offsets that are "visible" to
3580 // assistive software, hence not ignored. Ignored nodes are often used for
3581 // additional layout information, such as line and paragraph boundaries.
3582 // Their text is not currently processed.
3583 return !position->IsIgnored() && position->AtEndOfWord();
3584 }
3585
3586 static bool DefaultAbortMovePredicate(const AXPosition& move_from,
3587 const AXPosition& move_to,
3588 const AXMoveType move_type,
3589 const AXMoveDirection direction) {
3590 // Default behavior is to never abort.
3591 return false;
3592 }
3593
3594 // AbortMovePredicate function used to detect format boundaries.
3595 static bool AbortMoveAtFormatBoundary(const AXPosition& move_from,
3596 const AXPosition& move_to,
3597 const AXMoveType move_type,
3598 const AXMoveDirection direction) {
3599 if (move_from.IsNullPosition() || move_to.IsNullPosition() ||
3600 move_from.IsEmptyObjectReplacedByCharacter() ||
3601 move_to.IsEmptyObjectReplacedByCharacter()) {
3602 return true;
3603 }
3604
3605 // Treat moving into or out of nodes with certain roles as a format break.
3606 ax::mojom::Role from_role = move_from.GetAnchorRole();
3607 ax::mojom::Role to_role = move_to.GetAnchorRole();
3608 if (from_role != to_role) {
3609 if (IsFormatBoundary(from_role) || IsFormatBoundary(to_role))
3610 return true;
3611 }
3612
3613 // Stop moving when text styles differ.
3614 return move_from.AsLeafTreePosition()->GetTextStyles() !=
3615 move_to.AsLeafTreePosition()->GetTextStyles();
3616 }
3617
3618 static bool MoveCrossesLineBreakingObject(const AXPosition& move_from,
3619 const AXPosition& move_to,
3620 const AXMoveType move_type,
3621 const AXMoveDirection direction) {
3622 const bool move_from_break = move_from.IsInLineBreakingObject();
3623 const bool move_to_break = move_to.IsInLineBreakingObject();
3624
3625 switch (move_type) {
3626 case AXMoveType::kAncestor:
3627 // For Ancestor moves, only abort when exiting a block descendant.
3628 // We don't care if the ancestor is a block or not, since the
3629 // descendant is contained by it.
3630 return move_from_break;
3631 case AXMoveType::kDescendant:
3632 // For Descendant moves, only abort when entering a block descendant.
3633 // We don't care if the ancestor is a block or not, since the
3634 // descendant is contained by it.
3635 return move_to_break;
3636 case AXMoveType::kSibling:
3637 // For Sibling moves, abort if at least one of the siblings are a block,
3638 // because that would mean exiting and/or entering a block.
3639 return move_from_break || move_to_break;
3640 }
3642 return false;
3643 }
3644
3645 // AbortMovePredicate function used to detect paragraph boundaries.
3646 // We don't want to abort immediately after crossing a line breaking object
3647 // boundary if the anchor we're moving to is not a leaf, this is necessary to
3648 // avoid aborting if the next leaf position is whitespace-only; update
3649 // |crossed_line_breaking_object_token| and wait until a leaf anchor is
3650 // reached in order to correctly determine paragraph boundaries.
3651 static bool AbortMoveAtParagraphBoundary(
3652 bool& crossed_line_breaking_object_token,
3653 const AXPosition& move_from,
3654 const AXPosition& move_to,
3655 const AXMoveType move_type,
3656 const AXMoveDirection direction) {
3657 if (move_from.IsNullPosition() || move_to.IsNullPosition() ||
3658 move_from.IsEmptyObjectReplacedByCharacter() ||
3659 move_to.IsEmptyObjectReplacedByCharacter()) {
3660 return true;
3661 }
3662
3663 if (!crossed_line_breaking_object_token) {
3664 crossed_line_breaking_object_token = MoveCrossesLineBreakingObject(
3665 move_from, move_to, move_type, direction);
3666 }
3667
3668 if (crossed_line_breaking_object_token && move_to.IsLeaf()) {
3669 // If there's a sequence of whitespace-only anchors, collapse so only the
3670 // last whitespace-only anchor is considered a paragraph boundary.
3671 return direction != AXMoveDirection::kNextInTree ||
3672 !move_to.IsInWhiteSpace();
3673 }
3674 return false;
3675 }
3676
3677 // This AbortMovePredicate never aborts, but detects whether a sequence of
3678 // consecutive moves cross any line breaking object boundary.
3679 static bool UpdateCrossedLineBreakingObjectToken(
3680 bool& crossed_line_breaking_object_token,
3681 const AXPosition& move_from,
3682 const AXPosition& move_to,
3683 const AXMoveType move_type,
3684 const AXMoveDirection direction) {
3685 if (!crossed_line_breaking_object_token) {
3686 crossed_line_breaking_object_token = MoveCrossesLineBreakingObject(
3687 move_from, move_to, move_type, direction);
3688 }
3689 return false;
3690 }
3691
3692 // AbortMovePredicate function used to detect page boundaries.
3693 static bool AbortMoveAtPageBoundary(const AXPosition& move_from,
3694 const AXPosition& move_to,
3695 const AXMoveType move_type,
3696 const AXMoveDirection direction) {
3697 if (move_from.IsNullPosition() || move_to.IsNullPosition())
3698 return true;
3699
3700 const bool move_from_break = move_from.GetAnchor()->GetBoolAttribute(
3702 const bool move_to_break = move_to.GetAnchor()->GetBoolAttribute(
3704
3705 switch (move_type) {
3706 case AXMoveType::kAncestor:
3707 // For Ancestor moves, only abort when exiting a page break.
3708 // We don't care if the ancestor is a page break or not, since the
3709 // descendant is contained by it.
3710 return move_from_break;
3711 case AXMoveType::kDescendant:
3712 // For Descendant moves, only abort when entering a page break
3713 // descendant. We don't care if the ancestor is a page break or not,
3714 // since the descendant is contained by it.
3715 return move_to_break;
3716 case AXMoveType::kSibling:
3717 // For Sibling moves, abort if at both of the siblings are a page
3718 // break, because that would mean exiting and/or entering a page break.
3719 return move_from_break && move_to_break;
3720 }
3722 return false;
3723 }
3724
3725 static bool AbortMoveAtStartOfInlineBlock(const AXPosition& move_from,
3726 const AXPosition& move_to,
3727 const AXMoveType move_type,
3728 const AXMoveDirection direction) {
3729 if (move_from.IsNullPosition() || move_to.IsNullPosition())
3730 return true;
3731
3732 // These will only be available if AXMode has kHTML set.
3733 const bool move_from_is_inline_block =
3734 move_from.GetAnchor()->GetStringAttribute(
3735 ax::mojom::StringAttribute::kDisplay) == "inline-block";
3736 const bool move_to_is_inline_block =
3737 move_to.GetAnchor()->GetStringAttribute(
3738 ax::mojom::StringAttribute::kDisplay) == "inline-block";
3739
3740 switch (direction) {
3741 case AXMoveDirection::kNextInTree:
3742 // When moving forward, break if we enter an inline block.
3743 return move_to_is_inline_block &&
3744 (move_type == AXMoveType::kDescendant ||
3745 move_type == AXMoveType::kSibling);
3746 case AXMoveDirection::kPreviousInTree:
3747 // When moving backward, break if we exit an inline block.
3748 return move_from_is_inline_block &&
3749 (move_type == AXMoveType::kAncestor ||
3750 move_type == AXMoveType::kSibling);
3751 }
3753 return false;
3754 }
3755
3756 static AXPositionAdjustmentBehavior AdjustmentBehaviorFromBoundaryDirection(
3757 ax::mojom::MoveDirection move_direction) {
3758 switch (move_direction) {
3763 }
3764 }
3765
3766 static AXPositionAdjustmentBehavior OppositeAdjustmentBehavior(
3767 AXPositionAdjustmentBehavior adjustment_behavior) {
3768 switch (adjustment_behavior) {
3773 }
3774 }
3775
3776 static std::vector<int32_t> GetWordStartOffsetsFunc(
3777 const AXPositionInstance& position) {
3778 return position->GetWordStartOffsets();
3779 }
3780
3781 static std::vector<int32_t> GetWordEndOffsetsFunc(
3782 const AXPositionInstance& position) {
3783 return position->GetWordEndOffsets();
3784 }
3785
3786 AXPositionInstance CreateDocumentAncestorPosition() const {
3787 AXPositionInstance iterator = Clone();
3788 while (!iterator->IsNullPosition()) {
3789 if (IsDocument(iterator->GetAnchorRole()) &&
3790 iterator->CreateParentPosition()->IsNullPosition()) {
3791 break;
3792 }
3793 iterator = iterator->CreateParentPosition();
3794 }
3795 return iterator;
3796 }
3797
3798 // Creates a text position that is in the same anchor as the current
3799 // position, but starting from the current text offset, adjusts to the next
3800 // or the previous boundary offset depending on the boundary direction. If
3801 // there is no next / previous offset, the current text offset is unchanged.
3802 AXPositionInstance CreatePositionAtNextOffsetBoundary(
3803 ax::mojom::MoveDirection move_direction,
3804 BoundaryTextOffsetsFunc get_offsets) const {
3805 if (IsNullPosition() || !get_offsets)
3806 return Clone();
3807
3808 AXPositionInstance text_position = AsTextPosition();
3809 const std::vector<int32_t> boundary_offsets = get_offsets(text_position);
3810 if (boundary_offsets.empty())
3811 return text_position;
3812
3813 switch (move_direction) {
3815 const auto offsets_iterator =
3816 std::upper_bound(boundary_offsets.begin(), boundary_offsets.end(),
3817 int32_t{text_position->text_offset_});
3818 // If there is no next offset, the current offset should be unchanged.
3819 if (offsets_iterator < boundary_offsets.end()) {
3820 text_position->text_offset_ = static_cast<int>(*offsets_iterator);
3821 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
3822 }
3823 break;
3824 }
3826 auto offsets_iterator =
3827 std::lower_bound(boundary_offsets.begin(), boundary_offsets.end(),
3828 int32_t{text_position->text_offset_});
3829 // If there is no previous offset, the current offset should be
3830 // unchanged.
3831 if (offsets_iterator > boundary_offsets.begin()) {
3832 // Since we already checked if "boundary_offsets" are non-empty, we
3833 // can safely move the iterator one position back, even if it's
3834 // currently at the vector's end.
3835 --offsets_iterator;
3836 text_position->text_offset_ = static_cast<int>(*offsets_iterator);
3837 text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
3838 }
3839 break;
3840 }
3841 }
3842
3843 return text_position;
3844 }
3845
3846 // Creates a text position that is in the same anchor as the current
3847 // position, but adjusts its text offset to be either at the first or last
3848 // offset boundary, based on the boundary direction. When moving forward,
3849 // the text position is adjusted to point to the first offset boundary, or
3850 // to the end of its anchor if there are no offset boundaries. When moving
3851 // backward, it is adjusted to point to the last offset boundary, or to the
3852 // start of its anchor if there are no offset boundaries.
3853 AXPositionInstance CreatePositionAtFirstOffsetBoundary(
3854 ax::mojom::MoveDirection move_direction,
3855 BoundaryTextOffsetsFunc get_offsets) const {
3856 if (IsNullPosition() || !get_offsets)
3857 return Clone();
3858
3859 AXPositionInstance text_position = AsTextPosition();
3860 const std::vector<int32_t> boundary_offsets = get_offsets(text_position);
3861 switch (move_direction) {
3863 if (boundary_offsets.empty()) {
3864 return text_position->CreatePositionAtEndOfAnchor();
3865 } else {
3866 text_position->text_offset_ = static_cast<int>(boundary_offsets[0]);
3867 return text_position;
3868 }
3869 break;
3871 if (boundary_offsets.empty()) {
3872 return text_position->CreatePositionAtStartOfAnchor();
3873 } else {
3874 text_position->text_offset_ =
3875 static_cast<int>(boundary_offsets[boundary_offsets.size() - 1]);
3876 return text_position;
3877 }
3878 break;
3879 }
3880 }
3881
3882 // Returns the next leaf text position in the specified direction ensuring
3883 // that *AsLeafTextPosition() != *CreateAdjacentLeafTextPosition() is true;
3884 // returns a null position if no adjacent position exists.
3885 //
3886 // This method is the first step for CreateBoundary[Start|End]Position to
3887 // guarantee that the resulting position when using a boundary behavior other
3888 // than StopIfAlreadyAtBoundary is not equivalent to the initial position.
3889 //
3890 // Note that using CompareTo with text positions does not take into account
3891 // position affinity or tree pre-order, two text positions are considered
3892 // equivalent if their offsets in the text representation of the entire AXTree
3893 // are the same. As such, using Create[Next|Previous]LeafTextPosition is not
3894 // enough to create adjacent positions, e.g. the end of an anchor and the
3895 // start of the next one are equivalent; furthermore, there could be nodes
3896 // with no text representation between them, all of them being equivalent too.
3897 //
3898 // IMPORTANT: This method basically moves the given position one character
3899 // forward/backward, but it could end up at the middle of a grapheme cluster,
3900 // so it shouldn't be used to move by ax::mojom::TextBoundary::kCharacter (for
3901 // such purpose use Create[Next|Previous]CharacterPosition instead).
3902 AXPositionInstance CreateAdjacentLeafTextPosition(
3903 ax::mojom::MoveDirection move_direction) const {
3904 AXPositionInstance text_position = AsLeafTextPosition();
3905
3906 switch (move_direction) {
3908 // If we are at a text offset less than MaxTextOffset, we will simply
3909 // increase the offset by one; otherwise, create a position at the start
3910 // of the next leaf node with non-empty text and increase its offset.
3911 //
3912 // Note that a position located at offset 0 of an empty text node is
3913 // considered both, at the start and at the end of its anchor, so the
3914 // following loop skips over empty text leaf nodes, which is expected
3915 // since those positions are equivalent to both, the previous non-empty
3916 // leaf node's end and the next non-empty leaf node's start.
3917 while (text_position->AtEndOfAnchor()) {
3918 text_position = text_position->CreateNextLeafTextPosition();
3919 }
3920 if (!text_position->IsNullPosition())
3921 ++text_position->text_offset_;
3922 break;
3924 // If we are at a text offset greater than 0, we will simply decrease
3925 // the offset by one; otherwise, create a position at the end of the
3926 // previous leaf node with non-empty text and decrease its offset.
3927 //
3928 // Same as the comment above, using AtStartOfAnchor is enough to skip
3929 // empty text nodes that are equivalent to the initial position.
3930 while (text_position->AtStartOfAnchor()) {
3931 text_position = text_position->CreatePreviousLeafTextPosition()
3932 ->CreatePositionAtEndOfAnchor();
3933 }
3934 if (!text_position->IsNullPosition())
3935 --text_position->text_offset_;
3936 break;
3937 }
3938
3939 BASE_DCHECK(text_position->IsValid());
3940 return text_position;
3941 }
3942
3943 AXPositionKind kind_;
3944 AXTreeID tree_id_;
3945 AXNode::AXID anchor_id_;
3946
3947 // For text positions, |child_index_| is initially set to |-1| and only
3948 // computed on demand. The same with tree positions and |text_offset_|.
3949 int child_index_;
3950 // "text_offset_" represents the number of UTF16 code units before this
3951 // position. It doesn't count grapheme clusters.
3952 int text_offset_;
3953
3954 // Affinity is used to distinguish between two text positions that point to
3955 // the same text offset, but which happens to fall on a soft line break. A
3956 // soft line break doesn't insert any white space in the accessibility tree,
3957 // so without affinity there would be no way to determine whether a text
3958 // position is before or after the soft line break. An upstream affinity
3959 // means that the position is before the soft line break, whilst a
3960 // downstream affinity means that the position is after the soft line break.
3961 //
3962 // Please note that affinity could only be set to upstream for positions
3963 // that are anchored to non-leaf nodes. When on a leaf node, there could
3964 // never be an ambiguity as to which line a position points to because Blink
3965 // creates separate inline text boxes for each line of text. Therefore, a
3966 // leaf text position before the soft line break would be pointing to the
3967 // end of its anchor node, whilst a leaf text position after the soft line
3968 // break would be pointing to the start of the next node.
3969 ax::mojom::TextAffinity affinity_;
3970};
3971
3972template <class AXPositionType, class AXNodeType>
3974template <class AXPositionType, class AXNodeType>
3976template <class AXPositionType, class AXNodeType>
3978
3979template <class AXPositionType, class AXNodeType>
3982 const std::optional<int> compare_to_optional = first.CompareTo(second);
3983 return compare_to_optional.has_value() && compare_to_optional.value() == 0;
3984}
3985
3986template <class AXPositionType, class AXNodeType>
3989 const std::optional<int> compare_to_optional = first.CompareTo(second);
3990 return compare_to_optional.has_value() && compare_to_optional.value() != 0;
3991}
3992
3993template <class AXPositionType, class AXNodeType>
3996 const std::optional<int> compare_to_optional = first.CompareTo(second);
3997 return compare_to_optional.has_value() && compare_to_optional.value() < 0;
3998}
3999
4000template <class AXPositionType, class AXNodeType>
4003 const std::optional<int> compare_to_optional = first.CompareTo(second);
4004 return compare_to_optional.has_value() && compare_to_optional.value() <= 0;
4005}
4006
4007template <class AXPositionType, class AXNodeType>
4010 const std::optional<int> compare_to_optional = first.CompareTo(second);
4011 return compare_to_optional.has_value() && compare_to_optional.value() > 0;
4012}
4013
4014template <class AXPositionType, class AXNodeType>
4017 const std::optional<int> compare_to_optional = first.CompareTo(second);
4018 return compare_to_optional.has_value() && compare_to_optional.value() >= 0;
4019}
4020
4021template <class AXPositionType, class AXNodeType>
4026
4027template <class AXPositionType, class AXNodeType>
4028std::ostream& operator<<(
4029 std::ostream& stream,
4031 return stream << position.ToString();
4032}
4033
4034} // namespace ui
4035
4036#endif // UI_ACCESSIBILITY_AX_POSITION_H_
#define AX_EXPORT
Definition ax_export.h:29
int32_t AXID
Definition ax_node.h:36
static constexpr AXID kInvalidAXID
Definition ax_node.h:41
bool AtStartOfWord() const
bool AtStartOfParagraph() const
AXPositionInstance CreatePreviousParagraphEndPosition(AXBoundaryBehavior boundary_behavior) const
virtual AXTreeID GetTreeID(AXNodeType *node) const =0
AXPositionInstance CreateNextWordStartPosition(AXBoundaryBehavior boundary_behavior) const
bool AtEndOfParagraph() const
AXPositionInstance CreatePositionAtStartOfAXTree() const
bool AtStartOfPage() const
int child_index() const
bool IsLeafTreePosition() const
virtual bool IsEmbeddedObjectInParent() const =0
AXPositionInstance CreateAncestorPosition(const AXNodeType *ancestor_anchor, ax::mojom::MoveDirection move_direction=ax::mojom::MoveDirection::kForward) const
AXPositionInstance CreatePreviousLeafTreePosition() const
AXPositionInstance AsLeafTextPositionBeforeCharacter() const
virtual ~AXPosition()=default
virtual bool IsInTextObject() const =0
AXRange< AXPosition< AXPositionType, AXNodeType > > AXRangeType
AXPositionInstance CreatePreviousLineStartPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance LowestCommonAncestor(const AXPosition &second) const
AXPositionInstance CreateNextParagraphEndPosition(AXBoundaryBehavior boundary_behavior) const
AXNodeType * LowestCommonAnchor(const AXPosition &second) const
bool AtStartOfAXTree() const
AXPositionInstance CreateNextWordEndPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance AsTreePosition() const
AXPositionInstance CreatePreviousAnchorPosition() const
virtual int MaxTextOffset() const
AXPosition & operator=(const AXPosition &other)
AXPositionKind kind() const
virtual AXNodeType * GetLowestUnignoredAncestor() const =0
static AXPositionInstance Unserialize(const SerializedPosition &serialization)
virtual int AnchorUnignoredChildCount() const =0
bool AtEndOfWord() const
virtual void AnchorChild(int child_index, AXTreeID *tree_id, int32_t *child_id) const =0
AXNodeType * GetEmptyObjectAncestorNode() const
bool AtLastNodeInTree() const
virtual int32_t GetNextOnLineID(int32_t node_id) const =0
AXPositionInstance AsTextPosition() const
static constexpr char16_t kEmbeddedCharacter
AXPositionInstance CreatePositionAtStartOfAnchor() const
bool AtStartOfLine() const
virtual AXNodeType * GetNodeInTree(AXTreeID tree_id, int32_t node_id) const =0
virtual bool IsInLineBreak() const =0
AXPositionInstance CreatePositionAtEndOfAnchor() const
AXPositionInstance CreateNextFormatEndPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance AsLeafTextPositionAfterCharacter() const
AXPositionInstance AsLeafTreePosition() const
bool AtStartOfDocument() const
bool AtEndOfAXTree() const
virtual void AnchorParent(AXTreeID *tree_id, int32_t *parent_id) const =0
AXPositionInstance CreatePreviousWordEndPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance CreateBoundaryStartPosition(AXBoundaryBehavior boundary_behavior, ax::mojom::MoveDirection move_direction, BoundaryConditionPredicate at_start_condition, BoundaryConditionPredicate at_end_condition, BoundaryTextOffsetsFunc get_start_offsets={}) const
AXPositionInstance CreateChildPositionAt(int child_index) const
AXPositionInstance CreateBoundaryEndPosition(AXBoundaryBehavior boundary_behavior, ax::mojom::MoveDirection move_direction, BoundaryConditionPredicate at_start_condition, BoundaryConditionPredicate at_end_condition, BoundaryTextOffsetsFunc get_end_offsets={}) const
virtual int AnchorChildCount() const =0
std::unique_ptr< AXPosition< AXPositionType, AXNodeType > > AXPositionInstance
int MaxTextOffsetInParent() const
AXPositionInstance CreatePreviousCharacterPosition(AXBoundaryBehavior boundary_behavior) const
bool IsLeafTextPosition() const
static const int INVALID_OFFSET
void swap(AXPosition &other)
AXPositionInstance AsLeafTextPosition() const
AXRangeType ExpandToEnclosingTextBoundary(ax::mojom::TextBoundary boundary, AXRangeExpandBehavior expand_behavior) const
virtual ax::mojom::Role GetRole(AXNodeType *node) const =0
std::optional< int > CompareTo(const AXPosition &other) const
AXPositionInstance CreateNextLeafTextPosition(bool *crossed_line_breaking_object=nullptr) const
bool AtStartOfFormat() const
AXPositionInstance CreatePositionAtTextBoundary(ax::mojom::TextBoundary boundary, ax::mojom::MoveDirection direction, AXBoundaryBehavior boundary_behavior) const
AXTreeID tree_id() const
std::optional< int > SlowCompareTo(const AXPosition &other) const
std::vector< int32_t > BoundaryTextOffsetsFunc(const AXPositionInstance &)
void Initialize(AXPositionKind kind, AXTreeID tree_id, int32_t anchor_id, int child_index, int text_offset, ax::mojom::TextAffinity affinity)
bool IsTreePosition() const
virtual int AnchorIndexInParent() const =0
virtual std::u16string GetText() const =0
AXPositionInstance CreatePositionAtEndOfAXTree() const
AXPosition(const AXPosition &other)
AXPositionInstance CreatePreviousPageStartPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance CreateNextAnchorPosition() const
static AXPositionInstance CreateNullPosition()
bool AtEndOfFormat() const
bool IsInDescendantOfEmptyObject() const
AXPositionInstance CreateNextPageStartPosition(AXBoundaryBehavior boundary_behavior) const
bool AtEndOfDocument() const
AXNode::AXID anchor_id() const
AXPositionInstance CreatePreviousLineEndPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance CreateNextLineEndPosition(AXBoundaryBehavior boundary_behavior) const
SerializedPosition Serialize()
AXPositionInstance AsValidPosition() const
AXPositionInstance CreatePreviousPageEndPosition(AXBoundaryBehavior boundary_behavior) const
bool AtStartOfAnchor() const
bool AtEndOfPage() const
AXPositionInstance CreateNextLineStartPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance CreateNextCharacterPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance CreatePreviousLeafTextPosition() const
int text_offset() const
bool IsTextPosition() const
AXPositionInstance CreatePreviousFormatStartPosition(AXBoundaryBehavior boundary_behavior) const
virtual std::vector< int32_t > GetWordStartOffsets() const =0
bool IsLeaf() const
bool AtEndOfLine() const
virtual AXNodeTextStyles GetTextStyles() const =0
AXNodeType * GetAnchor() const
AXPositionInstance CreateNextPageEndPosition(AXBoundaryBehavior boundary_behavior) const
void SnapToMaxTextOffsetIfBeyond()
int AnchorTextOffsetInParent() const
bool IsNullPosition() const
virtual int AnchorSiblingCount() const =0
virtual bool IsInLineBreakingObject() const =0
bool IsValid() const
std::string ToString() const
AXBoundaryType GetFormatEndBoundaryType() const
virtual int32_t GetPreviousOnLineID(int32_t node_id) const =0
static AXPositionInstance CreateTreePosition(AXTreeID tree_id, AXNode::AXID anchor_id, int child_index)
virtual std::stack< AXNodeType * > GetAncestorAnchors() const =0
AXPositionInstance CreateNextLeafTreePosition() const
static const int BEFORE_TEXT
ax::mojom::TextAffinity affinity() const
virtual AXPositionInstance Clone() const =0
AXPositionInstance CreatePreviousWordStartPosition(AXBoundaryBehavior boundary_behavior) const
AXPositionInstance CreateParentPosition(ax::mojom::MoveDirection move_direction=ax::mojom::MoveDirection::kForward) const
virtual ax::mojom::Role GetAnchorRole() const =0
AXPositionInstance CreatePositionAtEndOfDocument() const
AXPositionInstance AsUnignoredPosition(AXPositionAdjustmentBehavior adjustment_behavior) const
static const int INVALID_INDEX
bool AtStartOfInlineBlock() const
AXPositionInstance CreateNextParagraphStartPosition(AXBoundaryBehavior boundary_behavior) const
bool AtEndOfAnchor() const
AXBoundaryType GetFormatStartBoundaryType() const
virtual bool IsInWhiteSpace() const =0
bool IsEmptyObjectReplacedByCharacter() const
AXPositionInstance CreatePositionAtStartOfDocument() const
bool IsIgnored() const
virtual std::vector< int32_t > GetWordEndOffsets() const =0
static AXPositionInstance CreateTextPosition(AXTreeID tree_id, AXNode::AXID anchor_id, int text_offset, ax::mojom::TextAffinity affinity)
virtual int32_t GetAnchorID(AXNodeType *node) const =0
AXPositionInstance CreatePreviousParagraphStartPosition(AXBoundaryBehavior boundary_behavior) const
bool BoundaryConditionPredicate(const AXPositionInstance &)
static AXTreeID FromString(const std::string &string)
Definition ax_tree_id.cc:37
std::string ToString() const
Definition ax_tree_id.cc:53
uint8_t value
GAsyncResult * result
std::u16string text
std::string UTF16ToUTF8(std::u16string src)
bool Contains(const Container &container, const Value &value)
std::string NumberToString(int32_t number)
Definition copy.py:1
AXEmbeddedObjectBehavior
Definition ax_position.h:98
AXPositionAdjustmentBehavior
Definition ax_position.h:67
bool operator==(const AXEventIntent &a, const AXEventIntent &b)
bool operator>=(const AXPosition< AXPositionType, AXNodeType > &first, const AXPosition< AXPositionType, AXNodeType > &second)
const char * ToString(ax::mojom::Event event)
const AXTreeID & AXTreeIDUnknown()
void swap(AXPosition< AXPositionType, AXNodeType > &first, AXPosition< AXPositionType, AXNodeType > &second)
bool IsIframe(ax::mojom::Role role)
std::ostream & operator<<(std::ostream &os, AXEventGenerator::Event event)
AXEmbeddedObjectBehavior g_ax_embedded_object_behavior
AXBoundaryBehavior
Definition ax_position.h:43
bool operator!=(const AXEventIntent &a, const AXEventIntent &b)
AXBoundaryType
Definition ax_position.h:53
bool IsFormatBoundary(const ax::mojom::Role role)
AXPositionKind
Definition ax_position.h:38
bool operator<=(const AXPosition< AXPositionType, AXNodeType > &first, const AXPosition< AXPositionType, AXNodeType > &second)
bool operator>(const AXPosition< AXPositionType, AXNodeType > &first, const AXPosition< AXPositionType, AXNodeType > &second)
AXRangeExpandBehavior
Definition ax_position.h:79
bool IsDocument(const ax::mojom::Role role)
bool operator<(const AXPosition< AXPositionType, AXNodeType > &first, const AXPosition< AXPositionType, AXNodeType > &second)
ax::mojom::TextAffinity affinity
#define BASE_DCHECK(condition)
Definition logging.h:63
#define BASE_UNREACHABLE()
Definition logging.h:69
#define BASE_LOG()
Definition logging.h:54