// Copyright 2025 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/tree/widget_ax_manager.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/accessibility/platform/browser_accessibility_manager.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/tree/widget_view_ax_cache.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

#if BUILDFLAG(IS_WIN)
#include "ui/views/win/hwnd_util.h"
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_MAC)
#include "ui/views/widget/native_widget_mac.h"
#endif  // BUILDFLAG(IS_MAC)

namespace {

using Event = ax::mojom::Event;

bool ShouldSerializeEvent(Event event_type) {
  // Events that are serialized and forwarded to BrowserAccessibilityManager.
  switch (event_type) {
    // TODO(crbug.com/40672441): Add events that must be serialized directly.
    case Event::kAlert:
    case Event::kControlsChanged:
    case Event::kEndOfTest:
    case Event::kTooltipClosed:
    case Event::kTooltipOpened:
    case Event::kWindowActivated:
    case Event::kWindowDeactivated:
    case Event::kWindowVisibilityChanged:
      return true;
    default:
      break;
  }

  // Events that are auto-generated by BrowserAccessibilityManager during
  // serialization, so we don't serialize them from views.
  switch (event_type) {
    // TODO(crbug.com/40672441): Add events here as needed.
    case Event::kActiveDescendantChanged:
    case Event::kCheckedStateChanged:
    case Event::kChildrenChanged:
    case Event::kEnabledChanged:
    case Event::kExpandedChanged:
    case Event::kFocus:
    case Event::kMenuPopupEnd:
    case Event::kMenuPopupStart:
    case Event::kLiveRegionChanged:
    case Event::kSelection:
    case Event::kSelectedChildrenChanged:
    case Event::kTextChanged:
    case Event::kTextSelectionChanged:
    case Event::kValueChanged:
      return false;
    default:
      break;
  }

  // Events that may be fired from views, but aren't fired to accessibility APIs
  // so we don't serialize them. Usually, these are only fired from views for
  // ChromeOS (which uses a different mechanism to serialize the tree) and were
  // not handled on Windows, Mac, or Linux at the time of performing the ViewsAX
  // migration.
  switch (event_type) {
    // TODO(crbug.com/40672441): Add events here as needed.
    case Event::kLocationChanged:
    case Event::kRowCollapsed:
    case Event::kRowCountChanged:
    case Event::kRowExpanded:
    case Event::kScrollPositionChanged:
    case Event::kStateChanged:
    case Event::kTreeChanged:
      return false;
    default:
      break;
  }

  // Events fired by views on some platforms but not yet handled. These are
  // being addressed incrementally, one event at a time.
  switch (event_type) {
    case Event::kFocusAfterMenuClose:
    case Event::kMenuEnd:
    case Event::kMenuStart:
      return false;
    default:
      break;
  }

  NOTREACHED() << "Unhandled event " << event_type
               << " in ShouldSerializeEvent";
}

}  // namespace

namespace views {

WidgetAXManager::WidgetAXManager(Widget* widget)
    : widget_(widget),
      ax_tree_id_(ui::AXTreeID::CreateNewAXTreeID()),
      cache_(std::make_unique<WidgetViewAXCache>()) {
  CHECK(ViewAccessibility::IsViewsAccessibilityTreeEnabled())
      << "WidgetAXManager should only be created when the "
         "accessibility tree feature is enabled.";

  if (widget_) {
    widget_created_ = widget_->IsNativeWidgetInitialized();
    widget_observation_.Observe(widget_);
  }
  ui::AXPlatform::GetInstance().AddModeObserver(this);
}

WidgetAXManager::~WidgetAXManager() {
  ClearAXTreeHost();
  DetachFromParentTree();
  ui::AXPlatform::GetInstance().RemoveModeObserver(this);
  ax_tree_manager_.reset();
}

void WidgetAXManager::Init() {
  CHECK(widget_->GetRootView());
  if (ui::AXPlatform::GetInstance().GetMode().has_mode(
          ui::AXMode::kNativeAPIs)) {
    EnableWhenWidgetCreated();
  } else {
    if (widget_->is_top_level()) {
      InitAXTreeManager();
    }
  }
}

void WidgetAXManager::OnEvent(ViewAccessibility& view_ax,
                              ax::mojom::Event event_type) {
  if (!is_enabled_) {
    return;
  }

  if (!ShouldSerializeEvent(event_type)) {
    return;
  }

  pending_events_.push_back({view_ax.GetUniqueId(), event_type});
  pending_data_updates_.insert(view_ax.GetUniqueId());

  // kTooltipClosed fires just before the tooltip widget is destroyed (see
  // TooltipAura::Hide).  Since SchedulePendingUpdate uses PostTask with a weak
  // pointer, the callback would be invalidated when the widget is destroyed,
  // silently dropping the event.  Flush synchronously to ensure the event
  // reaches BrowserAccessibilityManager before the widget is torn down.
  //
  // kTooltipOpened is also flushed synchronously.  The tooltip widget's views
  // are already in place when Show() fires kTooltipOpened (`widget_->Show()`
  // precedes the event in TooltipAura::Show), so the tree is ready for
  // serialization.  Flushing synchronously ensures platform events
  // (EVENT_OBJECT_SHOW / UIA_ToolTipOpenedEventId) fire before callers that
  // check for them (e.g. event recorders in tests) run their next step.
  //
  // kMenuPopupEnd fires just before the menu widget is hidden. Flush it while
  // the widget can still emit generated MENU_POPUP_END platform events.
  if (event_type == ax::mojom::Event::kTooltipClosed ||
      event_type == ax::mojom::Event::kTooltipOpened ||
      event_type == ax::mojom::Event::kMenuPopupEnd) {
    SendPendingUpdate();
  } else {
    SchedulePendingUpdate();
  }
}

void WidgetAXManager::OnTransientFocusRequested(ViewAccessibility& view_ax) {
  if (!is_enabled_) {
    return;
  }

  CHECK(tree_source_);
  pending_data_updates_.insert(view_ax.GetUniqueId());
  tree_source_->SetTransientFocusIdForNextSerialization(view_ax.GetUniqueId());
  auto clear_transient_focus = absl::MakeCleanup(
      [this] { tree_source_->ClearTransientFocusIdForNextSerialization(); });
  SendPendingUpdate();
}

void WidgetAXManager::OnDataChanged(ViewAccessibility& view_ax) {
  if (!is_enabled_) {
    return;
  }

  pending_data_updates_.insert(view_ax.GetUniqueId());

  SchedulePendingUpdate();
}

void WidgetAXManager::OnChildAdded(ViewAccessibility& child,
                                   ViewAccessibility& parent) {
  if (!is_enabled_) {
    return;
  }

  cache_->Insert(&child);
  pending_data_updates_.insert(parent.GetUniqueId());

  SchedulePendingUpdate();
}

void WidgetAXManager::OnChildRemoved(ViewAccessibility& child,
                                     ViewAccessibility& parent) {
  if (!is_enabled_) {
    return;
  }

  cache_->Remove(child.GetUniqueId());
  pending_data_updates_.insert(parent.GetUniqueId());

  SchedulePendingUpdate();
}

void WidgetAXManager::OnChildManagerAdded(WidgetAXManager& child_manager) {
  child_manager.UpdateParentTreeConnection();
}

void WidgetAXManager::OnChildManagerRemoved(WidgetAXManager& child_manager) {
  RemoveChildWidgetTreeHost(child_manager.ax_tree_id_);
  child_manager.SetParentAXTreeID(ui::AXTreeIDUnknown());
}

void WidgetAXManager::AppendChildWidgetTreeHosts(
    std::vector<raw_ptr<ViewAccessibility>>& out) const {
  for (const auto& host : child_widget_tree_hosts_) {
    out.push_back(host.get());
  }
}

void WidgetAXManager::UpdateParentTreeConnection() {
  Widget* parent = widget_ ? widget_->parent() : nullptr;
  WidgetAXManager* parent_manager = parent ? parent->ax_manager() : nullptr;

  // A View that hosts this tree owns the connection, and that View can live in
  // a widget that is not the parent widget.
  if (ax_tree_host_tracker_.view()) {
    if (parent_manager) {
      parent_manager->RemoveChildWidgetTreeHost(ax_tree_id_);
    }
    return;
  }

  if (!parent_manager) {
    DetachFromParentTree();
    return;
  }

  SetParentAXTreeID(parent_manager->ax_tree_id_);

  // A hidden widget stays out of the parent tree.
  if (!widget_->IsVisible()) {
    parent_manager->RemoveChildWidgetTreeHost(ax_tree_id_);
    return;
  }

  parent_manager->AddChildWidgetTreeHost(ax_tree_id_);
}

void WidgetAXManager::DetachFromParentTree() {
  Widget* parent = widget_ ? widget_->parent() : nullptr;
  if (WidgetAXManager* parent_manager =
          parent ? parent->ax_manager() : nullptr) {
    parent_manager->RemoveChildWidgetTreeHost(ax_tree_id_);
  }
  SetParentAXTreeID(ui::AXTreeIDUnknown());
}

void WidgetAXManager::AddChildWidgetTreeHost(
    const ui::AXTreeID& child_tree_id) {
  View* root_view = widget_ ? widget_->GetRootView() : nullptr;
  if (!root_view || FindChildWidgetTreeHost(child_tree_id)) {
    return;
  }

  // An ignored host is transparent, thus the hosted tree takes its place.
  auto owned_host = std::make_unique<AXVirtualView>();
  owned_host->SetRole(ax::mojom::Role::kNone);
  AXVirtualView* host = owned_host.get();
  child_widget_tree_hosts_.push_back(std::move(owned_host));

  ViewAccessibility& root_view_ax = root_view->GetViewAccessibility();
  host->set_parent_view(&root_view_ax);
  host->SetChildTreeID(child_tree_id);
  OnChildAdded(*host, root_view_ax);
}

void WidgetAXManager::RemoveChildWidgetTreeHost(
    const ui::AXTreeID& child_tree_id) {
  AXVirtualView* host = FindChildWidgetTreeHost(child_tree_id);
  if (!host) {
    return;
  }

  if (ViewAccessibility* parent = host->parent_view()) {
    OnChildRemoved(*host, *parent);
    host->set_parent_view(nullptr);
  }
  std::erase_if(child_widget_tree_hosts_,
                [host](const std::unique_ptr<AXVirtualView>& candidate) {
                  return candidate.get() == host;
                });
}

AXVirtualView* WidgetAXManager::FindChildWidgetTreeHost(
    const ui::AXTreeID& child_tree_id) const {
  for (const auto& host : child_widget_tree_hosts_) {
    if (host->GetChildTreeID() == child_tree_id) {
      return host.get();
    }
  }
  return nullptr;
}

void WidgetAXManager::HostAXTreeInView(ViewAccessibility& host_view_ax) {
  View* host_view = host_view_ax.view();
  Widget* host_widget = host_view_ax.GetWidget();
  WidgetAXManager* host_manager =
      host_widget ? host_widget->ax_manager() : nullptr;
  if (!host_view || !host_manager || host_manager == this) {
    ClearAXTreeHost();
    UpdateParentTreeConnection();
    return;
  }

  if (View* current_host_view = ax_tree_host_tracker_.view();
      current_host_view && current_host_view != host_view) {
    ClearAXTreeHost();
  }

  ++ax_tree_host_generation_;
  ax_tree_host_tracker_.SetView(host_view);
  // Only one node may claim this tree, so drop the host in the parent widget.
  UpdateParentTreeConnection();
  SetParentAXTreeID(host_manager->ax_tree_id_);
  host_view_ax.SetChildTreeID(ax_tree_id_);
}

void WidgetAXManager::ScheduleUnhostAXTree() {
  if (!ax_tree_host_tracker_) {
    UpdateParentTreeConnection();
    return;
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&WidgetAXManager::UnhostAXTreeAfterFlush,
                                weak_factory_.GetWeakPtr(), ax_tree_id_,
                                ax_tree_host_generation_));
}

void WidgetAXManager::AddObserver(WidgetAXManagerObserver* observer) {
  observers_.AddObserver(observer);
}

void WidgetAXManager::RemoveObserver(WidgetAXManagerObserver* observer) {
  observers_.RemoveObserver(observer);
}

void WidgetAXManager::OnAXModeAdded(ui::AXMode mode) {
  if (mode.has_mode(ui::AXMode::kNativeAPIs)) {
    EnableWhenWidgetCreated();
  }
}

void WidgetAXManager::OnWidgetCreated(Widget* widget) {
  CHECK_EQ(widget_, widget);
  widget_created_ = true;
  if (enable_on_widget_created_) {
    enable_on_widget_created_ = false;
    Enable();
  }
}

void WidgetAXManager::OnWidgetDestroyed(Widget* widget) {
  CHECK_EQ(widget_, widget);
  widget_observation_.Reset();
}

void WidgetAXManager::OnWidgetVisibilityChanged(Widget* widget, bool visible) {
  CHECK_EQ(widget_, widget);
  UpdateParentTreeConnection();
}

gfx::NativeViewAccessible WidgetAXManager::GetNativeViewAccessibleForId(
    ui::AXNodeID id) {
  if (!ax_tree_manager_) {
    return gfx::NativeViewAccessible();
  }
  ui::BrowserAccessibility* browser_node = ax_tree_manager_->GetFromID(id);
  if (!browser_node) {
    return gfx::NativeViewAccessible();
  }
  return browser_node->GetNativeViewAccessible();
}

ui::AXTreeID WidgetAXManager::GetAXTreeID() const {
  if (!ax_tree_manager_) {
    return ui::AXTreeIDUnknown();
  }

  return ax_tree_id_;
}

base::WeakPtr<ui::AXPlatformTreeManager>
WidgetAXManager::GetAXTreeManagerWeakPtrForTesting() {
  if (!ax_tree_manager_) {
    return nullptr;
  }
  return ax_tree_manager_->GetWeakPtr();
}

ui::AXPlatformNodeId WidgetAXManager::GetOrCreateAXNodeUniqueId(
    ui::AXNodeID ax_node_id) {
  // ViewAccessibility already generates a unique ID for each View. Return it.
  ViewAccessibility* view_ax = cache_->Get(ax_node_id);
  return view_ax ? view_ax->GetUniqueId() : ui::AXPlatformNodeId();
}

void WidgetAXManager::OnAXNodeDeleted(ui::AXNodeID ax_node_id) {
  // Do nothing. Those unique IDs aren't cached in WidgetAXManager, so they
  // don't need to be removed.
}

void WidgetAXManager::AccessibilityPerformAction(const ui::AXActionData& data) {
  tree_source_->HandleAccessibleAction(data);
}

bool WidgetAXManager::AccessibilityViewHasFocus() {
  return widget_ && widget_->IsActive();
}

void WidgetAXManager::AccessibilityViewSetFocus() {
  if (!widget_ || widget_->IsActive()) {
    return;
  }
  widget_->Activate();
}

gfx::Rect WidgetAXManager::AccessibilityGetViewBounds() {
  if (!widget_) {
    return gfx::Rect();
  }
  // View-to-screen conversions are anchored at the client area, not the window.
  return widget_->GetClientAreaBoundsInScreen();
}

float WidgetAXManager::AccessibilityGetDeviceScaleFactor() {
  // TODO(crbug.com/40672441): Confirm that the DSF is always 1.0f once we
  // serialize the views and can test it manually.
  return 1.0f;
}

void WidgetAXManager::UnrecoverableAccessibilityError() {
  // TODO(accessibility): Implement.
}

gfx::AcceleratedWidget WidgetAXManager::AccessibilityGetAcceleratedWidget() {
  // This method is only used on Windows, where we need the HWND to fire events.
#if BUILDFLAG(IS_WIN)
  if (!widget_) {
    return gfx::kNullAcceleratedWidget;
  }
  return HWNDForView(widget_->GetRootView());
#else
  return gfx::kNullAcceleratedWidget;
#endif  // BUILDFLAG(IS_WIN)
}

gfx::NativeViewAccessible
WidgetAXManager::AccessibilityGetNativeViewAccessible() {
  if (!widget_) {
    return gfx::NativeViewAccessible();
  }
#if BUILDFLAG(IS_MAC)
  // On macOS, the chromium accessibility tree is attached to an NSView. We must
  // return the NativeViewAccessible for the NSView to connect our internal tree
  // to the native one.
  if (auto* native_widget =
          static_cast<NativeWidgetMac*>(widget_->native_widget())) {
    return native_widget->GetNativeViewAccessibleForNSView();
  }
  return gfx::NativeViewAccessible();
#elif BUILDFLAG(IS_WIN)
  return HWNDNativeViewAccessibleForWidget(widget_);
#else
  return gfx::NativeViewAccessible();
#endif
}

gfx::NativeViewAccessible
WidgetAXManager::AccessibilityGetNativeViewAccessibleForWindow() {
  if (!widget_) {
    return gfx::NativeViewAccessible();
  }
#if BUILDFLAG(IS_MAC)
  // On macOS, the chromium accessibility tree is attached to an NSView itself
  // connected to an NSWindow. We must return the NativeViewAccessible for the
  // NSWindow to connect our internal tree to the native one.
  //
  // This function is only called on macOS to retrieve the NSWindow associated
  // with the node in the tree. No need to implement on other platforms for now.
  if (auto* native_widget =
          static_cast<NativeWidgetMac*>(widget_->native_widget())) {
    return native_widget->GetNativeViewAccessibleForNSWindow();
  }
#endif  // BUILDFLAG(IS_MAC)
  return gfx::NativeViewAccessible();
}

void WidgetAXManager::AccessibilityHitTest(
    const gfx::Point& point_in_view_pixels,
    const ax::mojom::Event& opt_event_to_fire,
    int opt_request_id,
    base::OnceCallback<void(ui::AXPlatformTreeManager* hit_manager,
                            ui::AXNodeID hit_node_id)> opt_callback) {
  // TODO(accessibility): Implement.
}

gfx::NativeWindow WidgetAXManager::GetTopLevelNativeWindow() {
  if (!widget_) {
    return gfx::NativeWindow();
  }

  auto* top = widget_->GetTopLevelWidget();
  if (!top) {
    return gfx::NativeWindow();
  }

  return top->GetNativeWindow();
}

bool WidgetAXManager::CanFireAccessibilityEvents() const {
  // Use IsVisible() rather than IsActive().  Tooltip, menu, and other non-
  // activatable widgets are visible to the user and must fire accessibility
  // events for assistive technology to track them.  IsActive() is always false
  // for TYPE_TOOLTIP widgets, which would silently drop all events.
  // Also check IsNativeWidgetInitialized() because this method can be called
  // during Widget::Init() before the native widget's window has a layer.
  return widget_ && widget_->IsNativeWidgetInitialized() &&
         widget_->IsVisible();
}

bool WidgetAXManager::AccessibilityIsRootFrame() const {
  // This always returns false for WidgetAXManager, since the "frame" concept is
  // unique to web content.
  return false;
}

bool WidgetAXManager::ShouldSuppressAXLoadComplete() {
  return true;
}

content::WebContentsAccessibility*
WidgetAXManager::AccessibilityGetWebContentsAccessibility() {
  return nullptr;
}

bool WidgetAXManager::AccessibilityIsWebContentSource() {
  return false;
}

void WidgetAXManager::OnDidChangeFocus(View* focused_before,
                                       View* focused_now) {
  // focused_node_id_ persists when the widget deactivates, matching web
  // content behavior. The BAM gates on AccessibilityViewHasFocus() to
  // suppress events when the window isn't active.
  focused_node_id_ = GetFocusedViewNodeId();
  if (is_enabled_) {
    CHECK(tree_source_);
    tree_source_->SetFocusedNodeId(focused_node_id_);
    SchedulePendingUpdate();
  }
}

void WidgetAXManager::OnFocusManagerDestroying(FocusManager* focus_manager) {
  focus_manager_observation_.Reset();
  focused_node_id_ = ui::kInvalidAXNodeID;
  CHECK(tree_source_);
  tree_source_->SetFocusedNodeId(ui::kInvalidAXNodeID);
}

void WidgetAXManager::StartObservingFocus() {
  if (focus_manager_observation_.IsObserving() || !widget_) {
    return;
  }
  FocusManager* fm = widget_->GetFocusManager();
  if (!fm) {
    return;
  }
  focus_manager_observation_.Observe(fm);
  focused_node_id_ = GetFocusedViewNodeId();
  CHECK(tree_source_);
  tree_source_->SetFocusedNodeId(focused_node_id_);
}

// Returns the focused view's AXNodeID, or kInvalidAXNodeID if no view
// has focus. Active descendants are not resolved here; the AXTree
// applies the activedescendant relationship when resolving focus,
// matching how Blink populates focus_id.
ui::AXNodeID WidgetAXManager::GetFocusedViewNodeId() const {
  if (!widget_) {
    return ui::kInvalidAXNodeID;
  }
  FocusManager* fm = widget_->GetFocusManager();
  if (!fm || !fm->GetFocusedView()) {
    return ui::kInvalidAXNodeID;
  }
  return fm->GetFocusedView()->GetViewAccessibility().GetUniqueId();
}

void WidgetAXManager::SchedulePendingUpdate() {
  if (processing_update_posted_ || !is_enabled_) {
    return;
  }

  processing_update_posted_ = true;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&WidgetAXManager::SendPendingUpdate,
                                weak_factory_.GetWeakPtr()));
}

void WidgetAXManager::InitAXTreeManager() {
  CHECK(!ax_tree_manager_);
  ui::AXNodeData root_data;
  widget_->GetRootView()->GetViewAccessibility().GetAccessibleNodeData(
      &root_data);
  ui::AXTreeUpdate update;
  update.root_id = root_data.id;
  update.nodes.push_back(root_data);

  update.has_tree_data = true;
  update.tree_data.tree_id = ax_tree_id_;
  update.tree_data.parent_tree_id = parent_ax_tree_id_;

  cache_->Init(widget_->GetRootView()->GetViewAccessibility(),
               false /* full_tree */);

  ax_tree_manager_.reset(
      ui::BrowserAccessibilityManager::Create(update, *this, this));
}

void WidgetAXManager::EnableWhenWidgetCreated() {
  if (!widget_) {
    return;
  }

  if (!widget_created_) {
    // WidgetAXManager can be attached after OnWidgetCreated() has already fired.
    widget_created_ = widget_->IsNativeWidgetInitialized();
  }

  if (widget_created_) {
    Enable();
    return;
  }

  enable_on_widget_created_ = true;
}

void WidgetAXManager::Enable() {
  if (is_enabled_) {
    return;
  }
  UpdateParentTreeConnection();
  is_enabled_ = true;
  tree_source_ = std::make_unique<ViewAccessibilityAXTreeSource>(
      widget_->GetRootView()->GetViewAccessibility().GetUniqueId(), ax_tree_id_,
      cache_.get());
  tree_source_->SetParentTreeId(parent_ax_tree_id_);
  tree_serializer_ =
      std::make_unique<ViewAccessibilityAXTreeSerializer>(tree_source_.get());

  // It's possible the AXTreeManager was already created if this widget is
  // top-level and accessibility wasn't enabled before. Ensure it is created
  // now.
  if (!ax_tree_manager_) {
    InitAXTreeManager();
  }
  cache_->Init(widget_->GetRootView()->GetViewAccessibility());

  StartObservingFocus();

  // Fully serialize the tree starting from the root immediately.
  pending_data_updates_.insert(
      widget_->GetRootView()->GetViewAccessibility().GetUniqueId());
  SendPendingUpdate();
  NotifyEnabled();
}

void WidgetAXManager::NotifyEnabled() {
  for (WidgetAXManagerObserver& observer : observers_) {
    observer.OnWidgetAXManagerEnabled();
  }
}

void WidgetAXManager::SetParentAXTreeID(const ui::AXTreeID& parent_ax_tree_id) {
  if (parent_ax_tree_id_ == parent_ax_tree_id) {
    return;
  }

  parent_ax_tree_id_ = parent_ax_tree_id;
  if (!tree_source_) {
    return;
  }

  tree_source_->SetParentTreeId(parent_ax_tree_id_);
  if (!is_enabled_) {
    return;
  }

  // The root view is already gone when the widget tears the manager down.
  View* root_view = widget_->GetRootView();
  if (!root_view) {
    return;
  }

  pending_data_updates_.insert(root_view->GetViewAccessibility().GetUniqueId());
  SchedulePendingUpdate();
}

void WidgetAXManager::ClearAXTreeHost() {
  ++ax_tree_host_generation_;

  if (View* host_view = ax_tree_host_tracker_.view()) {
    ViewAccessibility& host_view_ax = host_view->GetViewAccessibility();
    if (host_view_ax.GetChildTreeID() == ax_tree_id_) {
      host_view_ax.RemoveChildTreeID();
    }
  }

  ax_tree_host_tracker_.SetView(nullptr);
}

void WidgetAXManager::UnhostAXTreeAfterFlush(
    base::WeakPtr<WidgetAXManager> manager,
    ui::AXTreeID child_tree_id,
    uint32_t host_generation) {
  if (!manager || manager->ax_tree_host_generation_ != host_generation) {
    return;
  }

  if (View* host_view = manager->ax_tree_host_tracker_.view()) {
    ViewAccessibility& host_view_ax = host_view->GetViewAccessibility();
    if (host_view_ax.GetChildTreeID() != child_tree_id) {
      return;
    }
  }

  manager->ClearAXTreeHost();
  manager->UpdateParentTreeConnection();
}

void WidgetAXManager::SendPendingUpdate() {
  std::optional<ui::AXUpdatesAndEvents> maybe_updates_and_events;
  // Always invoke the test callback on exit.
  auto exit_cleanup = absl::MakeCleanup([this, &maybe_updates_and_events]() {
    if (!updates_and_events_callback_for_testing_.is_null()) {
      updates_and_events_callback_for_testing_.Run(maybe_updates_and_events);
    }
  });

  processing_update_posted_ = false;
  if (!is_enabled_) {
    return;
  }

  std::vector<ui::AXTreeUpdate> tree_updates;
  std::vector<ui::AXEvent> events;

  auto pending_events_copy = std::move(pending_events_);
  auto pending_data_changes_copy = std::move(pending_data_updates_);
  pending_events_.clear();
  pending_data_updates_.clear();

  absl::flat_hash_set<ui::AXNodeID> already_serialized_ids;

  // Serialize all changes first. This is necessary to ensure the nodes are
  // added in the cache (through the SerializeChanges call below).
  for (auto& id : pending_data_changes_copy) {
    if (already_serialized_ids.contains(id)) {
      // Don't serialize already serialized nodes a second time.
      continue;
    }

    ViewAccessibility* view_ax = cache_->Get(id);
    if (!view_ax) {
      continue;
    }

    ui::AXTreeUpdate update;
    if (!tree_serializer_->SerializeChanges(view_ax, &update)) {
      return;
    }

    for (auto& node : update.nodes) {
      already_serialized_ids.insert(node.id);
    }

    tree_updates.push_back(std::move(update));
  }

  // Serialize the events after.
  for (auto& event_copy : pending_events_copy) {
    const int id = event_copy.id;
    const ax::mojom::Event event_type = event_copy.event_type;

    CHECK(ShouldSerializeEvent(event_type));

    ViewAccessibility* view_ax = cache_->Get(id);
    if (!view_ax) {
      continue;
    }

    // We must fire the event if the node is in the client tree. To determine
    // if it is, we need to serialize the node first. We might have serialized
    // it already in the previous loop, so don't serialize it again if
    // it's already in the pending data changes. This happens when a view
    // updates its data and fires an event related to that data change. In the
    // pending_data_changes_copy loop above, we would have already serialized
    // the node in such a case -- no need to do it again.
    if (!pending_data_changes_copy.contains(id)) {
      if (already_serialized_ids.contains(id)) {
        // Don't serialize already serialized nodes a second time.
        continue;
      }

      ui::AXTreeUpdate update;
      if (!tree_serializer_->SerializeChanges(view_ax, &update)) {
        return;
      }
      tree_updates.push_back(std::move(update));
    }

    // Fire the event on the node, but only if it's actually in the tree.
    // Sometimes we get events fired on nodes with an ancestor that's
    // marked invisible, for example. In those cases we should still
    // call SerializeChanges (because the change may have affected the
    // ancestor) but we shouldn't fire the event on the node not in the tree.
    //
    // TODO(crbug.com/40672441): Add test once we have a way to dump events
    // for views.
    if (tree_serializer_->IsInClientTree(view_ax)) {
      ui::AXEvent event;
      event.id = view_ax->GetUniqueId();
      event.event_type = event_type;
      events.push_back(std::move(event));
    }
  }

  // Ensure the focused node is serialized so it is added to the accessibility
  // tree cache before tree_data.focus_id refers to it.
  if (focused_node_id_ != ui::kInvalidAXNodeID &&
      !already_serialized_ids.contains(focused_node_id_)) {
    if (ViewAccessibility* focused_ax = cache_->Get(focused_node_id_)) {
      ui::AXTreeUpdate update;
      if (tree_serializer_->SerializeChanges(focused_ax, &update)) {
        for (auto& node : update.nodes) {
          already_serialized_ids.insert(node.id);
        }
        tree_updates.push_back(std::move(update));
      }
    }
  }

  if (tree_updates.empty() && events.empty()) {
    // Nothing to do, no updates or events.
    return;
  }

  for (const auto& update : tree_updates) {
    for (const auto& node : update.nodes) {
      CHECK(cache_->Get(node.id))
          << "Unknown serialized node. All nodes we serialize should be known "
             "to the WidgetAXManager.";
    }
  }

  maybe_updates_and_events.emplace();
  maybe_updates_and_events->ax_tree_id = ax_tree_id_;
  maybe_updates_and_events->updates = std::move(tree_updates);
  maybe_updates_and_events->events = std::move(events);

  ax_tree_manager_->OnAccessibilityEvents(*maybe_updates_and_events);
}

}  // namespace views
