910e62b5创建于 1月15日历史提交
// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/342213636): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include "ui/accessibility/platform/browser_accessibility_com_win.h"

#include <algorithm>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <utility>

#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/typed_macros.h"
#include "base/win/enum_variant.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "ui/accessibility/platform/browser_accessibility_manager_win.h"
#include "ui/accessibility/platform/browser_accessibility_win.h"
#include "ui/accessibility/ax_common.h"
#include "ui/accessibility/ax_enum_localization_util.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/base/win/accessibility_ids_win.h"
#include "ui/base/win/atl_module.h"

namespace ui {

//
// BrowserAccessibilityComWin::WinAttributes
//

BrowserAccessibilityComWin::WinAttributes::WinAttributes() = default;

BrowserAccessibilityComWin::WinAttributes::~WinAttributes() = default;

//
// BrowserAccessibilityComWin::UpdateState
//

BrowserAccessibilityComWin::UpdateState::UpdateState() = default;

BrowserAccessibilityComWin::UpdateState::~UpdateState() = default;

//
// BrowserAccessibilityComWin
//
BrowserAccessibilityComWin::BrowserAccessibilityComWin()
    : win_attributes_(new WinAttributes()),
      previous_scroll_x_(0),
      previous_scroll_y_(0) {}

BrowserAccessibilityComWin::~BrowserAccessibilityComWin() = default;

void BrowserAccessibilityComWin::OnReferenced() {
  TRACE_EVENT_INSTANT("accessibility", "OnReferenced",
                      perfetto::Flow::FromPointer(this));
}

void BrowserAccessibilityComWin::OnDereferenced() {
  TRACE_EVENT_INSTANT("accessibility", "OnDereferenced",
                      perfetto::Flow::FromPointer(this));
}

//
// IAccessible2 overrides:
//

IFACEMETHODIMP BrowserAccessibilityComWin::get_attributes(BSTR* attributes) {
  // This can be removed once ISimpleDOMNode is migrated
  return AXPlatformNodeWin::get_attributes(attributes);
}

IFACEMETHODIMP BrowserAccessibilityComWin::scrollTo(IA2ScrollType scroll_type) {
  // This can be removed once ISimpleDOMNode is migrated
  return AXPlatformNodeWin::scrollTo(scroll_type);
}

//
// IAccessibleApplication methods.
//

IFACEMETHODIMP BrowserAccessibilityComWin::get_appName(BSTR* app_name) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_appName");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_APP_NAME);

  if (!app_name)
    return E_INVALIDARG;
  *app_name = SysAllocString(
      base::UTF8ToWide(AXPlatform::GetInstance().GetProductName()).c_str());
  DCHECK(*app_name);
  return *app_name ? S_OK : E_FAIL;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_appVersion(BSTR* app_version) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_appVersion");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_APP_VERSION);

  if (!app_version)
    return E_INVALIDARG;

  *app_version = SysAllocString(
      base::UTF8ToWide(AXPlatform::GetInstance().GetProductVersion()).c_str());
  DCHECK(*app_version);
  return *app_version ? S_OK : E_FAIL;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_toolkitName(BSTR* toolkit_name) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_toolkitName");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_TOOLKIT_NAME);
  if (!toolkit_name)
    return E_INVALIDARG;

  *toolkit_name = SysAllocString(FRAMEWORK_ID);
  DCHECK(*toolkit_name);
  return *toolkit_name ? S_OK : E_FAIL;
}

// TODO(https://crbug.com/337998769): Confirm this is the intended behavior of
// this API. Do ATs really need to know more than just the app version?
// In Chrome, we return the User agent string here.
IFACEMETHODIMP BrowserAccessibilityComWin::get_toolkitVersion(
    BSTR* toolkit_version) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_toolkitVersion");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_TOOLKIT_VERSION);
  if (!toolkit_version)
    return E_INVALIDARG;

  *toolkit_version = SysAllocString(
      base::UTF8ToWide(AXPlatform::GetInstance().GetToolkitVersion()).c_str());
  DCHECK(*toolkit_version);
  return *toolkit_version ? S_OK : E_FAIL;
}

//
// IAccessibleImage methods.
//

IFACEMETHODIMP BrowserAccessibilityComWin::get_description(BSTR* desc) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_description");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_DESCRIPTION);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!desc)
    return E_INVALIDARG;

  if (description().empty())
    return S_FALSE;

  *desc = SysAllocString(description().c_str());

  DCHECK(*desc);
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_imagePosition(
    IA2CoordinateType coordinate_type,
    LONG* x,
    LONG* y) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_imagePosition");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_IMAGE_POSITION);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!x || !y)
    return E_INVALIDARG;

  BrowserAccessibilityWin* const owner = GetOwner();
  if (coordinate_type == IA2_COORDTYPE_SCREEN_RELATIVE) {
    gfx::Rect bounds = owner->GetUnclippedScreenBoundsRect();
    *x = bounds.x();
    *y = bounds.y();
  } else if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) {
    gfx::Rect bounds = owner->GetClippedRootFrameBoundsRect();
    gfx::Rect parent_bounds;
    if (BrowserAccessibility* parent = owner->PlatformGetParent(); parent) {
      parent_bounds = parent->GetClippedRootFrameBoundsRect();
    }
    *x = bounds.x() - parent_bounds.x();
    *y = bounds.y() - parent_bounds.y();
  } else {
    return E_INVALIDARG;
  }

  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_imageSize(LONG* height,
                                                         LONG* width) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_imageSize");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_IMAGE_SIZE);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!height || !width)
    return E_INVALIDARG;

  gfx::Rect image_rect = GetOwner()->GetClippedRootFrameBoundsRect();
  *height = image_rect.height();
  *width = image_rect.width();
  return S_OK;
}

//
// IAccessibleText methods.
//

IFACEMETHODIMP BrowserAccessibilityComWin::get_characterExtents(
    LONG offset,
    IA2CoordinateType coordinate_type,
    LONG* out_x,
    LONG* out_y,
    LONG* out_width,
    LONG* out_height) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_characterExtents");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_CHARACTER_EXTENTS);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!out_x || !out_y || !out_width || !out_height) {
    return E_INVALIDARG;
  }

  OnInlineTextBoxesUsed();

  const std::u16string& text_str = GetHypertext();
  HandleSpecialTextOffset(&offset);
  if (offset < 0 || offset > static_cast<LONG>(text_str.size()))
    return E_INVALIDARG;

  BrowserAccessibilityWin* const owner = GetOwner();
  gfx::Rect character_bounds;
  if (coordinate_type == IA2_COORDTYPE_SCREEN_RELATIVE) {
    character_bounds = owner->GetScreenHypertextRangeBoundsRect(
        offset, 1, AXClippingBehavior::kUnclipped);
  } else if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) {
    character_bounds = owner->GetRootFrameHypertextRangeBoundsRect(
        offset, 1, AXClippingBehavior::kUnclipped);
    if (BrowserAccessibility* parent = owner->PlatformGetParent(); parent) {
      character_bounds -=
          parent->GetUnclippedRootFrameBoundsRect().OffsetFromOrigin();
    }
  } else {
    return E_INVALIDARG;
  }

  *out_x = character_bounds.x();
  *out_y = character_bounds.y();
  *out_width = character_bounds.width();
  *out_height = character_bounds.height();

  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_nSelections(LONG* n_selections) {
  return AXPlatformNodeWin::get_nSelections(n_selections);
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_selection(LONG selection_index,
                                                         LONG* start_offset,
                                                         LONG* end_offset) {
  return AXPlatformNodeWin::get_selection(selection_index, start_offset,
                                          end_offset);
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_text(LONG start_offset,
                                                    LONG end_offset,
                                                    BSTR* text) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_text");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_TEXT);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!text) {
    return E_INVALIDARG;
  }

  OnExtendedPropertiesUsed();

  const std::u16string& text_str = GetHypertext();
  HandleSpecialTextOffset(&start_offset);
  HandleSpecialTextOffset(&end_offset);

  // The spec allows the arguments to be reversed.
  if (start_offset > end_offset)
    std::swap(start_offset, end_offset);

  LONG len = static_cast<LONG>(text_str.length());
  if (start_offset < 0 || start_offset > len)
    return E_INVALIDARG;
  if (end_offset < 0 || end_offset > len)
    return E_INVALIDARG;

  std::u16string substr =
      text_str.substr(start_offset, end_offset - start_offset);
  if (substr.empty())
    return S_FALSE;

  *text = SysAllocString(base::as_wcstr(substr));
  DCHECK(*text);
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_newText(
    IA2TextSegment* new_text) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_newText");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_NEW_TEXT);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!new_text)
    return E_INVALIDARG;

  if (!update_state_) {
    return E_FAIL;
  }

  OnExtendedPropertiesUsed();

  size_t start, old_len, new_len;
  ComputeHypertextRemovedAndInserted(update_state_->old_hypertext, &start,
                                     &old_len, &new_len);
  if (new_len == 0)
    return E_FAIL;

  std::u16string substr = GetHypertext().substr(start, new_len);
  new_text->text = SysAllocString(base::as_wcstr(substr));
  new_text->start = static_cast<LONG>(start);
  new_text->end = static_cast<LONG>(start + new_len);
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_oldText(
    IA2TextSegment* old_text) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_oldText");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_OLD_TEXT);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!old_text)
    return E_INVALIDARG;
  if (!update_state_) {
    return E_FAIL;
  }

  OnExtendedPropertiesUsed();

  size_t start, old_len, new_len;
  ComputeHypertextRemovedAndInserted(update_state_->old_hypertext, &start,
                                     &old_len, &new_len);
  if (old_len == 0)
    return E_FAIL;

  const std::u16string& old_hypertext = update_state_->old_hypertext.hypertext;
  std::u16string substr = old_hypertext.substr(start, old_len);
  old_text->text = SysAllocString(base::as_wcstr(substr));
  old_text->start = static_cast<LONG>(start);
  old_text->end = static_cast<LONG>(start + old_len);
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::scrollSubstringTo(
    LONG start_index,
    LONG end_index,
    IA2ScrollType scroll_type) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("scrollSubstringTo");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_SUBSTRING_TO);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  // This is a sign that a screen reader is active, so treat it like inline
  // text box usage.
  OnInlineTextBoxesUsed();
  // TODO(dmazzoni): adjust this for the start and end index, too.
  // TODO(grt): Call an impl fn rather than the COM method.
  return scrollTo(scroll_type);
}

IFACEMETHODIMP BrowserAccessibilityComWin::scrollSubstringToPoint(
    LONG start_index,
    LONG end_index,
    IA2CoordinateType coordinate_type,
    LONG x,
    LONG y) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("scrollSubstringToPoint");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_SUBSTRING_TO_POINT);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  // This is a sign that a screen reader is active, so treat it like inline
  // text box usage.
  OnInlineTextBoxesUsed();

  if (start_index > end_index)
    std::swap(start_index, end_index);
  LONG length = end_index - start_index + 1;
  DCHECK_GE(length, 0);

  BrowserAccessibilityWin* const owner = GetOwner();
  gfx::Rect string_bounds = owner->GetRootFrameHypertextRangeBoundsRect(
      start_index, length, AXClippingBehavior::kUnclipped);
  string_bounds -= owner->GetUnclippedRootFrameBoundsRect().OffsetFromOrigin();
  x -= string_bounds.x();
  y -= string_bounds.y();

  return scrollToPoint(coordinate_type, x, y);
}

IFACEMETHODIMP BrowserAccessibilityComWin::setCaretOffset(LONG offset) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("setCaretOffset");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SET_CARET_OFFSET);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  OnExtendedPropertiesUsed();
  SetIA2HypertextSelection(offset, offset);
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::setSelection(LONG selection_index,
                                                        LONG start_offset,
                                                        LONG end_offset) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("setSelection");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SET_SELECTION);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (selection_index != 0)
    return E_INVALIDARG;
  OnExtendedPropertiesUsed();
  SetIA2HypertextSelection(start_offset, end_offset);
  return S_OK;
}

// IAccessibleText::get_attributes()
// Returns text attributes -- not HTML attributes!
IFACEMETHODIMP BrowserAccessibilityComWin::get_attributes(
    LONG offset,
    LONG* start_offset,
    LONG* end_offset,
    BSTR* text_attributes) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_attributes");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_IATEXT_GET_ATTRIBUTES);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!start_offset || !end_offset || !text_attributes)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();
  *start_offset = *end_offset = 0;
  *text_attributes = nullptr;

  const std::u16string text = GetHypertext();
  HandleSpecialTextOffset(&offset);
  if (offset < 0 || offset > static_cast<LONG>(text.size()))
    return E_INVALIDARG;

  ComputeStylesIfNeeded();
  *start_offset = FindStartOfStyle(offset, ax::mojom::MoveDirection::kBackward);
  *end_offset = FindStartOfStyle(offset, ax::mojom::MoveDirection::kForward);

  std::ostringstream attributes_stream;
  auto iter = offset_to_text_attributes().find(*start_offset);
  if (iter != offset_to_text_attributes().end()) {
    const TextAttributeList& attributes = iter->second;

    for (const TextAttribute& attribute : attributes) {
      // Don't expose the default language value of "en-US".
      // TODO(nektar): Determine if it's possible to check against the interface
      // language.
      if (attribute.first == "language" && attribute.second == "en-US")
        continue;

      attributes_stream << attribute.first << ":" << attribute.second << ";";
    }
  }
  std::wstring attributes_str = base::UTF8ToWide(attributes_stream.str());

  // Returning an empty string is valid and indicates no attributes.
  // This is better than returning S_FALSE which the screen reader
  // may not recognize as valid attributes.
  *text_attributes = SysAllocString(attributes_str.c_str());
  DCHECK(*text_attributes);
  return S_OK;
}

//
// IAccessibleHypertext methods.
//

IFACEMETHODIMP BrowserAccessibilityComWin::get_nHyperlinks(
    LONG* hyperlink_count) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_nHyperlinks");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_HYPERLINKS);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!hyperlink_count)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();

  *hyperlink_count = hypertext_.hyperlink_offset_to_index.size();

  DCHECK(!IsIframe(GetOwner()->GetRole()) || *hyperlink_count <= 1)
      << "iframes should have 1 hyperlink, unless the child document is "
         "destroyed/unloaded, in which case it should have 0";

  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_hyperlink(
    LONG index,
    IAccessibleHyperlink** hyperlink) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_hyperlink");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_HYPERLINK);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!hyperlink || index < 0 ||
      index >= static_cast<LONG>(hypertext_.hyperlinks.size())) {
    return E_INVALIDARG;
  }
  OnExtendedPropertiesUsed();
  *hyperlink = nullptr;

  DCHECK(!IsIframe(GetOwner()->GetRole()) || index == 0)
      << "An iframe cannot have more than 1 hyperlink";

  int32_t id = hypertext_.hyperlinks[index];
  AXPlatformNode* node = AXPlatformNodeWin::GetFromUniqueId(id);
  if (!node) {
    // TODO(https://crbug.com/id=1164043) Fix illegal hyperlink of iframes.
    // Based on information received from DumpWithoutCrashing() reports, this
    // is still sometimes occurring when get_hyperlink() is called on an
    // iframe, which would have exactly 1 hyperlink and no text. The
    // DumpWithoutCrashing() was removed to reduced crash report noise.
    // Interestingly, the top url reported was always called "empty".
    // Sample report for iframe issue: go/crash/93d7fce137a15ef0
    AXTreeManager* manager = GetDelegate()->GetTreeManager();
    LOG(FATAL) << "Hyperlink error:\n index=" << index
               << " nHyperLinks=" << hypertext_.hyperlinks.size()
               << " hyperlink_id=" << id << "\nparent=" << GetDelegate()->node()
               << "\nframe=" << manager->GetRoot()
               << "\nroot=" << manager->GetRootManager()->GetRoot();
    return E_FAIL;
  }
  auto* link = static_cast<BrowserAccessibilityComWin*>(node);
  if (!link)
    return E_FAIL;

  *hyperlink = static_cast<IAccessibleHyperlink*>(link->NewReference());
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_hyperlinkIndex(
    LONG char_index,
    LONG* hyperlink_index) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_hyperlinkIndex");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_HYPERLINK_INDEX);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!hyperlink_index)
    return E_INVALIDARG;
  if (char_index < 0 ||
      char_index >= static_cast<LONG>(GetHypertext().size())) {
    return E_INVALIDARG;
  }

  OnExtendedPropertiesUsed();

  auto it = hypertext_.hyperlink_offset_to_index.find(char_index);
  if (it == hypertext_.hyperlink_offset_to_index.end()) {
    *hyperlink_index = -1;
    return S_FALSE;
  }

  *hyperlink_index = it->second;
  return S_OK;
}

//
// IAccessibleHyperlink methods.
//

// Currently, only text links are supported.
IFACEMETHODIMP BrowserAccessibilityComWin::get_anchor(LONG index,
                                                      VARIANT* anchor) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_anchor");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ANCHOR);
  if (IsDestroyed() || !IsHyperlink()) {
    return E_FAIL;
  }

  // IA2 text links can have only one anchor, that is the text inside them.
  if (index != 0 || !anchor)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();

  BSTR ia2_hypertext = SysAllocString(base::as_wcstr(GetHypertext()));
  DCHECK(ia2_hypertext);
  anchor->vt = VT_BSTR;
  anchor->bstrVal = ia2_hypertext;

  // Returning S_FALSE is not mentioned in the IA2 Spec, but it might have been
  // an oversight.
  if (!SysStringLen(ia2_hypertext))
    return S_FALSE;

  return S_OK;
}

// Currently, only text links are supported.
IFACEMETHODIMP BrowserAccessibilityComWin::get_anchorTarget(
    LONG index,
    VARIANT* anchor_target) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_anchorTarget");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ANCHOR_TARGET);
  if (IsDestroyed() || !IsHyperlink()) {
    return E_FAIL;
  }

  // IA2 text links can have at most one target, that is when they represent an
  // HTML hyperlink, i.e. an <a> element with a "href" attribute.
  if (index != 0 || !anchor_target)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();

  BSTR target;
  if (!(MSAAState() & STATE_SYSTEM_LINKED) ||
      FAILED(GetStringAttributeAsBstr(ax::mojom::StringAttribute::kUrl,
                                      &target))) {
    target = SysAllocString(L"");
  }
  DCHECK(target);
  anchor_target->vt = VT_BSTR;
  anchor_target->bstrVal = target;

  // Returning S_FALSE is not mentioned in the IA2 Spec, but it might have been
  // an oversight.
  if (!SysStringLen(target))
    return S_FALSE;

  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_startIndex(LONG* index) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_startIndex");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_START_INDEX);
  if (IsDestroyed() || !IsHyperlink()) {
    return E_FAIL;
  }
  if (!index)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();

  int32_t hypertext_offset = 0;
  auto* parent = GetOwner()->PlatformGetParent();
  if (parent) {
    hypertext_offset =
        ToBrowserAccessibilityComWin(parent)->GetHypertextOffsetFromChild(this);
  }
  *index = static_cast<LONG>(hypertext_offset);
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_endIndex(LONG* index) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_endIndex");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_END_INDEX);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  OnExtendedPropertiesUsed();
  LONG start_index;
  // TODO(grt): Call an impl fn rather than the COM method.
  HRESULT hr = get_startIndex(&start_index);
  if (hr == S_OK)
    *index = start_index + 1;
  return hr;
}

// This method is deprecated in the IA2 Spec.
IFACEMETHODIMP BrowserAccessibilityComWin::get_valid(boolean* valid) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_valid");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_VALID);
  return E_NOTIMPL;
}

//
// IAccessibleAction partly implemented.
//

IFACEMETHODIMP BrowserAccessibilityComWin::nActions(LONG* n_actions) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("nActions");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_N_ACTIONS);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!n_actions)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();

  BrowserAccessibilityWin* const owner = GetOwner();
  *n_actions = static_cast<LONG>(
      owner->GetSupportedActions().size() +
      owner->GetIntListAttribute(ax::mojom::IntListAttribute::kActionsIds)
          .size());
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::doAction(LONG action_index) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("doAction");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_DO_ACTION);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  OnExtendedPropertiesUsed();

  BrowserAccessibilityWin* const owner = GetOwner();
  const std::vector<ax::mojom::Action> actions = owner->GetSupportedActions();
  const std::vector<int32_t>& aria_actions =
      owner->GetIntListAttribute(ax::mojom::IntListAttribute::kActionsIds);

  // Actions can be from Blink for the given markup, or from the aria-actions
  // attribute defined by the author.
  if (action_index < 0 ||
      action_index >= static_cast<LONG>(actions.size() + aria_actions.size())) {
    return E_INVALIDARG;
  }

  AXActionData data;

  // Handle Blink action.
  if (action_index < static_cast<LONG>(actions.size())) {
    data.action = actions[action_index];
    owner->AccessibilityPerformAction(data);
    return S_OK;
  }

  // action_index refers to a position in the combined Blink actions and
  // aria-actions vector. To find the corresponding index in the aria_actions
  // vector, subtract the number of Blink actions.
  int32_t aria_action_id = aria_actions[action_index - actions.size()];
  data.action = ax::mojom::Action::kDoDefault;
  GetFromID(aria_action_id)->GetOwner()->AccessibilityPerformAction(data);
  return S_OK;
}

IFACEMETHODIMP
BrowserAccessibilityComWin::get_description(LONG action_index,
                                            BSTR* description) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_description");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_IAACTION_GET_DESCRIPTION);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  OnExtendedPropertiesUsed();
  return E_NOTIMPL;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_keyBinding(LONG action_index,
                                                          LONG n_max_bindings,
                                                          BSTR** key_bindings,
                                                          LONG* n_bindings) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_keyBinding");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_KEY_BINDING);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!key_bindings || !n_bindings) {
    return E_INVALIDARG;
  }

  OnExtendedPropertiesUsed();

  *key_bindings = nullptr;
  *n_bindings = 0;

  BrowserAccessibilityWin* const owner = GetOwner();
  const std::vector<ax::mojom::Action> actions = owner->GetSupportedActions();
  if (action_index < 0 || action_index >= static_cast<LONG>(actions.size())) {
    return E_INVALIDARG;
  }

  // Only the default action, in index 0, may have a key binding. If it does,
  // it will be stored in the attribute kAccessKey.
  std::u16string key_binding_string;
  if (action_index != 0 || !owner->HasDefaultActionVerb() ||
      !owner->GetString16Attribute(ax::mojom::StringAttribute::kAccessKey,
                                   &key_binding_string)) {
    return S_FALSE;
  }

  *n_bindings = 1;
  *key_bindings = static_cast<BSTR*>(CoTaskMemAlloc(sizeof(BSTR)));
  (*key_bindings)[0] = SysAllocString(base::as_wcstr(key_binding_string));
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_name(LONG action_index,
                                                    BSTR* name) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_name");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_NAME);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!name) {
    return E_INVALIDARG;
  }

  OnExtendedPropertiesUsed();

  BrowserAccessibilityWin* const owner = GetOwner();
  const std::vector<ax::mojom::Action> actions = owner->GetSupportedActions();
  const std::vector<int32_t>& aria_actions =
      owner->GetIntListAttribute(ax::mojom::IntListAttribute::kActionsIds);

  if (action_index < 0 ||
      action_index >= static_cast<LONG>(actions.size() + aria_actions.size())) {
    *name = nullptr;
    return E_INVALIDARG;
  }

  int action;
  std::string action_verb;
  if (action_index == 0 &&
      owner->GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb,
                             &action)) {
    action_verb =
        ui::ToString(static_cast<ax::mojom::DefaultActionVerb>(action));
  } else if (action_index < static_cast<LONG>(actions.size())) {
    action_verb = ui::ToString(actions[action_index]);
  } else {
    // action_index refers to a position in the combined Blink actions and
    // aria-actions vector. To find the corresponding index in the aria_actions
    // vector, subtract the number of Blink actions.
    int32_t aria_action_id = aria_actions[action_index - actions.size()];
    BrowserAccessibilityComWin* aria_action_obj = GetFromID(aria_action_id);
    const std::string& html_id = aria_action_obj->GetStringAttribute(
        ax::mojom::StringAttribute::kHtmlId);
    action_verb = html_id.empty()
                      ? AXPlatformNodeBase::kAriaActionsPrefix
                      : AXPlatformNodeBase::kAriaActionsPrefix + "_" + html_id;
  }

  if (action_verb.empty() || action_verb.compare("none") == 0) {
    *name = nullptr;
    return S_FALSE;
  }

  *name = SysAllocString(base::as_wcstr(base::UTF8ToUTF16(action_verb)));
  DCHECK(name);
  return S_OK;
}

IFACEMETHODIMP
BrowserAccessibilityComWin::get_localizedName(LONG action_index,
                                              BSTR* localized_name) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_localizedName");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_LOCALIZED_NAME);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!localized_name) {
    return E_INVALIDARG;
  }

  OnExtendedPropertiesUsed();

  BrowserAccessibilityWin* const owner = GetOwner();
  const std::vector<ax::mojom::Action> actions = owner->GetSupportedActions();
  const std::vector<int32_t>& aria_actions =
      owner->GetIntListAttribute(ax::mojom::IntListAttribute::kActionsIds);

  if (action_index < 0 ||
      action_index >= static_cast<LONG>(actions.size() + aria_actions.size())) {
    *localized_name = nullptr;
    return E_INVALIDARG;
  }

  int action;
  std::string action_verb;

  // Blink actions and aria-actions are handled differently.
  if (action_index < static_cast<LONG>(actions.size())) {
    if (!owner->GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb,
                                &action) ||
        action_index != 0) {
      // There aren't localized names for actions except default ones, we fall
      // back to returning the hard-coded, not localized name.
      return get_name(action_index, localized_name);
    }

    action_verb =
        ToLocalizedString(static_cast<ax::mojom::DefaultActionVerb>(action));
  } else {
    // action_index refers to a position in the combined Blink actions and
    // aria-actions vector. To find the corresponding index in the aria_actions
    // vector, subtract the number of Blink actions.
    int32_t aria_action_id = aria_actions[action_index - actions.size()];
    BrowserAccessibilityComWin* aria_action_obj = GetFromID(aria_action_id);

    action_verb = aria_action_obj->GetName();
  }

  if (action_verb.empty()) {
    *localized_name = nullptr;
    return S_FALSE;
  }

  *localized_name =
      SysAllocString(base::as_wcstr(base::UTF8ToUTF16(action_verb)));
  DCHECK(localized_name);
  return S_OK;
}

//
// ISimpleDOMDocument methods.
//

IFACEMETHODIMP BrowserAccessibilityComWin::get_URL(BSTR* url) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_URL");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_URL);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  auto* manager = Manager();
  if (!manager)
    return E_FAIL;

  if (!url)
    return E_INVALIDARG;

  if (GetOwner() != manager->GetBrowserAccessibilityRoot()) {
    return E_FAIL;
  }

  OnExtendedPropertiesUsed();

  std::string str = manager->GetTreeData().url;
  if (str.empty())
    return S_FALSE;

  *url = SysAllocString(base::UTF8ToWide(str).c_str());
  DCHECK(*url);

  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_title(BSTR* title) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_title");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_TITLE);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  auto* manager = Manager();
  if (!manager)
    return E_FAIL;

  if (!title)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();

  std::string str = manager->GetTreeData().title;
  if (str.empty())
    return S_FALSE;

  *title = SysAllocString(base::UTF8ToWide(str).c_str());
  DCHECK(*title);

  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_mimeType(BSTR* mime_type) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_mimeType");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_MIME_TYPE);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  auto* manager = Manager();
  if (!manager)
    return E_FAIL;

  if (!mime_type)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();

  std::string str = manager->GetTreeData().mimetype;
  if (str.empty())
    return S_FALSE;

  *mime_type = SysAllocString(base::UTF8ToWide(str).c_str());
  DCHECK(*mime_type);

  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_docType(BSTR* doc_type) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_docType");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_DOC_TYPE);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  auto* manager = Manager();
  if (!manager)
    return E_FAIL;

  if (!doc_type)
    return E_INVALIDARG;

  OnExtendedPropertiesUsed();

  std::string str = manager->GetTreeData().doctype;
  if (str.empty())
    return S_FALSE;

  *doc_type = SysAllocString(base::UTF8ToWide(str).c_str());
  DCHECK(*doc_type);

  return S_OK;
}

IFACEMETHODIMP
BrowserAccessibilityComWin::get_nameSpaceURIForID(SHORT name_space_id,
                                                  BSTR* name_space_uri) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_nameSpaceURIForID");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_NAMESPACE_URI_FOR_ID);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  OnExtendedPropertiesUsed();
  return E_NOTIMPL;
}

IFACEMETHODIMP
BrowserAccessibilityComWin::put_alternateViewMediaTypes(
    BSTR* comma_separated_media_types) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("put_alternateViewMediaTypes");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_PUT_ALTERNATE_VIEW_MEDIA_TYPES);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  OnExtendedPropertiesUsed();
  return E_NOTIMPL;
}

//
// ISimpleDOMNode methods.
//

IFACEMETHODIMP BrowserAccessibilityComWin::get_nodeInfo(
    BSTR* node_name,
    SHORT* name_space_id,
    BSTR* node_value,
    unsigned int* num_children,
    unsigned int* unique_id,
    USHORT* node_type) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_nodeInfo");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_NODE_INFO);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!node_name || !name_space_id || !node_value || !num_children ||
      !unique_id || !node_type) {
    return E_INVALIDARG;
  }

  OnExtendedPropertiesUsed();

  BrowserAccessibilityWin* const owner = GetOwner();
  std::u16string tag;
  if (owner->GetString16Attribute(ax::mojom::StringAttribute::kHtmlTag, &tag)) {
    *node_name = SysAllocString(base::as_wcstr(tag));
  } else {
    *node_name = nullptr;
  }

  *name_space_id = 0;
  *node_value = SysAllocString(value().c_str());
  *num_children = owner->PlatformChildCount();
  *unique_id = -AXPlatformNodeWin::GetUniqueId();

  if (ui::IsPlatformDocument(owner->GetRole())) {
    *node_type = NODETYPE_DOCUMENT;
  } else if (owner->IsText()) {
    *node_type = NODETYPE_TEXT;
  } else {
    *node_type = NODETYPE_ELEMENT;
  }

  return S_OK;
}

// ISimpleDOMNode::get_attributes()
// Returns HTML attributes -- not text attributes!
// TODO(https://crbug.com/378908266) Remove 3 years after JAWS stops using.
IFACEMETHODIMP BrowserAccessibilityComWin::get_attributes(USHORT max_attribs,
                                                          BSTR* attrib_names,
                                                          SHORT* name_space_id,
                                                          BSTR* attrib_values,
                                                          USHORT* num_attribs) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_attributes");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ISIMPLEDOMNODE_GET_ATTRIBUTES);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!IsWebContent()) {
    return E_FAIL;
  }

  if (!attrib_names || !name_space_id || !attrib_values || !num_attribs)
    return E_INVALIDARG;

  AXPlatform::GetInstance().OnHTMLAttributesUsed();

#define ADD_ATTRIBUTE(name, value)                                          \
  if (index < max_attribs) {                                                \
    attrib_names[index] = SysAllocString(base::UTF8ToWide(name).c_str());   \
    attrib_values[index] = SysAllocString(base::UTF8ToWide(value).c_str()); \
    ++index;                                                                \
  }

  BrowserAccessibilityWin* const owner = GetOwner();
  // Add computed attributes first.
  USHORT index = 0;
  if (int max_length =
          owner->GetIntAttribute(ax::mojom::IntAttribute::kMaxLength)) {
    ADD_ATTRIBUTE("maxlength", base::NumberToString(max_length));
  }

  // JAWS 2024 and earlier use aria-label directly.
  // Do not use on image, where kAttribute is used for "alt".
  if (owner->GetData().GetNameFrom() == ax::mojom::NameFrom::kAttribute &&
      !ui::IsImage(owner->GetRole())) {
    ADD_ATTRIBUTE("aria-label",
                  owner->GetStringAttribute(ax::mojom::StringAttribute::kName));
  }

  // For OmniPass—rebranded as Fiserv Verifast. See https://crbug.com/378908266.
  const std::string& type_attr =
      owner->GetStringAttribute(ax::mojom::StringAttribute::kInputType);
  if (!type_attr.empty()) {
    ADD_ATTRIBUTE("type", type_attr);
  }
  const std::string& value_attr =
      owner->GetStringAttribute(ax::mojom::StringAttribute::kValue);
  if (!value_attr.empty()) {
    ADD_ATTRIBUTE("value", value_attr);
  }
  const std::string& name_attr =
      owner->GetStringAttribute(ax::mojom::StringAttribute::kHtmlInputName);
  if (!name_attr.empty()) {
    ADD_ATTRIBUTE("name", name_attr);
  }
  // JAWS url reading feature for links depends on "href", before JAWS 2025.
  if (ui::IsLink(owner->GetRole())) {
    ADD_ATTRIBUTE("href",
                  owner->GetStringAttribute(ax::mojom::StringAttribute::kUrl));
  }

  // Vispero's Inspect tool needs this temporarily, until they start tracking
  // nodes using the unique id. Also used by OmniPass / Fiserve Verifast.
  const std::string& id_attr =
      owner->GetStringAttribute(ax::mojom::StringAttribute::kHtmlId);
  if (!id_attr.empty()) {
    ADD_ATTRIBUTE("id", id_attr);
  }

  // Next add serialized attributes.
  const auto& serialized_attrs = owner->GetHtmlAttributes();
  for (const auto& serialized_attr : serialized_attrs) {
    ADD_ATTRIBUTE(serialized_attr.first, serialized_attr.second);
  }

  *num_attribs = index;

  return S_OK;
}

// ISimpleDOMNode::get_attributesForNames()
// Returns HTML attributes -- not text attributes!
IFACEMETHODIMP BrowserAccessibilityComWin::get_attributesForNames(
    USHORT num_attribs,
    BSTR* attrib_names,
    SHORT* name_space_id,
    BSTR* attrib_values) {
  // Workaround for math not being spoken by NVDA and its extension Access8Math.
  // https://github.com/nvaccess/nvda/issues/17421
  // https://crbug.com/380161205
  // Should normally return E_NOTIMPL.
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ATTRIBUTES_FOR_NAMES);
  return S_FALSE;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_computedStyle(
    USHORT max_style_properties,
    boolean use_alternate_view,
    BSTR* style_properties,
    BSTR* style_values,
    USHORT* num_style_properties) {
  // JAWS/NVDA no longer use this (and we only supported "display" property,
  // which is also exposed via the "display" object attribute).
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_COMPUTED_STYLE);
  return E_NOTIMPL;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_computedStyleForProperties(
    USHORT num_style_properties,
    boolean use_alternate_view,
    BSTR* style_properties,
    BSTR* style_values) {
  // JAWS/NVDA no longer use this (and we only supported "display" property,
  // which is also exposed via the "display" object attribute).
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_COMPUTED_STYLE_FOR_PROPERTIES);
  return E_NOTIMPL;
}

IFACEMETHODIMP BrowserAccessibilityComWin::scrollTo(boolean placeTopLeft) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("scrollTo");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ISIMPLEDOMNODE_SCROLL_TO);
  return scrollTo(placeTopLeft ? IA2_SCROLL_TYPE_TOP_LEFT
                               : IA2_SCROLL_TYPE_ANYWHERE);
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_parentNode(
    ISimpleDOMNode** node) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_parentNode");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PARENT_NODE);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!node)
    return E_INVALIDARG;

  *node = ToBrowserAccessibilityComWin(GetOwner()->PlatformGetParent())
              ->NewReference();
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_firstChild(
    ISimpleDOMNode** node) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_firstChild");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_FIRST_CHILD);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!node)
    return E_INVALIDARG;

  BrowserAccessibilityWin* const owner = GetOwner();
  if (owner->PlatformChildCount() == 0) {
    *node = NULL;
    return S_FALSE;
  }

  *node = ToBrowserAccessibilityComWin(owner->PlatformGetFirstChild())
              ->NewReference();
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_lastChild(
    ISimpleDOMNode** node) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_lastChild");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_LAST_CHILD);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!node)
    return E_INVALIDARG;

  BrowserAccessibilityWin* const owner = GetOwner();
  if (owner->PlatformChildCount() == 0) {
    *node = NULL;
    return S_FALSE;
  }

  *node = ToBrowserAccessibilityComWin(owner->PlatformGetLastChild())
              ->NewReference();
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_previousSibling(
    ISimpleDOMNode** node) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_previousSibling");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PREVIOUS_SIBLING);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!node)
    return E_INVALIDARG;

  BrowserAccessibilityWin* const owner = GetOwner();
  std::optional<size_t> index_in_parent = std::nullopt;
  if (owner->PlatformGetParent()) {
    index_in_parent = GetIndexInParent();
  }
  if (!index_in_parent.has_value() || index_in_parent.value() == 0) {
    *node = NULL;
    return S_FALSE;
  }

  *node = ToBrowserAccessibilityComWin(owner->InternalGetPreviousSibling())
              ->NewReference();
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_nextSibling(
    ISimpleDOMNode** node) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_nextSibling");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_NEXT_SIBLING);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!node)
    return E_INVALIDARG;

  BrowserAccessibilityWin* const owner = GetOwner();
  std::optional<size_t> index_in_parent = std::nullopt;
  if (owner->PlatformGetParent()) {
    index_in_parent = GetIndexInParent();
  }
  if (!index_in_parent.has_value() ||
      (index_in_parent.value() + 1) >=
          owner->PlatformGetParent()->InternalChildCount()) {
    *node = NULL;
    return S_FALSE;
  }

  *node = ToBrowserAccessibilityComWin(owner->InternalGetNextSibling())
              ->NewReference();
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_childAt(unsigned int child_index,
                                                       ISimpleDOMNode** node) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_childAt");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_CHILD_AT);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!node)
    return E_INVALIDARG;

  BrowserAccessibilityWin* const owner = GetOwner();
  if (child_index >= owner->PlatformChildCount()) {
    return E_INVALIDARG;
  }

  BrowserAccessibility* child = owner->PlatformGetChild(child_index);
  if (!child) {
    *node = NULL;
    return S_FALSE;
  }

  *node = ToBrowserAccessibilityComWin(child)->NewReference();
  return S_OK;
}

// We only support this method for retrieving MathML content.
IFACEMETHODIMP BrowserAccessibilityComWin::get_innerHTML(BSTR* innerHTML) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_innerHTML");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_INNER_HTML);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!IsWebContent()) {
    return E_FAIL;
  }
  // Inner HTML is exposed only for math, only when kExtendedProperties is on.
  OnExtendedPropertiesUsed();
  BrowserAccessibilityWin* const owner = GetOwner();
  if (owner->GetRole() != ax::mojom::Role::kMath &&
      owner->GetRole() != ax::mojom::Role::kMathMLMath) {
    // TODO(nektar): Make sure we only get calls for Math nodes.
    return E_NOTIMPL;
  }

  std::u16string inner_html =
      owner->GetString16Attribute(ax::mojom::StringAttribute::kMathContent);
  *innerHTML = SysAllocString(base::as_wcstr(inner_html));
  DCHECK(*innerHTML);
  return S_OK;
}

IFACEMETHODIMP
BrowserAccessibilityComWin::get_localInterface(void** local_interface) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_localInterface");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_LOCAL_INTERFACE);
  return E_NOTIMPL;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_language(BSTR* language) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_language");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_LANGUAGE);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!language) {
    return E_INVALIDARG;
  }

  OnExtendedPropertiesUsed();
  *language = nullptr;

  std::wstring lang = base::UTF8ToWide(GetOwner()->node()->GetLanguage());
  if (lang.empty())
    lang = L"en-US";

  *language = SysAllocString(lang.c_str());
  DCHECK(*language);
  return S_OK;
}

//
// ISimpleDOMText methods.
//

IFACEMETHODIMP BrowserAccessibilityComWin::get_domText(BSTR* dom_text) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_domText");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_DOM_TEXT);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!dom_text)
    return E_INVALIDARG;

  return GetNameAsBstr(dom_text);
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_clippedSubstringBounds(
    unsigned int start_index,
    unsigned int end_index,
    int* out_x,
    int* out_y,
    int* out_width,
    int* out_height) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_clippedSubstringBounds");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_CLIPPED_SUBSTRING_BOUNDS);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  OnInlineTextBoxesUsed();
  // TODO(dmazzoni): fully support this API by intersecting the
  // rect with the container's rect.
  return get_unclippedSubstringBounds(start_index, end_index, out_x, out_y,
                                      out_width, out_height);
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_unclippedSubstringBounds(
    unsigned int start_index,
    unsigned int end_index,
    int* out_x,
    int* out_y,
    int* out_width,
    int* out_height) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_unclippedSubstringBounds");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_UNCLIPPED_SUBSTRING_BOUNDS);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (!out_x || !out_y || !out_width || !out_height)
    return E_INVALIDARG;

  OnInlineTextBoxesUsed();

  unsigned int text_length = static_cast<unsigned int>(GetHypertext().size());
  if (start_index > text_length || end_index > text_length ||
      start_index > end_index) {
    return E_INVALIDARG;
  }

  gfx::Rect bounds = GetOwner()->GetScreenHypertextRangeBoundsRect(
      start_index, end_index - start_index, AXClippingBehavior::kUnclipped);
  *out_x = bounds.x();
  *out_y = bounds.y();
  *out_width = bounds.width();
  *out_height = bounds.height();
  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::scrollToSubstring(
    unsigned int start_index,
    unsigned int end_index) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("scrollToSubstring");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_TO_SUBSTRING);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  auto* manager = Manager();
  if (!manager)
    return E_FAIL;

  OnInlineTextBoxesUsed();

  unsigned int text_length = static_cast<unsigned int>(GetHypertext().size());
  if (start_index > text_length || end_index > text_length ||
      start_index > end_index) {
    return E_INVALIDARG;
  }

  BrowserAccessibilityWin* const owner = GetOwner();
  manager->ScrollToMakeVisible(*owner,
                               owner->GetRootFrameHypertextRangeBoundsRect(
                                   start_index, end_index - start_index,
                                   AXClippingBehavior::kUnclipped));

  return S_OK;
}

IFACEMETHODIMP BrowserAccessibilityComWin::get_fontFamily(BSTR* font_family) {
  WIN_ACCESSIBILITY_API_TRACE_EVENT("get_fontFamily");
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_FONT_FAMILY);
  if (IsDestroyed()) {
    return E_FAIL;
  }
  if (!font_family) {
    return E_INVALIDARG;
  }

  OnExtendedPropertiesUsed();

  *font_family = nullptr;
  std::u16string family = GetOwner()->GetInheritedString16Attribute(
      ax::mojom::StringAttribute::kFontFamily);
  if (family.empty())
    return S_FALSE;

  *font_family = SysAllocString(base::as_wcstr(family));
  DCHECK(*font_family);
  return S_OK;
}

//
// IServiceProvider methods.
//

IFACEMETHODIMP BrowserAccessibilityComWin::QueryService(REFGUID guid_service,
                                                        REFIID riid,
                                                        void** object) {
  TRACE_EVENT("accessibility", "QueryService",
              perfetto::Flow::FromPointer(this), "guidService",
              base::WideToASCII(base::win::WStringFromGUID(guid_service)),
              "riid", base::WideToASCII(base::win::WStringFromGUID(riid)));
  WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_QUERY_SERVICE);
  if (IsDestroyed()) {
    return E_FAIL;
  }

  if (guid_service == GUID_IAccessibleContentDocument) {
    // Special Mozilla extension: return the accessible for the root document.
    // Screen readers use this to distinguish between a document loaded event
    // on the root document vs on an iframe.
    BrowserAccessibility* node = GetOwner();
    while (node->PlatformGetParent())
      node =
          node->PlatformGetParent()->manager()->GetBrowserAccessibilityRoot();
    return ToBrowserAccessibilityComWin(node)->QueryInterface(IID_IAccessible2,
                                                              object);
  }

  if (guid_service == IID_IAccessible || guid_service == IID_IAccessible2 ||
      guid_service == IID_IAccessibleAction ||
      guid_service == IID_IAccessibleApplication ||
      guid_service == IID_IAccessibleHyperlink ||
      guid_service == IID_IAccessibleHypertext ||
      guid_service == IID_IAccessibleImage ||
      guid_service == IID_IAccessibleTable ||
      guid_service == IID_IAccessibleTable2 ||
      guid_service == IID_IAccessibleTableCell ||
      guid_service == IID_IAccessibleText ||
      guid_service == IID_IAccessibleTextSelectionContainer ||
      guid_service == IID_IAccessibleValue ||
      guid_service == IID_ISimpleDOMDocument ||
      guid_service == IID_ISimpleDOMNode ||
      guid_service == IID_ISimpleDOMText || guid_service == GUID_ISimpleDOM) {
    return QueryInterface(riid, object);
  }

  *object = NULL;
  return E_FAIL;
}

//
// CComObjectRootEx methods.
//

// static
STDMETHODIMP BrowserAccessibilityComWin::InternalQueryInterface(
    void* this_ptr,
    const _ATL_INTMAP_ENTRY* entries,
    REFIID iid,
    void** object) {
  BrowserAccessibilityComWin* accessibility =
      reinterpret_cast<BrowserAccessibilityComWin*>(this_ptr);

  if (!accessibility || accessibility->IsDestroyed()) {
    *object = nullptr;
    return E_NOINTERFACE;
  }

  if (iid == IID_IAccessibleImage) {
    const ax::mojom::Role role = accessibility->GetOwner()->GetRole();
    if (!IsImage(role)) {
      *object = nullptr;
      return E_NOINTERFACE;
    }
  } else if (iid == IID_ISimpleDOMDocument) {
    if (!ui::IsPlatformDocument(accessibility->GetRole())) {
      *object = nullptr;
      return E_NOINTERFACE;
    }
  } else if (iid == IID_IAccessibleHyperlink) {
    if (!accessibility->IsHyperlink()) {
      *object = nullptr;
      return E_NOINTERFACE;
    }
  }

  return AXPlatformNodeWin::InternalQueryInterface(this_ptr, entries, iid,
                                                   object);
}

void BrowserAccessibilityComWin::ComputeStylesIfNeeded() {
  if (!offset_to_text_attributes().empty())
    return;

  TextAttributeList default_attributes =
      AXPlatformNodeWin::ComputeTextAttributes();
  TextAttributeMap attributes_map =
      GetDelegate()->ComputeTextAttributeMap(default_attributes);
  win_attributes_->offset_to_text_attributes.swap(attributes_map);
}

//
// Private methods.
//

void BrowserAccessibilityComWin::UpdateStep1ComputeWinAttributes(
    UpdateState* update_state) {
  DCHECK(!update_state_);
  DCHECK(win_attributes_);

  BrowserAccessibilityWin* const owner = GetOwner();

  // Move win_attributes_ and hypertext_ into update_state, allowing us to see
  // exactly what changed and fire appropriate events. Note that update_state is
  // destroyed after UpdateStep3FireEvents.
  update_state->old_win_attributes = std::move(win_attributes_);
  update_state->old_hypertext = std::move(hypertext_);

  // Hold a pointer to this node's update state (which the caller of this
  // function shall keep valid) until the end of UpdateStep3FireEvents.
  update_state_ = update_state;

  hypertext_ = AXLegacyHypertext();

  win_attributes_ = std::make_unique<WinAttributes>();

  win_attributes_->ia_role = MSAARole();
  win_attributes_->ia_state = MSAAState();

  win_attributes_->ia2_role = ComputeIA2Role();
  // If we didn't explicitly set the IAccessible2 role, make it the same
  // as the MSAA role.
  if (!win_attributes_->ia2_role)
    win_attributes_->ia2_role = win_attributes_->ia_role;
  win_attributes_->ia2_state = ComputeIA2State();
  win_attributes_->name = base::UTF8ToWide(owner->GetName());
  win_attributes_->description = base::UTF8ToWide(
      owner->GetStringAttribute(ax::mojom::StringAttribute::kDescription));
  win_attributes_->value = base::UTF16ToWide(GetValueForControl());
  win_attributes_->ignored = owner->IsIgnored();
}

void BrowserAccessibilityComWin::UpdateStep2ComputeHypertext() {
  DCHECK(update_state_);
  UpdateComputedHypertext();
}

void BrowserAccessibilityComWin::UpdateStep3FireEvents() {
  DCHECK(update_state_);
  DCHECK(update_state_->old_win_attributes);

  BrowserAccessibilityWin* const owner = GetOwner();
  const bool ignored = owner->IsIgnored();

  const auto& old_win_attributes = *update_state_->old_win_attributes;

  // Suppress all of these events when the node is ignored, or when the ignored
  // state has changed on a node that isn't part of an active live region.
  if (ignored || (old_win_attributes.ignored != ignored &&
                  !owner->GetData().IsContainedInActiveLiveRegion() &&
                  !owner->GetData().IsActiveLiveRegionRoot())) {
    update_state_ = nullptr;
    return;
  }

  // The rest of the events only fire on changes, not on new objects.

  if (old_win_attributes.ia_role != 0) {
    // Fire an event if the description, help, or value changes.
    if (description() != old_win_attributes.description) {
      FireNativeEvent(EVENT_OBJECT_DESCRIPTIONCHANGE);
    }

    // Fire an event if this container object has scrolled.
    int sx = 0;
    int sy = 0;
    if (owner->GetIntAttribute(ax::mojom::IntAttribute::kScrollX, &sx) &&
        owner->GetIntAttribute(ax::mojom::IntAttribute::kScrollY, &sy)) {
      if (sx != previous_scroll_x_ || sy != previous_scroll_y_)
        FireNativeEvent(EVENT_SYSTEM_SCROLLINGEND);
      previous_scroll_x_ = sx;
      previous_scroll_y_ = sy;
    }

    // Fire hypertext-related events.
    // Do not fire removed/inserted when a name change event will be fired by
    // AXEventGenerator, as they are providing redundant information and will
    // lead to duplicate announcements.
    if (name() == old_win_attributes.name ||
        GetNameFrom() == ax::mojom::NameFrom::kContents) {
      size_t start, old_len, new_len;
      ComputeHypertextRemovedAndInserted(update_state_->old_hypertext, &start,
                                         &old_len, &new_len);
      if (old_len > 0) {
        // In-process screen readers may call IAccessibleText::get_oldText
        // in reaction to this event to retrieve the text that was removed.
        FireNativeEvent(IA2_EVENT_TEXT_REMOVED);
      }
      if (new_len > 0) {
        // In-process screen readers may call IAccessibleText::get_newText
        // in reaction to this event to retrieve the text that was inserted.
        FireNativeEvent(IA2_EVENT_TEXT_INSERTED);
      }
    }
  }

  update_state_ = nullptr;
}

BrowserAccessibilityWin* BrowserAccessibilityComWin::GetOwner() const {
  return static_cast<BrowserAccessibilityWin*>(GetDelegate());
}

BrowserAccessibilityManager* BrowserAccessibilityComWin::Manager() const {
  auto* manager = GetOwner()->manager();
  DCHECK(manager);
  return manager;
}

BrowserAccessibilityComWin* BrowserAccessibilityComWin::NewReference() {
  AddRef();
  return this;
}

BrowserAccessibilityComWin* BrowserAccessibilityComWin::GetTargetFromChildID(
    const VARIANT& var_id) {
  if (IsDestroyed()) {
    return nullptr;
  }

  if (var_id.vt != VT_I4)
    return nullptr;

  LONG child_id = var_id.lVal;
  if (child_id == CHILDID_SELF)
    return this;

  BrowserAccessibilityWin* const owner = GetOwner();
  if (child_id >= 1 &&
      child_id <= static_cast<LONG>(owner->PlatformChildCount())) {
    return ToBrowserAccessibilityComWin(owner->PlatformGetChild(child_id - 1));
  }

  auto* child = static_cast<BrowserAccessibilityComWin*>(
      AXPlatformNodeWin::GetFromUniqueId(-child_id));
  if (child && child->GetOwner()->IsDescendantOf(owner)) {
    return child;
  }

  return nullptr;
}

HRESULT BrowserAccessibilityComWin::GetStringAttributeAsBstr(
    ax::mojom::StringAttribute attribute,
    BSTR* value_bstr) {
  if (IsDestroyed()) {
    return E_FAIL;
  }

  std::u16string str;
  if (!GetOwner()->GetString16Attribute(attribute, &str)) {
    return S_FALSE;
  }

  *value_bstr = SysAllocString(base::as_wcstr(str));
  DCHECK(*value_bstr);

  return S_OK;
}

HRESULT BrowserAccessibilityComWin::GetNameAsBstr(BSTR* value_bstr) {
  if (IsDestroyed()) {
    return E_FAIL;
  }

  std::u16string str;
  str = GetOwner()->GetNameAsString16();
  *value_bstr = SysAllocString(base::as_wcstr(str));
  DCHECK(*value_bstr);

  return S_OK;
}

void BrowserAccessibilityComWin::SetIA2HypertextSelection(LONG start_offset,
                                                          LONG end_offset) {
  HandleSpecialTextOffset(&start_offset);
  HandleSpecialTextOffset(&end_offset);
  SetHypertextSelection(start_offset, end_offset);
}

LONG BrowserAccessibilityComWin::FindStartOfStyle(
    LONG start_offset,
    ax::mojom::MoveDirection direction) {
  LONG text_length = static_cast<LONG>(GetHypertext().length());
  DCHECK_GE(start_offset, 0);
  DCHECK_LE(start_offset, text_length);

  switch (direction) {
    case ax::mojom::MoveDirection::kNone:
      NOTREACHED();
    case ax::mojom::MoveDirection::kBackward: {
      if (offset_to_text_attributes().empty())
        return 0;

      auto iterator = offset_to_text_attributes().upper_bound(start_offset);
      --iterator;
      return static_cast<LONG>(iterator->first);
    }
    case ax::mojom::MoveDirection::kForward: {
      const auto iterator =
          offset_to_text_attributes().upper_bound(start_offset);
      if (iterator == offset_to_text_attributes().end())
        return text_length;
      return static_cast<LONG>(iterator->first);
    }
  }

  NOTREACHED();
}

BrowserAccessibilityComWin* BrowserAccessibilityComWin::GetFromID(
    int32_t id) const {
  if (IsDestroyed()) {
    return nullptr;
  }
  return ToBrowserAccessibilityComWin(Manager()->GetFromID(id));
}

void BrowserAccessibilityComWin::FireNativeEvent(LONG win_event_type) const {
  // We only allow events on descendants of a platform leaf when that platform
  // leaf is a popup button parent of a menu list popup. On Windows, the menu
  // list popup is not part of the tree when its parent is collapsed but events
  // should be fired anyway.
  BrowserAccessibilityWin* const owner = GetOwner();
  if (owner->IsChildOfLeaf() && !owner->GetCollapsedMenuListSelectAncestor()) {
    return;
  }

  Manager()->ToBrowserAccessibilityManagerWin()->FireWinAccessibilityEvent(
      win_event_type, owner);
}

BrowserAccessibilityComWin* ToBrowserAccessibilityComWin(
    BrowserAccessibility* obj) {
  if (!obj)
    return nullptr;
  auto* result = static_cast<BrowserAccessibilityWin*>(obj)->GetCOM();
  return result;
}

}  // namespace ui