// Copyright 2017 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/views/accessibility/view_accessibility.h"

#include <algorithm>
#include <string>
#include <utility>

#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/notimplemented.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/buildflags.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/accessibility/atomic_view_ax_tree_manager.h"
#include "ui/views/accessibility/ax_update_notifier.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/tree/widget_ax_manager.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"

#if BUILDFLAG(IS_WIN)
#include "ui/views/accessibility/view_ax_platform_node_delegate_win.h"
#elif BUILDFLAG(IS_MAC)
#include "ui/views/accessibility/view_ax_platform_node_delegate_mac.h"
#elif BUILDFLAG(IS_LINUX)
#include "ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h"
#endif

#if defined(USE_AURA)
#include "ui/views/accessibility/ax_aura_obj_cache.h"
#endif

namespace views {

namespace {

bool IsValidRoleForViews(ax::mojom::Role role) {
  switch (role) {
    // These roles all have special meaning and shouldn't ever be
    // set on a View.
    case ax::mojom::Role::kDesktop:
    case ax::mojom::Role::kIframe:
    case ax::mojom::Role::kIframePresentational:
    case ax::mojom::Role::kPdfRoot:
    case ax::mojom::Role::kRootWebArea:
    case ax::mojom::Role::kSvgRoot:
    case ax::mojom::Role::kUnknown:
      return false;

    // The role kDocument should not be allowed on Views, but it needs to be
    // allowed temporarily for the CaptionBubbleLabel view. This is because the
    // CaptionBubbleLabel is designed to be interacted with by a braille display
    // in virtual buffer mode. In order to activate the virtual buffer in NVDA,
    // we set the role to kDocument and the readonly restriction.
    //
    // TODO(crbug.com/339479333): Investigate this further to either add a
    // views-specific role that maps to the document role on the various
    // platform APIs, or remove this comment and update the allowed usage of the
    // kDocument role.
    case ax::mojom::Role::kDocument:  // Used for ARIA role="document".
      return true;
    default:
      return true;
  }
}

}  // namespace

#define RETURN_IF_UNAVAILABLE()                                          \
  if (is_widget_closed_) {                                               \
    return;                                                              \
  }                                                                      \
  CHECK(initialization_state_ != State::kInitializing)                   \
      << "Accessibility cache setters must not be used during complete " \
         "initialization of the accessibility cache. Instead, set the "  \
         "attributes directly on `AXNodeData` parameter.";

// static
std::unique_ptr<ViewAccessibility> ViewAccessibility::Create(View* view) {
  // With the feature enabled, the accessibility tree for Views is built using
  // the WidgetAXManager. ViewAccessibility is only used to manage the current
  // accessibility state for a view.
  if (IsViewsAccessibilityTreeEnabled()) {
    // Cannot use std::make_unique because constructor is protected.
    return base::WrapUnique(new ViewAccessibility(view));
  }

#if !BUILDFLAG(HAS_NATIVE_ACCESSIBILITY)
  // Cannot use std::make_unique because constructor is protected.
  return base::WrapUnique(new ViewAccessibility(view));
#elif BUILDFLAG(IS_WIN)
  return ViewAXPlatformNodeDelegateWin::CreatePlatformSpecific(view);
#elif BUILDFLAG(IS_MAC)
  return ViewAXPlatformNodeDelegateMac::CreatePlatformSpecific(view);
#elif BUILDFLAG(IS_LINUX)
  return ViewAXPlatformNodeDelegateAuraLinux::CreatePlatformSpecific(view);
#endif
}

// static
bool ViewAccessibility::IsViewsAccessibilityTreeEnabled() {
#if BUILDFLAG(IS_CHROMEOS)
  // ChromeOS never uses the Views accessibility tree, even if the feature is
  // enabled elsewhere. Instead, it uses the Automation API.
  return false;
#else
  return ::features::IsAccessibilityTreeForViewsEnabled();
#endif
}

ViewAccessibility::ViewAccessibility(View* view) : view_(view) {
  data_.id = GetUniqueId();
  CHECK(data_.id != ui::kInvalidAXNodeID);
}

ViewAccessibility::~ViewAccessibility() = default;

void ViewAccessibility::AddVirtualChildView(
    std::unique_ptr<AXVirtualView> virtual_view) {
  AddVirtualChildViewAt(std::move(virtual_view), virtual_children_.size());
}

void ViewAccessibility::AddVirtualChildViewAt(
    std::unique_ptr<AXVirtualView> virtual_view,
    size_t index) {
  CHECK(view_);
  DCHECK(virtual_view);
  DCHECK_LE(index, virtual_children_.size());

  if (virtual_view->parent_view() == this) {
    return;
  }
  DCHECK(!virtual_view->parent_view()) << "This |view| already has a View "
                                          "parent. Call RemoveVirtualChildView "
                                          "first.";
  DCHECK(!virtual_view->virtual_parent_view()) << "This |view| already has an "
                                                  "AXVirtualView parent. Call "
                                                  "RemoveChildView first.";
  // The first virtual child hides the real children. Notify before the insert,
  // while GetChildren still returns them.
  if (virtual_children_.empty()) {
    NotifyChildrenRemoved();
  }

  virtual_view->set_parent_view(this);
  auto insert_iterator =
      virtual_children_.begin() + static_cast<ptrdiff_t>(index);
  virtual_children_.insert(insert_iterator, std::move(virtual_view));

  AXVirtualView* added_view = virtual_children_[index].get();
  added_view->OnViewHasNewAncestor(view_);
  added_view->OnOwnerViewChanged();

  AXUpdateNotifier::Get()->NotifyChildAdded(added_view, this);
  added_view->OnVirtualViewAddedToWidget();
  FireLiveRegionChangedIfNeeded(LiveRegionEventTrigger::kAdditions);
}

std::unique_ptr<AXVirtualView> ViewAccessibility::RemoveVirtualChildView(
    AXVirtualView* virtual_view) {
  DCHECK(virtual_view);
  auto cur_index = GetIndexOf(virtual_view);
  if (!cur_index.has_value()) {
    return {};
  }

  AXVirtualView* child_to_remove = virtual_children_[cur_index.value()].get();
  child_to_remove->OnVirtualViewRemovedFromWidget();

  std::unique_ptr<AXVirtualView> child =
      std::move(virtual_children_[cur_index.value()]);
  virtual_children_.erase(virtual_children_.begin() +
                          static_cast<ptrdiff_t>(cur_index.value()));

  FireLiveRegionChangedIfNeeded(LiveRegionEventTrigger::kRemovals);

  child->set_parent_view(nullptr);
  child->OnOwnerViewChanged();

  // If the removed child (or any of its descendants) was the active descendant,
  // clear it.
  if (data_.HasIntAttribute(ax::mojom::IntAttribute::kActivedescendantId)) {
    int32_t active_descendant_id =
        data_.GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId);
    if (AXVirtualView* active_view =
            AXVirtualView::GetFromId(active_descendant_id)) {
      if (child->Contains(active_view)) {
        ClearActiveDescendant();
      }
    }
  }

  AXUpdateNotifier::Get()->NotifyChildRemoved(child.get(), this);

  // Removing the last virtual child exposes the real children again.
  if (virtual_children_.empty()) {
    NotifyChildrenAdded();
  }

  return child;
}

void ViewAccessibility::RemoveAllVirtualChildViews() {
  while (!virtual_children_.empty()) {
    RemoveVirtualChildView(virtual_children_.back().get());
  }
}

bool ViewAccessibility::Contains(const AXVirtualView* virtual_view) const {
  DCHECK(virtual_view);
  for (const auto& virtual_child : virtual_children_) {
    // AXVirtualView::Contains() also checks if the provided virtual view is the
    // same as |this|.
    if (virtual_child->Contains(virtual_view)) {
      return true;
    }
  }
  return false;
}

std::optional<size_t> ViewAccessibility::GetIndexOf(
    const AXVirtualView* virtual_view) const {
  DCHECK(virtual_view);
  const auto iter = std::ranges::find(virtual_children_, virtual_view,
                                      &std::unique_ptr<AXVirtualView>::get);
  return iter != virtual_children_.end()
             ? std::make_optional(
                   static_cast<size_t>(iter - virtual_children_.begin()))
             : std::nullopt;
}

void ViewAccessibility::GetAccessibleNodeData(ui::AXNodeData* data) const {
  // TODO(crbug.com/40672441): Investigate if we can safely remove this now that
  // all accessibility attributes are cached directly.
  if (is_widget_closed_) {
    // Views may misbehave if their widget is closed; set "null-like" attributes
    // rather than possibly crashing.
    SetDataForClosedWidget(data);
    return;
  }

  *data = data_;

  // Expose the View's id as the author-unique id. Virtual views have no
  // backing View and retain their existing identity.
  if (view_) {
    const int view_id = view_->GetID();
    if (view_id &&
        !data->HasStringAttribute(ax::mojom::StringAttribute::kHtmlId)) {
      data->AddStringAttribute(ax::mojom::StringAttribute::kHtmlId,
                               "view_" + base::NumberToString(view_id));
    }
  }
}

void ViewAccessibility::NotifyEvent(ax::mojom::Event event_type,
                                    bool send_native_event) {
  CHECK(view_);
  // If `ready_to_notify_events_` is false, it means we are initializing
  // property values. In this specific case, we do not want to notify platform
  // assistive technologies that a property has changed.
  if (!ready_to_notify_events_) {
    return;
  }

  Widget* const widget = GetWidget();
  // If it belongs to a widget but its native widget is already destructed, do
  // not send such accessibility event as it's unexpected to send such events
  // during destruction, and is likely to lead to crashes/problems.
  if (widget && !widget->GetNativeView()) {
    return;
  }

  AXUpdateNotifier::Get()->NotifyViewEvent(view_, event_type);

  if (send_native_event && widget) {
    FireNativeEvent(event_type);
  }

  view_->OnAccessibilityEvent(event_type);
}

bool ViewAccessibility::IsAccessibilityFocusable() const {
  bool focusable = data_.HasState(ax::mojom::State::kFocusable);
  if (focusable) {
    CHECK(!should_be_invisible_ &&
          !data_.HasState(ax::mojom::State::kInvisible))
        << "A view that is focusable should not be marked as invisible. This is"
           "also enforced in RunAccessibilityPaintChecks.";
  }
  return focusable;
}

bool ViewAccessibility::IsFocusedForTesting() const {
  return view_->HasFocus() &&
         !data_.HasIntAttribute(ax::mojom::IntAttribute::kActivedescendantId);
}

void ViewAccessibility::SetPopupFocusOverride() {
  NOTIMPLEMENTED();
}

void ViewAccessibility::EndPopupFocusOverride() {
  NOTIMPLEMENTED();
}

void ViewAccessibility::FireFocusAfterMenuClose() {
  NotifyEvent(ax::mojom::Event::kFocusAfterMenuClose, true);
}

void ViewAccessibility::NotifyTransientFocus() {
  if (IsViewsAccessibilityTreeEnabled()) {
    if (!ready_to_notify_events_) {
      return;
    }

    Widget* const widget = GetWidget();
    if (!widget || !widget->GetNativeView()) {
      return;
    }

    if (auto* ax_manager = widget->ax_manager()) {
      ax_manager->OnTransientFocusRequested(*this);
    }
    return;
  }

  NotifyEvent(ax::mojom::Event::kFocus, true);
}

void ViewAccessibility::SetIsLeaf(bool value) {
  if (value == ViewAccessibility::IsLeaf()) {
    return;
  }

  // GetChildren returns nothing for a leaf, so notify while it still returns
  // the children being hidden, and only once it returns the ones being shown.
  if (value) {
    NotifyChildrenRemoved();
    PruneSubtree();
    is_leaf_ = value;
  } else {
    is_leaf_ = value;
    UnpruneSubtree();
    NotifyChildrenAdded();
  }
}

bool ViewAccessibility::IsLeaf() const {
  return is_leaf_;
}

bool ViewAccessibility::IsChildOfLeaf() const {
  return pruned_;
}

void ViewAccessibility::SetReadOnly(bool read_only) {
  if ((read_only &&
       data_.GetRestriction() == ax::mojom::Restriction::kReadOnly) ||
      (!read_only &&
       data_.GetRestriction() != ax::mojom::Restriction::kReadOnly)) {
    return;
  }

  if (read_only) {
    data_.SetRestriction(ax::mojom::Restriction::kReadOnly);
  } else {
    data_.RemoveIntAttribute(ax::mojom::IntAttribute::kRestriction);
  }

  NotifyDataChanged();
}

bool ViewAccessibility::GetIsPruned() const {
  return pruned_;
}

void ViewAccessibility::SetCharacterOffsets(
    const std::vector<int32_t>& offsets) {
  data_.AddIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets,
                            offsets);

  OnIntListAttributeChanged(ax::mojom::IntListAttribute::kCharacterOffsets,
                            offsets);
  NotifyDataChanged();
}

const std::vector<int32_t>& ViewAccessibility::GetCharacterOffsets() const {
  return data_.GetIntListAttribute(
      ax::mojom::IntListAttribute::kCharacterOffsets);
}

void ViewAccessibility::SetWordStarts(const std::vector<int32_t>& offsets) {
  data_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts, offsets);

  OnIntListAttributeChanged(ax::mojom::IntListAttribute::kWordStarts, offsets);
  NotifyDataChanged();
}

const std::vector<int32_t>& ViewAccessibility::GetWordStarts() const {
  return data_.GetIntListAttribute(ax::mojom::IntListAttribute::kWordStarts);
}

void ViewAccessibility::SetWordEnds(const std::vector<int32_t>& offsets) {
  data_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds, offsets);

  OnIntListAttributeChanged(ax::mojom::IntListAttribute::kWordEnds, offsets);
  NotifyDataChanged();
}

const std::vector<int32_t>& ViewAccessibility::GetWordEnds() const {
  return data_.GetIntListAttribute(ax::mojom::IntListAttribute::kWordEnds);
}

void ViewAccessibility::ClearTextOffsets() {
  data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets);
  data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kWordStarts);
  data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kWordEnds);

  OnIntListAttributeChanged(ax::mojom::IntListAttribute::kCharacterOffsets,
                            std::nullopt);
  OnIntListAttributeChanged(ax::mojom::IntListAttribute::kWordStarts,
                            std::nullopt);
  OnIntListAttributeChanged(ax::mojom::IntListAttribute::kWordEnds,
                            std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::SetControlIds(const std::vector<int32_t>& ids) {
  data_.AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds, ids);
  NotifyDataChanged();
}

void ViewAccessibility::RemoveControlIds() {
  data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kControlsIds);
  NotifyDataChanged();
}

void ViewAccessibility::SetClipsChildren(bool clips_children) {
  data_.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren,
                         clips_children);

  OnBoolAttributeChanged(ax::mojom::BoolAttribute::kClipsChildren,
                         clips_children);
  NotifyDataChanged();
}

void ViewAccessibility::SetClassName(const std::string& class_name) {
  data_.AddStringAttribute(ax::mojom::StringAttribute::kClassName, class_name);

  OnStringAttributeChanged(ax::mojom::StringAttribute::kClassName, class_name);
  NotifyDataChanged();
}

void ViewAccessibility::SetHasPopup(const ax::mojom::HasPopup has_popup) {
  data_.SetHasPopup(has_popup);
  NotifyDataChanged();
}

void ViewAccessibility::SetRole(const ax::mojom::Role role) {
  RETURN_IF_UNAVAILABLE();
  DCHECK(IsValidRoleForViews(role)) << "Invalid role for Views.";
  if (role == GetCachedRole()) {
    return;
  }

  data_.role = role;

  if (data_.role == ax::mojom::Role::kAlertDialog) {
    // When an alert dialog is used, indicate this with xml-roles. This helps
    // JAWS understand that it's a dialog and not just an ordinary alert, even
    // though xml-roles is normally used to expose ARIA roles in web content.
    // Specifically, this enables the JAWS Insert+T read window title command.
    // Note: if an alert has focusable descendants such as buttons, it should
    // use kAlertDialog, not kAlert.
    data_.AddStringAttribute(ax::mojom::StringAttribute::kRole, "alertdialog");
  } else {
    data_.RemoveStringAttribute(ax::mojom::StringAttribute::kRole);
  }

  UpdateIgnoredState();
  UpdateInvisibleState();

  OnRoleChanged(role);

  NotifyDataChanged();
}

void ViewAccessibility::SetRole(const ax::mojom::Role role,
                                const std::u16string& role_description) {
  RETURN_IF_UNAVAILABLE();

  SetRole(role);
  SetRoleDescription(role_description);
}

void ViewAccessibility::SetName(std::u16string name,
                                ax::mojom::NameFrom name_from) {
  RETURN_IF_UNAVAILABLE();

  // Allow subclasses to adjust the name.
  if (view_) {
    view_->AdjustAccessibleName(name, name_from);
  }

  // Ensure we have a current `name_from` value. For instance, the name might
  // still be an empty string, but a view is now indicating that this is by
  // design by setting `NameFrom::kAttributeExplicitlyEmpty`.
  data_.SetNameFrom(name_from);

  if (name == GetCachedName()) {
    return;
  }

  std::u16string old_name = GetCachedName();

  if (name.empty()) {
    data_.RemoveStringAttribute(ax::mojom::StringAttribute::kName);
  } else {
    data_.SetNameChecked(name);
  }

  if (view_) {
    // If previously the accessible name was the same as the tooltip text, we
    // weren't using the tooltip text as the description, however now that the
    // name has changed, we should check if the tooltip text should be used as
    // the description.
    if (!old_name.empty() && old_name == view_->GetTooltipText()) {
      OnTooltipTextChanged();
    }

    // If a View sets the tooltip text before setting the accessible name, which
    // is a common pattern, and then the View sets the accessible name to the
    // same string, we need to make sure that we clear the description.
    // Otherwise we'll end up with the same accessible name and description.
    if (GetCachedName() == view_->GetTooltipText() &&
        GetCachedDescription() == view_->GetTooltipText()) {
      RemoveDescription();
    }

    view_->OnAccessibleNameChanged(name);
  }

  OnStringAttributeChanged(ax::mojom::StringAttribute::kName,
                           base::UTF16ToUTF8(name));

  NotifyEvent(ax::mojom::Event::kTextChanged, true);
  FireLiveRegionChangedIfNeeded(LiveRegionEventTrigger::kText);
  NotifyDataChanged();
}

void ViewAccessibility::SetName(std::string_view name,
                                ax::mojom::NameFrom name_from) {
  SetName(base::UTF8ToUTF16(name), name_from);
}

void ViewAccessibility::SetName(std::string_view name) {
  SetName(name, GetCachedNameFrom());
}

void ViewAccessibility::SetName(std::u16string name) {
  SetName(std::move(name), GetCachedNameFrom());
}

void ViewAccessibility::SetName(View& naming_view) {
  DCHECK_NE(view_, &naming_view);

  std::u16string name = naming_view.GetViewAccessibility().GetCachedName();
  DCHECK(!name.empty());
  SetName(std::move(name), ax::mojom::NameFrom::kRelatedElement);

  data_.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
                            {naming_view.GetViewAccessibility().GetUniqueId()});
  NotifyDataChanged();
}

void ViewAccessibility::RemoveName() {
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kName);
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kNameFrom);

  OnStringAttributeChanged(ax::mojom::StringAttribute::kName, std::nullopt);
  NotifyDataChanged();
}

std::u16string ViewAccessibility::GetCachedName() const {
  return data_.GetString16Attribute(ax::mojom::StringAttribute::kName);
}

ax::mojom::NameFrom ViewAccessibility::GetCachedNameFrom() const {
  return static_cast<ax::mojom::NameFrom>(
      data_.GetIntAttribute(ax::mojom::IntAttribute::kNameFrom));
}

ax::mojom::Role ViewAccessibility::GetCachedRole() const {
  return data_.role;
}

void ViewAccessibility::SetRoleDescription(
    const std::u16string& role_description) {
  if (role_description == data_.GetString16Attribute(
                              ax::mojom::StringAttribute::kRoleDescription)) {
    return;
  }

  if (!role_description.empty()) {
    data_.AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
                             base::UTF16ToUTF8(role_description));
  } else {
    RemoveRoleDescription();
  }

  OnStringAttributeChanged(ax::mojom::StringAttribute::kRoleDescription,
                           base::UTF16ToUTF8(role_description));
  NotifyDataChanged();
}

void ViewAccessibility::SetRoleDescription(
    const std::string& role_description) {
  SetRoleDescription(base::UTF8ToUTF16(role_description));
}

std::u16string ViewAccessibility::GetRoleDescription() const {
  return data_.GetString16Attribute(
      ax::mojom::StringAttribute::kRoleDescription);
}

void ViewAccessibility::RemoveRoleDescription() {
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kRoleDescription);

  OnStringAttributeChanged(ax::mojom::StringAttribute::kRoleDescription,
                           std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::SetIsEditable(bool editable) {
  SetState(ax::mojom::State::kEditable, editable);
}

void ViewAccessibility::SetBounds(const gfx::RectF& bounds) {
  if (bounds == data_.relative_bounds.bounds) {
    return;
  }
  data_.relative_bounds.bounds = bounds;
  NotifyEvent(ax::mojom::Event::kLocationChanged, false);
  NotifyDataChanged();
}

void ViewAccessibility::SetPosInSet(int pos_in_set) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, pos_in_set);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kPosInSet, pos_in_set);
  NotifyDataChanged();
}

void ViewAccessibility::SetSetSize(int set_size) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, set_size);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kSetSize, set_size);
  NotifyDataChanged();
}

void ViewAccessibility::ClearPosInSet() {
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kPosInSet);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kPosInSet, 0);
  NotifyDataChanged();
}

void ViewAccessibility::ClearSetSize() {
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kSetSize);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kSetSize, 0);
  NotifyDataChanged();
}

void ViewAccessibility::SetScrollX(int scroll_x) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollX, scroll_x);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollX, scroll_x);
  NotifyDataChanged();
}

void ViewAccessibility::SetScrollXMin(int scroll_x_min) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollXMin, scroll_x_min);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollXMin, scroll_x_min);
  NotifyDataChanged();
}

void ViewAccessibility::SetScrollXMax(int scroll_x_max) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollXMax, scroll_x_max);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollXMax, scroll_x_max);
  NotifyDataChanged();
}

void ViewAccessibility::SetScrollY(int scroll_y) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollY, scroll_y);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollY, scroll_y);
  NotifyDataChanged();
}

void ViewAccessibility::SetScrollYMin(int scroll_y_min) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollYMin, scroll_y_min);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollYMin, scroll_y_min);
  NotifyDataChanged();
}

void ViewAccessibility::SetScrollYMax(int scroll_y_max) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollYMax, scroll_y_max);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollYMax, scroll_y_max);
  NotifyDataChanged();
}

void ViewAccessibility::SetIsScrollable(bool is_scrollable) {
  data_.AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, is_scrollable);

  OnBoolAttributeChanged(ax::mojom::BoolAttribute::kScrollable, is_scrollable);
  NotifyDataChanged();
}

void ViewAccessibility::SetActiveDescendant(
    ViewAccessibility& view_accessibility) {
  ui::AXPlatformNodeId id = view_accessibility.GetUniqueId();
  if (data_.GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId) ==
      id) {
    return;
  }

  // Store the reference to the ViewAccessibility object.
  // TODO(https://crbug.com/40672441): Remove this once ViewsAX is fully enabled
  // and we can rely solely on WidgetAXManager cache for lookups by ID.
  active_descendant_view_ = view_accessibility.GetWeakPtr();

  data_.AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId, id);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kActivedescendantId, id);

  NotifyEvent(ax::mojom::Event::kActiveDescendantChanged, true);
  NotifyDataChanged();

  // If this view already has focus, fire a focus event on the new active
  // descendant.
  if (view_ && view_->HasFocus()) {
    view_accessibility.NotifyEvent(ax::mojom::Event::kFocus, true);
  }
}

void ViewAccessibility::SetActiveDescendant(views::View& view) {
  SetActiveDescendant(view.GetViewAccessibility());
}

void ViewAccessibility::ClearActiveDescendant() {
  if (!data_.HasIntAttribute(ax::mojom::IntAttribute::kActivedescendantId)) {
    return;
  }

  active_descendant_view_.reset();
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kActivedescendantId);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kActivedescendantId,
                        std::nullopt);

  NotifyEvent(ax::mojom::Event::kActiveDescendantChanged, true);
  NotifyDataChanged();

  // If this view has focus, fire a focus event on this view since focus is now
  // on the container itself (no active descendant).
  if (view_ && view_->HasFocus()) {
    NotifyEvent(ax::mojom::Event::kFocus, true);
  }
}

ViewAccessibility* ViewAccessibility::GetActiveDescendantView() const {
  // The WeakPtr will be null if the active descendant view was destroyed.
  ViewAccessibility* active_descendant = active_descendant_view_.get();

  // If the active descendant was destroyed, the WeakPtr will be null even
  // though the ID attribute is still set. In this case, return nullptr.
  // The ID attribute will be cleaned up when ClearActiveDescendant() is called.
  if (!active_descendant) {
    return nullptr;
  }

  // Validate that the stored reference is in sync with the ID attribute.
  CHECK(data_.HasIntAttribute(ax::mojom::IntAttribute::kActivedescendantId));
  CHECK_EQ(active_descendant->GetUniqueId(),
           data_.GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId))
      << "Active descendant reference is out of sync with stored ID. "
      << "Did you forget to call SetActiveDescendant or "
         "ClearActiveDescendant?";
  return active_descendant;
}

void ViewAccessibility::SetIsInvisible(bool is_invisible) {
  if (is_invisible == should_be_invisible_) {
    return;
  }

  should_be_invisible_ = is_invisible;

  UpdateInvisibleState();
}

void ViewAccessibility::SetIsDefault(bool is_default) {
  if (data_.HasState(ax::mojom::State::kDefault) == is_default) {
    return;
  }
  SetState(ax::mojom::State::kDefault, is_default);
}

bool ViewAccessibility::GetIsDefault() const {
  return data_.HasState(ax::mojom::State::kDefault);
}

void ViewAccessibility::SetIsEnabled(bool is_enabled) {
  RETURN_IF_UNAVAILABLE();
  if (is_enabled == GetIsEnabled()) {
    return;
  }

  if (!is_enabled) {
    data_.SetRestriction(ax::mojom::Restriction::kDisabled);
  } else if (data_.GetRestriction() == ax::mojom::Restriction::kDisabled) {
    // Take into account the possibility that the View is marked as readonly
    // but enabled. In other words, we can't just remove all restrictions,
    // unless the View is explicitly marked as disabled. Note that readonly is
    // another restriction state in addition to enabled and disabled, (see
    // `ax::mojom::Restriction`).
    data_.SetRestriction(ax::mojom::Restriction::kNone);
  }

  // AXVirtualViews should be marked as disabled if their
  // owner View is disabled.
  for (auto& virtual_child : virtual_children()) {
    virtual_child->SetIsEnabledRecursive(is_enabled);
  }

  UpdateFocusableState();

  OnIntAttributeChanged(ax::mojom::IntAttribute::kRestriction,
                        static_cast<int32_t>(data_.GetRestriction()));

  // Ignored nodes are not exposed to the platform accessibility tree. Firing
  // state-change events on them is incorrect and produces noise that may
  // confuse assistive technologies.
  if (!GetIsIgnored()) {
    NotifyEvent(ax::mojom::Event::kEnabledChanged, true);
  }
  NotifyDataChanged();
}

bool ViewAccessibility::GetIsEnabled() const {
  return data_.GetRestriction() != ax::mojom::Restriction::kDisabled;
}

void ViewAccessibility::SetTableRowCount(int row_count) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTableRowCount, row_count);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableRowCount, row_count);
  NotifyDataChanged();
}

void ViewAccessibility::SetTableColumnCount(int column_count) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTableColumnCount,
                        column_count);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableColumnCount,
                        column_count);
  NotifyDataChanged();
}

void ViewAccessibility::SetAriaTableRowCount(int row_count) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kAriaRowCount, row_count);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kAriaRowCount, row_count);
  NotifyDataChanged();
}

void ViewAccessibility::SetAriaTableColumnCount(int column_count) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kAriaColumnCount,
                        column_count);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kAriaColumnCount,
                        column_count);
  NotifyDataChanged();
}

void ViewAccessibility::ClearTableRowCount() {
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kTableRowCount);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableRowCount, std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::ClearTableColumnCount() {
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kTableColumnCount);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableColumnCount,
                        std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::ClearAriaTableRowCount() {
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kAriaRowCount);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kAriaRowCount, std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::ClearAriaTableColumnCount() {
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kAriaColumnCount);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kAriaColumnCount,
                        std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::SetTableRowIndex(int cell_index) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTableRowIndex, cell_index);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableRowIndex, cell_index);
  NotifyDataChanged();
}

int ViewAccessibility::GetTableRowIndex() const {
  return data_.GetIntAttribute(ax::mojom::IntAttribute::kTableRowIndex);
}

void ViewAccessibility::SetTableCellColumnIndex(int cell_index) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex,
                        cell_index);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableCellColumnIndex,
                        cell_index);
  NotifyDataChanged();
}

void ViewAccessibility::SetTableCellRowIndex(int row_index) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex, row_index);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableCellRowIndex, row_index);
  NotifyDataChanged();
}

void ViewAccessibility::SetTableCellRowSpan(int row_span) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowSpan, row_span);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableCellRowSpan, row_span);
  NotifyDataChanged();
}

void ViewAccessibility::SetTableCellColumnSpan(int column_span) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan,
                        column_span);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kTableCellColumnSpan,
                        column_span);
  NotifyDataChanged();
}

void ViewAccessibility::SetSortDirection(
    ax::mojom::SortDirection sort_direction) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kSortDirection,
                        static_cast<int>(sort_direction));

  OnIntAttributeChanged(ax::mojom::IntAttribute::kSortDirection,
                        static_cast<int>(sort_direction));
  NotifyDataChanged();
}

void ViewAccessibility::ClearDescriptionAndDescriptionFrom() {
  data_.SetDescriptionExplicitlyEmpty();

  OnStringAttributeChanged(ax::mojom::StringAttribute::kDescription,
                           std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::RemoveDescription() {
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kDescription);
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom);

  OnStringAttributeChanged(ax::mojom::StringAttribute::kDescription,
                           std::nullopt);
  OnIntAttributeChanged(ax::mojom::IntAttribute::kDescriptionFrom,
                        std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::SetDescription(
    const std::string& description,
    const ax::mojom::DescriptionFrom description_from) {
  if (description.empty() &&
      description_from !=
          ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty) {
    data_.RemoveStringAttribute(ax::mojom::StringAttribute::kDescription);
    data_.RemoveIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom);
    return;
  }

  data_.SetDescriptionFrom(description_from);
  data_.SetDescription(description);

  OnStringAttributeChanged(ax::mojom::StringAttribute::kDescription,
                           description);
  OnIntAttributeChanged(ax::mojom::IntAttribute::kDescriptionFrom,
                        static_cast<int>(description_from));
  NotifyDataChanged();
}

void ViewAccessibility::SetDescription(
    const std::u16string& description,
    const ax::mojom::DescriptionFrom description_from) {
  SetDescription(base::UTF16ToUTF8(description), description_from);
}

void ViewAccessibility::SetDescription(View& describing_view) {
  CHECK(view_);
  DCHECK_NE(view_, &describing_view);

  std::u16string name = describing_view.GetViewAccessibility().GetCachedName();
  DCHECK(!name.empty())
      << "The describing view must have an accessible name set.";
  SetDescription(name, ax::mojom::DescriptionFrom::kRelatedElement);

  std::vector<int32_t> ids = {
      describing_view.GetViewAccessibility().GetUniqueId()};

  data_.AddIntListAttribute(ax::mojom::IntListAttribute::kDescribedbyIds, ids);

  OnIntListAttributeChanged(ax::mojom::IntListAttribute::kDescribedbyIds, ids);
  NotifyDataChanged();
}

std::u16string ViewAccessibility::GetCachedDescription() const {
  if (data_.HasStringAttribute(ax::mojom::StringAttribute::kDescription)) {
    return base::UTF8ToUTF16(
        data_.GetStringAttribute(ax::mojom::StringAttribute::kDescription));
  }
  return std::u16string();
}

void ViewAccessibility::OnTooltipTextChanged(
    std::optional<std::u16string> old_tooltip_text) {
  if (!view_) {
    return;
  }

  if (data_.HasStringAttribute(ax::mojom::StringAttribute::kDescription) &&
      view_->GetTooltipText() == GetCachedDescription()) {
    return;
  }
  // Some screen readers announce the accessible description right after the
  // accessible name. Only use the tooltip as the accessible description if
  // it's different from the name, otherwise users might be puzzled as to why
  // their screen reader is announcing the same thing twice.
  const std::u16string tooltip = view_->GetTooltipText();
  // We only want to update the description if we were previously using the
  // tooltip as the description or if we had no description.
  if ((old_tooltip_text.has_value() &&
       old_tooltip_text == GetCachedDescription()) ||
      !data_.HasStringAttribute(ax::mojom::StringAttribute::kDescription)) {
    if (!tooltip.empty() && tooltip != GetCachedName()) {
      SetDescription(tooltip);
    } else {
      RemoveDescription();
    }
  }
}

void ViewAccessibility::OnViewAddedToWidget() {
  if (ViewAccessibility* parent = GetViewAccessibilityParent()) {
    AXUpdateNotifier::Get()->NotifyChildAdded(this, parent);
  }

  // Register any pre-existing virtual children with the WidgetAXManager.
  // They were not registered when originally added because the widget was not
  // yet available.
  OnVirtualViewAddedToWidget();

  // The accessibility class name is set after the view has been attached
  // to a widget, ensuring the object is fully constructed and its class
  // name is stable.
  std::string effective_class = std::string(view_->GetClassName());

#if BUILDFLAG(IS_WIN)
  // On Windows, Narrator restricts focus to web content in Scan Mode only when
  // the root web area’s parent has class name "Chrome_WidgetWin_1". This is a
  // hardcoded behavior. It worked before Chromium enabled UIA by default, since
  // the MSAA Proxy added the root web area under a window with that class name.
  // We’re collaborating with the Narrator team to update their tab detection
  // logic, but rollout will take time. This is a temporary mitigation. See
  // https://crbug.com/443225250 for details.
  if (::ui::AXPlatform::GetInstance().IsUiaProviderEnabled() &&
      features::IsFixNarratorWebContentContainmentEnabled() &&
      effective_class == "ContentsContainerView") {
    effective_class = "Chrome_WidgetWin_1";
  }
#endif  // BUILDFLAG(IS_WIN)

  SetClassName(effective_class);
}

void ViewAccessibility::OnViewRemovedFromWidget() {
  // Unregister virtual children before this view itself.
  OnVirtualViewRemovedFromWidget();

  if (ViewAccessibility* parent = GetViewAccessibilityParent()) {
    AXUpdateNotifier::Get()->NotifyChildRemoved(this, parent);
  }
}

void ViewAccessibility::OnVirtualViewAddedToWidget() {
  for (const auto& virtual_child : virtual_children()) {
    AXUpdateNotifier::Get()->NotifyChildAdded(virtual_child.get(), this);
    virtual_child->OnVirtualViewAddedToWidget();
  }
}

void ViewAccessibility::OnVirtualViewRemovedFromWidget() {
  for (const auto& virtual_child : virtual_children()) {
    virtual_child->OnVirtualViewRemovedFromWidget();
    AXUpdateNotifier::Get()->NotifyChildRemoved(virtual_child.get(), this);
  }
}

void ViewAccessibility::NotifyChildrenAdded() {
  for (ViewAccessibility* child : GetChildren()) {
    AXUpdateNotifier::Get()->NotifyChildAdded(child, this);
    child->NotifyChildrenAdded();
  }
}

void ViewAccessibility::NotifyChildrenRemoved() {
  for (ViewAccessibility* child : GetChildren()) {
    child->NotifyChildrenRemoved();
    AXUpdateNotifier::Get()->NotifyChildRemoved(child, this);
  }
}

void ViewAccessibility::SetPlaceholder(const std::string& placeholder) {
  data_.AddStringAttribute(ax::mojom::StringAttribute::kPlaceholder,
                           placeholder);
  NotifyDataChanged();
}

void ViewAccessibility::AddAction(ax::mojom::Action action) {
  if (data_.HasAction(action)) {
    return;
  }

  data_.AddAction(action);
  NotifyDataChanged();
}

void ViewAccessibility::SetCheckedState(ax::mojom::CheckedState checked_state) {
  if (checked_state == data_.GetCheckedState()) {
    return;
  }
  data_.SetCheckedState(checked_state);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kCheckedState,
                        static_cast<int>(checked_state));

  NotifyEvent(ax::mojom::Event::kCheckedStateChanged, true);
  NotifyDataChanged();
}

ax::mojom::CheckedState ViewAccessibility::GetCheckedState() const {
  return data_.GetCheckedState();
}

void ViewAccessibility::RemoveCheckedState() {
  if (data_.HasCheckedState()) {
    data_.RemoveIntAttribute(ax::mojom::IntAttribute::kCheckedState);

    OnIntAttributeChanged(ax::mojom::IntAttribute::kCheckedState, std::nullopt);
    NotifyDataChanged();
  }
}

void ViewAccessibility::SetKeyShortcuts(const std::string& key_shortcuts) {
  data_.AddStringAttribute(ax::mojom::StringAttribute::kKeyShortcuts,
                           key_shortcuts);
  NotifyDataChanged();
}

void ViewAccessibility::RemoveKeyShortcuts() {
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kKeyShortcuts);
  NotifyDataChanged();
}

void ViewAccessibility::SetAccessKey(const std::string& access_key) {
  data_.AddStringAttribute(ax::mojom::StringAttribute::kAccessKey, access_key);
  NotifyDataChanged();
}

void ViewAccessibility::RemoveAccessKey() {
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kAccessKey);
  NotifyDataChanged();
}

void ViewAccessibility::SetChildTreeNodeAppId(const std::string& app_id) {
  data_.AddStringAttribute(ax::mojom::StringAttribute::kChildTreeNodeAppId,
                           app_id);
  NotifyDataChanged();
}

void ViewAccessibility::RemoveChildTreeNodeAppId() {
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kChildTreeNodeAppId);
  NotifyDataChanged();
}

void ViewAccessibility::SetIsSelected(bool selected) {
  if (data_.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected) &&
      selected == data_.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
    return;
  }

  data_.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected);

  OnBoolAttributeChanged(ax::mojom::BoolAttribute::kSelected, selected);
  NotifyEvent(ax::mojom::Event::kSelection, true);

  for (ViewAccessibility* ancestor = GetViewAccessibilityParent(); ancestor;
       ancestor = ancestor->GetViewAccessibilityParent()) {
    if (ui::IsContainerWithSelectableChildren(ancestor->GetCachedRole())) {
      ancestor->NotifyEvent(ax::mojom::Event::kSelectedChildrenChanged, true);
      break;
    }
  }

  NotifyDataChanged();
}

void ViewAccessibility::SetIsMultiselectable(bool multiselectable) {
  SetState(ax::mojom::State::kMultiselectable, multiselectable);
  NotifyDataChanged();
}

void ViewAccessibility::SetIsModal(bool modal) {
  data_.AddBoolAttribute(ax::mojom::BoolAttribute::kModal, modal);
  NotifyDataChanged();
}

void ViewAccessibility::AddHTMLAttributes(
    std::pair<std::string, std::string> attribute) {
  data_.html_attributes.push_back(attribute);
  NotifyDataChanged();
}

void ViewAccessibility::SetIsIgnored(bool is_ignored) {
  if (is_ignored == should_be_ignored_) {
    return;
  }

  should_be_ignored_ = is_ignored;

  UpdateIgnoredState();
  NotifyEvent(ax::mojom::Event::kTreeChanged, true);
}

bool ViewAccessibility::GetIsIgnored() const {
  return data_.HasState(ax::mojom::State::kIgnored);
}

void ViewAccessibility::OverrideNativeWindowTitle(const std::string& title) {
  NOTIMPLEMENTED() << "Only implemented on Mac for now.";
}

void ViewAccessibility::OverrideNativeWindowTitle(const std::u16string& title) {
  OverrideNativeWindowTitle(base::UTF16ToUTF8(title));
}

void ViewAccessibility::SetNextFocus(Widget* widget) {
  if (widget) {
    next_focus_ = widget->GetWeakPtr();
  } else {
    next_focus_ = nullptr;
  }
}

void ViewAccessibility::SetPreviousFocus(Widget* widget) {
  if (widget) {
    previous_focus_ = widget->GetWeakPtr();
  } else {
    previous_focus_ = nullptr;
  }
}

Widget* ViewAccessibility::GetNextWindowFocus() const {
  return next_focus_.get();
}

Widget* ViewAccessibility::GetPreviousWindowFocus() const {
  return previous_focus_.get();
}

void ViewAccessibility::SetShowContextMenu(bool show_context_menu) {
  if (show_context_menu) {
    data_.AddAction(ax::mojom::Action::kShowContextMenu);
  } else {
    data_.RemoveAction(ax::mojom::Action::kShowContextMenu);
  }

  // We must add/remove the action to any virtual children as well.
  for (auto& virtual_child : virtual_children()) {
    virtual_child->SetShowContextMenuRecursive(show_context_menu);
  }

  NotifyDataChanged();
}

// static
const char* ViewAccessibility::LiveRegionStatusToString(
    LiveRegionStatus status) {
  switch (status) {
    case LiveRegionStatus::kPolite:
      return "polite";
    case LiveRegionStatus::kAssertive:
      return "assertive";
    case LiveRegionStatus::kOff:
      return "off";
  }
}

namespace {

struct LiveRegionRelevantEntry {
  uint8_t mask;
  const char* name;
};

constexpr LiveRegionRelevantEntry kLiveRegionRelevantEntries[] = {
    {ViewAccessibility::kLiveRegionRelevantAdditions, "additions"},
    {ViewAccessibility::kLiveRegionRelevantText, "text"},
    {ViewAccessibility::kLiveRegionRelevantRemovals, "removals"},
};

}  // namespace

// static
std::string ViewAccessibility::LiveRegionRelevantToString(uint8_t relevant) {
  if (!relevant) {
    relevant = kLiveRegionRelevantDefault;
  } else if (relevant == kLiveRegionRelevantAll) {
    return "all";
  }

  std::string result;
  for (const auto& entry : kLiveRegionRelevantEntries) {
    if (relevant & entry.mask) {
      if (!result.empty()) {
        result += " ";
      }
      result += entry.name;
    }
  }
  return result;
}

// static
uint8_t ViewAccessibility::LiveRegionRelevantFromString(
    const std::string& relevant) {
  if (relevant == "all") {
    return kLiveRegionRelevantAll;
  }
  uint8_t result = 0;
  for (const auto& entry : kLiveRegionRelevantEntries) {
    if (relevant.find(entry.name) != std::string::npos) {
      result |= entry.mask;
    }
  }
  return result;
}

void ViewAccessibility::SetLiveRegionContainer(LiveRegionStatus live_status,
                                               uint8_t relevant,
                                               bool atomic) {
  const char* live_status_str = LiveRegionStatusToString(live_status);
  const std::string relevant_str = LiveRegionRelevantToString(relevant);

  data_.AddStringAttribute(ax::mojom::StringAttribute::kLiveStatus,
                           live_status_str);
  data_.AddStringAttribute(ax::mojom::StringAttribute::kLiveRelevant,
                           relevant_str);
  data_.AddBoolAttribute(ax::mojom::BoolAttribute::kLiveAtomic, atomic);
  data_.AddStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus,
                           live_status_str);
  data_.AddStringAttribute(ax::mojom::StringAttribute::kContainerLiveRelevant,
                           relevant_str);

  UpdateContainerLiveStatusRecursive();
  NotifyDataChanged();
}

void ViewAccessibility::RemoveLiveRegionContainer() {
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kLiveStatus);
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kLiveRelevant);
  data_.RemoveBoolAttribute(ax::mojom::BoolAttribute::kLiveAtomic);
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus);
  data_.RemoveStringAttribute(
      ax::mojom::StringAttribute::kContainerLiveRelevant);

  UpdateContainerLiveStatusRecursive();
  NotifyDataChanged();
}

void ViewAccessibility::UpdateContainerLiveStatus() {
  std::string status =
      data_.GetStringAttribute(ax::mojom::StringAttribute::kLiveStatus);
  std::string relevant;
  if (status.empty()) {
    ViewAccessibility* parent = GetViewAccessibilityParent();
    if (parent) {
      status = parent->data_.GetStringAttribute(
          ax::mojom::StringAttribute::kContainerLiveStatus);
      relevant = parent->data_.GetStringAttribute(
          ax::mojom::StringAttribute::kContainerLiveRelevant);
    }
  } else {
    relevant =
        data_.GetStringAttribute(ax::mojom::StringAttribute::kLiveRelevant);
  }

  if (status.empty()) {
    data_.RemoveStringAttribute(
        ax::mojom::StringAttribute::kContainerLiveStatus);
    data_.RemoveStringAttribute(
        ax::mojom::StringAttribute::kContainerLiveRelevant);
  } else {
    data_.AddStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus,
                             status);
    if (!relevant.empty()) {
      data_.AddStringAttribute(
          ax::mojom::StringAttribute::kContainerLiveRelevant, relevant);
    }
  }
}

void ViewAccessibility::UpdateContainerLiveStatusRecursive() {
  UpdateContainerLiveStatus();

  if (view_) {
    internal::ScopedChildrenLock lock(view_);
    for (auto& child : view_->children()) {
      child->GetViewAccessibility().UpdateContainerLiveStatusRecursive();
    }
  }

  for (auto& child : virtual_children()) {
    child->UpdateContainerLiveStatusRecursive();
  }
}

void ViewAccessibility::FireLiveRegionChangedIfNeeded(
    LiveRegionEventTrigger trigger) {
  std::string container_live_status = data_.GetStringAttribute(
      ax::mojom::StringAttribute::kContainerLiveStatus);
  if (container_live_status.empty() || container_live_status == "off") {
    return;
  }

  // Walk up ancestors to find the live region root (the view with kLiveStatus).
  ViewAccessibility* live_region_root = this;
  while (live_region_root) {
    if (live_region_root->data_.HasStringAttribute(
            ax::mojom::StringAttribute::kLiveStatus)) {
      break;
    }
    live_region_root = live_region_root->GetViewAccessibilityParent();
  }

  if (!live_region_root) {
    return;
  }

  // Check if the trigger type is included in the aria-relevant attribute.
  const std::string relevant_str = live_region_root->data_.GetStringAttribute(
      ax::mojom::StringAttribute::kContainerLiveRelevant);
  const uint8_t relevant = relevant_str.empty()
                               ? kLiveRegionRelevantDefault
                               : LiveRegionRelevantFromString(relevant_str);
  uint8_t trigger_bit = 0;
  switch (trigger) {
    case LiveRegionEventTrigger::kAdditions:
      trigger_bit = kLiveRegionRelevantAdditions;
      break;
    case LiveRegionEventTrigger::kText:
      trigger_bit = kLiveRegionRelevantText;
      break;
    case LiveRegionEventTrigger::kRemovals:
      trigger_bit = kLiveRegionRelevantRemovals;
      break;
  }
  if (!(relevant & trigger_bit)) {
    return;
  }

  // Only fire for text changes on the container itself (not descendants),
  // and only when there is content to announce.
  if (trigger == LiveRegionEventTrigger::kText) {
    if (this != live_region_root) {
      return;
    }
    if (live_region_root->GetCachedName().empty()) {
      return;
    }
  }

  live_region_root->NotifyEvent(ax::mojom::Event::kLiveRegionChanged, true);
}

void ViewAccessibility::SetValue(const std::string& value) {
  if (value == data_.GetStringAttribute(ax::mojom::StringAttribute::kValue)) {
    return;
  }
  data_.AddStringAttribute(ax::mojom::StringAttribute::kValue, value);

  if (ready_to_notify_events_) {
    OnStringAttributeChanged(ax::mojom::StringAttribute::kValue, value);
    NotifyEvent(ax::mojom::Event::kValueChanged, true);

    // TODO(crbug.com/40672441): Remove this once ViewsAX is enabled on
    // Windows. Only fire a text changed event on text fields and select
    // elements on Windows so that UIA fires UIA_Text_TextChangedEventId.
    // On macOS and Linux, this incorrectly maps to title/name-changed
    // events rather than value-changed events.
#if BUILDFLAG(IS_WIN)
    if (data_.IsTextField() || ui::IsSelectElement(data_.role)) {
      NotifyEvent(ax::mojom::Event::kTextChanged, true);
    }
#endif
  }

  NotifyDataChanged();
}

void ViewAccessibility::SetValue(std::u16string_view value) {
  SetValue(base::UTF16ToUTF8(value));
}

void ViewAccessibility::RemoveValue() {
  if (!data_.HasStringAttribute(ax::mojom::StringAttribute::kValue)) {
    return;
  }
  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kValue);

  OnStringAttributeChanged(ax::mojom::StringAttribute::kValue, std::nullopt);

  NotifyEvent(ax::mojom::Event::kValueChanged, true);
  NotifyDataChanged();
}

std::u16string ViewAccessibility::GetValue() const {
  return base::UTF8ToUTF16(
      data_.GetStringAttribute(ax::mojom::StringAttribute::kValue));
}

void ViewAccessibility::SetValueForRange(float value) {
  if (data_.HasFloatAttribute(ax::mojom::FloatAttribute::kValueForRange) &&
      data_.GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange) ==
          value) {
    return;
  }
  data_.AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, value);
  NotifyDataChanged();
}

void ViewAccessibility::SetMinValueForRange(float value) {
  if (data_.HasFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange) &&
      data_.GetFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange) ==
          value) {
    return;
  }
  data_.AddFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange, value);
  NotifyDataChanged();
}

void ViewAccessibility::SetMaxValueForRange(float value) {
  if (data_.HasFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange) &&
      data_.GetFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange) ==
          value) {
    return;
  }
  data_.AddFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange, value);
  NotifyDataChanged();
}

void ViewAccessibility::SetDefaultActionVerb(
    const ax::mojom::DefaultActionVerb default_action_verb) {
  data_.SetDefaultActionVerb(default_action_verb);
}

ax::mojom::DefaultActionVerb ViewAccessibility::GetDefaultActionVerb() const {
  return data_.GetDefaultActionVerb();
}

void ViewAccessibility::RemoveDefaultActionVerb() {
  data_.RemoveIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb);

  OnIntAttributeChanged(ax::mojom::IntAttribute::kDefaultActionVerb,
                        std::nullopt);
  NotifyDataChanged();
}

void ViewAccessibility::SetAutoComplete(const std::string& autocomplete) {
  data_.AddStringAttribute(ax::mojom::StringAttribute::kAutoComplete,
                           autocomplete);
  NotifyDataChanged();
}

void ViewAccessibility::SetHasFocusableAncestor(bool ancestor_focusable) {
  has_focusable_ancestor_ = ancestor_focusable;
  UpdateIgnoredState();
}

void ViewAccessibility::SetHasFocusableAncestorRecursive(
    bool ancestor_focusable) {
  if (view_) {
    for (auto& child : view_->children()) {
      child->GetViewAccessibility().SetHasFocusableAncestor(ancestor_focusable);
      // If the child has been explicitly set to focusable, we skip its subtree
      // since their state will be respected and should already be up to date.
      if (child->GetFocusBehavior() != View::FocusBehavior::NEVER) {
        continue;
      }
      child->GetViewAccessibility().SetHasFocusableAncestorRecursive(
          ancestor_focusable);
    }
  }

  // Now we do the same for any virtual children.
  for (auto& child : virtual_children()) {
    child->SetHasFocusableAncestor(ancestor_focusable);
    child->SetHasFocusableAncestorRecursive(ancestor_focusable);
  }

  UpdateIgnoredState();
}

void ViewAccessibility::UpdateFocusableState() {
  CHECK(view_);
  bool is_focusable = view_->GetFocusBehavior() != View::FocusBehavior::NEVER &&
                      GetIsEnabled() &&
                      !data_.HasState(ax::mojom::State::kInvisible) &&
                      !ViewAccessibility::GetIsIgnored();
  if (is_focusable) {
    CHECK(!should_be_invisible_ &&
          !data_.HasState(ax::mojom::State::kInvisible))
        << "A view that focusable should not be marked as invisible. This is a "
           "check we also make in the Paint Checks.";
  }
  SetState(ax::mojom::State::kFocusable, is_focusable);
}

void ViewAccessibility::UpdateInvisibleByInheritanceRecursive(
    const View* initial_view,
    bool invisible_by_inheritance) {
  CHECK(view_);
  internal::ScopedChildrenLock lock(view_);
  if (view_.get() != initial_view) {
    is_invisible_by_inheritance_ = invisible_by_inheritance;
    if (!view_->GetVisible()) {
      return;
    }
  }
  UpdateInvisibleState();

  for (auto& child : view_->children()) {
    child->GetViewAccessibility().UpdateInvisibleByInheritanceRecursive(
        initial_view, invisible_by_inheritance);
  }

  // Now we do the same for any virtual children.
  for (auto& child : virtual_children()) {
    child->UpdateParentViewIsDrawnRecursive(initial_view,
                                            !invisible_by_inheritance);
  }
}

void ViewAccessibility::OnViewHasNewAncestor(const View* new_ancestor) {
  CHECK(view_->parent());
  // We need to make sure that we are propagating the right values down the
  // recursive calls. For the invisible state, this means we look at the direct
  // parent, rather than the `new_ancestor`, which in subsequent recursive calls
  // could be a root of an entire tree that is getting reparented. This is
  // because if at some point during the recursion, the parent is invisible, it
  // should affect its descendants, even if `new_ancestor` is not. For example,
  // if we have a tree like this:
  // A (visible)
  //   B (invisible)
  // and then a separate tree:
  // C (invisible)
  //   D (invisible by inheritance of C)
  // and then we reparent C to be a child of A:
  // A (visible)
  //   B (invisible)
  //   C (invisible)
  //     D (invisible by inheritance of C)
  // Even though `A` is visible ( A would be `new_ancestor`), we need to make
  // sure that during the recursion, we don't mark `D` as visible, since it's
  // parent is invisible.
  bool parent_invisible =
      view_->parent()->GetViewAccessibility().is_invisible_by_inheritance() ||
      !view_->parent()->GetVisible();
  bool ancestor_focusable =
      new_ancestor->GetFocusBehavior() != View::FocusBehavior::NEVER ||
      new_ancestor->GetViewAccessibility().has_focusable_ancestor();

  internal::ScopedChildrenLock lock(view_);

  is_invisible_by_inheritance_ = parent_invisible;

  UpdateInvisibleState();
  UpdateContainerLiveStatus();

  // We only want to propagate the `ancestor_focusable` value if it's true. This
  // is because if this view is unfocusable, and it gets added to a tree with a
  // focusable ancestor, it should now be marked as ignored. However, being
  // added to a tree with an unfocusable ancestor doesn't affect the ignored
  // state of this view or its descendants.
  if (ancestor_focusable) {
    SetHasFocusableAncestor(ancestor_focusable);
  }

  UpdateReadyToNotifyEvents();
  for (auto& child : view_->children()) {
    child->GetViewAccessibility().OnViewHasNewAncestor(new_ancestor);
  }

  // Now we do the same for any virtual children.
  for (auto& child : virtual_children()) {
    child->OnViewHasNewAncestor(ancestor_focusable);
  }
}

void ViewAccessibility::OnViewParentChanged() {
  CHECK(view_);
  if (view_->parent()) {
    OnViewHasNewAncestor(view_->parent());
  }

  UpdateOffsetContainerId();
}

ui::AXNodeID ViewAccessibility::GetOffsetContainerId() const {
  CHECK(view_);
  const ViewAccessibility* parent = GetViewAccessibilityParent();
  return parent ? static_cast<ui::AXNodeID>(parent->GetUniqueId())
                : ui::kInvalidAXNodeID;
}

void ViewAccessibility::UpdateOffsetContainerId() {
  const ui::AXNodeID offset_container_id = GetOffsetContainerId();
  if (data_.relative_bounds.offset_container_id == offset_container_id) {
    return;
  }

  data_.relative_bounds.offset_container_id = offset_container_id;
  NotifyDataChanged();
}

void ViewAccessibility::SetRootViewURL(const std::string& url) {
  CHECK(view_);
  CHECK(!view_->parent())
      << "This method should only be called on the RootView.";
  data_.AddStringAttribute(ax::mojom::StringAttribute::kUrl, url);
  OnStringAttributeChanged(ax::mojom::StringAttribute::kUrl, url);
  NotifyDataChanged();
}

void ViewAccessibility::SetRootViewIsReadyToNotifyEvents() {
  CHECK(view_);
  CHECK(!view_->parent())
      << "This method should only be called on the RootView.";
  ready_to_notify_events_ = true;
}

void ViewAccessibility::UpdateInvisibleState() {
  bool is_invisible =
      (!view_->GetVisible() && data_.role != ax::mojom::Role::kAlert) ||
      is_invisible_by_inheritance_ || should_be_invisible_;
  SetState(ax::mojom::State::kInvisible, is_invisible);
  UpdateFocusableState();
}

void ViewAccessibility::SetChildTreeID(ui::AXTreeID tree_id) {
  CHECK_NE(tree_id, ui::AXTreeIDUnknown())
      << "Call RemoveChildTreeID to remove the bridge to a child tree.";

  // The child tree hides the children. Notify before the attribute changes,
  // while GetChildren still returns them.
  NotifyChildrenRemoved();

  data_.AddChildTreeId(tree_id);

  const views::Widget* widget = GetWidget();
  if (widget && widget->GetNativeView() && display::Screen::Get()) {
    // TODO(accessibility): There potentially could be an issue where the
    // device scale factor changes from the time the tree ID is set to the
    // time `GetAccessibleNodeData` is queried. If this ever pops up, a
    // potential solution could be to make ViewAccessibility a DisplayObserver
    // and add `this` as an observer when the tree ID is set. Then, when the
    // display changes, we can update the scale factor in the cache, probably
    // by implementing `OnDisplayMetricsChanged`.
    const float scale_factor =
        display::Screen::Get()
            ->GetDisplayNearestView(widget->GetNativeView())
            .device_scale_factor();
    SetChildTreeScaleFactor(scale_factor);
  }

  OnStringAttributeChanged(ax::mojom::StringAttribute::kChildTreeId,
                           tree_id.ToString());
  NotifyDataChanged();
}

ui::AXTreeID ViewAccessibility::GetChildTreeID() const {
  std::optional<ui::AXTreeID> child_tree_id = data_.GetChildTreeID();
  return child_tree_id ? child_tree_id.value() : ui::AXTreeIDUnknown();
}

void ViewAccessibility::RemoveChildTreeID() {
  const bool had_child_tree_id = data_.HasChildTreeID();

  data_.RemoveStringAttribute(ax::mojom::StringAttribute::kChildTreeId);

  // Callers remove the child tree id repeatedly, so only notify when this call
  // is the one that exposes the children again.
  if (had_child_tree_id) {
    NotifyChildrenAdded();
  }

  OnStringAttributeChanged(ax::mojom::StringAttribute::kChildTreeId,
                           std::string());
  NotifyDataChanged();
}

void ViewAccessibility::SetChildTreeScaleFactor(float scale_factor) {
  if (data_.HasChildTreeID()) {
    data_.AddFloatAttribute(ax::mojom::FloatAttribute::kChildTreeScale,
                            scale_factor);
    NotifyDataChanged();
  }
}

gfx::NativeViewAccessible ViewAccessibility::GetNativeObject() const {
  if (!IsViewsAccessibilityTreeEnabled()) {
    return gfx::NativeViewAccessible();
  }

  // TODO(crbug.com/40672241): Investigate whether this function should
  // be removed altogether. Does it still make sense to let Views
  // access the native accessible object directly with ViewsAX, or should the
  // Views and Accessibility layer be completely separate?
  Widget* widget = GetWidget();
  if (!widget || !widget->ax_manager()) {
    return gfx::NativeViewAccessible();
  }

  return widget->ax_manager()->GetNativeViewAccessibleForId(GetUniqueId());
}

void ViewAccessibility::AnnounceAlert(std::u16string_view text) {
  Announce(text, ax::mojom::AriaNotificationPriority::kHigh);
}

void ViewAccessibility::AnnouncePolitely(std::u16string_view text) {
  Announce(text, ax::mojom::AriaNotificationPriority::kNormal);
}

void ViewAccessibility::AnnounceText(std::u16string_view text) {
  AnnounceAlert(text);
}

void ViewAccessibility::Announce(std::u16string_view text,
                                 ax::mojom::AriaNotificationPriority priority) {
  if (text.empty()) {
    return;
  }

  auto* widget = GetWidget();
  if (!widget) {
    return;
  }

  auto* root_view = static_cast<internal::RootView*>(widget->GetRootView());
  if (!root_view) {
    return;
  }

  if (IsViewsAccessibilityTreeEnabled()) {
    if (auto* manager = widget->ax_manager();
        manager && manager->is_enabled()) {
      root_view->GetViewAccessibility().AddAriaNotification(text, priority);
    }
    return;
  }

  root_view->AnnounceTextAs(
      std::u16string(text),
      priority == ax::mojom::AriaNotificationPriority::kHigh
          ? ui::AXPlatformNode::AnnouncementType::kAlert
          : ui::AXPlatformNode::AnnouncementType::kPolite);
}

void ViewAccessibility::AddAriaNotification(
    std::u16string_view text,
    ax::mojom::AriaNotificationPriority priority) {
  auto announcements = data_.GetStringListAttribute(
      ax::mojom::StringListAttribute::kAriaNotificationAnnouncements);
  auto priority_properties = data_.GetIntListAttribute(
      ax::mojom::IntListAttribute::kAriaNotificationPriorityProperties);
  auto interrupt_properties = data_.GetIntListAttribute(
      ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties);
  auto types = data_.GetStringListAttribute(
      ax::mojom::StringListAttribute::kAriaNotificationTypes);

  announcements.push_back(base::UTF16ToUTF8(text));
  priority_properties.push_back(static_cast<int32_t>(priority));
  interrupt_properties.push_back(
      static_cast<int32_t>(ax::mojom::AriaNotificationInterrupt::kNone));
  types.emplace_back();

  data_.AddStringListAttribute(
      ax::mojom::StringListAttribute::kAriaNotificationAnnouncements,
      announcements);
  data_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kAriaNotificationPriorityProperties,
      priority_properties);
  data_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties,
      interrupt_properties);
  data_.AddStringListAttribute(
      ax::mojom::StringListAttribute::kAriaNotificationTypes, types);
  NotifyDataChanged();
}

void ViewAccessibility::ClearPendingAriaNotifications() {
  data_.RemoveStringListAttribute(
      ax::mojom::StringListAttribute::kAriaNotificationAnnouncements);
  data_.RemoveIntListAttribute(
      ax::mojom::IntListAttribute::kAriaNotificationPriorityProperties);
  data_.RemoveIntListAttribute(
      ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties);
  data_.RemoveStringListAttribute(
      ax::mojom::StringListAttribute::kAriaNotificationTypes);
}

ui::AXPlatformNodeId ViewAccessibility::GetUniqueId() const {
  return unique_id_;
}

AtomicViewAXTreeManager*
ViewAccessibility::GetAtomicViewAXTreeManagerForTesting() const {
  return nullptr;
}

Widget* ViewAccessibility::GetWidget() const {
  if (!view_) {
    return nullptr;
  }
  return view_->GetWidget();
}

bool ViewAccessibility::IsRootViewForWidget() const {
  if (!view_) {
    return false;
  }
  const Widget* widget = view_->GetWidget();
  return widget && widget->GetRootView() == view_;
}

AXAuraObjWrapper* ViewAccessibility::GetOrCreateWrapper(AXAuraObjCache* cache) {
#if defined(USE_AURA)
  if (!view_) {
    return nullptr;
  }
  return cache->GetOrCreate(view_);
#else
  return nullptr;
#endif
}

ViewAccessibility* ViewAccessibility::GetViewAccessibilityParent() const {
  if (!view_) {
    return nullptr;
  }
  if (auto* parent = view_->parent()) {
    return &parent->GetViewAccessibility();
  }
  return nullptr;
}

ViewAccessibility* ViewAccessibility::GetUnignoredParent() const {
  ViewAccessibility* parent = GetViewAccessibilityParent();
  while (parent && parent->GetIsIgnored()) {
    parent = parent->GetViewAccessibilityParent();
  }
  return parent;
}

gfx::NativeViewAccessible ViewAccessibility::GetFocusedDescendant() {
  CHECK(view_);
  if (ViewAccessibility* active_descendant = GetActiveDescendantView()) {
    // Only redirect focus for virtual view active descendants. Redirecting
    // focus for real view descendants (e.g., OmniboxResultView) causes
    // get_accState to return focused=0, which breaks JAWS character-by-
    // character navigation in textfields.
    if (!active_descendant->view()) {
      return active_descendant->GetNativeObject();
    }
  }
  return view_->GetNativeViewAccessible();
}

std::vector<raw_ptr<ViewAccessibility>> ViewAccessibility::GetChildren() const {
  std::vector<raw_ptr<ViewAccessibility>> out;

  // The kChildTreeID attributes should always be set on leaf nodes only. Thus,
  // ignore all children views and virtual views if this node has a child tree
  // ID.
  if (IsLeaf() || data_.HasChildTreeID()) {
    return out;
  }

  WidgetAXManager* ax_manager =
      IsRootViewForWidget() ? view_->GetWidget()->ax_manager() : nullptr;
  const size_t child_widget_count =
      ax_manager ? ax_manager->child_widget_tree_host_count() : 0u;

  // The virtual children always override any real children the view might have.
  if (!virtual_children_.empty()) {
    out.reserve(virtual_children_.size() + child_widget_count);
    for (auto& v : virtual_children_) {
      out.push_back(v.get());
    }
  } else if (view_) {
    const auto& view_children = view_->children();
    out.reserve(view_children.size() + child_widget_count);
    for (auto child_view : view_children) {
      out.push_back(&child_view->GetViewAccessibility());
    }
  }

  // Child Widgets don't have a corresponding view or virtual view. They are
  // attached on the RootView through ignored virtual views maintained by the
  // widget manager.
  if (ax_manager) {
    ax_manager->AppendChildWidgetTreeHosts(out);
  }

  return out;
}

std::string ViewAccessibility::GetDebugString() const {
  return std::string(view_ ? view_->GetClassName() : "ViewAccessibility");
}

void ViewAccessibility::FireNativeEvent(ax::mojom::Event event_type) {
  if (accessibility_events_callback_) {
    accessibility_events_callback_.Run(nullptr, event_type);
  }
}

const ViewAccessibility::AccessibilityEventsCallback&
ViewAccessibility::accessibility_events_callback() const {
  return accessibility_events_callback_;
}

void ViewAccessibility::set_accessibility_events_callback(
    ViewAccessibility::AccessibilityEventsCallback callback) {
  accessibility_events_callback_ = std::move(callback);
}

void ViewAccessibility::CompleteCacheInitializationRecursive() {
  if (view_) {
    internal::ScopedChildrenLock lock(view_);
  }
  if (initialization_state_ == State::kInitialized) {
    return;
  }

  initialization_state_ = State::kInitializing;

  ui::AXNodeData data;
  if (view_) {
    view_->OnAccessibilityInitializing(&data);
  }

#if DCHECK_IS_ON()
  views::ViewAccessibilityUtils::ValidateAttributesNotSet(data, data_);
#endif

  // Merge it with the cache.
  views::ViewAccessibilityUtils::Merge(/*source*/ data, /*destination*/ data_);

  initialization_state_ = State::kInitialized;

  if (view_) {
    for (auto& child : view_->children()) {
      child->GetViewAccessibility().CompleteCacheInitializationRecursive();
    }
  }

  // Now we do the same for any virtual children.
  for (auto& child : virtual_children()) {
    child->CompleteCacheInitializationRecursive();
  }
}

void ViewAccessibility::OnWidgetUpdatedRecursive(Widget* widget,
                                                 Widget* old_widget) {
  CHECK(widget);

  // If we have already marked `is_widget_closed_` as true, then there's a
  // chance that the view was reparented to a non-closed widget. If so, we must
  // update `is_widget_closed_` in case the new widget is not closed.
  is_widget_closed_ = widget->IsClosed();

  // Initialize the AtomicViewAXTreeManager if necessary when the view gets
  // added to the widget. We must wait for the widget to become available to
  // get valid data our of GetData().
  if (needs_ax_tree_manager()) {
    EnsureAtomicViewAXTreeManager();
  }

  if (view_) {
    internal::ScopedChildrenLock lock(view_);
    for (auto& child : view_->children()) {
      child->GetViewAccessibility().OnWidgetUpdatedRecursive(widget,
                                                             old_widget);
    }
  }
  for (auto& child : virtual_children()) {
    child->OnWidgetUpdatedRecursive(widget, old_widget);
  }
}

void ViewAccessibility::OnWidgetClosing(Widget* widget) {
  // The RootView's ViewAccessibility should be the only registered
  // WidgetObserver.
  CHECK_EQ(view_, widget->GetRootView());
  SetWidgetClosedRecursive(widget, true);
}

void ViewAccessibility::OnWidgetDestroyed(Widget* widget) {
  // The RootView's ViewAccessibility should be the only registered
  // WidgetObserver.
  CHECK(widget->GetRootView());
  CHECK_EQ(view_, widget->GetRootView());
  SetWidgetClosedRecursive(widget, true);
}

void ViewAccessibility::OnWidgetUpdated(Widget* widget, Widget* old_widget) {
  CHECK(widget);
  DCHECK_EQ(widget, GetWidget());
  if (widget == old_widget) {
    return;
  }

  // There's a chance we are reparenting a view that was previously a root
  // view in another widget, if so we need to remove it as an observer of the
  // old widget.
  if (old_widget && old_widget != widget) {
    old_widget->RemoveObserver(this);
  }

  OnWidgetUpdatedRecursive(widget, old_widget);
}

void ViewAccessibility::CompleteCacheInitialization() {
  if (initialization_state_ == State::kInitialized) {
    return;
  }

  CompleteCacheInitializationRecursive();
}

bool ViewAccessibility::IsAccessibilityEnabled() const {
  return ui::AXPlatform::GetInstance().GetMode() == ui::AXMode::kNativeAPIs;
}

void ViewAccessibility::PruneSubtree() {
  CHECK(view_);
  internal::ScopedChildrenLock lock(view_);
  for (auto& child : view_->children()) {
    child->GetViewAccessibility().pruned_ = true;
    child->GetViewAccessibility().UpdateIgnoredState();
    child->GetViewAccessibility().PruneSubtree();
  }

  for (auto& child : virtual_children()) {
    child->PruneVirtualSubtree();
  }
}

void ViewAccessibility::UnpruneSubtree() {
  CHECK(view_);
  internal::ScopedChildrenLock lock(view_);
  for (auto& child : view_->children()) {
    child->GetViewAccessibility().pruned_ = false;
    child->GetViewAccessibility().UpdateIgnoredState();
    // If we encounter a node that has already been explicitly set to be a leaf,
    // don't unprune it/its subtree. Otherwise we could end up in situations
    // where we have a node that is set to be a leaf, but has unpruned children.
    if (child->GetViewAccessibility().ViewAccessibility::IsLeaf()) {
      continue;
    }
    child->GetViewAccessibility().UnpruneSubtree();
  }

  for (auto& child : virtual_children()) {
    child->UnpruneVirtualSubtree();
  }
}

void ViewAccessibility::UpdateIgnoredState() {
// TODO(crbug.com/371237539): In ChromeOS, its not an expectation that being
// a view unfocusable descendant of a focusable ancestor will make the view
// ignored.
#if !BUILDFLAG(IS_CHROMEOS)
  bool is_ignored = should_be_ignored_ || pruned_ ||
                    GetCachedRole() == ax::mojom::Role::kNone ||
                    (has_focusable_ancestor_ &&
                     view_->GetFocusBehavior() == View::FocusBehavior::NEVER);
#else
  bool is_ignored = should_be_ignored_ || pruned_ ||
                    GetCachedRole() == ax::mojom::Role::kNone;
#endif  // !BUILDFLAG(IS_CHROMEOS)
  SetState(ax::mojom::State::kIgnored, is_ignored);
  UpdateFocusableState();
}

void ViewAccessibility::UpdateReadyToNotifyEvents() {
  CHECK(view_);
  View* parent = view_->parent();
  if (parent && parent->GetViewAccessibility().ready_to_notify_events_) {
    SetReadyToNotifyEvents();
  }
}

void ViewAccessibility::SetReadyToNotifyEvents() {
  ready_to_notify_events_ = true;
}

void ViewAccessibility::SetWidgetClosedRecursive(Widget* widget, bool value) {
  is_widget_closed_ = value;

  if (view_) {
    internal::ScopedChildrenLock lock(view_);
    for (auto& child : view_->children()) {
      child->GetViewAccessibility().SetWidgetClosedRecursive(widget, value);
    }
  }

  for (auto& child : virtual_children()) {
    child->SetWidgetClosedRecursive(widget, value);
  }
}

void ViewAccessibility::SetDataForClosedWidget(ui::AXNodeData* data) const {
  data->id = data_.id;
  CHECK_EQ(data->id, GetUniqueId());

  data->role = ax::mojom::Role::kUnknown;
  data->SetRestriction(ax::mojom::Restriction::kDisabled);

  // Ordinarily, a view cannot be focusable if its widget has already closed.
  // So, it would have been appropriate to set the focusable state to false in
  // this particular case. However, the `FocusManager` may sometimes try to
  // retrieve the focusable state of this view via
  // `View::IsAccessibilityFocusable()`, even after this view's widget has
  // been closed. Returning the wrong result might cause a crash, because the
  // focus manager might be expecting the result to be the same regardless of
  // the state of the view's widget.
  if (ViewAccessibility::IsAccessibilityFocusable()) {
    data->AddState(ax::mojom::State::kFocusable);
    // Set this node as intentionally nameless to avoid DCHECKs for a missing
    // name of a focusable.
    data->SetNameExplicitlyEmpty();
  }

  // Some of the views like popup_view_views have state collapsed when the
  // widget has already closed and hence explicitly setting the state.
  if (data_.HasState(ax::mojom::State::kCollapsed)) {
    data->AddState(ax::mojom::State::kCollapsed);
  }

  // Some of the views like popup_view_views have state invisible when the
  // widget has already closed and hence explicitly setting the state.
  if (data_.HasState(ax::mojom::State::kInvisible)) {
    data->AddState(ax::mojom::State::kInvisible);
  }
}

void ViewAccessibility::OnRoleChanged(ax::mojom::Role role) {
  GetOrCreateAXAttributeChangedCallbacks()->NotifyRoleChanged(role);
}

base::CallbackListSubscription ViewAccessibility::AddRoleChangedCallback(
    RoleCallbackList::CallbackType callback) {
  return GetOrCreateAXAttributeChangedCallbacks()->AddRoleChangedCallback(
      callback);
}

void ViewAccessibility::OnStringAttributeChanged(
    ax::mojom::StringAttribute attribute,
    const std::optional<std::string>& value) {
  GetOrCreateAXAttributeChangedCallbacks()->NotifyStringAttributeChanged(
      attribute, value);
}

base::CallbackListSubscription
ViewAccessibility::AddStringAttributeChangedCallback(
    ax::mojom::StringAttribute attribute,
    StringAttributeCallbackList::CallbackType callback) {
  return GetOrCreateAXAttributeChangedCallbacks()
      ->AddStringAttributeChangedCallback(attribute, callback);
}

void ViewAccessibility::OnIntAttributeChanged(ax::mojom::IntAttribute attribute,
                                              std::optional<int> value) {
  GetOrCreateAXAttributeChangedCallbacks()->NotifyIntAttributeChanged(attribute,
                                                                      value);
}

base::CallbackListSubscription
ViewAccessibility::AddIntAttributeChangedCallback(
    ax::mojom::IntAttribute attribute,
    IntAttributeCallbackList::CallbackType callback) {
  return GetOrCreateAXAttributeChangedCallbacks()
      ->AddIntAttributeChangedCallback(attribute, callback);
}

void ViewAccessibility::OnBoolAttributeChanged(
    ax::mojom::BoolAttribute attribute,
    std::optional<bool> value) {
  GetOrCreateAXAttributeChangedCallbacks()->NotifyBoolAttributeChanged(
      attribute, value);
}

base::CallbackListSubscription
ViewAccessibility::AddBoolAttributeChangedCallback(
    ax::mojom::BoolAttribute attribute,
    BoolAttributeCallbackList::CallbackType callback) {
  return GetOrCreateAXAttributeChangedCallbacks()
      ->AddBoolAttributeChangedCallback(attribute, callback);
}

void ViewAccessibility::OnStateChanged(ax::mojom::State state, bool value) {
  GetOrCreateAXAttributeChangedCallbacks()->NotifyStateChanged(state, value);
}

base::CallbackListSubscription ViewAccessibility::AddStateChangedCallback(
    ax::mojom::State state,
    StateCallbackList::CallbackType callback) {
  return GetOrCreateAXAttributeChangedCallbacks()->AddStateChangedCallback(
      state, callback);
}

void ViewAccessibility::OnIntListAttributeChanged(
    ax::mojom::IntListAttribute attribute,
    const std::optional<std::vector<int>>& value) {
  GetOrCreateAXAttributeChangedCallbacks()->NotifyIntListAttributeChanged(
      attribute, value);
}

base::CallbackListSubscription
ViewAccessibility::AddIntListAttributeChangedCallback(
    ax::mojom::IntListAttribute attribute,
    IntListAttributeCallbackList::CallbackType callback) {
  return GetOrCreateAXAttributeChangedCallbacks()
      ->AddIntListAttributeChangedCallback(attribute, callback);
}

void ViewAccessibility::SetHierarchicalLevel(int hierarchical_level) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel,
                        hierarchical_level);
}

void ViewAccessibility::SetState(ax::mojom::State state, bool is_enabled) {
  if (is_enabled) {
    data_.AddState(state);
  } else {
    data_.RemoveState(state);
  }

  OnStateChanged(state, is_enabled);
  NotifyDataChanged();
}

void ViewAccessibility::SetBlockNotifyEvents(bool block) {
  // Warning: This method should ONLY be used for special cases by the
  // ScopedAccessibilityEventBlocker. Generally, we want to prevent
  // notifications from being sent until the view is added to a Widget's Views
  // tree, and we should only update `ready_to_notify_events_` when the view is
  // added to the tree. However, there are special cases where we might need to
  // set this variable to true or false at other times. For example, the
  // RootView should always be ready to notify events, and the AnnounceTextView
  // should NOT fire certain types of events due to its nature of being a hidden
  // special view just for making announcements. As of right now, the only
  // special cases are the root view and the announce text view, which is a
  // child of the root view.
  ready_to_notify_events_ = !block;
}

void ViewAccessibility::SetIsHovered(bool is_hovered) {
  if (is_hovered == GetIsHovered()) {
    return;
  }

  SetState(ax::mojom::State::kHovered, is_hovered);
}

bool ViewAccessibility::GetIsHovered() const {
  return data_.HasState(ax::mojom::State::kHovered);
}

void ViewAccessibility::SetPopupForId(ui::AXPlatformNodeId popup_for_id) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kPopupForId, popup_for_id);
  NotifyDataChanged();
}

void ViewAccessibility::SetTextDirection(int text_direction) {
  CHECK_GE(text_direction,
           static_cast<int32_t>(ax::mojom::WritingDirection::kMinValue));
  CHECK_LE(text_direction,
           static_cast<int32_t>(ax::mojom::WritingDirection::kMaxValue));
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection,
                        text_direction);
  NotifyDataChanged();
}

void ViewAccessibility::SetIsProtected(bool is_protected) {
  if (data_.HasState(ax::mojom::State::kProtected) == is_protected) {
    return;
  }

  SetState(ax::mojom::State::kProtected, is_protected);
}

void ViewAccessibility::SetIsExpanded() {
  // Check to see if the expanded state is already set, if already set no need
  // to add the state again.
  if (data_.HasState(ax::mojom::State::kExpanded)) {
    // The expanded and collapsed state must be mutually exclusive.
    CHECK(!data_.HasState(ax::mojom::State::kCollapsed));
    return;
  }

  bool should_notify = data_.HasState(ax::mojom::State::kCollapsed);
  SetState(ax::mojom::State::kExpanded, true);
  SetState(ax::mojom::State::kCollapsed, false);

  // We should not notify when initial state (expanded = false, collapsed =
  // false) changes. As changes to initial state generally stands for when the
  // accessibility properties are being set by a view constructor or when the
  // view author explicitly resets the value of expanded and collapsed state. In
  // both these cases we dont wont to fire the accessibility event.
  if (should_notify) {
    NotifyEvent(ax::mojom::Event::kExpandedChanged, true);
  }
}

void ViewAccessibility::SetIsCollapsed() {
  // Check to see if the collapsed state is already set, if already set no need
  // to add the state again.
  if (data_.HasState(ax::mojom::State::kCollapsed)) {
    // The expanded and collapsed state must be mutually exclusive.
    CHECK(!data_.HasState(ax::mojom::State::kExpanded));
    return;
  }

  bool should_notify = data_.HasState(ax::mojom::State::kExpanded);
  SetState(ax::mojom::State::kCollapsed, true);
  SetState(ax::mojom::State::kExpanded, false);

  // We should not notify when initial state (expanded = false, collapsed =
  // false) changes. As changes to initial state generally stands for when the
  // accessibility properties are being set by a view constructor or when the
  // view author explicitly resets the value of expanded and collapsed state. In
  // both these cases we dont wont to fire the accessibility event.
  if (should_notify) {
    NotifyEvent(ax::mojom::Event::kExpandedChanged, true);
  }
}

void ViewAccessibility::RemoveExpandCollapseState() {
  SetState(ax::mojom::State::kExpanded, false);
  SetState(ax::mojom::State::kCollapsed, false);
}

void ViewAccessibility::SetIsVertical(bool vertical) {
  CHECK(!data_.HasState(ax::mojom::State::kHorizontal));
  if (data_.HasState(ax::mojom::State::kVertical)) {
    return;
  }

  SetState(ax::mojom::State::kVertical, vertical);
}

void ViewAccessibility::SetTextSelStart(int32_t text_sel_start) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, text_sel_start);
  NotifyDataChanged();
}

void ViewAccessibility::SetTextSelEnd(int32_t text_sel_end) {
  data_.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, text_sel_end);
  NotifyDataChanged();
}

ui::AXAttributeChangedCallbacks*
ViewAccessibility::GetOrCreateAXAttributeChangedCallbacks() {
  if (!attribute_changed_callbacks_) {
    attribute_changed_callbacks_ =
        std::make_unique<ui::AXAttributeChangedCallbacks>();
  }

  return attribute_changed_callbacks_.get();
}

void ViewAccessibility::NotifyDataChanged() {
  CHECK(view_);
  AXUpdateNotifier::Get()->NotifyViewDataChanged(view_);
}

}  // namespace views
