blob: fbe188b16e957d7a3bba5784d93816a45d941daf [file]
/*
* Copyright (C) 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "AccessibilityCommonCocoa.h"
#import "AccessibilityNotificationHandler.h"
#import "AccessibilityUIElement.h"
#import "InjectedBundle.h"
#import "InjectedBundlePage.h"
#import "JSBasics.h"
#import <AppKit/NSAccessibility.h>
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JSStringRefCF.h>
#import <JavaScriptCore/JSObjectRef.h>
#import <WebCore/DateComponents.h>
#import <WebKit/WKBundleFrame.h>
#import <wtf/RetainPtr.h>
#import <wtf/Vector.h>
#import <wtf/cocoa/VectorCocoa.h>
#if HAVE(ACCESSIBILITY_FRAMEWORK)
#import <Accessibility/Accessibility.h>
#endif
#define NSAccessibilityDOMIdentifierAttribute @"AXDOMIdentifier"
#ifndef NSAccessibilityOwnsAttribute
#define NSAccessibilityOwnsAttribute @"AXOwns"
#endif
#ifndef NSAccessibilityGrabbedAttribute
#define NSAccessibilityGrabbedAttribute @"AXGrabbed"
#endif
#ifndef NSAccessibilityDropEffectsAttribute
#define NSAccessibilityDropEffectsAttribute @"AXDropEffects"
#endif
#ifndef NSAccessibilityPathAttribute
#define NSAccessibilityPathAttribute @"AXPath"
#endif
#ifndef NSAccessibilityARIACurrentAttribute
#define NSAccessibilityARIACurrentAttribute @"AXARIACurrent"
#endif
// Text
#ifndef NSAccessibilityEndTextMarkerForBoundsParameterizedAttribute
#define NSAccessibilityEndTextMarkerForBoundsParameterizedAttribute @"AXEndTextMarkerForBounds"
#endif
#ifndef NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute
#define NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute @"AXStartTextMarkerForBounds"
#endif
#ifndef NSAccessibilitySelectedTextMarkerRangeAttribute
#define NSAccessibilitySelectedTextMarkerRangeAttribute @"AXSelectedTextMarkerRange"
#endif
#ifndef NSAccessibilityTextInputMarkedRangeAttribute
#define NSAccessibilityTextInputMarkedRangeAttribute @"AXTextInputMarkedRange"
#endif
#ifndef NSAccessibilityTextInputMarkedTextMarkerRangeAttribute
#define NSAccessibilityTextInputMarkedTextMarkerRangeAttribute @"AXTextInputMarkedTextMarkerRange"
#endif
#ifndef NSAccessibilityIntersectionWithSelectionRangeAttribute
#define NSAccessibilityIntersectionWithSelectionRangeAttribute @"AXIntersectionWithSelectionRange"
#endif
typedef void (*AXPostedNotificationCallback)(id element, NSString* notification, void* context);
@interface NSObject (WebKitAccessibilityAdditions)
- (BOOL)accessibilityReplaceRange:(NSRange)range withText:(NSString *)string;
- (BOOL)accessibilityInsertText:(NSString *)text;
- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount;
- (NSArray *)_accessibilityChildrenFromIndex:(NSUInteger)index maxCount:(NSUInteger)maxCount returnPlatformElements:(BOOL)returnPlatformElements;
- (NSUInteger)accessibilityIndexOfChild:(id)child;
- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute;
- (void)_accessibilityScrollToMakeVisibleWithSubFocus:(NSRect)rect;
- (void)_accessibilityScrollToGlobalPoint:(NSPoint)point;
- (void)_accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName;
// For site-isolation testing use only.
- (id)_accessibilityHitTest:(NSPoint)point returnPlatformElements:(BOOL)returnPlatformElements;
- (void)_accessibilityHitTestResolvingRemoteFrame:(NSPoint)point callback:(void(^)(NSString *))callback;
@end
namespace WTR {
RefPtr<AccessibilityController> AccessibilityUIElement::s_controller;
AccessibilityUIElement::AccessibilityUIElement(id element)
: m_element(element)
{
if (!s_controller)
s_controller = InjectedBundle::singleton().accessibilityController();
}
AccessibilityUIElement::AccessibilityUIElement(const AccessibilityUIElement& other)
: JSWrappable()
, m_element(other.m_element)
{
}
AccessibilityUIElement::~AccessibilityUIElement()
{
}
bool AccessibilityUIElement::isEqual(AccessibilityUIElement* otherElement)
{
if (!otherElement)
return false;
return platformUIElement() == otherElement->platformUIElement();
}
RetainPtr<NSArray> supportedAttributes(id element)
{
RetainPtr<NSArray> attributes;
BEGIN_AX_OBJC_EXCEPTIONS
AccessibilityUIElement::s_controller->executeOnAXThreadAndWait([&attributes, &element] {
attributes = [element accessibilityAttributeNames];
});
END_AX_OBJC_EXCEPTIONS
return attributes;
}
static id attributeValue(id element, NSString *attribute)
{
// The given `element` may not respond to `accessibilityAttributeValue`, so first check to see if it responds to the attribute-specific selector.
if ([attribute isEqual:NSAccessibilityChildrenAttribute] && [element respondsToSelector:@selector(accessibilityChildren)])
return [element accessibilityChildren];
if ([attribute isEqual:NSAccessibilityDescriptionAttribute] && [element respondsToSelector:@selector(accessibilityLabel)])
return [element accessibilityLabel];
if ([attribute isEqual:NSAccessibilityParentAttribute] && [element respondsToSelector:@selector(accessibilityParent)])
return [element accessibilityParent];
if ([attribute isEqual:NSAccessibilityRoleAttribute] && [element respondsToSelector:@selector(accessibilityRole)])
return [element accessibilityRole];
if ([attribute isEqual:NSAccessibilityValueAttribute] && [element respondsToSelector:@selector(accessibilityValue)])
return [element accessibilityValue];
if ([attribute isEqual:NSAccessibilityFocusedUIElementAttribute] && [element respondsToSelector:@selector(accessibilityFocusedUIElement)])
return [element accessibilityFocusedUIElement];
// These are internal APIs used by DRT/WKTR; tests are allowed to use them but we don't want to advertise them.
static NeverDestroyed<RetainPtr<NSArray>> internalAttributes = @[
@"AXARIAPressedIsPresent",
@"AXARIARole",
@"AXAutocompleteValue",
@"AXClickPoint",
@"AXControllerFor",
@"AXControllers",
@"AXDRTSpeechAttribute",
@"AXDateTimeComponentsType",
@"AXDescribedBy",
@"AXDescriptionFor",
@"AXDetailsFor",
@"AXErrorMessageFor",
@"AXFlowFrom",
@"AXIsInCell",
@"_AXIsInTable",
@"AXIsInDescriptionListDetail",
@"AXIsInDescriptionListTerm",
@"AXIsIndeterminate",
@"AXIsMultiSelectable",
@"AXIsOnScreen",
@"AXIsRemoteFrame",
@"AXLabelFor",
@"AXLabelledBy",
@"AXLineRectsAndText",
@"AXOwners",
@"_AXPageRelativePosition",
@"AXStringValue",
@"AXValueAutofillType",
// FIXME: these shouldn't be here, but removing one of these causes tests to fail.
@"AXARIACurrent",
@"AXARIALive",
@"AXDescription",
@"AXKeyShortcutsValue",
@"AXOwns",
@"AXPopupValue",
@"AXValue",
];
NSArray<NSString *> *supportedAttributes = [element accessibilityAttributeNames];
if (![supportedAttributes containsObject:attribute] && ![internalAttributes.get() containsObject:attribute] && ![attribute isEqualToString:NSAccessibilityRoleAttribute])
return nil;
return [element accessibilityAttributeValue:attribute];
}
void setAttributeValue(id element, NSString* attribute, id value, bool synchronous = false)
{
BEGIN_AX_OBJC_EXCEPTIONS
AccessibilityUIElement::s_controller->executeOnAXThreadAndWait([&element, &attribute, &value, &synchronous] {
// FIXME: should always be asynchronous, fix tests.
synchronous ? [element _accessibilitySetValue:value forAttribute:attribute] :
[element accessibilitySetValue:value forAttribute:attribute];
});
END_AX_OBJC_EXCEPTIONS
}
RetainPtr<NSString> AccessibilityUIElement::descriptionOfValue(id valueObject) const
{
if (!valueObject)
return nil;
if ([valueObject isKindOfClass:[NSArray class]])
return [NSString stringWithFormat:@"<array of size %lu>", static_cast<unsigned long>([(NSArray*)valueObject count])];
if ([valueObject isKindOfClass:[NSNumber class]])
return [(NSNumber*)valueObject stringValue];
if ([valueObject isKindOfClass:[NSValue class]]) {
NSString *type = [NSString stringWithCString:[valueObject objCType] encoding:NSASCIIStringEncoding];
NSValue *value = (NSValue *)valueObject;
if ([type rangeOfString:@"NSRect"].length > 0)
return [NSString stringWithFormat:@"NSRect: %@", NSStringFromRect([value rectValue])];
if ([type rangeOfString:@"NSPoint"].length > 0)
return [NSString stringWithFormat:@"NSPoint: %@", NSStringFromPoint([value pointValue])];
if ([type rangeOfString:@"NSSize"].length > 0)
return [NSString stringWithFormat:@"NSSize: %@", NSStringFromSize([value sizeValue])];
if ([type rangeOfString:@"NSRange"].length > 0)
return [NSString stringWithFormat:@"NSRange: %@", NSStringFromRange([value rangeValue])];
}
// Strip absolute URL paths
NSString *description = [valueObject description];
NSRange range = [description rangeOfString:@"LayoutTests"];
if (range.length)
return [description substringFromIndex:range.location];
// Strip pointer locations
if ([description rangeOfString:@"0x"].length) {
auto role = attributeValue(NSAccessibilityRoleAttribute);
auto title = attributeValue(NSAccessibilityTitleAttribute);
if ([title length])
return [NSString stringWithFormat:@"<%@: '%@'>", role.get(), title.get()];
return [NSString stringWithFormat:@"<%@>", role.get()];
}
return [valueObject description];
}
static JSRetainPtr<JSStringRef> concatenateAttributeAndValue(NSString* attribute, NSString* value)
{
Vector<UniChar> buffer([attribute length]);
[attribute getCharacters:buffer.mutableSpan().data()];
buffer.append(':');
buffer.append(' ');
Vector<UniChar> valueBuffer([value length]);
[value getCharacters:valueBuffer.mutableSpan().data()];
buffer.appendVector(valueBuffer);
return adopt(JSStringCreateWithCharacters(buffer.span().data(), buffer.size()));
}
static JSRetainPtr<JSStringRef> descriptionOfElements(const Vector<RefPtr<AccessibilityUIElement>>& elements)
{
NSMutableString *allElementString = [NSMutableString string];
for (auto element : elements) {
NSString *attributes = [NSString stringWithJSStringRef:element->allAttributes().get()];
[allElementString appendFormat:@"%@\n------------\n", attributes];
}
return [allElementString createJSStringRef];
}
static NSDictionary *selectTextParameterizedAttributeForCriteria(JSContextRef context, JSStringRef ambiguityResolution, JSValueRef searchStrings, JSStringRef replacementString, JSStringRef activity)
{
NSMutableDictionary *parameterizedAttribute = [NSMutableDictionary dictionary];
if (ambiguityResolution)
[parameterizedAttribute setObject:[NSString stringWithJSStringRef:ambiguityResolution] forKey:@"AXSelectTextAmbiguityResolution"];
if (searchStrings) {
NSMutableArray *searchStringsParameter = [NSMutableArray array];
if (JSValueIsString(context, searchStrings))
[searchStringsParameter addObject:toWTFString(context, searchStrings).createNSString().get()];
else {
JSObjectRef searchStringsArray = JSValueToObject(context, searchStrings, nullptr);
unsigned searchStringsArrayLength = arrayLength(context, searchStringsArray);
for (unsigned i = 0; i < searchStringsArrayLength; ++i)
[searchStringsParameter addObject:toWTFString(context, JSObjectGetPropertyAtIndex(context, searchStringsArray, i, nullptr)).createNSString().get()];
}
[parameterizedAttribute setObject:searchStringsParameter forKey:@"AXSelectTextSearchStrings"];
}
if (replacementString) {
[parameterizedAttribute setObject:@"AXSelectTextActivityFindAndReplace" forKey:@"AXSelectTextActivity"];
[parameterizedAttribute setObject:[NSString stringWithJSStringRef:replacementString] forKey:@"AXSelectTextReplacementString"];
} else
[parameterizedAttribute setObject:@"AXSelectTextActivityFindAndSelect" forKey:@"AXSelectTextActivity"];
if (activity)
[parameterizedAttribute setObject:[NSString stringWithJSStringRef:activity] forKey:@"AXSelectTextActivity"];
return parameterizedAttribute;
}
static NSDictionary *searchTextParameterizedAttributeForCriteria(JSContextRef context, JSValueRef searchStrings, JSStringRef startFrom, JSStringRef direction)
{
NSMutableDictionary *parameterizedAttribute = [NSMutableDictionary dictionary];
if (searchStrings) {
NSMutableArray *searchStringsParameter = [NSMutableArray array];
if (JSValueIsString(context, searchStrings))
[searchStringsParameter addObject:toWTFString(context, searchStrings).createNSString().get()];
else {
JSObjectRef searchStringsArray = JSValueToObject(context, searchStrings, nullptr);
unsigned searchStringsArrayLength = arrayLength(context, searchStringsArray);
for (unsigned i = 0; i < searchStringsArrayLength; ++i)
[searchStringsParameter addObject:toWTFString(context, JSObjectGetPropertyAtIndex(context, searchStringsArray, i, nullptr)).createNSString().get()];
}
[parameterizedAttribute setObject:searchStringsParameter forKey:@"AXSearchTextSearchStrings"];
}
if (startFrom)
[parameterizedAttribute setObject:[NSString stringWithJSStringRef:startFrom] forKey:@"AXSearchTextStartFrom"];
if (direction)
[parameterizedAttribute setObject:[NSString stringWithJSStringRef:direction] forKey:@"AXSearchTextDirection"];
return parameterizedAttribute;
}
static NSDictionary *textOperationParameterizedAttribute(JSContextRef context, JSStringRef operationType, JSValueRef markerRanges, JSValueRef replacementStrings, bool shouldSmartReplace)
{
NSMutableDictionary *attributeParameters = [NSMutableDictionary dictionary];
if (operationType)
[attributeParameters setObject:[NSString stringWithJSStringRef:operationType] forKey:@"AXTextOperationType"];
if (markerRanges) {
JSObjectRef markerRangesArray = JSValueToObject(context, markerRanges, nullptr);
unsigned markerRangesArrayLength = arrayLength(context, markerRangesArray);
NSMutableArray *platformRanges = [NSMutableArray array];
for (unsigned i = 0; i < markerRangesArrayLength; ++i) {
auto propertyAtIndex = JSObjectGetPropertyAtIndex(context, markerRangesArray, i, nullptr);
auto markerRangeRef = toTextMarkerRange(JSValueToObject(context, propertyAtIndex, nullptr));
[platformRanges addObject:markerRangeRef->platformTextMarkerRange()];
}
[attributeParameters setObject:platformRanges forKey:@"AXTextOperationMarkerRanges"];
}
if (JSValueIsString(context, replacementStrings))
[attributeParameters setObject:toWTFString(context, replacementStrings).createNSString().get() forKey:@"AXTextOperationReplacementString"];
else {
NSMutableArray *individualReplacementStringsParameter = [NSMutableArray array];
JSObjectRef replacementStringsArray = JSValueToObject(context, replacementStrings, nullptr);
unsigned replacementStringsArrayLength = arrayLength(context, replacementStringsArray);
for (unsigned i = 0; i < replacementStringsArrayLength; ++i)
[individualReplacementStringsParameter addObject:toWTFString(context, JSObjectGetPropertyAtIndex(context, replacementStringsArray, i, nullptr)).createNSString().get()];
[attributeParameters setObject:individualReplacementStringsParameter forKey:@"AXTextOperationIndividualReplacementStrings"];
}
[attributeParameters setObject:[NSNumber numberWithBool:shouldSmartReplace] forKey:@"AXTextOperationSmartReplace"];
return attributeParameters;
}
static NSDictionary *misspellingSearchParameterizedAttributeForCriteria(AccessibilityTextMarkerRange* start, bool forward)
{
if (!start || !start->platformTextMarkerRange())
return nil;
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
[parameters setObject:start->platformTextMarkerRange() forKey:@"AXStartTextMarkerRange"];
[parameters setObject:[NSNumber numberWithBool:forward] forKey:@"AXSearchTextDirection"];
return parameters;
}
RetainPtr<id> AccessibilityUIElement::attributeValue(NSString *attributeName) const
{
RetainPtr<id> value;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([this, &attributeName, &value] {
value = WTR::attributeValue(m_element.getAutoreleased(), attributeName);
});
END_AX_OBJC_EXCEPTIONS
return value;
}
RetainPtr<id> AccessibilityUIElement::attributeValueForParameter(NSString *attributeName, id parameter) const
{
RetainPtr<id> value;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([this, &attributeName, &parameter, &value] {
value = [m_element accessibilityAttributeValue:attributeName forParameter:parameter];
});
END_AX_OBJC_EXCEPTIONS
return value;
}
unsigned AccessibilityUIElement::arrayAttributeCount(NSString *attributeName) const
{
unsigned count = 0;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([&attributeName, &count, this] {
count = [m_element accessibilityArrayAttributeCount:attributeName];
});
END_AX_OBJC_EXCEPTIONS
return count;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::domIdentifier() const
{
return stringAttributeValueNS(NSAccessibilityDOMIdentifierAttribute);
}
void AccessibilityUIElement::getLinkedUIElements(Vector<RefPtr<AccessibilityUIElement>>& elementVector)
{
elementVector = makeVector<RefPtr<AccessibilityUIElement>>(attributeValue(NSAccessibilityLinkedUIElementsAttribute).get());
}
void AccessibilityUIElement::getDocumentLinks(Vector<RefPtr<AccessibilityUIElement>>& elementVector)
{
elementVector = makeVector<RefPtr<AccessibilityUIElement>>(attributeValue(@"AXLinkUIElements").get());
}
void AccessibilityUIElement::getUIElementsWithAttribute(JSStringRef attribute, Vector<RefPtr<AccessibilityUIElement>>& elements) const
{
auto value = attributeValue([NSString stringWithJSStringRef:attribute]);
if ([value isKindOfClass:[NSArray class]])
elements = makeVector<RefPtr<AccessibilityUIElement>>(value.get());
}
JSValueRef AccessibilityUIElement::children(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
return makeJSArray(context, makeVector<RefPtr<AccessibilityUIElement>>(attributeValue(NSAccessibilityChildrenAttribute).get()));
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
Vector<RefPtr<AccessibilityUIElement>> AccessibilityUIElement::getChildren() const
{
return makeVector<RefPtr<AccessibilityUIElement>>(attributeValue(NSAccessibilityChildrenAttribute).get());
}
Vector<RefPtr<AccessibilityUIElement>> AccessibilityUIElement::getChildrenInRange(unsigned location, unsigned length) const
{
BEGIN_AX_OBJC_EXCEPTIONS
RetainPtr<NSArray> children;
s_controller->executeOnAXThreadAndWait([&children, location, length, this] {
children = [m_element accessibilityArrayAttributeValues:NSAccessibilityChildrenAttribute index:location maxCount:length];
});
return makeVector<RefPtr<AccessibilityUIElement>>(children.get());
END_AX_OBJC_EXCEPTIONS
return { };
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::childAtIndexWithRemoteElement(unsigned index)
{
RetainPtr<NSArray> children;
s_controller->executeOnAXThreadAndWait([&children, index, this] {
children = [m_element _accessibilityChildrenFromIndex:index maxCount:1 returnPlatformElements:NO];
});
auto resultChildren = makeVector<RefPtr<AccessibilityUIElement>>(children.get());
return resultChildren.size() == 1 ? resultChildren[0] : nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::customContent() const
{
BEGIN_AX_OBJC_EXCEPTIONS
#if HAVE(ACCESSIBILITY_FRAMEWORK)
auto customContent = adoptNS([[NSMutableArray alloc] init]);
s_controller->executeOnAXThreadAndWait([this, &customContent] {
for (AXCustomContent *content in [m_element accessibilityCustomContent])
[customContent addObject:[NSString stringWithFormat:@"%@: %@", content.label, content.value]];
});
return [[customContent.get() componentsJoinedByString:@"\n"] createJSStringRef];
#else
return nullptr;
#endif
END_AX_OBJC_EXCEPTIONS
}
JSValueRef AccessibilityUIElement::rowHeaders(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
Vector<RefPtr<AccessibilityUIElement>> elements;
auto value = attributeValue(NSAccessibilityRowHeaderUIElementsAttribute);
if ([value isKindOfClass:[NSArray class]])
elements = makeVector<RefPtr<AccessibilityUIElement>>(value.get());
return makeJSArray(context, elements);
END_AX_OBJC_EXCEPTIONS
}
JSValueRef AccessibilityUIElement::selectedCells(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(NSAccessibilitySelectedCellsAttribute);
if ([value isKindOfClass:[NSArray class]])
return makeJSArray(context, makeVector<RefPtr<AccessibilityUIElement>>(value.get()));
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSValueRef AccessibilityUIElement::columnHeaders(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
Vector<RefPtr<AccessibilityUIElement>> elements;
auto value = attributeValue(NSAccessibilityColumnHeaderUIElementsAttribute);
if ([value isKindOfClass:[NSArray class]])
elements = makeVector<RefPtr<AccessibilityUIElement>>(value.get());
return makeJSArray(context, elements);
END_AX_OBJC_EXCEPTIONS
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::elementAtPoint(int x, int y)
{
RetainPtr<id> element;
s_controller->executeOnAXThreadAndWait([&x, &y, &element, this] {
element = [m_element accessibilityHitTest:NSMakePoint(x, y)];
});
if (!element)
return nullptr;
return AccessibilityUIElement::create(element.get());
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::elementAtPointWithRemoteElement(int x, int y)
{
RetainPtr<id> element;
s_controller->executeOnAXThreadAndWait([&x, &y, &element, this] {
element = [m_element _accessibilityHitTest:NSMakePoint(x, y) returnPlatformElements:NO];
});
if (!element)
return nullptr;
return AccessibilityUIElement::create(element.get());
}
void AccessibilityUIElement::elementAtPointResolvingRemoteFrame(JSContextRef context, int x, int y, JSValueRef jsCallback)
{
JSValueProtect(context, jsCallback);
s_controller->executeOnAXThreadAndWait([x, y, protectedThis = Ref { *this }, jsCallback = WTFMove(jsCallback), context = JSRetainPtr { JSContextGetGlobalContext(context) }] () mutable {
auto callback = [jsCallback = WTFMove(jsCallback), context = WTFMove(context)](NSString *result) {
s_controller->executeOnMainThread([result = WTFMove(result), jsCallback = WTFMove(jsCallback), context = WTFMove(context)] () {
JSValueRef arguments[1];
arguments[0] = makeValueRefForValue(context.get(), result);
JSObjectCallAsFunction(context.get(), const_cast<JSObjectRef>(jsCallback), 0, 1, arguments, 0);
JSValueUnprotect(context.get(), jsCallback);
});
};
[protectedThis->m_element _accessibilityHitTestResolvingRemoteFrame:NSMakePoint(x, y) callback:WTFMove(callback)];
});
}
unsigned AccessibilityUIElement::indexOfChild(AccessibilityUIElement* element)
{
unsigned index;
id platformElement = element->platformUIElement();
s_controller->executeOnAXThreadAndWait([&platformElement, &index, this] {
index = [m_element accessibilityIndexOfChild:platformElement];
});
return index;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::elementForAttribute(NSString *attribute) const
{
auto element = attributeValue(attribute);
return element ? AccessibilityUIElement::create(element.get()) : RefPtr<AccessibilityUIElement>();
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::elementForAttributeAtIndex(NSString* attribute, unsigned index) const
{
auto elements = attributeValue(attribute);
return index < [elements count] ? AccessibilityUIElement::create([elements objectAtIndex:index]) : RefPtr<AccessibilityUIElement>();
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::linkedUIElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(NSAccessibilityLinkedUIElementsAttribute, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::controllerElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXControllers", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXControllerFor", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDescribedByElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXDescribedBy", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::descriptionForElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXDescriptionFor", index);
}
JSValueRef AccessibilityUIElement::detailsElements(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto elements = attributeValue(@"AXDetailsElements");
if ([elements isKindOfClass:NSArray.class])
return makeJSArray(context, makeVector<RefPtr<AccessibilityUIElement>>(elements.get()));
END_AX_OBJC_EXCEPTIONS
return { };
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDetailsElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXDetailsElements", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::detailsForElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXDetailsFor", index);
}
JSValueRef AccessibilityUIElement::errorMessageElements(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto elements = attributeValue(@"AXErrorMessageElements");
if ([elements isKindOfClass:NSArray.class])
return makeJSArray(context, makeVector<RefPtr<AccessibilityUIElement>>(elements.get()));
END_AX_OBJC_EXCEPTIONS
return { };
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaErrorMessageElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXErrorMessageElements", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::errorMessageForElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXErrorMessageFor", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::flowFromElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXFlowFrom", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(NSAccessibilityLinkedUIElementsAttribute, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaLabelledByElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXLabelledBy", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::labelForElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXLabelFor", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ownerElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(@"AXOwners", index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaOwnsElementAtIndex(unsigned index)
{
return elementForAttributeAtIndex(NSAccessibilityOwnsAttribute, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedRowAtIndex(unsigned index)
{
return elementForAttributeAtIndex(NSAccessibilityDisclosedRowsAttribute, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::rowAtIndex(unsigned index)
{
return elementForAttributeAtIndex(NSAccessibilityRowsAttribute, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::activeElement() const
{
return elementForAttribute(@"AXActiveElement");
}
JSValueRef AccessibilityUIElement::selectedChildren(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto children = attributeValue(NSAccessibilitySelectedChildrenAttribute);
if ([children isKindOfClass:NSArray.class])
return makeJSArray(context, makeVector<RefPtr<AccessibilityUIElement>>(children.get()));
END_AX_OBJC_EXCEPTIONS
return makeJSArray(context, { });
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedChildAtIndex(unsigned index) const
{
return elementForAttributeAtIndex(NSAccessibilitySelectedChildrenAttribute, index);
}
unsigned AccessibilityUIElement::selectedChildrenCount() const
{
return arrayAttributeCount(NSAccessibilitySelectedChildrenAttribute);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedRowAtIndex(unsigned index)
{
return elementForAttributeAtIndex(NSAccessibilitySelectedRowsAttribute, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::titleUIElement()
{
return elementForAttribute(NSAccessibilityTitleUIElementAttribute);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::parentElement()
{
return elementForAttribute(NSAccessibilityParentAttribute);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedByRow()
{
return elementForAttribute(NSAccessibilityDisclosedByRowAttribute);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfLinkedUIElements()
{
Vector<RefPtr<AccessibilityUIElement> > linkedElements;
getLinkedUIElements(linkedElements);
return descriptionOfElements(linkedElements);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfDocumentLinks()
{
Vector<RefPtr<AccessibilityUIElement> > linkElements;
getDocumentLinks(linkElements);
return descriptionOfElements(linkElements);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfChildren()
{
return descriptionOfElements(getChildren());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::allAttributes()
{
auto attributes = supportedAttributes(m_element.getAutoreleased());
NSMutableString *values = [NSMutableString string];
for (NSString *attribute in attributes.get()) {
// Exclude screen-specific values, since they can change depending on the system.
if ([attribute isEqualToString:@"AXPosition"]
|| [attribute isEqualToString:@"_AXPrimaryScreenHeight"]
|| [attribute isEqualToString:@"AXRelativeFrame"])
continue;
if ([attribute isEqualToString:@"AXVisibleCharacterRange"]) {
id value = attributeValue(NSAccessibilityRoleAttribute).get();
NSString *role = [value isKindOfClass:[NSString class]] ? (NSString *)value : nil;
if (role == nil || [role isEqualToString:@"AXList"] || [role isEqualToString:@"AXLink"] || [role isEqualToString:@"AXGroup"] || [role isEqualToString:@"AXRow"] || [role isEqualToString:@"AXColumn"] || [role isEqualToString:@"AXTable"] || [role isEqualToString:@"AXWebArea"]) {
// For some roles, behavior with ITM on and ITM off differ for this API in ways
// that are not clearly meaningful to any actual user-facing behavior. Skip dumping this
// attribute for all of the "dump every attribute for every element" tests.
// We can test visible-character-range in dedicated tests.
continue;
}
}
auto value = descriptionOfValue(attributeValue(attribute).get());
if (!value
&& ([attribute isEqualToString:NSAccessibilityTextInputMarkedRangeAttribute]
|| [attribute isEqualToString:NSAccessibilityTextInputMarkedTextMarkerRangeAttribute]))
continue;
[values appendFormat:@"%@: %@\n", attribute, value.get()];
}
return [values createJSStringRef];
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringDescriptionOfAttributeValue(JSStringRef attribute)
{
auto value = attributeValue([NSString stringWithJSStringRef:attribute]);
auto valueDescription = descriptionOfValue(value.get());
return [valueDescription createJSStringRef];
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringAttributeValue(JSStringRef attribute)
{
return stringAttributeValueNS([NSString stringWithJSStringRef:attribute]);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringAttributeValueNS(NSString *attribute) const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(attribute);
if ([value isKindOfClass:[NSString class]])
return [value createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
double AccessibilityUIElement::numberAttributeValue(JSStringRef attribute)
{
return numberAttributeValueNS([NSString stringWithJSStringRef:attribute]);
}
double AccessibilityUIElement::numberAttributeValueNS(NSString *attribute) const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(attribute);
if ([value isKindOfClass:[NSNumber class]])
return [value doubleValue];
END_AX_OBJC_EXCEPTIONS
return 0;
}
JSValueRef AccessibilityUIElement::uiElementArrayAttributeValue(JSContextRef context, JSStringRef attribute)
{
Vector<RefPtr<AccessibilityUIElement>> elements;
getUIElementsWithAttribute(attribute, elements);
return makeJSArray(context, elements);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementAttributeValue(JSStringRef attribute) const
{
if (auto value = attributeValue([NSString stringWithJSStringRef:attribute]))
return AccessibilityUIElement::create(value.get());
return nullptr;
}
bool AccessibilityUIElement::boolAttributeValueNS(NSString *attribute) const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(attribute);
if ([value isKindOfClass:[NSNumber class]])
return [value boolValue];
END_AX_OBJC_EXCEPTIONS
return false;
}
bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute)
{
return boolAttributeValueNS([NSString stringWithJSStringRef:attribute]);
}
void AccessibilityUIElement::attributeValueAsync(JSContextRef context, JSStringRef attribute, JSValueRef callback)
{
if (!attribute || !callback)
return;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([attribute = retainPtr([NSString stringWithJSStringRef:attribute]), callback = WTFMove(callback), context = JSRetainPtr { JSContextGetGlobalContext(context) }, this] () mutable {
id value = [m_element accessibilityAttributeValue:attribute.get()];
if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]])
value = [value description];
s_controller->executeOnMainThread([value = retainPtr(value), callback = WTFMove(callback), context = WTFMove(context)] () {
JSValueRef arguments[1];
arguments[0] = makeValueRefForValue(context.get(), value.get());
JSObjectCallAsFunction(context.get(), const_cast<JSObjectRef>(callback), 0, 1, arguments, 0);
});
});
END_AX_OBJC_EXCEPTIONS
}
void AccessibilityUIElement::setBoolAttributeValue(JSStringRef attribute, bool value)
{
setAttributeValue(m_element.getAutoreleased(), [NSString stringWithJSStringRef:attribute], @(value), true);
}
void AccessibilityUIElement::setValue(JSStringRef value)
{
setAttributeValue(m_element.getAutoreleased(), NSAccessibilityValueAttribute, [NSString stringWithJSStringRef:value]);
}
bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute)
{
return isAttributeSettableNS([NSString stringWithJSStringRef:attribute]);
}
bool AccessibilityUIElement::isAttributeSettableNS(NSString *attribute) const
{
bool value = false;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([this, &attribute, &value] {
value = [m_element accessibilityIsAttributeSettable:attribute];
});
END_AX_OBJC_EXCEPTIONS
return value;
}
bool AccessibilityUIElement::isAttributeSupported(JSStringRef attribute)
{
BEGIN_AX_OBJC_EXCEPTIONS
return [supportedAttributes(m_element.getAutoreleased()) containsObject:[NSString stringWithJSStringRef:attribute]];
END_AX_OBJC_EXCEPTIONS
return false;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::parameterizedAttributeNames()
{
NSArray *attributes = nil;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([&attributes, this] {
attributes = [m_element accessibilityParameterizedAttributeNames];
});
END_AX_OBJC_EXCEPTIONS
NSMutableString *attributesString = [NSMutableString string];
for (id attribute in attributes)
[attributesString appendFormat:@"%@\n", attribute];
return [attributesString createJSStringRef];
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::role()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto role = descriptionOfValue(attributeValue(NSAccessibilityRoleAttribute).get());
return concatenateAttributeAndValue(@"AXRole", role.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::subrole()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto subrole = descriptionOfValue(attributeValue(NSAccessibilitySubroleAttribute).get());
return concatenateAttributeAndValue(@"AXSubrole", subrole.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::roleDescription()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto role = descriptionOfValue(attributeValue(NSAccessibilityRoleDescriptionAttribute).get());
return concatenateAttributeAndValue(@"AXRoleDescription", role.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::computedRoleString()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto computedRoleString = descriptionOfValue(attributeValue(@"AXARIARole").get());
return [computedRoleString createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::title()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto title = descriptionOfValue(attributeValue(NSAccessibilityTitleAttribute).get());
return concatenateAttributeAndValue(@"AXTitle", title.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::description()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto description = descriptionOfValue(attributeValue(NSAccessibilityDescriptionAttribute).get());
return concatenateAttributeAndValue(@"AXDescription", description.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::brailleLabel() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto description = descriptionOfValue(attributeValue(@"AXBrailleLabel").get());
return concatenateAttributeAndValue(@"AXBrailleLabel", description.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::brailleRoleDescription() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto description = descriptionOfValue(attributeValue(@"AXBrailleRoleDescription").get());
return concatenateAttributeAndValue(@"AXBrailleRoleDescription", description.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::liveRegionStatus() const
{
return stringAttributeValueNS(@"AXARIALive");
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::liveRegionRelevant() const
{
return stringAttributeValueNS(@"AXARIARelevant");
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::orientation() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto description = descriptionOfValue(attributeValue(NSAccessibilityOrientationAttribute).get());
return concatenateAttributeAndValue(@"AXOrientation", description.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringValue()
{
BEGIN_AX_OBJC_EXCEPTIONS
RetainPtr<id> value;
auto role = attributeValue(NSAccessibilityRoleAttribute);
if ([role isEqualToString:@"AXDateTimeArea"])
value = attributeValue(@"AXStringValue");
else
value = attributeValue(NSAccessibilityValueAttribute);
if (auto description = descriptionOfValue(value.get()))
return concatenateAttributeAndValue(@"AXValue", description.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::dateValue()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(NSAccessibilityValueAttribute);
if (![value isKindOfClass:[NSDate class]])
return nullptr;
// Adjust the returned date per time zone and daylight savings in an equivalent way to what VoiceOver does.
NSInteger offset = [[NSTimeZone localTimeZone] secondsFromGMTForDate:[NSDate date]];
auto type = attributeValue(@"AXDateTimeComponentsType");
if ([type unsignedShortValue] != (uint8_t)WebCore::DateComponentsType::DateTimeLocal && [[NSTimeZone localTimeZone] isDaylightSavingTimeForDate:[NSDate date]])
offset -= 3600;
value = [NSDate dateWithTimeInterval:offset sinceDate:value.get()];
if (auto description = descriptionOfValue(value.get()))
return concatenateAttributeAndValue(@"AXDateValue", description.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::dateTimeValue() const
{
BEGIN_AX_OBJC_EXCEPTIONS
return stringAttributeValueNS(@"AXDateTimeValue");
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::language()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto description = descriptionOfValue(attributeValue(@"AXLanguage").get());
return concatenateAttributeAndValue(@"AXLanguage", description.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::helpText() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto description = descriptionOfValue(attributeValue(NSAccessibilityHelpAttribute).get());
return concatenateAttributeAndValue(@"AXHelp", description.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
double AccessibilityUIElement::pageX()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto positionValue = attributeValue(@"_AXPageRelativePosition");
return static_cast<double>([positionValue pointValue].x);
END_AX_OBJC_EXCEPTIONS
return 0.0f;
}
double AccessibilityUIElement::pageY()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto positionValue = attributeValue(@"_AXPageRelativePosition");
return static_cast<double>([positionValue pointValue].y);
END_AX_OBJC_EXCEPTIONS
return 0.0f;
}
double AccessibilityUIElement::x()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto positionValue = attributeValue(NSAccessibilityPositionAttribute);
return static_cast<double>([positionValue pointValue].x);
END_AX_OBJC_EXCEPTIONS
return 0.0f;
}
double AccessibilityUIElement::y()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto positionValue = attributeValue(NSAccessibilityPositionAttribute);
return static_cast<double>([positionValue pointValue].y);
END_AX_OBJC_EXCEPTIONS
return 0.0f;
}
double AccessibilityUIElement::width()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto sizeValue = attributeValue(NSAccessibilitySizeAttribute);
return static_cast<double>([sizeValue sizeValue].width);
END_AX_OBJC_EXCEPTIONS
return 0.0f;
}
double AccessibilityUIElement::height()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto sizeValue = attributeValue(NSAccessibilitySizeAttribute);
return static_cast<double>([sizeValue sizeValue].height);
END_AX_OBJC_EXCEPTIONS
return 0.0f;
}
double AccessibilityUIElement::clickPointX()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto positionValue = attributeValue(@"AXClickPoint");
return static_cast<double>([positionValue pointValue].x);
END_AX_OBJC_EXCEPTIONS
return 0.0f;
}
double AccessibilityUIElement::clickPointY()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto positionValue = attributeValue(@"AXClickPoint");
return static_cast<double>([positionValue pointValue].y);
END_AX_OBJC_EXCEPTIONS
return 0.0f;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::lineRectsAndText() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto lineRectsAndText = attributeValue(@"AXLineRectsAndText");
if (![lineRectsAndText isKindOfClass:NSArray.class])
return { };
return [[lineRectsAndText componentsJoinedByString:@"|"] createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return { };
}
double AccessibilityUIElement::intValue() const
{
return numberAttributeValueNS(NSAccessibilityValueAttribute);
}
double AccessibilityUIElement::minValue()
{
return numberAttributeValueNS(NSAccessibilityMinValueAttribute);
}
double AccessibilityUIElement::maxValue()
{
return numberAttributeValueNS(NSAccessibilityMaxValueAttribute);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::valueDescription()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto valueDescription = attributeValue(NSAccessibilityValueDescriptionAttribute);
if ([valueDescription isKindOfClass:[NSString class]])
return concatenateAttributeAndValue(@"AXValueDescription", valueDescription.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
unsigned AccessibilityUIElement::numberOfCharacters() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(NSAccessibilityNumberOfCharactersAttribute);
if ([value isKindOfClass:[NSNumber class]])
return [(NSNumber *)value unsignedIntValue];
END_AX_OBJC_EXCEPTIONS
return 0;
}
int AccessibilityUIElement::insertionPointLineNumber()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(NSAccessibilityInsertionPointLineNumberAttribute);
if ([value isKindOfClass:[NSNumber class]])
return [(NSNumber *)value intValue];
END_AX_OBJC_EXCEPTIONS
return -1;
}
bool AccessibilityUIElement::isPressActionSupported()
{
BEGIN_AX_OBJC_EXCEPTIONS
return [actionNames() containsObject:NSAccessibilityPressAction];
END_AX_OBJC_EXCEPTIONS
return false;
}
bool AccessibilityUIElement::isIncrementActionSupported()
{
BEGIN_AX_OBJC_EXCEPTIONS
return [actionNames() containsObject:NSAccessibilityIncrementAction];
END_AX_OBJC_EXCEPTIONS
return false;
}
bool AccessibilityUIElement::isDecrementActionSupported()
{
BEGIN_AX_OBJC_EXCEPTIONS
return [actionNames() containsObject:NSAccessibilityDecrementAction];
END_AX_OBJC_EXCEPTIONS
return false;
}
bool AccessibilityUIElement::isAtomicLiveRegion() const
{
return boolAttributeValueNS(@"AXARIAAtomic");
}
bool AccessibilityUIElement::isBusy() const
{
return boolAttributeValueNS(@"AXElementBusy");
}
bool AccessibilityUIElement::isEnabled()
{
return boolAttributeValueNS(NSAccessibilityEnabledAttribute);
}
bool AccessibilityUIElement::isRequired() const
{
return boolAttributeValueNS(@"AXRequired");
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::focusedElement() const
{
BEGIN_AX_OBJC_EXCEPTIONS
if (auto focus = attributeValue(NSAccessibilityFocusedUIElementAttribute))
return AccessibilityUIElement::create(focus.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::isFocused() const
{
return boolAttributeValueNS(NSAccessibilityFocusedAttribute);
}
bool AccessibilityUIElement::isSelected() const
{
auto value = attributeValue(NSAccessibilitySelectedAttribute);
if ([value isKindOfClass:[NSNumber class]])
return [value boolValue];
return false;
}
bool AccessibilityUIElement::isSelectedOptionActive() const
{
return false;
}
bool AccessibilityUIElement::isIndeterminate() const
{
return boolAttributeValueNS(@"AXIsIndeterminate");
}
bool AccessibilityUIElement::isExpanded() const
{
return boolAttributeValueNS(NSAccessibilityExpandedAttribute);
}
bool AccessibilityUIElement::isChecked() const
{
// On the Mac, intValue()==1 if a a checkable control is checked.
return intValue() == 1;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::currentStateValue() const
{
return stringAttributeValueNS(NSAccessibilityARIACurrentAttribute);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::sortDirection() const
{
return stringAttributeValueNS(NSAccessibilitySortDirectionAttribute);
}
int AccessibilityUIElement::hierarchicalLevel() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(NSAccessibilityDisclosureLevelAttribute);
if ([value isKindOfClass:[NSNumber class]])
return [value intValue];
END_AX_OBJC_EXCEPTIONS
return 0;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::classList() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(@"AXDOMClassList");
if (![value isKindOfClass:[NSArray class]])
return nullptr;
NSMutableString *classList = [NSMutableString string];
NSInteger length = [value count];
for (NSInteger k = 0; k < length; ++k) {
[classList appendString:[value objectAtIndex:k]];
if (k < length - 1)
[classList appendString:@", "];
}
return [classList createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::speakAs()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(@"AXDRTSpeechAttribute");
if ([value isKindOfClass:[NSArray class]])
return [[(NSArray *)value componentsJoinedByString:@", "] createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::isGrabbed() const
{
return boolAttributeValueNS(NSAccessibilityGrabbedAttribute);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::ariaDropEffects() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(NSAccessibilityDropEffectsAttribute);
if (![value isKindOfClass:[NSArray class]])
return nullptr;
NSMutableString *dropEffects = [NSMutableString string];
NSInteger length = [value count];
for (NSInteger k = 0; k < length; ++k) {
[dropEffects appendString:[value objectAtIndex:k]];
if (k < length - 1)
[dropEffects appendString:@","];
}
return [dropEffects createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
// parameterized attributes
int AccessibilityUIElement::lineForIndex(int index)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValueForParameter(NSAccessibilityLineForIndexParameterizedAttribute, @(index));
if ([value isKindOfClass:[NSNumber class]])
return [(NSNumber *)value intValue];
END_AX_OBJC_EXCEPTIONS
return -1;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForLine(int line)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValueForParameter(NSAccessibilityRangeForLineParameterizedAttribute, @(line));
if ([value isKindOfClass:[NSValue class]])
return [NSStringFromRange([value rangeValue]) createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForPosition(int x, int y)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValueForParameter(NSAccessibilityRangeForPositionParameterizedAttribute, [NSValue valueWithPoint:NSMakePoint(x, y)]);
if ([value isKindOfClass:[NSValue class]])
return [NSStringFromRange([value rangeValue]) createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
static NSMutableString* makeBoundsDescription(NSRect rect, bool exposePosition)
{
return [NSMutableString stringWithFormat:@"{{%f, %f}, {%f, %f}}", exposePosition ? rect.origin.x : -1.0f, exposePosition ? rect.origin.y : -1.0f, rect.size.width, rect.size.height];
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::boundsForRange(unsigned location, unsigned length)
{
NSRange range = NSMakeRange(location, length);
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValueForParameter(NSAccessibilityBoundsForRangeParameterizedAttribute, [NSValue valueWithRange:range]);
NSRect rect = NSMakeRect(0,0,0,0);
if ([value isKindOfClass:[NSValue class]])
rect = [value rectValue];
// don't return position information because it is platform dependent
NSMutableString* boundsDescription = makeBoundsDescription(rect, false /* exposePosition */);
return [boundsDescription createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::boundsForRangeWithPagePosition(unsigned location, unsigned length)
{
NSRange range = NSMakeRange(location, length);
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValueForParameter(@"_AXPageBoundsForTextMarkerRange", [NSValue valueWithRange:range]);
NSRect rect = NSMakeRect(0, 0, 0, 0);
if ([value isKindOfClass:[NSValue class]])
rect = [value rectValue];
NSMutableString* boundsDescription = makeBoundsDescription(rect, true /* exposePosition */);
return [boundsDescription createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForRange(unsigned location, unsigned length)
{
NSRange range = NSMakeRange(location, length);
BEGIN_AX_OBJC_EXCEPTIONS
auto string = attributeValueForParameter(NSAccessibilityStringForRangeParameterizedAttribute, [NSValue valueWithRange:range]);
if (![string isKindOfClass:[NSString class]])
return nullptr;
return [string createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForRange(unsigned location, unsigned length)
{
NSRange range = NSMakeRange(location, length);
BEGIN_AX_OBJC_EXCEPTIONS
auto string = attributeValueForParameter(NSAccessibilityAttributedStringForRangeParameterizedAttribute, [NSValue valueWithRange:range]);
if (![string isKindOfClass:[NSAttributedString class]])
return nullptr;
NSString* stringWithAttrs = [string description];
return [stringWithAttrs createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::attributedStringRangeIsMisspelled(unsigned location, unsigned length)
{
NSRange range = NSMakeRange(location, length);
BEGIN_AX_OBJC_EXCEPTIONS
auto string = attributeValueForParameter(NSAccessibilityAttributedStringForRangeParameterizedAttribute, [NSValue valueWithRange:range]);
if (![string isKindOfClass:[NSAttributedString class]])
return false;
NSDictionary* attrs = [string attributesAtIndex:0 effectiveRange:nil];
BOOL misspelled = [[attrs objectForKey:NSAccessibilityMisspelledTextAttribute] boolValue];
#if PLATFORM(MAC)
if (misspelled)
misspelled = [[attrs objectForKey:NSAccessibilityMarkedMisspelledTextAttribute] boolValue];
#endif
return misspelled;
END_AX_OBJC_EXCEPTIONS
return false;
}
unsigned AccessibilityUIElement::uiElementCountForSearchPredicate(JSContextRef context, AccessibilityUIElement *startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
{
BEGIN_AX_OBJC_EXCEPTIONS
NSDictionary *parameter = searchPredicateForSearchCriteria(context, startElement, nullptr, isDirectionNext, UINT_MAX, searchKey, searchText, visibleOnly, immediateDescendantsOnly);
auto value = attributeValueForParameter(@"AXUIElementsForSearchPredicate", parameter);
if ([value isKindOfClass:[NSArray class]])
return [value count];
END_AX_OBJC_EXCEPTIONS
return 0;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementForSearchPredicate(JSContextRef context, AccessibilityUIElement *startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
{
BEGIN_AX_OBJC_EXCEPTIONS
NSDictionary *parameter = searchPredicateForSearchCriteria(context, startElement, nullptr, isDirectionNext, 1, searchKey, searchText, visibleOnly, immediateDescendantsOnly);
auto searchResults = attributeValueForParameter(@"AXUIElementsForSearchPredicate", parameter);
if (![searchResults isKindOfClass:[NSArray class]] || ![searchResults count])
return nullptr;
id result = [searchResults firstObject];
if ([result isKindOfClass:NSDictionary.class]) {
RELEASE_ASSERT([result objectForKey:@"AXSearchResultElement"]);
return AccessibilityUIElement::create([result objectForKey:@"AXSearchResultElement"]);
}
return AccessibilityUIElement::create(result);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::selectTextWithCriteria(JSContextRef context, JSStringRef ambiguityResolution, JSValueRef searchStrings, JSStringRef replacementString, JSStringRef activity)
{
BEGIN_AX_OBJC_EXCEPTIONS
NSDictionary *parameterizedAttribute = selectTextParameterizedAttributeForCriteria(context, ambiguityResolution, searchStrings, replacementString, activity);
auto result = attributeValueForParameter(@"AXSelectTextWithCriteria", parameterizedAttribute);
if ([result isKindOfClass:[NSString class]])
return [result.get() createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
#if PLATFORM(MAC)
JSValueRef AccessibilityUIElement::searchTextWithCriteria(JSContextRef context, JSValueRef searchStrings, JSStringRef startFrom, JSStringRef direction)
{
BEGIN_AX_OBJC_EXCEPTIONS
NSDictionary *parameterizedAttribute = searchTextParameterizedAttributeForCriteria(context, searchStrings, startFrom, direction);
auto result = attributeValueForParameter(@"AXSearchTextWithCriteria", parameterizedAttribute);
if ([result isKindOfClass:[NSArray class]])
return makeJSArray(context, makeVector<RefPtr<AccessibilityTextMarkerRange>>(result.get()));
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSValueRef AccessibilityUIElement::performTextOperation(JSContextRef context, JSStringRef operationType, JSValueRef markerRanges, JSValueRef replacementStrings, bool shouldSmartReplace)
{
BEGIN_AX_OBJC_EXCEPTIONS
NSDictionary *parameterizedAttribute = textOperationParameterizedAttribute(context, operationType, markerRanges, replacementStrings, shouldSmartReplace);
auto result = attributeValueForParameter(@"AXTextOperation", parameterizedAttribute);
if ([result isKindOfClass:[NSArray class]])
return makeValueRefForValue(context, result.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
#endif
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders()
{
// not yet defined in AppKit... odd
BEGIN_AX_OBJC_EXCEPTIONS
auto columnHeaders = attributeValue(@"AXColumnHeaderUIElements");
auto columnHeadersVector = makeVector<RefPtr<AccessibilityUIElement>>(columnHeaders.get());
return descriptionOfElements(columnHeadersVector);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto rowHeaders = attributeValue(@"AXRowHeaderUIElements");
auto rowHeadersVector = makeVector<RefPtr<AccessibilityUIElement>>(rowHeaders.get());
return descriptionOfElements(rowHeadersVector);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto columns = attributeValue(NSAccessibilityColumnsAttribute);
auto columnsVector = makeVector<RefPtr<AccessibilityUIElement>>(columns.get());
return descriptionOfElements(columnsVector);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSValueRef AccessibilityUIElement::columns(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
return makeJSArray(context, makeVector<RefPtr<AccessibilityUIElement>>(attributeValue(NSAccessibilityColumnsAttribute).get()));
END_AX_OBJC_EXCEPTIONS
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto rows = attributeValue(NSAccessibilityRowsAttribute);
auto rowsVector = makeVector<RefPtr<AccessibilityUIElement>>(rows.get());
return descriptionOfElements(rowsVector);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto cells = attributeValue(@"AXVisibleCells");
auto cellsVector = makeVector<RefPtr<AccessibilityUIElement>>(cells.get());
return descriptionOfElements(cellsVector);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfHeader()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto header = attributeValue(NSAccessibilityHeaderAttribute);
if (!header)
return [@"" createJSStringRef];
Vector<RefPtr<AccessibilityUIElement>> headerVector;
headerVector.append(AccessibilityUIElement::create(header.get()));
return descriptionOfElements(headerVector);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
int AccessibilityUIElement::rowCount()
{
return arrayAttributeCount(NSAccessibilityRowsAttribute);
}
int AccessibilityUIElement::columnCount()
{
return arrayAttributeCount(NSAccessibilityColumnsAttribute);
}
int AccessibilityUIElement::indexInTable()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto indexNumber = attributeValue(NSAccessibilityIndexAttribute);
if (indexNumber)
return [indexNumber intValue];
END_AX_OBJC_EXCEPTIONS
return -1;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rowIndexRange()
{
NSRange range = NSMakeRange(0, 0);
BEGIN_AX_OBJC_EXCEPTIONS
auto indexRange = attributeValue(@"AXRowIndexRange");
if (indexRange)
range = [indexRange rangeValue];
NSMutableString *rangeDescription = [NSMutableString stringWithFormat:@"{%lu, %lu}", static_cast<unsigned long>(range.location), static_cast<unsigned long>(range.length)];
return [rangeDescription createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::columnIndexRange()
{
NSRange range = NSMakeRange(0, 0);
BEGIN_AX_OBJC_EXCEPTIONS
auto indexRange = attributeValue(@"AXColumnIndexRange");
if (indexRange)
range = [indexRange rangeValue];
NSMutableString *rangeDescription = [NSMutableString stringWithFormat:@"{%lu, %lu}", static_cast<unsigned long>(range.location), static_cast<unsigned long>(range.length)];
return [rangeDescription createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::cellForColumnAndRow(unsigned col, unsigned row)
{
NSArray *colRowArray = @[@(col), @(row)];
BEGIN_AX_OBJC_EXCEPTIONS
if (auto cell = attributeValueForParameter(@"AXCellForColumnAndRow", colRowArray))
return AccessibilityUIElement::create(cell.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::horizontalScrollbar() const
{
if (!m_element)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
if (id scrollbar = attributeValue(NSAccessibilityHorizontalScrollBarAttribute).get())
return AccessibilityUIElement::create(scrollbar);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::verticalScrollbar() const
{
if (!m_element)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
if (id scrollbar = attributeValue(NSAccessibilityVerticalScrollBarAttribute).get())
return AccessibilityUIElement::create(scrollbar);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
void AccessibilityUIElement::scrollToMakeVisible()
{
performAction(@"AXScrollToVisible");
}
void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
{
NSPoint point = NSMakePoint(x, y);
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThread([point, this] {
[m_element _accessibilityScrollToGlobalPoint:point];
});
END_AX_OBJC_EXCEPTIONS
}
void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
{
NSRect rect = NSMakeRect(x, y, width, height);
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThread([rect, this] {
[m_element _accessibilityScrollToMakeVisibleWithSubFocus:rect];
});
END_AX_OBJC_EXCEPTIONS
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::selectedText()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto string = attributeValue(@"AXSelectedText");
if (![string isKindOfClass:[NSString class]])
return nullptr;
return [string createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::selectedTextRange()
{
NSRange range = NSMakeRange(NSNotFound, 0);
BEGIN_AX_OBJC_EXCEPTIONS
auto indexRange = attributeValue(NSAccessibilitySelectedTextRangeAttribute);
if (indexRange)
range = [indexRange rangeValue];
NSString *rangeDescription = [NSString stringWithFormat:@"{%lu, %lu}", static_cast<unsigned long>(range.location), static_cast<unsigned long>(range.length)];
return [rangeDescription createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::intersectionWithSelectionRange()
{
BEGIN_AX_OBJC_EXCEPTIONS
if (auto rangeAttribute = attributeValue(NSAccessibilityIntersectionWithSelectionRangeAttribute)) {
NSRange range = [rangeAttribute rangeValue];
NSString *rangeDescription = [NSString stringWithFormat:@"{%lu, %lu}", static_cast<unsigned long>(range.location), static_cast<unsigned long>(range.length)];
return [rangeDescription createJSStringRef];
}
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length)
{
NSRange textRange = NSMakeRange(location, length);
NSValue *textRangeValue = [NSValue valueWithRange:textRange];
setAttributeValue(m_element.getAutoreleased(), NSAccessibilitySelectedTextRangeAttribute, textRangeValue);
return true;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::textInputMarkedRange() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(NSAccessibilityTextInputMarkedRangeAttribute);
if (value)
return [NSStringFromRange([value rangeValue]) createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::dismiss()
{
return performAction(@"AXDismissAction");
}
bool AccessibilityUIElement::setSelectedTextMarkerRange(AccessibilityTextMarkerRange* markerRange)
{
if (!markerRange)
return false;
setAttributeValue(m_element.getAutoreleased(), NSAccessibilitySelectedTextMarkerRangeAttribute, markerRange->platformTextMarkerRange());
return true;
}
void AccessibilityUIElement::increment()
{
performAction(@"AXSyncIncrementAction");
}
void AccessibilityUIElement::decrement()
{
performAction(@"AXSyncDecrementAction");
}
void AccessibilityUIElement::asyncIncrement()
{
performAction(NSAccessibilityIncrementAction);
}
void AccessibilityUIElement::asyncDecrement()
{
performAction(NSAccessibilityDecrementAction);
}
void AccessibilityUIElement::showMenu()
{
performAction(NSAccessibilityShowMenuAction);
}
void AccessibilityUIElement::press()
{
performAction(NSAccessibilityPressAction);
}
void AccessibilityUIElement::syncPress()
{
performAction(@"AXSyncPressAction");
}
void AccessibilityUIElement::setSelectedChild(AccessibilityUIElement* element) const
{
NSArray* array = element ? @[element->platformUIElement()] : @[];
setAttributeValue(m_element.getAutoreleased(), NSAccessibilitySelectedChildrenAttribute, array);
}
void AccessibilityUIElement::setSelectedChildAtIndex(unsigned index) const
{
RefPtr<AccessibilityUIElement> element = const_cast<AccessibilityUIElement*>(this)->childAtIndex(index);
if (!element)
return;
auto selectedChildren = attributeValue(NSAccessibilitySelectedChildrenAttribute);
NSArray *array = @[element->platformUIElement()];
if (selectedChildren)
array = [selectedChildren arrayByAddingObjectsFromArray:array];
setAttributeValue(m_element.getAutoreleased(), NSAccessibilitySelectedChildrenAttribute, array, true);
}
void AccessibilityUIElement::removeSelectionAtIndex(unsigned index) const
{
RefPtr<AccessibilityUIElement> element = const_cast<AccessibilityUIElement*>(this)->childAtIndex(index);
if (!element)
return;
auto selectedChildren = attributeValue(NSAccessibilitySelectedChildrenAttribute);
if (!selectedChildren)
return;
NSMutableArray *array = [NSMutableArray arrayWithArray:selectedChildren.get()];
[array removeObject:element->platformUIElement()];
setAttributeValue(m_element.getAutoreleased(), NSAccessibilitySelectedChildrenAttribute, array, true);
}
void AccessibilityUIElement::clearSelectedChildren() const
{
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::accessibilityValue() const
{
return createJSString();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::url()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto url = attributeValue(NSAccessibilityURLAttribute);
if ([url isKindOfClass:[NSURL class]])
return [[url absoluteString] createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::addNotificationListener(JSContextRef context, JSValueRef functionCallback)
{
if (!functionCallback)
return false;
// Mac programmers should not be adding more than one notification listener per element.
// Other platforms may be different.
if (m_notificationHandler)
return false;
m_notificationHandler = adoptNS([[AccessibilityNotificationHandler alloc] initWithContext:context]);
[m_notificationHandler setPlatformElement:platformUIElement()];
[m_notificationHandler setCallback:functionCallback];
[m_notificationHandler startObserving];
return true;
}
bool AccessibilityUIElement::removeNotificationListener()
{
// Mac programmers should not be trying to remove a listener that's already removed.
ASSERT(m_notificationHandler);
[m_notificationHandler stopObserving];
m_notificationHandler = nil;
return true;
}
bool AccessibilityUIElement::isFocusable() const
{
return isAttributeSettableNS(NSAccessibilityFocusedAttribute);
}
bool AccessibilityUIElement::isSelectable() const
{
return isAttributeSettableNS(NSAccessibilitySelectedAttribute);
}
bool AccessibilityUIElement::isMultiSelectable() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(@"AXIsMultiSelectable");
if ([value isKindOfClass:[NSNumber class]])
return [value boolValue];
END_AX_OBJC_EXCEPTIONS
return false;
}
bool AccessibilityUIElement::isVisible() const
{
return false;
}
bool AccessibilityUIElement::isOnScreen() const
{
auto value = attributeValue(@"AXIsOnScreen");
if ([value isKindOfClass:[NSNumber class]])
return [value boolValue];
return false;
}
bool AccessibilityUIElement::isOffScreen() const
{
return false;
}
bool AccessibilityUIElement::isCollapsed() const
{
return false;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::embeddedImageDescription() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = descriptionOfValue(attributeValue(@"AXEmbeddedImageDescription").get());
return concatenateAttributeAndValue(@"AXEmbeddedImageDescription", value.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSValueRef AccessibilityUIElement::imageOverlayElements(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
return makeJSArray(context, makeVector<RefPtr<AccessibilityUIElement>>(attributeValue(@"AXImageOverlayElements").get()));
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::isIgnored() const
{
BOOL result = NO;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([&result, this] {
result = m_element ? [m_element accessibilityIsIgnored] : YES;
});
END_AX_OBJC_EXCEPTIONS
return result;
}
bool AccessibilityUIElement::isSingleLine() const
{
return false;
}
bool AccessibilityUIElement::isMultiLine() const
{
return false;
}
bool AccessibilityUIElement::hasPopup() const
{
return boolAttributeValueNS(@"AXHasPopup");
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::popupValue() const
{
if (auto result = stringAttributeValueNS(@"AXPopupValue"))
return result;
return [@"false" createJSStringRef];
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::focusableAncestor()
{
BEGIN_AX_OBJC_EXCEPTIONS
if (id ancestor = attributeValue(@"AXFocusableAncestor").get())
return AccessibilityUIElement::create(ancestor);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::editableAncestor()
{
BEGIN_AX_OBJC_EXCEPTIONS
if (id ancestor = attributeValue(@"AXEditableAncestor").get())
return AccessibilityUIElement::create(ancestor);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::highestEditableAncestor()
{
BEGIN_AX_OBJC_EXCEPTIONS
if (id ancestor = attributeValue(@"AXHighestEditableAncestor").get())
return AccessibilityUIElement::create(ancestor);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::isInDescriptionListDetail() const
{
return boolAttributeValueNS(@"AXIsInDescriptionListDetail");
}
bool AccessibilityUIElement::isInDescriptionListTerm() const
{
return boolAttributeValueNS(@"AXIsInDescriptionListTerm");
}
bool AccessibilityUIElement::isInCell() const
{
return boolAttributeValueNS(@"AXIsInCell");
}
bool AccessibilityUIElement::isInTable() const
{
return boolAttributeValueNS(@"_AXIsInTable");
}
void AccessibilityUIElement::takeFocus()
{
setAttributeValue(m_element.getAutoreleased(), NSAccessibilityFocusedAttribute, @YES);
}
void AccessibilityUIElement::takeSelection()
{
}
void AccessibilityUIElement::addSelection()
{
}
void AccessibilityUIElement::removeSelection()
{
}
// Text markers
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::lineTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXLineTextMarkerRangeForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::rightLineTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXRightLineTextMarkerRangeForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::leftLineTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXLeftLineTextMarkerRangeForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousLineStartTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto marker = attributeValueForParameter(@"AXPreviousLineStartTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(marker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextLineEndTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto marker = attributeValueForParameter(@"AXNextLineEndTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(marker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
int AccessibilityUIElement::lineIndexForTextMarker(AccessibilityTextMarker* marker) const
{
if (!marker)
return -1;
BEGIN_AX_OBJC_EXCEPTIONS
return [attributeValueForParameter(@"AXLineForTextMarker", marker->platformTextMarker()) intValue];
END_AX_OBJC_EXCEPTIONS
return -1;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::styleTextMarkerRangeForTextMarker(AccessibilityTextMarker* marker)
{
if (!marker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXStyleTextMarkerRangeForTextMarker", marker->platformTextMarker());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForSearchPredicate(JSContextRef context, AccessibilityTextMarkerRange *startRange, bool forward, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
{
BEGIN_AX_OBJC_EXCEPTIONS
NSDictionary *parameter = searchPredicateForSearchCriteria(context, nullptr, startRange, forward, 1, searchKey, searchText, visibleOnly, immediateDescendantsOnly);
auto searchResults = attributeValueForParameter(@"AXRangesForSearchPredicate", parameter);
if (![searchResults isKindOfClass:[NSArray class]] || ![searchResults count])
return nullptr;
id result = [searchResults firstObject];
if (![result isKindOfClass:NSDictionary.class])
return nullptr;
id rangeRef = [result objectForKey:@"AXSearchResultRange"];
if (rangeRef && CFGetTypeID((__bridge CFTypeRef)rangeRef) == AXTextMarkerRangeGetTypeID())
return AccessibilityTextMarkerRange::create(rangeRef);
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::misspellingTextMarkerRange(AccessibilityTextMarkerRange* start, bool forward)
{
BEGIN_AX_OBJC_EXCEPTIONS
NSDictionary *parameters = misspellingSearchParameterizedAttributeForCriteria(start, forward);
auto textMarkerRange = attributeValueForParameter(@"AXMisspellingTextMarkerRange", parameters);
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForElement(AccessibilityUIElement* element)
{
if (!element)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXTextMarkerRangeForUIElement", element->platformUIElement());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
int AccessibilityUIElement::textMarkerRangeLength(AccessibilityTextMarkerRange* range)
{
if (!range)
return 0;
BEGIN_AX_OBJC_EXCEPTIONS
auto lengthValue = attributeValueForParameter(@"AXLengthForTextMarkerRange", range->platformTextMarkerRange());
return [lengthValue intValue];
END_AX_OBJC_EXCEPTIONS
return 0;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto previousMarker = attributeValueForParameter(@"AXPreviousTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(previousMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto nextMarker = attributeValueForParameter(@"AXNextTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(nextMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForLine(long lineIndex)
{
if (lineIndex < 0)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXTextMarkerRangeForLine", @(static_cast<unsigned>(lineIndex)));
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForTextMarkerRange(AccessibilityTextMarkerRange* markerRange)
{
if (!markerRange)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textString = attributeValueForParameter(@"AXStringForTextMarkerRange", markerRange->platformTextMarkerRange());
return [textString createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rectsForTextMarkerRange(AccessibilityTextMarkerRange*, JSStringRef)
{
// Not implemented on macOS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForMarkers(AccessibilityTextMarker* startMarker, AccessibilityTextMarker* endMarker)
{
BEGIN_AX_OBJC_EXCEPTIONS
if (!startMarker->platformTextMarker() || !endMarker->platformTextMarker())
return nullptr;
NSArray *textMarkers = @[startMarker->platformTextMarker(), endMarker->platformTextMarker()];
auto textMarkerRange = attributeValueForParameter(@"AXTextMarkerRangeForTextMarkers", textMarkers);
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForUnorderedMarkers(AccessibilityTextMarker* startMarker, AccessibilityTextMarker* endMarker)
{
BEGIN_AX_OBJC_EXCEPTIONS
if (!startMarker->platformTextMarker() || !endMarker->platformTextMarker())
return nullptr;
NSArray *textMarkers = @[startMarker->platformTextMarker(), endMarker->platformTextMarker()];
auto textMarkerRange = attributeValueForParameter(@"AXTextMarkerRangeForUnorderedTextMarkers", textMarkers);
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForRange(unsigned location, unsigned length)
{
BEGIN_AX_OBJC_EXCEPTIONS
return AccessibilityTextMarkerRange::create(attributeValueForParameter(@"_AXTextMarkerRangeForNSRange",
[NSValue valueWithRange:NSMakeRange(location, length)]).get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::selectedTextMarkerRange()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValue(NSAccessibilitySelectedTextMarkerRangeAttribute);
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
void AccessibilityUIElement::resetSelectedTextMarkerRange()
{
auto start = attributeValue(@"AXStartTextMarker");
if (!start)
return;
NSArray *textMarkers = @[start.get(), start.get()];
auto textMarkerRange = attributeValueForParameter(@"AXTextMarkerRangeForUnorderedTextMarkers", textMarkers);
if (!textMarkerRange)
return;
setAttributeValue(m_element.getAutoreleased(), NSAccessibilitySelectedTextMarkerRangeAttribute, textMarkerRange.get(), true);
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textInputMarkedTextMarkerRange() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValue(NSAccessibilityTextInputMarkedTextMarkerRangeAttribute);
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range)
{
if (!range)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarker = attributeValueForParameter(@"_AXStartTextMarkerForTextMarkerRange", range->platformTextMarkerRange());
return AccessibilityTextMarker::create(textMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range)
{
if (!range)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarker = attributeValueForParameter(@"_AXEndTextMarkerForTextMarkerRange", range->platformTextMarkerRange());
return AccessibilityTextMarker::create(textMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForBounds(int x, int y, int width, int height)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarker = attributeValueForParameter(NSAccessibilityEndTextMarkerForBoundsParameterizedAttribute,
[NSValue valueWithRect:NSMakeRect(x, y, width, height)]);
return AccessibilityTextMarker::create(textMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::replaceTextInRange(JSStringRef string, int location, int length)
{
bool result = false;
BEGIN_AX_OBJC_EXCEPTIONS
AccessibilityUIElement::s_controller->executeOnAXThreadAndWait([text = [NSString stringWithJSStringRef:string], range = NSMakeRange(location, length), this, &result] {
result = [m_element accessibilityReplaceRange:range withText:text];
});
END_AX_OBJC_EXCEPTIONS
return result;
}
bool AccessibilityUIElement::insertText(JSStringRef text)
{
bool result = false;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([text = [NSString stringWithJSStringRef:text], &result, this] {
result = [m_element accessibilityInsertText:text];
});
END_AX_OBJC_EXCEPTIONS
return result;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForBounds(int x, int y, int width, int height)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarker = attributeValueForParameter(NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute,
[NSValue valueWithRect:NSMakeRect(x, y, width, height)]);
return AccessibilityTextMarker::create(textMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForPoint(int x, int y)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarker = attributeValueForParameter(@"AXTextMarkerForPosition", [NSValue valueWithPoint:NSMakePoint(x, y)]);
return AccessibilityTextMarker::create(textMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::accessibilityElementForTextMarker(AccessibilityTextMarker* marker)
{
if (!marker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto uiElement = attributeValueForParameter(@"AXUIElementForTextMarker", marker->platformTextMarker());
if (uiElement)
return AccessibilityUIElement::create(uiElement.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
static NSString *descriptionForColor(CGColorRef color)
{
// This is a hack to get an OK description for a CGColor by crudely parsing its debug description, e.g.:
//
// <CGColor 0x13bf07670> <CGColorSpace 0x12be0ce40> [ (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1)] ( 0 0 0 0 )
// Ideally we convert it to a WebCore::Color and then call serializationForCSS(const Color&), but I can't
// get that to link succesfully, despite these symbols being WEBCORE_EXPORTed.
auto string = adoptNS([[NSMutableString alloc] init]);
[string appendFormat:@"%@", color];
NSArray *stringComponents = [string componentsSeparatedByString:@">"];
if (stringComponents.count) {
NSString *bracketsRemovedString = [[stringComponents objectAtIndex:stringComponents.count - 1] stringByReplacingOccurrencesOfString:@"]" withString:@""];
return [bracketsRemovedString stringByReplacingOccurrencesOfString:@"headroom = 1.000000 " withString:@""];
}
return nil;
}
static void appendColorDescription(RetainPtr<NSMutableString> string, NSString* attributeKey, NSDictionary<NSString *, id> *attributes)
{
id color = [attributes objectForKey:attributeKey];
if (!color)
return;
if (CFGetTypeID(color) == CGColorGetTypeID())
[string appendFormat:@"%@:%@\n", attributeKey, descriptionForColor((CGColorRef)color)];
}
static JSRetainPtr<JSStringRef> createJSStringRef(id string, bool includeDidSpellCheck)
{
auto mutableString = adoptNS([[NSMutableString alloc] init]);
id attributeEnumerationBlock = ^(NSDictionary<NSString *, id> *attributes, NSRange range, BOOL *stop) {
[mutableString appendFormat:@"Attributes in range %@:\n", NSStringFromRange(range)];
BOOL misspelled = [[attributes objectForKey:NSAccessibilityMisspelledTextAttribute] boolValue];
if (misspelled)
misspelled = [[attributes objectForKey:NSAccessibilityMarkedMisspelledTextAttribute] boolValue];
if (misspelled)
[mutableString appendString:@"Misspelled, "];
if (includeDidSpellCheck) {
BOOL didSpellCheck = [[attributes objectForKey:@"AXDidSpellCheck"] boolValue];
[mutableString appendFormat:@"AXDidSpellCheck: %d\n", didSpellCheck];
}
id font = [attributes objectForKey:(__bridge id)kAXFontTextAttribute];
if (font)
[mutableString appendFormat:@"%@: %@\n", (__bridge id)kAXFontTextAttribute, font];
appendColorDescription(mutableString, NSAccessibilityForegroundColorTextAttribute, attributes);
appendColorDescription(mutableString, NSAccessibilityBackgroundColorTextAttribute, attributes);
int scriptState = [[attributes objectForKey:NSAccessibilitySuperscriptTextAttribute] intValue];
if (scriptState == -1) {
// -1 == subscript
[mutableString appendFormat:@"%@: -1\n", NSAccessibilitySuperscriptTextAttribute];
} else if (scriptState == 1) {
// 1 == superscript
[mutableString appendFormat:@"%@: 1\n", NSAccessibilitySuperscriptTextAttribute];
}
BOOL hasTextShadow = [[attributes objectForKey:NSAccessibilityShadowTextAttribute] boolValue];
if (hasTextShadow)
[mutableString appendFormat:@"%@: YES\n", NSAccessibilityShadowTextAttribute];
BOOL hasUnderline = [[attributes objectForKey:NSAccessibilityUnderlineTextAttribute] boolValue];
if (hasUnderline) {
[mutableString appendFormat:@"%@: YES\n", NSAccessibilityUnderlineTextAttribute];
appendColorDescription(mutableString, NSAccessibilityUnderlineColorTextAttribute, attributes);
}
BOOL hasLinethrough = [[attributes objectForKey:NSAccessibilityStrikethroughTextAttribute] boolValue];
if (hasLinethrough) {
[mutableString appendFormat:@"%@: YES\n", NSAccessibilityStrikethroughTextAttribute];
appendColorDescription(mutableString, NSAccessibilityStrikethroughColorTextAttribute, attributes);
}
id attachment = [attributes objectForKey:NSAccessibilityAttachmentTextAttribute];
if (attachment)
[mutableString appendFormat:@"%@: {present}\n", NSAccessibilityAttachmentTextAttribute];
};
[string enumerateAttributesInRange:NSMakeRange(0, [string length]) options:(NSAttributedStringEnumerationOptions)0 usingBlock:attributeEnumerationBlock];
[mutableString appendString:[string string]];
return [mutableString createJSStringRef];
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRange(AccessibilityTextMarkerRange* markerRange)
{
if (!markerRange)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto string = attributeValueForParameter(@"AXAttributedStringForTextMarkerRange", markerRange->platformTextMarkerRange());
if ([string isKindOfClass:[NSAttributedString class]])
return createJSStringRef(string.get(), /* IncludeDidSpellCheck */ false);
END_AX_OBJC_EXCEPTIONS
return nil;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRangeWithDidSpellCheck(AccessibilityTextMarkerRange* markerRange)
{
if (!markerRange)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto string = attributeValueForParameter(@"AXAttributedStringForTextMarkerRange", markerRange->platformTextMarkerRange());
if ([string isKindOfClass:[NSAttributedString class]])
return createJSStringRef(string.get(), /* IncludeDidSpellCheck */ true);
END_AX_OBJC_EXCEPTIONS
return nil;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRangeWithOptions(AccessibilityTextMarkerRange* markerRange, bool includeSpellCheck)
{
if (!markerRange || !markerRange->platformTextMarkerRange())
return nullptr;
id parameter = nil;
if (includeSpellCheck)
parameter = @{ @"AXSpellCheck": includeSpellCheck ? @YES : @NO, @"AXTextMarkerRange": markerRange->platformTextMarkerRange() };
else
parameter = markerRange->platformTextMarkerRange();
BEGIN_AX_OBJC_EXCEPTIONS
auto string = attributeValueForParameter(@"AXAttributedStringForTextMarkerRangeWithOptions", parameter);
if ([string isKindOfClass:[NSAttributedString class]])
return createJSStringRef(string.get(), /* IncludeDidSpellCheck */ false);
END_AX_OBJC_EXCEPTIONS
return nil;
}
bool AccessibilityUIElement::attributedStringForTextMarkerRangeContainsAttribute(JSStringRef attribute, AccessibilityTextMarkerRange* range)
{
if (!range)
return false;
BEGIN_AX_OBJC_EXCEPTIONS
auto string = attributeValueForParameter(@"AXAttributedStringForTextMarkerRange", range->platformTextMarkerRange());
if (![string isKindOfClass:[NSAttributedString class]])
return false;
NSDictionary* attrs = [string attributesAtIndex:0 effectiveRange:nil];
if ([attrs objectForKey:[NSString stringWithJSStringRef:attribute]])
return true;
END_AX_OBJC_EXCEPTIONS
return false;
}
int AccessibilityUIElement::indexForTextMarker(AccessibilityTextMarker* marker)
{
if (!marker)
return -1;
BEGIN_AX_OBJC_EXCEPTIONS
auto indexNumber = attributeValueForParameter(@"AXIndexForTextMarker", marker->platformTextMarker());
return [indexNumber intValue];
END_AX_OBJC_EXCEPTIONS
return -1;
}
bool AccessibilityUIElement::isTextMarkerNull(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return true;
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValueForParameter(@"AXTextMarkerIsNull", textMarker->platformTextMarker());
return [value boolValue];
END_AX_OBJC_EXCEPTIONS
return false;
}
bool AccessibilityUIElement::isTextMarkerValid(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return false;
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValueForParameter(@"AXTextMarkerIsValid", textMarker->platformTextMarker());
return [value boolValue];
END_AX_OBJC_EXCEPTIONS
return false;
}
bool AccessibilityUIElement::isTextMarkerRangeValid(AccessibilityTextMarkerRange* textMarkerRange)
{
if (!textMarkerRange)
return false;
BEGIN_AX_OBJC_EXCEPTIONS
return [[m_element accessibilityAttributeValue:@"AXTextMarkerRangeIsValid" forParameter:textMarkerRange->platformTextMarkerRange()] boolValue];
END_AX_OBJC_EXCEPTIONS
return false;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForIndex(int textIndex)
{
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarker = attributeValueForParameter(@"AXTextMarkerForIndex", [NSNumber numberWithInteger:textIndex]);
return AccessibilityTextMarker::create(textMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarker()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarker = attributeValue(@"AXStartTextMarker");
return AccessibilityTextMarker::create(textMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarker()
{
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarker = attributeValue(@"AXEndTextMarker");
return AccessibilityTextMarker::create(textMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::leftWordTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXLeftWordTextMarkerRangeForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::rightWordTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXRightWordTextMarkerRangeForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousWordStartTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto previousWordStartMarker = attributeValueForParameter(@"AXPreviousWordStartTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(previousWordStartMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextWordEndTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto nextWordEndMarker = attributeValueForParameter(@"AXNextWordEndTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(nextWordEndMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::paragraphTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXParagraphTextMarkerRangeForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousParagraphStartTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto previousParagraphStartMarker = attributeValueForParameter(@"AXPreviousParagraphStartTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(previousParagraphStartMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextParagraphEndTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto nextParagraphEndMarker = attributeValueForParameter(@"AXNextParagraphEndTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(nextParagraphEndMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::sentenceTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto textMarkerRange = attributeValueForParameter(@"AXSentenceTextMarkerRangeForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarkerRange::create(textMarkerRange.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousSentenceStartTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto previousParagraphStartMarker = attributeValueForParameter(@"AXPreviousSentenceStartTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(previousParagraphStartMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextSentenceEndTextMarkerForTextMarker(AccessibilityTextMarker* textMarker)
{
if (!textMarker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
auto nextParagraphEndMarker = attributeValueForParameter(@"AXNextSentenceEndTextMarkerForTextMarker", textMarker->platformTextMarker());
return AccessibilityTextMarker::create(nextParagraphEndMarker.get());
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::textMarkerDebugDescription(AccessibilityTextMarker* marker)
{
if (!marker)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
RetainPtr description = attributeValueForParameter(@"AXTextMarkerDebugDescription", marker->platformTextMarker());
return [description createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::textMarkerRangeDebugDescription(AccessibilityTextMarkerRange* range)
{
if (!range)
return nullptr;
BEGIN_AX_OBJC_EXCEPTIONS
RetainPtr description = attributeValueForParameter(@"AXTextMarkerRangeDebugDescription", range->platformTextMarkerRange());
return [description createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
static NSString *_convertMathMultiscriptPairsToString(NSArray *pairs)
{
__block NSMutableString *result = [NSMutableString string];
[pairs enumerateObjectsUsingBlock:^(id pair, NSUInteger index, BOOL *stop) {
for (NSString *key in pair) {
auto element = AccessibilityUIElement::create([pair objectForKey:key]);
auto subrole = element->attributeValue(NSAccessibilitySubroleAttribute);
[result appendFormat:@"\t%lu. %@ = %@\n", (unsigned long)index, key, subrole.get()];
}
}];
return result;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPostscriptsDescription() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto pairs = attributeValue(@"AXMathPostscripts");
return [_convertMathMultiscriptPairsToString(pairs.get()) createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPrescriptsDescription() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto pairs = attributeValue(@"AXMathPrescripts");
return [_convertMathMultiscriptPairsToString(pairs.get()) createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSValueRef AccessibilityUIElement::mathRootRadicand(JSContextRef context)
{
BEGIN_AX_OBJC_EXCEPTIONS
return makeJSArray(context, makeVector<RefPtr<AccessibilityUIElement>>(attributeValue(@"AXMathRootRadicand").get()));
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::pathDescription() const
{
BEGIN_AX_OBJC_EXCEPTIONS
NSMutableString *result = [NSMutableString stringWithString:@"\nStart Path\n"];
auto bezierPath = attributeValue(NSAccessibilityPathAttribute);
NSUInteger elementCount = [bezierPath elementCount];
NSPoint points[3];
for (NSUInteger i = 0; i < elementCount; i++) {
switch ([bezierPath elementAtIndex:i associatedPoints:points]) {
case NSMoveToBezierPathElement:
[result appendString:@"\tMove to point\n"];
break;
case NSLineToBezierPathElement:
[result appendString:@"\tLine to\n"];
break;
case NSCurveToBezierPathElement:
[result appendString:@"\tCurve to\n"];
break;
case NSClosePathBezierPathElement:
[result appendString:@"\tClose\n"];
break;
default:
break;
}
}
return [result createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
NSArray *AccessibilityUIElement::actionNames() const
{
NSArray *actions = nil;
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThreadAndWait([this, &actions] {
actions = [m_element accessibilityActionNames];
});
END_AX_OBJC_EXCEPTIONS
return actions;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::supportedActions() const
{
BEGIN_AX_OBJC_EXCEPTIONS
return [[actionNames() componentsJoinedByString:@","] createJSStringRef];
END_AX_OBJC_EXCEPTIONS
return nullptr;
}
bool AccessibilityUIElement::performAction(NSString *actionName) const
{
BEGIN_AX_OBJC_EXCEPTIONS
s_controller->executeOnAXThread([actionName, this] {
[m_element accessibilityPerformAction:actionName];
});
END_AX_OBJC_EXCEPTIONS
// macOS actions don't return a value.
return true;
}
bool AccessibilityUIElement::isInsertion() const
{
return false;
}
bool AccessibilityUIElement::isDeletion() const
{
return false;
}
bool AccessibilityUIElement::isFirstItemInSuggestion() const
{
return false;
}
bool AccessibilityUIElement::isLastItemInSuggestion() const
{
return false;
}
bool AccessibilityUIElement::isRemoteFrame() const
{
BEGIN_AX_OBJC_EXCEPTIONS
auto value = attributeValue(@"AXIsRemoteFrame");
if ([value isKindOfClass:[NSNumber class]])
return [value boolValue];
END_AX_OBJC_EXCEPTIONS
return false;
}
} // namespace WTR