blob: c95a42e0622e292aadf7e9a58ba7eb423bf69c58 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/accessibility/platform/inspect/ax_element_wrapper_mac.h"
#include <ostream>
#include "base/containers/fixed_flat_set.h"
#include "base/debug/stack_trace.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/strings/pattern.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/accessibility/platform/ax_private_attributes_mac.h"
// error: 'accessibilityAttributeNames' is deprecated: first deprecated in
// macOS 10.10 - Use the NSAccessibility protocol methods instead (see
// NSAccessibilityProtocols.h
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
namespace ui {
using base::SysNSStringToUTF8;
constexpr char kUnsupportedObject[] =
"Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
// static
bool AXElementWrapper::IsValidElement(const id node) {
return AXElementWrapper(node).IsValidElement();
}
bool AXElementWrapper::IsNSAccessibilityElement(const id node) {
return AXElementWrapper(node).IsNSAccessibilityElement();
}
bool AXElementWrapper::IsAXUIElement(const id node) {
return AXElementWrapper(node).IsAXUIElement();
}
NSArray* AXElementWrapper::ChildrenOf(const id node) {
return AXElementWrapper(node).Children();
}
// Returns DOM id of a given node (either AXUIElement or
// BrowserAccessibilityCocoa).
std::string AXElementWrapper::DOMIdOf(const id node) {
return AXElementWrapper(node).DOMId();
}
bool AXElementWrapper::IsValidElement() const {
return IsNSAccessibilityElement() || IsAXUIElement();
}
bool AXElementWrapper::IsNSAccessibilityElement() const {
return [node_ isKindOfClass:[NSAccessibilityElement class]];
}
bool AXElementWrapper::IsAXUIElement() const {
return CFGetTypeID(node_) == AXUIElementGetTypeID();
}
id AXElementWrapper::AsId() const {
return node_;
}
std::string AXElementWrapper::DOMId() const {
const id domid_value = *GetAttributeValue(@"AXDOMIdentifier");
return base::SysNSStringToUTF8(static_cast<NSString*>(domid_value));
}
NSArray* AXElementWrapper::Children() const {
if (IsNSAccessibilityElement())
return [node_ children];
if (IsAXUIElement()) {
CFTypeRef children_ref;
if ((AXUIElementCopyAttributeValue(static_cast<AXUIElementRef>(node_),
kAXChildrenAttribute, &children_ref)) ==
kAXErrorSuccess)
return static_cast<NSArray*>(children_ref);
return nil;
}
NOTREACHED()
<< "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
return nil;
}
NSSize AXElementWrapper::Size() const {
if (IsNSAccessibilityElement()) {
return [node_ accessibilityFrame].size;
}
if (!IsAXUIElement()) {
NOTREACHED()
<< "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
return NSMakeSize(0, 0);
}
id value = *GetAttributeValue(NSAccessibilitySizeAttribute);
if (value && CFGetTypeID(value) == AXValueGetTypeID()) {
AXValueType type = AXValueGetType(static_cast<AXValueRef>(value));
if (type == kAXValueCGSizeType) {
NSSize size;
if (AXValueGetValue(static_cast<AXValueRef>(value), type, &size)) {
return size;
}
}
}
return NSMakeSize(0, 0);
}
NSPoint AXElementWrapper::Position() const {
if (IsNSAccessibilityElement()) {
return [node_ accessibilityFrame].origin;
}
if (!IsAXUIElement()) {
NOTREACHED()
<< "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
return NSMakePoint(0, 0);
}
id value = *GetAttributeValue(NSAccessibilityPositionAttribute);
if (value && CFGetTypeID(value) == AXValueGetTypeID()) {
AXValueType type = AXValueGetType(static_cast<AXValueRef>(value));
if (type == kAXValueCGPointType) {
NSPoint point;
if (AXValueGetValue(static_cast<AXValueRef>(value), type, &point)) {
return point;
}
}
}
return NSMakePoint(0, 0);
}
NSArray* AXElementWrapper::AttributeNames() const {
if (IsNSAccessibilityElement())
return [node_ accessibilityAttributeNames];
if (IsAXUIElement()) {
CFArrayRef attributes_ref;
AXError result = AXUIElementCopyAttributeNames(
static_cast<AXUIElementRef>(node_), &attributes_ref);
if (AXSuccess(result, "AXAttributeNamesOf"))
return static_cast<NSArray*>(attributes_ref);
return nil;
}
NOTREACHED()
<< "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
return nil;
}
NSArray* AXElementWrapper::ParameterizedAttributeNames() const {
if (IsNSAccessibilityElement())
return [node_ accessibilityParameterizedAttributeNames];
if (IsAXUIElement()) {
CFArrayRef attributes_ref;
AXError result = AXUIElementCopyParameterizedAttributeNames(
static_cast<AXUIElementRef>(node_), &attributes_ref);
if (AXSuccess(result, "AXParameterizedAttributeNamesOf"))
return static_cast<NSArray*>(attributes_ref);
return nil;
}
NOTREACHED()
<< "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
return nil;
}
AXOptionalNSObject AXElementWrapper::GetAttributeValue(
NSString* attribute) const {
if (IsNSAccessibilityElement())
return AXOptionalNSObject([node_ accessibilityAttributeValue:attribute]);
if (IsAXUIElement()) {
CFTypeRef value_ref;
AXError result = AXUIElementCopyAttributeValue(
static_cast<AXUIElementRef>(node_), static_cast<CFStringRef>(attribute),
&value_ref);
return ToOptional(
static_cast<id>(value_ref), result,
"AXGetAttributeValue(" + base::SysNSStringToUTF8(attribute) + ")");
}
return AXOptionalNSObject::Error(kUnsupportedObject);
}
AXOptionalNSObject AXElementWrapper::GetParameterizedAttributeValue(
NSString* attribute,
id parameter) const {
if (IsNSAccessibilityElement())
return AXOptionalNSObject([node_ accessibilityAttributeValue:attribute
forParameter:parameter]);
if (IsAXUIElement()) {
// Convert NSValue parameter to CFTypeRef if needed.
CFTypeRef parameter_ref = static_cast<CFTypeRef>(parameter);
if ([parameter isKindOfClass:[NSValue class]] &&
!strcmp([static_cast<NSValue*>(parameter) objCType],
@encode(NSRange))) {
NSRange range = [static_cast<NSValue*>(parameter) rangeValue];
parameter_ref = AXValueCreate(kAXValueTypeCFRange, &range);
}
// Get value.
CFTypeRef value_ref;
AXError result = AXUIElementCopyParameterizedAttributeValue(
static_cast<AXUIElementRef>(node_), static_cast<CFStringRef>(attribute),
parameter_ref, &value_ref);
return ToOptional(static_cast<id>(value_ref), result,
"GetParameterizedAttributeValue(" +
base::SysNSStringToUTF8(attribute) + ")");
}
return AXOptionalNSObject::Error(kUnsupportedObject);
}
absl::optional<id> AXElementWrapper::PerformSelector(
const std::string& selector_string) const {
if (![node_ conformsToProtocol:@protocol(NSAccessibility)])
return absl::nullopt;
NSString* selector_nsstring = base::SysUTF8ToNSString(selector_string);
SEL selector = NSSelectorFromString(selector_nsstring);
if ([node_ respondsToSelector:selector])
return [node_ valueForKey:selector_nsstring];
return absl::nullopt;
}
absl::optional<id> AXElementWrapper::PerformSelector(
const std::string& selector_string,
const std::string& argument_string) const {
if (![node_ conformsToProtocol:@protocol(NSAccessibility)])
return absl::nullopt;
SEL selector =
NSSelectorFromString(base::SysUTF8ToNSString(selector_string + ":"));
NSString* argument = base::SysUTF8ToNSString(argument_string);
if ([node_ respondsToSelector:selector])
return [node_ performSelector:selector withObject:argument];
return absl::nullopt;
}
void AXElementWrapper::SetAttributeValue(NSString* attribute, id value) const {
if (IsNSAccessibilityElement()) {
[node_ accessibilitySetValue:value forAttribute:attribute];
return;
}
if (IsAXUIElement()) {
AXUIElementSetAttributeValue(static_cast<AXUIElementRef>(node_),
static_cast<CFStringRef>(attribute),
static_cast<CFTypeRef>(value));
return;
}
NOTREACHED()
<< "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
}
NSArray* AXElementWrapper::ActionNames() const {
if (IsNSAccessibilityElement())
return [node_ accessibilityActionNames];
if (IsAXUIElement()) {
CFArrayRef attributes_ref;
if ((AXUIElementCopyActionNames(static_cast<AXUIElementRef>(node_),
&attributes_ref)) == kAXErrorSuccess)
return static_cast<NSArray*>(attributes_ref);
return nil;
}
NOTREACHED()
<< "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
return nil;
}
void AXElementWrapper::PerformAction(NSString* action) const {
if (IsNSAccessibilityElement()) {
[node_ accessibilityPerformAction:action];
return;
}
if (IsAXUIElement()) {
AXUIElementPerformAction(static_cast<AXUIElementRef>(node_),
static_cast<CFStringRef>(action));
return;
}
NOTREACHED()
<< "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
}
std::string AXElementWrapper::AXErrorMessage(AXError result,
const std::string& message) const {
if (result == kAXErrorSuccess) {
return {};
}
std::string error;
switch (result) {
case kAXErrorAttributeUnsupported:
error = "attribute unsupported";
break;
case kAXErrorParameterizedAttributeUnsupported:
error = "parameterized attribute unsupported";
break;
case kAXErrorNoValue:
error = "no value";
break;
case kAXErrorIllegalArgument:
error = "illegal argument";
break;
case kAXErrorInvalidUIElement:
error = "invalid UIElement";
break;
case kAXErrorCannotComplete:
error = "cannot complete";
break;
case kAXErrorNotImplemented:
error = "not implemented";
break;
default:
error = "unknown error";
break;
}
return {message + ": " + error};
}
bool AXElementWrapper::AXSuccess(AXError result,
const std::string& message) const {
std::string message_text = AXErrorMessage(result, message);
if (message_text.empty())
return true;
LOG(WARNING) << message_text;
return false;
}
AXOptionalNSObject AXElementWrapper::ToOptional(
id value,
AXError result,
const std::string& message) const {
if (result == kAXErrorSuccess)
return AXOptionalNSObject(value);
return AXOptionalNSObject::Error(AXErrorMessage(result, message));
}
} // namespace ui
#pragma clang diagnostic pop