910e62b5创建于 1月15日历史提交
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_ACCESSIBILITY_AX_POSITION_H_
#define UI_ACCESSIBILITY_AX_POSITION_H_

#include <math.h>
#include <stdint.h>

#include <functional>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/containers/stack.h"
#include "base/export_template.h"
#include "base/i18n/break_iterator.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_common.h"
#include "ui/accessibility/ax_constants.mojom.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/ax_table_info.h"
#include "ui/accessibility/ax_text_attributes.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_manager.h"
#include "ui/gfx/utf16_indexing.h"

namespace ui {

class AXNodePosition;
class AXNode;

// Defines the type of position in the accessibility tree.
// A tree position is used when referring to a specific child of a node in the
// accessibility tree.
// A text position is used when referring to a specific character of text inside
// a particular node.
// A null position is used to signify that the provided data is invalid or that
// a boundary has been reached.
enum class AXPositionKind { NULL_POSITION, TREE_POSITION, TEXT_POSITION };

// Defines how creating the next or previous position should behave whenever we
// are at or are crossing a text boundary, (such as the start of a word or the
// end of a sentence), or whenever we are crossing the initial position's
// anchor. Note that the "anchor" is the node to which an AXPosition is attached
// to. It is provided when a position is created.
enum class AXBoundaryBehavior {
  // Crosses all boundaries. If the bounds of the current window-like container,
  // such as the current webpage, have been reached, returns a null position.
  kCrossBoundary,
  // Stops if the current anchor is crossed, regardless of how the resulting
  // position has been computed. For example, even though in order to find the
  // next or previous word start in a text field we need to descend to the leaf
  // equivalent position, this behavior will only stop when the bounds of the
  // original anchor, i.e. the text field, have been crossed.
  kStopAtAnchorBoundary,
  // Stops if we have reached the start or the end of of a window-like
  // container, such as a webpage, a PDF, a dialog, the browser's UI (AKA
  // Views), or the whole desktop.
  kStopAtLastAnchorBoundary
};

// Defines whether moving to the next or previous position should consider the
// initial position before testing for the given boundary/behavior.
// kCheckInitialPosition should be used if the current position should be
// maintained if it meets the boundary criteria. Otherwise,
// kDontCheckInitialPosition will move to the next/previous position before
// testing for the specified boundary.
enum class AXBoundaryDetection {
  kCheckInitialPosition,
  kDontCheckInitialPosition,
};

struct AXMovementOptions {
  AXMovementOptions(AXBoundaryBehavior boundary, AXBoundaryDetection detection)
      : boundary_behavior(boundary), boundary_detection(detection) {}

  AXBoundaryBehavior boundary_behavior;
  AXBoundaryDetection boundary_detection;

  // If true, indicates that an upstream position should not be crossed when
  // moving forward and should skip its initial check when moving backward. This
  // primarily applies to getting a pair of positions around a line from an
  // upstream caret.
  bool upstream_bounded = false;
};

// Describes in further detail what type of boundary a current position is on.
//
// For complex boundaries such as format boundaries, it can be useful to know
// why a particular boundary was chosen.
enum class AXBoundaryType {
  // Not at a unit boundary.
  kNone,
  // At a unit boundary (e.g. a format boundary).
  kUnitBoundary,
  // At the start of the whole content, possibly spanning multiple accessibility
  // trees.
  kContentStart,
  // At the end of the whole content, possibly spanning multiple accessibility
  // trees.
  kContentEnd
};

// When converting to an unignored position, determines how to adjust the new
// position in order to make it valid, either moving backward or forward in
// the accessibility tree.
enum class AXPositionAdjustmentBehavior { kMoveBackward, kMoveForward };

// Specifies how AXPosition::ExpandToEnclosingTextBoundary behaves.
//
// As an example, imagine we have the text "hello world" and a position before
// the space character. We want to expand to the surrounding word boundary.
// Since we are right at the end of the first word, we could either expand to
// the left first, find the start of the first word and then use that to find
// the corresponding word end, resulting in the word "Hello". Another
// possibility is to expand to the right first, find the end of the next word
// and use that as our starting point to find the previous word start, resulting
// in the word "world".
enum class AXRangeExpandBehavior {
  // Expands to the left boundary first and then uses that position as the
  // starting point to find the boundary to the right.
  kLeftFirst,
  // Expands to the right boundary first and then uses that position as the
  // starting point to find the boundary to the left.
  kRightFirst
};

// Some platforms require most objects, including empty objects, to be
// represented by an "embedded object character" in order for text navigation to
// work correctly. This enum controls whether a replacement character will be
// exposed for such objects.
//
// When an embedded object is replaced by this special character, the
// expectations are the same with this character as with other ordinary
// characters.
//
// For example, with UIA on Windows, we need to be able to navigate inside and
// outside of this character as if it was an ordinary character, using the
// `AXPlatformNodeTextRangeProvider` methods. Since an "embedded object
// character" is the only character in a node, we also treat this character as a
// word.
//
// However, there is a special case for UIA. kExposeCharacterForHypertext is
// used mainly to enable the hypertext logic and calculation for cases where the
// embedded object character is not needed. This logic is IA2 and ATK specific,
// and should not be used for UIA relevant calls and calculations. As a result,
// we have the kUIAExposeCharacterForTextContent which avoids the IA2/ATK
// specific logic for the text calculation but also keeps the same embedded
// object character behavior for cases when it is needed.
enum class AXEmbeddedObjectBehavior {
  kExposeCharacterForHypertext,
  kSuppressCharacter,
  kUIAExposeCharacterForTextContent,
};

// Controls whether embedded objects are represented by a replacement
// character. This is initialized to a per-platform default but can be
// overridden for testing.
//
// On some platforms, most objects are represented in the text of their parents
// with a special "embedded object character" and not with their actual text
// contents. Also on the same platforms, if a node has only ignored descendants,
// i.e., it appears to be empty to assistive software, we need to treat it as a
// character and a word boundary. For example, an empty text field should act as
// a character and a word boundary when a screen reader user tries to navigate
// through it, otherwise the text field would be missed by the user.
//
// Tests should use ScopedAXEmbeddedObjectBehaviorSetter to change this.
// TODO(crbug.com/40764129) Don't export this so tests can't change it.
extern AX_EXPORT AXEmbeddedObjectBehavior g_ax_embedded_object_behavior;

class AX_EXPORT ScopedAXEmbeddedObjectBehaviorSetter {
 public:
  explicit ScopedAXEmbeddedObjectBehaviorSetter(
      AXEmbeddedObjectBehavior behavior);
  ~ScopedAXEmbeddedObjectBehaviorSetter();

 private:
  AXEmbeddedObjectBehavior prev_behavior_;
};

// Forward declarations.
template <class AXPositionType, class AXNodeType>
class AXPosition;
template <class AXPositionType>
class AXRange;
template <class AXPositionType, class AXNodeType>
bool operator==(const AXPosition<AXPositionType, AXNodeType>& first,
                const AXPosition<AXPositionType, AXNodeType>& second);

// A position in the accessibility tree.
//
// This class could either represent a tree position or a text position.
// Tree positions point to either a child of a specific node or at the end of a
// node (i.e. an "after children" position).
// Text positions point to either a character offset in the text inside a
// particular node including text from all its children, or to the end of the
// node's text, (i.e. an "after text" position).
// On tree positions that have a leaf node as their anchor, we also need to
// distinguish between "before text" and "after text" positions. To do this, if
// the child index is 0 and the anchor is a leaf node, then it's an "after text"
// position. If the child index is |BEFORE_TEXT| and the anchor is a leaf node,
// then this is a "before text" position.
// It doesn't make sense to have a "before text" position on a text position,
// because it is identical to setting its offset to the first character.
//
// To avoid re-computing either the text offset or the child index when
// converting between the two types of positions, both values are saved after
// the first conversion.
//
// This class template uses static polymorphism in order to allow sub-classes to
// be created from the base class without the base class knowing the type of the
// sub-class in advance.
// The template argument |AXPositionType| should always be set to the type of
// any class that inherits from this template, making this a
// "curiously recursive template".
//
// This class can be copied using the |Clone| method. It is designed to be
// immutable.
template <class AXPositionType, class AXNodeType>
class AXPosition {
 public:
  using AXPositionInstance =
      std::unique_ptr<AXPosition<AXPositionType, AXNodeType>>;

  using AXRangeType = AXRange<AXPosition<AXPositionType, AXNodeType>>;

  using BoundaryConditionPredicate =
      base::RepeatingCallback<bool(const AXPositionInstance&)>;

  using BoundaryTextOffsetsFunc =
      base::RepeatingCallback<const std::vector<int32_t>&(
          const AXPositionInstance&)>;

  static const int BEFORE_TEXT = -1;
  static const int INVALID_INDEX = -2;
  static const int INVALID_OFFSET = ax::mojom::kNoSelectionOffset;

  static AXPositionInstance CreateNullPosition() {
    AXPositionInstance new_position(new AXPositionType());
    new_position->Initialize(AXPositionKind::NULL_POSITION, AXTreeIDUnknown(),
                             kInvalidAXNodeID, INVALID_INDEX, INVALID_OFFSET,
                             ax::mojom::TextAffinity::kDownstream);
    return new_position;
  }

  static AXPositionInstance CreateTreePosition(const AXNode& anchor,
                                               int child_index) {
    DCHECK(anchor.tree());
    DCHECK_NE(anchor.tree()->GetAXTreeID(), AXTreeIDUnknown());
    DCHECK_NE(anchor.id(), kInvalidAXNodeID);

    AXPositionInstance new_position(new AXPositionType());
    new_position->Initialize(AXPositionKind::TREE_POSITION,
                             anchor.tree()->GetAXTreeID(), anchor.id(),
                             child_index, INVALID_OFFSET,
                             ax::mojom::TextAffinity::kDownstream);
    return new_position;
  }

  static AXPositionInstance CreateTreePositionAtStartOfAnchor(
      const AXNode& anchor) {
    // Initialize the child index:
    // - For a leaf, the child index will be BEFORE_TEXT.
    // - Otherwise the child index will be 0.
    int child_index = IsLeafNodeForTreePosition(anchor) ? BEFORE_TEXT : 0;
    return CreateTreePosition(anchor, child_index);
  }

  static AXPositionInstance CreateTreePositionAtEndOfAnchor(
      const AXNode& anchor) {
    // Initialize the child index to the anchor's child count.
    return CreateTreePosition(anchor,
                              anchor.GetChildCountCrossingTreeBoundary());
  }

  static AXPositionInstance CreateTextPosition(
      const AXNode& anchor,
      int text_offset,
      ax::mojom::TextAffinity affinity) {
    DCHECK(anchor.tree());
    DCHECK_NE(anchor.tree()->GetAXTreeID(), AXTreeIDUnknown());
    DCHECK_NE(anchor.id(), kInvalidAXNodeID);

    AXPositionInstance new_position(new AXPositionType());
    new_position->Initialize(AXPositionKind::TEXT_POSITION,
                             anchor.tree()->GetAXTreeID(), anchor.id(),
                             INVALID_INDEX, text_offset, affinity);
    return new_position;
  }

  virtual ~AXPosition() = default;

  // Implemented based on the copy and swap idiom.
  AXPosition& operator=(const AXPosition& other) {
    AXPositionInstance clone = other.Clone();
    swap(*clone);
    return *this;
  }

  virtual AXPositionInstance Clone() const = 0;

  AXPositionInstance CloneWithDownstreamAffinity() const {
    if (!IsTextPosition()) {
      NOTREACHED() << "Only text positions have affinity.";
    }

    AXPositionInstance clone_with_downstream_affinity = Clone();
    clone_with_downstream_affinity->affinity_ =
        ax::mojom::TextAffinity::kDownstream;
    return clone_with_downstream_affinity;
  }

  AXPositionInstance CloneWithUpstreamAffinity() const {
    if (!IsTextPosition()) {
      NOTREACHED() << "Only text positions have affinity.";
    }

    AXPositionInstance clone_with_upstream_affinity = Clone();
    clone_with_upstream_affinity->affinity_ =
        ax::mojom::TextAffinity::kUpstream;
    return clone_with_upstream_affinity;
  }

  // A serialization of a position as POD. Not for sharing on disk or sharing
  // across thread or process boundaries, just for passing a position to an
  // API that works with positions as opaque objects.
  struct SerializedPosition {
    AXPositionKind kind;
    AXNodeID anchor_id;
    int child_index;
    int text_offset;
    ax::mojom::TextAffinity affinity;
    char tree_id[33];
  };

  static_assert(std::is_trivially_copyable<SerializedPosition>::value,
                "SerializedPosition must be POD");

  SerializedPosition Serialize() {
    SerializedPosition result;
    result.kind = kind_;

    // A tree ID can be serialized as a 32-byte string.
    std::string tree_id_string = tree_id_.ToString();
    DCHECK_LE(tree_id_string.size(), 32U);
    UNSAFE_TODO(strncpy(result.tree_id, tree_id_string.c_str(), 32));
    result.tree_id[32] = 0;

    result.anchor_id = anchor_id_;
    result.child_index = child_index_;
    result.text_offset = text_offset_;
    result.affinity = affinity_;
    return result;
  }

  static AXPositionInstance Unserialize(
      const SerializedPosition& serialization) {
    AXPositionInstance new_position(new AXPositionType());
    // Use initialize without validation because this is used by ATs that
    // used outdated information to generated a selection request.
    new_position->InitializeWithoutValidation(
        serialization.kind, AXTreeID::FromString(serialization.tree_id),
        serialization.anchor_id, serialization.child_index,
        serialization.text_offset, serialization.affinity);
    return new_position;
  }

  std::string ToString() const {
    std::string str;
    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        return "NullPosition";
      case AXPositionKind::TREE_POSITION: {
        std::string str_child_index;
        if (child_index_ == BEFORE_TEXT) {
          str_child_index = "before_text";
        } else if (child_index_ == INVALID_INDEX) {
          str_child_index = "invalid";
        } else {
          str_child_index = base::NumberToString(child_index_);
        }
        str = "TreePosition tree_id=" + tree_id_.ToString() +
              " anchor_id=" + base::NumberToString(anchor_id_) +
              " child_index=" + str_child_index;
        break;
      }
      case AXPositionKind::TEXT_POSITION: {
        std::string str_text_offset;
        if (text_offset_ == INVALID_OFFSET) {
          str_text_offset = "invalid";
        } else {
          str_text_offset = base::NumberToString(text_offset_);
        }
        str = "TextPosition anchor_id=" + base::NumberToString(anchor_id_) +
              " text_offset=" + str_text_offset + " affinity=" +
              ui::ToString(static_cast<ax::mojom::TextAffinity>(affinity_));
        break;
      }
    }

    if (!IsTextPosition() || text_offset_ < 0 || text_offset_ > MaxTextOffset())
      return str;

    const std::u16string& text = GetText();
    DCHECK_GE(text_offset_, 0);
    const size_t max_text_offset = text.size();
    DCHECK_LE(text_offset_, static_cast<int>(max_text_offset)) << text;
    std::u16string annotated_text;
    if (text_offset_ == static_cast<int>(max_text_offset)) {
      annotated_text = text + u"<>";
    } else {
      // TODO(aleventhal) This extra casting is only necessary to satisfy a
      // compiler error that strangely occurs only when Initialize() contains
      // SnapToMaxTextOffsetIfBeyond().
      size_t unsigned_text_offset = static_cast<size_t>(text_offset_);
      annotated_text = text.substr(0, unsigned_text_offset) + u"<" +
                       text[unsigned_text_offset] + u">" +
                       text.substr(unsigned_text_offset + 1);
    }

    return str + " annotated_text=" + base::UTF16ToUTF8(annotated_text);
  }

  // Helper for logging the position, the AXTreeManager and the anchor node.
  std::string ToDebugString() const {
    std::ostringstream str;
    str << "* Position: " << ToString();
    if (GetAnchor()) {
      str << "\n* Anchor node: " << *GetAnchor();
      if (IsTreePosition()) {
        str << "\n* AnchorChildCount(): " << AnchorChildCount()
            << "\n* IsLeaf(): " << IsLeaf();
      } else {
        str << "\n* TextOffset: " << text_offset()
            << "\n* MaxTextOffset: " << MaxTextOffset();
      }
    }
    if (GetManager())
      str << "\n* Tree: " << GetManager()->ax_tree()->data().ToString();
    return str.str();
  }

  AXPositionKind kind() const { return kind_; }
  AXTreeID tree_id() const { return tree_id_; }
  AXNodeID anchor_id() const { return anchor_id_; }

  AXTreeManager* GetManager() const { return AXTreeManager::FromID(tree_id()); }

  // Returns true if this position is within an "empty object", i.e. within a
  // node that should contribute no text to the accessibility tree's text
  // representation. For example, returns true if this position is within an
  // empty control, such as an empty text field or (on Windows) a collapsed
  // popup menu. On some platforms, such nodes need to be represented by an
  // "object replacement character". This character is inserted purely for
  // navigational purposes. This is because empty controls still need to act as
  // a word and character boundary on those platforms.
  static bool IsEmptyObject(const AXNode& node) {
    // A collapsed popup button that contains a menu list popup (i.e, the exact
    // subtree representation we get from a collapsed <select> element on
    // Windows) should not expose its descendants even though they are not
    // ignored.
    if (node.IsCollapsedMenuListSelect())
      return true;

    // All anchor nodes that are empty leaf nodes should be treated as empty
    // objects. Empty leaf nodes are defined as nodes whose descendants are (A)
    // not exposed to any platform accessibility APIs and (B) do not contribute
    // any text to the tree's text representation. They may have unignored
    // descendants however. They do not have any text content, hence they are
    // empty from our perspective. For example, an empty text field may still
    // have an unignored generic container inside it.
    if (!node.IsEmptyLeaf())
      return false;

    // While atomic text fields from web content have a text node descendant,
    // atomic text fields from Views don't. Their text value is set in the value
    // attribute of the text field node directly.
    if (node.IsView() && node.data().IsAtomicTextField() &&
        !node.GetValueForControl().empty()) {
      return false;
    }

    // One exception to the above rule that all empty leaf nodes are empty
    // objects in AXPosition are <embed> and <object> elements that have
    // children. They should not be treated as empty objects even when their
    // descendants are all ignored so that text navigation won't stop on such
    // nodes.
    ax::mojom::Role role = node.GetRole();
    if ((role == ax::mojom::Role::kEmbeddedObject ||
         role == ax::mojom::Role::kPluginObject) &&
        node.GetChildCountCrossingTreeBoundary()) {
      return false;
    }

    // Nodes that are skipped during text navigation should also be "empty
    // objects".
    //
    // Note that nodes that are skipped during text navigation could still have
    // positions anchored to them, e.g. for determining if a paragraph boundary
    // should be reported before or after such a node. Descending into the
    // children of such objects could add unnecessary extra text boundaries.
    if (node.IsIgnoredForTextNavigation())
      return true;

    // Another exception to the rule that all leaf nodes in the accessibility
    // tree should be "empty objects" are kRootWebArea, kPdfRoot, kIframe,
    // kIframePresentational, and text nodes. We don't want text navigation to
    // stop on any of the above roles. On the other hand, nodes that only have
    // ignored children (e.g., a button that contains only an empty ignored div)
    // need to be treated as leaf nodes.
    //
    // Note that we have already determined that the anchor at this position
    // doesn't have an unignored child, making this a leaf tree or text
    // position, or a leaf's descendant.
    return (!IsPlatformDocument(role) && !IsIframe(role) && !node.IsText());
  }

  // Return true if the node is a leaf, or has no selectable text content.
  static bool IsLeafNodeForTreePosition(const AXNode& node) {
    // Unignored text list markers expose text on their own, and all their
    // descendants are ignored. Make sure they are treated as leaves, not empty
    // containers.
    if (node.GetRole() == ax::mojom::Role::kListMarker && !node.IsIgnored() &&
        !node.GetUnignoredChildCountCrossingTreeBoundary()) {
      return true;
    }
    return !node.GetChildCountCrossingTreeBoundary() || IsEmptyObject(node);
  }

  AXNode* GetAnchor() const {
    if (tree_id_ == AXTreeIDUnknown() || anchor_id_ == kInvalidAXNodeID)
      return nullptr;

    const AXTreeManager* manager = GetManager();
    if (manager)
      return manager->GetNode(anchor_id());

    return nullptr;
  }

  int GetAnchorSiblingCount() const {
    if (IsNullPosition())
      return 0;

    AXPositionInstance parent_position = AsTreePosition()->CreateParentPosition(
        ax::mojom::MoveDirection::kBackward);
    if (!parent_position->IsNullPosition())
      return parent_position->AnchorChildCount();

    return 0;
  }

  int child_index() const { return child_index_; }
  int text_offset() const { return text_offset_; }
  ax::mojom::TextAffinity affinity() const { return affinity_; }

  bool IsIgnored() const {
    if (IsNullPosition())
      return false;

    DCHECK(GetAnchor());
    // If this position is anchored to an ignored node, then consider this
    // position to be ignored.
    if (GetAnchor()->IsIgnored())
      return true;

    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        NOTREACHED();
      case AXPositionKind::TREE_POSITION: {
        // If this is a "before text" or an "after text" tree position, it's
        // pointing to the anchor itself, which we've determined to be
        // unignored.
        DCHECK(child_index_ == BEFORE_TEXT ||
               child_index_ == AnchorChildCount() || !IsLeaf())
            << "Leaf nodes can only have a position before or after, so they "
               "must have a child index of BEFORE_TEXT or AnchorChildCount(): "
            << ToString() << "  AnchorChildCount(): " << AnchorChildCount();
        DCHECK(child_index_ != BEFORE_TEXT || IsLeaf())
            << "Non-leaf nodes cannot have a position of BEFORE_TEXT: "
            << *GetAnchor();
        if (child_index_ == BEFORE_TEXT || IsLeaf())
          return false;

        // If this position is an "after children" position, consider the
        // position to be ignored if the last child is ignored. This is because
        // the last child will not be visible in the unignored tree.
        //
        // For example, in the following tree if the position is not adjusted,
        // the resulting position would erroneously point before the second
        // child in the unignored subtree rooted at the last child.
        //
        // 1 kRootWebArea
        // ++2 kGenericContainer ignored
        // ++++3 kStaticText "Line 1."
        // ++++4 kStaticText "Line 2."
        //
        // Tree position anchor=kGenericContainer, child_index=1.
        //
        // Alternatively, if there is a node at the position pointed to by
        // "child_index_", i.e. this position is neither a leaf position nor an
        // "after children" position, consider this tree position to be ignored
        // if the child node is ignored.
        int adjusted_child_index = child_index_ != AnchorChildCount()
                                       ? child_index_
                                       : child_index_ - 1;
        AXPositionInstance child_position =
            CreateChildPositionAt(adjusted_child_index);
        DCHECK(child_position && !child_position->IsNullPosition());
        return child_position->IsNullPosition() ||
               child_position->GetAnchor()->IsIgnored();
      }
      case AXPositionKind::TEXT_POSITION:
        // If the corresponding leaf position is ignored, the current text
        // offset will point to ignored text. Therefore, consider this position
        // to be ignored.
        if (!IsLeaf())
          return AsLeafTreePosition()->IsIgnored();
        return false;
    }
  }

  bool IsNullPosition() const {
    return kind_ == AXPositionKind::NULL_POSITION || !GetAnchor();
  }

  bool IsTreePosition() const {
    return GetAnchor() && kind_ == AXPositionKind::TREE_POSITION;
  }

  bool IsLeafTreePosition() const { return IsTreePosition() && IsLeaf(); }

  bool IsTextPosition() const {
    return GetAnchor() && kind_ == AXPositionKind::TEXT_POSITION;
  }

  bool IsLeafTextPosition() const { return IsTextPosition() && IsLeaf(); }

  bool IsLeaf() const {
    if (IsNullPosition())
      return false;

    AXNode* anchor = GetAnchor();
    DCHECK(anchor);

    return IsLeafNodeForTreePosition(*anchor);
  }

  // Returns true if this is a valid position, e.g. the child_index_ or
  // text_offset_ is within a valid range.
  //
  // A position is always valid at creation time, but could become invalid after
  // a tree update. For performance reasons, we don't check for validity every
  // time a position is used, expecting clients to use this method instead.
  bool IsValid() const {
    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        return tree_id_ == AXTreeIDUnknown() &&
               anchor_id_ == kInvalidAXNodeID &&
               child_index_ == INVALID_INDEX &&
               text_offset_ == INVALID_OFFSET &&
               affinity_ == ax::mojom::TextAffinity::kDownstream;
      case AXPositionKind::TREE_POSITION:
        if (!GetAnchor())
          return false;

        // The `BEFORE_TEXT` constant is only needed on leaf positions because
        // on any other position a `child_index_` of 0 could be used. On leaf
        // positions, however, often there are no child nodes and so a
        // `child_index_` of 0 would confusingly indicate both a "before text"
        // as well as an "after text" position. Note that some leaf positions,
        // e.g. positions in empty objects, do have children.
        if (IsLeaf()) {
          // Leaf nodes can only have a position before or after, so they must
          // have a child index of BEFORE_TEXT or AnchorChildCount().
          return child_index_ == BEFORE_TEXT ||
                 child_index_ == AnchorChildCount();
        }

        return child_index_ >= 0 && child_index_ <= AnchorChildCount();
      case AXPositionKind::TEXT_POSITION:
        if (!GetAnchor())
          return false;

        // For performance reasons we skip any validation of the text offset
        // that involves retrieving the anchor's text, if the offset is set to
        // 0, because 0 is frequently used and always valid regardless of the
        // actual text.
        return text_offset_ == 0 ||
               (text_offset_ > 0 && text_offset_ <= MaxTextOffset());
    }
  }

  bool AtStartOfAnchor() const {
    if (!GetAnchor())
      return false;
    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        if (IsLeaf())
          return child_index_ == BEFORE_TEXT;
        return child_index_ == 0;
      case AXPositionKind::TEXT_POSITION:
        return text_offset_ == 0;
    }
  }

  bool AtEndOfAnchor() const {
    if (!GetAnchor())
      return false;
    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        // A few positions are anchored to nodes that have children but we want
        // to treat them as leaf positions. An example is an empty text field;
        // it often has empty unignored divs coming from Blink inside it.
        return child_index_ == AnchorChildCount();
      case AXPositionKind::TEXT_POSITION:
        return text_offset_ == MaxTextOffset();
    }
  }

  bool AtStartOfWord() const {
    AXPositionInstance text_position;
    if (!AtEndOfAnchor()) {
      // We could get a leaf text position at the end of its anchor, where word
      // start offsets would surely not be present. In such cases, we need to
      // normalize to the start of the next leaf anchor. We avoid making this
      // change when we are at the end of our anchor because this could
      // effectively shift the position forward.
      text_position = AsLeafTextPositionBeforeCharacter();
    } else {
      text_position = AsLeafTextPosition();
    }

    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        const std::vector<int32_t>& word_starts =
            text_position->GetWordStartOffsets();
        return base::Contains(word_starts,
                              int32_t{text_position->text_offset_});
      }
    }
  }

  bool AtEndOfWord() const {
    AXPositionInstance text_position;
    if (!AtStartOfAnchor()) {
      // We could get a leaf text position at the start of its anchor, where
      // word end offsets would surely not be present. In such cases, we need to
      // normalize to the end of the previous leaf anchor. We avoid making this
      // change when we are at the start of our anchor because this could
      // effectively shift the position backward.
      text_position = AsLeafTextPositionAfterCharacter();
    } else {
      text_position = AsLeafTextPosition();
    }

    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        const std::vector<int32_t>& word_ends =
            text_position->GetWordEndOffsets();
        return base::Contains(word_ends, int32_t{text_position->text_offset_});
      }
    }
  }

  bool AtStartOfLine() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION:
        // We treat a position after some white space that is not connected to
        // any node after it via "next on line ID", to be equivalent to a
        // position before the next line, and therefore as being at start of
        // line.
        //
        // We assume that white space, including but not limited to hard line
        // breaks, might be used to separate lines. For example, an inline text
        // box with just a single space character inside it can be used to
        // represent a soft line break. If an inline text box containing white
        // space separates two lines, it should always be connected to the first
        // line via "kPreviousOnLineId". This is guaranteed by the renderer. If
        // there are multiple line breaks separating the two lines, then only
        // the first line break is connected to the first line via
        // "kPreviousOnLineId".
        //
        // Sometimes there might be an inline text box with a single space in it
        // at the end of a text field. We should not mark positions that are at
        // the end of text fields, or in general at the end of their anchor, as
        // being at the start of line, except when that anchor is an inline text
        // box that is in the middle of a text span. Note that in most but not
        // all cases, the parent of an inline text box is a static text object,
        // whose end signifies the end of the text span. One exception is line
        // breaks.
        if (text_position->AtEndOfAnchor() &&
            !text_position->AtEndOfTextSpan() &&
            text_position->IsInWhiteSpace() &&
            text_position->GetNextOnLineID() == kInvalidAXNodeID) {
          return true;
        }

        // If the anchor is ignored, then by default it will not have a
        // PreviousOnLineID set since we only set this on unignored nodes.
        // However, it could still have something previous to it on the same
        // line, like for example if we have some text on the same line, and a
        // text node in the middle is set to aria-hidden.
        return text_position->GetPreviousOnLineID() == kInvalidAXNodeID &&
               text_position->AtStartOfAnchor() &&
               !text_position->GetAnchor()->IsIgnored();
    }
  }

  bool AtEndOfLine() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION:
        // Text positions on objects with no text should not be considered at
        // end of line because the empty position may share a text offset with
        // a non-empty text position in which case the end of line iterators
        // must move to the line end of the non-empty content. Specified next
        // line IDs are ignored.
        if (text_position->MaxTextOffset() == 0)
          return false;

        // If affinity has been used to specify whether the caret is at the end
        // of a line or at the start of the next one, this should have been
        // reflected in the leaf text position we got via "AsLeafTextPosition".
        // If affinity had been set to upstream, the leaf text position should
        // be pointing to the end of the inline text box that ends the first
        // line. If it had been set to downstream, the leaf text position should
        // be pointing to the start of the inline text box that starts the
        // second line.
        //
        // In other cases, we assume that white space, including but not limited
        // to hard line breaks, might be used to separate lines. For example, an
        // inline text box with just a single space character inside it can be
        // used to represent a soft line break. If an inline text box containing
        // white space separates two lines, it should always be connected to the
        // first line via "kPreviousOnLineId". This is guaranteed by the
        // renderer. If there are multiple line breaks separating the two lines,
        // then only the first line break is connected to the first line via
        // "kPreviousOnLineId".
        //
        // We don't treat a position that is at the start of white space that is
        // on a line by itself as being at the end of the line. This is in order
        // to enable screen readers to recognize and announce blank lines
        // correctly. However, we do treat positions at the start of white space
        // that end a line of text as being at the end of that line. We also
        // treat positions at the end of white space that is on a line by
        // itself, i.e. on a blank line, as being at the end of that line.
        //
        // Sometimes there might be an inline text box with a single space in it
        // at the end of a text field. We should mark positions that are at the
        // end of text fields, or in general at the end of an anchor with no
        // "kNextOnLineId", as being at end of line, except when that anchor is
        // an inline text box that is in the middle of a text span. Note that
        // in most but not all cases, the parent of an inline text box is a
        // static text object, whose end signifies the end of the text span. One
        // exception is line breaks.
        if (text_position->GetNextOnLineID() == kInvalidAXNodeID) {
          return (!text_position->AtEndOfTextSpan() &&
                  text_position->IsInWhiteSpace() &&
                  text_position->GetPreviousOnLineID() != kInvalidAXNodeID)
                     ? text_position->AtStartOfAnchor()
                     : text_position->AtEndOfAnchor();
        }

        // The current anchor might be followed by a soft line break.
        return text_position->AtEndOfAnchor() &&
               text_position->CreateNextLeafTextPosition()->AtEndOfLine();
    }
  }

  AXBoundaryType GetFormatStartBoundaryType() const {
    // Since formats are stored on text anchors, the start of a format boundary
    // must be at the start of an anchor.
    if (IsNullPosition() || !AtStartOfAnchor()) {
      return AXBoundaryType::kNone;
    }

    // Treat the first iterable node as a format boundary.
    if (CreatePreviousLeafTreePosition(
            base::BindRepeating(&AbortMoveAtRootBoundary))
            ->IsNullPosition()) {
      return AXBoundaryType::kContentStart;
    }

    // Ignored positions cannot be format boundaries.
    if (IsIgnored()) {
      return AXBoundaryType::kNone;
    }

    // Iterate over anchors until a format boundary is found. This will return a
    // null position upon crossing a boundary. Make sure the previous position
    // is not on an ignored node.
    AXPositionInstance previous_position = Clone();
    do {
      previous_position = previous_position->CreatePreviousLeafTreePosition(
          base::BindRepeating(&AbortMoveAtFormatBoundary));
    } while (previous_position->IsIgnored());

    if (previous_position->IsNullPosition())
      return AXBoundaryType::kUnitBoundary;

    return AXBoundaryType::kNone;
  }

  bool AtStartOfFormat() const {
    AXPositionInstance text_position = AsLeafTextPosition();

    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        const std::vector<int32_t>& format_starts =
            text_position->GetFormatStartOffsets();
        if (format_starts.size() <= 1) {
          return GetFormatStartBoundaryType() != AXBoundaryType::kNone;
        }
        return base::Contains(format_starts,
                              int32_t{text_position->text_offset_});
      }
    }
  }

  AXBoundaryType GetFormatEndBoundaryType() const {
    // Since formats are stored on text anchors, the end of a format break must
    // be at the end of an anchor.
    if (IsNullPosition() || !AtEndOfAnchor()) {
      return AXBoundaryType::kNone;
    }

    // Treat the last iterable node as a format boundary
    if (CreateNextLeafTreePosition(
            base::BindRepeating(&AbortMoveAtRootBoundary))
            ->IsNullPosition())
      return AXBoundaryType::kContentEnd;

    // Ignored positions cannot be format boundaries.
    if (IsIgnored()) {
      return AXBoundaryType::kNone;
    }

    // Iterate over anchors until a format boundary is found. This will return a
    // null position upon crossing a boundary. Make sure the next position is
    // not on an ignored node.
    AXPositionInstance next_position = Clone();
    do {
      next_position = next_position->CreateNextLeafTreePosition(
          base::BindRepeating(&AbortMoveAtFormatBoundary));
    } while (next_position->IsIgnored());

    if (next_position->IsNullPosition())
      return AXBoundaryType::kUnitBoundary;

    return AXBoundaryType::kNone;
  }

  bool AtEndOfFormat() const {
    AXPositionInstance text_position = AsLeafTextPosition();

    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        const std::vector<int32_t>& format_ends =
            text_position->GetFormatEndOffsets();
        if (format_ends.size() <= 1) {
          return GetFormatEndBoundaryType() != AXBoundaryType::kNone;
        }
        return base::Contains(format_ends,
                              int32_t{text_position->text_offset_});
      }
    }
  }

  bool AtStartOfSentence() const {
    AXPositionInstance text_position;
    if (!AtEndOfAnchor()) {
      // We could get a leaf text position at the end of its anchor, where
      // sentence start offsets would surely not be present. In such cases, we
      // need to normalize to the start of the next leaf anchor. We avoid making
      // this change when we are at the end of our anchor because this could
      // effectively shift the position forward.
      text_position = AsLeafTextPositionBeforeCharacter();
    } else {
      text_position = AsLeafTextPosition();
    }

    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        const std::vector<int32_t>& sentence_starts =
            text_position->GetAnchor()->GetIntListAttribute(
                ax::mojom::IntListAttribute::kSentenceStarts);
        return base::Contains(sentence_starts,
                              int32_t{text_position->text_offset_});
      }
    }
  }

  bool AtEndOfSentence() const {
    AXPositionInstance text_position;
    if (!AtStartOfAnchor()) {
      // We could get a leaf text position at the start of its anchor, where
      // sentence end offsets would surely not be present. In such cases, we
      // need to normalize to the end of the previous leaf anchor. We avoid
      // making this change when we are at the start of our anchor because this
      // could effectively shift the position backward.
      text_position = AsLeafTextPositionAfterCharacter();
    } else {
      text_position = AsLeafTextPosition();
    }

    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        const std::vector<int32_t>& sentence_ends =
            text_position->GetAnchor()->GetIntListAttribute(
                ax::mojom::IntListAttribute::kSentenceEnds);
        return base::Contains(sentence_ends,
                              int32_t{text_position->text_offset_});
      }
    }
  }

  // `AtStartOfParagraph` is asymmetric from `AtEndOfParagraph` because line
  // breaks could be present between paragraphs. The end of the paragraph is
  // always before all such breaks, whilst the start of paragraph is always
  // after.
  //
  // The start of a paragraph should be a leaf text position (or equivalent),
  // either at the start of the whole content, or at the start of a leaf text
  // position which is right after the one representing the end of the previous
  // paragraph, or the one representing one or more line breaks that separate
  // the two paragraphs.
  //
  // In other words, a position `AsLeafTextPosition` is the start of a paragraph
  // if one of the following is true :
  // 1. The current leaf text position must be at the start of an anchor, or
  // after a '\n' character if white space is preserved (e.g. when using
  // <pre>...</pre>, or when in an ARIA label), but not before a '\n' character
  // in a <br> element unless multiple consecutive <br> elements are present and
  // so empty paragraphs have been created.
  // 2. Either (a) the current leaf text position is the first leaf text
  // position in the whole content, or (b) there is a line breaking object
  // between it and the previous leaf text position including any <br> element.
  bool AtStartOfParagraph() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        // 1. The current leaf text position must be at the start of an anchor,
        // or after a '\n' character if white space is preserved (e.g. when
        // using <pre>...</pre>, or when in an ARIA label), but not before a
        // '\n' character in a <br> element unless multiple consecutive <br>
        // elements are present and so empty paragraphs have been created.
        //
        // Note that, in theory, `!AtStartOfAnchor()` implies that
        // `MaxTextOffset()` > 0 and `text_offset()` > 0. Therefore,
        // `text_position->GetText().at(text_position->text_offset_ - 1)` should
        // always be valid. However, as reported by https://crbug.com/1379716,
        // this logic appears to have flaws.
        //
        // TODO(accessibility): Investigate what are these edge cases that lead
        // to have a `text_offset_` greater than or equal to the `text` length.
        if (!text_position->AtStartOfAnchor()) {
          if (!text_position->IsPointingToLineBreak()) {
            const std::u16string text = text_position->GetText();
            if (static_cast<size_t>(text_position->text_offset_) <
                    text.length() &&
                text.at(text_position->text_offset_) == '\n') {
              return true;
            }
          }
          return false;
        }

        // 2. Either (a) the current leaf text position is the first leaf text
        // position in the whole content, or (b) there is a line breaking object
        // between it and the previous leaf text position including any <br>
        // element.
        //
        // Search for the previous text position within the current paragraph,
        // using the paragraph boundary abort predicate. If a valid position was
        // found, then this position cannot be the start of a paragraph. The
        // predicate will return a null position when an anchor movement would
        // cross a paragraph boundary, or the start of content has been reached.
        const AbortMovePredicate abort_move_predicate =
            base::BindRepeating(&AbortMoveAtParagraphBoundary,
                                ax::mojom::TextBoundary::kParagraphStart);
        return text_position
            ->CreatePreviousLeafTextPosition(abort_move_predicate)
            ->IsNullPosition();
      }
    }
  }

  // `AtEndOfParagraph` is asymmetric from `AtStartOfParagraph` because line
  // breaks could be present between paragraphs. The end of the paragraph is
  // always before all such breaks, whilst the start of paragraph is always
  // after.
  //
  // The end of a paragraph should be a leaf text position (or equivalent),
  // either at the end of the whole content, or at the end of a leaf text
  // position which is right before the one representing the start of the next
  // paragraph, or the one representing one or more line breaks that separate
  // the two paragraphs.
  //
  // In other words, a position `AsLeafTextPosition` is the end of a paragraph
  // if one of the following is true :
  // 1. The current leaf text position must be at the end of an anchor, or
  // before a '\n' character if white space is preserved (e.g. when using
  // <pre>...</pre>, or when in an ARIA label), but not after a '\n' character
  // in a <br> element unless multiple consecutive <br> elements are present and
  // so empty paragraphs have been created.
  // 2. Either (a) the current leaf text position is the last leaf text position
  // in the whole content, or (b) there is a line breaking object between it and
  // the next leaf text position, including any <br> element.
  bool AtEndOfParagraph() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        // 1. The current leaf text position must be at the end of an anchor, or
        // before a '\n' character if white space is preserved (e.g. when using
        // <pre>...</pre>, or when in an ARIA label), but not after a '\n'
        // character in a <br> element unless multiple consecutive <br> elements
        // are present and so empty paragraphs have been created.
        //
        // Note that, in theory, `!AtEndOfAnchor()` implies
        // `AtStartOfAnchor()` != `AtEndOfAnchor()` which in turn implies that
        // `MaxTextOffset()` > 0 and `text_offset()` < `MaxTextOffset()`.
        // Therefore, `text_position->GetText().at(text_position->text_offset_)`
        // should always be valid. However, as reported by
        // https://crbug.com/1379716, this logic appears to have flaws.
        //
        // TODO(accessibility): Investigate what are these edge cases that lead
        // to have a `text_offset_` greater than or equal to the `text` length.
        if (!text_position->AtEndOfAnchor()) {
          if (!text_position->IsPointingToLineBreak()) {
            const std::u16string text = text_position->GetText();
            if (static_cast<size_t>(text_position->text_offset_) <
                    text.length() &&
                text.at(text_position->text_offset_) == '\n') {
              return true;
            }
          }
          return false;
        }

        // 2. Either (a) the current leaf text position is the last leaf text
        // position in the whole content, or (b) there is a line breaking object
        // between it and the next leaf text position, including any <br>
        // element.
        //
        // Search for the next text position within the current paragraph, using
        // the paragraph boundary abort predicate. If a valid position was
        // found, then this position cannot be the end of a paragraph. The
        // predicate will return a null position when an anchor movement would
        // cross a paragraph boundary, or the end of content has been reached.
        const AbortMovePredicate abort_move_predicate =
            base::BindRepeating(&AbortMoveAtParagraphBoundary,
                                ax::mojom::TextBoundary::kParagraphEnd);
        return text_position->CreateNextLeafTextPosition(abort_move_predicate)
            ->IsNullPosition();
      }
    }
  }

  // Returns true if this position is at the start or right before content that
  // is laid out using "display: inline-block".
  bool AtStartOfInlineBlock() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        if (text_position->AtStartOfAnchor()) {
          AXPositionInstance previous_position =
              text_position->CreatePreviousLeafTreePosition();

          // Check that this position is not the start of the first anchor.
          if (!previous_position->IsNullPosition()) {
            previous_position = text_position->CreatePreviousLeafTreePosition(
                base::BindRepeating(&AbortMoveAtStartOfInlineBlock));

            // If we get a null position here it means we have crossed an inline
            // block's start, thus this position is located at such start.
            if (previous_position->IsNullPosition())
              return true;
          }
        }
        if (text_position->AtEndOfAnchor()) {
          AXPositionInstance next_position =
              text_position->CreateNextLeafTreePosition();

          // Check that this position is not the end of the last anchor.
          if (!next_position->IsNullPosition()) {
            next_position = text_position->CreateNextLeafTreePosition(
                base::BindRepeating(&AbortMoveAtStartOfInlineBlock));

            // If we get a null position here it means we have crossed an inline
            // block's start, thus this position is located at such start.
            if (next_position->IsNullPosition())
              return true;
          }
        }
        return false;
      }
    }
  }

  // Page boundaries are only supported in certain content types, e.g. PDF
  // documents.
  bool AtStartOfPage() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        if (!text_position->AtStartOfAnchor())
          return false;

        // Search for the previous text position within the current page,
        // using the page boundary abort predicate.
        // If a valid position was found, then this position cannot be
        // the start of a page.
        // This will return a null position when an anchor movement would
        // cross a page boundary, or the start of content was reached.
        AXPositionInstance previous_text_position =
            text_position->CreatePreviousLeafTextPosition(
                base::BindRepeating(&AbortMoveAtPageBoundary));
        return previous_text_position->IsNullPosition();
      }
    }
  }

  // Page boundaries are only supported in certain content types, e.g. PDF
  // documents.
  bool AtEndOfPage() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    switch (text_position->kind_) {
      case AXPositionKind::NULL_POSITION:
        return false;
      case AXPositionKind::TREE_POSITION:
        NOTREACHED();
      case AXPositionKind::TEXT_POSITION: {
        if (!text_position->AtEndOfAnchor())
          return false;

        // Search for the next text position within the current page,
        // using the page boundary abort predicate.
        // If a valid position was found, then this position cannot be
        // the end of a page.
        // This will return a null position when an anchor movement would
        // cross a page boundary, or the end of content was reached.
        AXPositionInstance next_text_position =
            text_position->CreateNextLeafTextPosition(
                base::BindRepeating(&AbortMoveAtPageBoundary));
        return next_text_position->IsNullPosition();
      }
    }
  }

  // Returns true if this position is at the start of the current accessibility
  // tree, such as the current iframe, webpage, PDF document, dialog or window.
  // Note that the current webpage could be made up of multiple accessibility
  // trees stitched together, e.g. an out-of-process iframe will be in its own
  // accessibility tree. For the purposes of this method, we don't distinguish
  // between out-of-process and in-process iframes, treating them both as tree
  // boundaries.
  bool AtStartOfAXTree() const {
    if (IsNullPosition() || !AtStartOfAnchor())
      return false;

    AXPositionInstance previous_anchor = CreatePreviousAnchorPosition();
    // The start of the whole content should also be the start of an AXTree.
    if (previous_anchor->IsNullPosition())
      return true;

    return previous_anchor->tree_id() != tree_id();
  }

  // Returns true if this position is at the end of the current accessibility
  // tree, such as the current iframe, webpage, PDF document, dialog or window.
  // Note that the current webpage could be made up of multiple accessibility
  // trees stitched together, e.g. an out-of-process iframe will be in its own
  // accessibility tree. For the purposes of this method, we don't distinguish
  // between out-of-process and in-process iframes, treating them both as tree
  // boundaries.
  bool AtEndOfAXTree() const {
    if (IsNullPosition() || !IsLeaf() || !AtEndOfAnchor())
      return false;

    return *CreatePositionAtEndOfAXTree() == *this;
  }

  // Returns true if this position is at the start of all content. This might
  // refer to e.g. a single webpage (made up of multiple iframes), or a PDF
  // document. Note that the current webpage could be made up of multiple
  // accessibility trees stitched together, so even though a position could be
  // at the start of a specific accessibility tree, it might not be at the start
  // of the whole content.
  bool AtStartOfContent() const {
    if (IsNullPosition() || !AtStartOfAnchor())
      return false;

    return *CreatePositionAtStartOfContent() == *this;
  }

  // Returns true if this position is at the end of all content. This might
  // refer to e.g. a single webpage (made up of multiple iframes), or a PDF
  // document. Note that the current webpage could be made up of multiple
  // accessibility trees stitched together, so even though a position could be
  // at the end of a specific accessibility tree, it might not be at the end of
  // the whole content.
  bool AtEndOfContent() const {
    if (IsNullPosition() || !AtEndOfAnchor())
      return false;

    return *CreatePositionAtEndOfContent() == *this;
  }

  // This method finds the lowest common ancestor node in the accessibility tree
  // of this and |other| positions' anchor nodes.
  AXNode* LowestCommonAnchor(const AXPosition& other) const {
    if (IsNullPosition() || other.IsNullPosition())
      return nullptr;
    if (GetAnchor() == other.GetAnchor())
      return GetAnchor();

    base::stack<AXNode*> our_ancestors = GetAncestorAnchors();
    base::stack<AXNode*> other_ancestors = other.GetAncestorAnchors();

    AXNode* common_anchor = nullptr;
    while (!our_ancestors.empty() && !other_ancestors.empty() &&
           our_ancestors.top() == other_ancestors.top()) {
      common_anchor = our_ancestors.top();
      our_ancestors.pop();
      other_ancestors.pop();
    }
    return common_anchor;
  }

  // This method returns a position instead of a node because this allows us to
  // return the corresponding text offset or child index in the ancestor that
  // relates to the current position.
  // Also, this method uses position instead of tree logic to traverse the tree,
  // because positions can handle moving across multiple trees, while trees
  // cannot.
  AXPositionInstance LowestCommonAncestorPosition(
      const AXPosition& other,
      ax::mojom::MoveDirection move_direction) const {
    return CreateAncestorPosition(LowestCommonAnchor(other), move_direction);
  }

  // See "CreateParentPosition" for an explanation of the use of
  // |move_direction|.
  AXPositionInstance CreateAncestorPosition(
      const AXNode* ancestor_anchor,
      ax::mojom::MoveDirection move_direction) const {
    if (!ancestor_anchor)
      return CreateNullPosition();

    AXPositionInstance ancestor_position = Clone();
    while (!ancestor_position->IsNullPosition() &&
           ancestor_position->GetAnchor() != ancestor_anchor) {
      ancestor_position =
          ancestor_position->CreateParentPosition(move_direction);
    }
    return ancestor_position;
  }

  // If the position is not valid, we return a new valid position that is
  // closest to the original position if possible, or a null position otherwise.
  AXPositionInstance AsValidPosition() const {
    AXPositionInstance position = Clone();
    switch (position->kind_) {
      case AXPositionKind::NULL_POSITION:
        // We avoid cloning to ensure that all fields will be valid.
        return CreateNullPosition();
      case AXPositionKind::TREE_POSITION: {
        if (!position->GetAnchor())
          return CreateNullPosition();

        const AXNode* leaf_node = GetEmptyObjectAncestorNode();
        if (!leaf_node && position->IsLeaf()) {
          // If there is no empty object ancestor, but the current position's
          // anchor is a leaf, then use the same anchor, as it will be valid
          // as long as a valid offset is used.
          leaf_node = position->GetAnchor();
        }
        if (leaf_node) {
          // In this class, we define the empty node as a leaf node (see
          // `AXNode::IsLeaf()`) that doesn't have any content. On certain
          // platforms, and so that such nodes will act as a character and a
          // word boundary, we insert an "embedded object replacement character"
          // in their text contents. This character is a string of length
          // `AXNode::kEmbeddedObjectCharacterLengthUTF16`. For example, an
          // empty text field should act as a character and a word boundary when
          // a screen reader user tries to navigate through it, otherwise the
          // text field would be missed by the user.
          //
          // Since we just explained that on certain platforms empty leaf nodes
          // expose the "embedded object replacement character" in their text
          // contents, and since we assume that all text is found only on leaf
          // nodes, we should hide any descendants. Thus, a position on a
          // descendant of an empty object is defined as invalid. To make it
          // valid we move the position from the descendant to the empty leaf
          // node itself. Otherwise, character and word navigation won't work
          // properly.
          AXPositionInstance new_position =
              position->child_index() == BEFORE_TEXT
                  ? CreateTreePositionAtStartOfAnchor(*leaf_node)
                  : CreateTreePositionAtEndOfAnchor(*leaf_node);
          DCHECK(new_position->IsLeaf());
          return new_position;
        }

        DCHECK(!position->IsLeaf());
        // Not a leaf: use a child index from 0 to AnchorChilkdCount().
        if (position->child_index_ == BEFORE_TEXT) {
          position->child_index_ = 0;
          return position;
        }

        DCHECK_GE(position->child_index_, 0);
        if (position->child_index_ > position->AnchorChildCount())
          position->child_index_ = position->AnchorChildCount();
        break;
      }
      case AXPositionKind::TEXT_POSITION: {
        if (!position->GetAnchor())
          return CreateNullPosition();

        if (const AXNode* empty_object_node = GetEmptyObjectAncestorNode()) {
          // This is needed because an empty object as defined in this class can
          // have descendants that should not be exposed. See comment above in
          // similar implementation for AXPositionKind::TREE_POSITION.
          //
          // We set the |text_offset_| to either 0 or (on certain platforms) the
          // length of the embedded object character here because the
          // `MaxTextOffset()` of an empty object on those platforms is
          // `AXNode::kEmbeddedObjectCharacterLengthUTF16`. If the invalid
          // position was already at the start of the node, we set it to 0.
          AXPositionInstance valid_position = CreateTextPosition(
              *empty_object_node,
              /* text_offset */ 0, ax::mojom::TextAffinity::kDownstream);
          if (position->text_offset() > 0)
            return valid_position->CreatePositionAtEndOfAnchor();
          return std::move(valid_position);
        }

        if (position->text_offset_ <= 0) {
          // 0 is always a valid offset, so skip calling MaxTextOffset in that
          // case.
          position->text_offset_ = 0;
          position->affinity_ = ax::mojom::TextAffinity::kDownstream;
        } else {
          int max_text_offset = position->MaxTextOffset();
          if (position->text_offset_ > max_text_offset) {
            position->text_offset_ = max_text_offset;
            position->affinity_ = ax::mojom::TextAffinity::kDownstream;
          }
        }
        break;
      }
    }
    DCHECK(position->IsValid()) << *position;
    return position;
  }

  AXPositionInstance AsTreePosition() const {
    if (IsNullPosition() || IsTreePosition())
      return Clone();

    AXPositionInstance copy = Clone();
    DCHECK_GE(copy->text_offset_, 0);
    // Note that by design, `AXPosition::IsLeaf()` excludes the text found in
    // ignored subtrees from the accessibility tree's text representation. (See
    // `AXNode::IsEmptyLeaf()`.)
    if (copy->IsLeaf()) {
      // Even though leaf positions are generally not anchored to a node with a
      // lot of descendants, still, there is the possibility that the leaf node
      // is a text field with a large amount of text. We avoid computing
      // `MaxTextOffset()` unless it is really necessary.
      if (copy->text_offset_ == 0) {
        copy->child_index_ = BEFORE_TEXT;
      } else {
        const int max_text_offset = copy->MaxTextOffset();
        copy->child_index_ = copy->text_offset_ != max_text_offset
                                 ? BEFORE_TEXT
                                 : AnchorChildCount();
      }

      copy->kind_ = AXPositionKind::TREE_POSITION;
      DCHECK(copy->IsValid());
      return copy;
    }

    // We stop at the first child that we can reach with the current text
    // offset. We do not attempt to validate `MaxTextOffset()` in case it
    // doesn't match the total length of all our children. This may happen if,
    // for example, there is a bug in the internal accessibility tree we get
    // from the renderer. In contrast, the current offset could not be greater
    // than the length of all our children because the position would have been
    // invalid.
    //
    // Note that even though ignored children should not contribute any text
    // content or hypertext to the tree's text representation, we have to
    // include them because they might contain unignored descendants. We only
    // exclude them if they are both ignored and contain no text content or
    // hypertext. The latter is to avoid, as much as we can, the possibility
    // that an unignored position will turn into an ignored one after calling
    // this method.

    int child_index = 0;
    for (int current_offset = 0; child_index < copy->AnchorChildCount();
         ++child_index) {
      AXPositionInstance child = copy->CreateChildPositionAt(child_index);
      DCHECK(!child->IsNullPosition());

      // If the text offset falls on the boundary between two adjacent children,
      // we look at the affinity to decide whether to place the tree position on
      // the first child vs. the second child. Upstream affinity would always
      // choose the first child, whilst downstream affinity the second. This
      // also has implications when converting the resulting tree position back
      // to a text position. In that case, maintaining an upstream affinity
      // would place the text position at the end of the first child, whilst
      // maintaining a downstream affinity will place the text position at the
      // beginning of the second child. This is vital for text positions on soft
      // line breaks, as well as text positions before and after character, to
      // work properly.
      //
      // Note that in this context "adjacent children" excludes ignored
      // children. Note also that children with no text content or no hypertext
      // are not skipped, otherwise the following situation will produce an
      // erroneous tree position:
      // ++kTextField contenteditable=true "" (empty)
      // ++++kStaticText "\n" ignored
      // ++++++kInlineTextBox "\n" ignored
      // ++++kStaticText "" (empty)
      // ++++++kInlineTextOffset "" (empty)
      // TextPosition anchor=kTextField text_offset=0 affinity=downstream
      // AsTreePosition should produce:
      // TreePosition anchor=kTextField child_index=1, and not child_index=0 or
      // child_index=2
      //
      // See also `CreateLeafTextPositionBeforeCharacter` and
      // `CreateLeafTextPositionAfterCharacter`.

      const int child_length = child->MaxTextOffsetInParent();
      const bool contributes_no_text_in_parent = !child_length;
      const bool is_anchor_unignored = !child->GetAnchor()->IsIgnored();
      if (copy->text_offset_ >= current_offset &&
          (copy->text_offset_ < (current_offset + child_length) ||
           ((copy->affinity_ == ax::mojom::TextAffinity::kUpstream ||
             (contributes_no_text_in_parent && is_anchor_unignored)) &&
            copy->text_offset_ == (current_offset + child_length)))) {
        break;
      }

      current_offset += child_length;
    }

    copy->child_index_ = child_index;
    copy->kind_ = AXPositionKind::TREE_POSITION;
    return copy;
  }

  // This is an optimization over "AsLeafTextPosition", in cases when computing
  // the corresponding text offset on the leaf node is not needed. If this
  // method is called on a text position, it will conservatively fall back to
  // the non-optimized "AsLeafTextPosition", if the current text offset is
  // greater than 0, or the affinity is upstream, since converting to a tree
  // position at any point before reaching the leaf node could potentially lose
  // information.
  AXPositionInstance AsLeafTreePosition() const {
    if (IsNullPosition() || IsLeaf())
      return AsTreePosition();

    // If our text offset is greater than 0, or if our affinity is set to
    // upstream, we need to ensure that text offset and affinity will be taken
    // into consideration during our descend to the leaves. Switching to a tree
    // position early in this case will potentially lose information, so we
    // descend using a text position instead.
    //
    // We purposely don't check whether this position is a text position, to
    // allow for the possibility that this position has recently been converted
    // from a text to a tree position and text offset or affinity information
    // has been left intact.
    if (text_offset_ > 0 || affinity_ == ax::mojom::TextAffinity::kUpstream)
      return AsLeafTextPosition()->AsTreePosition();

    AXPositionInstance tree_position = AsTreePosition();
    do {
      if (tree_position->AtEndOfAnchor()) {
        tree_position =
            tree_position
                ->CreateChildPositionAt(tree_position->child_index_ - 1)
                ->CreatePositionAtEndOfAnchor();
      } else {
        tree_position =
            tree_position->CreateChildPositionAt(tree_position->child_index_);
      }
      DCHECK(!tree_position->IsNullPosition());
    } while (!tree_position->IsLeaf());

    DCHECK(tree_position->IsLeafTreePosition());
    return tree_position;
  }

  AXPositionInstance AsTextPosition() const {
    if (IsNullPosition() || IsTextPosition())
      return Clone();

    AXPositionInstance copy = Clone();
    // Check if it is a "before text" position.
    if (copy->child_index_ == BEFORE_TEXT) {
      DCHECK(copy->IsLeaf())
          << "Before text positions can only appear on leaf nodes.";
      // If the current text offset is valid, we don't touch it to potentially
      // allow converting from a text position to a tree position and back
      // without losing information.
      //
      // We test for INVALID_OFFSET and greater than 0 first, due to the
      // possible performance cost of calling `MaxTextOffset()`. Also, if the
      // text offset is already 0, we don't need to touch it, and if it is less
      // than `MaxTextOffset()` we don't modify it as explained above.
      DCHECK_GE(copy->text_offset_, INVALID_OFFSET)
          << "Unrecognized text offset.";
      if (copy->text_offset_ == INVALID_OFFSET ||
          (copy->text_offset_ > 0 &&
           copy->text_offset_ >= copy->MaxTextOffset())) {
        copy->text_offset_ = 0;
      }

      copy->kind_ = AXPositionKind::TEXT_POSITION;
      return copy;
    }

    // Leaf nodes might have descendants that should be hidden for text
    // navigation purposes, thus we can't rely solely on `AnchorChildCount()`.
    // Any child index that is not `BEFORE_TEXT` should be treated as indicating
    // an "after text" position. (See `IsInEmptyObject()` for more information.)
    // ++kButton "<embedded_object_character>" (empty)
    // ++++kGenericContainer ignored (Might sometimes be added by Blink.)
    if (copy->IsLeaf() || copy->child_index_ == copy->AnchorChildCount()) {
      copy->text_offset_ = copy->MaxTextOffset();
      copy->kind_ = AXPositionKind::TEXT_POSITION;
      return copy;
    }

    DCHECK_GE(copy->child_index_, 0);
    DCHECK_LT(copy->child_index_, copy->AnchorChildCount());
    int new_offset = 0;
    for (int i = 0; i <= child_index_; ++i) {
      AXPositionInstance child = copy->CreateChildPositionAt(i);
      DCHECK(!child->IsNullPosition());
      // If the current text offset is valid, we don't touch it to
      // potentially allow converting from a text position to a tree
      // position and back without losing information. Otherwise, if the
      // text_offset is invalid, equals to 0 or is smaller than
      // |new_offset|, we reset it to the beginning of the current child.
      if (i == child_index_ && copy->text_offset_ <= new_offset) {
        copy->text_offset_ = new_offset;
        break;
      }

      int child_length = child->MaxTextOffsetInParent();
      // Same comment as above: we don't touch the text offset if it's
      // already valid.
      if (i == child_index_ &&
          (copy->text_offset_ > (new_offset + child_length) ||
           // When the text offset is equal to the text's length but this is
           // not an "after text" position.
           (!copy->AtEndOfAnchor() &&
            copy->text_offset_ == (new_offset + child_length)))) {
        copy->text_offset_ = new_offset;
        break;
      }

      new_offset += child_length;
    }

      // Affinity should always be left as downstream. The only case when the
      // resulting text position is at the end of the line is when we get an
      // "after text" leaf position, but even in this case downstream is
      // appropriate because there is no ambiguity whether the position is at
      // the end of the current line vs. the start of the next line. It would
      // always be the former.
      copy->kind_ = AXPositionKind::TEXT_POSITION;
      return copy;
  }

  AXPositionInstance AsLeafTextPosition() const {
    if (IsNullPosition() || IsLeaf())
      return AsTextPosition();

    AXPositionInstance text_position = Clone();
    if (IsTreePosition()) {
      DCHECK_NE(child_index(), BEFORE_TEXT)
          << "Before text positions should only be present on leaf anchor "
             "nodes.";
      DCHECK_GT(AnchorChildCount(), 0)
          << "Non-leaf positions should be anchored to nodes that have "
             "children.";

      // We can't go directly to a text position if we are initially dealing
      // with a tree position, because empty child objects contribute no text to
      // the tree's text representation and thus the existing child index
      // information would be lost.
      //
      // ++kRootWebArea
      // ++++kGenericContainer (empty object)
      // ++++kGenericContainer (empty object)
      // TreePosition anchor=kRootWebArea child_index=1 would turn into a text
      // position on the same anchor but with a text offset of 0 if we call
      // `AsTextPosition()` immediately before first anchoring ourselves to the
      // selected child node.
      if (child_index() > 0 && child_index() == AnchorChildCount()) {
        text_position = CreateChildPositionAt(child_index() - 1)
                            ->CreatePositionAtEndOfAnchor();
      } else {
        text_position = CreateChildPositionAt(child_index());
      }
    }
    text_position = text_position->AsTextPosition();
    DCHECK(!text_position->IsNullPosition());

    int offset_in_parent = text_position->text_offset_;
    // Determine the anchor and text offset of the leaf equivalent position by
    // counting characters that are previous in tree order than
    // `offset_in_parent`.
    while (!text_position->IsLeaf()) {
      AXPositionInstance child = text_position->CreateChildPositionAt(0);
      DCHECK(!child->IsNullPosition());

      // Note that even though ignored children should not contribute any text
      // content or hypertext to the tree's text representation, we have to
      // include them because they might contain unignored descendants. We only
      // exclude them if they are both ignored and contain no text content or
      // hypertext. The latter is to avoid, as much as we can, the possibility
      // that an unignored position will turn into an ignored one after calling
      // this method.
      for (int i = 1;
           i < text_position->AnchorChildCount() && offset_in_parent >= 0;
           ++i) {
        const int child_length_in_parent = child->MaxTextOffsetInParent();
        const bool contributes_no_text_in_parent =
            (child_length_in_parent == 0);
        const bool is_anchor_unignored = !child->GetAnchor()->IsIgnored();
        if (offset_in_parent == 0 && contributes_no_text_in_parent &&
            is_anchor_unignored) {
          // If the text offset corresponds to multiple child positions because
          // some of the children have no text content or hypertext, the above
          // condition ensures that the first child will be chosen; unless it is
          // ignored as explained before.
          break;
        }

        if (offset_in_parent < child_length_in_parent)
          break;

        if (affinity_ == ax::mojom::TextAffinity::kUpstream &&
            offset_in_parent == child_length_in_parent) {
          // Maintain upstream affinity so that we'll be able to choose the
          // correct leaf anchor if the text offset is right on the boundary
          // between two leaves.
          child->affinity_ = ax::mojom::TextAffinity::kUpstream;
          break;
        }

        child = text_position->CreateChildPositionAt(i);
        offset_in_parent -= child_length_in_parent;
      }

      // The text offset provided by our parent position might need to be
      // adjusted, if this is an "after text" position and our anchor node is an
      // embedded object (as determined by `IsEmbeddedObjectInParent()`).
      // ++kRootWebArea "<embedded_object>"
      // ++++kParagraph "Hello"
      // TextPosition anchor=kRootWebArea text_offset=1
      // should be translated into the following text position
      // TextPosition anchor=kParagraph text_offset=5 annotated_text=Hello<>
      // and not into the following one
      // TextPosition anchor=kParagraph text_offset=1 annotated_text=<H>ello
      if (child->IsEmbeddedObjectInParent() &&
          offset_in_parent == child->MaxTextOffsetInParent()) {
        offset_in_parent -= child->MaxTextOffsetInParent();
        offset_in_parent += child->MaxTextOffset();
      }

      text_position = std::move(child);
    }

    DCHECK(text_position->IsLeafTextPosition());
    text_position->text_offset_ = offset_in_parent;
    // A leaf Text position is always downstream since there is no ambiguity as
    // to whether it refers to the end of the current or the start of the next
    // line.
    text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
    return text_position;
  }

  // Converts to a text position that is suitable for passing into the renderer
  // as a selection endpoint. In other words, converts to a position that is
  // suitable for setting as a DOM selection range endpoint.
  //
  // When blink is asked to set selection, it expects a text position to be
  // anchored to the text node (otherwise a generic tree position is assumed
  // and the offset is interpreted as a child index).
  AXPositionInstance AsDomSelectionPosition() const {
    if (IsNullPosition() || GetAnchor()->data().IsAtomicTextField())
      return Clone();

    AXPositionInstance text_position = AsLeafTextPosition();
    if (text_position->GetAnchor() && text_position->GetAnchor()->GetRole() ==
                                          ax::mojom::Role::kInlineTextBox) {
      return text_position->CreateParentPosition();
    }
    return text_position;
  }

  // We deploy three strategies in order to find the best match for an ignored
  // position in the accessibility tree:
  //
  // 1. In the case of a text position, we move up the parent positions until we
  // find the next unignored equivalent parent position. We don't do this for
  // tree positions because, unlike text positions which maintain the
  // corresponding text offset in the text content of the parent node, tree
  // positions would lose some information every time a parent position is
  // computed. In other words, the parent position of a tree position is, in
  // most cases, non-equivalent to the child position.
  // 2. If no equivalent and unignored parent position can be computed, we try
  // computing the leaf equivalent position. If this is unignored, we return it.
  // This can happen both for tree and text positions, provided that the leaf
  // node and its text content is visible to platform APIs, i.e. it's unignored.
  // 3. As a last resort, we move either to the next or previous unignored
  // position in the accessibility tree, based on the "adjustment_behavior".
  AXPositionInstance AsUnignoredPosition(
      AXPositionAdjustmentBehavior adjustment_behavior) const {
    if (IsNullPosition() || !IsIgnored())
      return Clone();

    AXPositionInstance leaf_tree_position = AsLeafTreePosition();

    // If this is a text position, first try moving up to a parent equivalent
    // position and check if the resulting position is still ignored. This
    // won't result in the loss of any information. We can't do that in the
    // case of tree positions, because we would be better off to move to the
    // next or previous position within the same anchor, as this would lose
    // less information than moving to a parent equivalent position.
    //
    // Text positions are considered ignored if either the current anchor is
    // ignored, or if the equivalent leaf tree position is ignored.
    // If this position is a leaf text position, or the equivalent leaf tree
    // position is ignored, then it's not possible to create an ancestor text
    // position that is unignored.
    if (IsTextPosition() && !IsLeafTextPosition() &&
        !leaf_tree_position->IsIgnored()) {
      AXPositionInstance unignored_position = CreateParentPosition();
      while (!unignored_position->IsNullPosition()) {
        // Since the equivalent leaf tree position is unignored, search for the
        // first unignored ancestor anchor and return that text position.
        if (!unignored_position->GetAnchor()->IsIgnored()) {
          DCHECK(!unignored_position->IsIgnored());
          return unignored_position;
        }
        unignored_position = unignored_position->CreateParentPosition();
      }
    }

    // There is a possibility that the position became unignored by moving to a
    // leaf equivalent position. Otherwise, we have no choice but to move to the
    // next or previous position and lose some information in the process.
    while (leaf_tree_position->IsIgnored()) {
      switch (adjustment_behavior) {
        case AXPositionAdjustmentBehavior::kMoveForward:
          leaf_tree_position = leaf_tree_position->CreateNextLeafTreePosition();
          break;
        case AXPositionAdjustmentBehavior::kMoveBackward:
          leaf_tree_position =
              leaf_tree_position->CreatePreviousLeafTreePosition();
          // in case the unignored leaf node contains some text, ensure that the
          // resulting position is an "after text" position, as such a position
          // would be the closest to the ignored one, given the fact that we are
          // moving backwards through the tree.
          leaf_tree_position =
              leaf_tree_position->CreatePositionAtEndOfAnchor();
          break;
      }
    }

    if (IsTextPosition())
      return leaf_tree_position->AsTextPosition();
    return leaf_tree_position;
  }

  // This method is similar to `AsUnignoredPosition`, but it will never cross
  // an anchor boundary. This means that if the position is at the start or end
  // of the anchor, it will return a position at the start or end of the anchor,
  // respectively. This is useful when we want to ensure that the resulting
  // position is still within the same anchor. If no unignored position can be
  // found, it will return a null position.
  AXPositionInstance TryAsUnignoredPositionPreservingAnchor(
      AXPositionAdjustmentBehavior behavior) const {
    if (IsNullPosition()) {
      return Clone();
    }

    AXPositionInstance new_position = AsUnignoredPosition(behavior);
    if (GetAnchor() == new_position->GetAnchor()) {
      return new_position;
    }

    // As a last resort, AsUnignoredPosition() may return a position that is
    // anchored at a different node. In such case, we need to create a new
    // position that is anchored at the same node as this position. To do this,
    // Try Calling AsUnignoredPosition in the other direction, starting from
    // one of the ends, as there may not have been any unignored positions in
    // the original direction.
    switch (behavior) {
      case AXPositionAdjustmentBehavior::kMoveBackward:
        new_position = CreatePositionAtStartOfAnchor()->AsUnignoredPosition(
            AXPositionAdjustmentBehavior::kMoveForward);
        break;
      case AXPositionAdjustmentBehavior::kMoveForward:
        new_position = CreatePositionAtEndOfAnchor()->AsUnignoredPosition(
            AXPositionAdjustmentBehavior::kMoveBackward);
        break;
    }
    if (GetAnchor() != new_position->GetAnchor()) {
      // Check to see if the new position can be expressed in terms of the
      // current anchor.
      new_position = new_position->CreateAncestorPosition(
          GetAnchor(), behavior == AXPositionAdjustmentBehavior::kMoveForward
                           ? ax::mojom::MoveDirection::kForward
                           : ax::mojom::MoveDirection::kBackward);
    }
    // It could be that there are no unignored positions that can be rooted
    // at the current anchor. In such case, we return a null position.
    if (new_position->IsIgnored()) {
      return CreateNullPosition();
    }

    // Retain original position type.
    if (IsTextPosition()) {
      new_position = new_position->AsTextPosition();
    } else {
      new_position = new_position->AsTreePosition();
    }
    return new_position;
  }

  // Searches backward and forward from this position until it finds the given
  // text boundary, and creates an AXRange that spans from the former to the
  // latter. The resulting AXRange is always a forward range: its anchor always
  // comes before its focus in document order. The resulting AXRange is bounded
  // by the anchor of this position and the requested boundary type, i.e. the
  // AXMovementOptions is set to `AXBoundaryBehavior::kStopAtAnchorBoundary` and
  // `AXBoundaryDetection::kCheckInitialPosition`. The
  // exception is `ax::mojom::TextBoundary::kWebPage`, where this behavior won't
  // make sense. This behavior is based on current platform needs and might be
  // relaxed if necessary in the future.
  //
  // Observe that `expand_behavior` has an effect only when this position is
  // between text units, e.g. between words, lines, paragraphs, etc. Also,
  // please note that `expand_behavior` should have no effect for
  // `ax::mojom::TextBoundary::kObject` and `ax::mojom::TextBoundary::kWebPage`
  // because the range should be the same regardless if we first move left or
  // right.
  AXRangeType ExpandToEnclosingTextBoundary(
      ax::mojom::TextBoundary boundary,
      AXRangeExpandBehavior expand_behavior) const {
    AXMovementOptions left_options{AXBoundaryBehavior::kStopAtAnchorBoundary,
                                   AXBoundaryDetection::kCheckInitialPosition};
    AXMovementOptions right_options{
        AXBoundaryBehavior::kStopAtAnchorBoundary,
        AXBoundaryDetection::kDontCheckInitialPosition};
    if (boundary == ax::mojom::TextBoundary::kWebPage) {
      left_options =
          right_options = {AXBoundaryBehavior::kCrossBoundary,
                           AXBoundaryDetection::kDontCheckInitialPosition};
    }

    switch (expand_behavior) {
      case AXRangeExpandBehavior::kLeftFirst: {
        AXPositionInstance left_position = CreatePositionAtTextBoundary(
            boundary, ax::mojom::MoveDirection::kBackward, left_options);
        AXPositionInstance right_position =
            left_position->CreatePositionAtTextBoundary(
                boundary, ax::mojom::MoveDirection::kForward, right_options);
        return AXRangeType(std::move(left_position), std::move(right_position));
      }
      case AXRangeExpandBehavior::kRightFirst: {
        AXPositionInstance right_position = CreatePositionAtTextBoundary(
            boundary, ax::mojom::MoveDirection::kForward, left_options);
        AXPositionInstance left_position =
            right_position->CreatePositionAtTextBoundary(
                boundary, ax::mojom::MoveDirection::kBackward, right_options);
        return AXRangeType(std::move(left_position), std::move(right_position));
      }
    }
  }

  // Starting from this position, moves in the given direction until it finds
  // the given text boundary, and creates a new position at that location.
  //
  // When a boundary has the "StartOrEnd" suffix, it means that this method will
  // find the start boundary when moving in the backward direction, and the end
  // boundary when moving in the forward direction.
  AXPositionInstance CreatePositionAtTextBoundary(
      ax::mojom::TextBoundary boundary,
      ax::mojom::MoveDirection direction,
      AXMovementOptions options) const {
    AXPositionInstance resulting_position = CreateNullPosition();
    switch (boundary) {
      case ax::mojom::TextBoundary::kNone:
        NOTREACHED();

      case ax::mojom::TextBoundary::kCharacter:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousCharacterPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextCharacterPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kFormatEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousFormatEndPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextFormatEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kFormatStart:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousFormatStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextFormatStartPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kFormatStartOrEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousFormatStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextFormatEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kLineEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousLineEndPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextLineEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kLineStart:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousLineStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextLineStartPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kLineStartOrEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousLineStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextLineEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kObject:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePositionAtStartOfAnchor();
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreatePositionAtEndOfAnchor();
            break;
        }
        break;

      case ax::mojom::TextBoundary::kPageEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousPageEndPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextPageEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kPageStart:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousPageStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextPageStartPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kPageStartOrEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousPageStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextPageEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kParagraphEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousParagraphEndPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextParagraphEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kParagraphStart:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousParagraphStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextParagraphStartPosition(options);
            break;
        }
        break;

      // For UI Automation, empty lines after a paragraph should be merged into
      // the preceding paragraph.
      //
      // See
      // https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-uiautomationtextunits#paragraph
      case ax::mojom::TextBoundary::kParagraphStartSkippingEmptyParagraphs:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position =
                CreatePreviousParagraphStartPositionSkippingEmptyParagraphs(
                    options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position =
                CreateNextParagraphStartPositionSkippingEmptyParagraphs(
                    options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kParagraphStartOrEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousParagraphStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextParagraphEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kSentenceEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousSentenceEndPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextSentenceEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kSentenceStart:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousSentenceStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextSentenceStartPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kSentenceStartOrEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousSentenceStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextSentenceEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kWebPage:
        DCHECK_EQ(options.boundary_behavior, AXBoundaryBehavior::kCrossBoundary)
            << "We can't reach the start of the whole contents if we are "
               "disallowed from crossing boundaries.";
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePositionAtStartOfContent();
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreatePositionAtEndOfContent();
            break;
        }
        break;

      case ax::mojom::TextBoundary::kWordEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousWordEndPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextWordEndPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kWordStart:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousWordStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextWordStartPosition(options);
            break;
        }
        break;

      case ax::mojom::TextBoundary::kWordStartOrEnd:
        switch (direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            resulting_position = CreatePreviousWordStartPosition(options);
            break;
          case ax::mojom::MoveDirection::kForward:
            resulting_position = CreateNextWordEndPosition(options);
            break;
        }
        break;
    }

    return resulting_position;
  }

  AXPositionInstance CreatePositionAtStartOfAnchor() const {
    const AXNode* anchor = GetAnchor();
    if (!anchor)
      return CreateNullPosition();

    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        return CreateNullPosition();
      case AXPositionKind::TREE_POSITION:
        return CreateTreePositionAtStartOfAnchor(*anchor);
      case AXPositionKind::TEXT_POSITION:
        return CreateTextPosition(*anchor, 0 /* text_offset */,
                                  ax::mojom::TextAffinity::kDownstream);
    }
  }

  AXPositionInstance CreatePositionAtEndOfAnchor() const {
    const AXNode* anchor = GetAnchor();
    if (!anchor)
      return CreateNullPosition();

    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        return CreateNullPosition();
      case AXPositionKind::TREE_POSITION:
        return CreateTreePositionAtEndOfAnchor(*anchor);
      case AXPositionKind::TEXT_POSITION:
        return CreateTextPosition(*anchor, MaxTextOffset(),
                                  ax::mojom::TextAffinity::kDownstream);
    }
  }

  // Creates a position at the start of this position's accessibility tree, e.g.
  // at the start of the current iframe, PDF plugin, Views tree, dialog, etc. We
  // don't distinguish between out-of-process and in-process iframes, treating
  // them both as tree boundaries.
  //
  // For a similar method that does not stop at iframe boundaries, see
  // `CreatePositionAtStartOfContent()`.
  AXPositionInstance CreatePositionAtStartOfAXTree() const {
    AXPositionInstance root_position =
        AsTreePosition()
            ->CreateAXTreeRootAncestorPosition(
                ax::mojom::MoveDirection::kBackward)
            ->CreatePositionAtStartOfAnchor();
    if (IsTextPosition())
      root_position = root_position->AsTextPosition();
    DCHECK_EQ(root_position->tree_id_, tree_id_)
        << "`CreatePositionAtStartOfAXTree` should not cross any tree "
           "boundaries, neither return the null position.";
    return root_position;
  }

  // Creates a position at the end of this position's accessibility tree, e.g.
  // at the end of the current iframe, PDF plugin, Views tree, dialog, etc. We
  // don't distinguish between out-of-process and in-process iframes, treating
  // them both as tree boundaries.
  //
  // For a similar method that does not stop at iframe boundaries, see
  // `CreatePositionAtEndOfContent()`.
  AXPositionInstance CreatePositionAtEndOfAXTree() const {
    AXPositionInstance root_position =
        AsTreePosition()->CreateAXTreeRootAncestorPosition(
            ax::mojom::MoveDirection::kBackward);
    AXPositionInstance last_position =
        root_position->CreatePositionAtEndOfAnchor()->AsLeafTreePosition();
    if (IsTextPosition())
      last_position = last_position->AsTextPosition();
    return last_position;
  }

  // Creates a position at the start of all content, e.g. at the start of the
  // whole webpage, PDF plugin, Views tree, dialog (native, ARIA or HTML),
  // window, or the whole desktop.
  //
  // Note that this method will break out of an out-of-process iframe and return
  // a position at the start of the top-level document, but it will not break
  // into the Views tree if present. For a similar method that stops at all
  // iframe boundaries, see `CreatePositionAtStartOfAXTree()`.
  AXPositionInstance CreatePositionAtStartOfContent() const {
    AXPositionInstance root_position =
        AsTreePosition()
            ->CreateRootAncestorPosition(ax::mojom::MoveDirection::kBackward)
            ->CreatePositionAtStartOfAnchor();
    if (IsTextPosition())
      root_position = root_position->AsTextPosition();
    return root_position;
  }

  // Creates a position at the end of all content, e.g. at the end of the whole
  // webpage, PDF plugin, Views tree, dialog (native, ARIA or HTML), window, or
  // the whole desktop.
  //
  // Note that this method will break out of an out-of-process iframe and return
  // a position at the end of the top-level document, but it will not break into
  // the Views tree if present. For a similar method that stops at all iframe
  // boundaries, see `CreatePositionAtEndOfAXTree()`.
  AXPositionInstance CreatePositionAtEndOfContent() const {
    AXPositionInstance root_position =
        AsTreePosition()->CreateRootAncestorPosition(
            ax::mojom::MoveDirection::kBackward);
    AXPositionInstance last_position =
        root_position->CreatePositionAtEndOfAnchor()->AsLeafTreePosition();
    if (IsTextPosition())
      last_position = last_position->AsTextPosition();
    return last_position;
  }

  AXPositionInstance CreateChildPositionAt(int child_index) const {
    if (IsNullPosition() || IsLeaf())
      return CreateNullPosition();

    if (child_index < 0 || child_index >= AnchorChildCount())
      return CreateNullPosition();

    const AXNode* child_anchor =
        GetAnchor()->GetChildAtIndexCrossingTreeBoundary(child_index);
    if (!child_anchor)
      return CreateNullPosition();

    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        NOTREACHED();
      case AXPositionKind::TREE_POSITION:
        return CreateTreePositionAtStartOfAnchor(*child_anchor);
      case AXPositionKind::TEXT_POSITION:
        return CreateTextPosition(*child_anchor, 0 /* text_offset */,
                                  ax::mojom::TextAffinity::kDownstream);
    }

    return CreateNullPosition();
  }

  // Creates a parent equivalent position.
  //
  // Note that "move_direction" is only taken into consideration when all of
  // these three conditions apply: This is a text position, we are in the
  // process of searching for a text boundary, and this is a platform where
  // child nodes are represented by "object replacement characters". On such
  // platforms, the `IsEmbeddedObjectInParent` method returns true. We need to
  // decide whether to create a parent equivalent position that is before or
  // after the child node, since moving to a parent position would always cause
  // us to lose some information. We can't simply re-use the text offset of the
  // child position because by definition the parent node doesn't include all
  // the text of the child node, but only a single "object replacement
  // character".
  //
  // staticText name='Line one' IA2-hypertext='<embedded_object>'
  // ++inlineTextBox name='Line one'
  //
  // If we are given a text position pointing to somewhere inside the
  // inlineTextBox, and we move to the parent equivalent position, we need to
  // decide whether the parent position would be set to point to before the
  // object replacement character or after it. Both are valid, depending on the
  // direction on motion, e.g. if we are trying to find the start of the line
  // vs. the end of the line.
  AXPositionInstance CreateParentPosition(
      ax::mojom::MoveDirection move_direction =
          ax::mojom::MoveDirection::kForward) const {
    if (IsNullPosition())
      return CreateNullPosition();

    const AXNode* parent_anchor = GetAnchor()->GetParentCrossingTreeBoundary();
    if (!parent_anchor)
      return CreateNullPosition();

    const AXTree* tree = parent_anchor->tree();
    DCHECK(tree);

    switch (kind_) {
      case AXPositionKind::NULL_POSITION:
        NOTREACHED();

      case AXPositionKind::TREE_POSITION: {
        if (IsLeafNodeForTreePosition(*parent_anchor)) {
          if (AtEndOfAnchor() ||
              move_direction == ax::mojom::MoveDirection::kForward) {
            // If this position is an "after children" or an "after text"
            // position inside of a leaf, or we are seeking a parent position
            // for a forward movement operation with a parent leaf anchor,
            // return a position at the end of the parent anchor.
            return CreateTreePositionAtEndOfAnchor(*parent_anchor);
          }
          // If we are seeking a parent position for a backward movement
          // operation, return a position at the start of the parent anchor.
          return CreateTreePositionAtStartOfAnchor(*parent_anchor);
        }

        // If this position is an "after children" or an "after text" position,
        // return either an "after children" position on the parent anchor, or a
        // position anchored at the next child, depending on whether this is the
        // last child in its parent anchor.
        int child_index = AnchorIndexInParent();
        if (AtEndOfAnchor())
          return CreateTreePosition(*parent_anchor, (child_index + 1));

        switch (move_direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            // "move_direction" is only important when this position is an
            // "embedded object in parent", i.e., when this position's anchor is
            // represented by an "object replacement character" in the text of
            // its parent anchor. In this case we need to keep the child index
            // to be right before the "object replacement character". If this is
            // not an "embedded object in parent", then we simply need to use
            // the "AnchorIndexInParent" for the child index. However, since
            // "AnchorIndexInParent" always returns a child index that is before
            // any "object replacement character" in our parent, we use that for
            // both situations.
            return CreateTreePosition(*parent_anchor, child_index);
          case ax::mojom::MoveDirection::kForward:
            // "move_direction" is only important when this position is an
            // "embedded object in parent", i.e., when this position's anchor is
            // represented by an "object replacement character" in the text of
            // its parent anchor. In this case we need to move the child index
            // to be after the "object replacement character" when this position
            // is not at the start of its anchor. If this is not an "embedded
            // object in parent", then we simply need to use the
            // "AnchorIndexInParent" for the child index.
            if (!AtStartOfAnchor() && IsEmbeddedObjectInParent())
              ++child_index;
            return CreateTreePosition(*parent_anchor, child_index);
        }
      }

      case AXPositionKind::TEXT_POSITION: {
        // On some platforms, such as Android, Mac and Chrome OS, the text
        // content of a node is made up by concatenating the text of child
        // nodes. On other platforms, such as Windows IAccessible2 and Linux
        // ATK, child nodes are represented by a single "object replacement
        // character".
        //
        // If our parent's text content is a concatenation of all its children's
        // text, we need to maintain the affinity and compute the corresponding
        // text offset. Otherwise, we have no choice but to return a position
        // that is either before or after this child, losing some information in
        // the process. Regardless to whether our parent contains all our text,
        // we always recompute the affinity when the position is after the
        // child.
        //
        // Recomputing the affinity in the latter situation is important because
        // even though a text position might unambiguously be at the end of a
        // line, its parent position might be the same as the parent position of
        // a position that represents the start of the next line. For example:
        //
        // staticText name='Line oneLine two'
        // ++inlineTextBox name='Line one'
        // ++inlineTextBox name='Line two'
        //
        // If the original position is at the end of the inline text box for
        // "Line one", then the resulting parent equivalent position would be
        // the same as the one that would have been computed if the original
        // position were at the start of the inline text box for "Line two".

        const int max_text_offset = MaxTextOffset();

        // TODO(crbug.com/40885940): temporary disabled until ax position
        // autocorrection issue is fixed.
        // DCHECK_LE(text_offset_, max_text_offset);

        const int max_text_offset_in_parent =
            IsEmbeddedObjectInParent()
                ? AXNode::kEmbeddedObjectCharacterLengthUTF16
                : max_text_offset;
        int parent_offset = AnchorTextOffsetInParent();
        ax::mojom::TextAffinity parent_affinity = affinity_;

        // "max_text_offset > 0" is required to filter out anchor nodes that are
        // either ignored or empty, i.e. those that contribute no text content
        // or hypertext to their parent's text representation. (See example in
        // the "else" block.)
        if (max_text_offset > 0 &&
            max_text_offset == max_text_offset_in_parent) {
          // Our parent contains all our text. No information would be lost when
          // moving to a parent equivalent position. It turns out, that even in
          // the unusual case where there is a single character in our anchor's
          // text content but our anchor is represented in our parent by an
          // "embedded object replacement character" and not by our text
          // content, the outcome is still correct.
          parent_offset += text_offset_;
        } else {
          // Our parent represents our anchor node using an "object replacement"
          // character in its text representation. Or, our anchor is a text node
          // that is ignored or empty, and so contributes no text in its
          // parent's text representation. For example:
          // ++kTextField "Before after."
          // ++++kStaticText "Before "
          // ++++kStaticText "Ignored text" ignored
          // ++++kStaticText "after."
          // TextPosition anchor=kStaticText (ignored) text_offset=2
          // annotated_text="Ig<n>ored text"

          if (text_offset_ > 0 && text_offset_ < max_text_offset) {
            // If this is a "before text" or an "after text" position, i.e. if
            // "text_offset_" == 0 or "max_text_offset", then the child position
            // is clearly before or clearly after any "object replacement
            // character". No information would be lost when moving to a parent
            // equivalent position, including affinity which can easily be
            // computed. Otherwise, we should decide whether to set the parent
            // position to be before or after the child, based on the direction
            // of motion, and also reset the affinity.
            switch (move_direction) {
              case ax::mojom::MoveDirection::kNone:
                NOTREACHED();
              case ax::mojom::MoveDirection::kBackward:
                // Keep the offset to be right before the embedded object
                // character.
                break;
              case ax::mojom::MoveDirection::kForward:
                // Set the offset to be after the embedded object character.
                parent_offset += max_text_offset_in_parent;
                break;
            }
          } else if (text_offset_ == max_text_offset) {
            // Clearly, this is an "after text" position. The text offset should
            // be after the "object replacement character". No information would
            // be lost when moving to a parent equivalent position, including
            // affinity which can easily be computed.
            parent_offset += max_text_offset_in_parent;
          }

          // The original affinity doesn't apply any more. In most cases, it
          // should be downstream, unless there is an ambiguity as to whether
          // the parent position is between the end of one line and the start of
          // the next. We perform this check below.
          parent_affinity = ax::mojom::TextAffinity::kDownstream;
        }

        // There are two cases for which we need to set an upstream affinity on
        // the parent position:
        //
        // Case 1:
        // If the current position is pointing at the end of its anchor, we need
        // to check if the parent position has introduced ambiguity as to
        // whether it refers to the end of a line or the start of the next.
        // Ambiguity is only present when the parent position points to a text
        // offset that is neither at the start nor at the end of its anchor. We
        // check for ambiguity by creating the parent position and testing if it
        // is erroneously at the start of the next line. Given that the current
        // position, by the nature of being at the end of its anchor, could only
        // be at end of line, the fact that the parent position is also
        // determined to be at start of line demonstrates the presence of
        // ambiguity which is resolved by setting its affinity to upstream.
        //
        // We could not have checked if the child was at the end of the line,
        // because our "AtEndOfLine" predicate takes into account trailing line
        // breaks, which would create false positives.
        //
        // Case 2:
        // If the current position is followed by a generated newline character,
        // which is a character that is actually not represented in the text
        // content of the nodes but should still act as a stop when navigating
        // to the previous/next character.
        //
        // When this is the case, we almost always want to set an upstream
        // affinity on the `parent_position`. The only exception is when our
        // current position is contained on a descendant of an empty object,
        // because an empty object will hide the textual representation of its
        // descendants, including the generated newline characters, by exposing
        // a only the empty object character.
        //
        // Example:
        // ++1 kLink "<embedded_object>"
        // ++++2 kStaticText "hello" IsLineBreakingObject=true
        // ++++++3 kInlineTextBox "hello"
        // ++++4 kStaticText "world"
        // ++++++5 kInlineTextBox "world"
        //
        // While there should be a generated newline character at the end of a
        // position created on node 2, there won't be one represented to the
        // user because node 1 simply exposes the empty object character and not
        // its children's text.

        AXPositionInstance parent_position =
            CreateTextPosition(*parent_anchor, parent_offset, parent_affinity);
        if ((AtEndOfAnchor() && !parent_position->AtStartOfAnchor() &&
             !parent_position->AtEndOfAnchor() &&
             parent_position->AtStartOfLine()) ||
            (!IsEmbeddedObjectInParent() && IsFollowedByGeneratedNewline())) {
          parent_position->affinity_ = ax::mojom::TextAffinity::kUpstream;
        }
        return parent_position;
      }
    }
  }

  // Creates the next tree position that is anchored at a leaf node of the
  // AXTree.
  AXPositionInstance CreateNextLeafTreePosition() const {
    return CreateNextLeafTreePosition(
        base::BindRepeating(&DefaultAbortMovePredicate));
  }

  // Creates the previous tree position that is anchored at a leaf node of the
  // AXTree.
  AXPositionInstance CreatePreviousLeafTreePosition() const {
    return CreatePreviousLeafTreePosition(
        base::BindRepeating(&DefaultAbortMovePredicate));
  }

  // Creates the next text position that is anchored at a leaf node of the
  // AXTree.
  AXPositionInstance CreateNextLeafTextPosition() const {
    return CreateNextLeafTextPosition(
        base::BindRepeating(&DefaultAbortMovePredicate));
  }

  // Creates the previous text position that is anchored at a leaf node of the
  // AXTree.
  AXPositionInstance CreatePreviousLeafTextPosition() const {
    return CreatePreviousLeafTextPosition(
        base::BindRepeating(&DefaultAbortMovePredicate));
  }

  AXPositionInstance CreateNextPositionAtAnchorWithText() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    do {
      text_position = text_position->CreateNextLeafTextPosition(
          base::BindRepeating(&AbortMoveAtRootBoundary));
    } while (!text_position->IsNullPosition() &&
             (text_position->IsIgnored() || !text_position->MaxTextOffset()));

    return text_position;
  }

  AXPositionInstance CreatePreviousPositionAtAnchorWithText() const {
    AXPositionInstance text_position = AsLeafTextPosition();
    do {
      text_position = text_position->CreatePreviousLeafTextPosition(
          base::BindRepeating(&AbortMoveAtRootBoundary));
    } while (!text_position->IsNullPosition() &&
             (text_position->IsIgnored() || !text_position->MaxTextOffset()));

    return text_position->CreatePositionAtEndOfAnchor();
  }

  // Generated newline characters are not part of any AXNode in the AXTree. They
  // are appended to the accessible textual representation exposed to ATs in
  // AXRange::GetText. They are necessary to expose the implicit newlines
  // created from the layout breaks to screen reader users. For example, the
  // following HTML will create an implicit line break after "hello":
  //
  // <div contenteditable>
  //     <div>hello</div>
  //     <div>world</div>
  // </div>
  //
  // Even though there is not explicit line break in this template, the text
  // returned for this contenteditable is "hello\nworld". In order to allow
  // screen reader users to navigate (either using the caret or the controls
  // built-in the AT), we need to create character stops around these
  // generated characters.
  //
  // We can only create character stops around generated newline characters
  // when empty objects are represented in the accessible text (ie. when the
  // behavior is set to
  // `AXEmbeddedObjectBehavior::kExposeCharacterForHypertext`). Otherwise,
  // there's a risk that `CreateParentPosition` will create a position that
  // doesn't point to the same character. This is because a position located
  // right before a generated newline character will be represented in the
  // parent ancestor with an upstream affinity.
  //
  // Let's consider this AXTree:
  // 1 root
  // ++2 button
  // ++3 checkbox
  // ++4 static text
  // ++++5 inline text box "abc"
  //
  // The text representation for the entire document, including the generated
  // newlines, will be "\n\nabc" if the empty objects do not expose the empty
  // object character. If we were to allow character stops at generated
  // newline characters, it would be possible to create a next and previous
  // position located before/after a generated newline character. However,
  // creating an equivalent position in an ancestor would potentially lead to
  // an incorrect position.
  //
  // Example:
  // leaf_position_1: anchor=2, text_offset=0, affinity=downstream
  // leaf_position_2: anchor=3, text_offset=0, affinity=downstream
  //
  // `leaf_position_1` and `leaf_position_2` should both return true for
  // `AtStartOfParagraph` and `AtEndOfParagraph`. Calling
  // `CreateParentPosition` on each of those will respectively create:
  // parent_position_1: anchor=1, text_offset=0, affinity=upstream
  // parent_position_1: anchor=1, text_offset=0, affinity=upstream
  //
  // ...which are both the same. `CreateParentPosition` is relatively important
  // when it comes to moving the position by character because
  // `CreatePreviousCharacterPosition` uses it in many cases to create the
  // previous position on the same anchor as the original position.
  //
  // This is a quirk of the current implementation which cannot easily be fixed,
  // because when object replacement characters are missing from empty objects
  // (sucha as a checkbox without a label, etc.) any leaf equivalent position
  // from one of the objects' ancestors would skip the empty object and create
  // the child position at the first non-empty object. Consequently,
  // CreateParentPosition cannot easily determine the correct affinity when
  // computing parent equivalent positions from positions on empty objects, i.e.
  // like the example positions given here. Skipping empty objects when creating
  // leaf equivalent positions had to be done, because on platforms where they
  // are not represented by an object replacement character, the AT does not
  // even know they are there.
  bool AllowsCharacterStopsOnGeneratedNewline() const {
    return g_ax_embedded_object_behavior ==
               AXEmbeddedObjectBehavior::kExposeCharacterForHypertext ||
           g_ax_embedded_object_behavior ==
               AXEmbeddedObjectBehavior::kUIAExposeCharacterForTextContent ||
           !IsInUnignoredEmptyObject();
  }

  bool IsFollowedByGeneratedNewline() const {
    // Hard line breaks (such as <br> in HTML) are discounted because generated
    // newlines are only inserted between neighboring block elements (such as
    // <p>Hello</p><p>world</p>). Generated newlines are always a product of
    // layout and have no corresponding AXNode to it. Hard line breaks have
    // a matching AXNode and thus do not require to be treated differently.
    AXPositionInstance leaf_text_position = AsLeafTextPosition();
    if (!leaf_text_position->AllowsCharacterStopsOnGeneratedNewline() ||
        leaf_text_position->affinity_ != ax::mojom::TextAffinity::kDownstream ||
        leaf_text_position->GetAnchor()->IsLineBreak() ||
        !leaf_text_position->AtEndOfParagraph()) {
      return false;
    }

    AXPositionInstance next_position =
        leaf_text_position->CreateNextPositionAtAnchorWithText();
    return next_position->AllowsCharacterStopsOnGeneratedNewline() &&
           !next_position->IsNullPosition() &&
           !next_position->GetAnchor()->IsLineBreak() &&
           next_position->AtStartOfParagraph();
  }

  bool IsPrecededByGeneratedNewline() const {
    // Hard line breaks (such as <br> in HTML) are discounted because generated
    // newlines are only inserted between neighboring block elements (such as
    // <p>Hello</p><p>world</p>). Generated newlines are always a product of
    // layout and have no corresponding AXNode to it. Hard line breaks have
    // a matching AXNode and thus do not require to be treated differently.
    AXPositionInstance leaf_text_position = AsLeafTextPosition();
    if (!leaf_text_position->AllowsCharacterStopsOnGeneratedNewline() ||
        leaf_text_position->GetAnchor()->IsLineBreak() ||
        !leaf_text_position->AtStartOfParagraph()) {
      return false;
    }

    AXPositionInstance previous_position =
        leaf_text_position->CreatePreviousPositionAtAnchorWithText();
    if (previous_position->IsNullPosition()) {
      // When it's null, it's because we've reached the beginning of the tree.
      // We need to make sure we didn't skip any generated newlines that could
      // have been before the start of the page and our current position.
      AXPositionInstance start_of_content =
          CreatePositionAtStartOfContent()->AsLeafTextPosition();
      return *start_of_content < *this &&
             start_of_content->IsFollowedByGeneratedNewline();
    }

    return previous_position->AllowsCharacterStopsOnGeneratedNewline() &&
           !previous_position->GetAnchor()->IsLineBreak() &&
           previous_position->AtEndOfParagraph();
  }

  // Returns a text position located right before the next character (from this
  // position) in the tree's text representation, following these conditions:
  //
  //   - If this position is at the end of its anchor, normalize it to the start
  //   of the next text anchor, regardless of the position's affinity.
  //   Both text positions are equal when compared, but we consider the start of
  //   an anchor to be a position BEFORE its first character and the end of the
  //   previous to be AFTER its last character.
  //
  //   - Skip any empty text anchors; they're "invisible" to the text
  //   representation and the next character could be ahead.
  //
  //   - Return a null position if there is no next character forward.
  //
  // If possible, return a position anchored at the current position's anchor;
  // this is necessary because we don't want to return any position that might
  // be located in the shadow DOM or in a position anchored at a node that is
  // not visible to a specific platform's APIs.
  //
  // Also, |text_offset| is adjusted to point to a valid character offset, i.e.
  // it cannot be pointing to a low surrogate pair or to the middle of a
  // grapheme cluster.
  AXPositionInstance AsLeafTextPositionBeforeCharacter() const {
    if (IsNullPosition())
      return Clone();

    AXPositionInstance leaf_text_position = AsLeafTextPosition();
    if (leaf_text_position->IsFollowedByGeneratedNewline()) {
      return leaf_text_position;
    }

    AXPositionInstance text_position = AsTextPosition();

    // In case the input affinity is upstream, reset it to downstream.
    //
    // This is to ensure that when we find the equivalent leaf text position, it
    // will be at the start of anchor if the original position is anchored to a
    // node higher up in the tree and pointing to a text offset that falls on
    // the boundary between two leaf nodes. In other words, the returned
    // position will always be "before character".
    text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
    text_position = text_position->AsLeafTextPosition();
    DCHECK(!text_position->IsNullPosition())
        << "Adjusting to a leaf position should never turn a non-null position "
           "into a null one.";

    if (!text_position->IsIgnored() && !text_position->AtEndOfAnchor()) {
      std::unique_ptr<base::i18n::BreakIterator> grapheme_iterator =
          text_position->GetGraphemeIterator();
      // The following situation should not be possible but there are existing
      // crashes in the field.
      //
      // TODO(nektar): Remove this workaround as soon as the source of the bug
      // is identified.
      if (text_position->text_offset_ < 0 ||
          text_position->text_offset_ > text_position->MaxTextOffset()) {
        SANITIZER_NOTREACHED() << "Offset range error:\n" << ToDebugString();
        return CreateNullPosition();
      }
      DCHECK_GE(text_position->text_offset_, 0);
      DCHECK_LE(text_position->text_offset_, text_position->MaxTextOffset());
      while (!text_position->AtStartOfAnchor() &&
             (!gfx::IsValidCodePointIndex(
                  text_position->GetText(),
                  static_cast<size_t>(text_position->text_offset_)) ||
              (grapheme_iterator &&
               !grapheme_iterator->IsGraphemeBoundary(
                   static_cast<size_t>(text_position->text_offset_))))) {
        --text_position->text_offset_;
      }
      return text_position;
    }

    return text_position->CreateNextPositionAtAnchorWithText();
  }

  // Returns a text position located right after the previous character (from
  // this position) in the tree's text representation.
  //
  // See `AsLeafTextPositionBeforeCharacter`, as this is its "reversed" version.
  AXPositionInstance AsLeafTextPositionAfterCharacter() const {
    if (IsNullPosition())
      return Clone();

    AXPositionInstance leaf_text_position = AsLeafTextPosition();
    if (leaf_text_position->IsPrecededByGeneratedNewline())
      return leaf_text_position;

    AXPositionInstance text_position = AsTextPosition();
    // Temporarily set the affinity to upstream.
    //
    // This is to ensure that when we find the equivalent leaf text position, it
    // will be at the end of anchor if the original position is anchored to a
    // node higher up in the tree and pointing to a text offset that falls on
    // the boundary between two leaf nodes. In other words, the returned
    // position will always be "after character".
    text_position->affinity_ = ax::mojom::TextAffinity::kUpstream;
    text_position = text_position->AsLeafTextPosition();
    DCHECK(!text_position->IsNullPosition())
        << "Adjusting to a leaf position should never turn a non-null position "
           "into a null one.";

    if (!text_position->IsIgnored() && !text_position->AtStartOfAnchor()) {
      std::unique_ptr<base::i18n::BreakIterator> grapheme_iterator =
          text_position->GetGraphemeIterator();
      // The following situation should not be possible but there are existing
      // crashes in the field.
      //
      // TODO(nektar): Remove this workaround as soon as the source of the bug
      // is identified.
      if (text_position->text_offset_ < 0 ||
          text_position->text_offset_ > text_position->MaxTextOffset()) {
        SANITIZER_NOTREACHED() << "Offset range error:\n" << ToDebugString();
        return CreateNullPosition();
      }
      DCHECK_GE(text_position->text_offset_, 0);
      DCHECK_LE(text_position->text_offset_, text_position->MaxTextOffset());
      while (!text_position->AtEndOfAnchor() &&
             (!gfx::IsValidCodePointIndex(
                  text_position->GetText(),
                  static_cast<size_t>(text_position->text_offset_)) ||
              (grapheme_iterator &&
               !grapheme_iterator->IsGraphemeBoundary(
                   static_cast<size_t>(text_position->text_offset_))))) {
        ++text_position->text_offset_;
      }

      // Reset the affinity to downstream, because an upstream affinity doesn't
      // make sense on a leaf anchor.
      text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
      return text_position;
    }

    return text_position->CreatePreviousPositionAtAnchorWithText();
  }

  // Creates a position pointing to before the next character, which is defined
  // as the start of the next grapheme cluster. Also, ensures that the created
  // position will not point to a low surrogate pair.
  //
  // A grapheme cluster is what an end-user would consider a character and it
  // could include a letter with additional diacritics. It could be more than
  // one Unicode code unit in length.
  //
  // See also http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
  AXPositionInstance CreateNextCharacterPosition(
      AXMovementOptions options) const {
    if (options.boundary_behavior ==
            AXBoundaryBehavior::kStopAtAnchorBoundary &&
        AtEndOfAnchor()) {
      return Clone();
    }

    AXPositionInstance text_position = AsLeafTextPositionBeforeCharacter();
    if (text_position->IsNullPosition()) {
      if (options.boundary_behavior != AXBoundaryBehavior::kCrossBoundary)
        text_position = Clone();

      return text_position;
    }

    if (text_position->IsFollowedByGeneratedNewline()) {
      return CreateNextPositionAtAnchorWithText();
    }

    // Calling "AsLeafTextPositionBeforeCharacter" should have created a text
    // position that is either at a grapheme boundary, or a null position. If
    // our text offset is pointing to a position that is in the middle of a
    // grapheme cluster, we should not erroneously assume that we are at a
    // character boundary and stop because we had been asked to "stop if already
    // at boundary". However, we should not modify our position if
    // `AsLeafTextPositionBeforeCharacter` has simply moved us to the start of
    // the next leaf anchor because we originally happened to be at the end of
    // our current anchor. We also need to ensure that we are comparing two
    // positions that have the same affinity, since
    // `AsLeafTextPositionBeforeCharacter` resets the affinity to downstream,
    // while the original affinity might have been upstream.
    if (options.boundary_behavior ==
            AXBoundaryBehavior::kStopAtAnchorBoundary &&
        options.boundary_detection ==
            AXBoundaryDetection::kCheckInitialPosition &&
        (AtEndOfAnchor() ||
         (IsTextPosition() &&
          *text_position == *CloneWithDownstreamAffinity()))) {
      return Clone();
    }

    int max_text_offset = text_position->MaxTextOffset();
    DCHECK_LT(text_position->text_offset_, max_text_offset);
    std::unique_ptr<base::i18n::BreakIterator> grapheme_iterator =
        text_position->GetGraphemeIterator();
    do {
      ++text_position->text_offset_;
    } while (text_position->text_offset_ < max_text_offset &&
             grapheme_iterator &&
             !grapheme_iterator->IsGraphemeBoundary(
                 static_cast<size_t>(text_position->text_offset_)));
    DCHECK_GT(text_position->text_offset_, 0);
    DCHECK_LE(text_position->text_offset_, text_position->MaxTextOffset());

    // If the character boundary is in the same subtree, return a position
    // rooted at this position's anchor. This is necessary because we don't want
    // to return a position that might be in the shadow DOM when this position
    // is not.
    const AXNode* common_anchor = text_position->LowestCommonAnchor(*this);
    if (GetAnchor() == common_anchor) {
      text_position = text_position->CreateAncestorPosition(
          common_anchor, ax::mojom::MoveDirection::kForward);
    } else if (options.boundary_behavior ==
               AXBoundaryBehavior::kStopAtAnchorBoundary) {
      // If the next character position crosses the current anchor boundary
      // with kStopAtAnchorBoundary, snap to the end of the current anchor.
      return CreatePositionAtEndOfAnchor();
    }

    // Even if the resulting position is right on a soft line break, affinity is
    // defaulted to downstream so that this method will always produce the same
    // result regardless of the direction of motion or the input affinity.
    text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;

    if (IsTreePosition())
      return text_position->AsTreePosition();
    return text_position;
  }

  // Creates a position pointing to before the previous character, which is
  // defined as the start of the previous grapheme cluster. Also, ensures that
  // the created position will not point to a low surrogate pair.
  //
  // See the comment above `CreateNextCharacterPosition` for the definition of a
  // grapheme cluster.
  AXPositionInstance CreatePreviousCharacterPosition(
      AXMovementOptions options) const {
    if (options.boundary_behavior ==
            AXBoundaryBehavior::kStopAtAnchorBoundary &&
        AtStartOfAnchor()) {
      return Clone();
    }

    AXPositionInstance text_position = AsLeafTextPositionAfterCharacter();
    if (text_position->IsNullPosition()) {
      if (options.boundary_behavior != AXBoundaryBehavior::kCrossBoundary)
        text_position = Clone();

      return text_position;
    }

    // Calling "AsLeafTextPositionAfterCharacter" should have created a text
    // position that is either at the start of an anchor that is preceded by a
    // generated newline, at a grapheme boundary or a null position.
    if (text_position->IsPrecededByGeneratedNewline()) {
      // When `text_position` is right after a generated newline character, we
      // should create a position located at the end of the previous anchor.
      text_position = CreatePreviousPositionAtAnchorWithText();
      DCHECK(!text_position->IsNullPosition());
    } else {
      // If our text offset is pointing to a position that is in the middle of a
      // grapheme cluster, we should not erroneously assume that we are at a
      // character boundary and stop because we had been asked to "stop if
      // already at boundary". However, we should not modify our position if
      // `AsLeafTextPositionAfterCharacter` has simply moved us to the end of
      // the previous leaf anchor because we originally happened to be at the
      // start of our current anchor. We also need to ignore any differences
      // that might be due to the affinity, because that should not be a
      // determining factor as to whether we would stop if we are already at
      // boundary or not.
      if (options.boundary_behavior ==
              AXBoundaryBehavior::kStopAtAnchorBoundary &&
          options.boundary_detection ==
              AXBoundaryDetection::kCheckInitialPosition &&
          (AtStartOfAnchor() ||
           (IsTextPosition() &&
            (*text_position == *CloneWithUpstreamAffinity() ||
             *text_position == *CloneWithDownstreamAffinity())))) {
        return Clone();
      }

      DCHECK_GT(text_position->text_offset_, 0);
      std::unique_ptr<base::i18n::BreakIterator> grapheme_iterator =
          text_position->GetGraphemeIterator();
      do {
        --text_position->text_offset_;
      } while (!text_position->AtStartOfAnchor() && grapheme_iterator &&
               !grapheme_iterator->IsGraphemeBoundary(
                   static_cast<size_t>(text_position->text_offset_)));
      DCHECK_GE(text_position->text_offset_, 0);
      DCHECK_LT(text_position->text_offset_, text_position->MaxTextOffset());
    }

    // The character boundary should be in the same subtree. Return a position
    // rooted at this position's anchor. This is necessary because we don't want
    // to return a position that might be in the shadow DOM when this position
    // is not.
    const AXNode* common_anchor = text_position->LowestCommonAnchor(*this);
    if (GetAnchor() == common_anchor) {
      text_position = text_position->CreateAncestorPosition(
          common_anchor, ax::mojom::MoveDirection::kBackward);
    } else if (options.boundary_behavior ==
               AXBoundaryBehavior::kStopAtAnchorBoundary) {
      // If the previous character position crosses the current anchor boundary
      // with StopAtAnchorBoundary, snap to the start of the current anchor.
      return CreatePositionAtStartOfAnchor();
    }

    // Even if the resulting position is right on a soft line break, affinity is
    // defaulted to downstream so that this method will always produce the same
    // result regardless of the direction of motion or the input affinity.
    text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;

    if (IsTreePosition())
      return text_position->AsTreePosition();
    return text_position;
  }

  AXPositionInstance CreateNextWordStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfWordPredicate),
        base::BindRepeating(&AtEndOfWordPredicate),
        base::BindRepeating(&GetWordStartOffsetsFunc));
  }

  AXPositionInstance CreatePreviousWordStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfWordPredicate),
        base::BindRepeating(&AtEndOfWordPredicate),
        base::BindRepeating(&GetWordStartOffsetsFunc));
  }

  // Word end positions are one past the last character of the word.
  AXPositionInstance CreateNextWordEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfWordPredicate),
        base::BindRepeating(&AtEndOfWordPredicate),
        base::BindRepeating(&GetWordEndOffsetsFunc));
  }

  // Word end positions are one past the last character of the word.
  AXPositionInstance CreatePreviousWordEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfWordPredicate),
        base::BindRepeating(&AtEndOfWordPredicate),
        base::BindRepeating(&GetWordEndOffsetsFunc));
  }

  AXPositionInstance CreateNextLineStartPosition(
      AXMovementOptions options) const {
    options.upstream_bounded = true;
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfLinePredicate),
        base::BindRepeating(&AtEndOfLinePredicate));
  }

  AXPositionInstance CreatePreviousLineStartPosition(
      AXMovementOptions options) const {
    options.upstream_bounded = true;
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfLinePredicate),
        base::BindRepeating(&AtEndOfLinePredicate));
  }

  // Line end positions are one past the last character of the line, excluding
  // any white space or newline characters that separate the lines.
  AXPositionInstance CreateNextLineEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfLinePredicate),
        base::BindRepeating(&AtEndOfLinePredicate));
  }

  // Line end positions are one past the last character of the line, excluding
  // any white space or newline characters separating the lines.
  AXPositionInstance CreatePreviousLineEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfLinePredicate),
        base::BindRepeating(&AtEndOfLinePredicate));
  }

  AXPositionInstance CreateNextFormatStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfFormatPredicate),
        base::BindRepeating(&AtEndOfFormatPredicate),
        base::BindRepeating(&GetFormatStartOffsetsFunc));
  }

  AXPositionInstance CreatePreviousFormatStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfFormatPredicate),
        base::BindRepeating(&AtEndOfFormatPredicate),
        base::BindRepeating(&GetFormatStartOffsetsFunc));
  }

  AXPositionInstance CreateNextFormatEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfFormatPredicate),
        base::BindRepeating(&AtEndOfFormatPredicate),
        base::BindRepeating(&GetFormatEndOffsetsFunc));
  }

  AXPositionInstance CreatePreviousFormatEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfFormatPredicate),
        base::BindRepeating(&AtEndOfFormatPredicate),
        base::BindRepeating(&GetFormatEndOffsetsFunc));
  }

  AXPositionInstance CreateNextSentenceStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfSentencePredicate),
        base::BindRepeating(&AtEndOfSentencePredicate),
        base::BindRepeating(&GetSentenceStartOffsetsFunc));
  }

  AXPositionInstance CreatePreviousSentenceStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfSentencePredicate),
        base::BindRepeating(&AtEndOfSentencePredicate),
        base::BindRepeating(&GetSentenceStartOffsetsFunc));
  }

  AXPositionInstance CreateNextSentenceEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfSentencePredicate),
        base::BindRepeating(&AtEndOfSentencePredicate),
        base::BindRepeating(&GetSentenceEndOffsetsFunc));
  }

  AXPositionInstance CreatePreviousSentenceEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfSentencePredicate),
        base::BindRepeating(&AtEndOfSentencePredicate),
        base::BindRepeating(&GetSentenceEndOffsetsFunc));
  }

  AXPositionInstance CreateNextParagraphStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfParagraphPredicate),
        base::BindRepeating(&AtEndOfParagraphPredicate));
  }

  AXPositionInstance CreateNextParagraphStartPositionSkippingEmptyParagraphs(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(
            &AtStartOfParagraphExcludingEmptyParagraphsPredicate),
        base::BindRepeating(
            &AtStartOfParagraphExcludingEmptyParagraphsPredicate));
  }

  AXPositionInstance CreatePreviousParagraphStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfParagraphPredicate),
        base::BindRepeating(&AtEndOfParagraphPredicate));
  }

  AXPositionInstance
  CreatePreviousParagraphStartPositionSkippingEmptyParagraphs(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(
            &AtStartOfParagraphExcludingEmptyParagraphsPredicate),
        base::BindRepeating(
            &AtStartOfParagraphExcludingEmptyParagraphsPredicate));
  }

  AXPositionInstance CreateNextParagraphEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfParagraphPredicate),
        base::BindRepeating(&AtEndOfParagraphPredicate));
  }

  AXPositionInstance CreatePreviousParagraphEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfParagraphPredicate),
        base::BindRepeating(&AtEndOfParagraphPredicate));
  }

  AXPositionInstance CreateNextPageStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfPagePredicate),
        base::BindRepeating(&AtEndOfPagePredicate));
  }

  AXPositionInstance CreatePreviousPageStartPosition(
      AXMovementOptions options) const {
    return CreateBoundaryStartPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfPagePredicate),
        base::BindRepeating(&AtEndOfPagePredicate));
  }

  AXPositionInstance CreateNextPageEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kForward,
        base::BindRepeating(&AtStartOfPagePredicate),
        base::BindRepeating(&AtEndOfPagePredicate));
  }

  AXPositionInstance CreatePreviousPageEndPosition(
      AXMovementOptions options) const {
    return CreateBoundaryEndPosition(
        options, ax::mojom::MoveDirection::kBackward,
        base::BindRepeating(&AtStartOfPagePredicate),
        base::BindRepeating(&AtEndOfPagePredicate));
  }

  AXPositionInstance CreateBoundaryStartPosition(
      AXMovementOptions options,
      ax::mojom::MoveDirection move_direction,
      BoundaryConditionPredicate at_start_condition,
      BoundaryConditionPredicate at_end_condition,
      BoundaryTextOffsetsFunc get_start_offsets =
          BoundaryTextOffsetsFunc()) const {
    AXPositionInstance text_position;
    if (!AtEndOfAnchor()) {
      // We could get a leaf text position at the end of its anchor, where
      // boundary start offsets would surely not be present. In such cases, we
      // need to normalize to the start of the next leaf anchor. We avoid making
      // this change when we are at the end of our anchor because this could
      // effectively shift the position forward.
      text_position = AsLeafTextPositionBeforeCharacter();
    } else {
      text_position = AsLeafTextPosition();
    }

    if (text_position->IsNullPosition()) {
      return text_position;
    }

    // If true, we should not move the position any further.
    bool forward_upstream =
        options.upstream_bounded &&
        move_direction == ax::mojom::MoveDirection::kForward &&
        affinity() == ax::mojom::TextAffinity::kUpstream;

    // If true, we should skip the initial position and move at least once.
    bool backward_upstream =
        options.upstream_bounded &&
        move_direction == ax::mojom::MoveDirection::kBackward &&
        affinity() == ax::mojom::TextAffinity::kUpstream;

    if (backward_upstream ||
        (options.boundary_detection ==
             AXBoundaryDetection::kDontCheckInitialPosition &&
         !forward_upstream)) {
      text_position =
          text_position->CreateAdjacentLeafTextPosition(move_direction);
      if (text_position->IsNullPosition()) {
        // There is no adjacent position to move to; in such case, CrossBoundary
        // behavior shall return a null position, while any other behavior shall
        // fallback to return the initial position.
        if (options.boundary_behavior == AXBoundaryBehavior::kCrossBoundary) {
          return text_position;
        }

        return Clone();
      }
    }

    if (!forward_upstream && !at_start_condition.Run(text_position)) {
      text_position = text_position->CreatePositionAtNextOffsetBoundary(
          move_direction, get_start_offsets);

      while (!at_start_condition.Run(text_position)) {
        AXPositionInstance next_position;
        switch (move_direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            if (text_position->AtStartOfAnchor()) {
              next_position = text_position->CreatePreviousLeafTextPosition(
                  base::BindRepeating(&AbortMoveAtRootBoundary));
            } else {
              text_position = text_position->CreatePositionAtStartOfAnchor();
              DCHECK(!text_position->IsNullPosition());
              continue;
            }
            break;
          case ax::mojom::MoveDirection::kForward:
            next_position = text_position->CreateNextLeafTextPosition(
                base::BindRepeating(&AbortMoveAtRootBoundary));
            break;
        }

        if (next_position->IsNullPosition()) {
          if (options.boundary_behavior ==
              AXBoundaryBehavior::kStopAtAnchorBoundary) {
            switch (move_direction) {
              case ax::mojom::MoveDirection::kNone:
                NOTREACHED();
              case ax::mojom::MoveDirection::kBackward:
                return CreatePositionAtStartOfAnchor()
                    ->TryAsUnignoredPositionPreservingAnchor(
                        AXPositionAdjustmentBehavior::kMoveBackward);
              case ax::mojom::MoveDirection::kForward:
                return CreatePositionAtEndOfAnchor()
                    ->TryAsUnignoredPositionPreservingAnchor(
                        AXPositionAdjustmentBehavior::kMoveForward);
            }
          }

          if (options.boundary_behavior ==
              AXBoundaryBehavior::kStopAtLastAnchorBoundary) {
            // We can't simply return the following position; break and after
            // this loop we'll try to do some adjustments to text_position.
            switch (move_direction) {
              case ax::mojom::MoveDirection::kNone:
                NOTREACHED();
              case ax::mojom::MoveDirection::kBackward:
                text_position = text_position->CreatePositionAtStartOfAnchor();
                break;
              case ax::mojom::MoveDirection::kForward:
                text_position = text_position->CreatePositionAtEndOfAnchor();
                break;
            }

            break;
          }

          return next_position->AsUnignoredPosition(
              AXPositionAdjustmentBehavior::kMoveForward);
        }

        // Continue searching for the next boundary start in the specified
        // direction until the next logical text position is reached.
        text_position = next_position->CreatePositionAtFirstOffsetBoundary(
            move_direction, get_start_offsets);
      }
    }

    // If the boundary is in the same subtree, return a position rooted at this
    // position's anchor. This is necessary because we don't want to return a
    // position that might be in the shadow DOM when this position is not.
    const AXNode* common_anchor = text_position->LowestCommonAnchor(*this);
    if (GetAnchor() == common_anchor) {
      text_position =
          text_position->CreateAncestorPosition(common_anchor, move_direction);
    } else if (options.boundary_behavior ==
               AXBoundaryBehavior::kStopAtAnchorBoundary) {
      switch (move_direction) {
        case ax::mojom::MoveDirection::kNone:
          NOTREACHED();
        case ax::mojom::MoveDirection::kBackward:
          text_position = CreatePositionAtStartOfAnchor()
                              ->TryAsUnignoredPositionPreservingAnchor(
                                  AXPositionAdjustmentBehavior::kMoveBackward);
          break;
        case ax::mojom::MoveDirection::kForward:
          text_position = CreatePositionAtEndOfAnchor()
                              ->TryAsUnignoredPositionPreservingAnchor(
                                  AXPositionAdjustmentBehavior::kMoveForward);
      }

      // Preserve affinity for forward upstream positions.
      if (forward_upstream) {
        text_position->affinity_ = ax::mojom::TextAffinity::kUpstream;
      }

      return text_position;
    }

    if (IsTreePosition()) {
      text_position = text_position->AsTreePosition();
    }

    AXPositionInstance unignored_position = text_position->AsUnignoredPosition(
        AXPositionAdjustmentBehavior::kMoveForward);

    // If there are no unignored positions then `text_position` is anchored in
    // ignored content at the end of the whole content. For
    // `kStopAtLastAnchorBoundary`, try to adjust in the opposite direction to
    // return a position within the whole content just before crossing into the
    // ignored content. This will be the last unignored anchor boundary.
    if (unignored_position->IsNullPosition() &&
        options.boundary_behavior ==
            AXBoundaryBehavior::kStopAtLastAnchorBoundary) {
      unignored_position = text_position->AsUnignoredPosition(
          AXPositionAdjustmentBehavior::kMoveBackward);
    }

    unignored_position->affinity_ = forward_upstream
                                        ? ax::mojom::TextAffinity::kUpstream
                                        : ax::mojom::TextAffinity::kDownstream;

    return unignored_position;
  }

  AXPositionInstance CreateBoundaryEndPosition(
      AXMovementOptions options,
      ax::mojom::MoveDirection move_direction,
      BoundaryConditionPredicate at_start_condition,
      BoundaryConditionPredicate at_end_condition,
      BoundaryTextOffsetsFunc get_end_offsets =
          BoundaryTextOffsetsFunc()) const {
    AXPositionInstance text_position;
    if (!AtStartOfAnchor()) {
      // We could get a leaf text position at the start of its anchor, where
      // boundary end offsets would surely not be present. In such cases, we
      // need to normalize to the end of the previous leaf anchor. We avoid
      // making this change when we are at the start of our anchor because this
      // could effectively shift the position backward.
      text_position = AsLeafTextPositionAfterCharacter();
    } else {
      text_position = AsLeafTextPosition();
    }

    if (text_position->IsNullPosition())
      return text_position;

    if (options.boundary_detection ==
        AXBoundaryDetection::kDontCheckInitialPosition) {
      text_position =
          text_position->CreateAdjacentLeafTextPosition(move_direction);
      if (text_position->IsNullPosition()) {
        // There is no adjacent position to move to; in such case, CrossBoundary
        // behavior shall return a null position, while any other behavior shall
        // fallback to return the initial position.
        if (options.boundary_behavior == AXBoundaryBehavior::kCrossBoundary)
          return text_position;
        return Clone();
      }
    }

    if (!at_end_condition.Run(text_position)) {
      text_position = text_position->CreatePositionAtNextOffsetBoundary(
          move_direction, get_end_offsets);

      while (!at_end_condition.Run(text_position)) {
        AXPositionInstance next_position;
        switch (move_direction) {
          case ax::mojom::MoveDirection::kNone:
            NOTREACHED();
          case ax::mojom::MoveDirection::kBackward:
            next_position =
                text_position
                    ->CreatePreviousLeafTextPosition(
                        base::BindRepeating(&AbortMoveAtRootBoundary))
                    ->CreatePositionAtEndOfAnchor();
            break;
          case ax::mojom::MoveDirection::kForward:
            if (text_position->AtEndOfAnchor()) {
              next_position = text_position->CreateNextLeafTextPosition(
                  base::BindRepeating(&AbortMoveAtRootBoundary));
            } else {
              text_position = text_position->CreatePositionAtEndOfAnchor();
              DCHECK(!text_position->IsNullPosition());
              continue;
            }
            break;
        }

        if (next_position->IsNullPosition()) {
          if (options.boundary_behavior ==
              AXBoundaryBehavior::kStopAtAnchorBoundary) {
            switch (move_direction) {
              case ax::mojom::MoveDirection::kNone:
                NOTREACHED();
              case ax::mojom::MoveDirection::kBackward:
                return CreatePositionAtStartOfAnchor()
                    ->TryAsUnignoredPositionPreservingAnchor(
                        AXPositionAdjustmentBehavior::kMoveBackward);
              case ax::mojom::MoveDirection::kForward:
                return CreatePositionAtEndOfAnchor()
                    ->TryAsUnignoredPositionPreservingAnchor(
                        AXPositionAdjustmentBehavior::kMoveForward);
            }
          }

          if (options.boundary_behavior ==
              AXBoundaryBehavior::kStopAtLastAnchorBoundary) {
            // We can't simply return the following position; break and after
            // this loop we'll try to do some adjustments to text_position.
            switch (move_direction) {
              case ax::mojom::MoveDirection::kNone:
                NOTREACHED();
              case ax::mojom::MoveDirection::kBackward:
                text_position = text_position->CreatePositionAtStartOfAnchor();
                break;
              case ax::mojom::MoveDirection::kForward:
                text_position = text_position->CreatePositionAtEndOfAnchor();
                break;
            }

            break;
          }

          return next_position->AsUnignoredPosition(
              AXPositionAdjustmentBehavior::kMoveBackward);
        }

        // Continue searching for the next boundary end in the specified
        // direction until the next logical text position is reached.
        text_position = next_position->CreatePositionAtFirstOffsetBoundary(
            move_direction, get_end_offsets);
      }
    }

    // If the boundary is in the same subtree, return a position rooted at this
    // position's anchor. This is necessary because we don't want to return a
    // position that might be in the shadow DOM when this position is not.
    const AXNode* common_anchor = text_position->LowestCommonAnchor(*this);
    if (GetAnchor() == common_anchor) {
      text_position =
          text_position->CreateAncestorPosition(common_anchor, move_direction);
    } else if (options.boundary_behavior ==
               AXBoundaryBehavior::kStopAtAnchorBoundary) {
      switch (move_direction) {
        case ax::mojom::MoveDirection::kNone:
          NOTREACHED();
        case ax::mojom::MoveDirection::kBackward:
          return CreatePositionAtStartOfAnchor()
              ->TryAsUnignoredPositionPreservingAnchor(
                  AXPositionAdjustmentBehavior::kMoveBackward);
        case ax::mojom::MoveDirection::kForward:
          return CreatePositionAtEndOfAnchor()
              ->TryAsUnignoredPositionPreservingAnchor(
                  AXPositionAdjustmentBehavior::kMoveForward);
      }
    }

    // If there is no ambiguity as to whether the position is at the end of
    // the current boundary or the start of the next boundary, an upstream
    // affinity should be reset to downstream in order to get consistent output
    // from this method, regardless of input affinity.
    //
    // Note that there could be no ambiguity if the boundary is either at the
    // start or the end of the current anchor, so we should always reset to
    // downstream affinity in those cases.
    if (text_position->affinity_ == ax::mojom::TextAffinity::kUpstream) {
      AXPositionInstance downstream_position =
          text_position->CloneWithDownstreamAffinity();
      if (downstream_position->AtStartOfAnchor() ||
          downstream_position->AtEndOfAnchor() ||
          !downstream_position->AtStartOfLine()) {
        text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
      }
    }

    if (IsTreePosition())
      text_position = text_position->AsTreePosition();
    AXPositionInstance unignored_position = text_position->AsUnignoredPosition(
        AXPositionAdjustmentBehavior::kMoveBackward);
    // If there are no unignored positions then `text_position` is anchored in
    // ignored content at the start or end of the whole content. For
    // `kStopAtLastAnchorBoundary`, try to adjust in the opposite direction to
    // return a position within the whole content just before crossing into the
    // ignored content. This will be the last unignored anchor boundary.
    if (unignored_position->IsNullPosition() &&
        options.boundary_behavior ==
            AXBoundaryBehavior::kStopAtLastAnchorBoundary) {
      unignored_position = text_position->AsUnignoredPosition(
          AXPositionAdjustmentBehavior::kMoveForward);
    }
    return unignored_position;
  }

  // Uses depth-first pre-order traversal.
  AXPositionInstance CreateNextAnchorPosition() const {
    return CreateNextAnchorPosition(
        base::BindRepeating(&DefaultAbortMovePredicate));
  }

  // Uses depth-first pre-order traversal.
  AXPositionInstance CreatePreviousAnchorPosition() const {
    return CreatePreviousAnchorPosition(
        base::BindRepeating(&DefaultAbortMovePredicate));
  }

  // Returns an optional integer indicating the logical order of this position
  // compared to another position or returns an empty optional if the positions
  // are not comparable. Any text position at the same character location is
  // logically equivalent although they may be on different anchors or have
  // different text offsets. Positions are not comparable when one position is
  // null and the other is not or if the positions do not have any common
  // ancestor.
  //
  //    0: if this position is logically equivalent to the other position
  //   <0: if this position is logically less than the other position
  //   >0: if this position is logically greater than the other position
  std::optional<int> CompareTo(const AXPosition& other) const {
    if (IsNullPosition() || other.IsNullPosition()) {
      if (IsNullPosition() && other.IsNullPosition())
        return 0;
      return std::nullopt;
    }
    // Valid positions are required for comparison. Use `AsValidPosition`
    // or `SnapToMaxTextOffsetIfBeyond` before calling `CompareTo` or making
    // comparisons.
    DCHECK(IsValid());
    DCHECK(other.IsValid());

    if (GetAnchor() == other.GetAnchor())
      return SlowCompareTo(other);  // No optimization is necessary.

    // Ancestor positions are expensive to compute. If possible, we will avoid
    // doing so by computing the ancestor chain of the two positions' anchors.
    // If the lowest common ancestor is neither position's anchor, we can use
    // the order of the first uncommon ancestors as a proxy for the order of the
    // positions. Obviously, this heuristic cannot be used if one position is
    // the ancestor of the other.
    //
    // In order to do that, we need to normalize text positions at the end of an
    // anchor to equivalent positions at the start of the next anchor. Ignored
    // positions are a special case in that they need to be shifted to the
    // nearest unignored position in order to be normalized. That shifting can
    // change the comparison result, so if we have an ignored position, we must
    // use a different, slower method which does away with many of our
    // optimizations.
    if (IsIgnored() || other.IsIgnored())
      return SlowCompareTo(other);

    // Normalize any text positions at the end of an anchor to equivalent
    // positions at the start of the next anchor. This will potentially make the
    // two positions not be ancestors of one another, if they originally were.
    AXPositionInstance normalized_this_position = Clone();
    if (normalized_this_position->IsTextPosition()) {
      normalized_this_position =
          normalized_this_position->AsLeafTextPositionBeforeCharacter();
    }

    AXPositionInstance normalized_other_position = other.Clone();
    if (normalized_other_position->IsTextPosition()) {
      normalized_other_position =
          normalized_other_position->AsLeafTextPositionBeforeCharacter();
    }

    if (normalized_this_position->IsNullPosition()) {
      if (normalized_other_position->IsNullPosition()) {
        // Both positions normalized to a position past the end of the whole
        // content. There is no way that they could be ancestors of one another,
        // so using the slow path is not required.
        DCHECK_EQ(SlowCompareTo(other).value(), 0);
        return 0;
      }
      // |this| normalized to a position past the end of the whole content.
      // Since we don't know if one position is the ancestor of the other, we
      // need to use the slow path.
      return SlowCompareTo(other);
    }
    if (normalized_other_position->IsNullPosition()) {
      // |other| normalized to a position past the end of the whole content.
      // Since we don't know if one position is the ancestor of the other, we
      // need to use the slow path.
      return SlowCompareTo(other);
    }

    // Compute the ancestor stacks of both positions and walk them ourselves
    // rather than calling `LowestCommonAnchor`. That way, we can discover the
    // first uncommon ancestors which we need to use in order to compare the two
    // positions.
    const AXNode* common_anchor = nullptr;
    base::stack<AXNode*> our_ancestors =
        normalized_this_position->GetAncestorAnchors();
    base::stack<AXNode*> other_ancestors =
        normalized_other_position->GetAncestorAnchors();
    while (!our_ancestors.empty() && !other_ancestors.empty() &&
           our_ancestors.top() == other_ancestors.top()) {
      common_anchor = our_ancestors.top();
      our_ancestors.pop();
      other_ancestors.pop();
    }

    if (!common_anchor)
      return std::nullopt;

    // If each position has an uncommon ancestor node, we can compare those
    // instead of needing to compute ancestor positions. Otherwise we need to
    // use "SlowCompareTo". Also, if the two positions became equivalent after
    // being normalized above, we can't compare using this optimized method. We
    // need to use "SlowCompareTo", because affinity information would have been
    // lost during the normalization process. See comments in "SlowCompareTo"
    // for an explanation of how affinity could affect the comparison. If one
    // position is the ancestor of the other, we need to use "SlowCompareTo",
    // especially if either or both positions are text positions, because the
    // conversion to tree positions below would lose information that could
    // affect the comparison. In the case where the positions are ancestors of
    // one another, but they are both tree positions, using the "SlowCompareTo"
    // method will not affect performance, so we still opt for that. Note that
    // determining whether two positions are ancestors of one another could
    // easily be accomplished by checking if there are any ancestors left after
    // removing the common ancestor anchor from either position's ancestor
    // stack.
    if (our_ancestors.empty() || other_ancestors.empty())
      return SlowCompareTo(other);

    AXPositionInstance this_uncommon_tree_position =
        CreateTreePositionAtStartOfAnchor(*our_ancestors.top());
    int this_uncommon_ancestor_index =
        this_uncommon_tree_position->AnchorIndexInParent();
    AXPositionInstance other_uncommon_tree_position =
        CreateTreePositionAtStartOfAnchor(*other_ancestors.top());
    int other_uncommon_ancestor_index =
        other_uncommon_tree_position->AnchorIndexInParent();
    DCHECK_NE(this_uncommon_ancestor_index, other_uncommon_ancestor_index)
        << "Deepest uncommon ancestors should truly be uncommon, i.e. not "
           "the same.";
    int result = this_uncommon_ancestor_index - other_uncommon_ancestor_index;

    // On platforms that support embedded objects, if a text position is within
    // an embedded object and if it is not at the start of that object, the
    // resulting ancestor position should be adjusted to point after the
    // embedded object. Otherwise, assistive software will not be able to get
    // out of the embedded object if its text is not editable when navigating by
    // character or by word. The "SlowCompareTo" method can handle such corner
    // cases. For some reproduction steps see https://crbug.com/1057831.
    //
    // For example, look at the following accessibility tree and the two example
    // text positions together with their equivalent ancestor positions.
    // ++1 kRootWebArea
    // ++++2 kTextField "Before<embedded_object>after"
    // ++++++3 kStaticText "Before"
    // ++++++++4 kInlineTextBox "Before"
    // ++++++5 kImage "Test image"
    // ++++++6 kStaticText "after"
    // ++++++++7 kInlineTextBox "after"
    //
    // Note that the alt text of an image cannot be navigated with cursor
    // left/right, even when the rest of the contents are in a contenteditable.
    //
    // 1. Ancestor position should not be adjusted:
    // TextPosition anchor_id=kImage text_offset=0 affinity=downstream
    // annotated_text=<T>est image
    //
    // AncestorTextPosition anchor_id=kTextField text_offset=6
    // affinity=downstream annotated_text=Before<embedded_object>after
    //
    // 2. Ancestor position should be adjusted:
    // TextPosition anchor_id=kImage text_offset=1 affinity=downstream
    // annotated_text=T<e>st image
    //
    // AncestorTextPosition anchor_id=kTextField text_offset=7
    // affinity=downstream annotated_text=Beforeembedded_object<a>fter
    //
    // Note that since the adjustment to the distance between the ancestor
    // positions could at most be by one, we skip doing this check if the
    // ancestor positions have a distance of more than one since it can never
    // change the outcome of the comparison. We also don't need to perform an
    // adjustment if one of the positions is not right after the "object
    // replacement character" representing the object inside which the other
    // position is located, hence the `AtStartOfAnchor()` and
    // `IsEmbeddedObjectInParent()` checks.
    if (abs(result) == 1 &&
        ((IsTextPosition() && !AtStartOfAnchor() &&
          this_uncommon_tree_position->IsEmbeddedObjectInParent()) ||
         (other.IsTextPosition() && !other.AtStartOfAnchor() &&
          other_uncommon_tree_position->IsEmbeddedObjectInParent()))) {
      return SlowCompareTo(other);
    }

#if DCHECK_IS_ON()
    // Validate the optimization against the non-optimized version of the
    // method.
    int slow_result = SlowCompareTo(other).value();
    DCHECK((result == 0 && slow_result == 0) ||
           (result < 0 && slow_result < 0) || (result > 0 && slow_result > 0))
        << result << " vs. " << slow_result;
#endif  // DCHECK_IS_ON()

    return result;
  }

  // A less optimized, but much slower version of "CompareTo". Should only be
  // used when optimizations cannot be applied, e.g. when comparing ignored
  // positions. See "CompareTo" for an explanation of the return values.
  std::optional<int> SlowCompareTo(const AXPosition& other) const {
    if (IsNullPosition() && other.IsNullPosition())
      return 0;
    if (IsNullPosition() || other.IsNullPosition())
      return std::nullopt;

    // If both positions share an anchor and either one is a text position, or
    // both are tree positions, we can do a straight comparison of text offsets
    // or child indices.
    if (GetAnchor() == other.GetAnchor()) {
      std::optional<int> optional_result;
      ax::mojom::TextAffinity this_affinity;
      ax::mojom::TextAffinity other_affinity;

      if (IsTextPosition()) {
        AXPositionInstance other_text_position = other.AsTextPosition();
        optional_result = text_offset_ - other_text_position->text_offset_;
        this_affinity = affinity();
        other_affinity = other_text_position->affinity();
      } else if (other.IsTextPosition()) {
        AXPositionInstance this_text_position = AsTextPosition();
        optional_result = this_text_position->text_offset_ - other.text_offset_;
        this_affinity = this_text_position->affinity();
        other_affinity = other.affinity();
      }

      if (optional_result) {
        // Only when the two positions are otherwise equivalent will affinity
        // play a role.
        if (*optional_result != 0)
          return optional_result;

        if (this_affinity == ax::mojom::TextAffinity::kUpstream &&
            other_affinity == ax::mojom::TextAffinity::kDownstream) {
          return -1;
        }
        if (this_affinity == ax::mojom::TextAffinity::kDownstream &&
            other_affinity == ax::mojom::TextAffinity::kUpstream) {
          return 1;
        }

        return optional_result;
      }

      return child_index_ - other.child_index_;
    }

    // It is potentially costly to compute the parent position of a text
    // position, whilst computing the parent position of a tree position is
    // really inexpensive. In order to find the lowest common ancestor position,
    // especially if that ancestor is all the way up to the root of the tree,
    // computing the parent position will need to be done repeatedly. We avoid
    // the performance hit by converting both positions to tree positions and
    // only falling back to computing ancestor text positions if at least one
    // position is a text position and they don't have the same anchor.
    //
    // Essentially, the question we need to answer is: "When are two non
    // equivalent positions going to erroneously have the same lowest common
    // ancestor position when converted to tree positions as the ones they had
    // before the conversion?" In other words, when will
    // "this->AsTreePosition()->LowestCommonAncestorPosition(*other.AsTreePosition())
    // ==
    // other.AsTreePosition()->LowestCommonAncestorPosition(*this->AsTreePosition())"?
    // The answer is either when they have the same anchor and at least one is a
    // text position, (a case that was dealt with in the previous block), or
    // when at least one is a text position and one is an ancestor position of
    // the other. In all other cases, no information will be lost when
    // converting to tree positions.

    const AXNode* common_anchor = this->LowestCommonAnchor(other);
    if (!common_anchor)
      return std::nullopt;

    // If either of the two positions is a text position, and if one position is
    // an ancestor of the other, we need to compare using text positions,
    // because converting to tree positions will potentially lose information if
    // the text offset is anything other than 0 or `MaxTextOffset()`.
    if (IsTextPosition() || other.IsTextPosition()) {
      std::optional<int> optional_result;
      ax::mojom::TextAffinity this_affinity;
      ax::mojom::TextAffinity other_affinity;

      // The following two "if" blocks deal with comparisons between two
      // positions (one of which is a text position) that are ancestors of one
      // another. The third "if" block deals with comparisons between two text
      // positions that may or may not be ancestors of one another. Obviously,
      // in the case of two text positions, affinity could always play a role
      // (see comment in the relevant "if" block for an example). For the first
      // two cases, affinity still needs to be taken into consideration because
      // an "object replacement character" could be used to represent child
      // nodes in the text of their parents. Here is an example of how affinity
      // can influence a text/tree position comparison.
      //
      // 1 kRootWebArea
      // ++2 kGenericContainer
      // "<embedded_object_character><embedded_object_character>"
      // ++3 kButton "Line 1"
      // ++++++4 kStaticText "Line 1"
      // ++++++++5 kInlineTextBox "Line 1"
      // ++++6 kImage "<embedded_object_character>" kIsLineBreakingObject
      //
      // TextPosition anchor_id=5 text_offset=2 affinity=downstream
      // annotated_text=Li<n>e 1
      //
      // TreePosition anchor_id=6 child_index=BEFORE_TEXT
      //
      // The `LowestCommonAncestor` for both will differ in its affinity:
      // TextPosition anchor_id=2 text_offset=1 affinity=...
      // annotated_text=embedded_object_character<embedded_object_character>
      //
      // The text position would create a kUpstream position, while the tree
      // position would create a kDownstream position.

      if (GetAnchor() == common_anchor) {
        DCHECK_EQ(AsTextPosition()->GetAnchor(), common_anchor)
            << "AsTextPosition() should never modify the position's anchor.";
        // This text position's anchor is the common ancestor of the other text
        // position's anchor. We don't need to compute the ancestor position of
        // this position at the common anchor, since we already have it.
        //
        // Note that we convert the other position to an ancestor text position
        // using a forward direction, so that if there are any "object
        // replacement characters", two positions one inside the character and
        // one after it would compare as equivalent. Otherwise, screen readers
        // might get stuck inside embedded objects while navigating by character
        // or word. For some reproduction steps see https://crbug.com/1057831.
        // Per the IAccessible2 Spec, any selection that partially selects text
        // inside an embedded object, should select the entire "object
        // replacement character" in the parent object where the character
        // appears.

        AXPositionInstance other_text_position =
            other.AsTextPosition()->CreateAncestorPosition(
                common_anchor, ax::mojom::MoveDirection::kForward);
        DCHECK_EQ(other_text_position->GetAnchor(), common_anchor);
        other_affinity = other_text_position->affinity();
        AXPositionInstance this_text_position = AsTextPosition();
        this_affinity = this_text_position->affinity();
        optional_result = this_text_position->text_offset() -
                          other_text_position->text_offset();
      } else if (other.GetAnchor() == common_anchor) {
        DCHECK_EQ(other.AsTextPosition()->GetAnchor(), common_anchor)
            << "AsTextPosition() should never modify the position's anchor.";
        // The other text position's anchor is the common ancestor of this text
        // position's anchor. We don't need to compute the ancestor position of
        // the other position at the common anchor, since we already have it.
        //
        // Note that we convert this position to an ancestor text position using
        // a forward direction, so that if there are any "object replacement
        // characters", two positions one inside the character and one after it
        // would compare as equivalent. Otherwise, screen readers might get
        // stuck inside embedded objects while navigating by character or word.
        // For some reproduction steps see https://crbug.com/1057831.
        // Per the IAccessible2 Spec, any selection that partially selects text
        // inside an embedded object, should select the entire "object
        // replacement character" in the parent object where the character
        // appears.

        AXPositionInstance this_text_position =
            AsTextPosition()->CreateAncestorPosition(
                common_anchor, ax::mojom::MoveDirection::kForward);
        DCHECK_EQ(this_text_position->GetAnchor(), common_anchor);
        this_affinity = this_text_position->affinity();
        AXPositionInstance other_text_position = other.AsTextPosition();
        other_affinity = other_text_position->affinity();
        optional_result = this_text_position->text_offset() -
                          other_text_position->text_offset();
      } else if (IsTextPosition() && other.IsTextPosition()) {
        // We should compute and compare using the common ancestor text
        // position. Computing an ancestor text position will automatically take
        // affinity into consideration. It will also normalize text positions at
        // the end of their anchors to equivalent positions at the start of the
        // next anchor. Additionally, it would normalize positions within
        // "object replacement characters" to before the character, because the
        // two positions are not ancestors of one another and thus the special
        // case (see previous block) defined in the IAccessible2 Spec doesn't
        // apply. This process would maintain the characteristics of text
        // position comparisons, since a particular offset in the tree's text
        // representation could refer to multiple equivalent positions which are
        // anchored to different nodes in the tree, i.e. nodes which are
        // adjacent, or nodes that are at different levels of the tree.
        //
        // Here is an example of how affinity can influence a text position
        // comparison when at a line boundary:
        //
        // 1 kRootWebArea
        // ++2 kTextField "Line 1Line 2"
        // ++++3 kStaticText "Line 1"
        // ++++++4 kInlineTextBox "Line 1"
        // ++++5 kGenericContainer kIsLineBreakingObject
        // ++++++6 kStaticText "Line 2"
        // ++++++++7 kInlineTextBox "Line 2"
        //
        // TextPosition anchor_id=4 text_offset=6 affinity=downstream
        // annotated_text=Line 1<>
        //
        // TextPosition anchor_id=7 text_offset=0 affinity=downstream
        // annotated_text=<L>ine 2
        //
        // The `LowestCommonAncestor` for both will differ only in its affinity:
        // TextPosition anchor_id=2 text_offset=6 affinity=...
        // annotated_text=Line 1<L>ine 2
        //
        // anchor_id=4 would create a kUpstream position, while anchor_id=7
        // would create a kDownstream position.

        AXPositionInstance this_text_position_ancestor =
            LowestCommonAncestorPosition(other,
                                         ax::mojom::MoveDirection::kBackward);
        AXPositionInstance other_text_position_ancestor =
            other.LowestCommonAncestorPosition(
                *this, ax::mojom::MoveDirection::kBackward);
        DCHECK(this_text_position_ancestor->IsTextPosition());
        DCHECK(other_text_position_ancestor->IsTextPosition());

        this_affinity = this_text_position_ancestor->affinity();
        other_affinity = other_text_position_ancestor->affinity();
        optional_result = this_text_position_ancestor->text_offset() -
                          other_text_position_ancestor->text_offset();
      }

      if (optional_result) {
        // Only when the two positions are otherwise equivalent will affinity
        // play a role.
        if (*optional_result != 0)
          return optional_result;

        if (this_affinity == ax::mojom::TextAffinity::kUpstream &&
            other_affinity == ax::mojom::TextAffinity::kDownstream) {
          return -1;
        }
        if (this_affinity == ax::mojom::TextAffinity::kDownstream &&
            other_affinity == ax::mojom::TextAffinity::kUpstream) {
          return 1;
        }

        return optional_result;
      }
    }

    // Both positions are tree positions. We should normalize all tree positions
    // to the beginning of their anchors, unless one of the positions is the
    // ancestor of the other. In the latter case, such a normalization would
    // potentially lose information if performed on any of the two positions.
    //
    // ++kRootWebArea "<embedded_object><embedded_object>"
    // ++++kParagraph "Paragraph1"
    // ++++kParagraph "paragraph2"
    // A tree position at the end of the root web area and a tree position at
    // the end of the second paragraph should compare as equal. Normalizing any
    // of the two positions to the start of their respective anchors would make
    // the two positions unequal.
    //
    // Unlike text positions, two tree positions on two adjacent anchors, (the
    // first position at the end of its anchor, (i.e. an "after children"
    // position), and the other at its beginning), should not compare as equal.
    // This is because each position in the tree is unique, unlike an offset in
    // the tree's text representation which can refer to more than one tree
    // position. Meanwhile, affinity does not play any role in this case, since
    // except for "after children" positions, tree positions are collapsed to
    // the beginning of their parent node when computing their parent position.

    AXPositionInstance this_normalized_tree_position = AsTreePosition();
    AXPositionInstance other_normalized_tree_position = other.AsTreePosition();
    if (GetAnchor() != common_anchor &&
        other_normalized_tree_position->GetAnchor() != common_anchor) {
      // None of the positions is the ancestor of the other, so normalization
      // could go ahead.
      this_normalized_tree_position =
          this_normalized_tree_position->CreatePositionAtStartOfAnchor();
      other_normalized_tree_position =
          other_normalized_tree_position->CreatePositionAtStartOfAnchor();
    }

    AXPositionInstance this_tree_position_ancestor =
        this_normalized_tree_position->CreateAncestorPosition(
            common_anchor, ax::mojom::MoveDirection::kBackward);
    AXPositionInstance other_tree_position_ancestor =
        other_normalized_tree_position->CreateAncestorPosition(
            common_anchor, ax::mojom::MoveDirection::kBackward);
    DCHECK(this_tree_position_ancestor->IsTreePosition());
    DCHECK(other_tree_position_ancestor->IsTreePosition());
    return this_tree_position_ancestor->child_index_ -
           other_tree_position_ancestor->child_index_;
  }

  // A valid position can become invalid if the underlying tree structure
  // changes. This is expected behavior, but it is sometimes necessary to
  // maintain valid positions. This method modifies an invalid position that is
  // beyond MaxTextOffset to snap to MaxTextOffset.
  void SnapToMaxTextOffsetIfBeyond() {
    int max_text_offset = MaxTextOffset();
    if (text_offset_ > max_text_offset)
      text_offset_ = max_text_offset;
  }

  bool IsInEmptyObject() const {
    if (IsNullPosition())
      return false;

    return IsEmptyObject(*GetAnchor());
  }

  bool IsInUnignoredEmptyObject() const {
    return GetAnchor() && !GetAnchor()->IsIgnored() && IsInEmptyObject();
  }

  // Returns whether the position is anchored in an unignored and empty object,
  // has an author specified name that is not empty, and it is not anchored in
  // an image. This is because in UIA we want to expose embedded object
  // characters for image elements, even if they have an author specified name.
  // Only used for UIA.
  bool EmptyObjectShouldProvideNameFromAttribute() const {
    DCHECK(IsInUnignoredEmptyObject());
    return GetAnchor()->GetNameFrom() == ax::mojom::NameFrom::kAttribute &&
           !IsImage(GetAnchor()->GetRole()) &&
           !GetAnchor()->GetNameUTF16().empty();
  }

  AXNode* GetEmptyObjectAncestorNode() const {
    if (!GetAnchor())
      return nullptr;

    if (!GetAnchor()->IsIgnored()) {
      // The only cases where a descendant of an empty object can be unignored
      // is when on Windows we are inside of a collapsed popup button which is
      // the parent of a menu list popup, or on all platforms inside a generic
      // container that is the child of an empty text field.
      if (AXNode* popup_button =
              GetAnchor()->GetCollapsedMenuListSelectAncestor()) {
        return popup_button;
      }

      if (GetAnchorRole() == ax::mojom::Role::kGenericContainer &&
          !AnchorUnignoredChildCount()) {
        return GetAnchor()->GetTextFieldAncestor();
      }

      return nullptr;
    }

    // The first unignored ancestor is necessarily the empty object if this node
    // is the descendant of an empty object.
    AXNode* ancestor_node = GetLowestUnignoredAncestor();
    if (!ancestor_node)
      return nullptr;

    AXPositionInstance position =
        CreateTextPosition(*ancestor_node, 0 /* text_offset */,
                           ax::mojom::TextAffinity::kDownstream);
    if (position->IsInUnignoredEmptyObject())
      return ancestor_node;

    return nullptr;
  }

  void swap(AXPosition& other) {
    std::swap(kind_, other.kind_);
    std::swap(tree_id_, other.tree_id_);
    std::swap(anchor_id_, other.anchor_id_);
    std::swap(child_index_, other.child_index_);
    std::swap(text_offset_, other.text_offset_);
    std::swap(affinity_, other.affinity_);
    // We explicitly don't swap any cached members.
    name_ = std::u16string();
    other.name_ = std::u16string();
  }

  // Returns the text (in UTF16 format) that is present inside the anchor node,
  // including any text found in descendant text nodes, based on the platform's
  // text representation. Some platforms use an embedded object replacement
  // character that replaces the text coming from most child nodes and empty
  // objects.
  std::u16string GetText(
      const AXEmbeddedObjectBehavior embedded_object_behavior =
          g_ax_embedded_object_behavior) const {
    if (IsNullPosition()) {
      return std::u16string();
    }

    static const base::NoDestructor<std::u16string> embedded_character_str(
        AXNode::kEmbeddedObjectCharacterUTF16);
    switch (embedded_object_behavior) {
      case AXEmbeddedObjectBehavior::kSuppressCharacter:
        return GetAnchor()->GetTextContentUTF16();
      case AXEmbeddedObjectBehavior::kExposeCharacterForHypertext:
        // Special case, if a position's anchor node has only ignored
        // descendants, i.e., it appears to be empty to assistive software, on
        // some platforms we need to still treat it as a character and a word
        // boundary. We achieve this by adding an embedded object character in
        // the text representation used by this class, but we don't expose that
        // character to assistive software that tries to retrieve the node's
        // text content.
        if (IsInUnignoredEmptyObject()) {
          return *embedded_character_str;
        }
        return GetAnchor()->GetHypertext();
      case AXEmbeddedObjectBehavior::kUIAExposeCharacterForTextContent:
        // For UIA, we still have the notion of embedded object characters for
        // text navigation purposes. I.e. when AT's need to navigate around
        // nodes and elements which are empty and should then be exposed as
        // embedded object characters.
        //
        // According to the spec, we should favor author supplied names over
        // names from content. However, trying to fulfill this in every case
        // leads to bugs in the UIA implementation in the TextRangeProvider
        // since we create leaf text positions, which means that they will
        // always have name from content. As such, for now we are
        // implementing this special case where we will only return the author
        // specified name if NameFrom is kAttribute and the name is not empty.
        // Even though a case like:
        // <button aria-label="label">hello</button>
        // Should have its name exposed as "label" according to the spec
        // but we will expose "hello" instead.
        // Exposing the aria label here would make us expose text that isn't on
        // a leaf position, and throughout our UIA implementation, we always
        // assume and expect to be on leaf positions. Exposing the label
        // when it has text from content would effectively hide the subtree
        // from UIA ATs
        // https://www.w3.org/TR/accname-1.1/#mapping_additional_nd_te

        if (IsInUnignoredEmptyObject()) {
          if (EmptyObjectShouldProvideNameFromAttribute()) {
            return GetAnchor()->GetNameUTF16();
          }
          return *embedded_character_str;
        }
        // However, for UIA, we don't want to expose the Hypertext like the
        // kExposeCharacterForHypertext case does, since that computation for
        // Hypertext is IA2-specific. Instead, UIA needs the text contents of
        // the node, which is what GetTextContentUTF16() returns.
        return GetAnchor()->GetTextContentUTF16();
    }
  }

  // Determines if this position is pointing to text inside a node that causes a
  // line break. For example, a tree position pointing to a <br> element or a
  // text node whose only content is the
  // '\n' character, or a text position pointing to a '\n' character in its
  // anchor's text representation.
  bool IsPointingToLineBreak() const {
    if (IsNullPosition())
      return false;

    // The position might be an ancestor position that does not currently point
    // to a line break node, but once resolved to a leaf position, it might do
    // so. This could only occur when we have a text position, because tree
    // positions do not point to text unless they are anchored directly to a
    // text node.
    if (IsTextPosition()) {
      AXPositionInstance leaf_text_position = AsLeafTextPosition();
      DCHECK(leaf_text_position->GetAnchor());
      if (leaf_text_position->GetAnchor()->IsLineBreak())
        return true;
      std::u16string text = leaf_text_position->GetText();
      if (text.empty() ||
          static_cast<size_t>(leaf_text_position->text_offset()) >=
              text.length()) {
        return false;
      }
      return text[leaf_text_position->text_offset()] == '\n';
    }

    // Tree position.
    return GetAnchor()->IsLineBreak();
  }

  // Determines if the anchor containing this position is a text object.
  bool IsInTextObject() const {
    if (IsNullPosition())
      return false;
    return GetAnchor()->IsText();
  }

  // Determines if the anchor containing this position is a text field object.
  bool IsInTextField() const {
    if (IsNullPosition()) {
      return false;
    }
    return GetAnchor()->data().IsTextField();
  }

  // Determines if the text representation of this position's anchor contains
  // only whitespace characters; <br> objects span a single '\n' character, so
  // positions inside line breaks are also considered "in whitespace". Note that
  // by the above definition, if a position is pointing to a whitespace
  // character, but not all of the text inside the position's anchor is
  // whitespace, this method returns false.
  bool IsInWhiteSpace() const {
    if (IsNullPosition())
      return false;
    if (GetAnchor()->IsLineBreak())
      return true;  // A <br> or a text node whose contents is a single '\n'.
    std::u16string text = GetText();
    // `base::ContainsOnlyChars` returns true if the text is empty, which is not
    // what we want here because the empty text is not the same as text with
    // only whitespace characters. So, we explicitly exclude that possibility.
    return !text.empty() &&
           base::ContainsOnlyChars(text, base::kWhitespaceUTF16);
  }

  // Returns the length of the text that is present inside the anchor node,
  // including any text found in descendant text nodes. This is based on the
  // platform's text representation. Some platforms use an embedded object
  // character that replaces the text coming from most child nodes and empty
  // objects.
  //
  // Similar to "text_offset_", the length of the text is in UTF16 code units,
  // not in grapheme clusters.
  int MaxTextOffset(const AXEmbeddedObjectBehavior embedded_object_behavior =
                        g_ax_embedded_object_behavior) const {
    if (IsNullPosition())
      return INVALID_OFFSET;

    switch (embedded_object_behavior) {
      case AXEmbeddedObjectBehavior::kSuppressCharacter:
        // TODO(nektar): Switch to anchor->GetTextContentLengthUTF8() after
        // AXPosition switches to using UTF8.
        return GetAnchor()->GetTextContentLengthUTF16();
      case AXEmbeddedObjectBehavior::kExposeCharacterForHypertext:
        // Special case: If a node has only ignored descendants, i.e., it
        // appears to be empty to assistive software, on some platforms we need
        // to still treat it as a character and a word boundary. We achieve this
        // by adding an "object replacement character" in the accessibility
        // tree's text representation, but we don't expose that character to
        // assistive software that tries to retrieve the node's text content or
        // hypertext.
        if (IsInUnignoredEmptyObject())
          return AXNode::kEmbeddedObjectCharacterLengthUTF16;
        return static_cast<int>(GetAnchor()->GetHypertext().length());
      case AXEmbeddedObjectBehavior::kUIAExposeCharacterForTextContent:
        // For UIA, we still have the notion of embedded object characters for
        // text navigation purposes. I.e. when AT's need to navigate around
        // nodes and elements which are empty and should then be exposed as
        // embedded object characters, and as such we need to return the length
        // of the embedded object character when calculating the `MaxTextOffset`
        // for these nodes.
        //
        // According to the spec, we should favor author supplied names over
        // names from content. However, trying to fulfill this in every case
        // leads to bugs in the UIA implementation in the TextRangeProvider
        // since we create leaf text positions, which means that they will
        // always have name from content. As such, for now we are
        // implementing this special case where we will only return the author
        // specified name if NameFrom is kAttribute and the name is not empty.
        if (IsInUnignoredEmptyObject()) {
          if (EmptyObjectShouldProvideNameFromAttribute()) {
            return (int)GetAnchor()->GetNameUTF16().length();
          }
          return AXNode::kEmbeddedObjectCharacterLengthUTF16;
        }
        // However, for UIA, we don't want to expose the Hypertext like the
        // kExposeCharacterForHypertext case does, since that computation for
        // Hypertext is IA2-specific. Instead, UIA needs the text contents of
        // the node, so for `MaxTextOffset()` we should return the length of the
        // text content.
        return GetAnchor()->GetTextContentLengthUTF16();
    }
  }

  // Returns the accessibility role of this position's anchor node. If this is a
  // "null position", returns `ax::mojom::Role::kUnknown`.
  ax::mojom::Role GetRole() const {
    if (IsNullPosition())
      return ax::mojom::Role::kUnknown;
    return GetAnchor()->GetRole();
  }

  AXTextAttributes GetTextAttributes() const {
    // Check either the current anchor or its parent for text attributes.
    AXTextAttributes current_anchor_text_attributes =
        !IsNullPosition() ? GetAnchor()->GetTextAttributes()
                          : AXTextAttributes();
    if (current_anchor_text_attributes.IsUnset()) {
      AXPositionInstance parent_position =
          AsTreePosition()->CreateParentPosition(
              ax::mojom::MoveDirection::kBackward);
      if (!parent_position->IsNullPosition()) {
        return parent_position->GetAnchor()->GetTextAttributes();
      }
    }
    return current_anchor_text_attributes;
  }

 protected:
  AXPosition()
      : tree_id_(AXTreeIDUnknown()),
        anchor_id_(kInvalidAXNodeID),
        child_index_(INVALID_INDEX),
        text_offset_(INVALID_OFFSET) {}

  // We explicitly don't copy any cached members.
  AXPosition(const AXPosition& other)
      : kind_(other.kind_),
        tree_id_(other.tree_id_),
        anchor_id_(other.anchor_id_),
        child_index_(other.child_index_),
        text_offset_(other.text_offset_),
        affinity_(other.affinity_) {}

  // Returns the character offset inside our anchor's parent at which our text
  // starts.
  int AnchorTextOffsetInParent() const {
    if (IsNullPosition())
      return INVALID_OFFSET;

    // Calculate how much text there is to the left of this anchor.
    //
    // Work with a tree position so as not to incur any performance hit for
    // calculating the corresponding text offset in the parent anchor on
    // platforms that do not use an "object replacement character" to represent
    // child nodes.
    //
    // Ignored positions are not visible to platform APIs. As a result, their
    // text content or hypertext does not appear in their parent node, but the
    // text of their unignored children does. (See `AXNode::GetHypertext()` for
    // the meaning of "hypertext" in this context.
    AXPositionInstance tree_position =
        CreatePositionAtStartOfAnchor()->AsTreePosition();
    DCHECK(!tree_position->IsNullPosition());
    AXPositionInstance parent_position = tree_position->CreateParentPosition(
        ax::mojom::MoveDirection::kBackward);
    if (parent_position->IsNullPosition())
      return 0;  // There is only a single root node.

    int offset_in_parent = 0;
    for (int i = 0; i < parent_position->child_index(); ++i) {
      AXPositionInstance child = parent_position->CreateChildPositionAt(i);
      DCHECK(!child->IsNullPosition());
      offset_in_parent += child->MaxTextOffsetInParent();
    }
    return offset_in_parent;
  }

  // In the case of a text position, lazily initializes or returns the existing
  // grapheme iterator for the position's text. The grapheme iterator breaks at
  // every grapheme cluster boundary.
  //
  // We only allow creating this iterator on leaf nodes. We currently don't need
  // to move by grapheme boundaries on non-leaf nodes and computing plus caching
  // the text content for all nodes is costly.
  std::unique_ptr<base::i18n::BreakIterator> GetGraphemeIterator() const {
    if (!IsLeafTextPosition())
      return {};

    // TODO(nektar): Remove member variable `name_` once hypertext has been
    // migrated to AXNode. Currently, hypertext in AXNode gets updated every
    // time the `AXNode::GetHypertext()` method is called which erroniously
    // invalidates this AXPosition.
    name_ = GetText();
    auto grapheme_iterator = std::make_unique<base::i18n::BreakIterator>(
        name_, base::i18n::BreakIterator::BREAK_CHARACTER);
    if (!grapheme_iterator->Init())
      return {};
    return grapheme_iterator;
  }

  void InitializeWithoutValidation(AXPositionKind kind,
                                   AXTreeID tree_id,
                                   AXNodeID anchor_id,
                                   int child_index,
                                   int text_offset,
                                   ax::mojom::TextAffinity affinity) {
    kind_ = kind;
    tree_id_ = tree_id;
    anchor_id_ = anchor_id;
    child_index_ = child_index;
    text_offset_ = text_offset;
    affinity_ = affinity;

    if (!IsValid()) {
      // Reset to the null position.
      kind_ = AXPositionKind::NULL_POSITION;
      tree_id_ = AXTreeIDUnknown();
      anchor_id_ = kInvalidAXNodeID;
      child_index_ = INVALID_INDEX;
      text_offset_ = INVALID_OFFSET;
      affinity_ = ax::mojom::TextAffinity::kDownstream;
    }
  }

  void Initialize(AXPositionKind kind,
                  AXTreeID tree_id,
                  AXNodeID anchor_id,
                  int child_index,
                  int text_offset,
                  ax::mojom::TextAffinity affinity) {
    kind_ = kind;
    tree_id_ = tree_id;
    anchor_id_ = anchor_id;
    child_index_ = child_index;
    text_offset_ = text_offset;
    affinity_ = affinity;

    // TODO(accessibility) Consider using WeakPtr<AXTree> instead of an
    // AXTreeID, which would be both faster and easier to use in combination
    // with AXTreeSnapshotter, which does not use AXTreeManager to cache
    // AXTreeIDs in a map.
    SANITIZER_CHECK(GetManager() || kind_ == AXPositionKind::NULL_POSITION)
        << "Tree manager required, tree_id = " << tree_id.ToString()
        << "  is unknown = " << (tree_id == AXTreeIDUnknown());
    SANITIZER_CHECK(GetAnchor() || kind_ == AXPositionKind::NULL_POSITION)
        << "Creating a position without an anchor is disallowed:\n"
        << ToDebugString();

    // TODO(crbug.com/40885940) Remove this line and let the below IsValid()
    // assertion get triggered instead. We shouldn't be creating test positions
    // with offsets that are too large. This seems to occur when the anchor node
    // is ignored, and leads to a number of failing tests.
    // Comment this line out as a known performance culprit (also see
    // crbug.com/1401591).
    // SnapToMaxTextOffsetIfBeyond();

#if defined(AX_EXTRA_MAC_NODES)
    // Temporary hack to constrain child index when extra mac nodes are present.
    // TODO(accessibility) Remove this hack that works around the fact that Mac
    // can set a selection on extra mac nodes, which looks invalid because the
    // child index is larger than AnchorChildCount(), which does not account
    // for them. We need to get a child count that includes extra mac nodes,
    // similar to how BrowserAccessibility::PlatformChildCount() does.
    if (!IsValid() && IsTreePosition() && IsTableLike(GetAnchor()->GetRole()) &&
        child_index > AnchorChildCount()) {
      child_index_ = AnchorChildCount();
    }
#endif

    // TODO(crbug.com/40885940) see TODO above.
    // Also look for the failures in
    // AXPositionTest.AsLeafTextPositionBeforeCharacterIncludingGeneratedNewlines,
    // AXPlatformNodeTextRangeProviderTest.TestNormalizeTextRangeForceSameAnchorOnDegenerateRange.
    // SANITIZER_CHECK(IsValid()) << "Creating invalid positions is
    // disallowed:\n"
    //                            << ToDebugString();
  }

  int AnchorChildCount() const {
    if (!GetAnchor())
      return 0;
    return static_cast<int>(GetAnchor()->GetChildCountCrossingTreeBoundary());
  }

  // When a child is ignored, it looks for unignored nodes of that child's
  // children until there are no more descendants.
  //
  // For example:
  // ++TextField
  // ++++GenericContainer ignored
  // ++++++StaticText "Hello"
  // When we call the following method on TextField, it would return 1.
  int AnchorUnignoredChildCount() const {
    if (!GetAnchor())
      return 0;
    return static_cast<int>(
        GetAnchor()->GetUnignoredChildCountCrossingTreeBoundary());
  }

  int AnchorIndexInParent() const {
    // If this is the root tree, the index in parent will be 0.
    return GetAnchor() ? static_cast<int>(GetAnchor()->GetIndexInParent())
                       : INVALID_INDEX;
  }

  base::stack<AXNode*> GetAncestorAnchors() const {
    if (!GetAnchor())
      return base::stack<AXNode*>();
    return GetAnchor()->GetAncestorsCrossingTreeBoundaryAsStack();
  }

  AXNode* GetLowestUnignoredAncestor() const {
    if (!GetAnchor())
      return nullptr;
    return GetAnchor()->GetLowestPlatformAncestor();
  }

  // Returns the length of text (in UTF16 code points) that this anchor node
  // takes up in its parent.
  //
  // On some platforms, embedded objects are represented in their parent with a
  // single "embedded object character".
  int MaxTextOffsetInParent() const {
    if (IsNullPosition())
      return 0;

    // Ignored anchors are not visible to platform APIs. As a result, their
    // text content or hypertext does not appear in their parent node, but the
    // text of their unignored children does, if any. (See
    // `AXNode::GetHypertext()` for the meaning of "hypertext" in this context.
    if (!GetAnchor()->IsIgnored()) {
      if (IsEmbeddedObjectInParent())
        return AXNode::kEmbeddedObjectCharacterLengthUTF16;
    } else {
      // Ignored leaf (text) nodes might contain text content or hypertext, but
      // it should not be exposed in their parent.
      if (!AnchorUnignoredChildCount())
        return 0;
    }
    return MaxTextOffset();
  }

  // Returns whether or not this anchor is represented in their parent with a
  // single "object replacement character".
  bool IsEmbeddedObjectInParent() const {
    switch (g_ax_embedded_object_behavior) {
      case AXEmbeddedObjectBehavior::kSuppressCharacter:
        return false;
      case AXEmbeddedObjectBehavior::kExposeCharacterForHypertext:
        // We expose an "object replacement character" for all nodes except:
        // A) Textual nodes, such as static text, inline text boxes and line
        // breaks, and B) Nodes that are invisible to platform APIs.
        //
        // In the first case, textual nodes cannot be represented by an "object
        // replacement character" in the hypertext of their unignored parents,
        // because we want to maintain compatibility with how Firefox exposes
        // text in IAccessibleText. In the second case, ignored nodes and nodes
        // that are descendants of platform leaves should maintain the actual
        // text of all their static text descendants, otherwise there would be
        // loss of information while traversing the accessibility tree upwards.
        // An example of a platform leaf is an <input> text field, because all
        // of the accessibility subtree inside the text field is hidden from
        // platform APIs. An example of how an ignored node can affect the
        // hypertext of an unignored ancestor is shown below:
        // ++kTextField "Hello"
        // ++++kGenericContainer ignored "Hello"
        // ++++++kStaticText "Hello"
        // ++++++++kInlineTextBox "Hello"
        // The generic container, even though it is ignored, should nevertheless
        // maintain the text of its static text child and not use an "object
        // replacement character". Otherwise, the value of the text field would
        // be wrong.
        //
        // Please note that there is one more method that controls whether an
        // "object replacement character" would be exposed. See
        // `AXPosition::IsInUnignoredEmptyObject()`.
        return !IsNullPosition() && !GetAnchor()->IsIgnored() &&
               !GetAnchor()->IsText() && !GetAnchor()->IsChildOfLeaf();
      case AXEmbeddedObjectBehavior::kUIAExposeCharacterForTextContent:
        return !IsNullPosition() && !GetAnchor()->IsIgnored() &&
               GetAnchor()->IsLeaf() && IsInUnignoredEmptyObject();
    }
  }

  // Determines if the anchor containing this position produces a hard line
  // break in the text representation, e.g. the anchor is a block level element
  // or a <br>.
  bool IsInLineBreakingObject() const {
    if (IsNullPosition())
      return false;
    return GetAnchor()->GetBoolAttribute(
        ax::mojom::BoolAttribute::kIsLineBreakingObject);
  }

  ax::mojom::Role GetAnchorRole() const {
    if (IsNullPosition())
      return ax::mojom::Role::kUnknown;
    return GetRole(GetAnchor());
  }

  ax::mojom::Role GetRole(AXNode* node) const { return node->GetRole(); }

  const std::vector<int32_t>& GetWordStartOffsets() const {
    if (IsNullPosition()) {
      static const base::NoDestructor<std::vector<int32_t>> empty_word_starts;
      return *empty_word_starts;
    }
    DCHECK(GetAnchor());

    // An embedded object replacement character is exposed in a node's text
    // representation when a control, such as a text field, is empty. Since the
    // control has no text, no word start offsets are present in the
    // `ax::mojom::IntListAttribute::kWordStarts` attribute, so we need to
    // special case them here.
    //
    // For the kUIAExposeCharacterForHypertext case, we only want to return a
    // vector with {0} if the empty object does not have an author specified
    // name that we are exposing.
    if (IsInUnignoredEmptyObject() &&
        (g_ax_embedded_object_behavior ==
             AXEmbeddedObjectBehavior::kExposeCharacterForHypertext ||
         (g_ax_embedded_object_behavior ==
              AXEmbeddedObjectBehavior::kUIAExposeCharacterForTextContent &&
          !EmptyObjectShouldProvideNameFromAttribute()))) {
      // Using braces ensures that the vector will contain the given value, and
      // not create a vector of size 0.
      static const base::NoDestructor<std::vector<int32_t>>
          embedded_word_starts{{0}};
      return *embedded_word_starts;
    }

    return GetAnchor()->GetIntListAttribute(
        ax::mojom::IntListAttribute::kWordStarts);
  }

  const std::vector<int32_t>& GetWordEndOffsets() const {
    if (IsNullPosition()) {
      static const base::NoDestructor<std::vector<int32_t>> empty_word_ends;
      return *empty_word_ends;
    }
    DCHECK(GetAnchor());

    // An embedded object replacement character is exposed in a node's text
    // representation when a control, such as a text field, is empty. Since the
    // control has no text, no word end offsets are present in the
    // `ax::mojom::IntListAttribute::kWordEnds` attribute, so we need to special
    // case them here.
    //
    // Since the whole text exposed inside of an embedded object is of
    // length 1 (the embedded object replacement character), the word end offset
    // is positioned at 1. Because we want to treat embedded object replacement
    // characters as ordinary characters, it wouldn't be consistent to assume
    // they have no length and return 0 instead of 1.
    if (IsInUnignoredEmptyObject() &&
        (g_ax_embedded_object_behavior ==
             AXEmbeddedObjectBehavior::kExposeCharacterForHypertext ||
         (g_ax_embedded_object_behavior ==
              AXEmbeddedObjectBehavior::kUIAExposeCharacterForTextContent &&
          !EmptyObjectShouldProvideNameFromAttribute()))) {
      // Using braces ensures that the vector will contain the given value, and
      // not create a vector of size 1.
      static const base::NoDestructor<std::vector<int32_t>> embedded_word_ends{
          {1}};
      return *embedded_word_ends;
    }

    return GetAnchor()->GetIntListAttribute(
        ax::mojom::IntListAttribute::kWordEnds);
  }

  const std::vector<int32_t>& GetFormatStartOffsets() const {
    if (IsNullPosition()) {
      static const base::NoDestructor<std::vector<int32_t>> empty_format_starts;
      return *empty_format_starts;
    }
    DCHECK(GetAnchor());

    std::vector<int32_t> format_starts;
    format_starts.push_back(0);

    // Format is almost always consistent throughout any node -- the only
    // exception are inline text boxes with CSS highlights. Therefore, unless
    // the node is an inline text box with CSS highlights, we can assume the
    // node's format starts only at index 0.
    if (GetAnchor()->GetRole() != ax::mojom::Role::kInlineTextBox) {
      static const base::NoDestructor<std::vector<int32_t>> format_starts_copy(
          std::move(format_starts));
      return *format_starts_copy;
    }

    AXNode* parent = GetAnchor()->GetUnignoredParent();

    const std::vector<int32_t>& marker_types =
        parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes);
    const std::vector<int32_t>& highlight_types = parent->GetIntListAttribute(
        ax::mojom::IntListAttribute::kHighlightTypes);

    // Since, there are no highlights, there is no possibility of any spelling
    // or grammar highlights.
    if (highlight_types.empty()) {
      static const base::NoDestructor<std::vector<int32_t>> format_starts_copy(
          std::move(format_starts));
      return *format_starts_copy;
    }
    CHECK_EQ(marker_types.size(), highlight_types.size());

    const std::vector<int>& marker_starts =
        parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts);
    const std::vector<int>& marker_ends =
        parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds);

    CHECK_EQ(marker_types.size(), marker_starts.size());
    CHECK_EQ(marker_types.size(), marker_ends.size());

    int text_length = GetAnchor()->GetTextContentLengthUTF16();
    for (size_t i = 0; i < marker_types.size(); ++i) {
      if (HasSpellingOrGrammarErrorHighlight(
              static_cast<ax::mojom::MarkerType>(marker_types[i]),
              static_cast<ax::mojom::HighlightType>(highlight_types[i]))) {
        if (marker_starts[i] != 0) {  // 0 is already added
          format_starts.push_back(marker_starts[i]);
        }
        if (marker_ends[i] < text_length - 1) {
          format_starts.push_back(marker_ends[i]);
        }
      }
    }

    static const base::NoDestructor<std::vector<int32_t>> format_starts_copy(
        std::move(format_starts));
    return *format_starts_copy;
  }

  const std::vector<int32_t>& GetFormatEndOffsets() const {
    if (IsNullPosition()) {
      static const base::NoDestructor<std::vector<int32_t>> empty_format_ends;
      return *empty_format_ends;
    }
    DCHECK(GetAnchor());

    int text_length = GetAnchor()->GetTextContentLengthUTF16();
    std::vector<int32_t> format_ends;
    format_ends.push_back(text_length);

    // Format is almost always consistent throughout any node -- the only
    // exception are inline text boxes with CSS highlights. Therefore, unless
    // the node is an inline text box with CSS highlights, we can assume the
    // node's format ends only at the text length.
    if (GetAnchor()->GetRole() != ax::mojom::Role::kInlineTextBox) {
      static const base::NoDestructor<std::vector<int32_t>> format_ends_copy(
          std::move(format_ends));
      return *format_ends_copy;
    }

    AXNode* parent = GetAnchor()->GetUnignoredParent();

    const std::vector<int32_t>& marker_types =
        parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes);
    const std::vector<int32_t>& highlight_types = parent->GetIntListAttribute(
        ax::mojom::IntListAttribute::kHighlightTypes);

    // Since, there are no highlights, there is no possibility of any spelling
    // or grammar highlights.
    if (highlight_types.empty()) {
      static const base::NoDestructor<std::vector<int32_t>> format_ends_copy(
          std::move(format_ends));
      return *format_ends_copy;
    }
    CHECK_EQ(marker_types.size(), highlight_types.size());

    const std::vector<int>& marker_starts =
        parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts);
    const std::vector<int>& marker_ends =
        parent->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds);

    CHECK_EQ(marker_types.size(), marker_starts.size());
    CHECK_EQ(marker_types.size(), marker_ends.size());

    format_ends.clear();
    for (size_t i = 0; i < marker_types.size(); ++i) {
      if (HasSpellingOrGrammarErrorHighlight(
              static_cast<ax::mojom::MarkerType>(marker_types[i]),
              static_cast<ax::mojom::HighlightType>(highlight_types[i]))) {
        if (marker_starts[i] > 0) {
          format_ends.push_back(marker_starts[i]);
        }
        format_ends.push_back(marker_ends[i]);
      }
    }

    if (format_ends.empty() || format_ends.back() != text_length) {
      format_ends.push_back(text_length);
    }

    static const base::NoDestructor<std::vector<int32_t>> format_ends_copy(
        std::move(format_ends));
    return *format_ends_copy;
  }

  static bool HasSpellingOrGrammarErrorHighlight(
      ax::mojom::MarkerType marker_type,
      ax::mojom::HighlightType highlight_type) {
    return marker_type == ax::mojom::MarkerType::kHighlight &&
           (highlight_type == ax::mojom::HighlightType::kSpellingError ||
            highlight_type == ax::mojom::HighlightType::kGrammarError);
  }

  AXNodeID GetNextOnLineID() const {
    if (IsNullPosition())
      return kInvalidAXNodeID;
    DCHECK(GetAnchor());

    if (GetAnchor()->HasIntAttribute(ax::mojom::IntAttribute::kNextOnLineId)) {
      return static_cast<AXNodeID>(
          GetAnchor()->GetIntAttribute(ax::mojom::IntAttribute::kNextOnLineId));
    }
    AXNode* parent = GetAnchor()->GetUnignoredParent();

    if (!parent) {
      return kInvalidAXNodeID;
    }

    // We should not need to bubble up to find the NextOnLine if we are not
    // in an InlineTextBox, because the only cases where the relevant NextOnLine
    // information is stored in the parent is in cases where we have text inside
    // inline-block elements.
    //
    // We only want to bubble up to the parent to find the nextOnLine
    // if we are in a leaf that is a last child.
    // This is because if we have a structure where there are multiple
    // InlineTextBox children that are in different lines, and the parent's
    // NextOnLine only applies to the last child.
    if (GetAnchor()->GetRole() != ax::mojom::Role::kInlineTextBox ||
        parent->GetLastUnignoredChild() != GetAnchor()) {
      return kInvalidAXNodeID;
    }

    while (parent &&
           !parent->HasIntAttribute(ax::mojom::IntAttribute::kNextOnLineId)) {
      parent = parent->GetUnignoredParent();
    }

    if (parent) {
      return static_cast<AXNodeID>(
          parent->GetIntAttribute(ax::mojom::IntAttribute::kNextOnLineId));
    }
    return kInvalidAXNodeID;
  }

  AXNodeID GetPreviousOnLineID() const {
    if (IsNullPosition())
      return kInvalidAXNodeID;
    DCHECK(GetAnchor());

    if (GetAnchor()->HasIntAttribute(
            ax::mojom::IntAttribute::kPreviousOnLineId)) {
      return static_cast<AXNodeID>(GetAnchor()->GetIntAttribute(
          ax::mojom::IntAttribute::kPreviousOnLineId));
    }
    AXNode* parent = GetAnchor()->GetUnignoredParent();

    if (!parent) {
      return kInvalidAXNodeID;
    }

    // We should not need to bubble up to find the PreviousOnLine if we are not
    // in an InlineTextBox, because the only cases where the relevant
    // PreviousOnLine information is stored in the parent is in cases where we
    // have text inside inline-block elements.
    //
    // We have some expectations that
    // line break elements are not expected to have a previous on line element.
    //
    // We only want to bubble up to the parent to find the previousOnLine
    // if we are in a leaf that is a first child.
    // This is because if we have a structure where there are multiple
    // InlineTextBox children that are in different lines, and the parent's
    // PreviousOnLine only applies to the first child.
    if (GetAnchor()->GetRole() != ax::mojom::Role::kInlineTextBox ||
        parent->GetRole() == ax::mojom::Role::kLineBreak ||
        parent->GetFirstUnignoredChild() != GetAnchor()) {
      return kInvalidAXNodeID;
    }

    while (parent && !parent->HasIntAttribute(
                         ax::mojom::IntAttribute::kPreviousOnLineId)) {
      parent = parent->GetUnignoredParent();
    }

    if (parent) {
      return static_cast<AXNodeID>(
          parent->GetIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId));
    }
    return kInvalidAXNodeID;
  }

 private:
  // Defines the relationship between positions during traversal.
  // For example, moving from a descendant to an ancestor, is a kAncestor move.
  enum class AXMoveType {
    kAncestor,
    kDescendant,
    kSibling,
  };

  // Defines the direction of position movement, either next / previous in tree.
  enum class AXMoveDirection {
    kNextInTree,
    kPreviousInTree,
  };

  // Type of predicate function called during anchor navigation.
  // When the predicate returns |true|, the navigation stops and returns a
  // null position object.
  using AbortMovePredicate =
      base::RepeatingCallback<bool(const AXPosition& move_from,
                                   const AXPosition& move_to,
                                   const AXMoveType type,
                                   const AXMoveDirection direction)>;

  // A text span is defined by a series of inline text boxes that make up a
  // single static text object.
  bool AtEndOfTextSpan() const {
    if (GetAnchorRole() != ax::mojom::Role::kInlineTextBox || !AtEndOfAnchor())
      return false;

    // We are at the end of text span if |this| position has
    // role::kInlineTextBox, the parent of |this| has role::kStaticText, and the
    // anchor node of |this| is the last child of its parent's children.
    const bool is_last_child =
        AnchorIndexInParent() == (GetAnchorSiblingCount() - 1);

    DCHECK(GetAnchor());
    return is_last_child &&
           GetRole(GetAnchor()->GetParentCrossingTreeBoundary()) ==
               ax::mojom::Role::kStaticText;
  }

  // Uses depth-first pre-order traversal.
  AXPositionInstance CreateNextAnchorPosition(
      const AbortMovePredicate& abort_predicate) const {
    if (IsNullPosition())
      return Clone();

    AXPositionInstance current_position = AsTreePosition();
    DCHECK(!current_position->IsNullPosition());

    if (!IsLeaf()) {
      const int child_index = current_position->child_index_;
      if (child_index < current_position->AnchorChildCount()) {
        AXPositionInstance child_position =
            current_position->CreateChildPositionAt(child_index);

        if (abort_predicate.Run(*current_position, *child_position,
                                AXMoveType::kDescendant,
                                AXMoveDirection::kNextInTree)) {
          return CreateNullPosition();
        }
        return child_position;
      }
    }

    AXPositionInstance parent_position =
        current_position->CreateParentPosition();

    // Get the next sibling if it exists, otherwise move up the AXTree to the
    // lowest next sibling of this position's ancestors.
    while (!parent_position->IsNullPosition()) {
      const int index_in_parent = current_position->AnchorIndexInParent();
      if (index_in_parent + 1 < parent_position->AnchorChildCount()) {
        AXPositionInstance next_sibling =
            parent_position->CreateChildPositionAt(index_in_parent + 1);
        DCHECK(!next_sibling->IsNullPosition());

        if (abort_predicate.Run(*current_position, *next_sibling,
                                AXMoveType::kSibling,
                                AXMoveDirection::kNextInTree)) {
          return CreateNullPosition();
        }
        return next_sibling;
      }

      if (abort_predicate.Run(*current_position, *parent_position,
                              AXMoveType::kAncestor,
                              AXMoveDirection::kNextInTree)) {
        return CreateNullPosition();
      }

      current_position = std::move(parent_position);
      parent_position = current_position->CreateParentPosition();
    }
    return CreateNullPosition();
  }

  // Uses depth-first pre-order traversal.
  AXPositionInstance CreatePreviousAnchorPosition(
      const AbortMovePredicate& abort_predicate) const {
    if (IsNullPosition())
      return Clone();

    AXPositionInstance current_position = AsTreePosition();
    DCHECK(!current_position->IsNullPosition());

    AXPositionInstance parent_position =
        current_position->CreateParentPosition();
    if (parent_position->IsNullPosition())
      return parent_position;

    // If there is no previous sibling, or the parent itself is a leaf, move up
    // to the parent. The parent can be a leaf if we start with a tree position
    // that is a descendant of a node that is an empty control represented by an
    // "object replacement character" (see `IsInUnignoredEmptyObject()`).
    const int index_in_parent = current_position->AnchorIndexInParent();
    if (index_in_parent <= 0 || parent_position->IsLeaf()) {
      if (abort_predicate.Run(*current_position, *parent_position,
                              AXMoveType::kAncestor,
                              AXMoveDirection::kPreviousInTree)) {
        return CreateNullPosition();
      }
      return parent_position;
    }

    // Get the previous sibling's deepest last child.
    AXPositionInstance rightmost_leaf =
        parent_position->CreateChildPositionAt(index_in_parent - 1);
    DCHECK(!rightmost_leaf->IsNullPosition());

    if (abort_predicate.Run(*current_position, *rightmost_leaf,
                            AXMoveType::kSibling,
                            AXMoveDirection::kPreviousInTree)) {
      return CreateNullPosition();
    }

    CHECK(!rightmost_leaf->IsNullPosition());
    while (!rightmost_leaf->IsLeaf()) {
      parent_position = std::move(rightmost_leaf);
      rightmost_leaf = parent_position->CreateChildPositionAt(
          parent_position->AnchorChildCount() - 1);
      DCHECK(!rightmost_leaf->IsNullPosition());

      if (abort_predicate.Run(*parent_position, *rightmost_leaf,
                              AXMoveType::kDescendant,
                              AXMoveDirection::kPreviousInTree)) {
        return CreateNullPosition();
      }
      CHECK(!rightmost_leaf->IsNullPosition());
    }
    return rightmost_leaf;
  }

  // Creates a text position using the next leaf node as its anchor.
  // Nearly all of the text in the accessibility tree is contained in leaf
  // nodes, so this method is mostly used to move through text nodes.
  AXPositionInstance CreateNextLeafTextPosition(
      const AbortMovePredicate& abort_predicate) const {
    // If this is an ancestor text position, resolve to its leaf text position.
    if (IsTextPosition() && !IsLeaf())
      return AsLeafTextPosition();

    AXPositionInstance next_leaf = CreateNextAnchorPosition(abort_predicate);
    while (!next_leaf->IsNullPosition() && !next_leaf->IsLeaf())
      next_leaf = next_leaf->CreateNextAnchorPosition(abort_predicate);

    DCHECK(next_leaf);
    return next_leaf->AsLeafTextPosition();
  }

  // Creates a text position using the previous leaf node as its anchor.
  // Nearly all of the text in the accessibility tree is contained in leaf
  // nodes, so this method is mostly used to move through text nodes.
  AXPositionInstance CreatePreviousLeafTextPosition(
      const AbortMovePredicate& abort_predicate) const {
    // If this is an ancestor text position, resolve to its leaf text position.
    if (IsTextPosition() && !IsLeaf())
      return AsLeafTextPosition();

    AXPositionInstance previous_leaf =
        CreatePreviousAnchorPosition(abort_predicate);
    while (!previous_leaf->IsNullPosition() && !previous_leaf->IsLeaf()) {
      previous_leaf =
          previous_leaf->CreatePreviousAnchorPosition(abort_predicate);
    }

    DCHECK(previous_leaf);
    return previous_leaf->AsLeafTextPosition();
  }

  // Creates a tree position using the next leaf node as its anchor.
  // Nearly all of the text in the accessibility tree is contained in leaf
  // nodes, so this method is mostly used to move through text nodes.
  AXPositionInstance CreateNextLeafTreePosition(
      const AbortMovePredicate& abort_predicate) const {
    AXPositionInstance next_leaf =
        AsTreePosition()->CreateNextAnchorPosition(abort_predicate);
    while (!next_leaf->IsNullPosition() && !next_leaf->IsLeaf())
      next_leaf = next_leaf->CreateNextAnchorPosition(abort_predicate);

    DCHECK(next_leaf);
    return next_leaf;
  }

  // Creates a tree position using the previous leaf node as its anchor.
  // Nearly all of the text in the accessibility tree is contained in leaf
  // nodes, so this method is mostly used to move through text nodes.
  AXPositionInstance CreatePreviousLeafTreePosition(
      const AbortMovePredicate& abort_predicate) const {
    AXPositionInstance previous_leaf =
        AsTreePosition()->CreatePreviousAnchorPosition(abort_predicate);
    while (!previous_leaf->IsNullPosition() && !previous_leaf->IsLeaf()) {
      previous_leaf =
          previous_leaf->CreatePreviousAnchorPosition(abort_predicate);
    }

    DCHECK(previous_leaf);
    return previous_leaf;
  }

  //
  // Static helpers for lambda usage.
  //

  static bool AtStartOfPagePredicate(const AXPositionInstance& position) {
    // If a page boundary is ignored, then it should not be exposed to assistive
    // software.
    return !position->IsIgnored() && position->AtStartOfPage();
  }

  static bool AtEndOfPagePredicate(const AXPositionInstance& position) {
    // If a page boundary is ignored, then it should not be exposed to assistive
    // software.
    return !position->IsIgnored() && position->AtEndOfPage();
  }

  static bool AtStartOfParagraphPredicate(const AXPositionInstance& position) {
    // Sometimes, nodes that are used to signify paragraph boundaries are
    // ignored, e.g. <div aria-hidden="true"></div>". We make the design
    // decision to expose such boundaries to assistive software. Their
    // associated ignored nodes are still not exposed. This ensures that
    // navigation keys in text fields, such as Ctrl+Up/Down, will behave the
    // same way as related screen reader commands.
    return position->AtStartOfParagraph();
  }

  static bool AtStartOfParagraphExcludingEmptyParagraphsPredicate(
      const AXPositionInstance& position) {
    // For UI Automation, empty lines after a paragraph should be merged into
    // the preceding paragraph.
    //
    // See
    // https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-uiautomationtextunits#paragraph
    const bool is_empty_paragraph =
        position->IsPointingToLineBreak() ||
        (position->IsInLineBreakingObject() &&
         (position->GetAnchor()->IsEmptyLeaf() || position->GetText().empty()));
    return !is_empty_paragraph && AtStartOfParagraphPredicate(position);
  }

  static bool AtEndOfParagraphPredicate(const AXPositionInstance& position) {
    // Sometimes, nodes that are used to signify paragraph boundaries are
    // ignored, e.g. <div aria-hidden="true"></div>". We make the design
    // decision to expose such boundaries to assistive software. Their
    // associated ignored nodes are still not exposed. This ensures that
    // navigation keys in text fields, such as Ctrl+Up/Down, will behave the
    // same way as related screen reader commands.
    return position->AtEndOfParagraph();
  }

  static bool AtStartOfLinePredicate(const AXPositionInstance& position) {
    // Sometimes, nodes that are used to signify line boundaries are ignored,
    // e.g. <span contenteditable="false"> <br role="presentation"></span> which
    // is used to make a hard line break appear as a soft one. We make the
    // design decision to expose such boundaries to assistive software. Their
    // associated ignored nodes are still not exposed.
    return position->AtStartOfLine();
  }

  static bool AtEndOfLinePredicate(const AXPositionInstance& position) {
    // Sometimes, nodes that are used to signify line boundaries are ignored,
    // e.g. <span contenteditable="false"> <br role="presentation"></span> which
    // is used to make a hard line break appear as a soft one. We make the
    // design decision to expose such boundaries to assistive software. Their
    // associated ignored nodes are still not exposed.
    return position->AtEndOfLine();
  }

  static bool AtStartOfSentencePredicate(const AXPositionInstance& position) {
    // Sentence boundaries should be at specific text offsets that are "visible"
    // to assistive software, hence not ignored. Ignored nodes are often used
    // for additional layout information, such as line and paragraph boundaries.
    // Their text is not currently processed.
    return !position->IsIgnored() && position->AtStartOfSentence();
  }

  static bool AtEndOfSentencePredicate(const AXPositionInstance& position) {
    // Sentence boundaries should be at specific text offsets that are "visible"
    // to assistive software, hence not ignored. Ignored nodes are often used
    // for additional layout information, such as line and paragraph boundaries.
    // Their text is not currently processed.
    return !position->IsIgnored() && position->AtEndOfSentence();
  }

  static bool AtStartOfFormatPredicate(const AXPositionInstance& position) {
    return position->AtStartOfFormat();
  }

  static bool AtEndOfFormatPredicate(const AXPositionInstance& position) {
    return position->AtEndOfFormat();
  }

  static bool AtStartOfWordPredicate(const AXPositionInstance& position) {
    // Word boundaries should be at specific text offsets that are "visible" to
    // assistive software, hence not ignored. Ignored nodes are often used for
    // additional layout information, such as line and paragraph boundaries.
    // Their text is not currently processed.
    return !position->IsIgnored() && position->AtStartOfWord();
  }

  static bool AtEndOfWordPredicate(const AXPositionInstance& position) {
    // Word boundaries should be at specific text offsets that are "visible" to
    // assistive software, hence not ignored. Ignored nodes are often used for
    // additional layout information, such as line and paragraph boundaries.
    // Their text is not currently processed.
    return !position->IsIgnored() && position->AtEndOfWord();
  }

  static bool DefaultAbortMovePredicate(const AXPosition& move_from,
                                        const AXPosition& move_to,
                                        const AXMoveType move_type,
                                        const AXMoveDirection direction) {
    // Default behavior is to never abort.
    return false;
  }

  // AbortMovePredicate function used to detect format boundaries.
  static bool AbortMoveAtFormatBoundary(const AXPosition& move_from,
                                        const AXPosition& move_to,
                                        const AXMoveType move_type,
                                        const AXMoveDirection direction) {
    if (move_from.IsNullPosition() || move_to.IsNullPosition() ||
        move_from.IsInUnignoredEmptyObject() ||
        move_to.IsInUnignoredEmptyObject()) {
      return true;
    }

    // Treat moving into or out of nodes with certain roles as a format break.
    ax::mojom::Role from_role = move_from.GetAnchorRole();
    ax::mojom::Role to_role = move_to.GetAnchorRole();
    if (from_role != to_role) {
      if (IsFormatBoundary(from_role) || IsFormatBoundary(to_role))
        return true;
    }

    // Stop moving when text attributes differ.
    return move_from.AsLeafTreePosition()->GetTextAttributes() !=
           move_to.AsLeafTreePosition()->GetTextAttributes();
  }

  static bool MoveCrossesLineBreakingObject(
      const ax::mojom::TextBoundary paragraph_boundary,
      const AXPosition& move_from,
      const AXPosition& move_to,
      const AXMoveType move_type,
      const AXMoveDirection direction) {
    const AXPosition* proceeding_position = &move_from;
    const AXPosition* trailing_position = &move_to;
    switch (direction) {
      case AXMoveDirection::kNextInTree:
        break;
      case AXMoveDirection::kPreviousInTree:
        std::swap(proceeding_position, trailing_position);
        break;
    }

    switch (paragraph_boundary) {
      case ax::mojom::TextBoundary::kParagraphEnd: {
        const bool trailing_block = trailing_position->IsInLineBreakingObject();
        const bool trailing_line_break =
            trailing_position->IsPointingToLineBreak();
        return trailing_block || trailing_line_break;
      }
      case ax::mojom::TextBoundary::kParagraphStart: {
        // The trailing object does not need to be a block or a line break for
        // it to represent a start of a new paragraph.
        //
        // 1. Preceding block before "world" creates a paragraph start:
        // <div><p>hello</p>world</div>
        // 2. Preceding line break before "world" creates a paragraph start:
        // <div>Hello<br>world</div>
        const bool preceding_block =
            proceeding_position->IsInLineBreakingObject();
        const bool preceding_line_break =
            proceeding_position->IsPointingToLineBreak();
        return preceding_block || preceding_line_break;
      }
      default:
        NOTREACHED();
    }
  }

  // AbortMovePredicate function used to detect paragraph boundaries.
  static bool AbortMoveAtParagraphBoundary(
      const ax::mojom::TextBoundary paragraph_boundary,
      const AXPosition& move_from,
      const AXPosition& move_to,
      const AXMoveType move_type,
      const AXMoveDirection direction) {
    if (move_from.IsNullPosition() || move_to.IsNullPosition() ||
        move_from.IsInUnignoredEmptyObject() ||
        move_to.IsInUnignoredEmptyObject()) {
      // We deliberately put empty objects, such as empty text fields, in their
      // own paragraph for easier navigation. Otherwise, they could easily be
      // missed by screen reader users.
      return true;
    }

    return MoveCrossesLineBreakingObject(paragraph_boundary, move_from, move_to,
                                         move_type, direction);
  }

  // AbortMovePredicate function used to detect page boundaries.
  //
  // Depending on the type of content, it might be separated into a number of
  // pages. For example, a PDF document may expose multiple pages.
  static bool AbortMoveAtPageBoundary(const AXPosition& move_from,
                                      const AXPosition& move_to,
                                      const AXMoveType move_type,
                                      const AXMoveDirection direction) {
    if (move_from.IsNullPosition() || move_to.IsNullPosition())
      return true;

    const bool move_from_break = move_from.GetAnchor()->GetBoolAttribute(
        ax::mojom::BoolAttribute::kIsPageBreakingObject);
    const bool move_to_break = move_to.GetAnchor()->GetBoolAttribute(
        ax::mojom::BoolAttribute::kIsPageBreakingObject);

    switch (move_type) {
      case AXMoveType::kAncestor:
        // For Ancestor moves, only abort when exiting a page break.
        // We don't care if the ancestor is a page break or not, since the
        // descendant is contained by it.
        return move_from_break;
      case AXMoveType::kDescendant:
        // For Descendant moves, only abort when entering a page break
        // descendant. We don't care if the ancestor is a page break  or not,
        // since the descendant is contained by it.
        return move_to_break;
      case AXMoveType::kSibling:
        // For Sibling moves, abort if both of the siblings are a page break,
        // because that would mean exiting and/or entering a page break.
        return move_from_break && move_to_break;
    }
  }

  // AbortMovePredicate function used to detect crossing through the boundaries
  // of a window-like container, such as a webpage, a PDF, a dialog, the
  // browser's UI (AKA Views), or the whole desktop. Window-like containers
  // that are ignored should not cause us to abort. For example, a hidden dialog
  // should not cause a break.
  static bool AbortMoveAtRootBoundary(const AXPosition& move_from,
                                      const AXPosition& move_to,
                                      const AXMoveType move_type,
                                      const AXMoveDirection direction) {
    // Positions are null when moving past the whole content, therefore the root
    // of a window-like container has certainly been crossed.
    if (move_from.IsNullPosition() || move_to.IsNullPosition())
      return true;

    const ax::mojom::Role move_from_role = move_from.GetAnchorRole();
    const ax::mojom::Role move_to_role = move_to.GetAnchorRole();
    switch (move_type) {
      case AXMoveType::kAncestor:
        // For Ancestor moves, only abort when exiting an unignored window-like
        // container. We don't care if the ancestor is the root of a window-like
        // container or not, since the descendant is contained by it. However,
        // we do care if the ancestor is an iframe because a webpage should be
        // navigated as a single document together with all its iframes,
        // (out-of-process or otherwise).
        return IsRootLike(move_from_role) && !IsIframe(move_to_role) &&
               !move_from.IsIgnored();
      case AXMoveType::kDescendant:
        // For Descendant moves, only abort when entering an unignored
        // window-like container. We don't care if the ancestor is the root of a
        // window-like container or not, since the descendant is contained by
        // it. However, we do care if the ancestor is an iframe because a
        // webpage should be navigated as a single document together with all
        // its iframes, (out-of-process or otherwise).
        return IsRootLike(move_to_role) && !IsIframe(move_from_role) &&
               !move_to.IsIgnored();
      case AXMoveType::kSibling:
        // For Sibling moves, abort if both of the siblings are at the root of
        // unignored window-like containers because that would mean exiting
        // and/or entering a new window-like container. Iframes should not be
        // present in this case because an iframe should never contain more than
        // one kRootWebArea as its immediate child.
        return IsRootLike(move_from_role) && IsRootLike(move_to_role) &&
               !move_from.IsIgnored() && !move_to.IsIgnored();
    }
  }

  static bool AbortMoveAtStartOfInlineBlock(const AXPosition& move_from,
                                            const AXPosition& move_to,
                                            const AXMoveType move_type,
                                            const AXMoveDirection direction) {
    if (move_from.IsNullPosition() || move_to.IsNullPosition())
      return true;

    // These will only be available if AXMode has kHTML set.
    const bool move_from_is_inline_block =
        move_from.GetAnchor()->GetStringAttribute(
            ax::mojom::StringAttribute::kDisplay) == "inline-block";
    const bool move_to_is_inline_block =
        move_to.GetAnchor()->GetStringAttribute(
            ax::mojom::StringAttribute::kDisplay) == "inline-block";

    switch (direction) {
      case AXMoveDirection::kNextInTree:
        // When moving forward, break if we enter an inline block.
        return move_to_is_inline_block &&
               (move_type == AXMoveType::kDescendant ||
                move_type == AXMoveType::kSibling);
      case AXMoveDirection::kPreviousInTree:
        // When moving backward, break if we exit an inline block.
        return move_from_is_inline_block &&
               (move_type == AXMoveType::kAncestor ||
                move_type == AXMoveType::kSibling);
    }
    NOTREACHED();
  }

  static const std::vector<int32_t>& GetSentenceStartOffsetsFunc(
      const AXPositionInstance& position) {
    if (position->IsNullPosition()) {
      static const base::NoDestructor<std::vector<int32_t>>
          empty_sentence_starts;
      return *empty_sentence_starts;
    }
    DCHECK(position->GetAnchor());
    return position->GetAnchor()->GetIntListAttribute(
        ax::mojom::IntListAttribute::kSentenceStarts);
  }

  static const std::vector<int32_t>& GetSentenceEndOffsetsFunc(
      const AXPositionInstance& position) {
    if (position->IsNullPosition()) {
      static const base::NoDestructor<std::vector<int32_t>> empty_sentence_ends;
      return *empty_sentence_ends;
    }
    DCHECK(position->GetAnchor());
    return position->GetAnchor()->GetIntListAttribute(
        ax::mojom::IntListAttribute::kSentenceEnds);
  }

  static const std::vector<int32_t>& GetWordStartOffsetsFunc(
      const AXPositionInstance& position) {
    return position->GetWordStartOffsets();
  }

  static const std::vector<int32_t>& GetWordEndOffsetsFunc(
      const AXPositionInstance& position) {
    return position->GetWordEndOffsets();
  }

  static const std::vector<int32_t>& GetFormatStartOffsetsFunc(
      const AXPositionInstance& position) {
    return position->GetFormatStartOffsets();
  }

  static const std::vector<int32_t>& GetFormatEndOffsetsFunc(
      const AXPositionInstance& position) {
    return position->GetFormatEndOffsets();
  }

  // Creates an ancestor equivalent position at the root node of this position's
  // accessibility tree, e.g. at the root of the current iframe (out-of-process
  // or not), PDF plugin, Views tree, dialog (native, ARIA or HTML), window, or
  // the whole desktop.
  //
  // For a similar method that does not stop at all iframe boundaries, see
  // `CreateRootAncestorPosition`.
  //
  // See `CreateParentPosition` for an explanation of the use of
  // |move_direction|.
  AXPositionInstance CreateAXTreeRootAncestorPosition(
      ax::mojom::MoveDirection move_direction) const {
    if (IsNullPosition())
      return Clone();

    AXPositionInstance root_position = Clone();
    while (!IsRootLike(root_position->GetAnchorRole())) {
      AXPositionInstance parent_position =
          root_position->CreateParentPosition(move_direction);
      if (parent_position->IsNullPosition())
        break;
      root_position = std::move(parent_position);
    }

    return root_position;
  }

  // Creates an ancestor equivalent position at the root node of all content,
  // e.g. at the root of the whole webpage, PDF plugin, Views tree, dialog
  // (native, ARIA or HTML), window, or the whole desktop.
  //
  // Note that this method will break out of an out-of-process iframe and return
  // a position at the root of the top-level document, but it will not break
  // into the Views tree if present. For a similar method that stops at all
  // iframe boundaries, see `CreateAXTreeRootAncestorPosition`.
  //
  // See `CreateParentPosition` for an explanation of the use of
  // |move_direction|.
  AXPositionInstance CreateRootAncestorPosition(
      ax::mojom::MoveDirection move_direction) const {
    AXPositionInstance root_position =
        CreateAXTreeRootAncestorPosition(move_direction);
    AXPositionInstance web_root_position = CreateNullPosition();
    for (; !root_position->IsNullPosition();
         root_position =
             root_position->CreateAXTreeRootAncestorPosition(move_direction)) {
      // An "ax::mojom::Role::kRootWebArea" could also be present at the root of
      // iframes or embedded objects, so we need to check that for that specific
      // role the position is also at the top of the forest of accessibility
      // trees making up the webpage. Note that the forest of accessibility
      // trees would include Views and on Chrome OS the whole desktop, so in the
      // case of a web root, checking if the parent position is the null
      // position will not work.
      if (root_position->GetAnchorRole() != ax::mojom::Role::kRootWebArea) {
        if (web_root_position->IsNullPosition())
          return root_position;  // Original position is not in web contents.

        // The previously saved web root is the shallowest in the forest of
        // accessibility trees.
        return web_root_position;
      }

      // Save this web root position and check if it is the shallowest in the
      // forest of accessibility trees.
      web_root_position = root_position->Clone();
      root_position = root_position->CreateParentPosition(move_direction);
    }
    return web_root_position;
  }

  // Creates a text position that is in the same anchor as the current
  // position, but starting from the current text offset, adjusts to the next
  // or the previous boundary offset depending on the boundary direction. If
  // there is no next / previous offset, the current text offset is unchanged.
  AXPositionInstance CreatePositionAtNextOffsetBoundary(
      ax::mojom::MoveDirection move_direction,
      BoundaryTextOffsetsFunc get_offsets) const {
    if (IsNullPosition() || get_offsets.is_null())
      return Clone();

    AXPositionInstance text_position = AsTextPosition();
    const std::vector<int32_t>& boundary_offsets =
        get_offsets.Run(text_position);
    if (boundary_offsets.empty())
      return text_position;

    switch (move_direction) {
      case ax::mojom::MoveDirection::kNone:
        NOTREACHED();
      case ax::mojom::MoveDirection::kBackward: {
        auto offsets_iterator =
            std::lower_bound(boundary_offsets.begin(), boundary_offsets.end(),
                             int32_t{text_position->text_offset_});
        // If there is no previous offset, the current offset should be
        // unchanged.
        if (offsets_iterator > boundary_offsets.begin()) {
          // Since we already checked if "boundary_offsets" are non-empty, we
          // can safely move the iterator one position back, even if it's
          // currently at the vector's end.
          --offsets_iterator;
          auto offsets_iterator_ref = *offsets_iterator;
          text_position->text_offset_ = static_cast<int>(offsets_iterator_ref);
          text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
        }
        break;
      }
      case ax::mojom::MoveDirection::kForward: {
        const auto offsets_iterator =
            std::upper_bound(boundary_offsets.begin(), boundary_offsets.end(),
                             int32_t{text_position->text_offset_});
        // If there is no next offset, the current offset should be unchanged.
        if (offsets_iterator < boundary_offsets.end()) {
          auto offsets_iterator_ref = *offsets_iterator;
          text_position->text_offset_ = static_cast<int>(offsets_iterator_ref);
          text_position->affinity_ = ax::mojom::TextAffinity::kDownstream;
        }
        break;
      }
    }

    return text_position;
  }

  // Creates a text position that is in the same anchor as the current
  // position, but adjusts its text offset to be either at the first or last
  // offset boundary, based on the boundary direction. When moving forward,
  // the text position is adjusted to point to the first offset boundary, or
  // to the end of its anchor if there are no offset boundaries. When moving
  // backward, it is adjusted to point to the last offset boundary, or to the
  // start of its anchor if there are no offset boundaries.
  AXPositionInstance CreatePositionAtFirstOffsetBoundary(
      ax::mojom::MoveDirection move_direction,
      BoundaryTextOffsetsFunc get_offsets) const {
    if (IsNullPosition() || get_offsets.is_null())
      return Clone();

    AXPositionInstance text_position = AsTextPosition();
    const std::vector<int32_t>& boundary_offsets =
        get_offsets.Run(text_position);
    switch (move_direction) {
      case ax::mojom::MoveDirection::kNone:
        NOTREACHED();
      case ax::mojom::MoveDirection::kBackward:
        if (boundary_offsets.empty()) {
          return text_position->CreatePositionAtStartOfAnchor();
        } else {
          text_position->text_offset_ =
              int{boundary_offsets[boundary_offsets.size() - 1]};
          return text_position;
        }
        break;
      case ax::mojom::MoveDirection::kForward:
        if (boundary_offsets.empty()) {
          return text_position->CreatePositionAtEndOfAnchor();
        } else {
          text_position->text_offset_ = int{boundary_offsets[0]};
          return text_position;
        }
        break;
    }
  }

  // Returns the next unignored leaf text position in the specified direction,
  // also ensuring that *AsLeafTextPosition() !=
  // *CreateAdjacentLeafTextPosition() is true; returns a null position if no
  // adjacent position exists.
  //
  // This method is the first step for CreateBoundary[Start|End]Position to
  // guarantee that the resulting position when using a boundary behavior other
  // than `AXBoundaryBehavior::kStopAtAnchorBoundaryOrIfAlreadyAtBoundary` is
  // not equivalent to the initial position. That's why ignored positions are
  // also skipped. Otherwise, if a boundary is present on an ignored position,
  // the search for the next or previous boundary would stop prematurely. Note
  // that if there are multiple adjacent ignored positions and all of them
  // create a boundary, we'll skip them all on purpose. For example, adjacent
  // ignored paragraph boundaries could be created by using multiple aria-hidden
  // divs next to one another. These should not contribute more than one
  // paragraph boundary to the tree's text representation, otherwise this will
  // create user confusion.
  //
  // Note that using the `CompareTo` method with text positions does not take
  // into account position affinity or the order of their anchors in the tree:
  // two text positions are considered equivalent if their offsets in the text
  // representation of the entire AXTree are the same. As such, using
  // Create[Next|Previous]LeafTextPosition is not enough to create adjacent
  // positions, e.g. the end of an anchor and the start of the next one are
  // equivalent; furthermore, there could be nodes with no text between them,
  // all of them being equivalent too.
  //
  // IMPORTANT! This method basically moves the given position one character
  // forward/backward, but it could end up at the middle of a grapheme cluster,
  // so it shouldn't be used to move by ax::mojom::TextBoundary::kCharacter (for
  // such a purpose use Create[Next|Previous]CharacterPosition instead).
  AXPositionInstance CreateAdjacentLeafTextPosition(
      ax::mojom::MoveDirection move_direction) const {
    AXPositionInstance text_position = AsLeafTextPosition();

    switch (move_direction) {
      case ax::mojom::MoveDirection::kNone:
        NOTREACHED();
      case ax::mojom::MoveDirection::kBackward:
        // If we are at a text offset greater than 0, we will simply decrease
        // the offset by one; otherwise, we will create a position at the end of
        // the previous unignored leaf node with non-empty text and decrease its
        // offset.
        //
        // Note that a position located at offset 0 of an empty text node is
        // considered both at the start and at the end of its anchor, so the
        // following loop skips over empty text leaf nodes, which is expected
        // since those positions are equivalent to both the previous non-empty
        // leaf node's end and the next non-empty leaf node's start.
        while (text_position->AtStartOfAnchor() || text_position->IsIgnored()) {
          text_position = text_position
                              ->CreatePreviousLeafTextPosition(
                                  base::BindRepeating(&AbortMoveAtRootBoundary))
                              ->CreatePositionAtEndOfAnchor();
        }
        if (!text_position->IsNullPosition())
          --text_position->text_offset_;
        break;
      case ax::mojom::MoveDirection::kForward:
        // If we are at a text offset less than MaxTextOffset, we will simply
        // increase the offset by one; otherwise, we will create a position at
        // the start of the next unignored leaf node with non-empty text and
        // increase its offset.
        //
        // Same as the comment above: using AtEndOfAnchor is enough to skip
        // empty text nodes that are equivalent to the initial position.
        while (text_position->AtEndOfAnchor() || text_position->IsIgnored()) {
          text_position = text_position->CreateNextLeafTextPosition(
              base::BindRepeating(&AbortMoveAtRootBoundary));
        }
        if (!text_position->IsNullPosition())
          ++text_position->text_offset_;
        break;
    }

    DCHECK(text_position->IsValid());
    return text_position;
  }

  AXPositionKind kind_ = AXPositionKind::NULL_POSITION;
  // TODO(crbug.com/40864560): use weak pointers for the AXTree, so that
  // AXPosition can be used without AXTreeManager support (and also faster than
  // the slow AXTreeID).
  AXTreeID tree_id_;
  AXNodeID anchor_id_;

  // For text positions, |child_index_| is initially set to |-1| and only
  // computed on demand. The same with tree positions and |text_offset_|.
  int child_index_;
  // "text_offset_" represents the number of UTF16 code units before this
  // position. It doesn't count grapheme clusters.
  int text_offset_;

  // Affinity is used to distinguish between two text positions that point to
  // the same text offset, but which happens to fall on a soft line break. A
  // soft line break doesn't insert any white space in the accessibility tree,
  // so without affinity there would be no way to determine whether a text
  // position is before or after the soft line break. An upstream affinity
  // means that the position is before the soft line break, whilst a
  // downstream affinity means that the position is after the soft line break.
  //
  // Please note that affinity could only be set to upstream for positions
  // that are anchored to non-leaf nodes. When on a leaf node, there could
  // never be an ambiguity as to which line a position points to because Blink
  // creates separate inline text boxes for each line of text. Therefore, a
  // leaf text position before the soft line break would be pointing to the
  // end of its anchor node, whilst a leaf text position after the soft line
  // break would be pointing to the start of the next node.
  ax::mojom::TextAffinity affinity_ = ax::mojom::TextAffinity::kDownstream;

  //
  // Cached members that should be lazily created on first use.
  //

  // In the case of a leaf position, its text content (in UTF16 format). Used
  // for initializing a grapheme break iterator.
  mutable std::u16string name_;
};

template <class AXPositionType, class AXNodeType>
const int AXPosition<AXPositionType, AXNodeType>::BEFORE_TEXT;
template <class AXPositionType, class AXNodeType>
const int AXPosition<AXPositionType, AXNodeType>::INVALID_INDEX;
template <class AXPositionType, class AXNodeType>
const int AXPosition<AXPositionType, AXNodeType>::INVALID_OFFSET;

template <class AXPositionType, class AXNodeType>
bool operator==(const AXPosition<AXPositionType, AXNodeType>& first,
                const AXPosition<AXPositionType, AXNodeType>& second) {
  const std::optional<int> compare_to_optional = first.CompareTo(second);
  return compare_to_optional.has_value() && compare_to_optional.value() == 0;
}

template <class AXPositionType, class AXNodeType>
bool operator<(const AXPosition<AXPositionType, AXNodeType>& first,
               const AXPosition<AXPositionType, AXNodeType>& second) {
  const std::optional<int> compare_to_optional = first.CompareTo(second);
  return compare_to_optional.has_value() && compare_to_optional.value() < 0;
}

template <class AXPositionType, class AXNodeType>
bool operator<=(const AXPosition<AXPositionType, AXNodeType>& first,
                const AXPosition<AXPositionType, AXNodeType>& second) {
  const std::optional<int> compare_to_optional = first.CompareTo(second);
  return compare_to_optional.has_value() && compare_to_optional.value() <= 0;
}

template <class AXPositionType, class AXNodeType>
bool operator>(const AXPosition<AXPositionType, AXNodeType>& first,
               const AXPosition<AXPositionType, AXNodeType>& second) {
  const std::optional<int> compare_to_optional = first.CompareTo(second);
  return compare_to_optional.has_value() && compare_to_optional.value() > 0;
}

template <class AXPositionType, class AXNodeType>
bool operator>=(const AXPosition<AXPositionType, AXNodeType>& first,
                const AXPosition<AXPositionType, AXNodeType>& second) {
  const std::optional<int> compare_to_optional = first.CompareTo(second);
  return compare_to_optional.has_value() && compare_to_optional.value() >= 0;
}

template <class AXPositionType, class AXNodeType>
void swap(AXPosition<AXPositionType, AXNodeType>& first,
          AXPosition<AXPositionType, AXNodeType>& second) {
  first.swap(second);
}

template <class AXPositionType, class AXNodeType>
std::ostream& operator<<(
    std::ostream& stream,
    const AXPosition<AXPositionType, AXNodeType>& position) {
  return stream << position.ToString();
}

extern template class EXPORT_TEMPLATE_DECLARE(AX_EXPORT)
    AXPosition<AXNodePosition, AXNode>;

}  // namespace ui

#endif  // UI_ACCESSIBILITY_AX_POSITION_H_