blob: 5a199a365d5e8a0b21b258f863e5c33812e729e4 [file] [log] [blame]
// Copyright 2015 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_node_win.h"
#include <wrl/client.h>
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/lazy_instance.h"
#include "base/metrics/histogram_functions.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/accessibility_switches.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_node_position.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_fragment_root_win.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/accessibility/platform/ax_platform_node_textprovider_win.h"
#include "ui/accessibility/platform/ax_platform_relation_win.h"
#include "ui/base/win/atl_module.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
//
// Macros to use at the top of any AXPlatformNodeWin function that implements
// a non-UIA COM interface. Because COM objects are reference counted and
// clients are completely untrusted, it's important to always first check that
// our object is still valid, and then check that all pointer arguments are not
// NULL.
//
#define COM_OBJECT_VALIDATE() \
if (!GetDelegate()) \
return E_FAIL;
#define COM_OBJECT_VALIDATE_1_ARG(arg) \
if (!GetDelegate()) \
return E_FAIL; \
if (!arg) \
return E_INVALIDARG; \
*arg = {};
#define COM_OBJECT_VALIDATE_2_ARGS(arg1, arg2) \
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {};
#define COM_OBJECT_VALIDATE_3_ARGS(arg1, arg2, arg3) \
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {};
#define COM_OBJECT_VALIDATE_4_ARGS(arg1, arg2, arg3, arg4) \
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {}; \
if (!arg4) \
return E_INVALIDARG; \
*arg4 = {};
#define COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET(var_id, target) \
if (!GetDelegate()) \
return E_FAIL; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;
#define COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, arg, target) \
if (!GetDelegate()) \
return E_FAIL; \
if (!arg) \
return E_INVALIDARG; \
*arg = {}; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;
#define COM_OBJECT_VALIDATE_VAR_ID_2_ARGS_AND_GET_TARGET(var_id, arg1, arg2, \
target) \
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;
#define COM_OBJECT_VALIDATE_VAR_ID_3_ARGS_AND_GET_TARGET(var_id, arg1, arg2, \
arg3, target) \
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {}; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;
#define COM_OBJECT_VALIDATE_VAR_ID_4_ARGS_AND_GET_TARGET(var_id, arg1, arg2, \
arg3, arg4, target) \
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {}; \
if (!arg4) \
return E_INVALIDARG; \
*arg4 = {}; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;
namespace ui {
namespace {
typedef std::unordered_set<AXPlatformNodeWin*> AXPlatformNodeWinSet;
// Set of all AXPlatformNodeWin objects that were the target of an
// alert event.
base::LazyInstance<AXPlatformNodeWinSet>::Leaky g_alert_targets =
LAZY_INSTANCE_INITIALIZER;
base::LazyInstance<base::ObserverList<IAccessible2UsageObserver>::Unchecked>::
Leaky g_iaccessible2_usage_observer_list = LAZY_INSTANCE_INITIALIZER;
// Sets the multiplier by which large changes to a RangeValueProvider are
// greater than small changes.
constexpr int kLargeChangeScaleFactor = 10;
// The amount to scroll when UI Automation asks to scroll by a small increment.
// Value is in device independent pixels and is the same used by Blink when
// cursor keys are used to scroll a webpage.
constexpr float kSmallScrollIncrement = 40.0f;
void AppendTextToString(base::string16 extra_text, base::string16* string) {
if (extra_text.empty())
return;
if (string->empty()) {
*string = extra_text;
return;
}
*string += base::string16(L". ") + extra_text;
}
} // namespace
void AXPlatformNodeWin::AddAttributeToList(const char* name,
const char* value,
PlatformAttributeList* attributes) {
std::string str_value = value;
SanitizeStringAttribute(str_value, &str_value);
attributes->push_back(base::UTF8ToUTF16(name) + L":" +
base::UTF8ToUTF16(str_value));
}
// There is no easy way to decouple |kScreenReader| and |kHTML| accessibility
// modes when Windows screen readers are used. For example, certain roles use
// the HTML tag name. Input fields require their type attribute to be exposed.
const uint32_t kScreenReaderAndHTMLAccessibilityModes =
AXMode::kScreenReader | AXMode::kHTML;
//
// IAccessible2UsageObserver
//
IAccessible2UsageObserver::IAccessible2UsageObserver() {}
IAccessible2UsageObserver::~IAccessible2UsageObserver() {}
// static
base::ObserverList<IAccessible2UsageObserver>::Unchecked&
GetIAccessible2UsageObserverList() {
return g_iaccessible2_usage_observer_list.Get();
}
//
// AXPlatformNode::Create
//
// static
AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) {
// Make sure ATL is initialized in this module.
win::CreateATLModuleIfNeeded();
CComObject<AXPlatformNodeWin>* instance = nullptr;
HRESULT hr = CComObject<AXPlatformNodeWin>::CreateInstance(&instance);
DCHECK(SUCCEEDED(hr));
instance->Init(delegate);
instance->AddRef();
return instance;
}
// static
AXPlatformNode* AXPlatformNode::FromNativeViewAccessible(
gfx::NativeViewAccessible accessible) {
if (!accessible)
return nullptr;
Microsoft::WRL::ComPtr<AXPlatformNodeWin> ax_platform_node;
accessible->QueryInterface(ax_platform_node.GetAddressOf());
return ax_platform_node.Get();
}
//
// AXPlatformNodeWin
//
AXPlatformNodeWin::AXPlatformNodeWin() {}
AXPlatformNodeWin::~AXPlatformNodeWin() {
ClearOwnRelations();
}
void AXPlatformNodeWin::Init(AXPlatformNodeDelegate* delegate) {
AXPlatformNodeBase::Init(delegate);
}
void AXPlatformNodeWin::ClearOwnRelations() {
for (size_t i = 0; i < relations_.size(); ++i)
relations_[i]->Invalidate();
relations_.clear();
}
// Static
void AXPlatformNodeWin::SanitizeStringAttributeForUIAAriaProperty(
const base::string16& input,
base::string16* output) {
DCHECK(output);
// According to the UIA Spec, these characters need to be escaped with a
// backslash in an AriaProperties string: backslash, equals and semicolon.
// Note that backslash must be replaced first.
base::ReplaceChars(input, L"\\", L"\\\\", output);
base::ReplaceChars(*output, L"=", L"\\=", output);
base::ReplaceChars(*output, L";", L"\\;", output);
}
void AXPlatformNodeWin::StringAttributeToUIAAriaProperty(
std::vector<base::string16>& properties,
ax::mojom::StringAttribute attribute,
const char* uia_aria_property) {
base::string16 value;
if (GetString16Attribute(attribute, &value)) {
SanitizeStringAttributeForUIAAriaProperty(value, &value);
properties.push_back(base::ASCIIToUTF16(uia_aria_property) + L"=" + value);
}
}
void AXPlatformNodeWin::BoolAttributeToUIAAriaProperty(
std::vector<base::string16>& properties,
ax::mojom::BoolAttribute attribute,
const char* uia_aria_property) {
bool value;
if (GetBoolAttribute(attribute, &value)) {
properties.push_back((base::ASCIIToUTF16(uia_aria_property) + L"=") +
(value ? L"true" : L"false"));
}
}
void AXPlatformNodeWin::IntAttributeToUIAAriaProperty(
std::vector<base::string16>& properties,
ax::mojom::IntAttribute attribute,
const char* uia_aria_property) {
int value;
if (GetIntAttribute(attribute, &value)) {
properties.push_back(base::ASCIIToUTF16(uia_aria_property) + L"=" +
base::IntToString16(value));
}
}
void AXPlatformNodeWin::FloatAttributeToUIAAriaProperty(
std::vector<base::string16>& properties,
ax::mojom::FloatAttribute attribute,
const char* uia_aria_property) {
float value;
if (GetFloatAttribute(attribute, &value)) {
properties.push_back(base::ASCIIToUTF16(uia_aria_property) + L"=" +
base::NumberToString16(value));
}
}
void AXPlatformNodeWin::StateToUIAAriaProperty(
std::vector<base::string16>& properties,
ax::mojom::State state,
const char* uia_aria_property) {
const AXNodeData& data = GetData();
bool value = data.HasState(state);
properties.push_back((base::ASCIIToUTF16(uia_aria_property) + L"=") +
(value ? L"true" : L"false"));
}
void AXPlatformNodeWin::HtmlAttributeToUIAAriaProperty(
std::vector<base::string16>& properties,
const char* html_attribute_name,
const char* uia_aria_property) {
base::string16 html_attribute_value;
if (GetData().GetHtmlAttribute(html_attribute_name, &html_attribute_value)) {
SanitizeStringAttributeForUIAAriaProperty(html_attribute_value,
&html_attribute_value);
properties.push_back(base::ASCIIToUTF16(uia_aria_property) + L"=" +
html_attribute_value);
}
}
SAFEARRAY* AXPlatformNodeWin::CreateUIAElementsArrayForRelation(
const ax::mojom::IntListAttribute& attribute) {
std::vector<int32_t> id_list = GetIntListAttribute(attribute);
base::EraseIf(id_list, [&](int32_t node_id) {
return !GetDelegate()->GetFromNodeID(node_id);
});
SAFEARRAY* propertyvalue = CreateUIAElementsArrayFromIdVector(id_list);
return propertyvalue;
}
SAFEARRAY* AXPlatformNodeWin::CreateUIAElementsArrayForReverseRelation(
const ax::mojom::IntListAttribute& attribute) {
std::set<AXPlatformNode*> reverse_relations =
GetDelegate()->GetReverseRelations(attribute);
std::vector<int32_t> ax_platform_node_ids;
ax_platform_node_ids.reserve(reverse_relations.size());
for (AXPlatformNode* ax_platform_node : reverse_relations) {
if (ax_platform_node) {
ax_platform_node_ids.push_back(
static_cast<AXPlatformNodeWin*>(ax_platform_node)->GetData().id);
}
}
return CreateUIAElementsArrayFromIdVector(ax_platform_node_ids);
}
SAFEARRAY* AXPlatformNodeWin::CreateUIAElementsArrayFromIdVector(
std::vector<int32_t>& ids) {
SAFEARRAY* uia_array = SafeArrayCreateVector(VT_UNKNOWN, 0, ids.size());
LONG i = 0;
for (const auto& node_id : ids) {
AXPlatformNodeWin* node_win =
static_cast<AXPlatformNodeWin*>(GetDelegate()->GetFromNodeID(node_id));
DCHECK(node_win);
SafeArrayPutElement(uia_array, &i,
static_cast<IRawElementProviderSimple*>(node_win));
++i;
}
return uia_array;
}
SAFEARRAY* AXPlatformNodeWin::CreateClickablePointArray() {
SAFEARRAY* clickable_point_array = SafeArrayCreateVector(VT_R8, 0, 2);
gfx::Point center =
GetDelegate()->GetUnclippedScreenBoundsRect().CenterPoint();
double* double_array;
SafeArrayAccessData(clickable_point_array,
reinterpret_cast<void**>(&double_array));
double_array[0] = center.x();
double_array[1] = center.y();
SafeArrayUnaccessData(clickable_point_array);
return clickable_point_array;
}
gfx::Vector2d AXPlatformNodeWin::CalculateUIAScrollPoint(
const ScrollAmount horizontal_amount,
const ScrollAmount vertical_amount) const {
if (!GetDelegate() || !IsScrollable())
return {};
const gfx::Rect bounds = GetDelegate()->GetClippedScreenBoundsRect();
const int large_horizontal_change = bounds.width();
const int large_vertical_change = bounds.height();
const HWND hwnd = GetDelegate()->GetTargetForNativeAccessibilityEvent();
DCHECK(hwnd);
const float scale_factor =
display::win::ScreenWin::GetScaleFactorForHWND(hwnd);
const int small_change =
gfx::ToRoundedInt(kSmallScrollIncrement * scale_factor);
const int x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
const int x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
const int y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
const int y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
int x = GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
int y = GetIntAttribute(ax::mojom::IntAttribute::kScrollY);
switch (horizontal_amount) {
case ScrollAmount_LargeDecrement:
x -= large_horizontal_change;
break;
case ScrollAmount_LargeIncrement:
x += large_horizontal_change;
break;
case ScrollAmount_NoAmount:
break;
case ScrollAmount_SmallDecrement:
x -= small_change;
break;
case ScrollAmount_SmallIncrement:
x += small_change;
break;
}
x = std::min(x, x_max);
x = std::max(x, x_min);
switch (vertical_amount) {
case ScrollAmount_LargeDecrement:
y -= large_vertical_change;
break;
case ScrollAmount_LargeIncrement:
y += large_vertical_change;
break;
case ScrollAmount_NoAmount:
break;
case ScrollAmount_SmallDecrement:
y -= small_change;
break;
case ScrollAmount_SmallIncrement:
y += small_change;
break;
}
y = std::min(y, y_max);
y = std::max(y, y_min);
return {x, y};
}
//
// AXPlatformNodeBase implementation.
//
void AXPlatformNodeWin::Dispose() {
Release();
}
void AXPlatformNodeWin::Destroy() {
RemoveAlertTarget();
// This will end up calling Dispose() which may result in deleting this object
// if there are no more outstanding references.
AXPlatformNodeBase::Destroy();
}
//
// AXPlatformNode implementation.
//
gfx::NativeViewAccessible AXPlatformNodeWin::GetNativeViewAccessible() {
return this;
}
void AXPlatformNodeWin::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
// Menu items fire selection events but Windows screen readers work reliably
// with focus events. Remap here.
if (event_type == ax::mojom::Event::kSelection) {
// A menu item could have something other than a role of
// |ROLE_SYSTEM_MENUITEM|. Zoom modification controls for example have a
// role of button.
auto* parent =
static_cast<AXPlatformNodeWin*>(FromNativeViewAccessible(GetParent()));
if (MSAARole() == ROLE_SYSTEM_MENUITEM ||
(parent && parent->MSAARole() == ROLE_SYSTEM_MENUPOPUP)) {
event_type = ax::mojom::Event::kFocus;
}
}
if (base::Optional<DWORD> native_event = MSAAEvent(event_type)) {
HWND hwnd = GetDelegate()->GetTargetForNativeAccessibilityEvent();
if (!hwnd)
return;
::NotifyWinEvent((*native_event), hwnd, OBJID_CLIENT, -GetUniqueId());
}
if (base::Optional<EVENTID> uia_event = UIAEvent(event_type))
::UiaRaiseAutomationEvent(this, (*uia_event));
// Keep track of objects that are a target of an alert event.
if (event_type == ax::mojom::Event::kAlert)
AddAlertTarget();
}
int AXPlatformNodeWin::GetIndexInParent() {
Microsoft::WRL::ComPtr<IDispatch> parent_dispatch;
Microsoft::WRL::ComPtr<IAccessible> parent_accessible;
if (S_OK != get_accParent(parent_dispatch.GetAddressOf()))
return -1;
if (S_OK != parent_dispatch.CopyTo(parent_accessible.GetAddressOf()))
return -1;
LONG child_count = 0;
if (S_OK != parent_accessible->get_accChildCount(&child_count))
return -1;
for (LONG index = 1; index <= child_count; ++index) {
base::win::ScopedVariant childid_index(index);
Microsoft::WRL::ComPtr<IDispatch> child_dispatch;
Microsoft::WRL::ComPtr<IAccessible> child_accessible;
if (S_OK == parent_accessible->get_accChild(
childid_index, child_dispatch.GetAddressOf()) &&
S_OK == child_dispatch.CopyTo(child_accessible.GetAddressOf())) {
if (child_accessible.Get() == this)
return index - 1;
}
}
return -1;
}
base::string16 AXPlatformNodeWin::GetText() const {
if (IsChildOfLeaf())
return AXPlatformNodeBase::GetText();
return hypertext_.hypertext;
}
//
// IAccessible implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::accHitTest(LONG x_left,
LONG y_top,
VARIANT* child) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ACC_HIT_TEST);
COM_OBJECT_VALIDATE_1_ARG(child);
gfx::Point point(x_left, y_top);
if (!GetDelegate()->GetClippedScreenBoundsRect().Contains(point)) {
// Return S_FALSE and VT_EMPTY when outside the object's boundaries.
child->vt = VT_EMPTY;
return S_FALSE;
}
gfx::NativeViewAccessible hit_child =
GetDelegate()->HitTestSync(x_left, y_top);
if (!hit_child) {
child->vt = VT_EMPTY;
return S_FALSE;
}
if (hit_child == this) {
// This object is the best match, so return CHILDID_SELF. It's tempting to
// simplify the logic and use VT_DISPATCH everywhere, but the Windows
// call AccessibleObjectFromPoint will keep calling accHitTest until some
// object returns CHILDID_SELF.
child->vt = VT_I4;
child->lVal = CHILDID_SELF;
return S_OK;
}
// Call accHitTest recursively on the result, which may be a recursive call
// to this function or it may be overridden, for example in the case of a
// WebView.
HRESULT result = hit_child->accHitTest(x_left, y_top, child);
// If the recursive call returned CHILDID_SELF, we have to convert that
// into a VT_DISPATCH for the return value to this call.
if (S_OK == result && child->vt == VT_I4 && child->lVal == CHILDID_SELF) {
child->vt = VT_DISPATCH;
child->pdispVal = hit_child;
// Always increment ref when returning a reference to a COM object.
child->pdispVal->AddRef();
}
return result;
}
IFACEMETHODIMP AXPlatformNodeWin::accDoDefaultAction(VARIANT var_id) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ACC_DO_DEFAULT_ACTION);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET(var_id, target);
AXActionData data;
data.action = ax::mojom::Action::kDoDefault;
if (target->GetDelegate()->AccessibilityPerformAction(data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::accLocation(LONG* x_left,
LONG* y_top,
LONG* width,
LONG* height,
VARIANT var_id) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ACC_LOCATION);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_4_ARGS_AND_GET_TARGET(var_id, x_left, y_top, width,
height, target);
gfx::Rect bounds = target->GetDelegate()->GetUnclippedScreenBoundsRect();
*x_left = bounds.x();
*y_top = bounds.y();
*width = bounds.width();
*height = bounds.height();
if (bounds.IsEmpty())
return S_FALSE;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::accNavigate(LONG nav_dir,
VARIANT start,
VARIANT* end) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ACC_NAVIGATE);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(start, end, target);
end->vt = VT_EMPTY;
if ((nav_dir == NAVDIR_FIRSTCHILD || nav_dir == NAVDIR_LASTCHILD) &&
V_VT(&start) == VT_I4 && V_I4(&start) != CHILDID_SELF) {
// MSAA states that navigating to first/last child can only be from self.
return E_INVALIDARG;
}
IAccessible* result = nullptr;
switch (nav_dir) {
case NAVDIR_FIRSTCHILD:
if (GetDelegate()->GetChildCount() > 0)
result = GetDelegate()->ChildAtIndex(0);
break;
case NAVDIR_LASTCHILD:
if (GetDelegate()->GetChildCount() > 0)
result =
GetDelegate()->ChildAtIndex(GetDelegate()->GetChildCount() - 1);
break;
case NAVDIR_NEXT: {
AXPlatformNodeBase* next = target->GetNextSibling();
if (next)
result = next->GetNativeViewAccessible();
break;
}
case NAVDIR_PREVIOUS: {
AXPlatformNodeBase* previous = target->GetPreviousSibling();
if (previous)
result = previous->GetNativeViewAccessible();
break;
}
case NAVDIR_DOWN: {
// This direction is not implemented except in tables.
if (!IsTableLike(GetData().role) && !IsCellOrTableHeader(GetData().role))
return E_NOTIMPL;
AXPlatformNodeBase* next = target->GetTableCell(
GetTableRow() + GetTableRowSpan(), GetTableColumn());
if (!next)
return S_OK;
result = next->GetNativeViewAccessible();
break;
}
case NAVDIR_UP: {
// This direction is not implemented except in tables.
if (!IsTableLike(GetData().role) && !IsCellOrTableHeader(GetData().role))
return E_NOTIMPL;
AXPlatformNodeBase* next =
target->GetTableCell(GetTableRow() - 1, GetTableColumn());
if (!next)
return S_OK;
result = next->GetNativeViewAccessible();
break;
}
case NAVDIR_LEFT: {
// This direction is not implemented except in tables.
if (!IsTableLike(GetData().role) && !IsCellOrTableHeader(GetData().role))
return E_NOTIMPL;
AXPlatformNodeBase* next =
target->GetTableCell(GetTableRow(), GetTableColumn() - 1);
if (!next)
return S_OK;
result = next->GetNativeViewAccessible();
break;
}
case NAVDIR_RIGHT: {
// This direction is not implemented except in tables.
if (!IsTableLike(GetData().role) && !IsCellOrTableHeader(GetData().role))
return E_NOTIMPL;
AXPlatformNodeBase* next = target->GetTableCell(
GetTableRow(), GetTableColumn() + GetTableColumnSpan());
if (!next)
return S_OK;
result = next->GetNativeViewAccessible();
break;
}
}
if (!result)
return S_FALSE;
end->vt = VT_DISPATCH;
end->pdispVal = result;
// Always increment ref when returning a reference to a COM object.
end->pdispVal->AddRef();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accChild(VARIANT var_child,
IDispatch** disp_child) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_CHILD);
*disp_child = nullptr;
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET(var_child, target);
*disp_child = target;
(*disp_child)->AddRef();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accChildCount(LONG* child_count) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_CHILD_COUNT);
COM_OBJECT_VALIDATE_1_ARG(child_count);
*child_count = GetDelegate()->GetChildCount();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accDefaultAction(VARIANT var_id,
BSTR* def_action) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_DEFAULT_ACTION);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, def_action, target);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
int action;
if (!target->GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb,
&action)) {
*def_action = nullptr;
return S_FALSE;
}
base::string16 action_verb = ActionVerbToLocalizedString(
static_cast<ax::mojom::DefaultActionVerb>(action));
if (action_verb.empty()) {
*def_action = nullptr;
return S_FALSE;
}
*def_action = SysAllocString(action_verb.c_str());
DCHECK(def_action);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accDescription(VARIANT var_id,
BSTR* desc) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_DESCRIPTION);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, desc, target);
return target->GetStringAttributeAsBstr(
ax::mojom::StringAttribute::kDescription, desc);
}
IFACEMETHODIMP AXPlatformNodeWin::get_accFocus(VARIANT* focus_child) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_FOCUS);
COM_OBJECT_VALIDATE_1_ARG(focus_child);
gfx::NativeViewAccessible focus_accessible = GetDelegate()->GetFocus();
if (focus_accessible == this) {
focus_child->vt = VT_I4;
focus_child->lVal = CHILDID_SELF;
} else if (focus_accessible) {
focus_child->vt = VT_DISPATCH;
focus_child->pdispVal = focus_accessible;
focus_child->pdispVal->AddRef();
return S_OK;
} else {
focus_child->vt = VT_EMPTY;
}
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accKeyboardShortcut(VARIANT var_id,
BSTR* acc_key) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_KEYBOARD_SHORTCUT);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, acc_key, target);
return target->GetStringAttributeAsBstr(
ax::mojom::StringAttribute::kKeyShortcuts, acc_key);
}
IFACEMETHODIMP AXPlatformNodeWin::get_accName(VARIANT var_id, BSTR* name_bstr) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_NAME);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, name_bstr, target);
for (IAccessible2UsageObserver& observer :
GetIAccessible2UsageObserverList()) {
observer.OnAccNameCalled();
}
bool has_name = target->HasStringAttribute(ax::mojom::StringAttribute::kName);
base::string16 name =
target->GetString16Attribute(ax::mojom::StringAttribute::kName);
auto status = GetData().GetImageAnnotationStatus();
switch (status) {
case ax::mojom::ImageAnnotationStatus::kNone:
case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
break;
case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
AppendTextToString(
GetDelegate()->GetLocalizedStringForImageAnnotationStatus(status),
&name);
break;
case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
AppendTextToString(
GetString16Attribute(ax::mojom::StringAttribute::kImageAnnotation),
&name);
break;
}
if (name.empty() && !has_name)
return S_FALSE;
*name_bstr = SysAllocString(name.c_str());
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accParent(IDispatch** disp_parent) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_PARENT);
COM_OBJECT_VALIDATE_1_ARG(disp_parent);
*disp_parent = GetParent();
if (*disp_parent) {
(*disp_parent)->AddRef();
return S_OK;
}
return S_FALSE;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accRole(VARIANT var_id, VARIANT* role) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_ROLE);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, role, target);
// For historical reasons, we return a string (typically
// containing the HTML tag name) as the MSAA role, rather
// than a int.
std::string role_string =
base::ToUpperASCII(target->StringOverrideForMSAARole());
if (!role_string.empty()) {
role->vt = VT_BSTR;
std::wstring wsTmp(role_string.begin(), role_string.end());
role->bstrVal = SysAllocString(wsTmp.c_str());
return S_OK;
}
role->vt = VT_I4;
role->lVal = target->MSAARole();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accState(VARIANT var_id, VARIANT* state) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_STATE);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, state, target);
state->vt = VT_I4;
state->lVal = target->MSAAState();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accHelp(VARIANT var_id, BSTR* help) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_HELP);
COM_OBJECT_VALIDATE_1_ARG(help);
return S_FALSE;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accValue(VARIANT var_id, BSTR* value) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_VALUE);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, value, target);
// get_accValue() has two sets of special cases depending on the node's role.
// The first set apply without regard for the nodes |value| attribute. That is
// the nodes value attribute isn't consider for the first set of special
// cases. For example, if the node role is ax::mojom::Role::kColorWell, we do
// not care at all about the node's ax::mojom::StringAttribute::kValue
// attribute. The second set of special cases only apply if the value
// attribute for the node is empty. That is, if
// ax::mojom::StringAttribute::kValue is empty, we do something special.
base::string16 result;
//
// Color Well special case (Use ax::mojom::IntAttribute::kColorValue)
//
if (target->GetData().role == ax::mojom::Role::kColorWell) {
unsigned int color = static_cast<unsigned int>(target->GetIntAttribute(
ax::mojom::IntAttribute::kColorValue)); // todo, why the static cast?
unsigned int red = SkColorGetR(color);
unsigned int green = SkColorGetG(color);
unsigned int blue = SkColorGetB(color);
base::string16 value_text;
value_text = base::UintToString16(red * 100 / 255) + L"% red " +
base::UintToString16(green * 100 / 255) + L"% green " +
base::UintToString16(blue * 100 / 255) + L"% blue";
*value = SysAllocString(value_text.c_str());
DCHECK(*value);
return S_OK;
}
//
// Document special case (Use the document's URL)
//
if (target->GetData().role == ax::mojom::Role::kRootWebArea ||
target->GetData().role == ax::mojom::Role::kWebArea) {
result = base::UTF8ToUTF16(target->GetDelegate()->GetTreeData().url);
*value = SysAllocString(result.c_str());
DCHECK(*value);
return S_OK;
}
//
// Links (Use ax::mojom::StringAttribute::kUrl)
//
if (target->GetData().role == ax::mojom::Role::kLink) {
result = target->GetString16Attribute(ax::mojom::StringAttribute::kUrl);
*value = SysAllocString(result.c_str());
DCHECK(*value);
return S_OK;
}
// For range controls, e.g. sliders and spin buttons, |ax_attr_value| holds
// the aria-valuetext if present but not the inner text. The actual value,
// provided either via aria-valuenow or the actual control's value is held in
// |ax::mojom::FloatAttribute::kValueForRange|.
result = target->GetString16Attribute(ax::mojom::StringAttribute::kValue);
if (result.empty() && target->IsRangeValueSupported()) {
float fval;
if (target->GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange,
&fval)) {
result = base::NumberToString16(fval);
*value = SysAllocString(result.c_str());
DCHECK(*value);
return S_OK;
}
}
if (result.empty() && target->IsRichTextField())
result = target->GetInnerText();
*value = SysAllocString(result.c_str());
DCHECK(*value);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::put_accValue(VARIANT var_id, BSTR new_value) {
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET(var_id, target);
if (!new_value)
return E_INVALIDARG;
AXActionData data;
data.action = ax::mojom::Action::kSetValue;
data.value = base::WideToUTF8(new_value);
if (target->GetDelegate()->AccessibilityPerformAction(data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accSelection(VARIANT* selected) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_SELECTION);
COM_OBJECT_VALIDATE_1_ARG(selected);
std::vector<Microsoft::WRL::ComPtr<IDispatch>> selected_nodes;
for (int i = 0; i < GetDelegate()->GetChildCount(); ++i) {
auto* node = static_cast<AXPlatformNodeWin*>(
FromNativeViewAccessible(GetDelegate()->ChildAtIndex(i)));
if (node &&
node->GetData().GetBoolAttribute(ax::mojom::BoolAttribute::kSelected))
selected_nodes.emplace_back(node);
}
if (selected_nodes.empty()) {
selected->vt = VT_EMPTY;
return S_OK;
}
if (selected_nodes.size() == 1) {
selected->vt = VT_DISPATCH;
selected->pdispVal = selected_nodes[0].Detach();
return S_OK;
}
// Multiple items are selected.
LONG selected_count = static_cast<LONG>(selected_nodes.size());
auto* enum_variant = new base::win::EnumVariant(selected_count);
enum_variant->AddRef();
for (LONG i = 0; i < selected_count; ++i) {
enum_variant->ItemAt(i)->vt = VT_DISPATCH;
enum_variant->ItemAt(i)->pdispVal = selected_nodes[i].Detach();
}
selected->vt = VT_UNKNOWN;
HRESULT hr = enum_variant->QueryInterface(IID_PPV_ARGS(&V_UNKNOWN(selected)));
enum_variant->Release();
return hr;
}
IFACEMETHODIMP AXPlatformNodeWin::accSelect(LONG flagsSelect, VARIANT var_id) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ACC_SELECT);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET(var_id, target);
if (flagsSelect & SELFLAG_TAKEFOCUS) {
AXActionData action_data;
action_data.action = ax::mojom::Action::kFocus;
target->GetDelegate()->AccessibilityPerformAction(action_data);
return S_OK;
}
return S_FALSE;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accHelpTopic(BSTR* help_file,
VARIANT var_id,
LONG* topic_id) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_HELP_TOPIC);
AXPlatformNodeWin* target;
COM_OBJECT_VALIDATE_VAR_ID_2_ARGS_AND_GET_TARGET(var_id, help_file, topic_id,
target);
if (help_file) {
*help_file = nullptr;
}
if (topic_id) {
*topic_id = static_cast<LONG>(-1);
}
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::put_accName(VARIANT var_id, BSTR put_name) {
// TODO(dougt): We may want to collect an API histogram here.
// Deprecated.
return E_NOTIMPL;
}
//
// IAccessible2 implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::role(LONG* role) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ROLE);
COM_OBJECT_VALIDATE_1_ARG(role);
*role = ComputeIA2Role();
// If we didn't explicitly set the IAccessible2 role, make it the same
// as the MSAA role.
if (!*role)
*role = MSAARole();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_states(AccessibleStates* states) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_STATES);
COM_OBJECT_VALIDATE_1_ARG(states);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
*states = ComputeIA2State();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_uniqueID(LONG* id) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_UNIQUE_ID);
COM_OBJECT_VALIDATE_1_ARG(id);
*id = -GetUniqueId();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_windowHandle(HWND* window_handle) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_WINDOW_HANDLE);
COM_OBJECT_VALIDATE_1_ARG(window_handle);
*window_handle = GetDelegate()->GetTargetForNativeAccessibilityEvent();
return *window_handle ? S_OK : S_FALSE;
}
IFACEMETHODIMP AXPlatformNodeWin::get_relationTargetsOfType(BSTR type_bstr,
LONG max_targets,
IUnknown*** targets,
LONG* n_targets) {
COM_OBJECT_VALIDATE_2_ARGS(targets, n_targets);
if (!type_bstr)
return E_INVALIDARG;
*n_targets = 0;
*targets = nullptr;
// Special case for relations of type "alerts".
base::string16 type(type_bstr);
if (type == L"alerts") {
// Collect all of the objects that have had an alert fired on them that
// are a descendant of this object.
std::vector<AXPlatformNodeWin*> alert_targets;
for (auto iter = g_alert_targets.Get().begin();
iter != g_alert_targets.Get().end(); ++iter) {
AXPlatformNodeWin* target = *iter;
if (IsDescendant(target))
alert_targets.push_back(target);
}
LONG count = static_cast<LONG>(alert_targets.size());
if (count == 0)
return S_FALSE;
// Don't return more targets than max_targets - but note that the caller
// is allowed to specify max_targets=0 to mean no limit.
if (max_targets > 0 && count > max_targets)
count = max_targets;
// Return the number of targets.
*n_targets = count;
// Allocate COM memory for the result array and populate it.
*targets =
static_cast<IUnknown**>(CoTaskMemAlloc(count * sizeof(IUnknown*)));
for (LONG i = 0; i < count; ++i) {
(*targets)[i] = static_cast<IAccessible*>(alert_targets[i]);
(*targets)[i]->AddRef();
}
return S_OK;
}
base::string16 relation_type;
std::set<AXPlatformNode*> enumerated_targets;
int found = AXPlatformRelationWin::EnumerateRelationships(
this, 0, type, &relation_type, &enumerated_targets);
if (found == 0)
return S_FALSE;
// Don't return more targets than max_targets - but note that the caller
// is allowed to specify max_targets=0 to mean no limit.
int count = static_cast<int>(enumerated_targets.size());
if (max_targets > 0 && count > max_targets)
count = max_targets;
// Allocate COM memory for the result array and populate it.
*targets = static_cast<IUnknown**>(CoTaskMemAlloc(count * sizeof(IUnknown*)));
int index = 0;
for (AXPlatformNode* target : enumerated_targets) {
if (target) {
AXPlatformNodeWin* win_target = static_cast<AXPlatformNodeWin*>(target);
(*targets)[index] = static_cast<IAccessible*>(win_target);
(*targets)[index]->AddRef();
if (++index > count)
break;
}
}
*n_targets = index;
return index > 0 ? S_OK : S_FALSE;
}
IFACEMETHODIMP AXPlatformNodeWin::get_attributes(BSTR* attributes) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_IA2_GET_ATTRIBUTES);
COM_OBJECT_VALIDATE_1_ARG(attributes);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
*attributes = nullptr;
base::string16 attributes_str;
std::vector<base::string16> computed_attributes = ComputeIA2Attributes();
for (const base::string16& attribute : computed_attributes)
attributes_str += attribute + L';';
if (attributes_str.empty())
return S_FALSE;
*attributes = SysAllocString(attributes_str.c_str());
DCHECK(*attributes);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_indexInParent(LONG* index_in_parent) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_INDEX_IN_PARENT);
COM_OBJECT_VALIDATE_1_ARG(index_in_parent);
*index_in_parent = GetIndexInParent();
if (*index_in_parent < 0)
return E_FAIL;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_nRelations(LONG* n_relations) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_RELATIONS);
COM_OBJECT_VALIDATE_1_ARG(n_relations);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
int count = AXPlatformRelationWin::EnumerateRelationships(
this, -1, base::string16(), nullptr, nullptr);
*n_relations = count;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_relation(LONG relation_index,
IAccessibleRelation** relation) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_RELATION);
COM_OBJECT_VALIDATE_1_ARG(relation);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
base::string16 relation_type;
std::set<AXPlatformNode*> targets;
int found = AXPlatformRelationWin::EnumerateRelationships(
this, relation_index, base::string16(), &relation_type, &targets);
if (found == 0)
return E_INVALIDARG;
CComObject<AXPlatformRelationWin>* relation_obj;
HRESULT hr = CComObject<AXPlatformRelationWin>::CreateInstance(&relation_obj);
DCHECK(SUCCEEDED(hr));
relation_obj->AddRef();
relation_obj->Initialize(relation_type);
for (AXPlatformNode* target : targets) {
if (target)
relation_obj->AddTarget(static_cast<AXPlatformNodeWin*>(target));
}
// Maintain references to all relations returned by this object.
// Every time this object changes state, invalidate them.
relations_.push_back(relation_obj);
*relation = relation_obj;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_relations(LONG max_relations,
IAccessibleRelation** relations,
LONG* n_relations) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_RELATIONS);
COM_OBJECT_VALIDATE_2_ARGS(relations, n_relations);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
LONG count;
HRESULT hr = get_nRelations(&count);
if (!SUCCEEDED(hr))
return hr;
count = std::min(count, max_relations);
*n_relations = count;
for (LONG i = 0; i < count; i++) {
hr = get_relation(i, &relations[i]);
if (!SUCCEEDED(hr))
return hr;
}
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_groupPosition(
LONG* group_level,
LONG* similar_items_in_group,
LONG* position_in_group) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_GROUP_POSITION);
COM_OBJECT_VALIDATE_3_ARGS(group_level, similar_items_in_group,
position_in_group);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
*group_level = GetIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel);
*similar_items_in_group = GetSetSize();
*position_in_group = GetPosInSet();
if (!*group_level && !*similar_items_in_group && !*position_in_group)
return S_FALSE;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_localizedExtendedRole(
BSTR* localized_extended_role) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_LOCALIZED_EXTENDED_ROLE);
COM_OBJECT_VALIDATE_1_ARG(localized_extended_role);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
base::string16 role_description = GetRoleDescription();
*localized_extended_role = SysAllocString(role_description.c_str());
return S_OK;
}
//
// IAccessible2 methods not implemented.
//
IFACEMETHODIMP AXPlatformNodeWin::get_attribute(BSTR name, VARIANT* attribute) {
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_extendedRole(BSTR* extended_role) {
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::scrollTo(enum IA2ScrollType scroll_type) {
COM_OBJECT_VALIDATE();
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_IA2_SCROLL_TO);
// ax::mojom::Action::kScrollToMakeVisible wants a target rect in *local*
// coords.
gfx::Rect r = gfx::ToEnclosingRect(GetData().relative_bounds.bounds);
r -= r.OffsetFromOrigin();
switch (scroll_type) {
case IA2_SCROLL_TYPE_TOP_LEFT:
r = gfx::Rect(r.x(), r.y(), 0, 0);
break;
case IA2_SCROLL_TYPE_BOTTOM_RIGHT:
r = gfx::Rect(r.right(), r.bottom(), 0, 0);
break;
case IA2_SCROLL_TYPE_TOP_EDGE:
r = gfx::Rect(r.x(), r.y(), r.width(), 0);
break;
case IA2_SCROLL_TYPE_BOTTOM_EDGE:
r = gfx::Rect(r.x(), r.bottom(), r.width(), 0);
break;
case IA2_SCROLL_TYPE_LEFT_EDGE:
r = gfx::Rect(r.x(), r.y(), 0, r.height());
break;
case IA2_SCROLL_TYPE_RIGHT_EDGE:
r = gfx::Rect(r.right(), r.y(), 0, r.height());
break;
case IA2_SCROLL_TYPE_ANYWHERE:
break;
}
ui::AXActionData action_data;
action_data.target_node_id = GetData().id;
action_data.action = ax::mojom::Action::kScrollToMakeVisible;
action_data.target_rect = r;
GetDelegate()->AccessibilityPerformAction(action_data);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::scrollToPoint(
enum IA2CoordinateType coordinate_type,
LONG x,
LONG y) {
COM_OBJECT_VALIDATE();
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_TO_POINT);
// Convert to screen-relative coordinates if necessary.
gfx::Point scroll_to(x, y);
if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) {
if (GetParent()) {
AXPlatformNodeBase* base = FromNativeViewAccessible(GetParent());
scroll_to += base->GetDelegate()
->GetUnclippedScreenBoundsRect()
.OffsetFromOrigin();
}
} else if (coordinate_type != IA2_COORDTYPE_SCREEN_RELATIVE) {
return E_INVALIDARG;
}
ui::AXActionData action_data;
action_data.target_node_id = GetData().id;
action_data.action = ax::mojom::Action::kScrollToPoint;
action_data.target_point = scroll_to;
GetDelegate()->AccessibilityPerformAction(action_data);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_nExtendedStates(LONG* n_extended_states) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_EXTENDED_STATES);
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_extendedStates(LONG max_extended_states,
BSTR** extended_states,
LONG* n_extended_states) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_EXTENDED_STATES);
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_localizedExtendedStates(
LONG max_localized_extended_states,
BSTR** localized_extended_states,
LONG* n_localized_extended_states) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_LOCALIZED_EXTENDED_STATES);
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_locale(IA2Locale* locale) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_LOCALE);
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_accessibleWithCaret(IUnknown** accessible,
LONG* caret_offset) {
return E_NOTIMPL;
}
//
// IAccessible2_3 implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::get_selectionRanges(IA2Range** ranges,
LONG* nRanges) {
COM_OBJECT_VALIDATE_2_ARGS(ranges, nRanges);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
int32_t anchor_id = GetDelegate()->GetTreeData().sel_anchor_object_id;
auto* anchor_node =
static_cast<AXPlatformNodeWin*>(GetDelegate()->GetFromNodeID(anchor_id));
if (!anchor_node)
return E_FAIL;
int anchor_offset = int{GetDelegate()->GetTreeData().sel_anchor_offset};
int32_t focus_id = GetDelegate()->GetTreeData().sel_focus_object_id;
auto* focus_node =
static_cast<AXPlatformNodeWin*>(GetDelegate()->GetFromNodeID(focus_id));
if (!focus_node)
return E_FAIL;
int focus_offset = int{GetDelegate()->GetTreeData().sel_focus_offset};
if (!IsDescendant(anchor_node) || !IsDescendant(focus_node))
return S_FALSE; // No selection within this subtree.
*ranges = reinterpret_cast<IA2Range*>(CoTaskMemAlloc(sizeof(IA2Range)));
anchor_node->AddRef();
ranges[0]->anchor = static_cast<IAccessible*>(anchor_node);
ranges[0]->anchorOffset = anchor_offset;
focus_node->AddRef();
ranges[0]->active = static_cast<IAccessible*>(focus_node);
ranges[0]->activeOffset = focus_offset;
*nRanges = 1;
return S_OK;
}
//
// IAccessible2_4 implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::setSelectionRanges(LONG nRanges,
IA2Range* ranges) {
COM_OBJECT_VALIDATE();
// Blink supports only one selection range for now.
if (nRanges != 1)
return E_INVALIDARG;
if (!ranges)
return E_INVALIDARG;
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!ranges->anchor)
return E_INVALIDARG;
if (!ranges->active)
return E_INVALIDARG;
Microsoft::WRL::ComPtr<IAccessible> anchor;
if (FAILED(ranges->anchor->QueryInterface(IID_PPV_ARGS(&anchor))))
return E_INVALIDARG;
Microsoft::WRL::ComPtr<IAccessible> focus;
if (FAILED(ranges->active->QueryInterface(IID_PPV_ARGS(&focus))))
return E_INVALIDARG;
const auto* anchor_node =
static_cast<AXPlatformNodeWin*>(FromNativeViewAccessible(anchor.Get()));
const auto* focus_node =
static_cast<AXPlatformNodeWin*>(FromNativeViewAccessible(focus.Get()));
if (!anchor_node || !focus_node)
return E_INVALIDARG;
if (ranges->anchorOffset < 0 || ranges->activeOffset < 0)
return E_INVALIDARG;
if (anchor_node->IsTextOnlyObject() || anchor_node->IsPlainTextField()) {
if (size_t{ranges->anchorOffset} > anchor_node->GetText().length()) {
return E_INVALIDARG;
}
} else {
if (ranges->anchorOffset > anchor_node->GetChildCount())
return E_INVALIDARG;
}
if (focus_node->IsTextOnlyObject() || focus_node->IsPlainTextField()) {
if (size_t{ranges->activeOffset} > focus_node->GetText().length())
return E_INVALIDARG;
} else {
if (ranges->activeOffset > focus_node->GetChildCount())
return E_INVALIDARG;
}
AXActionData action_data;
action_data.action = ax::mojom::Action::kSetSelection;
action_data.anchor_node_id = anchor_node->GetData().id;
action_data.anchor_offset = int32_t{ranges->anchorOffset};
action_data.focus_node_id = focus_node->GetData().id;
action_data.focus_offset = int32_t{ranges->activeOffset};
if (GetDelegate()->AccessibilityPerformAction(action_data))
return S_OK;
return S_FALSE;
}
//
// IAccessibleEx implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::GetObjectForChild(LONG child_id,
IAccessibleEx** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_OBJECT_FOR_CHILD);
// No support for child IDs in this implementation.
COM_OBJECT_VALIDATE_1_ARG(result);
*result = nullptr;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::GetIAccessiblePair(IAccessible** accessible,
LONG* child_id) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_IACCESSIBLE_PAIR);
COM_OBJECT_VALIDATE_2_ARGS(accessible, child_id);
*accessible = static_cast<IAccessible*>(this);
(*accessible)->AddRef();
*child_id = CHILDID_SELF;
return S_OK;
}
//
// IExpandCollapseProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::Collapse() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_EXPANDCOLLAPSE_COLLAPSE);
UIA_VALIDATE_CALL();
if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
return UIA_E_ELEMENTNOTAVAILABLE;
if (GetData().HasState(ax::mojom::State::kCollapsed))
return UIA_E_INVALIDOPERATION;
AXActionData action_data;
action_data.action = ax::mojom::Action::kDoDefault;
if (GetDelegate()->AccessibilityPerformAction(action_data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::Expand() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_EXPANDCOLLAPSE_EXPAND);
UIA_VALIDATE_CALL();
if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
return UIA_E_ELEMENTNOTAVAILABLE;
if (GetData().HasState(ax::mojom::State::kExpanded))
return UIA_E_INVALIDOPERATION;
AXActionData action_data;
action_data.action = ax::mojom::Action::kDoDefault;
if (GetDelegate()->AccessibilityPerformAction(action_data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_ExpandCollapseState(
ExpandCollapseState* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(
UMA_API_EXPANDCOLLAPSE_GET_EXPANDCOLLAPSESTATE);
UIA_VALIDATE_CALL_1_ARG(result);
const AXNodeData& data = GetData();
if (data.HasState(ax::mojom::State::kExpanded)) {
*result = ExpandCollapseState_Expanded;
} else if (data.HasState(ax::mojom::State::kCollapsed)) {
*result = ExpandCollapseState_Collapsed;
} else {
*result = ExpandCollapseState_LeafNode;
}
return S_OK;
}
//
// IGridItemProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::get_Column(int* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GRIDITEM_GET_COLUMN);
UIA_VALIDATE_CALL_1_ARG(result);
*result = GetTableColumn();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_ColumnSpan(int* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GRIDITEM_GET_COLUMNSPAN);
UIA_VALIDATE_CALL_1_ARG(result);
*result = GetTableColumnSpan();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_ContainingGrid(
IRawElementProviderSimple** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GRIDITEM_GET_CONTAININGGRID);
UIA_VALIDATE_CALL_1_ARG(result);
AXPlatformNodeBase* table = GetTable();
if (!table)
return E_FAIL;
auto* node_win = static_cast<AXPlatformNodeWin*>(table);
node_win->AddRef();
*result = static_cast<IRawElementProviderSimple*>(node_win);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_Row(int* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GRIDITEM_GET_ROW);
UIA_VALIDATE_CALL_1_ARG(result);
*result = GetTableRow();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_RowSpan(int* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GRIDITEM_GET_ROWSPAN);
UIA_VALIDATE_CALL_1_ARG(result);
*result = GetTableRowSpan();
return S_OK;
}
//
// IGridProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::GetItem(int row,
int column,
IRawElementProviderSimple** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GRID_GETITEM);
UIA_VALIDATE_CALL_1_ARG(result);
AXPlatformNodeBase* cell = GetTableCell(row, column);
if (!cell)
return E_INVALIDARG;
auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
node_win->AddRef();
*result = static_cast<IRawElementProviderSimple*>(node_win);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_RowCount(int* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GRID_GET_ROWCOUNT);
UIA_VALIDATE_CALL_1_ARG(result);
base::Optional<int32_t> row_count = GetTableAriaRowCount();
if (!row_count)
return E_UNEXPECTED;
*result = *row_count;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_ColumnCount(int* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GRID_GET_COLUMNCOUNT);
UIA_VALIDATE_CALL_1_ARG(result);
base::Optional<int32_t> column_count = GetTableAriaColumnCount();
if (!column_count)
return E_UNEXPECTED;
*result = *column_count;
return S_OK;
}
//
// IInvokeProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::Invoke() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_INVOKE_INVOKE);
UIA_VALIDATE_CALL();
if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
return UIA_E_ELEMENTNOTENABLED;
AXActionData action_data;
action_data.action = ax::mojom::Action::kDoDefault;
GetDelegate()->AccessibilityPerformAction(action_data);
return S_OK;
}
//
// IScrollItemProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::ScrollIntoView() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLLITEM_SCROLLINTOVIEW);
UIA_VALIDATE_CALL();
gfx::Rect r = gfx::ToEnclosingRect(GetData().relative_bounds.bounds);
r -= r.OffsetFromOrigin();
AXActionData action_data;
action_data.target_node_id = GetData().id;
action_data.target_rect = r;
action_data.action = ax::mojom::Action::kScrollToMakeVisible;
if (GetDelegate()->AccessibilityPerformAction(action_data))
return S_OK;
return E_FAIL;
}
//
// IScrollProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::Scroll(ScrollAmount horizontal_amount,
ScrollAmount vertical_amount) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_SCROLL);
UIA_VALIDATE_CALL();
if (!IsScrollable())
return E_FAIL;
AXActionData action_data;
action_data.target_node_id = GetData().id;
action_data.action = ax::mojom::Action::kSetScrollOffset;
action_data.target_point = gfx::PointAtOffsetFromOrigin(
CalculateUIAScrollPoint(horizontal_amount, vertical_amount));
if (GetDelegate()->AccessibilityPerformAction(action_data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::SetScrollPercent(double horizontal_percent,
double vertical_percent) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_SETSCROLLPERCENT);
UIA_VALIDATE_CALL();
if (!IsScrollable())
return E_FAIL;
const double x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
const double x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
const double y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
const double y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
const int x = gfx::ToRoundedInt(horizontal_percent * (x_max - x_min) + x_min);
const int y = gfx::ToRoundedInt(vertical_percent * (y_max - y_min) + y_min);
const gfx::Point scroll_to(x, y);
AXActionData action_data;
action_data.target_node_id = GetData().id;
action_data.action = ax::mojom::Action::kSetScrollOffset;
action_data.target_point = scroll_to;
if (GetDelegate()->AccessibilityPerformAction(action_data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_HorizontallyScrollable(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_GET_HORIZONTALLYSCROLLABLE);
UIA_VALIDATE_CALL_1_ARG(result);
*result = IsHorizontallyScrollable();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_HorizontalScrollPercent(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_GET_HORIZONTALSCROLLPERCENT);
UIA_VALIDATE_CALL_1_ARG(result);
if (!IsHorizontallyScrollable()) {
*result = UIA_ScrollPatternNoScroll;
return S_OK;
}
float x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
float x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
float x = GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
*result = 100.0 * (x - x_min) / (x_max - x_min);
return S_OK;
}
// Horizontal size of the viewable region as a percentage of the total content
// area.
IFACEMETHODIMP AXPlatformNodeWin::get_HorizontalViewSize(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_GET_HORIZONTALVIEWSIZE);
UIA_VALIDATE_CALL_1_ARG(result);
if (!IsHorizontallyScrollable()) {
*result = 100.;
return S_OK;
}
gfx::RectF clipped_bounds(GetDelegate()->GetClippedScreenBoundsRect());
float x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
float x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
float total_width = clipped_bounds.width() + x_max - x_min;
DCHECK_LE(clipped_bounds.width(), total_width);
*result = 100.0 * clipped_bounds.width() / total_width;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_VerticallyScrollable(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_GET_VERTICALLYSCROLLABLE);
UIA_VALIDATE_CALL_1_ARG(result);
*result = IsVerticallyScrollable();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_VerticalScrollPercent(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_GET_VERTICALSCROLLPERCENT);
UIA_VALIDATE_CALL_1_ARG(result);
if (!IsVerticallyScrollable()) {
*result = UIA_ScrollPatternNoScroll;
return S_OK;
}
float y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
float y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
float y = GetIntAttribute(ax::mojom::IntAttribute::kScrollY);
*result = 100.0 * (y - y_min) / (y_max - y_min);
return S_OK;
}
// Vertical size of the viewable region as a percentage of the total content
// area.
IFACEMETHODIMP AXPlatformNodeWin::get_VerticalViewSize(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SCROLL_GET_VERTICALVIEWSIZE);
UIA_VALIDATE_CALL_1_ARG(result);
if (!IsVerticallyScrollable()) {
*result = 100.0;
return S_OK;
}
gfx::RectF clipped_bounds(GetDelegate()->GetClippedScreenBoundsRect());
float y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
float y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
float total_height = clipped_bounds.height() + y_max - y_min;
DCHECK_LE(clipped_bounds.height(), total_height);
*result = 100.0 * clipped_bounds.height() / total_height;
return S_OK;
}
//
// ISelectionItemProvider implementation.
//
HRESULT AXPlatformNodeWin::ISelectionItemProviderSetSelected(bool selected) {
UIA_VALIDATE_CALL();
if (!IsSelectionItemSupported())
return UIA_E_INVALIDOPERATION;
int restriction;
if (GetIntAttribute(ax::mojom::IntAttribute::kRestriction, &restriction)) {
if (restriction == static_cast<int>(ax::mojom::Restriction::kDisabled))
return UIA_E_ELEMENTNOTENABLED;
}
bool is_selected;
if (!GetBoolAttribute(ax::mojom::BoolAttribute::kSelected, &is_selected))
return UIA_E_INVALIDOPERATION;
if (is_selected == selected)
return S_OK;
AXActionData data;
data.action = ax::mojom::Action::kDoDefault;
if (GetDelegate()->AccessibilityPerformAction(data))
return S_OK;
return UIA_E_INVALIDOPERATION;
}
IFACEMETHODIMP AXPlatformNodeWin::AddToSelection() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTIONITEM_ADDTOSELECTION);
return ISelectionItemProviderSetSelected(true);
}
IFACEMETHODIMP AXPlatformNodeWin::RemoveFromSelection() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTIONITEM_REMOVEFROMSELECTION);
return ISelectionItemProviderSetSelected(false);
}
IFACEMETHODIMP AXPlatformNodeWin::Select() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTIONITEM_SELECT);
return ISelectionItemProviderSetSelected(true);
}
IFACEMETHODIMP AXPlatformNodeWin::get_IsSelected(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTIONITEM_GET_ISSELECTED);
UIA_VALIDATE_CALL_1_ARG(result);
if (!IsSelectionItemSupported())
return UIA_E_INVALIDOPERATION;
// https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
// SelectionItem.IsSelected is exposed when aria-checked is True or False,
// for 'radio' and 'menuitemradio' roles
if (GetData().role == ax::mojom::Role::kRadioButton ||
GetData().role == ax::mojom::Role::kMenuItemRadio) {
const auto checked_state = GetData().GetCheckedState();
switch (checked_state) {
case ax::mojom::CheckedState::kTrue:
case ax::mojom::CheckedState::kFalse: {
*result = (checked_state == ax::mojom::CheckedState::kTrue);
return S_OK;
}
case ax::mojom::CheckedState::kMixed:
case ax::mojom::CheckedState::kNone: {
return UIA_E_INVALIDOPERATION;
}
}
}
// https://www.w3.org/TR/wai-aria-1.1/#aria-selected
// Elements are not selectable when aria-selected is undefined
bool is_selected;
if (GetBoolAttribute(ax::mojom::BoolAttribute::kSelected, &is_selected)) {
*result = is_selected;
return S_OK;
}
return UIA_E_INVALIDOPERATION;
}
IFACEMETHODIMP AXPlatformNodeWin::get_SelectionContainer(
IRawElementProviderSimple** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTIONITEM_GET_SELECTIONCONTAINER);
UIA_VALIDATE_CALL_1_ARG(result);
auto* node_win = static_cast<AXPlatformNodeWin*>(GetSelectionContainer());
if (!node_win)
return E_FAIL;
node_win->AddRef();
*result = static_cast<IRawElementProviderSimple*>(node_win);
return S_OK;
}
//
// ISelectionProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::GetSelection(SAFEARRAY** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTION_GETSELECTION);
UIA_VALIDATE_CALL_1_ARG(result);
std::vector<AXPlatformNodeWin*> selected_children;
LONG child_count = GetDelegate()->GetChildCount();
for (LONG i = 0; i < child_count; ++i) {
auto* child = static_cast<AXPlatformNodeWin*>(
FromNativeViewAccessible(GetDelegate()->ChildAtIndex(i)));
DCHECK(child);
if (child->GetData().GetBoolAttribute(ax::mojom::BoolAttribute::kSelected))
selected_children.push_back(child);
}
LONG selected_children_count = selected_children.size();
*result = SafeArrayCreateVector(VT_UNKNOWN, 0, selected_children_count);
if (!*result)
return E_OUTOFMEMORY;
for (LONG i = 0; i < selected_children_count; ++i) {
HRESULT hr = SafeArrayPutElement(
*result, &i,
static_cast<IRawElementProviderSimple*>(selected_children[i]));
if (FAILED(hr)) {
SafeArrayDestroy(*result);
*result = nullptr;
return hr;
}
}
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_CanSelectMultiple(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTION_GET_CANSELECTMULTIPLE);
UIA_VALIDATE_CALL_1_ARG(result);
*result = GetData().HasState(ax::mojom::State::kMultiselectable);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_IsSelectionRequired(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTION_GET_ISSELECTIONREQUIRED);
UIA_VALIDATE_CALL_1_ARG(result);
*result = GetData().HasState(ax::mojom::State::kRequired);
return S_OK;
}
//
// ITableItemProvider methods.
//
IFACEMETHODIMP AXPlatformNodeWin::GetColumnHeaderItems(SAFEARRAY** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TABLEITEM_GETCOLUMNHEADERITEMS);
UIA_VALIDATE_CALL_1_ARG(result);
if (!IsCellOrTableHeader(GetData().role) || !GetTable())
return E_FAIL;
std::vector<int32_t> column_header_ids =
GetDelegate()->GetColHeaderNodeIds(GetTableColumn());
if (column_header_ids.empty())
return S_FALSE;
*result = CreateUIAElementsArrayFromIdVector(column_header_ids);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::GetRowHeaderItems(SAFEARRAY** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TABLEITEM_GETROWHEADERITEMS);
UIA_VALIDATE_CALL_1_ARG(result);
if (!IsCellOrTableHeader(GetData().role) || !GetTable())
return E_FAIL;
std::vector<int32_t> row_header_ids =
GetDelegate()->GetRowHeaderNodeIds(GetTableRow());
if (row_header_ids.empty())
return S_FALSE;
*result = CreateUIAElementsArrayFromIdVector(row_header_ids);
return S_OK;
}
//
// ITableProvider methods.
//
IFACEMETHODIMP AXPlatformNodeWin::GetColumnHeaders(SAFEARRAY** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TABLE_GETCOLUMNHEADERS);
UIA_VALIDATE_CALL_1_ARG(result);
if (!GetTable())
return E_FAIL;
std::vector<int32_t> column_header_ids = GetDelegate()->GetColHeaderNodeIds();
*result = CreateUIAElementsArrayFromIdVector(column_header_ids);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::GetRowHeaders(SAFEARRAY** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TABLE_GETROWHEADERS);
UIA_VALIDATE_CALL_1_ARG(result);
if (!GetTable())
return E_FAIL;
std::vector<int32_t> row_header_ids = GetDelegate()->GetRowHeaderNodeIds();
*result = CreateUIAElementsArrayFromIdVector(row_header_ids);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_RowOrColumnMajor(
RowOrColumnMajor* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TABLE_GET_ROWORCOLUMNMAJOR);
UIA_VALIDATE_CALL_1_ARG(result);
// Tables and ARIA grids are always in row major order
// see AXPlatformNodeBase::GetTableCell
*result = RowOrColumnMajor_RowMajor;
return S_OK;
}
//
// IToggleProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::Toggle() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TOGGLE_TOGGLE);
UIA_VALIDATE_CALL();
AXActionData action_data;
action_data.action = ax::mojom::Action::kDoDefault;
if (GetDelegate()->AccessibilityPerformAction(action_data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_ToggleState(ToggleState* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TOGGLE_GET_TOGGLESTATE);
UIA_VALIDATE_CALL_1_ARG(result);
const auto checked_state = GetData().GetCheckedState();
if (checked_state == ax::mojom::CheckedState::kTrue) {
*result = ToggleState_On;
} else if (checked_state == ax::mojom::CheckedState::kMixed) {
*result = ToggleState_Indeterminate;
} else {
*result = ToggleState_Off;
}
return S_OK;
}
//
// IValueProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::SetValue(LPCWSTR value) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_VALUE_SETVALUE);
UIA_VALIDATE_CALL();
if (!value)
return E_INVALIDARG;
AXActionData data;
data.action = ax::mojom::Action::kSetValue;
data.value = base::WideToUTF8(value);
if (GetDelegate()->AccessibilityPerformAction(data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_IsReadOnly(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_VALUE_GET_ISREADONLY);
UIA_VALIDATE_CALL_1_ARG(result);
int restriction;
if (GetIntAttribute(ax::mojom::IntAttribute::kRestriction, &restriction)) {
*result = static_cast<ax::mojom::Restriction>(restriction) ==
ax::mojom::Restriction::kReadOnly;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_Value(BSTR* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_VALUE_GET_VALUE);
UIA_VALIDATE_CALL_1_ARG(result);
if (GetStringAttributeAsBstr(ax::mojom::StringAttribute::kValue, result))
return S_OK;
return E_FAIL;
}
//
// IWindowProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::SetVisualState(
WindowVisualState window_visual_state) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_SETVISUALSTATE);
UIA_VALIDATE_CALL();
return UIA_E_NOTSUPPORTED;
}
IFACEMETHODIMP AXPlatformNodeWin::Close() {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_CLOSE);
UIA_VALIDATE_CALL();
return UIA_E_NOTSUPPORTED;
}
IFACEMETHODIMP AXPlatformNodeWin::WaitForInputIdle(int milliseconds,
BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_WAITFORINPUTIDLE);
UIA_VALIDATE_CALL_1_ARG(result);
return UIA_E_NOTSUPPORTED;
}
IFACEMETHODIMP AXPlatformNodeWin::get_CanMaximize(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_GET_CANMAXIMIZE);
UIA_VALIDATE_CALL_1_ARG(result);
return UIA_E_NOTSUPPORTED;
}
IFACEMETHODIMP AXPlatformNodeWin::get_CanMinimize(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_GET_CANMINIMIZE);
UIA_VALIDATE_CALL_1_ARG(result);
return UIA_E_NOTSUPPORTED;
}
IFACEMETHODIMP AXPlatformNodeWin::get_IsModal(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_GET_ISMODAL);
UIA_VALIDATE_CALL_1_ARG(result);
*result = GetBoolAttribute(ax::mojom::BoolAttribute::kModal);
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_WindowVisualState(
WindowVisualState* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_GET_WINDOWVISUALSTATE);
UIA_VALIDATE_CALL_1_ARG(result);
return UIA_E_NOTSUPPORTED;
}
IFACEMETHODIMP AXPlatformNodeWin::get_WindowInteractionState(
WindowInteractionState* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_GET_WINDOWINTERACTIONSTATE);
UIA_VALIDATE_CALL_1_ARG(result);
return UIA_E_NOTSUPPORTED;
}
IFACEMETHODIMP AXPlatformNodeWin::get_IsTopmost(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_WINDOW_GET_ISTOPMOST);
UIA_VALIDATE_CALL_1_ARG(result);
return UIA_E_NOTSUPPORTED;
}
//
// IRangeValueProvider implementation.
//
IFACEMETHODIMP AXPlatformNodeWin::SetValue(double value) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_RANGEVALUE_SETVALUE);
UIA_VALIDATE_CALL();
AXActionData data;
data.action = ax::mojom::Action::kSetValue;
data.value = base::NumberToString(value);
if (GetDelegate()->AccessibilityPerformAction(data))
return S_OK;
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_LargeChange(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_RANGEVALUE_GET_LARGECHANGE);
UIA_VALIDATE_CALL_1_ARG(result);
float attribute;
if (GetFloatAttribute(ax::mojom::FloatAttribute::kStepValueForRange,
&attribute)) {
*result = attribute * kLargeChangeScaleFactor;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_Maximum(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_RANGEVALUE_GET_MAXIMUM);
UIA_VALIDATE_CALL_1_ARG(result);
float attribute;
if (GetFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange,
&attribute)) {
*result = attribute;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_Minimum(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_RANGEVALUE_GET_MINIMUM);
UIA_VALIDATE_CALL_1_ARG(result);
float attribute;
if (GetFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange,
&attribute)) {
*result = attribute;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_SmallChange(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_RANGEVALUE_GET_SMALLCHANGE);
UIA_VALIDATE_CALL_1_ARG(result);
float attribute;
if (GetFloatAttribute(ax::mojom::FloatAttribute::kStepValueForRange,
&attribute)) {
*result = attribute;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_Value(double* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_RANGEVALUE_GET_VALUE);
UIA_VALIDATE_CALL_1_ARG(result);
float attribute;
if (GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange,
&attribute)) {
*result = attribute;
return S_OK;
}
return E_FAIL;
}
// IAccessibleEx methods not implemented.
IFACEMETHODIMP
AXPlatformNodeWin::ConvertReturnedElement(IRawElementProviderSimple* element,
IAccessibleEx** acc) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_CONVERT_RETURNED_ELEMENT);
return E_NOTIMPL;
}
//
// IAccessibleTable methods.
//
IFACEMETHODIMP AXPlatformNodeWin::get_accessibleAt(LONG row,
LONG column,
IUnknown** accessible) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACCESSIBLE_AT);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!accessible)
return E_INVALIDARG;
AXPlatformNodeBase* cell =
GetTableCell(static_cast<int>(row), static_cast<int>(column));
if (cell) {
auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
node_win->AddRef();
*accessible = static_cast<IAccessible*>(node_win);
return S_OK;
}
*accessible = nullptr;
return E_INVALIDARG;
}
IFACEMETHODIMP AXPlatformNodeWin::get_caption(IUnknown** accessible) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_CAPTION);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!accessible)
return E_INVALIDARG;
// TODO(dmazzoni): implement
*accessible = nullptr;
return S_FALSE;
}
IFACEMETHODIMP AXPlatformNodeWin::get_childIndex(LONG row,
LONG column,
LONG* cell_index) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_CHILD_INDEX);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!cell_index)
return E_INVALIDARG;
AXPlatformNodeBase* cell =
GetTableCell(static_cast<int>(row), static_cast<int>(column));
if (cell) {
*cell_index = static_cast<LONG>(cell->GetTableCellIndex());
return S_OK;
}
*cell_index = 0;
return E_INVALIDARG;
}
IFACEMETHODIMP AXPlatformNodeWin::get_columnDescription(LONG column,
BSTR* description) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_COLUMN_DESCRIPTION);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!description)
return E_INVALIDARG;
int columns = GetTableColumnCount();
if (column < 0 || column >= columns)
return E_INVALIDARG;
int rows = GetTableRowCount();
if (rows <= 0) {
*description = nullptr;
return S_FALSE;
}
for (int r = 0; r < rows; ++r) {
AXPlatformNodeBase* cell = GetTableCell(r, column);
if (cell && cell->GetData().role == ax::mojom::Role::kColumnHeader) {
base::string16 cell_name =
cell->GetString16Attribute(ax::mojom::StringAttribute::kName);
if (cell_name.size() > 0) {
*description = SysAllocString(cell_name.c_str());
return S_OK;
}
cell_name =
cell->GetString16Attribute(ax::mojom::StringAttribute::kDescription);
if (cell_name.size() > 0) {
*description = SysAllocString(cell_name.c_str());
return S_OK;
}
}
}
*description = nullptr;
return S_FALSE;
}
IFACEMETHODIMP AXPlatformNodeWin::get_columnExtentAt(LONG row,
LONG column,
LONG* n_columns_spanned) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_COLUMN_EXTENT_AT);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!n_columns_spanned)
return E_INVALIDARG;
AXPlatformNodeBase* cell =
GetTableCell(static_cast<int>(row), static_cast<int>(column));
if (!cell)
return E_INVALIDARG;
*n_columns_spanned = cell->GetTableColumnSpan();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_columnHeader(
IAccessibleTable** accessible_table,
LONG* starting_row_index) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_COLUMN_HEADER);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
// TODO(dmazzoni): implement
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_columnIndex(LONG cell_index,
LONG* column_index) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_COLUMN_INDEX);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!column_index)
return E_INVALIDARG;
AXPlatformNodeBase* cell = GetTableCell(cell_index);
if (!cell)
return E_INVALIDARG;
*column_index = cell->GetTableColumn();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_nColumns(LONG* column_count) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_COLUMNS);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!column_count)
return E_INVALIDARG;
*column_count = GetTableColumnCount();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_nRows(LONG* row_count) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_ROWS);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!row_count)
return E_INVALIDARG;
*row_count = GetTableRowCount();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_nSelectedChildren(LONG* cell_count) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_SELECTED_CHILDREN);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!cell_count)
return E_INVALIDARG;
*cell_count = 0;
int columns = GetTableColumnCount();
int rows = GetTableRowCount();
if (columns <= 0 || rows <= 0)
return S_FALSE;
LONG result = 0;
for (int r = 0; r < rows; ++r) {
for (int c = 0; c < columns; ++c) {
AXPlatformNodeBase* cell = GetTableCell(r, c);
if (cell &&
cell->GetData().GetBoolAttribute(ax::mojom::BoolAttribute::kSelected))
result++;
}
}
*cell_count = result;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_nSelectedColumns(LONG* column_count) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_SELECTED_COLUMNS);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!column_count)
return E_INVALIDARG;
*column_count = 0;
int columns = GetTableColumnCount();
int rows = GetTableRowCount();
if (columns <= 0 || rows <= 0)
return S_FALSE;
// If every cell in a column is selected, then that column is selected.
LONG result = 0;
for (int c = 0; c < columns; ++c) {
bool selected = true;
for (int r = 0; r < rows && selected == true; ++r) {
AXPlatformNodeBase* cell = GetTableCell(r, c);
if (!cell || !(cell->GetData().GetBoolAttribute(
ax::mojom::BoolAttribute::kSelected)))
selected = false;
}
if (selected)
result++;
}
*column_count = result;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_nSelectedRows(LONG* row_count) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_SELECTED_ROWS);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!row_count)
return E_INVALIDARG;
*row_count = 0;
int columns = GetTableColumnCount();
int rows = GetTableRowCount();
if (columns <= 0 || rows <= 0)
return S_FALSE;
// If every cell in a row is selected, then that row is selected.
LONG result = 0;
for (int r = 0; r < rows; ++r) {
bool selected = true;
for (int c = 0; c < columns && selected == true; ++c) {
AXPlatformNodeBase* cell = GetTableCell(r, c);
if (!cell || !(cell->GetData().GetBoolAttribute(
ax::mojom::BoolAttribute::kSelected)))
selected = false;
}
if (selected)
result++;
}
*row_count = result;
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_rowDescription(LONG row,
BSTR* description) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ROW_DESCRIPTION);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!description)
return E_INVALIDARG;
if (row < 0 || row >= GetTableRowCount())
return E_INVALIDARG;
int columns = GetTableColumnCount();
if (columns <= 0) {
*description = nullptr;
return S_FALSE;
}
for (int c = 0; c < columns; ++c) {
AXPlatformNodeBase* cell = GetTableCell(row, c);
if (cell && cell->GetData().role == ax::mojom::Role::kRowHeader) {
base::string16 cell_name =
cell->GetString16Attribute(ax::mojom::StringAttribute::kName);
if (cell_name.size() > 0) {
*description = SysAllocString(cell_name.c_str());
return S_OK;
}
cell_name =
cell->GetString16Attribute(ax::mojom::StringAttribute::kDescription);
if (cell_name.size() > 0) {
*description = SysAllocString(cell_name.c_str());
return S_OK;
}
}
}
*description = nullptr;
return S_FALSE;
}
IFACEMETHODIMP AXPlatformNodeWin::get_rowExtentAt(LONG row,
LONG column,
LONG* n_rows_spanned) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ROW_EXTENT_AT);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!n_rows_spanned)
return E_INVALIDARG;
AXPlatformNodeBase* cell = GetTableCell(row, column);
if (!cell)
return E_INVALIDARG;
*n_rows_spanned = GetTableRowSpan();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_rowHeader(
IAccessibleTable** accessible_table,
LONG* starting_column_index) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ROW_HEADER);
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
// TODO(dmazzoni): implement
return E_NOTIMPL;
}
IFACEMETHODIMP AXPlatformNodeWin::get_rowIndex(LONG cell_index,
LONG* row_index) {
// TODO(dougt) WIN_ACCESSIBILITY_API_HISTOGRAM?
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!row_index)
return E_INVALIDARG;
AXPlatformNodeBase* cell = GetTableCell(cell_index);
if (!cell)
return E_INVALIDARG;
*row_index = cell->GetTableRow();
return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_selectedChildren(LONG max_children,
LONG** children,
LONG* n_children) {
// TODO(dougt) WIN_ACCESSIBILITY_API_HISTOGRAM?
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
if (!children || !n_children || max_children <= 0)
return E_INVALIDARG;
int columns = GetTableColumnCount();
int rows = GetTableRowCount();
if (columns <= 0 || rows <= 0)
return S_FALSE;
std::vector<LONG> results;
for (int r = 0; r < rows; ++r) {
for (int c = 0; c <