chromium/ui/accessibility/platform/ax_platform_relation_win.cc

// 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/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

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

#include <wrl/client.h>

#include <algorithm>
#include <vector>

#include "base/lazy_instance.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/enum_variant.h"
#include "base/win/scoped_variant.h"
#include "third_party/iaccessible2/ia2_api_all.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_mode_observer.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/ax_text_utils.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/win/atl_module.h"
#include "ui/gfx/geometry/rect_conversions.h"

namespace ui {

AXPlatformRelationWin::AXPlatformRelationWin() {
  win::CreateATLModuleIfNeeded();
}

AXPlatformRelationWin::~AXPlatformRelationWin() {}

std::wstring GetIA2RelationFromIntAttr(ax::mojom::IntAttribute attribute) {
  switch (attribute) {
    case ax::mojom::IntAttribute::kMemberOfId:
      return IA2_RELATION_MEMBER_OF;
    case ax::mojom::IntAttribute::kPopupForId:
      // Map "popup for" to "controlled by".
      // Unlike ATK there is no special IA2 popup-for relationship, but it can
      // be exposed via the controlled by relation, which is also computed for
      // content as the reverse of the controls relationship.
      return IA2_RELATION_CONTROLLED_BY;
    default:
      return std::wstring();
  }
}

std::wstring GetIA2RelationFromIntListAttr(
    ax::mojom::IntListAttribute attribute) {
  switch (attribute) {
    case ax::mojom::IntListAttribute::kControlsIds:
      return IA2_RELATION_CONTROLLER_FOR;
    case ax::mojom::IntListAttribute::kDescribedbyIds:
      return IA2_RELATION_DESCRIBED_BY;
    case ax::mojom::IntListAttribute::kDetailsIds:
      return IA2_RELATION_DETAILS;
    case ax::mojom::IntListAttribute::kErrormessageIds:
      return IA2_RELATION_ERROR;
    case ax::mojom::IntListAttribute::kFlowtoIds:
      return IA2_RELATION_FLOWS_TO;
    case ax::mojom::IntListAttribute::kLabelledbyIds:
      return IA2_RELATION_LABELLED_BY;
    default:
      return std::wstring();
  }
}

std::wstring GetIA2ReverseRelationFromIntAttr(
    ax::mojom::IntAttribute attribute) {
  switch (attribute) {
    default:
      return std::wstring();
  }
}

std::wstring GetIA2ReverseRelationFromIntListAttr(
    ax::mojom::IntListAttribute attribute) {
  switch (attribute) {
    case ax::mojom::IntListAttribute::kControlsIds:
      return IA2_RELATION_CONTROLLED_BY;
    case ax::mojom::IntListAttribute::kDescribedbyIds:
      return IA2_RELATION_DESCRIPTION_FOR;
    case ax::mojom::IntListAttribute::kDetailsIds:
      return IA2_RELATION_DETAILS_FOR;
    case ax::mojom::IntListAttribute::kErrormessageIds:
      return IA2_RELATION_ERROR_FOR;
    case ax::mojom::IntListAttribute::kFlowtoIds:
      return IA2_RELATION_FLOWS_FROM;
    case ax::mojom::IntListAttribute::kLabelledbyIds:
      return IA2_RELATION_LABEL_FOR;
    default:
      return std::wstring();
  }
}

// static
int AXPlatformRelationWin::EnumerateRelationships(
    AXPlatformNodeBase* node,
    int desired_index,
    const std::wstring& desired_ia2_relation,
    std::wstring* out_ia2_relation,
    std::vector<AXPlatformNode*>* out_targets) {
  AXPlatformNodeDelegate* delegate = node->GetDelegate();

  // The first time this is called, populate vectors with all of the
  // int attributes and intlist attributes that have reverse relations
  // we care about on Windows. Computing these by calling
  // GetIA2ReverseRelationFrom{Int|IntList}Attr on every possible attribute
  // simplifies the work needed to support an additional relation
  // attribute in the future.
  static std::vector<ax::mojom::IntAttribute>
      int_attributes_with_reverse_relations;
  static std::vector<ax::mojom::IntListAttribute>
      intlist_attributes_with_reverse_relations;
  static bool first_time = true;
  if (first_time) {
    for (int32_t attr_index =
             static_cast<int32_t>(ax::mojom::IntAttribute::kNone);
         attr_index <= static_cast<int32_t>(ax::mojom::IntAttribute::kMaxValue);
         ++attr_index) {
      auto attr = static_cast<ax::mojom::IntAttribute>(attr_index);
      if (!GetIA2ReverseRelationFromIntAttr(attr).empty())
        int_attributes_with_reverse_relations.push_back(attr);
    }
    for (int32_t attr_index =
             static_cast<int32_t>(ax::mojom::IntListAttribute::kNone);
         attr_index <=
         static_cast<int32_t>(ax::mojom::IntListAttribute::kMaxValue);
         ++attr_index) {
      auto attr = static_cast<ax::mojom::IntListAttribute>(attr_index);
      if (!GetIA2ReverseRelationFromIntListAttr(attr).empty())
        intlist_attributes_with_reverse_relations.push_back(attr);
    }
    first_time = false;
  }

  // Enumerate all of the relations and reverse relations that
  // are exposed via IAccessible2 on Windows. We do this with a series
  // of loops. Every time we encounter one, we check if the caller
  // requested that particular relation by index, and return it.
  // Otherwise we build up and return the total number of relations found.
  int total_count = 0;

  // Iterate over all int attributes on this node to check the ones
  // that correspond to IAccessible2 relations.
  for (const auto& attribute_value_pair : node->GetIntAttributes()) {
    ax::mojom::IntAttribute int_attribute = attribute_value_pair.first;
    std::wstring relation = GetIA2RelationFromIntAttr(int_attribute);
    if (!relation.empty() &&
        (desired_ia2_relation.empty() || desired_ia2_relation == relation)) {
      AXPlatformNode* target =
          delegate->GetTargetNodeForRelation(int_attribute);
      if (!target) {
        continue;
      }
      if (desired_index == total_count) {
        *out_ia2_relation = relation;
        out_targets->push_back(target);
        return 1;
      }
      total_count++;
    }
  }

  // Iterate over all of the int attributes that have reverse relations
  // in IAccessible2, and query AXTree to see if the reverse relation exists.
  for (ax::mojom::IntAttribute int_attribute :
       int_attributes_with_reverse_relations) {
    std::wstring relation = GetIA2ReverseRelationFromIntAttr(int_attribute);
    std::vector<AXPlatformNode*> targets =
        delegate->GetSourceNodesForReverseRelations(int_attribute);
    if (targets.size()) {
      if (!relation.empty() &&
          (desired_ia2_relation.empty() || desired_ia2_relation == relation)) {
        if (desired_index == total_count) {
          *out_ia2_relation = relation;
          *out_targets = targets;
          return 1;
        }
        total_count++;
      }
    }
  }

  // Iterate over all intlist attributes on this node to check the ones
  // that correspond to IAccessible2 relations.
  for (const auto& attribute_value_pair : node->GetIntListAttributes()) {
    ax::mojom::IntListAttribute intlist_attribute = attribute_value_pair.first;
    std::wstring relation = GetIA2RelationFromIntListAttr(intlist_attribute);
    if (!relation.empty() &&
        (desired_ia2_relation.empty() || desired_ia2_relation == relation)) {
      if (desired_index == total_count) {
        *out_ia2_relation = relation;
        *out_targets = delegate->GetTargetNodesForRelation(intlist_attribute);
        if (out_targets->size() == 0)
          continue;
        return 1;
      }
      total_count++;
    }
  }

  // Iterate over all of the intlist attributes that have reverse relations
  // in IAccessible2, and query AXTree to see if the reverse relation exists.
  for (ax::mojom::IntListAttribute intlist_attribute :
       intlist_attributes_with_reverse_relations) {
    std::wstring relation =
        GetIA2ReverseRelationFromIntListAttr(intlist_attribute);
    std::vector<AXPlatformNode*> targets =
        delegate->GetSourceNodesForReverseRelations(intlist_attribute);
    if (targets.size()) {
      if (!relation.empty() &&
          (desired_ia2_relation.empty() || desired_ia2_relation == relation)) {
        if (desired_index == total_count) {
          *out_ia2_relation = relation;
          *out_targets = targets;
          return 1;
        }
        total_count++;
      }
    }
  }

  return total_count;
}

void AXPlatformRelationWin::Initialize(const std::wstring& type) {
  type_ = type;
}

void AXPlatformRelationWin::Invalidate() {
  targets_.clear();
}

void AXPlatformRelationWin::AddTarget(AXPlatformNodeWin* target) {
  targets_.push_back(target);
}

IFACEMETHODIMP AXPlatformRelationWin::get_relationType(BSTR* relation_type) {
  if (!relation_type)
    return E_INVALIDARG;

  *relation_type = SysAllocString(type_.c_str());
  DCHECK(*relation_type);
  return S_OK;
}

IFACEMETHODIMP AXPlatformRelationWin::get_nTargets(LONG* n_targets) {
  if (!n_targets)
    return E_INVALIDARG;

  *n_targets = static_cast<LONG>(targets_.size());
  return S_OK;
}

IFACEMETHODIMP AXPlatformRelationWin::get_target(LONG target_index,
                                                 IUnknown** target) {
  if (!target)
    return E_INVALIDARG;

  if (target_index < 0 || target_index >= static_cast<LONG>(targets_.size())) {
    return E_INVALIDARG;
  }

  *target = static_cast<IAccessible*>(targets_[target_index].Get());
  (*target)->AddRef();
  return S_OK;
}

IFACEMETHODIMP AXPlatformRelationWin::get_targets(LONG max_targets,
                                                  IUnknown** targets,
                                                  LONG* n_targets) {
  if (!targets || !n_targets)
    return E_INVALIDARG;

  LONG count = static_cast<LONG>(targets_.size());
  if (count > max_targets)
    count = max_targets;

  *n_targets = count;
  if (count == 0)
    return S_FALSE;

  for (LONG i = 0; i < count; ++i) {
    HRESULT result = get_target(i, &targets[i]);
    if (result != S_OK)
      return result;
  }

  return S_OK;
}

IFACEMETHODIMP
AXPlatformRelationWin::get_localizedRelationType(BSTR* relation_type) {
  return E_NOTIMPL;
}

}  // namespace ui