// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/accessibility/ax_node.h"

#include <algorithm>

#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_computed_node_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_hypertext.h"
#include "ui/accessibility/ax_language_detection.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/ax_selection.h"
#include "ui/accessibility/ax_table_info.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_manager.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/transform.h"

namespace ui {

// Definition of static class members.
constexpr char AXNode::kEmbeddedObjectCharacterUTF8[];
constexpr char16_t AXNode::kEmbeddedObjectCharacterUTF16[];
constexpr int AXNode::kEmbeddedObjectCharacterLengthUTF8;
constexpr int AXNode::kEmbeddedObjectCharacterLengthUTF16;

AXNode::AXNode(AXTree* tree,
               AXNode* parent,
               AXNodeID id,
               size_t index_in_parent,
               size_t unignored_index_in_parent)
    : tree_(tree),
      index_in_parent_(index_in_parent),
      unignored_index_in_parent_(unignored_index_in_parent),
      parent_(parent) {
  data_.id = id;
}

AXNode::~AXNode() = default;

AXNodeData&& AXNode::TakeData() {
  return std::move(data_);
}

const std::vector<AXNode*>& AXNode::GetAllChildren() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return children_;
}

size_t AXNode::GetChildCount() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return children_.size();
}

size_t AXNode::GetChildCountCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());

  const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
  if (child_tree_manager)
    return 1u;

  return GetChildCount();
}

size_t AXNode::GetUnignoredChildCount() const {
  DCHECK(!IsIgnored()) << "Called unignored method on ignored node: " << *this;
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return unignored_child_count_;
}

size_t AXNode::GetUnignoredChildCountCrossingTreeBoundary() const {
  // TODO(nektar): Should DCHECK that this node is not ignored.
  DCHECK(!tree_->GetTreeUpdateInProgressState());

  const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
  if (child_tree_manager) {
    DCHECK_EQ(unignored_child_count_, 0u)
        << "A node cannot be hosting both a child tree and other nodes as "
           "children.";
    return 1u;  // A child tree is never ignored.
  }

  return unignored_child_count_;
}

AXNode* AXNode::GetChildAtIndex(size_t index) const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  if (index >= GetChildCount())
    return nullptr;
  return children_[index];
}

AXNode* AXNode::GetChildAtIndexCrossingTreeBoundary(size_t index) const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());

  const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
  if (child_tree_manager) {
    DCHECK_EQ(index, 0u)
        << "A node cannot be hosting both a child tree and other nodes as "
           "children.";
    return child_tree_manager->GetRoot();
  }

  return GetChildAtIndex(index);
}

AXNode* AXNode::GetUnignoredChildAtIndex(size_t index) const {
  // TODO(nektar): Should DCHECK that this node is not ignored.
  DCHECK(!tree_->GetTreeUpdateInProgressState());

  for (auto it = UnignoredChildrenBegin(); it != UnignoredChildrenEnd(); ++it) {
    if (index == 0)
      return it.get();
    --index;
  }

  return nullptr;
}

AXNode* AXNode::GetUnignoredChildAtIndexCrossingTreeBoundary(
    size_t index) const {
  // TODO(nektar): Should DCHECK that this node is not ignored.
  DCHECK(!tree_->GetTreeUpdateInProgressState());

  const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
  if (child_tree_manager) {
    DCHECK_EQ(index, 0u)
        << "A node cannot be hosting both a child tree and other nodes as "
           "children.";
    // A child tree is never ignored.
    return child_tree_manager->GetRoot();
  }

  return GetUnignoredChildAtIndex(index);
}

AXNode* AXNode::GetParent() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return parent_;
}

AXNode* AXNode::GetParentCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  if (parent_)
    return parent_;
  const AXTreeManager* manager = GetManager();
  if (manager)
    return manager->GetParentNodeFromParentTree();
  return nullptr;
}

AXNode* AXNode::GetUnignoredParent() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  AXNode* unignored_parent = GetParent();
  while (unignored_parent && unignored_parent->IsIgnored())
    unignored_parent = unignored_parent->GetParent();
  return unignored_parent;
}

AXNode* AXNode::GetUnignoredParentCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  AXNode* unignored_parent = GetUnignoredParent();
  if (!unignored_parent) {
    const AXTreeManager* manager = GetManager();
    if (manager)
      unignored_parent = manager->GetParentNodeFromParentTree();
  }
  return unignored_parent;
}

base::queue<AXNode*> AXNode::GetAncestorsCrossingTreeBoundaryAsQueue() const {
  base::queue<AXNode*> ancestors;
  AXNode* ancestor = const_cast<AXNode*>(this);
  while (ancestor) {
    ancestors.push(ancestor);
    ancestor = ancestor->GetParentCrossingTreeBoundary();
  }
  return ancestors;
}

base::stack<AXNode*> AXNode::GetAncestorsCrossingTreeBoundaryAsStack() const {
  base::stack<AXNode*> ancestors;
  AXNode* ancestor = const_cast<AXNode*>(this);
  while (ancestor) {
    ancestors.push(ancestor);
    ancestor = ancestor->GetParentCrossingTreeBoundary();
  }
  return ancestors;
}

size_t AXNode::GetIndexInParent() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return index_in_parent_;
}

size_t AXNode::GetUnignoredIndexInParent() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return unignored_index_in_parent_;
}

AXNode* AXNode::GetFirstChild() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return GetChildAtIndex(0);
}

AXNode* AXNode::GetFirstChildCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return GetChildAtIndexCrossingTreeBoundary(0);
}

AXNode* AXNode::GetFirstUnignoredChild() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return ComputeFirstUnignoredChildRecursive();
}

AXNode* AXNode::GetFirstUnignoredChildCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());

  const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
  if (child_tree_manager)
    return child_tree_manager->GetRoot();

  return ComputeFirstUnignoredChildRecursive();
}

AXNode* AXNode::GetLastChild() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  size_t n = GetChildCount();
  if (n == 0)
    return nullptr;
  return GetChildAtIndex(n - 1);
}

AXNode* AXNode::GetLastChildCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  size_t n = GetChildCountCrossingTreeBoundary();
  if (n == 0)
    return nullptr;
  return GetChildAtIndexCrossingTreeBoundary(n - 1);
}

AXNode* AXNode::GetLastUnignoredChild() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return ComputeLastUnignoredChildRecursive();
}

AXNode* AXNode::GetLastUnignoredChildCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());

  const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
  if (child_tree_manager)
    return child_tree_manager->GetRoot();

  return ComputeLastUnignoredChildRecursive();
}

AXNode* AXNode::GetDeepestFirstChild() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  if (!GetChildCount())
    return nullptr;

  AXNode* deepest_child = GetFirstChild();
  DCHECK(deepest_child);
  while (deepest_child->GetChildCount()) {
    deepest_child = deepest_child->GetFirstChild();
    DCHECK(deepest_child);
  }

  return deepest_child;
}

AXNode* AXNode::GetDeepestFirstChildCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  if (!GetChildCountCrossingTreeBoundary())
    return nullptr;

  AXNode* deepest_child = GetFirstChildCrossingTreeBoundary();
  DCHECK(deepest_child);
  while (deepest_child->GetChildCountCrossingTreeBoundary()) {
    deepest_child = deepest_child->GetFirstChildCrossingTreeBoundary();
    DCHECK(deepest_child);
  }

  return deepest_child;
}

AXNode* AXNode::GetDeepestFirstUnignoredChild() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  DCHECK(!IsIgnored()) << "Called unignored method on ignored node: " << *this;
  if (!GetUnignoredChildCount())
    return nullptr;

  AXNode* deepest_child = GetFirstUnignoredChild();
  DCHECK(deepest_child);
  while (deepest_child->GetUnignoredChildCount()) {
    deepest_child = deepest_child->GetFirstUnignoredChild();
    DCHECK(deepest_child);
  }

  return deepest_child;
}

AXNode* AXNode::GetDeepestFirstUnignoredChildCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  DCHECK(!IsIgnored()) << "Called unignored method on ignored node: " << *this;
  if (!GetUnignoredChildCountCrossingTreeBoundary())
    return nullptr;

  AXNode* deepest_child = GetFirstUnignoredChildCrossingTreeBoundary();
  DCHECK(deepest_child);
  while (deepest_child->GetUnignoredChildCountCrossingTreeBoundary()) {
    deepest_child = deepest_child->GetFirstUnignoredChildCrossingTreeBoundary();
    DCHECK(deepest_child);
  }

  return deepest_child;
}

AXNode* AXNode::GetDeepestLastChild() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  if (!GetChildCount())
    return nullptr;

  AXNode* deepest_child = GetLastChild();
  DCHECK(deepest_child);
  while (deepest_child->GetChildCount()) {
    deepest_child = deepest_child->GetLastChild();
    DCHECK(deepest_child);
  }

  return deepest_child;
}

AXNode* AXNode::GetDeepestLastChildCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  DCHECK(!IsIgnored()) << "Called unignored method on ignored node: " << *this;
  if (!GetChildCountCrossingTreeBoundary())
    return nullptr;

  AXNode* deepest_child = GetLastChildCrossingTreeBoundary();
  DCHECK(deepest_child);
  while (deepest_child->GetChildCountCrossingTreeBoundary()) {
    deepest_child = deepest_child->GetLastChildCrossingTreeBoundary();
    DCHECK(deepest_child);
  }

  return deepest_child;
}

AXNode* AXNode::GetDeepestLastUnignoredChild() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  DCHECK(!IsIgnored()) << "Called unignored method on ignored node: " << *this;
  if (!GetUnignoredChildCount())
    return nullptr;

  AXNode* deepest_child = GetLastUnignoredChild();
  DCHECK(deepest_child);
  while (deepest_child->GetUnignoredChildCount()) {
    deepest_child = deepest_child->GetLastUnignoredChild();
    DCHECK(deepest_child);
  }

  return deepest_child;
}

AXNode* AXNode::GetDeepestLastUnignoredChildCrossingTreeBoundary() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  DCHECK(!IsIgnored()) << "Called unignored method on ignored node: " << *this;
  if (!GetUnignoredChildCountCrossingTreeBoundary())
    return nullptr;

  AXNode* deepest_child = GetLastUnignoredChildCrossingTreeBoundary();
  DCHECK(deepest_child);
  while (deepest_child->GetUnignoredChildCountCrossingTreeBoundary()) {
    deepest_child = deepest_child->GetLastUnignoredChildCrossingTreeBoundary();
    DCHECK(deepest_child);
  }

  return deepest_child;
}

AXNode* AXNode::GetNextSibling() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  AXNode* parent = GetParent();
  if (!parent)
    return nullptr;
  DCHECK(parent || !GetIndexInParent())
      << "Root nodes lack a parent. Their index_in_parent should be 0.";
  size_t nextIndex = GetIndexInParent() + 1;
  if (nextIndex >= parent->GetChildCount())
    return nullptr;
  return parent->GetChildAtIndex(nextIndex);
}

// Search for the next sibling of this node, skipping over any ignored nodes
// encountered.
//
// In our search:
//   If we find an ignored sibling, we consider its children as our siblings.
//   If we run out of siblings, we consider an ignored parent's siblings as our
//     own siblings.
//
// Note: this behaviour of 'skipping over' an ignored node makes this subtly
// different to finding the next (direct) sibling which is unignored.
//
// Consider a tree, where (i) marks a node as ignored:
//
//   1
//   ├── 2
//   ├── 3(i)
//   │   └── 5
//   └── 4
//
// The next sibling of node 2 is node 3, which is ignored.
// The next unignored sibling of node 2 could be either:
//  1) node 4 - next unignored sibling in the literal tree, or
//  2) node 5 - next unignored sibling in the logical document.
//
// There is no next sibling of node 5.
// The next unignored sibling of node 5 could be either:
//  1) null   - no next sibling in the literal tree, or
//  2) node 4 - next unignored sibling in the logical document.
//
// In both cases, this method implements approach (2).
//
// TODO(chrishall): Can we remove this non-reflexive case by forbidding
//   GetNextUnignoredSibling calls on an ignored started node?
// Note: this means that Next/Previous-UnignoredSibling are not reflexive if
// either of the nodes in question are ignored. From above we get an example:
//   NextUnignoredSibling(3)     is 4, but
//   PreviousUnignoredSibling(4) is 5.
//
// The view of unignored siblings for node 3 includes both node 2 and node 4:
//    2 <-- [3(i)] --> 4
//
// Whereas nodes 2, 5, and 4 do not consider node 3 to be an unignored sibling:
// null <-- [2] --> 5
//    2 <-- [5] --> 4
//    5 <-- [4] --> null
AXNode* AXNode::GetNextUnignoredSibling() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXNode* current = this;

  // If there are children of the |current| node still to consider.
  bool considerChildren = false;

  while (current) {
    // A |candidate| sibling to consider.
    // If it is unignored then we have found our result.
    // Otherwise promote it to |current| and consider its children.
    AXNode* candidate;

    if (considerChildren && (candidate = current->GetFirstChild())) {
      if (!candidate->IsIgnored())
        return candidate;
      current = candidate;

    } else if ((candidate = current->GetNextSibling())) {
      if (!candidate->IsIgnored())
        return candidate;
      current = candidate;
      // Look through the ignored candidate node to consider their children as
      // though they were siblings.
      considerChildren = true;

    } else {
      // Continue our search through a parent iff they are ignored.
      //
      // If |current| has an ignored parent, then we consider the parent's
      // siblings as though they were siblings of |current|.
      //
      // Given a tree:
      //   1
      //   ├── 2(?)
      //   │   └── [4]
      //   └── 3
      //
      // Node 4's view of siblings:
      //   literal tree:   null <-- [4] --> null
      //
      // If node 2 is not ignored, then node 4's view doesn't change, and we
      // have no more nodes to consider:
      //   unignored tree: null <-- [4] --> null
      //
      // If instead node 2 is ignored, then node 4's view of siblings grows to
      // include node 3, and we have more nodes to consider:
      //   unignored tree: null <-- [4] --> 3
      current = current->GetParent();
      if (!current || !current->IsIgnored())
        return nullptr;

      // We have already considered all relevant descendants of |current|.
      considerChildren = false;
    }
  }

  return nullptr;
}

AXNode* AXNode::GetPreviousSibling() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  DCHECK(GetParent() || !GetIndexInParent())
      << "Root nodes lack a parent. Their index_in_parent should be 0.";
  size_t index = GetIndexInParent();
  if (index == 0)
    return nullptr;
  return GetParent()->GetChildAtIndex(index - 1);
}

// Search for the previous sibling of this node, skipping over any ignored nodes
// encountered.
//
// In our search for a sibling:
//   If we find an ignored sibling, we may consider its children as siblings.
//   If we run out of siblings, we may consider an ignored parent's siblings as
//     our own.
//
// See the documentation for |GetNextUnignoredSibling| for more details.
AXNode* AXNode::GetPreviousUnignoredSibling() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXNode* current = this;

  // If there are children of the |current| node still to consider.
  bool considerChildren = false;

  while (current) {
    // A |candidate| sibling to consider.
    // If it is unignored then we have found our result.
    // Otherwise promote it to |current| and consider its children.
    AXNode* candidate;

    if (considerChildren && (candidate = current->GetLastChild())) {
      if (!candidate->IsIgnored())
        return candidate;
      current = candidate;

    } else if ((candidate = current->GetPreviousSibling())) {
      if (!candidate->IsIgnored())
        return candidate;
      current = candidate;
      // Look through the ignored candidate node to consider their children as
      // though they were siblings.
      considerChildren = true;

    } else {
      // Continue our search through a parent iff they are ignored.
      //
      // If |current| has an ignored parent, then we consider the parent's
      // siblings as though they were siblings of |current|.
      //
      // Given a tree:
      //   1
      //   ├── 2
      //   └── 3(?)
      //       └── [4]
      //
      // Node 4's view of siblings:
      //   literal tree:   null <-- [4] --> null
      //
      // If node 3 is not ignored, then node 4's view doesn't change, and we
      // have no more nodes to consider:
      //   unignored tree: null <-- [4] --> null
      //
      // If instead node 3 is ignored, then node 4's view of siblings grows to
      // include node 2, and we have more nodes to consider:
      //   unignored tree:    2 <-- [4] --> null
      current = current->GetParent();
      if (!current || !current->IsIgnored())
        return nullptr;

      // We have already considered all relevant descendants of |current|.
      considerChildren = false;
    }
  }

  return nullptr;
}

AXNode* AXNode::GetNextUnignoredInTreeOrder() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  if (GetUnignoredChildCount())
    return GetFirstUnignoredChild();

  const AXNode* node = this;
  while (node) {
    AXNode* sibling = node->GetNextUnignoredSibling();
    if (sibling)
      return sibling;

    node = node->GetUnignoredParent();
  }

  return nullptr;
}

AXNode* AXNode::GetPreviousUnignoredInTreeOrder() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  AXNode* sibling = GetPreviousUnignoredSibling();
  if (!sibling)
    return GetUnignoredParent();

  if (sibling->GetUnignoredChildCount())
    return sibling->GetDeepestLastUnignoredChild();

  return sibling;
}

AXNode::AllChildIterator AXNode::AllChildrenBegin() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return AllChildIterator(this, GetFirstChild());
}

AXNode::AllChildIterator AXNode::AllChildrenEnd() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return AllChildIterator(this, nullptr);
}

AXNode::AllChildCrossingTreeBoundaryIterator
AXNode::AllChildrenCrossingTreeBoundaryBegin() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return AllChildCrossingTreeBoundaryIterator(
      this, GetFirstChildCrossingTreeBoundary());
}

AXNode::AllChildCrossingTreeBoundaryIterator
AXNode::AllChildrenCrossingTreeBoundaryEnd() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return AllChildCrossingTreeBoundaryIterator(this, nullptr);
}

AXNode::UnignoredChildIterator AXNode::UnignoredChildrenBegin() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return UnignoredChildIterator(this, GetFirstUnignoredChild());
}

AXNode::UnignoredChildIterator AXNode::UnignoredChildrenEnd() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return UnignoredChildIterator(this, nullptr);
}

AXNode::UnignoredChildCrossingTreeBoundaryIterator
AXNode::UnignoredChildrenCrossingTreeBoundaryBegin() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return UnignoredChildCrossingTreeBoundaryIterator(
      this, GetFirstUnignoredChildCrossingTreeBoundary());
}

AXNode::UnignoredChildCrossingTreeBoundaryIterator
AXNode::UnignoredChildrenCrossingTreeBoundaryEnd() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return UnignoredChildCrossingTreeBoundaryIterator(this, nullptr);
}

bool AXNode::CanFireEvents() const {
  // TODO(nektar): Cache the `IsChildOfLeaf` state in `AXComputedNodeData`.
  return !IsChildOfLeaf();
}

AXNode* AXNode::GetLowestCommonAncestor(const AXNode& other) {
  if (this == &other)
    return this;

  AXNode* common_ancestor = nullptr;
  base::stack<AXNode*> our_ancestors =
      GetAncestorsCrossingTreeBoundaryAsStack();
  base::stack<AXNode*> other_ancestors =
      other.GetAncestorsCrossingTreeBoundaryAsStack();
  while (!our_ancestors.empty() && !other_ancestors.empty() &&
         our_ancestors.top() == other_ancestors.top()) {
    common_ancestor = our_ancestors.top();
    our_ancestors.pop();
    other_ancestors.pop();
  }
  return common_ancestor;
}

absl::optional<int> AXNode::CompareTo(const AXNode& other) const {
  if (this == &other)
    return 0;

  AXNode* common_ancestor = nullptr;
  base::stack<AXNode*> our_ancestors =
      GetAncestorsCrossingTreeBoundaryAsStack();
  base::stack<AXNode*> other_ancestors =
      other.GetAncestorsCrossingTreeBoundaryAsStack();
  while (!our_ancestors.empty() && !other_ancestors.empty() &&
         our_ancestors.top() == other_ancestors.top()) {
    common_ancestor = our_ancestors.top();
    our_ancestors.pop();
    other_ancestors.pop();
  }

  if (!common_ancestor)
    return absl::nullopt;
  if (common_ancestor == this)
    return -1;
  if (common_ancestor == &other)
    return 1;

  if (our_ancestors.empty() || other_ancestors.empty()) {
    NOTREACHED() << "The common ancestor should be followed by two uncommon "
                    "children in the two corresponding lists of ancestors.";
    return absl::nullopt;
  }

  size_t this_uncommon_ancestor_index = our_ancestors.top()->GetIndexInParent();
  size_t other_uncommon_ancestor_index =
      other_ancestors.top()->GetIndexInParent();
  DCHECK_NE(this_uncommon_ancestor_index, other_uncommon_ancestor_index)
      << "Deepest uncommon ancestors should truly be uncommon, i.e. not be the "
         "same node.";
  return this_uncommon_ancestor_index - other_uncommon_ancestor_index;
}

bool AXNode::IsText() const {
  // Regular list markers only expose their alternative text, but do not expose
  // their descendants; and the descendants should be ignored. This is because
  // the alternative text depends on the counter style and can be different from
  // the actual (visual) marker text, and hence, inconsistent with the
  // descendants. We treat a list marker as non-text only if it still has
  // non-ignored descendants, which happens only when:
  // - The list marker itself is ignored but the descendants are not
  // - Or the list marker contains images
  if (GetRole() == ax::mojom::Role::kListMarker)
    return !GetUnignoredChildCount();
  return ui::IsText(GetRole());
}

bool AXNode::IsLineBreak() const {
  // The last condition captures inline text nodes whose only content is an '\n'
  // character.
  return GetRole() == ax::mojom::Role::kLineBreak ||
         (GetRole() == ax::mojom::Role::kInlineTextBox &&
          GetBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject));
}

void AXNode::SetData(const AXNodeData& src) {
  data_ = src;
}

void AXNode::SetLocation(AXNodeID offset_container_id,
                         const gfx::RectF& location,
                         gfx::Transform* transform) {
  data_.relative_bounds.offset_container_id = offset_container_id;
  data_.relative_bounds.bounds = location;
  if (transform) {
    data_.relative_bounds.transform =
        std::make_unique<gfx::Transform>(*transform);
  } else {
    data_.relative_bounds.transform.reset();
  }
}

void AXNode::SetIndexInParent(size_t index_in_parent) {
  index_in_parent_ = index_in_parent;
}

void AXNode::UpdateUnignoredCachedValues() {
  computed_node_data_.reset();
  if (!IsIgnored())
    UpdateUnignoredCachedValuesRecursive(0);
}

void AXNode::SwapChildren(std::vector<AXNode*>* children) {
  children->swap(children_);
}

bool AXNode::IsDescendantOf(const AXNode* ancestor) const {
  if (!ancestor)
    return false;
  if (this == ancestor)
    return true;
  if (const AXNode* parent = GetParent())
    return parent->IsDescendantOf(ancestor);
  return false;
}

bool AXNode::IsDescendantOfCrossingTreeBoundary(const AXNode* ancestor) const {
  if (!ancestor)
    return false;
  if (this == ancestor)
    return true;
  if (const AXNode* parent = GetParentCrossingTreeBoundary())
    return parent->IsDescendantOfCrossingTreeBoundary(ancestor);
  return false;
}

SkColor AXNode::ComputeColor() const {
  return ComputeColorAttribute(ax::mojom::IntAttribute::kColor);
}

SkColor AXNode::ComputeBackgroundColor() const {
  return ComputeColorAttribute(ax::mojom::IntAttribute::kBackgroundColor);
}

SkColor AXNode::ComputeColorAttribute(ax::mojom::IntAttribute attr) const {
  SkColor color = GetIntAttribute(attr);
  AXNode* ancestor = GetParent();

  // If the color has some transparency, keep blending with background
  // colors until we get an opaque color or reach the root.
  while (ancestor && SkColorGetA(color) != SK_AlphaOPAQUE) {
    SkColor background_color = ancestor->GetIntAttribute(attr);
    color = color_utils::GetResultingPaintColor(color, background_color);
    ancestor = ancestor->GetParent();
  }

  return color;
}

AXTreeManager* AXNode::GetManager() const {
  return AXTreeManager::FromID(tree_->GetAXTreeID());
}

bool AXNode::HasVisibleCaretOrSelection() const {
  const AXSelection selection = GetSelection();
  const AXNode* focus = tree()->GetFromId(selection.focus_object_id);
  if (!focus || !focus->IsDescendantOf(this))
    return false;

  // A selection or the caret will be visible in a focused text field (including
  // a content editable).
  const AXNode* text_field = GetTextFieldAncestor();
  if (text_field)
    return true;

  // The selection will be visible in non-editable content only if it is not
  // collapsed.
  return !selection.IsCollapsed();
}

AXSelection AXNode::GetSelection() const {
  DCHECK(tree()) << "Cannot retrieve the current selection if the node is not "
                    "attached to an accessibility tree.\n"
                 << *this;
  return tree()->GetSelection();
}

AXSelection AXNode::GetUnignoredSelection() const {
  DCHECK(tree()) << "Cannot retrieve the current selection if the node is not "
                    "attached to an accessibility tree.\n"
                 << *this;
  AXSelection selection = tree()->GetUnignoredSelection();

  // "selection.anchor_offset" and "selection.focus_ofset" might need to be
  // adjusted if the anchor or the focus nodes include ignored children.
  //
  // TODO(nektar): Move this logic into its own "AXSelection" class and cache
  // the result for faster reuse.
  const AXNode* anchor = tree()->GetFromId(selection.anchor_object_id);
  if (anchor && !anchor->IsLeaf()) {
    DCHECK_GE(selection.anchor_offset, 0);
    if (static_cast<size_t>(selection.anchor_offset) <
        anchor->GetChildCount()) {
      const AXNode* anchor_child =
          anchor->GetChildAtIndex(selection.anchor_offset);
      DCHECK(anchor_child);
      selection.anchor_offset =
          static_cast<int>(anchor_child->GetUnignoredIndexInParent());
    } else {
      selection.anchor_offset =
          static_cast<int>(anchor->GetUnignoredChildCount());
    }
  }

  const AXNode* focus = tree()->GetFromId(selection.focus_object_id);
  if (focus && !focus->IsLeaf()) {
    DCHECK_GE(selection.focus_offset, 0);
    if (static_cast<size_t>(selection.focus_offset) < focus->GetChildCount()) {
      const AXNode* focus_child =
          focus->GetChildAtIndex(selection.focus_offset);
      DCHECK(focus_child);
      selection.focus_offset =
          static_cast<int>(focus_child->GetUnignoredIndexInParent());
    } else {
      selection.focus_offset =
          static_cast<int>(focus->GetUnignoredChildCount());
    }
  }
  return selection;
}

bool AXNode::HasStringAttribute(ax::mojom::StringAttribute attribute) const {
  return GetComputedNodeData().HasOrCanComputeAttribute(attribute);
}

const std::string& AXNode::GetStringAttribute(
    ax::mojom::StringAttribute attribute) const {
  return GetComputedNodeData().GetOrComputeAttributeUTF8(attribute);
}

bool AXNode::GetStringAttribute(ax::mojom::StringAttribute attribute,
                                std::string* value) const {
  if (GetComputedNodeData().HasOrCanComputeAttribute(attribute)) {
    *value = GetComputedNodeData().GetOrComputeAttributeUTF8(attribute);
    return true;
  }
  return false;
}

std::u16string AXNode::GetString16Attribute(
    ax::mojom::StringAttribute attribute) const {
  return GetComputedNodeData().GetOrComputeAttributeUTF16(attribute);
}

bool AXNode::GetString16Attribute(ax::mojom::StringAttribute attribute,
                                  std::u16string* value) const {
  if (GetComputedNodeData().HasOrCanComputeAttribute(attribute)) {
    *value = GetComputedNodeData().GetOrComputeAttributeUTF16(attribute);
    return true;
  }
  return false;
}

bool AXNode::HasInheritedStringAttribute(
    ax::mojom::StringAttribute attribute) const {
  for (const AXNode* current_node = this; current_node;
       current_node = current_node->GetParent()) {
    if (current_node->HasStringAttribute(attribute))
      return true;
  }
  return false;
}

const std::string& AXNode::GetInheritedStringAttribute(
    ax::mojom::StringAttribute attribute) const {
  for (const AXNode* current_node = this; current_node;
       current_node = current_node->GetParent()) {
    if (current_node->HasStringAttribute(attribute))
      return current_node->GetStringAttribute(attribute);
  }
  return base::EmptyString();
}

std::u16string AXNode::GetInheritedString16Attribute(
    ax::mojom::StringAttribute attribute) const {
  return base::UTF8ToUTF16(GetInheritedStringAttribute(attribute));
}

bool AXNode::HasIntListAttribute(ax::mojom::IntListAttribute attribute) const {
  return GetComputedNodeData().HasOrCanComputeAttribute(attribute);
}

const std::vector<int32_t>& AXNode::GetIntListAttribute(
    ax::mojom::IntListAttribute attribute) const {
  return GetComputedNodeData().GetOrComputeAttribute(attribute);
}

bool AXNode::GetIntListAttribute(ax::mojom::IntListAttribute attribute,
                                 std::vector<int32_t>* value) const {
  if (GetComputedNodeData().HasOrCanComputeAttribute(attribute)) {
    *value = GetComputedNodeData().GetOrComputeAttribute(attribute);
    return true;
  }
  return false;
}

AXLanguageInfo* AXNode::GetLanguageInfo() const {
  return language_info_.get();
}

void AXNode::SetLanguageInfo(std::unique_ptr<AXLanguageInfo> lang_info) {
  language_info_ = std::move(lang_info);
}

void AXNode::ClearLanguageInfo() {
  language_info_.reset();
}

const AXComputedNodeData& AXNode::GetComputedNodeData() const {
  if (!computed_node_data_)
    computed_node_data_ = std::make_unique<AXComputedNodeData>(*this);
  return *computed_node_data_;
}

void AXNode::ClearComputedNodeData() {
  computed_node_data_.reset();
}

const std::string& AXNode::GetNameUTF8() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXNode* node = this;
  if (GetRole() == ax::mojom::Role::kPortal &&
      GetNameFrom() == ax::mojom::NameFrom::kNone) {
    const AXTreeManager* child_tree_manager =
        AXTreeManager::ForChildTree(*this);
    if (child_tree_manager)
      node = child_tree_manager->GetRoot();
  }

  return node->GetStringAttribute(ax::mojom::StringAttribute::kName);
}

std::u16string AXNode::GetNameUTF16() const {
  // Storing a copy of the name in UTF16 would probably not be helpful because
  // it could potentially double the memory usage of AXTree.
  return base::UTF8ToUTF16(GetNameUTF8());
}

const std::u16string& AXNode::GetHypertext() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  // TODO(nektar): Introduce proper caching of hypertext via
  // `AXHypertext::needs_update`.
  hypertext_ = AXHypertext();

  // Hypertext is not exposed for descendants of leaf nodes. For such nodes,
  // their text content is equivalent to their hypertext. Otherwise, we would
  // never be able to compute equivalent ancestor positions in atomic text
  // fields given an AXPosition on an inline text box descendant, because there
  // is often an ignored generic container between the text descendants and the
  // text field node.
  //
  // For example, look at the following accessibility tree and the text
  // positions indicated using "<>" symbols in the text content of every node,
  // and then imagine what would happen if the generic container was represented
  // by an "embedded object replacement character" in the text of its text field
  // parent.
  // ++kTextField "Hell<o>" IsLeaf=true
  // ++++kGenericContainer "Hell<o>" ignored IsChildOfLeaf=true
  // ++++++kStaticText "Hell<o>" IsChildOfLeaf=true
  // ++++++++kInlineTextBox "Hell<o>" IsChildOfLeaf=true

  if (IsLeaf() || IsChildOfLeaf()) {
    hypertext_.hypertext = GetTextContentUTF16();
  } else {
    // Construct the hypertext for this node, which contains the concatenation
    // of the text content of this node's textual children, and an "object
    // replacement character" for all the other children.
    //
    // Note that the word "hypertext" comes from the IAccessible2 Standard and
    // has nothing to do with HTML.
    static const base::NoDestructor<std::u16string> embedded_character_str(
        AXNode::kEmbeddedObjectCharacterUTF16);
    auto first = UnignoredChildrenCrossingTreeBoundaryBegin();
    for (auto iter = first; iter != UnignoredChildrenCrossingTreeBoundaryEnd();
         ++iter) {
      // Similar to Firefox, we don't expose text nodes in IAccessible2 and ATK
      // hypertext with the embedded object character. We copy all of their text
      // instead.
      if (iter->IsText()) {
        hypertext_.hypertext += iter->GetTextContentUTF16();
      } else {
        int character_offset = static_cast<int>(hypertext_.hypertext.size());
        auto inserted =
            hypertext_.hypertext_offset_to_hyperlink_child_index.emplace(
                character_offset, static_cast<int>(std::distance(first, iter)));
        DCHECK(inserted.second) << "An embedded object at " << character_offset
                                << " has already been encountered.";
        hypertext_.hypertext += *embedded_character_str;
      }
    }
  }

  hypertext_.needs_update = false;
  return hypertext_.hypertext;
}

void AXNode::SetNeedsToUpdateHypertext() {
  old_hypertext_ = hypertext_;
  hypertext_.needs_update = true;
  // TODO(nektar): Introduce proper caching of hypertext via
  // `AXHypertext::needs_update`.
  GetHypertext();  // Forces `hypertext_` to immediately update.
}

const std::map<int, int>& AXNode::GetHypertextOffsetToHyperlinkChildIndex()
    const {
  // TODO(nektar): Introduce proper caching of hypertext via
  // `AXHypertext::needs_update`.
  GetHypertext();  // Update `hypertext_` if not up-to-date.
  return hypertext_.hypertext_offset_to_hyperlink_child_index;
}

const AXHypertext& AXNode::GetOldHypertext() const {
  // TODO(nektar): Introduce proper caching of hypertext via
  // `AXHypertext::needs_update`.
  GetHypertext();  // Update `hypertext_` if not up-to-date.
  return old_hypertext_;
}

const std::string& AXNode::GetTextContentUTF8() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return GetComputedNodeData().GetOrComputeTextContentUTF8();
}

const std::u16string& AXNode::GetTextContentUTF16() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return GetComputedNodeData().GetOrComputeTextContentUTF16();
}

int AXNode::GetTextContentLengthUTF8() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return GetComputedNodeData().GetOrComputeTextContentLengthUTF8();
}

int AXNode::GetTextContentLengthUTF16() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  return GetComputedNodeData().GetOrComputeTextContentLengthUTF16();
}

gfx::RectF AXNode::GetTextContentRangeBoundsUTF8(int start_offset,
                                                 int end_offset) const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  DCHECK_LE(start_offset, end_offset)
      << "Invalid `start_offset` and `end_offset`.\n"
      << start_offset << ' ' << end_offset << "\nin\n"
      << *this;
  // Since we DCHECK that `start_offset` <= `end_offset`, there is no need to
  // check whether `start_offset` is also in range.
  if (end_offset > GetTextContentLengthUTF8())
    return gfx::RectF();

  // TODO(nektar): Update this to use
  // "base/strings/utf_offset_string_conversions.h" which provides caching of
  // offsets.
  std::u16string out_trancated_string_utf16;
  if (!base::UTF8ToUTF16(GetTextContentUTF8().data(),
                         base::checked_cast<size_t>(start_offset),
                         &out_trancated_string_utf16)) {
    return gfx::RectF();
  }
  start_offset = base::checked_cast<int>(out_trancated_string_utf16.length());
  if (!base::UTF8ToUTF16(GetTextContentUTF8().data(),
                         base::checked_cast<size_t>(end_offset),
                         &out_trancated_string_utf16)) {
    return gfx::RectF();
  }
  end_offset = base::checked_cast<int>(out_trancated_string_utf16.length());
  return GetTextContentRangeBoundsUTF16(start_offset, end_offset);
}

gfx::RectF AXNode::GetTextContentRangeBoundsUTF16(int start_offset,
                                                  int end_offset) const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  DCHECK_LE(start_offset, end_offset)
      << "Invalid `start_offset` and `end_offset`.\n"
      << start_offset << ' ' << end_offset << "\nin\n"
      << *this;
  // Since we DCHECK that `start_offset` <= `end_offset`, there is no need to
  // check whether `start_offset` is also in range.
  if (end_offset > GetTextContentLengthUTF16())
    return gfx::RectF();

  const std::vector<int32_t>& character_offsets =
      GetIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets);
  int character_offsets_length =
      base::checked_cast<int>(character_offsets.size());
  // Charactger offsets are always based on the UTF-16 representation of the
  // text.
  if (character_offsets_length < GetTextContentLengthUTF16()) {
    // Blink might not return pixel offsets for all characters. Clamp the
    // character range to be within the number of provided pixels. Note that the
    // first character always starts at pixel 0, so an offset for that character
    // is not provided.
    //
    // TODO(accessibility): We need to fix this bug in Blink.
    start_offset = std::min(start_offset, character_offsets_length);
    end_offset = std::min(end_offset, character_offsets_length);
  }

  // TODO(nektar): Remove all this code and fix up the character offsets vector
  // itself.
  int start_pixel_offset =
      start_offset > 0
          ? character_offsets[base::checked_cast<size_t>(start_offset - 1)]
          : 0;
  int end_pixel_offset =
      end_offset > 0
          ? character_offsets[base::checked_cast<size_t>(end_offset - 1)]
          : 0;
  int max_pixel_offset = character_offsets_length > 0
                             ? character_offsets[character_offsets_length - 1]
                             : 0;
  const gfx::RectF& node_bounds = data().relative_bounds.bounds;

  gfx::RectF out_bounds;
  switch (static_cast<ax::mojom::WritingDirection>(
      GetIntAttribute(ax::mojom::IntAttribute::kTextDirection))) {
    case ax::mojom::WritingDirection::kNone:
    case ax::mojom::WritingDirection::kLtr:
      out_bounds = gfx::RectF(start_pixel_offset, 0,
                              end_pixel_offset - start_pixel_offset,
                              node_bounds.height());
      break;
    case ax::mojom::WritingDirection::kRtl: {
      int left = max_pixel_offset - end_pixel_offset;
      int right = max_pixel_offset - start_pixel_offset;
      out_bounds = gfx::RectF(left, 0, right - left, node_bounds.height());
      break;
    }
    case ax::mojom::WritingDirection::kTtb:
      out_bounds = gfx::RectF(0, start_pixel_offset, node_bounds.width(),
                              end_pixel_offset - start_pixel_offset);
      break;
    case ax::mojom::WritingDirection::kBtt: {
      int top = max_pixel_offset - end_pixel_offset;
      int bottom = max_pixel_offset - start_pixel_offset;
      out_bounds = gfx::RectF(0, top, node_bounds.width(), bottom - top);
      break;
    }
  }
  return out_bounds;
}

std::string AXNode::GetLanguage() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  // Walk up tree considering both detected and author declared languages.
  for (const AXNode* cur = this; cur; cur = cur->GetParent()) {
    // If language detection has assigned a language then we prefer that.
    const AXLanguageInfo* lang_info = cur->GetLanguageInfo();
    if (lang_info && !lang_info->language.empty())
      return lang_info->language;

    // If the page author has declared a language attribute we fallback to that.
    if (cur->HasStringAttribute(ax::mojom::StringAttribute::kLanguage))
      return cur->GetStringAttribute(ax::mojom::StringAttribute::kLanguage);
  }

  return std::string();
}

std::string AXNode::GetValueForControl() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  if (data().IsTextField()) {
    // Returns the value of a text field. If necessary, computes the value from
    // the field's internal representation in the accessibility tree, in order
    // to minimize cross-process communication between the renderer and the
    // browser processes.
    return GetStringAttribute(ax::mojom::StringAttribute::kValue);
  }

  if (data().IsRangeValueSupported())
    return GetTextForRangeValue();
  if (GetRole() == ax::mojom::Role::kColorWell)
    return GetValueForColorWell();
  if (!IsControl(GetRole()))
    return std::string();

  return GetStringAttribute(ax::mojom::StringAttribute::kValue);
}

std::ostream& operator<<(std::ostream& stream, const AXNode& node) {
  stream << node.data().ToString(/*verbose*/ false);
  if (node.GetUnignoredChildCountCrossingTreeBoundary()) {
    stream << " unignored_child_ids=";
    bool needs_comma = false;
    for (auto it = node.UnignoredChildrenBegin();
         it != node.UnignoredChildrenEnd(); ++it) {
      if (needs_comma) {
        stream << ",";
      } else {
        needs_comma = true;
      }
      stream << it.get()->data().id;
    }
  }
  return stream;
}

std::ostream& operator<<(std::ostream& stream, const AXNode* node) {
  if (!node) {
    return stream << "null";
  }

  return stream << *node;
}

bool AXNode::IsTable() const {
  return IsTableLike(GetRole());
}

absl::optional<int> AXNode::GetTableColCount() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;
  return static_cast<int>(table_info->col_count);
}

absl::optional<int> AXNode::GetTableRowCount() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;
  return static_cast<int>(table_info->row_count);
}

absl::optional<int> AXNode::GetTableAriaColCount() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;
  return absl::make_optional(table_info->aria_col_count);
}

absl::optional<int> AXNode::GetTableAriaRowCount() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;
  return absl::make_optional(table_info->aria_row_count);
}

absl::optional<int> AXNode::GetTableCellCount() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;

  return static_cast<int>(table_info->unique_cell_ids.size());
}

AXNode* AXNode::GetTableCellFromIndex(int index) const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return nullptr;

  // There is a table but there is no cell with the given index.
  if (index < 0 ||
      static_cast<size_t>(index) >= table_info->unique_cell_ids.size()) {
    return nullptr;
  }

  return tree_->GetFromId(
      table_info->unique_cell_ids[static_cast<size_t>(index)]);
}

AXNode* AXNode::GetTableCaption() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return nullptr;

  return tree_->GetFromId(table_info->caption_id);
}

AXNode* AXNode::GetTableCellFromCoords(int row_index, int col_index) const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return nullptr;

  // There is a table but the given coordinates are outside the table.
  if (row_index < 0 ||
      static_cast<size_t>(row_index) >= table_info->row_count ||
      col_index < 0 ||
      static_cast<size_t>(col_index) >= table_info->col_count) {
    return nullptr;
  }

  return tree_->GetFromId(table_info->cell_ids[static_cast<size_t>(row_index)]
                                              [static_cast<size_t>(col_index)]);
}

std::vector<AXNodeID> AXNode::GetTableColHeaderNodeIds() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return std::vector<AXNodeID>();

  std::vector<AXNodeID> col_header_ids;
  // Flatten and add column header ids of each column to |col_header_ids|.
  for (std::vector<AXNodeID> col_headers_at_index : table_info->col_headers) {
    col_header_ids.insert(col_header_ids.end(), col_headers_at_index.begin(),
                          col_headers_at_index.end());
  }

  return col_header_ids;
}

std::vector<AXNodeID> AXNode::GetTableColHeaderNodeIds(int col_index) const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return std::vector<AXNodeID>();

  if (col_index < 0 || static_cast<size_t>(col_index) >= table_info->col_count)
    return std::vector<AXNodeID>();

  return std::vector<AXNodeID>(
      table_info->col_headers[static_cast<size_t>(col_index)]);
}

std::vector<AXNodeID> AXNode::GetTableRowHeaderNodeIds(int row_index) const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return std::vector<AXNodeID>();

  if (row_index < 0 || static_cast<size_t>(row_index) >= table_info->row_count)
    return std::vector<AXNodeID>();

  return std::vector<AXNodeID>(
      table_info->row_headers[static_cast<size_t>(row_index)]);
}

std::vector<AXNodeID> AXNode::GetTableUniqueCellIds() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return std::vector<AXNodeID>();

  return std::vector<AXNodeID>(table_info->unique_cell_ids);
}

const std::vector<AXNode*>* AXNode::GetExtraMacNodes() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  // Should only be available on the table node itself, not any of its children.
  const AXTableInfo* table_info = tree_->GetTableInfo(this);
  if (!table_info)
    return nullptr;

  return &table_info->extra_mac_nodes;
}

bool AXNode::IsGenerated() const {
  bool is_generated_node = id() < 0;
#if DCHECK_IS_ON()
  // Currently, the only generated nodes are columns and table header
  // containers, and when those roles occur, they are always extra mac nodes.
  // This could change in the future.
  bool is_extra_mac_node_role =
      GetRole() == ax::mojom::Role::kColumn ||
      GetRole() == ax::mojom::Role::kTableHeaderContainer;
  DCHECK_EQ(is_generated_node, is_extra_mac_node_role);
#endif
  return is_generated_node;
}

//
// Table row-like nodes.
//

bool AXNode::IsTableRow() const {
  return ui::IsTableRow(GetRole());
}

absl::optional<int> AXNode::GetTableRowRowIndex() const {
  if (!IsTableRow())
    return absl::nullopt;

  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;

  const auto& iter = table_info->row_id_to_index.find(id());
  if (iter == table_info->row_id_to_index.end())
    return absl::nullopt;
  return static_cast<int>(iter->second);
}

std::vector<AXNodeID> AXNode::GetTableRowNodeIds() const {
  std::vector<AXNodeID> row_node_ids;
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return row_node_ids;

  for (AXNode* node : table_info->row_nodes)
    row_node_ids.push_back(node->id());

  return row_node_ids;
}

#if BUILDFLAG(IS_APPLE)

//
// Table column-like nodes. These nodes are only present on macOS.
//

bool AXNode::IsTableColumn() const {
  return ui::IsTableColumn(GetRole());
}

absl::optional<int> AXNode::GetTableColColIndex() const {
  if (!IsTableColumn())
    return absl::nullopt;

  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;

  int index = 0;
  for (const AXNode* node : table_info->extra_mac_nodes) {
    if (node == this)
      break;
    index++;
  }
  return index;
}

#endif  // BUILDFLAG(IS_APPLE)

//
// Table cell-like nodes.
//

bool AXNode::IsTableCellOrHeader() const {
  return IsCellOrTableHeader(GetRole());
}

absl::optional<int> AXNode::GetTableCellIndex() const {
  if (!IsTableCellOrHeader())
    return absl::nullopt;

  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;

  const auto& iter = table_info->cell_id_to_index.find(id());
  if (iter != table_info->cell_id_to_index.end())
    return static_cast<int>(iter->second);
  return absl::nullopt;
}

absl::optional<int> AXNode::GetTableCellColIndex() const {
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;

  absl::optional<int> index = GetTableCellIndex();
  if (!index)
    return absl::nullopt;

  return static_cast<int>(table_info->cell_data_vector[*index].col_index);
}

absl::optional<int> AXNode::GetTableCellRowIndex() const {
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;

  absl::optional<int> index = GetTableCellIndex();
  if (!index)
    return absl::nullopt;

  return static_cast<int>(table_info->cell_data_vector[*index].row_index);
}

absl::optional<int> AXNode::GetTableCellColSpan() const {
  // If it's not a table cell, don't return a col span.
  if (!IsTableCellOrHeader())
    return absl::nullopt;

  // Otherwise, try to return a colspan, with 1 as the default if it's not
  // specified.
  int col_span;
  if (GetIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan, &col_span))
    return col_span;
  return 1;
}

absl::optional<int> AXNode::GetTableCellRowSpan() const {
  // If it's not a table cell, don't return a row span.
  if (!IsTableCellOrHeader())
    return absl::nullopt;

  // Otherwise, try to return a row span, with 1 as the default if it's not
  // specified.
  int row_span;
  if (GetIntAttribute(ax::mojom::IntAttribute::kTableCellRowSpan, &row_span))
    return row_span;
  return 1;
}

absl::optional<int> AXNode::GetTableCellAriaColIndex() const {
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;

  absl::optional<int> index = GetTableCellIndex();
  if (!index)
    return absl::nullopt;

  int aria_col_index =
      static_cast<int>(table_info->cell_data_vector[*index].aria_col_index);
  // |aria-colindex| attribute is one-based, value less than 1 is invalid.
  // https://www.w3.org/TR/wai-aria-1.2/#aria-colindex
  return (aria_col_index > 0) ? absl::optional<int>(aria_col_index)
                              : absl::nullopt;
}

absl::optional<int> AXNode::GetTableCellAriaRowIndex() const {
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info)
    return absl::nullopt;

  absl::optional<int> index = GetTableCellIndex();
  if (!index)
    return absl::nullopt;

  int aria_row_index =
      static_cast<int>(table_info->cell_data_vector[*index].aria_row_index);
  // |aria-rowindex| attribute is one-based, value less than 1 is invalid.
  // https://www.w3.org/TR/wai-aria-1.2/#aria-rowindex
  return (aria_row_index > 0) ? absl::optional<int>(aria_row_index)
                              : absl::nullopt;
}

std::vector<AXNodeID> AXNode::GetTableCellColHeaderNodeIds() const {
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info || table_info->col_count <= 0)
    return std::vector<AXNodeID>();

  // If this node is not a cell, then return the headers for the first column.
  int col_index = GetTableCellColIndex().value_or(0);

  return std::vector<AXNodeID>(table_info->col_headers[col_index]);
}

void AXNode::GetTableCellColHeaders(std::vector<AXNode*>* col_headers) const {
  DCHECK(col_headers);

  std::vector<AXNodeID> col_header_ids = GetTableCellColHeaderNodeIds();
  IdVectorToNodeVector(col_header_ids, col_headers);
}

std::vector<AXNodeID> AXNode::GetTableCellRowHeaderNodeIds() const {
  const AXTableInfo* table_info = GetAncestorTableInfo();
  if (!table_info || table_info->row_count <= 0)
    return std::vector<AXNodeID>();

  // If this node is not a cell, then return the headers for the first row.
  int row_index = GetTableCellRowIndex().value_or(0);

  return std::vector<AXNodeID>(table_info->row_headers[row_index]);
}

void AXNode::GetTableCellRowHeaders(std::vector<AXNode*>* row_headers) const {
  DCHECK(row_headers);

  std::vector<AXNodeID> row_header_ids = GetTableCellRowHeaderNodeIds();
  IdVectorToNodeVector(row_header_ids, row_headers);
}

bool AXNode::IsCellOrHeaderOfAriaGrid() const {
  if (!IsTableCellOrHeader())
    return false;

  const AXNode* node = this;
  while (node && !node->IsTable())
    node = node->GetParent();
  if (!node)
    return false;

  return node->GetRole() == ax::mojom::Role::kGrid ||
         node->GetRole() == ax::mojom::Role::kTreeGrid;
}

AXTableInfo* AXNode::GetAncestorTableInfo() const {
  const AXNode* node = this;
  while (node && !node->IsTable())
    node = node->GetParent();
  if (node)
    return tree_->GetTableInfo(node);
  return nullptr;
}

void AXNode::IdVectorToNodeVector(const std::vector<AXNodeID>& ids,
                                  std::vector<AXNode*>* nodes) const {
  for (AXNodeID id : ids) {
    AXNode* node = tree_->GetFromId(id);
    if (node)
      nodes->push_back(node);
  }
}

absl::optional<int> AXNode::GetHierarchicalLevel() const {
  int hierarchical_level =
      GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel);

  // According to the WAI_ARIA spec, a defined hierarchical level value is
  // greater than 0.
  // https://www.w3.org/TR/wai-aria-1.1/#aria-level
  if (hierarchical_level > 0)
    return hierarchical_level;

  return absl::nullopt;
}

bool AXNode::IsOrderedSetItem() const {
  // Tree grid rows should be treated as ordered set items. Since we don't have
  // a separate row role for tree grid rows, we can't just add the Role::kRow to
  // IsItemLike. We need to validate that the row is indeed part of a tree grid.
  if (IsRowInTreeGrid(GetOrderedSet()))
    return true;

  return ui::IsItemLike(GetRole());
}

bool AXNode::IsOrderedSet() const {
  // Tree grid rows should be considered like ordered set items and a tree grid
  // like an ordered set. Continuing that logic, in order to compute the right
  // PosInSet and SetSize, row groups inside of a tree grid should also be
  // ordered sets.
  if (IsRowGroupInTreeGrid())
    return true;

  return ui::IsSetLike(GetRole());
}

// Uses AXTree's cache to calculate node's PosInSet.
absl::optional<int> AXNode::GetPosInSet() const {
  return tree_->GetPosInSet(*this);
}

// Uses AXTree's cache to calculate node's SetSize.
absl::optional<int> AXNode::GetSetSize() const {
  return tree_->GetSetSize(*this);
}

// Returns true if the role of ordered set matches the role of item.
// Returns false otherwise.
bool AXNode::SetRoleMatchesItemRole(const AXNode* ordered_set) const {
  ax::mojom::Role item_role = GetRole();

  // Tree grid rows should be treated as ordered set items.
  if (IsRowInTreeGrid(ordered_set))
    return true;

  // Switch on role of ordered set
  switch (ordered_set->GetRole()) {
    case ax::mojom::Role::kFeed:
      return item_role == ax::mojom::Role::kArticle;
    case ax::mojom::Role::kList:
      return item_role == ax::mojom::Role::kListItem;
    case ax::mojom::Role::kGroup:
      return item_role == ax::mojom::Role::kComment ||
             item_role == ax::mojom::Role::kListItem ||
             item_role == ax::mojom::Role::kMenuItem ||
             item_role == ax::mojom::Role::kMenuItemRadio ||
             item_role == ax::mojom::Role::kMenuItemCheckBox ||
             item_role == ax::mojom::Role::kListBoxOption ||
             item_role == ax::mojom::Role::kTreeItem;
    case ax::mojom::Role::kMenu:
      return item_role == ax::mojom::Role::kMenuItem ||
             item_role == ax::mojom::Role::kMenuItemRadio ||
             item_role == ax::mojom::Role::kMenuItemCheckBox;
    case ax::mojom::Role::kMenuBar:
      return item_role == ax::mojom::Role::kMenuItem ||
             item_role == ax::mojom::Role::kMenuItemRadio ||
             item_role == ax::mojom::Role::kMenuItemCheckBox;
    case ax::mojom::Role::kTabList:
      return item_role == ax::mojom::Role::kTab;
    case ax::mojom::Role::kTree:
    case ax::mojom::Role::kTreeItem:
      return item_role == ax::mojom::Role::kTreeItem;
    case ax::mojom::Role::kListBox:
      return item_role == ax::mojom::Role::kListBoxOption;
    case ax::mojom::Role::kMenuListPopup:
      return item_role == ax::mojom::Role::kMenuListOption ||
             item_role == ax::mojom::Role::kMenuItem ||
             item_role == ax::mojom::Role::kMenuItemRadio ||
             item_role == ax::mojom::Role::kMenuItemCheckBox;
    case ax::mojom::Role::kRadioGroup:
      return item_role == ax::mojom::Role::kRadioButton;
    case ax::mojom::Role::kDescriptionList:
      // Only the term for each description list entry should receive posinset
      // and setsize.
      return item_role == ax::mojom::Role::kDescriptionListTerm ||
             item_role == ax::mojom::Role::kTerm;
    case ax::mojom::Role::kComboBoxSelect:
      // kComboBoxSelect wraps a kMenuListPopUp.
      return item_role == ax::mojom::Role::kMenuListPopup;
    default:
      return false;
  }
}

bool AXNode::IsIgnoredContainerForOrderedSet() const {
  return IsIgnored() || IsEmbeddedGroup() ||
         GetRole() == ax::mojom::Role::kLabelText ||
         GetRole() == ax::mojom::Role::kListItem ||
         GetRole() == ax::mojom::Role::kGenericContainer ||
         GetRole() == ax::mojom::Role::kScrollView ||
         GetRole() == ax::mojom::Role::kUnknown;
}

bool AXNode::IsRowInTreeGrid(const AXNode* ordered_set) const {
  // Tree grid rows have the requirement of being focusable, so we use it to
  // avoid iterating over rows that clearly aren't part of a tree grid.
  if (GetRole() != ax::mojom::Role::kRow || !ordered_set || !IsFocusable())
    return false;

  if (ordered_set->GetRole() == ax::mojom::Role::kTreeGrid)
    return true;

  return ordered_set->IsRowGroupInTreeGrid();
}

bool AXNode::IsRowGroupInTreeGrid() const {
  // To the best of our understanding, row groups can't be nested.
  //
  // According to https://www.w3.org/TR/wai-aria-1.1/#rowgroup, a row group is a
  // "structural equivalent to the thead, tfoot, and tbody elements in an HTML
  // table". It is specified in the spec of the thead, tfoot and tbody elements
  // that they need to be children of a table element, meaning that there can
  // only be one level of such elements. We assume the same for row groups.
  if (GetRole() != ax::mojom::Role::kRowGroup)
    return false;

  AXNode* ordered_set = GetOrderedSet();
  return ordered_set && ordered_set->GetRole() == ax::mojom::Role::kTreeGrid;
}

int AXNode::UpdateUnignoredCachedValuesRecursive(int startIndex) {
  int count = 0;
  for (AXNode* child : children()) {
    if (child->IsIgnored()) {
      child->unignored_index_in_parent_ = 0;
      count += child->UpdateUnignoredCachedValuesRecursive(startIndex + count);
    } else {
      child->unignored_index_in_parent_ = startIndex + count++;
    }
  }
  unignored_child_count_ = count;
  return count;
}

// Finds ordered set that contains node.
// Is not required for set's role to match node's role.
AXNode* AXNode::GetOrderedSet() const {
  AXNode* result = GetParent();
  // Continue walking up while parent is invalid, ignored, a generic container,
  // unknown, or embedded group.
  while (result && result->IsIgnoredContainerForOrderedSet()) {
    result = result->GetParent();
  }

  return result;
}

bool AXNode::IsReadOnlySupported() const {
  // Grid cells and headers can't be derived solely from the role (need to check
  // the ancestor chain) so check this first.
  if (IsCellOrHeaderOfAriaGrid())
    return true;

  return ui::IsReadOnlySupported(GetRole());
}

bool AXNode::IsReadOnlyOrDisabled() const {
  switch (data().GetRestriction()) {
    case ax::mojom::Restriction::kReadOnly:
    case ax::mojom::Restriction::kDisabled:
      return true;
    case ax::mojom::Restriction::kNone: {
      if (HasState(ax::mojom::State::kEditable) ||
          HasState(ax::mojom::State::kRichlyEditable)) {
        return false;
      }

      if (ShouldHaveReadonlyStateByDefault(GetRole()))
        return true;

      // When readonly is not supported, we assume that the node is always
      // read-only and mark it as such since this is the default behavior.
      return !IsReadOnlySupported();
    }
  }
}

AXNode* AXNode::ComputeLastUnignoredChildRecursive() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  if (children().empty())
    return nullptr;

  for (int i = static_cast<int>(children().size()) - 1; i >= 0; --i) {
    AXNode* child = children_[i];
    if (!child->IsIgnored())
      return child;

    AXNode* descendant = child->ComputeLastUnignoredChildRecursive();
    if (descendant)
      return descendant;
  }
  return nullptr;
}

AXNode* AXNode::ComputeFirstUnignoredChildRecursive() const {
  DCHECK(!tree_->GetTreeUpdateInProgressState());
  for (size_t i = 0; i < children().size(); i++) {
    AXNode* child = children_[i];
    if (!child->IsIgnored())
      return child;

    AXNode* descendant = child->ComputeFirstUnignoredChildRecursive();
    if (descendant)
      return descendant;
  }
  return nullptr;
}

std::string AXNode::GetTextForRangeValue() const {
  DCHECK(data().IsRangeValueSupported());
  std::string range_value =
      GetStringAttribute(ax::mojom::StringAttribute::kValue);
  float numeric_value;
  if (range_value.empty() &&
      GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange,
                        &numeric_value)) {
    // This method of number to string conversion creates a localized string
    // and avoids padding with extra zeros after the decimal point.
    // For example, 3.5 is converted to "3.5" rather than "3.50000".
    return base::StringPrintf("%g", numeric_value);
  }
  return range_value;
}

std::string AXNode::GetValueForColorWell() const {
  DCHECK_EQ(GetRole(), ax::mojom::Role::kColorWell);
  // static cast because SkColor is a 4-byte unsigned int
  unsigned int color = static_cast<unsigned int>(
      GetIntAttribute(ax::mojom::IntAttribute::kColorValue));

  unsigned int red = SkColorGetR(color);
  unsigned int green = SkColorGetG(color);
  unsigned int blue = SkColorGetB(color);
  return base::StringPrintf("%d%% red %d%% green %d%% blue", red * 100 / 255,
                            green * 100 / 255, blue * 100 / 255);
}

bool AXNode::IsIgnored() const {
  // If the focus has moved, then it could make a previously ignored node
  // unignored or vice versa. We never ignore focused nodes otherwise users of
  // assistive software might be unable to interact with the webpage.
  return AXTree::ComputeNodeIsIgnored(&tree_->data(), data());
}

bool AXNode::IsIgnoredForTextNavigation() const {
  // Splitters do not contribute anything to the tree's text representation, so
  // stopping on a splitter would erroniously appear to a screen reader user
  // that the cursor has stopped on the next unignored object.
  if (GetRole() == ax::mojom::Role::kSplitter)
    return true;

  // A generic container without any unignored children that is not editable
  // should not be used for text-based navigation. Such nodes don't make sense
  // for screen readers to land on, since no role / text will be announced and
  // no action is possible.
  if (GetRole() == ax::mojom::Role::kGenericContainer &&
      !GetUnignoredChildCount() && !HasState(ax::mojom::State::kEditable)) {
    return true;
  }

  return false;
}

bool AXNode::IsInvisibleOrIgnored() const {
  return id() != tree_->data().focus_id && (IsIgnored() || data_.IsInvisible());
}

bool AXNode::IsChildOfLeaf() const {
  // TODO(nektar): Cache this state in `AXComputedNodeData`.
  for (const AXNode* ancestor = GetUnignoredParent(); ancestor;
       ancestor = ancestor->GetUnignoredParent()) {
    if (ancestor->IsLeaf())
      return true;
  }
  return false;
}

bool AXNode::IsEmptyLeaf() const {
  if (!IsLeaf())
    return false;
  if (GetUnignoredChildCountCrossingTreeBoundary())
    return !GetTextContentLengthUTF8();
  // Text exposed by ignored leaf (text) nodes is not exposed to the platforms'
  // accessibility layer, hence such leaf nodes are in effect empty.
  return IsIgnored() || !GetTextContentLengthUTF8();
}

bool AXNode::IsLeaf() const {
  // A node is a leaf if it has no descendants, i.e. if it is at the bottom of
  // the tree, regardless whether it is ignored or not.
  if (!GetChildCountCrossingTreeBoundary())
    return true;

  // Ignored nodes with any kind of descendants, (ignored or unignored), cannot
  // be leaves because: A) If some of their descendants are unignored then those
  // descendants need to be exposed to the platform layer, and B) If all of
  // their descendants are ignored they cannot be at the bottom of the platform
  // tree since that tree does not expose any ignored objects.
  if (IsIgnored())
    return false;

  // An unignored node is a leaf if all of its descendants are ignored.
  int child_count = GetUnignoredChildCountCrossingTreeBoundary();
  if (!child_count)
    return true;

#if BUILDFLAG(IS_WIN)
  // On Windows, we want to hide the subtree of a collapsed <select> element.
  // Otherwise, ATs are always going to announce its options whether it's
  // collapsed or expanded. In the AXTree, this element corresponds to a node
  // with role ax::mojom::Role::kComboBoxSelect that is the parent of a node
  // with // role ax::mojom::Role::kMenuListPopup.
  if (IsCollapsedMenuListSelect())
    return true;
#endif  // BUILDFLAG(IS_WIN)

  // These types of objects may have children that we use as internal
  // implementation details, but we want to expose them as leaves to platform
  // accessibility APIs because screen readers might be confused if they find
  // any children.
  if (data().IsAtomicTextField() || IsText())
    return true;

  // Non atomic text fields may have children that we want to expose.
  // For example, a <div contenteditable> may have child elements such as
  // more <div>s that we want to expose.
  if (data().IsNonAtomicTextField())
    return false;

  // Roles whose children are only presentational according to the ARIA and
  // HTML5 Specs should be hidden from screen readers.
  switch (GetRole()) {
    // According to the ARIA and Core-AAM specs:
    // https://w3c.github.io/aria/#button,
    // https://www.w3.org/TR/core-aam-1.1/#exclude_elements
    // buttons' children are presentational only and should be hidden from
    // screen readers. However, we cannot enforce the leafiness of buttons
    // because they may contain many rich, interactive descendants such as a day
    // in a calendar, and screen readers will need to interact with these
    // contents. See https://crbug.com/689204.
    // So we decided to not enforce the leafiness of buttons and expose all
    // children.
    case ax::mojom::Role::kButton:
      return false;
    case ax::mojom::Role::kImage: {
      // HTML images (i.e. <img> elements) are not leaves when they are image
      // maps. Therefore, do not truncate descendants except in the case where
      // ARIA role=img or role=image because that's how we want to treat
      // ARIA-based images.
      const std::string role =
          GetStringAttribute(ax::mojom::StringAttribute::kRole);
      return role == "img" || role == "image";
    }
    case ax::mojom::Role::kDocCover:
    case ax::mojom::Role::kGraphicsSymbol:
    case ax::mojom::Role::kMeter:
    case ax::mojom::Role::kScrollBar:
    case ax::mojom::Role::kSpinButton:
    case ax::mojom::Role::kSlider:
    case ax::mojom::Role::kSplitter:
    case ax::mojom::Role::kProgressIndicator:
      return true;
    case ax::mojom::Role::kCheckBox:
    case ax::mojom::Role::kListBoxOption:
    // role="math" is flat. But always return false for kMathMLMath since the
    // children of a <math> tag should be exposed to make MathML accessible.
    case ax::mojom::Role::kMath:
    case ax::mojom::Role::kMenuListOption:
    case ax::mojom::Role::kMenuItem:
    case ax::mojom::Role::kMenuItemCheckBox:
    case ax::mojom::Role::kMenuItemRadio:
    case ax::mojom::Role::kPopUpButton:
    case ax::mojom::Role::kToggleButton:
    case ax::mojom::Role::kRadioButton:
    case ax::mojom::Role::kSwitch:
    case ax::mojom::Role::kTab: {
      // For historical reasons, truncate the children of these roles when they
      // have a single text child and are not editable.
      // TODO(accessibility) Consider removing this in the future, and exposing
      // all descendants, as it seems ATs do a good job of avoiding redundant
      // speech even if they have a text child. Removing this rule would allow
      // AT users to select any text visible in the page, and ensure that all
      // text is available to ATs that use the position of objects on the
      // screen. This has been manually tested in JAWS, NVDA, VoiceOver, Orca
      // and ChromeVox.
      // Note that the ARIA spec says, "User agents SHOULD NOT expose
      // descendants of this element through the platform accessibility API. If
      // user agents do not hide the descendant nodes, some information may be
      // read twice." However, this is not a MUST, and in non-simple cases
      // Chrome and Firefox already expose descendants, without causing issues.
      // Allow up to 2 text nodes so that list items with bullets are leaves.
      if (child_count > 2 || HasState(ax::mojom::State::kEditable))
        return false;
      const AXNode* child1 = GetFirstUnignoredChildCrossingTreeBoundary();
      if (!child1 || !child1->IsText())
        return false;
      const AXNode* child2 = child1->GetNextSibling();
      return !child2 || child2->IsText();
    }
    default:
      return false;
  }
}

bool AXNode::IsFocusable() const {
  return HasState(ax::mojom::State::kFocusable) ||
         IsLikelyARIAActiveDescendant();
}

bool AXNode::IsLikelyARIAActiveDescendant() const {
  // Should be menu item, option, etc.
  if (!ui::IsLikelyActiveDescendantRole(GetRole()))
    return false;

  // False if invisible, ignored or disabled.
  if (IsInvisibleOrIgnored() ||
      GetIntAttribute(ax::mojom::IntAttribute::kRestriction) ==
          static_cast<int>(ax::mojom::Restriction::kDisabled)) {
    return false;
  }

  // False if no ARIA role -- not a perfect rule, but a reasonable heuristic.
  if (!HasStringAttribute(ax::mojom::StringAttribute::kRole))
    return false;

  // False if no id attribute -- nothing to point to.
  // This requirement may need to be removed if ARIA element reflection is
  // implemented. HTML attribute serialization must currently be turned on in
  // order to pass this requirement.
  if (!HasHtmlAttribute("id"))
    return false;

  // Finally, check for the required ancestor.
  for (AXNode* ancestor_node = GetUnignoredParent(); ancestor_node;
       ancestor_node = ancestor_node->GetUnignoredParent()) {
    // Check for an ancestor with aria-activedescendant.
    if (ancestor_node->HasIntAttribute(
            ax::mojom::IntAttribute::kActivedescendantId)) {
      return true;
    }
    // Check for an ancestor listbox that is controlled by a textfield combobox
    // that also has an aria-activedescendant.
    // Note: blink will map aria-owns to aria-controls in the textfield combobox
    // case as it was the older technique, but treating as an actual aria-owns
    // makes no sense as a textfield cannot have children.
    if (ancestor_node->GetRole() == ax::mojom::Role::kListBox) {
      std::set<AXNodeID> nodes_that_control_this_list =
          tree()->GetReverseRelations(ax::mojom::IntListAttribute::kControlsIds,
                                      ancestor_node->id());
      for (AXNodeID id : nodes_that_control_this_list) {
        if (AXNode* node = tree()->GetFromId(id)) {
          if (ui::IsTextField(node->GetRole())) {
            return node->HasIntAttribute(
                ax::mojom::IntAttribute::kActivedescendantId);
          }
        }
      }
    }
    // TODO(aleventhal) Re-add this once Google Slides no longer needs
    // special hack where the aria-activedescendant is on a containing
    // contenteditable, which is currently done in the slides thumb strip for
    // copy/paste reasons. See matching code in AXPlatformNode win which clears
    // IA2_STATE_EDITABLE for this case, but requires the descendant tree items
    // to have the FOCUSABLE state. See also the related dump tree test
    // aria-focusable-subwidget-not-editable.html.
    // (IsContainerWithSelectableChildren(ancestor_node->GetRole())) {
    //   // No need to check more ancestors.
    //   break;
    // }
  }

  return false;
}

bool AXNode::IsInListMarker() const {
  if (GetRole() == ax::mojom::Role::kListMarker)
    return true;

  // The children of a list marker node can only be text nodes.
  if (!IsText())
    return false;

  // There is no need to iterate over all the ancestors of the current node
  // since a list marker has descendants that are only 2 levels deep, i.e.:
  // AXLayoutObject role=kListMarker
  // ++StaticText
  // ++++InlineTextBox
  AXNode* parent_node = GetUnignoredParent();
  if (!parent_node)
    return false;

  if (parent_node->GetRole() == ax::mojom::Role::kListMarker)
    return true;

  AXNode* grandparent_node = parent_node->GetUnignoredParent();
  return grandparent_node &&
         grandparent_node->GetRole() == ax::mojom::Role::kListMarker;
}

bool AXNode::IsCollapsedMenuListSelect() const {
  return HasState(ax::mojom::State::kCollapsed) &&
         GetRole() == ax::mojom::Role::kComboBoxSelect;
}

bool AXNode::IsRootWebAreaForPresentationalIframe() const {
  if (!ui::IsPlatformDocument(GetRole()))
    return false;
  const AXNode* parent = GetUnignoredParentCrossingTreeBoundary();
  if (!parent)
    return false;
  return parent->GetRole() == ax::mojom::Role::kIframePresentational;
}

AXNode* AXNode::GetCollapsedMenuListSelectAncestor() const {
  AXNode* node = GetOrderedSet();

  if (!node)
    return nullptr;

  // The ordered set returned is either the popup element child of the select
  // combobox or the select combobox itself. We need |node| to point to the
  // select combobox.
  if (node->GetRole() != ax::mojom::Role::kComboBoxSelect) {
    node = node->GetParent();
    if (!node)
      return nullptr;
  }

  return node->IsCollapsedMenuListSelect() ? node : nullptr;
}

bool AXNode::IsEmbeddedGroup() const {
  if (GetRole() != ax::mojom::Role::kGroup || !GetParent())
    return false;

  return ui::IsSetLike(GetParent()->GetRole());
}

AXNode* AXNode::GetLowestPlatformAncestor() const {
  AXNode* current_node = const_cast<AXNode*>(this);
  AXNode* lowest_unignored_node = current_node;
  for (; lowest_unignored_node && lowest_unignored_node->IsIgnored();
       lowest_unignored_node = lowest_unignored_node->GetParent()) {
  }

  // `highest_leaf_node` could be nullptr.
  AXNode* highest_leaf_node = lowest_unignored_node;
  // For the purposes of this method, a leaf node does not include leaves in the
  // internal accessibility tree, only in the platform exposed tree.
  for (AXNode* ancestor_node = lowest_unignored_node; ancestor_node;
       ancestor_node = ancestor_node->GetUnignoredParent()) {
    if (ancestor_node->IsLeaf())
      highest_leaf_node = ancestor_node;
  }
  if (highest_leaf_node)
    return highest_leaf_node;

  if (lowest_unignored_node)
    return lowest_unignored_node;
  return current_node;
}

AXNode* AXNode::GetTextFieldAncestor() const {
  // The descendants of a text field usually have State::kEditable, however in
  // the case of Role::kSearchBox or Role::kSpinButton being the text field
  // ancestor, its immediate descendant can have Role::kGenericContainer without
  // State::kEditable. Same with inline text boxes and placeholder text.
  // TODO(nektar): Fix all such inconsistencies in Blink.
  //
  // Also, ARIA text and search boxes may not have the contenteditable attribute
  // set, but they should still be treated the same as all other text fields.
  // (See `AXNodeData::IsAtomicTextField()` for more details.)
  for (AXNode* ancestor = const_cast<AXNode*>(this); ancestor;
       ancestor = ancestor->GetUnignoredParent()) {
    if (ancestor->data().IsTextField())
      return ancestor;
  }
  return nullptr;
}

AXNode* AXNode::GetTextFieldInnerEditorElement() const {
  if (!data().IsAtomicTextField() || !GetUnignoredChildCount())
    return nullptr;

  // Text fields wrap their static text and inline text boxes in generic
  // containers, and some, like <input type="search">, wrap the wrapper as well.
  // There are several incarnations of this structure.
  // 1. An empty atomic text field:
  // -- Generic container <-- there can be any number of these in a chain.
  //    However, some empty text fields have the below structure, with empty
  //    text boxes.
  // 2. A single line, an atomic text field with some text in it:
  // -- Generic container <-- there can be any number of these in a chain.
  // ---- Static text
  // ------ Inline text box children (zero or more)
  // ---- Line Break (optional,  a placeholder break element if the text data
  //                    ends with '\n' or '\r')
  // 3. A multiline textarea with some text in it:
  //    Similar to #2, but can repeat the static text, line break children
  //    multiple times.

  AXNode* text_container = GetDeepestFirstUnignoredChild();
  DCHECK(text_container) << "Unable to retrieve deepest unignored child on\n"
                         << *this;
  // Non-empty text fields expose a set of static text objects with one or more
  // inline text boxes each. On some platforms, such as Android, we don't enable
  // inline text boxes, and only the static text objects are exposed.
  if (text_container->GetRole() == ax::mojom::Role::kInlineTextBox)
    text_container = text_container->GetUnignoredParent();

  // Get the parent of the static text or the line break, if any; a line break
  // is possible when the field contains a line break as its first character.
  if (text_container->GetRole() == ax::mojom::Role::kStaticText ||
      text_container->GetRole() == ax::mojom::Role::kLineBreak) {
    text_container = text_container->GetUnignoredParent();
  }

  DCHECK(text_container) << "Unexpected unignored parent while computing text "
                            "field inner editor element on\n"
                         << *this;
  if (text_container->GetRole() == ax::mojom::Role::kGenericContainer)
    return text_container;
  return nullptr;
}

AXNode* AXNode::GetSelectionContainer() const {
  // Avoid walking ancestors if the role cannot support the selectable state.
  if (!IsSelectSupported(GetRole()))
    return nullptr;
  if (IsInvisibleOrIgnored() ||
      GetIntAttribute(ax::mojom::IntAttribute::kRestriction) ==
          static_cast<int>(ax::mojom::Restriction::kDisabled)) {
    return nullptr;
  }
  for (AXNode* ancestor = const_cast<AXNode*>(this); ancestor;
       ancestor = ancestor->GetUnignoredParent()) {
    if (ui::IsContainerWithSelectableChildren(ancestor->GetRole()))
      return ancestor;
  }
  return nullptr;
}

AXNode* AXNode::GetTableAncestor() const {
  for (AXNode* ancestor = const_cast<AXNode*>(this); ancestor;
       ancestor = ancestor->GetUnignoredParent()) {
    if (ancestor->IsTable())
      return ancestor;
  }
  return nullptr;
}

bool AXNode::IsDescendantOfAtomicTextField() const {
  AXNode* text_field_node = GetTextFieldAncestor();
  return text_field_node && text_field_node->data().IsAtomicTextField();
}

}  // namespace ui