blob: 165789772ad9cb87062394728400990662a4bad3 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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_delegate.h"
#include "ui/accessibility/platform/ax_unique_id.h"
#include "ui/base/win/atl_module.h"
#include "ui/gfx/geometry/rect_conversions.h"
const WCHAR* const IA2_RELATION_DETAILS = L"details";
const WCHAR* const IA2_RELATION_DETAILS_FOR = L"detailsFor";
const WCHAR* const IA2_RELATION_ERROR_MESSAGE = L"errorMessage";
namespace ui {
AXPlatformRelationWin::AXPlatformRelationWin() {
win::CreateATLModuleIfNeeded();
}
AXPlatformRelationWin::~AXPlatformRelationWin() {}
base::string16 GetIA2RelationFromIntAttr(ax::mojom::IntAttribute attribute) {
switch (attribute) {
case ax::mojom::IntAttribute::kDetailsId:
return IA2_RELATION_DETAILS;
case ax::mojom::IntAttribute::kMemberOfId:
return IA2_RELATION_MEMBER_OF;
case ax::mojom::IntAttribute::kErrormessageId:
return IA2_RELATION_ERROR_MESSAGE;
default:
break;
}
return base::string16();
}
base::string16 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::kFlowtoIds:
return IA2_RELATION_FLOWS_TO;
case ax::mojom::IntListAttribute::kLabelledbyIds:
return IA2_RELATION_LABELLED_BY;
default:
break;
}
return base::string16();
}
base::string16 GetIA2ReverseRelationFromIntAttr(
ax::mojom::IntAttribute attribute) {
switch (attribute) {
case ax::mojom::IntAttribute::kDetailsId:
return IA2_RELATION_DETAILS_FOR;
default:
break;
}
return base::string16();
}
base::string16 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::kFlowtoIds:
return IA2_RELATION_FLOWS_FROM;
case ax::mojom::IntListAttribute::kLabelledbyIds:
return IA2_RELATION_LABEL_FOR;
default:
break;
}
return base::string16();
}
// static
int AXPlatformRelationWin::EnumerateRelationships(
const AXNodeData& node_data,
AXPlatformNodeDelegate* delegate,
int desired_index,
const base::string16& desired_ia2_relation,
base::string16* out_ia2_relation,
std::set<int32_t>* out_targets) {
// 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 (size_t i = 0; i < node_data.int_attributes.size(); ++i) {
ax::mojom::IntAttribute int_attribute = node_data.int_attributes[i].first;
base::string16 relation = GetIA2RelationFromIntAttr(int_attribute);
if (!relation.empty() &&
(desired_ia2_relation.empty() || desired_ia2_relation == relation)) {
// Skip reflexive relations
if (node_data.int_attributes[i].second == node_data.id)
continue;
if (desired_index == total_count) {
*out_ia2_relation = relation;
out_targets->insert(node_data.int_attributes[i].second);
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) {
base::string16 relation = GetIA2ReverseRelationFromIntAttr(int_attribute);
std::set<int32_t> targets =
delegate->GetReverseRelations(int_attribute, node_data.id);
// Erase reflexive relations.
targets.erase(node_data.id);
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 (size_t i = 0; i < node_data.intlist_attributes.size(); ++i) {
ax::mojom::IntListAttribute intlist_attribute =
node_data.intlist_attributes[i].first;
base::string16 relation = GetIA2RelationFromIntListAttr(intlist_attribute);
if (!relation.empty() &&
(desired_ia2_relation.empty() || desired_ia2_relation == relation)) {
if (desired_index == total_count) {
*out_ia2_relation = relation;
for (int32_t target_id : node_data.intlist_attributes[i].second) {
// Skip reflexive relations
if (target_id == node_data.id)
continue;
out_targets->insert(target_id);
}
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) {
base::string16 relation =
GetIA2ReverseRelationFromIntListAttr(intlist_attribute);
std::set<int32_t> targets =
delegate->GetReverseRelations(intlist_attribute, node_data.id);
// Erase reflexive relations.
targets.erase(node_data.id);
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 base::string16& 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