blob: ea0616e907cd61cbf66c99761573a8c82be05ac0 [file] [log] [blame]
/*
* Copyright (C) 2008, 2009, 2010, 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.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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 "WebAccessibilityObjectWrapperMac.h"
#if ENABLE(ACCESSIBILITY) && PLATFORM(MAC)
#import "AXLogger.h"
#import "AXObjectCache.h"
#import "AccessibilityARIAGridRow.h"
#import "AccessibilityLabel.h"
#import "AccessibilityList.h"
#import "AccessibilityListBox.h"
#import "AccessibilityProgressIndicator.h"
#import "AccessibilityRenderObject.h"
#import "AccessibilityScrollView.h"
#import "AccessibilitySpinButton.h"
#import "AccessibilityTable.h"
#import "AccessibilityTableCell.h"
#import "AccessibilityTableColumn.h"
#import "AccessibilityTableRow.h"
#import "Chrome.h"
#import "ChromeClient.h"
#import "ColorMac.h"
#import "ContextMenuController.h"
#import "Editing.h"
#import "Editor.h"
#import "Font.h"
#import "FontCascade.h"
#import "Frame.h"
#import "FrameLoaderClient.h"
#import "FrameSelection.h"
#import "HTMLAnchorElement.h"
#import "HTMLAreaElement.h"
#import "HTMLFrameOwnerElement.h"
#import "HTMLImageElement.h"
#import "HTMLInputElement.h"
#import "HTMLNames.h"
#import "LocalizedStrings.h"
#import "Page.h"
#import "PluginDocument.h"
#import "PluginViewBase.h"
#import "Range.h"
#import "RenderInline.h"
#import "RenderTextControl.h"
#import "RenderView.h"
#import "RenderWidget.h"
#import "ScrollView.h"
#import "TextCheckerClient.h"
#import "TextCheckingHelper.h"
#import "TextDecorationPainter.h"
#import "TextIterator.h"
#import "VisibleUnits.h"
#import "WebCoreFrameView.h"
#import <pal/SessionID.h>
#import <pal/spi/cocoa/NSAccessibilitySPI.h>
#import <wtf/cocoa/TypeCastsCocoa.h>
#import <wtf/cocoa/VectorCocoa.h>
using namespace WebCore;
// Cell Tables
#ifndef NSAccessibilitySelectedCellsAttribute
#define NSAccessibilitySelectedCellsAttribute @"AXSelectedCells"
#endif
#ifndef NSAccessibilityVisibleCellsAttribute
#define NSAccessibilityVisibleCellsAttribute @"AXVisibleCells"
#endif
#ifndef NSAccessibilityRowIndexRangeAttribute
#define NSAccessibilityRowIndexRangeAttribute @"AXRowIndexRange"
#endif
#ifndef NSAccessibilityColumnIndexRangeAttribute
#define NSAccessibilityColumnIndexRangeAttribute @"AXColumnIndexRange"
#endif
#ifndef NSAccessibilityCellForColumnAndRowParameterizedAttribute
#define NSAccessibilityCellForColumnAndRowParameterizedAttribute @"AXCellForColumnAndRow"
#endif
#ifndef NSAccessibilityCellRole
#define NSAccessibilityCellRole @"AXCell"
#endif
#ifndef NSAccessibilityDefinitionListSubrole
#define NSAccessibilityDefinitionListSubrole @"AXDefinitionList"
#endif
// Miscellaneous
#ifndef NSAccessibilityBlockQuoteLevelAttribute
#define NSAccessibilityBlockQuoteLevelAttribute @"AXBlockQuoteLevel"
#endif
#ifndef NSAccessibilityAccessKeyAttribute
#define NSAccessibilityAccessKeyAttribute @"AXAccessKey"
#endif
#ifndef NSAccessibilityValueAutofilledAttribute
#define NSAccessibilityValueAutofilledAttribute @"AXValueAutofilled"
#endif
#ifndef NSAccessibilityValueAutofillAvailableAttribute
#define NSAccessibilityValueAutofillAvailableAttribute @"AXValueAutofillAvailable"
#endif
#ifndef NSAccessibilityValueAutofillTypeAttribute
#define NSAccessibilityValueAutofillTypeAttribute @"AXValueAutofillType"
#endif
#ifndef NSAccessibilityLanguageAttribute
#define NSAccessibilityLanguageAttribute @"AXLanguage"
#endif
#ifndef NSAccessibilityRequiredAttribute
#define NSAccessibilityRequiredAttribute @"AXRequired"
#endif
#ifndef NSAccessibilityInvalidAttribute
#define NSAccessibilityInvalidAttribute @"AXInvalid"
#endif
#ifndef NSAccessibilityOwnsAttribute
#define NSAccessibilityOwnsAttribute @"AXOwns"
#endif
#ifndef NSAccessibilityGrabbedAttribute
#define NSAccessibilityGrabbedAttribute @"AXGrabbed"
#endif
#ifndef NSAccessibilityDatetimeValueAttribute
#define NSAccessibilityDatetimeValueAttribute @"AXDateTimeValue"
#endif
#ifndef NSAccessibilityInlineTextAttribute
#define NSAccessibilityInlineTextAttribute @"AXInlineText"
#endif
#ifndef NSAccessibilityDropEffectsAttribute
#define NSAccessibilityDropEffectsAttribute @"AXDropEffects"
#endif
#ifndef NSAccessibilityARIALiveAttribute
#define NSAccessibilityARIALiveAttribute @"AXARIALive"
#endif
#ifndef NSAccessibilityARIAAtomicAttribute
#define NSAccessibilityARIAAtomicAttribute @"AXARIAAtomic"
#endif
#ifndef NSAccessibilityARIARelevantAttribute
#define NSAccessibilityARIARelevantAttribute @"AXARIARelevant"
#endif
#ifndef NSAccessibilityElementBusyAttribute
#define NSAccessibilityElementBusyAttribute @"AXElementBusy"
#endif
#ifndef NSAccessibilityARIAPosInSetAttribute
#define NSAccessibilityARIAPosInSetAttribute @"AXARIAPosInSet"
#endif
#ifndef NSAccessibilityARIASetSizeAttribute
#define NSAccessibilityARIASetSizeAttribute @"AXARIASetSize"
#endif
#ifndef NSAccessibilityLoadingProgressAttribute
#define NSAccessibilityLoadingProgressAttribute @"AXLoadingProgress"
#endif
#ifndef NSAccessibilityHasPopupAttribute
#define NSAccessibilityHasPopupAttribute @"AXHasPopup"
#endif
#ifndef NSAccessibilityPopupValueAttribute
#define NSAccessibilityPopupValueAttribute @"AXPopupValue"
#endif
#ifndef NSAccessibilityPlaceholderValueAttribute
#define NSAccessibilityPlaceholderValueAttribute @"AXPlaceholderValue"
#endif
#define NSAccessibilityTextMarkerIsValidParameterizedAttribute @"AXTextMarkerIsValid"
#define NSAccessibilityIndexForTextMarkerParameterizedAttribute @"AXIndexForTextMarker"
#define NSAccessibilityTextMarkerForIndexParameterizedAttribute @"AXTextMarkerForIndex"
#ifndef NSAccessibilityScrollToVisibleAction
#define NSAccessibilityScrollToVisibleAction @"AXScrollToVisible"
#endif
#ifndef NSAccessibilityPathAttribute
#define NSAccessibilityPathAttribute @"AXPath"
#endif
#ifndef NSAccessibilityExpandedTextValueAttribute
#define NSAccessibilityExpandedTextValueAttribute @"AXExpandedTextValue"
#endif
#ifndef NSAccessibilityIsMultiSelectableAttribute
#define NSAccessibilityIsMultiSelectableAttribute @"AXIsMultiSelectable"
#endif
#ifndef NSAccessibilityDocumentURIAttribute
#define NSAccessibilityDocumentURIAttribute @"AXDocumentURI"
#endif
#ifndef NSAccessibilityDocumentEncodingAttribute
#define NSAccessibilityDocumentEncodingAttribute @"AXDocumentEncoding"
#endif
#ifndef NSAccessibilityAriaControlsAttribute
#define NSAccessibilityAriaControlsAttribute @"AXARIAControls"
#endif
#define NSAccessibilityDOMIdentifierAttribute @"AXDOMIdentifier"
#define NSAccessibilityDOMClassListAttribute @"AXDOMClassList"
#ifndef NSAccessibilityARIACurrentAttribute
#define NSAccessibilityARIACurrentAttribute @"AXARIACurrent"
#endif
// Table/grid attributes
#ifndef NSAccessibilityARIAColumnIndexAttribute
#define NSAccessibilityARIAColumnIndexAttribute @"AXARIAColumnIndex"
#endif
#ifndef NSAccessibilityARIARowIndexAttribute
#define NSAccessibilityARIARowIndexAttribute @"AXARIARowIndex"
#endif
#ifndef NSAccessibilityARIAColumnCountAttribute
#define NSAccessibilityARIAColumnCountAttribute @"AXARIAColumnCount"
#endif
#ifndef NSAccessibilityARIARowCountAttribute
#define NSAccessibilityARIARowCountAttribute @"AXARIARowCount"
#endif
#ifndef NSAccessibilityUIElementCountForSearchPredicateParameterizedAttribute
#define NSAccessibilityUIElementCountForSearchPredicateParameterizedAttribute @"AXUIElementCountForSearchPredicate"
#endif
#ifndef NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute
#define NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute @"AXUIElementsForSearchPredicate"
#endif
// Text markers
#ifndef NSAccessibilityEndTextMarkerForBoundsParameterizedAttribute
#define NSAccessibilityEndTextMarkerForBoundsParameterizedAttribute @"AXEndTextMarkerForBounds"
#endif
#ifndef NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute
#define NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute @"AXStartTextMarkerForBounds"
#endif
#ifndef NSAccessibilityLineTextMarkerRangeForTextMarkerParameterizedAttribute
#define NSAccessibilityLineTextMarkerRangeForTextMarkerParameterizedAttribute @"AXLineTextMarkerRangeForTextMarker"
#endif
#ifndef NSAccessibilityMisspellingTextMarkerRangeParameterizedAttribute
#define NSAccessibilityMisspellingTextMarkerRangeParameterizedAttribute @"AXMisspellingTextMarkerRange"
#endif
// Text selection
#ifndef NSAccessibilitySelectTextActivity
#define NSAccessibilitySelectTextActivity @"AXSelectTextActivity"
#endif
#ifndef NSAccessibilitySelectTextActivityFindAndReplace
#define NSAccessibilitySelectTextActivityFindAndReplace @"AXSelectTextActivityFindAndReplace"
#endif
#ifndef NSAccessibilitySelectTextActivityFindAndSelect
#define NSAccessibilitySelectTextActivityFindAndSelect @"AXSelectTextActivityFindAndSelect"
#endif
#ifndef kAXSelectTextActivityFindAndCapitalize
#define kAXSelectTextActivityFindAndCapitalize @"AXSelectTextActivityFindAndCapitalize"
#endif
#ifndef kAXSelectTextActivityFindAndLowercase
#define kAXSelectTextActivityFindAndLowercase @"AXSelectTextActivityFindAndLowercase"
#endif
#ifndef kAXSelectTextActivityFindAndUppercase
#define kAXSelectTextActivityFindAndUppercase @"AXSelectTextActivityFindAndUppercase"
#endif
#ifndef NSAccessibilitySelectTextAmbiguityResolution
#define NSAccessibilitySelectTextAmbiguityResolution @"AXSelectTextAmbiguityResolution"
#endif
#ifndef NSAccessibilitySelectTextAmbiguityResolutionClosestAfterSelection
#define NSAccessibilitySelectTextAmbiguityResolutionClosestAfterSelection @"AXSelectTextAmbiguityResolutionClosestAfterSelection"
#endif
#ifndef NSAccessibilitySelectTextAmbiguityResolutionClosestBeforeSelection
#define NSAccessibilitySelectTextAmbiguityResolutionClosestBeforeSelection @"AXSelectTextAmbiguityResolutionClosestBeforeSelection"
#endif
#ifndef NSAccessibilitySelectTextAmbiguityResolutionClosestToSelection
#define NSAccessibilitySelectTextAmbiguityResolutionClosestToSelection @"AXSelectTextAmbiguityResolutionClosestToSelection"
#endif
#ifndef NSAccessibilitySelectTextReplacementString
#define NSAccessibilitySelectTextReplacementString @"AXSelectTextReplacementString"
#endif
#ifndef NSAccessibilitySelectTextSearchStrings
#define NSAccessibilitySelectTextSearchStrings @"AXSelectTextSearchStrings"
#endif
#ifndef NSAccessibilitySelectTextWithCriteriaParameterizedAttribute
#define NSAccessibilitySelectTextWithCriteriaParameterizedAttribute @"AXSelectTextWithCriteria"
#endif
// Text search
#ifndef NSAccessibilitySearchTextWithCriteriaParameterizedAttribute
/* Performs a text search with the given parameters.
Returns an NSArray of text marker ranges of the search hits.
*/
#define NSAccessibilitySearchTextWithCriteriaParameterizedAttribute @"AXSearchTextWithCriteria"
#endif
#ifndef NSAccessibilitySearchTextSearchStrings
// NSArray of strings to search for.
#define NSAccessibilitySearchTextSearchStrings @"AXSearchTextSearchStrings"
#endif
#ifndef NSAccessibilitySearchTextStartFrom
// NSString specifying the start point of the search: selection, begin or end.
#define NSAccessibilitySearchTextStartFrom @"AXSearchTextStartFrom"
#endif
#ifndef NSAccessibilitySearchTextStartFromBegin
// Value for SearchTextStartFrom.
#define NSAccessibilitySearchTextStartFromBegin @"AXSearchTextStartFromBegin"
#endif
#ifndef NSAccessibilitySearchTextStartFromSelection
// Value for SearchTextStartFrom.
#define NSAccessibilitySearchTextStartFromSelection @"AXSearchTextStartFromSelection"
#endif
#ifndef NSAccessibilitySearchTextStartFromEnd
// Value for SearchTextStartFrom.
#define NSAccessibilitySearchTextStartFromEnd @"AXSearchTextStartFromEnd"
#endif
#ifndef NSAccessibilitySearchTextDirection
// NSString specifying the direction of the search: forward, backward, closest, all.
#define NSAccessibilitySearchTextDirection @"AXSearchTextDirection"
#endif
#ifndef NSAccessibilitySearchTextDirectionForward
// Value for SearchTextDirection.
#define NSAccessibilitySearchTextDirectionForward @"AXSearchTextDirectionForward"
#endif
#ifndef NSAccessibilitySearchTextDirectionBackward
// Value for SearchTextDirection.
#define NSAccessibilitySearchTextDirectionBackward @"AXSearchTextDirectionBackward"
#endif
#ifndef NSAccessibilitySearchTextDirectionClosest
// Value for SearchTextDirection.
#define NSAccessibilitySearchTextDirectionClosest @"AXSearchTextDirectionClosest"
#endif
#ifndef NSAccessibilitySearchTextDirectionAll
// Value for SearchTextDirection.
#define NSAccessibilitySearchTextDirectionAll @"AXSearchTextDirectionAll"
#endif
// Text operations
#ifndef NSAccessibilityTextOperationParameterizedAttribute
// Performs an operation on the given text.
#define NSAccessibilityTextOperationParameterizedAttribute @"AXTextOperation"
#endif
#ifndef NSAccessibilityTextOperationMarkerRanges
// Text on which to perform operation.
#define NSAccessibilityTextOperationMarkerRanges @"AXTextOperationMarkerRanges"
#endif
#ifndef NSAccessibilityTextOperationType
// The type of operation to be performed: select, replace, capitalize....
#define NSAccessibilityTextOperationType @"AXTextOperationType"
#endif
#ifndef NSAccessibilityTextOperationSelect
// Value for TextOperationType.
#define NSAccessibilityTextOperationSelect @"TextOperationSelect"
#endif
#ifndef NSAccessibilityTextOperationReplace
// Value for TextOperationType.
#define NSAccessibilityTextOperationReplace @"TextOperationReplace"
#endif
#ifndef NSAccessibilityTextOperationCapitalize
// Value for TextOperationType.
#define NSAccessibilityTextOperationCapitalize @"Capitalize"
#endif
#ifndef NSAccessibilityTextOperationLowercase
// Value for TextOperationType.
#define NSAccessibilityTextOperationLowercase @"Lowercase"
#endif
#ifndef NSAccessibilityTextOperationUppercase
// Value for TextOperationType.
#define NSAccessibilityTextOperationUppercase @"Uppercase"
#endif
#ifndef NSAccessibilityTextOperationReplacementString
// Replacement text for operation replace.
#define NSAccessibilityTextOperationReplacementString @"AXTextOperationReplacementString"
#endif
// Math attributes
#define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand"
#define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex"
#define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator"
#define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator"
#define NSAccessibilityMathBaseAttribute @"AXMathBase"
#define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript"
#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript"
#define NSAccessibilityMathUnderAttribute @"AXMathUnder"
#define NSAccessibilityMathOverAttribute @"AXMathOver"
#define NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen"
#define NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose"
#define NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness"
#define NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts"
#define NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts"
#ifndef NSAccessibilityPreventKeyboardDOMEventDispatchAttribute
#define NSAccessibilityPreventKeyboardDOMEventDispatchAttribute @"AXPreventKeyboardDOMEventDispatch"
#endif
#ifndef NSAccessibilityCaretBrowsingEnabledAttribute
#define NSAccessibilityCaretBrowsingEnabledAttribute @"AXCaretBrowsingEnabled"
#endif
#ifndef NSAccessibilityWebSessionIDAttribute
#define NSAccessibilityWebSessionIDAttribute @"AXWebSessionID"
#endif
#ifndef NSAccessibilitFocusableAncestorAttribute
#define NSAccessibilityFocusableAncestorAttribute @"AXFocusableAncestor"
#endif
#ifndef NSAccessibilityEditableAncestorAttribute
#define NSAccessibilityEditableAncestorAttribute @"AXEditableAncestor"
#endif
#ifndef NSAccessibilityHighestEditableAncestorAttribute
#define NSAccessibilityHighestEditableAncestorAttribute @"AXHighestEditableAncestor"
#endif
#ifndef NSAccessibilityLinkRelationshipTypeAttribute
#define NSAccessibilityLinkRelationshipTypeAttribute @"AXLinkRelationshipType"
#endif
#ifndef NSAccessibilityRelativeFrameAttribute
#define NSAccessibilityRelativeFrameAttribute @"AXRelativeFrame"
#endif
#ifndef NSAccessibilityBrailleLabelAttribute
#define NSAccessibilityBrailleLabelAttribute @"AXBrailleLabel"
#endif
#ifndef NSAccessibilityBrailleRoleDescriptionAttribute
#define NSAccessibilityBrailleRoleDescriptionAttribute @"AXBrailleRoleDescription"
#endif
#ifndef NSAccessibilityEmbeddedImageDescriptionAttribute
#define NSAccessibilityEmbeddedImageDescriptionAttribute @"AXEmbeddedImageDescription"
#endif
#ifndef NSAccessibilityImageOverlayElementsAttribute
#define NSAccessibilityImageOverlayElementsAttribute @"AXImageOverlayElements"
#endif
#ifndef AXHasDocumentRoleAncestorAttribute
#define AXHasDocumentRoleAncestorAttribute @"AXHasDocumentRoleAncestor"
#endif
#ifndef AXHasWebApplicationAncestorAttribute
#define AXHasWebApplicationAncestorAttribute @"AXHasWebApplicationAncestor"
#endif
extern "C" AXUIElementRef NSAccessibilityCreateAXUIElementRef(id element);
@implementation WebAccessibilityObjectWrapper
- (void)unregisterUniqueIdForUIElement
{
ASSERT(isMainThread());
NSAccessibilityUnregisterUniqueIdForUIElement(self);
}
- (void)detach
{
// Send unregisterUniqueIdForUIElement unconditionally because if it is
// ever accidentally not done (via other bugs in our AX implementation) you
// end up with a crash like <rdar://problem/4273149>. It is safe and not
// expensive to send even if the object is not registered.
[self unregisterUniqueIdForUIElement];
[super detach];
}
- (id)attachmentView
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* widget = backingObject->widgetForAttachmentView();
return widget ? NSAccessibilityUnignoredDescendant(widget->platformWidget()) : nil;
});
}
#pragma mark SystemInterface wrappers
static inline BOOL AXObjectIsTextMarker(id object)
{
return object && CFGetTypeID((__bridge CFTypeRef)object) == AXTextMarkerGetTypeID();
}
static inline BOOL AXObjectIsTextMarkerRange(id object)
{
return object && CFGetTypeID((__bridge CFTypeRef)object) == AXTextMarkerRangeGetTypeID();
}
#pragma mark Other helpers
- (IntRect)screenToContents:(const IntRect&)rect
{
ASSERT(isMainThread());
Document* document = self.axBackingObject->document();
if (!document)
return IntRect();
FrameView* frameView = document->view();
if (!frameView)
return IntRect();
IntPoint startPoint = frameView->screenToContents(rect.minXMaxYCorner());
IntPoint endPoint = frameView->screenToContents(rect.maxXMinYCorner());
return IntRect(startPoint.x(), startPoint.y(), endPoint.x() - startPoint.x(), endPoint.y() - startPoint.y());
}
#pragma mark Select text helpers
// To be deprecated.
static std::pair<AccessibilitySearchTextCriteria, AccessibilityTextOperation> accessibilityTextCriteriaForParameterizedAttribute(const NSDictionary *parameterizedAttribute)
{
AccessibilitySearchTextCriteria criteria;
AccessibilityTextOperation operation;
NSString *activityParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextActivity];
NSString *ambiguityResolutionParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextAmbiguityResolution];
NSString *replacementStringParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextReplacementString];
NSArray *searchStringsParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextSearchStrings];
if ([activityParameter isKindOfClass:[NSString class]]) {
if ([activityParameter isEqualToString:NSAccessibilitySelectTextActivityFindAndReplace])
operation.type = AccessibilityTextOperationType::Replace;
else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndCapitalize])
operation.type = AccessibilityTextOperationType::Capitalize;
else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndLowercase])
operation.type = AccessibilityTextOperationType::Lowercase;
else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndUppercase])
operation.type = AccessibilityTextOperationType::Uppercase;
}
criteria.direction = AccessibilitySearchTextDirection::Closest;
if ([ambiguityResolutionParameter isKindOfClass:[NSString class]]) {
if ([ambiguityResolutionParameter isEqualToString:NSAccessibilitySelectTextAmbiguityResolutionClosestAfterSelection])
criteria.direction = AccessibilitySearchTextDirection::Forward;
else if ([ambiguityResolutionParameter isEqualToString:NSAccessibilitySelectTextAmbiguityResolutionClosestBeforeSelection])
criteria.direction = AccessibilitySearchTextDirection::Backward;
}
if ([replacementStringParameter isKindOfClass:[NSString class]])
operation.replacementText = replacementStringParameter;
if ([searchStringsParameter isKindOfClass:[NSArray class]])
criteria.searchStrings = makeVector<String>(searchStringsParameter);
return std::make_pair(criteria, operation);
}
static AccessibilitySearchTextCriteria accessibilitySearchTextCriteriaForParameterizedAttribute(const NSDictionary *params)
{
AccessibilitySearchTextCriteria criteria;
NSArray *searchStrings = [params objectForKey:NSAccessibilitySearchTextSearchStrings];
NSString *start = [params objectForKey:NSAccessibilitySearchTextStartFrom];
NSString *direction = [params objectForKey:NSAccessibilitySearchTextDirection];
if ([searchStrings isKindOfClass:[NSArray class]])
criteria.searchStrings = makeVector<String>(searchStrings);
if ([start isKindOfClass:[NSString class]]) {
if ([start isEqualToString:NSAccessibilitySearchTextStartFromBegin])
criteria.start = AccessibilitySearchTextStartFrom::Begin;
else if ([start isEqualToString:NSAccessibilitySearchTextStartFromEnd])
criteria.start = AccessibilitySearchTextStartFrom::End;
}
if ([direction isKindOfClass:[NSString class]]) {
if ([direction isEqualToString:NSAccessibilitySearchTextDirectionBackward])
criteria.direction = AccessibilitySearchTextDirection::Backward;
else if ([direction isEqualToString:NSAccessibilitySearchTextDirectionClosest])
criteria.direction = AccessibilitySearchTextDirection::Closest;
else if ([direction isEqualToString:NSAccessibilitySearchTextDirectionAll])
criteria.direction = AccessibilitySearchTextDirection::All;
}
return criteria;
}
static AccessibilityTextOperation accessibilityTextOperationForParameterizedAttribute(AXObjectCache* axObjectCache, const NSDictionary *parameterizedAttribute)
{
AccessibilityTextOperation operation;
NSArray *markerRanges = [parameterizedAttribute objectForKey:NSAccessibilityTextOperationMarkerRanges];
NSString *operationType = [parameterizedAttribute objectForKey:NSAccessibilityTextOperationType];
NSString *replacementString = [parameterizedAttribute objectForKey:NSAccessibilityTextOperationReplacementString];
if ([markerRanges isKindOfClass:[NSArray class]]) {
operation.textRanges = makeVector(markerRanges, [&axObjectCache] (id markerRange) {
ASSERT(AXObjectIsTextMarkerRange(markerRange));
return rangeForTextMarkerRange(axObjectCache, (AXTextMarkerRangeRef)markerRange);
});
}
if ([operationType isKindOfClass:[NSString class]]) {
if ([operationType isEqualToString:NSAccessibilityTextOperationReplace])
operation.type = AccessibilityTextOperationType::Replace;
else if ([operationType isEqualToString:NSAccessibilityTextOperationCapitalize])
operation.type = AccessibilityTextOperationType::Capitalize;
else if ([operationType isEqualToString:NSAccessibilityTextOperationLowercase])
operation.type = AccessibilityTextOperationType::Lowercase;
else if ([operationType isEqualToString:NSAccessibilityTextOperationUppercase])
operation.type = AccessibilityTextOperationType::Uppercase;
}
if ([replacementString isKindOfClass:[NSString class]])
operation.replacementText = replacementString;
return operation;
}
static std::pair<std::optional<SimpleRange>, AccessibilitySearchDirection> accessibilityMisspellingSearchCriteriaForParameterizedAttribute(AXObjectCache* axObjectCache, const NSDictionary *params)
{
ASSERT(isMainThread());
std::pair<std::optional<SimpleRange>, AccessibilitySearchDirection> criteria;
ASSERT(AXObjectIsTextMarkerRange([params objectForKey:@"AXStartTextMarkerRange"]));
criteria.first = rangeForTextMarkerRange(axObjectCache, (AXTextMarkerRangeRef)[params objectForKey:@"AXStartTextMarkerRange"]);
NSNumber *forward = [params objectForKey:NSAccessibilitySearchTextDirection];
if ([forward isKindOfClass:[NSNumber class]])
criteria.second = [forward boolValue] ? AccessibilitySearchDirection::Next : AccessibilitySearchDirection::Previous;
else
criteria.second = AccessibilitySearchDirection::Next;
return criteria;
}
#pragma mark Text Marker helpers
static RetainPtr<AXTextMarkerRef> nextTextMarkerForCharacterOffset(AXObjectCache* cache, CharacterOffset& characterOffset)
{
if (!cache)
return nil;
TextMarkerData textMarkerData;
cache->textMarkerDataForNextCharacterOffset(textMarkerData, characterOffset);
if (!textMarkerData.axID)
return nil;
return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData)));
}
static RetainPtr<AXTextMarkerRef> previousTextMarkerForCharacterOffset(AXObjectCache* cache, CharacterOffset& characterOffset)
{
if (!cache)
return nil;
TextMarkerData textMarkerData;
cache->textMarkerDataForPreviousCharacterOffset(textMarkerData, characterOffset);
if (!textMarkerData.axID)
return nil;
return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData)));
}
- (RetainPtr<AXTextMarkerRef>)nextTextMarkerForCharacterOffset:(CharacterOffset&)characterOffset
{
return nextTextMarkerForCharacterOffset(self.axBackingObject->axObjectCache(), characterOffset);
}
- (RetainPtr<AXTextMarkerRef>)previousTextMarkerForCharacterOffset:(CharacterOffset&)characterOffset
{
return previousTextMarkerForCharacterOffset(self.axBackingObject->axObjectCache(), characterOffset);
}
// FIXME: Remove this method since clients should not need to call this method and should not be exposed in the public interface.
// Inside WebCore, use WebCore::textMarkerFromVisiblePosition instead.
- (id)textMarkerForVisiblePosition:(const VisiblePosition&)position
{
ASSERT(isMainThread());
auto *backingObject = self.axBackingObject;
return backingObject ? (id)textMarkerForVisiblePosition(backingObject->axObjectCache(), position) : nil;
}
- (RetainPtr<AXTextMarkerRef>)textMarkerForFirstPositionInTextControl:(HTMLTextFormControlElement &)textControl
{
ASSERT(isMainThread());
auto* backingObject = self.axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
auto textMarkerData = cache->textMarkerDataForFirstPositionInTextControl(textControl);
if (!textMarkerData)
return nil;
return adoptCF(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData.value(), sizeof(textMarkerData.value())));
}
// When modifying attributed strings, the range can come from a source which may provide faulty information (e.g. the spell checker).
// To protect against such cases the range should be validated before adding or removing attributes.
static BOOL AXAttributedStringRangeIsValid(NSAttributedString *attrString, NSRange range)
{
return (range.location < [attrString length] && NSMaxRange(range) <= [attrString length]);
}
static void AXAttributeStringSetFont(NSMutableAttributedString *attrString, NSString *attribute, CTFontRef font, NSRange range)
{
if (!AXAttributedStringRangeIsValid(attrString, range))
return;
if (font) {
NSDictionary *dictionary = @{
NSAccessibilityFontNameKey: (__bridge NSString *)adoptCF(CTFontCopyPostScriptName(font)).get(),
NSAccessibilityFontFamilyKey: (__bridge NSString *)adoptCF(CTFontCopyFamilyName(font)).get(),
NSAccessibilityVisibleNameKey: (__bridge NSString *)adoptCF(CTFontCopyDisplayName(font)).get(),
NSAccessibilityFontSizeKey: @(CTFontGetSize(font)),
};
[attrString addAttribute:attribute value:dictionary range:range];
} else
[attrString removeAttribute:attribute range:range];
}
static void AXAttributeStringSetColor(NSMutableAttributedString* attrString, NSString* attribute, NSColor* color, NSRange range)
{
if (!AXAttributedStringRangeIsValid(attrString, range))
return;
if (color) {
CGColorRef cgColor = color.CGColor;
id existingColor = [attrString attribute:attribute atIndex:range.location effectiveRange:nil];
if (!existingColor || !CGColorEqualToColor((__bridge CGColorRef)existingColor, cgColor))
[attrString addAttribute:attribute value:(__bridge id)cgColor range:range];
} else
[attrString removeAttribute:attribute range:range];
}
static void AXAttributeStringSetNumber(NSMutableAttributedString* attrString, NSString* attribute, NSNumber* number, NSRange range)
{
if (!AXAttributedStringRangeIsValid(attrString, range))
return;
if (number)
[attrString addAttribute:attribute value:number range:range];
else
[attrString removeAttribute:attribute range:range];
}
static void AXAttributeStringSetStyle(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range)
{
const RenderStyle& style = renderer->style();
// set basic font info
AXAttributeStringSetFont(attrString, NSAccessibilityFontTextAttribute, style.fontCascade().primaryFont().getCTFont(), range);
// set basic colors
AXAttributeStringSetColor(attrString, NSAccessibilityForegroundColorTextAttribute, cocoaColor(style.visitedDependentColor(CSSPropertyColor)).get(), range);
AXAttributeStringSetColor(attrString, NSAccessibilityBackgroundColorTextAttribute, cocoaColor(style.visitedDependentColor(CSSPropertyBackgroundColor)).get(), range);
// set super/sub scripting
VerticalAlign alignment = style.verticalAlign();
if (alignment == VerticalAlign::Sub)
AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, @(-1), range);
else if (alignment == VerticalAlign::Super)
AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, @(1), range);
else
[attrString removeAttribute:NSAccessibilitySuperscriptTextAttribute range:range];
// set shadow
if (style.textShadow())
AXAttributeStringSetNumber(attrString, NSAccessibilityShadowTextAttribute, @YES, range);
else
[attrString removeAttribute:NSAccessibilityShadowTextAttribute range:range];
// set underline and strikethrough
auto decor = style.textDecorationsInEffect();
if (!(decor & TextDecorationLine::Underline)) {
[attrString removeAttribute:NSAccessibilityUnderlineTextAttribute range:range];
[attrString removeAttribute:NSAccessibilityUnderlineColorTextAttribute range:range];
}
if (!(decor & TextDecorationLine::LineThrough)) {
[attrString removeAttribute:NSAccessibilityStrikethroughTextAttribute range:range];
[attrString removeAttribute:NSAccessibilityStrikethroughColorTextAttribute range:range];
}
if (decor & TextDecorationLine::Underline || decor & TextDecorationLine::LineThrough) {
// FIXME: Should the underline style be reported here?
auto decorationStyles = TextDecorationPainter::stylesForRenderer(*renderer, decor);
if (decor & TextDecorationLine::Underline) {
AXAttributeStringSetNumber(attrString, NSAccessibilityUnderlineTextAttribute, @YES, range);
AXAttributeStringSetColor(attrString, NSAccessibilityUnderlineColorTextAttribute, cocoaColor(decorationStyles.underlineColor).get(), range);
}
if (decor & TextDecorationLine::LineThrough) {
AXAttributeStringSetNumber(attrString, NSAccessibilityStrikethroughTextAttribute, @YES, range);
AXAttributeStringSetColor(attrString, NSAccessibilityStrikethroughColorTextAttribute, cocoaColor(decorationStyles.linethroughColor).get(), range);
}
}
// FIXME: This traversal should be replaced by a flag in AccessibilityObject::m_ancestorFlags (e.g., AXAncestorFlag::HasHTMLMarkAncestor)
// Indicate background highlighting.
for (Node* node = renderer->node(); node; node = node->parentNode()) {
if (node->hasTagName(HTMLNames::markTag))
AXAttributeStringSetNumber(attrString, @"AXHighlight", @YES, range);
}
}
static void AXAttributeStringSetBlockquoteLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range)
{
if (!AXAttributedStringRangeIsValid(attrString, range))
return;
AccessibilityObject* obj = renderer->document().axObjectCache()->getOrCreate(renderer);
int quoteLevel = obj->blockquoteLevel();
if (quoteLevel)
[attrString addAttribute:NSAccessibilityBlockQuoteLevelAttribute value:@(quoteLevel) range:range];
else
[attrString removeAttribute:NSAccessibilityBlockQuoteLevelAttribute range:range];
}
static void AXAttributeStringSetSpelling(NSMutableAttributedString* attrString, Node* node, StringView text, NSRange range)
{
ASSERT(node);
// If this node is not inside editable content, do not run the spell checker on the text.
if (!node->document().axObjectCache()->rootAXEditableElement(node))
return;
if (unifiedTextCheckerEnabled(node->document().frame())) {
// Check the spelling directly since document->markersForNode() does not store the misspelled marking when the cursor is in a word.
TextCheckerClient* checker = node->document().editor().textChecker();
// checkTextOfParagraph is the only spelling/grammar checker implemented in WK1 and WK2
Vector<TextCheckingResult> results;
checkTextOfParagraph(*checker, text, TextCheckingType::Spelling, results, node->document().frame()->selection().selection());
size_t size = results.size();
for (unsigned i = 0; i < size; i++) {
const TextCheckingResult& result = results[i];
AXAttributeStringSetNumber(attrString, NSAccessibilityMisspelledTextAttribute, @YES, NSMakeRange(result.range.location + range.location, result.range.length));
#if PLATFORM(MAC)
AXAttributeStringSetNumber(attrString, NSAccessibilityMarkedMisspelledTextAttribute, @YES, NSMakeRange(result.range.location + range.location, result.range.length));
#endif
}
return;
}
for (unsigned currentPosition = 0; currentPosition < text.length(); ) {
int misspellingLocation = -1;
int misspellingLength = 0;
node->document().editor().textChecker()->checkSpellingOfString(text.substring(currentPosition), &misspellingLocation, &misspellingLength);
if (misspellingLocation == -1 || !misspellingLength)
break;
NSRange spellRange = NSMakeRange(range.location + currentPosition + misspellingLocation, misspellingLength);
AXAttributeStringSetNumber(attrString, NSAccessibilityMisspelledTextAttribute, @YES, spellRange);
#if PLATFORM(MAC)
AXAttributeStringSetNumber(attrString, NSAccessibilityMarkedMisspelledTextAttribute, @YES, spellRange);
#endif
currentPosition += misspellingLocation + misspellingLength;
}
}
static void AXAttributeStringSetExpandedTextValue(NSMutableAttributedString *attrString, RenderObject* renderer, NSRange range)
{
if (!renderer || !AXAttributedStringRangeIsValid(attrString, range))
return;
AccessibilityObject* axObject = renderer->document().axObjectCache()->getOrCreate(renderer);
if (axObject->supportsExpandedTextValue())
[attrString addAttribute:NSAccessibilityExpandedTextValueAttribute value:axObject->expandedTextValue() range:range];
else
[attrString removeAttribute:NSAccessibilityExpandedTextValueAttribute range:range];
}
static void AXAttributeStringSetHeadingLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range)
{
if (!renderer)
return;
if (!AXAttributedStringRangeIsValid(attrString, range))
return;
// Sometimes there are objects between the text and the heading.
// In those cases the parent hierarchy should be queried to see if there is a heading level.
int parentHeadingLevel = 0;
AccessibilityObject* parentObject = renderer->document().axObjectCache()->getOrCreate(renderer->parent());
for (; parentObject; parentObject = parentObject->parentObject()) {
parentHeadingLevel = parentObject->headingLevel();
if (parentHeadingLevel)
break;
}
if (parentHeadingLevel)
[attrString addAttribute:@"AXHeadingLevel" value:@(parentHeadingLevel) range:range];
else
[attrString removeAttribute:@"AXHeadingLevel" range:range];
}
static void AXAttributeStringSetElement(NSMutableAttributedString* attrString, NSString* attribute, AccessibilityObject* object, NSRange range)
{
if (!AXAttributedStringRangeIsValid(attrString, range))
return;
if (is<AccessibilityRenderObject>(object)) {
// make a serializable AX object
RenderObject* renderer = downcast<AccessibilityRenderObject>(*object).renderer();
if (!renderer)
return;
AXObjectCache* cache = renderer->document().axObjectCache();
if (!cache)
return;
id objectWrapper = object->wrapper();
if ([attribute isEqualToString:NSAccessibilityAttachmentTextAttribute] && object->isAttachment() && [objectWrapper attachmentView])
objectWrapper = [objectWrapper attachmentView];
if (auto axElement = adoptCF(NSAccessibilityCreateAXUIElementRef(objectWrapper)))
[attrString addAttribute:attribute value:(__bridge id)axElement.get() range:range];
} else
[attrString removeAttribute:attribute range:range];
}
static void AXAttributedStringAppendText(NSMutableAttributedString* attrString, Node* node, StringView text, bool spellCheck)
{
// skip invisible text
RenderObject* renderer = node->renderer();
if (!renderer || !text.length())
return;
// easier to calculate the range before appending the string
NSRange attrStringRange = NSMakeRange([attrString length], text.length());
// append the string from this node
[[attrString mutableString] appendString:text.createNSStringWithoutCopying().get()];
// add new attributes and remove irrelevant inherited ones
// NOTE: color attributes are handled specially because -[NSMutableAttributedString addAttribute: value: range:] does not merge
// identical colors. Workaround is to not replace an existing color attribute if it matches what we are adding. This also means
// we cannot just pre-remove all inherited attributes on the appended string, so we have to remove the irrelevant ones individually.
// remove inherited attachment from prior AXAttributedStringAppendReplaced
[attrString removeAttribute:NSAccessibilityAttachmentTextAttribute range:attrStringRange];
if (spellCheck) {
#if PLATFORM(MAC)
[attrString removeAttribute:NSAccessibilityMarkedMisspelledTextAttribute range:attrStringRange];
#endif
[attrString removeAttribute:NSAccessibilityMisspelledTextAttribute range:attrStringRange];
}
// set new attributes
AXAttributeStringSetStyle(attrString, renderer, attrStringRange);
AXAttributeStringSetHeadingLevel(attrString, renderer, attrStringRange);
AXAttributeStringSetBlockquoteLevel(attrString, renderer, attrStringRange);
AXAttributeStringSetExpandedTextValue(attrString, renderer, attrStringRange);
AXAttributeStringSetElement(attrString, NSAccessibilityLinkTextAttribute, AccessibilityObject::anchorElementForNode(node), attrStringRange);
// do spelling last because it tends to break up the range
if (spellCheck)
AXAttributeStringSetSpelling(attrString, node, text, attrStringRange);
}
static NSString* nsStringForReplacedNode(Node* replacedNode)
{
// we should always be given a rendered node and a replaced node, but be safe
// replaced nodes are either attachments (widgets) or images
if (!replacedNode || !isRendererReplacedElement(replacedNode->renderer()) || replacedNode->isTextNode()) {
ASSERT_NOT_REACHED();
return nil;
}
// create an AX object, but skip it if it is not supposed to be seen
RefPtr<AccessibilityObject> obj = replacedNode->renderer()->document().axObjectCache()->getOrCreate(replacedNode->renderer());
if (obj->accessibilityIsIgnored())
return nil;
// use the attachmentCharacter to represent the replaced node
const UniChar attachmentChar = NSAttachmentCharacter;
return [NSString stringWithCharacters:&attachmentChar length:1];
}
- (NSAttributedString *)doAXAttributedStringForTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange spellCheck:(BOOL)spellCheck
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<NSAttributedString *>([&textMarkerRange, &spellCheck, protectedSelf = retainPtr(self)] () -> RetainPtr<NSAttributedString> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto range = rangeForTextMarkerRange(backingObject->axObjectCache(), textMarkerRange);
if (!range)
return nil;
auto attrString = adoptNS([[NSMutableAttributedString alloc] init]);
TextIterator it(*range);
while (!it.atEnd()) {
Node& node = it.range().start.container;
// non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
if (it.text().length()) {
// Add the text of the list marker item if necessary.
String listMarkerText = AccessibilityObject::listMarkerTextForNodeAndPosition(&node, makeContainerOffsetPosition(it.range().start));
if (!listMarkerText.isEmpty())
AXAttributedStringAppendText(attrString.get(), &node, listMarkerText, spellCheck);
AXAttributedStringAppendText(attrString.get(), &node, it.text(), spellCheck);
} else {
Node* replacedNode = it.node();
NSString *attachmentString = nsStringForReplacedNode(replacedNode);
if (attachmentString) {
NSRange attrStringRange = NSMakeRange([attrString length], [attachmentString length]);
// append the placeholder string
[[attrString mutableString] appendString:attachmentString];
// remove all inherited attributes
[attrString setAttributes:nil range:attrStringRange];
// add the attachment attribute
AccessibilityObject* obj = replacedNode->renderer()->document().axObjectCache()->getOrCreate(replacedNode->renderer());
AXAttributeStringSetElement(attrString.get(), NSAccessibilityAttachmentTextAttribute, obj, attrStringRange);
}
}
it.advance();
}
return attrString;
});
}
// FIXME: Remove this method since clients should not need to call this method and should not be exposed in the public interface.
// Inside WebCore, use WebCore::textMarkerRangeFromVisiblePositions instead.
- (id)textMarkerRangeFromVisiblePositions:(const VisiblePosition&)startPosition endPosition:(const VisiblePosition&)endPosition
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&startPosition, &endPosition, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
return (id)textMarkerRangeFromVisiblePositions(backingObject->axObjectCache(), startPosition, endPosition);
});
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (NSArray*)accessibilityActionNames
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return nil;
// All elements should get ShowMenu and ScrollToVisible.
// But certain earlier VoiceOver versions do not support scroll to visible, and it confuses them to see it in the list.
static NeverDestroyed<RetainPtr<NSArray>> defaultElementActions = @[NSAccessibilityShowMenuAction, NSAccessibilityScrollToVisibleAction];
// Action elements allow Press.
// The order is important to VoiceOver, which expects the 'default' action to be the first action. In this case the default action should be press.
static NeverDestroyed<RetainPtr<NSArray>> actionElementActions = @[NSAccessibilityPressAction, NSAccessibilityShowMenuAction, NSAccessibilityScrollToVisibleAction];
// Menu elements allow Press and Cancel.
static NeverDestroyed<RetainPtr<NSArray>> menuElementActions = [actionElementActions.get() arrayByAddingObject:NSAccessibilityCancelAction];
// Slider elements allow Increment/Decrement.
static NeverDestroyed<RetainPtr<NSArray>> sliderActions = [defaultElementActions.get() arrayByAddingObjectsFromArray:@[NSAccessibilityIncrementAction, NSAccessibilityDecrementAction]];
NSArray *actions;
if (backingObject->supportsPressAction())
actions = actionElementActions.get().get();
else if (backingObject->isMenuRelated())
actions = menuElementActions.get().get();
else if (backingObject->isSlider())
actions = sliderActions.get().get();
else if (backingObject->isAttachment())
actions = [[self attachmentView] accessibilityActionNames];
else
actions = defaultElementActions.get().get();
return actions;
}
- (NSArray*)additionalAccessibilityAttributeNames
{
auto* backingObject = self.axBackingObject;
if (!backingObject)
return nil;
NSMutableArray *additional = [NSMutableArray array];
if (backingObject->supportsARIAOwns())
[additional addObject:NSAccessibilityOwnsAttribute];
if (backingObject->isToggleButton())
[additional addObject:NSAccessibilityValueAttribute];
if (backingObject->supportsExpanded() || backingObject->isSummary())
[additional addObject:NSAccessibilityExpandedAttribute];
if (backingObject->isScrollbar()
|| backingObject->isRadioGroup()
|| backingObject->isSplitter()
|| backingObject->isToolbar())
[additional addObject:NSAccessibilityOrientationAttribute];
if (backingObject->supportsDragging())
[additional addObject:NSAccessibilityGrabbedAttribute];
if (backingObject->supportsDropping())
[additional addObject:NSAccessibilityDropEffectsAttribute];
if (backingObject->isTable() && backingObject->isExposable() && backingObject->supportsSelectedRows())
[additional addObject:NSAccessibilitySelectedRowsAttribute];
if (backingObject->supportsLiveRegion()) {
[additional addObject:NSAccessibilityARIALiveAttribute];
[additional addObject:NSAccessibilityARIARelevantAttribute];
}
if (backingObject->supportsSetSize())
[additional addObject:NSAccessibilityARIASetSizeAttribute];
if (backingObject->supportsPosInSet())
[additional addObject:NSAccessibilityARIAPosInSetAttribute];
AccessibilitySortDirection sortDirection = backingObject->sortDirection();
if (sortDirection != AccessibilitySortDirection::None && sortDirection != AccessibilitySortDirection::Invalid)
[additional addObject:NSAccessibilitySortDirectionAttribute];
// If an object is a child of a live region, then add these
if (backingObject->isInsideLiveRegion())
[additional addObject:NSAccessibilityARIAAtomicAttribute];
// All objects should expose the ARIA busy attribute (ARIA 1.1 with ISSUE-538).
[additional addObject:NSAccessibilityElementBusyAttribute];
// Popup buttons on the Mac expose the value attribute.
if (backingObject->isPopUpButton())
[additional addObject:NSAccessibilityValueAttribute];
if (backingObject->supportsDatetimeAttribute())
[additional addObject:NSAccessibilityDatetimeValueAttribute];
if (backingObject->supportsRequiredAttribute())
[additional addObject:NSAccessibilityRequiredAttribute];
if (backingObject->hasPopup())
[additional addObject:NSAccessibilityHasPopupAttribute];
if (backingObject->isMathRoot()) {
// The index of a square root is always known, so there's no object associated with it.
if (!backingObject->isMathSquareRoot())
[additional addObject:NSAccessibilityMathRootIndexAttribute];
[additional addObject:NSAccessibilityMathRootRadicandAttribute];
} else if (backingObject->isMathFraction()) {
[additional addObject:NSAccessibilityMathFractionNumeratorAttribute];
[additional addObject:NSAccessibilityMathFractionDenominatorAttribute];
[additional addObject:NSAccessibilityMathLineThicknessAttribute];
} else if (backingObject->isMathSubscriptSuperscript()) {
[additional addObject:NSAccessibilityMathBaseAttribute];
[additional addObject:NSAccessibilityMathSubscriptAttribute];
[additional addObject:NSAccessibilityMathSuperscriptAttribute];
} else if (backingObject->isMathUnderOver()) {
[additional addObject:NSAccessibilityMathBaseAttribute];
[additional addObject:NSAccessibilityMathUnderAttribute];
[additional addObject:NSAccessibilityMathOverAttribute];
} else if (backingObject->isMathFenced()) {
[additional addObject:NSAccessibilityMathFencedOpenAttribute];
[additional addObject:NSAccessibilityMathFencedCloseAttribute];
} else if (backingObject->isMathMultiscript()) {
[additional addObject:NSAccessibilityMathBaseAttribute];
[additional addObject:NSAccessibilityMathPrescriptsAttribute];
[additional addObject:NSAccessibilityMathPostscriptsAttribute];
}
if (backingObject->supportsPath())
[additional addObject:NSAccessibilityPathAttribute];
if (backingObject->supportsExpandedTextValue())
[additional addObject:NSAccessibilityExpandedTextValueAttribute];
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
if (AXObjectCache::isIsolatedTreeEnabled())
[additional addObject:NSAccessibilityRelativeFrameAttribute];
#endif
return additional;
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (NSArray*)accessibilityAttributeNames
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
auto* backingObject = self.axBackingObject;
if (!backingObject)
return nil;
if (backingObject->isAttachment())
return [[self attachmentView] accessibilityAttributeNames];
static NeverDestroyed<RetainPtr<NSArray>> attributes = @[
AXHasDocumentRoleAncestorAttribute,
AXHasWebApplicationAncestorAttribute,
NSAccessibilityRoleAttribute,
NSAccessibilitySubroleAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilityChildrenAttribute,
NSAccessibilityChildrenInNavigationOrderAttribute,
NSAccessibilityHelpAttribute,
NSAccessibilityParentAttribute,
NSAccessibilityPositionAttribute,
NSAccessibilitySizeAttribute,
NSAccessibilityTitleAttribute,
NSAccessibilityDescriptionAttribute,
NSAccessibilityValueAttribute,
NSAccessibilityFocusedAttribute,
NSAccessibilityEnabledAttribute,
NSAccessibilityWindowAttribute,
@"AXSelectedTextMarkerRange",
@"AXStartTextMarker",
@"AXEndTextMarker",
@"AXVisited",
NSAccessibilityLinkedUIElementsAttribute,
NSAccessibilitySelectedAttribute,
NSAccessibilityBlockQuoteLevelAttribute,
NSAccessibilityTopLevelUIElementAttribute,
NSAccessibilityLanguageAttribute,
NSAccessibilityDOMIdentifierAttribute,
NSAccessibilityDOMClassListAttribute,
NSAccessibilityFocusableAncestorAttribute,
NSAccessibilityEditableAncestorAttribute,
NSAccessibilityHighestEditableAncestorAttribute,
];
static NeverDestroyed incrementorAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityIncrementButtonAttribute];
[tempArray addObject:NSAccessibilityDecrementButtonAttribute];
[tempArray addObject:NSAccessibilityValueDescriptionAttribute];
[tempArray addObject:NSAccessibilityMinValueAttribute];
[tempArray addObject:NSAccessibilityMaxValueAttribute];
return tempArray;
}();
static NeverDestroyed anchorAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityURLAttribute];
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
[tempArray addObject:NSAccessibilityLinkRelationshipTypeAttribute];
return tempArray;
}();
static NeverDestroyed<RetainPtr<NSArray>> commonMenuAttrs = @[
NSAccessibilityRoleAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilityChildrenAttribute,
NSAccessibilityParentAttribute,
NSAccessibilityEnabledAttribute,
NSAccessibilityPositionAttribute,
NSAccessibilitySizeAttribute,
];
static NeverDestroyed webAreaAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
// WebAreas should not expose AXSubrole.
[tempArray removeObject:NSAccessibilitySubroleAttribute];
// WebAreas should not expose ancestor attributes
[tempArray removeObject:NSAccessibilityFocusableAncestorAttribute];
[tempArray removeObject:NSAccessibilityEditableAncestorAttribute];
[tempArray removeObject:NSAccessibilityHighestEditableAncestorAttribute];
[tempArray addObject:@"AXLinkUIElements"];
[tempArray addObject:@"AXLoaded"];
[tempArray addObject:@"AXLayoutCount"];
[tempArray addObject:NSAccessibilityLoadingProgressAttribute];
[tempArray addObject:NSAccessibilityURLAttribute];
[tempArray addObject:NSAccessibilityCaretBrowsingEnabledAttribute];
[tempArray addObject:NSAccessibilityPreventKeyboardDOMEventDispatchAttribute];
[tempArray addObject:NSAccessibilityWebSessionIDAttribute];
return tempArray;
}();
static NeverDestroyed textAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityNumberOfCharactersAttribute];
[tempArray addObject:NSAccessibilitySelectedTextAttribute];
[tempArray addObject:NSAccessibilitySelectedTextRangeAttribute];
[tempArray addObject:NSAccessibilityVisibleCharacterRangeAttribute];
[tempArray addObject:NSAccessibilityInsertionPointLineNumberAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
[tempArray addObject:NSAccessibilityRequiredAttribute];
[tempArray addObject:NSAccessibilityInvalidAttribute];
[tempArray addObject:NSAccessibilityPlaceholderValueAttribute];
[tempArray addObject:NSAccessibilityValueAutofilledAttribute];
return tempArray;
}();
static NeverDestroyed listAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilitySelectedChildrenAttribute];
[tempArray addObject:NSAccessibilityVisibleChildrenAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
return tempArray;
}();
static NeverDestroyed listBoxAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:listAttrs.get().get()]);
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
[tempArray addObject:NSAccessibilityRequiredAttribute];
[tempArray addObject:NSAccessibilityInvalidAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
return tempArray;
}();
static NeverDestroyed rangeAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityMinValueAttribute];
[tempArray addObject:NSAccessibilityMaxValueAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
[tempArray addObject:NSAccessibilityValueDescriptionAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
return tempArray;
}();
static NeverDestroyed menuBarAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:commonMenuAttrs.get().get()]);
[tempArray addObject:NSAccessibilitySelectedChildrenAttribute];
[tempArray addObject:NSAccessibilityVisibleChildrenAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
return tempArray;
}();
static NeverDestroyed menuAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:commonMenuAttrs.get().get()]);
[tempArray addObject:NSAccessibilitySelectedChildrenAttribute];
[tempArray addObject:NSAccessibilityVisibleChildrenAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
return tempArray;
}();
static NeverDestroyed menuItemAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:commonMenuAttrs.get().get()]);
[tempArray addObject:NSAccessibilityTitleAttribute];
[tempArray addObject:NSAccessibilityDescriptionAttribute];
[tempArray addObject:NSAccessibilityHelpAttribute];
[tempArray addObject:NSAccessibilitySelectedAttribute];
[tempArray addObject:NSAccessibilityValueAttribute];
[tempArray addObject:(NSString*)kAXMenuItemCmdCharAttribute];
[tempArray addObject:(NSString*)kAXMenuItemCmdVirtualKeyAttribute];
[tempArray addObject:(NSString*)kAXMenuItemCmdGlyphAttribute];
[tempArray addObject:(NSString*)kAXMenuItemCmdModifiersAttribute];
[tempArray addObject:(NSString*)kAXMenuItemMarkCharAttribute];
[tempArray addObject:(NSString*)kAXMenuItemPrimaryUIElementAttribute];
[tempArray addObject:NSAccessibilityServesAsTitleForUIElementsAttribute];
[tempArray addObject:NSAccessibilityFocusedAttribute];
return tempArray;
}();
static NeverDestroyed<RetainPtr<NSArray>> menuButtonAttrs = @[
NSAccessibilityRoleAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilityParentAttribute,
NSAccessibilityPositionAttribute,
NSAccessibilitySizeAttribute,
NSAccessibilityWindowAttribute,
NSAccessibilityEnabledAttribute,
NSAccessibilityFocusedAttribute,
NSAccessibilityTitleAttribute,
NSAccessibilityChildrenAttribute,
];
static NeverDestroyed controlAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
[tempArray addObject:NSAccessibilityRequiredAttribute];
[tempArray addObject:NSAccessibilityInvalidAttribute];
return tempArray;
}();
static NeverDestroyed buttonAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
// Buttons should not expose AXValue.
[tempArray removeObject:NSAccessibilityValueAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
return tempArray;
}();
static NeverDestroyed comboBoxAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:controlAttrs.get().get()]);
[tempArray addObject:NSAccessibilityExpandedAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
return tempArray;
}();
static NeverDestroyed tableAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityRowsAttribute];
[tempArray addObject:NSAccessibilityVisibleRowsAttribute];
[tempArray addObject:NSAccessibilityColumnsAttribute];
[tempArray addObject:NSAccessibilityVisibleColumnsAttribute];
[tempArray addObject:NSAccessibilityVisibleCellsAttribute];
[tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute];
[tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute];
[tempArray addObject:NSAccessibilityHeaderAttribute];
[tempArray addObject:NSAccessibilityColumnCountAttribute];
[tempArray addObject:NSAccessibilityRowCountAttribute];
[tempArray addObject:NSAccessibilityARIAColumnCountAttribute];
[tempArray addObject:NSAccessibilityARIARowCountAttribute];
return tempArray;
}();
static NeverDestroyed tableRowAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityIndexAttribute];
return tempArray;
}();
static NeverDestroyed tableColAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityIndexAttribute];
[tempArray addObject:NSAccessibilityHeaderAttribute];
[tempArray addObject:NSAccessibilityRowsAttribute];
[tempArray addObject:NSAccessibilityVisibleRowsAttribute];
return tempArray;
}();
static NeverDestroyed tableCellAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityRowIndexRangeAttribute];
[tempArray addObject:NSAccessibilityColumnIndexRangeAttribute];
[tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute];
[tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute];
[tempArray addObject:NSAccessibilityARIAColumnIndexAttribute];
[tempArray addObject:NSAccessibilityARIARowIndexAttribute];
return tempArray;
}();
static NeverDestroyed groupAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
return tempArray;
}();
static NeverDestroyed inputImageAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:buttonAttrs.get().get()]);
[tempArray addObject:NSAccessibilityURLAttribute];
return tempArray;
}();
static NeverDestroyed passwordFieldAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityRequiredAttribute];
[tempArray addObject:NSAccessibilityInvalidAttribute];
[tempArray addObject:NSAccessibilityPlaceholderValueAttribute];
[tempArray addObject:NSAccessibilityValueAutofilledAttribute];
return tempArray;
}();
static NeverDestroyed tabListAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityTabsAttribute];
[tempArray addObject:NSAccessibilityContentsAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
return tempArray;
}();
static NeverDestroyed outlineAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilitySelectedRowsAttribute];
[tempArray addObject:NSAccessibilityRowsAttribute];
[tempArray addObject:NSAccessibilityColumnsAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
return tempArray;
}();
static NeverDestroyed outlineRowAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:tableRowAttrs.get().get()]);
[tempArray addObject:NSAccessibilityDisclosingAttribute];
[tempArray addObject:NSAccessibilityDisclosedByRowAttribute];
[tempArray addObject:NSAccessibilityDisclosureLevelAttribute];
[tempArray addObject:NSAccessibilityDisclosedRowsAttribute];
[tempArray removeObject:NSAccessibilityValueAttribute];
return tempArray;
}();
static NeverDestroyed scrollViewAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityContentsAttribute];
[tempArray addObject:NSAccessibilityHorizontalScrollBarAttribute];
[tempArray addObject:NSAccessibilityVerticalScrollBarAttribute];
return tempArray;
}();
static NeverDestroyed imageAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
[tempArray addObject:NSAccessibilityImageOverlayElementsAttribute];
[tempArray addObject:NSAccessibilityEmbeddedImageDescriptionAttribute];
[tempArray addObject:NSAccessibilityURLAttribute];
return tempArray;
}();
static NeverDestroyed videoAttrs = [] {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:attributes.get().get()]);
// This should represent the URL of the video content, not the poster.
[tempArray addObject:NSAccessibilityURLAttribute];
return tempArray;
}();
NSArray *objectAttributes = attributes.get().get();
if (backingObject->isPasswordField())
objectAttributes = passwordFieldAttrs.get().get();
else if (backingObject->isWebArea())
objectAttributes = webAreaAttrs.get().get();
else if (backingObject->isTextControl())
objectAttributes = textAttrs.get().get();
else if (backingObject->isLink())
objectAttributes = anchorAttrs.get().get();
else if (backingObject->isImage())
objectAttributes = imageAttrs.get().get();
else if (backingObject->isTable() && backingObject->isExposable())
objectAttributes = tableAttrs.get().get();
else if (backingObject->isTableColumn())
objectAttributes = tableColAttrs.get().get();
else if (backingObject->isTableCell())
objectAttributes = tableCellAttrs.get().get();
else if (backingObject->isTableRow()) {
// An ARIA table row can be collapsed and expanded, so it needs the extra attributes.
if (backingObject->isARIATreeGridRow())
objectAttributes = outlineRowAttrs.get().get();
else
objectAttributes = tableRowAttrs.get().get();
} else if (backingObject->isTree())
objectAttributes = outlineAttrs.get().get();
else if (backingObject->isTreeItem()) {
if (backingObject->supportsCheckedState())
objectAttributes = [outlineRowAttrs.get() arrayByAddingObject:NSAccessibilityValueAttribute];
else
objectAttributes = outlineRowAttrs.get().get();
}
else if (backingObject->isListBox())
objectAttributes = listBoxAttrs.get().get();
else if (backingObject->isList())
objectAttributes = listAttrs.get().get();
else if (backingObject->isComboBox())
objectAttributes = comboBoxAttrs.get().get();
else if (backingObject->isProgressIndicator() || backingObject->isSlider())
objectAttributes = rangeAttrs.get().get();
// These are processed in order because an input image is a button, and a button is a control.
else if (backingObject->isInputImage())
objectAttributes = inputImageAttrs.get().get();
else if (backingObject->isButton())
objectAttributes = buttonAttrs.get().get();
else if (backingObject->isControl())
objectAttributes = controlAttrs.get().get();
else if (backingObject->isGroup() || backingObject->isListItem())
objectAttributes = groupAttrs.get().get();
else if (backingObject->isTabList())
objectAttributes = tabListAttrs.get().get();
else if (backingObject->isScrollView())
objectAttributes = scrollViewAttrs.get().get();
else if (backingObject->isSpinButton())
objectAttributes = incrementorAttrs.get().get();
else if (backingObject->isMenu())
objectAttributes = menuAttrs.get().get();
else if (backingObject->isMenuBar())
objectAttributes = menuBarAttrs.get().get();
else if (backingObject->isMenuButton())
objectAttributes = menuButtonAttrs.get().get();
else if (backingObject->isMenuItem())
objectAttributes = menuItemAttrs.get().get();
else if (backingObject->isVideo())
objectAttributes = videoAttrs.get().get();
NSArray *additionalAttributes = [self additionalAccessibilityAttributeNames];
if ([additionalAttributes count])
objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes];
// Only expose AXARIACurrent attribute when the element is set to be current item.
if (backingObject->currentState() != AccessibilityCurrentState::False)
objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:@[ NSAccessibilityARIACurrentAttribute ]];
// AppKit needs to know the screen height in order to do the coordinate conversion.
objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:@[ NSAccessibilityPrimaryScreenHeightAttribute ]];
return objectAttributes;
}
- (NSArray *)renderWidgetChildren
{
auto* backingObject = self.axBackingObject;
if (!backingObject || !backingObject->isWidget())
return nil;
id child = Accessibility::retrieveAutoreleasedValueFromMainThread<id>([protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* widget = backingObject->widget();
return widget ? widget->accessibilityObject() : nil;
});
if (child)
return @[child];
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
return [backingObject->platformWidget() accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
ALLOW_DEPRECATED_DECLARATIONS_END
}
- (id)remoteAccessibilityParentObject
{
auto* backingObject = self.axBackingObject;
return backingObject ? backingObject->remoteParentObject() : nil;
}
static void convertToVector(NSArray* array, AccessibilityObject::AccessibilityChildrenVector& vector)
{
unsigned length = [array count];
vector.reserveInitialCapacity(length);
for (unsigned i = 0; i < length; ++i) {
AXCoreObject* obj = [[array objectAtIndex:i] axBackingObject];
if (obj)
vector.append(obj);
}
}
- (AXTextMarkerRangeRef)selectedTextMarkerRange
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<AXTextMarkerRangeRef>([protectedSelf = retainPtr(self)] () -> RetainPtr<AXTextMarkerRangeRef> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto selectedVisiblePositionRange = backingObject->selectedVisiblePositionRange();
if (selectedVisiblePositionRange.isNull())
return nil;
return textMarkerRangeFromVisiblePositions(backingObject->axObjectCache(), selectedVisiblePositionRange.start, selectedVisiblePositionRange.end);
});
}
- (id)associatedPluginParent
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject || !backingObject->hasApplePDFAnnotationAttribute())
return nil;
if (!backingObject->document()->isPluginDocument())
return nil;
Widget* pluginWidget = static_cast<PluginDocument*>(backingObject->document())->pluginWidget();
if (!pluginWidget || !pluginWidget->isPluginViewBase())
return nil;
return static_cast<PluginViewBase*>(pluginWidget)->accessibilityAssociatedPluginParentForElement(backingObject->element());
});
}
static void WebTransformCGPathToNSBezierPath(void* info, const CGPathElement *element)
{
NSBezierPath *bezierPath = (__bridge NSBezierPath *)info;
switch (element->type) {
case kCGPathElementMoveToPoint:
[bezierPath moveToPoint:NSPointFromCGPoint(element->points[0])];
break;
case kCGPathElementAddLineToPoint:
[bezierPath lineToPoint:NSPointFromCGPoint(element->points[0])];
break;
case kCGPathElementAddCurveToPoint:
[bezierPath curveToPoint:NSPointFromCGPoint(element->points[0]) controlPoint1:NSPointFromCGPoint(element->points[1]) controlPoint2:NSPointFromCGPoint(element->points[2])];
break;
case kCGPathElementCloseSubpath:
[bezierPath closePath];
break;
default:
break;
}
}
- (NSBezierPath *)bezierPathFromPath:(CGPathRef)path
{
NSBezierPath *bezierPath = [NSBezierPath bezierPath];
CGPathApply(path, (__bridge void*)bezierPath, WebTransformCGPathToNSBezierPath);
return bezierPath;
}
- (NSBezierPath *)path
{
Path path = self.axBackingObject->elementPath();
if (path.isEmpty())
return NULL;
CGPathRef transformedPath = [self convertPathToScreenSpace:path];
return [self bezierPathFromPath:transformedPath];
}
- (NSNumber *)primaryScreenHeight
{
// The rect for primary screen should not change in normal use. So cache it
// here to avoid hitting the main thread repeatedly.
static FloatRect screenRect = Accessibility::retrieveValueFromMainThread<FloatRect>([] () -> FloatRect {
return screenRectForPrimaryScreen();
});
return [NSNumber numberWithFloat:screenRect.height()];
}
- (size_t)childrenVectorSize
{
return self.axBackingObject->children().size();
}
- (NSArray<WebAccessibilityObjectWrapper *> *)childrenVectorArray
{
return makeNSArray(self.axBackingObject->children());
}
- (NSValue *)position
{
auto* backingObject = self.axBackingObject;
if (!backingObject)
return nil;
auto rect = snappedIntRect(backingObject->elementRect());
// The Cocoa accessibility API wants the lower-left corner.
FloatPoint floatPoint(rect.x(), rect.maxY());
// FIXME: add a function to convert a point, no need to convert a rect when you only need a point.
FloatRect floatRect(floatPoint, FloatSize());
CGRect cgRect(backingObject->convertRectToPlatformSpace(floatRect, AccessibilityConversionSpace::Screen));
return @(cgRect.origin);
}
- (NSString*)role
{
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (self.axBackingObject->isAttachment())
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleAttribute];
ALLOW_DEPRECATED_DECLARATIONS_END
NSString *string = self.axBackingObject->rolePlatformString();
if (string.length)
return string;
return NSAccessibilityUnknownRole;
}
- (BOOL)isEmptyGroup
{
auto* backingObject = self.axBackingObject;
if (!backingObject)
return false;
#if ENABLE(MODEL_ELEMENT)
if (backingObject->isModel())
return false;
#endif
return [[self role] isEqual:NSAccessibilityGroupRole]
&& backingObject->children().isEmpty()
&& ![[self renderWidgetChildren] count];
}
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
- (NSString*)subrole
{
auto* backingObject = self.axBackingObject;
if (!backingObject)
return nil;
if ([self isEmptyGroup])
return @"AXEmptyGroup";
auto subrole = backingObject->subrolePlatformString();
if (!subrole.isEmpty())
return subrole;
return nil;
}
ALLOW_DEPRECATED_DECLARATIONS_END
- (NSString*)roleDescription
{
auto* backingObject = self.axBackingObject;
if (!backingObject)
return nil;
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
// attachments have the AXImage role, but a different subrole
if (backingObject->isAttachment())
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleDescriptionAttribute];
ALLOW_DEPRECATED_DECLARATIONS_END
String roleDescription = backingObject->roleDescription();
if (!roleDescription.isEmpty())
return roleDescription;
NSString *axRole = backingObject->rolePlatformString();
NSString *subrole = self.subrole;
// Fallback to the system default role description.
// If we get the same string back, then as a last resort, return unknown.
NSString *defaultRoleDescription = NSAccessibilityRoleDescription(axRole, subrole);
// On earlier Mac versions (Lion), using a non-standard subrole would result in a role description
// being returned that looked like AXRole:AXSubrole. To make all platforms have the same role descriptions
// we should fallback on a role description ignoring the subrole in these cases.
if ([defaultRoleDescription isEqualToString:[NSString stringWithFormat:@"%@:%@", axRole, subrole]])
defaultRoleDescription = NSAccessibilityRoleDescription(axRole, nil);
if (![defaultRoleDescription isEqualToString:axRole])
return defaultRoleDescription;
return NSAccessibilityRoleDescription(NSAccessibilityUnknownRole, nil);
}
- (NSString *)computedRoleString
{
if (!self.axBackingObject)
return nil;
return self.axBackingObject->computedRoleString();
}
- (id)scrollViewParent
{
auto* backingObject = self.axBackingObject;
if (!backingObject || !backingObject->isScrollView())
return nil;
// If this scroll view provides it's parent object (because it's a sub-frame), then
// we should not find the remoteAccessibilityParent.
if (backingObject->parentObject())
return nil;
if (auto platformWidget = backingObject->platformWidget())
return NSAccessibilityUnignoredAncestor(platformWidget);
return backingObject->remoteParentObject();
}
- (id)windowElement:(NSString*)attributeName
{
if (id remoteParent = self.remoteAccessibilityParentObject) {
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
return [remoteParent accessibilityAttributeValue:attributeName];
ALLOW_DEPRECATED_DECLARATIONS_END
}
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
if (auto* fv = backingObject->documentFrameView())
return [fv->platformWidget() window];
return nil;
});
}
// FIXME: split up this function in a better way.
// suggestions: Use a hash table that maps attribute names to function calls,
// or maybe pointers to member functions
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (id)accessibilityAttributeValue:(NSString*)attributeName
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
AXTRACE(makeString("WebAccessibilityObjectWrapper accessibilityAttributeValue:", String(attributeName)));
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject) {
AXLOG("No backingObject!!!");
return nil;
}
if (backingObject->isDetachedFromParent()) {
AXLOG("backingObject is detached from parent!!!");
return nil;
}
if ([attributeName isEqualToString: NSAccessibilityRoleAttribute])
return [self role];
if ([attributeName isEqualToString: NSAccessibilitySubroleAttribute])
return [self subrole];
if ([attributeName isEqualToString: NSAccessibilityRoleDescriptionAttribute])
return [self roleDescription];
// AXARIARole is only used by DumpRenderTree (so far).
if ([attributeName isEqualToString:@"AXARIARole"])
return [self computedRoleString];
if ([attributeName isEqualToString: NSAccessibilityParentAttribute]) {
// This will return the parent of the AXWebArea, if this is a web area.
id scrollViewParent = [self scrollViewParent];
if (scrollViewParent)
return scrollViewParent;
// Tree item (changed to AXRows) can only report the tree (AXOutline) as its parent.
if (backingObject->isTreeItem()) {
auto parent = backingObject->parentObjectUnignored();
while (parent) {
if (parent->isTree())
return parent->wrapper();
parent = parent->parentObjectUnignored();
}
}
auto parent = backingObject->parentObjectUnignored();
if (!parent)
return nil;
// In WebKit1, the scroll view is provided by the system (the attachment view), so the parent
// should be reported directly as such.
if (backingObject->isWebArea() && parent->isAttachment())
return [parent->wrapper() attachmentView];
return parent->wrapper();
}
if ([attributeName isEqualToString:NSAccessibilityChildrenAttribute] || [attributeName isEqualToString:NSAccessibilityChildrenInNavigationOrderAttribute]) {
#if ENABLE(MODEL_ELEMENT)
if (backingObject->isModel()) {
auto modelChildren = backingObject->modelElementChildren();
if (modelChildren.size()) {
return createNSArray(modelChildren, [] (auto& child) -> id {
return child.get();
}).autorelease();
}
}
#endif
if (!self.childrenVectorSize) {
if (NSArray *children = [self renderWidgetChildren])
return children;
}
// The tree's (AXOutline) children are supposed to be its rows and columns.
// The ARIA spec doesn't have columns, so we just need rows.
if (backingObject->isTree())
return [self accessibilityAttributeValue:NSAccessibilityRowsAttribute];
// A tree item should only expose its content as its children (not its rows)
if (backingObject->isTreeItem()) {
AccessibilityObject::AccessibilityChildrenVector contentCopy;
backingObject->ariaTreeItemContent(contentCopy);
return makeNSArray(contentCopy);
}
return self.childrenVectorArray;
}
if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) {
if (backingObject->canHaveSelectedChildren()) {
AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy;
backingObject->selectedChildren(selectedChildrenCopy);
return makeNSArray(selectedChildrenCopy);
}
return nil;
}
if ([attributeName isEqualToString: NSAccessibilityVisibleChildrenAttribute]) {
if (backingObject->isListBox()) {
AccessibilityObject::AccessibilityChildrenVector visibleChildrenCopy;
backingObject->visibleChildren(visibleChildrenCopy);
return makeNSArray(visibleChildrenCopy);
}
if (backingObject->isList())
return [self accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
return nil;
}
if (backingObject->isWebArea()) {
if ([attributeName isEqualToString:@"AXLinkUIElements"])
return makeNSArray(backingObject->documentLinks());
if ([attributeName isEqualToString:@"AXLoaded"])
return [NSNumber numberWithBool:backingObject->isLoaded()];
if ([attributeName isEqualToString:@"AXLayoutCount"])
return @(backingObject->layoutCount());
if ([attributeName isEqualToString:NSAccessibilityLoadingProgressAttribute])
return @(backingObject->loadingProgress());
if ([attributeName isEqualToString:NSAccessibilityPreventKeyboardDOMEventDispatchAttribute])
return [NSNumber numberWithBool:backingObject->preventKeyboardDOMEventDispatch()];
if ([attributeName isEqualToString:NSAccessibilityCaretBrowsingEnabledAttribute])
return [NSNumber numberWithBool:backingObject->caretBrowsingEnabled()];
if ([attributeName isEqualToString:NSAccessibilityWebSessionIDAttribute])
return @(backingObject->sessionID().toUInt64());
}
if (backingObject->isTextControl()) {
if ([attributeName isEqualToString: NSAccessibilityNumberOfCharactersAttribute]) {
int length = backingObject->textLength();
if (length < 0)
return nil;
return @(length);
}
if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) {
String selectedText = backingObject->selectedText();
if (selectedText.isNull())
return nil;
return (NSString*)selectedText;
}
if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) {
PlainTextRange textRange = backingObject->selectedTextRange();
return [NSValue valueWithRange:NSMakeRange(textRange.start, textRange.length)];
}
if ([attributeName isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) {
int lineNumber = backingObject->insertionPointLineNumber();
return lineNumber >= 0 ? @(lineNumber) : nil;
}
}
if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) {
if (backingObject->isPasswordField())
return nil;
// TODO: Get actual visible range. <rdar://problem/4712101>
if (backingObject->isTextControl())
return [NSValue valueWithRange:NSMakeRange(0, backingObject->textLength())];
return [NSValue valueWithRange:[self accessibilityVisibleCharacterRange]];
}
if ([attributeName isEqualToString: NSAccessibilityURLAttribute]) {
URL url = backingObject->url();
if (url.isNull())
return nil;
return (NSURL*)url;
}
if ([attributeName isEqualToString:NSAccessibilityIncrementButtonAttribute]) {
auto incrementButton = backingObject->incrementButton();
return incrementButton ? incrementButton->wrapper() : nil;
}
if ([attributeName isEqualToString:NSAccessibilityDecrementButtonAttribute]) {
auto decrementButton = backingObject->decrementButton();
return decrementButton ? decrementButton->wrapper() : nil;
}
if ([attributeName isEqualToString: @"AXVisited"])
return [NSNumber numberWithBool: backingObject->isVisited()];
if ([attributeName isEqualToString: NSAccessibilityTitleAttribute]) {
if (backingObject->isAttachment()) {
if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityTitleAttribute])
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityTitleAttribute];
}
return backingObject->titleAttributeValue();
}
if ([attributeName isEqualToString: NSAccessibilityDescriptionAttribute]) {
if (backingObject->isAttachment()) {
if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityDescriptionAttribute])
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityDescriptionAttribute];
}
return [self baseAccessibilityDescription];
}
if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) {
if (backingObject->isAttachment()) {
if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityValueAttribute])
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityValueAttribute];
}
auto value = backingObject->value();
return WTF::switchOn(value,
[] (bool& typedValue) -> id { return @(typedValue); },
[] (unsigned& typedValue) -> id { return @(typedValue); },
[] (float& typedValue) -> id { return @(typedValue); },
[] (String& typedValue) -> id { return (NSString *)typedValue; },
[] (AccessibilityButtonState& typedValue) -> id { return @((unsigned)typedValue); },
[] (AXCoreObject*& typedValue) { return typedValue ? (id)typedValue->wrapper() : nil; },
[] (auto&) { return nil; }
);
}
if ([attributeName isEqualToString:(NSString *)kAXMenuItemMarkCharAttribute]) {
const unichar ch = 0x2713; // ✓ used on Mac for selected menu items.
return (backingObject->isChecked()) ? [NSString stringWithCharacters:&ch length:1] : nil;
}
if ([attributeName isEqualToString: NSAccessibilityMinValueAttribute]) {
// Indeterminate progress indicator should return 0.
if (backingObject->ariaRoleAttribute() == AccessibilityRole::ProgressIndicator && !backingObject->hasARIAValueNow())
return @0;
return [NSNumber numberWithFloat:backingObject->minValueForRange()];
}
if ([attributeName isEqualToString: NSAccessibilityMaxValueAttribute]) {
// Indeterminate progress indicator should return 0.
if (backingObject->ariaRoleAttribute() == AccessibilityRole::ProgressIndicator && !backingObject->hasARIAValueNow())
return @0;
return [NSNumber numberWithFloat:backingObject->maxValueForRange()];
}
if ([attributeName isEqualToString: NSAccessibilityHelpAttribute])
return [self baseAccessibilityHelpText];
if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute])
return [NSNumber numberWithBool: backingObject->isFocused()];
if ([attributeName isEqualToString: NSAccessibilityEnabledAttribute])
return [NSNumber numberWithBool: backingObject->isEnabled()];
if ([attributeName isEqualToString: NSAccessibilitySizeAttribute]) {
IntSize s = snappedIntRect(backingObject->elementRect()).size();
return [NSValue valueWithSize: NSMakeSize(s.width(), s.height())];
}
if ([attributeName isEqualToString: NSAccessibilityPrimaryScreenHeightAttribute])
return [self primaryScreenHeight];
if ([attributeName isEqualToString: NSAccessibilityPositionAttribute])
return [self position];
if ([attributeName isEqualToString:NSAccessibilityPathAttribute])
return [self path];
if ([attributeName isEqualToString:@"AXLineRectsAndText"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<NSArray *>([protectedSelf = retainPtr(self)] () -> RetainPtr<NSArray> {
return protectedSelf.get().lineRectsAndText;
});
}
if ([attributeName isEqualToString:NSAccessibilityImageOverlayElementsAttribute]) {
auto imageOverlayElements = backingObject->imageOverlayElements();
return imageOverlayElements ? makeNSArray(*imageOverlayElements) : nil;
}
if ([attributeName isEqualToString:NSAccessibilityEmbeddedImageDescriptionAttribute])
return backingObject->embeddedImageDescription();
if ([attributeName isEqualToString:NSAccessibilityWindowAttribute]
|| [attributeName isEqualToString:NSAccessibilityTopLevelUIElementAttribute])
return [self windowElement:attributeName];
if ([attributeName isEqualToString:NSAccessibilityAccessKeyAttribute]) {
AtomString accessKey = backingObject->accessKey();
if (accessKey.isNull())
return nil;
return accessKey;
}
if ([attributeName isEqualToString:NSAccessibilityLinkRelationshipTypeAttribute])
return backingObject->linkRelValue();
if ([attributeName isEqualToString:NSAccessibilityTabsAttribute]) {
if (backingObject->isTabList()) {
AccessibilityObject::AccessibilityChildrenVector tabsChildren;
backingObject->tabChildren(tabsChildren);
return makeNSArray(tabsChildren);
}
}
if ([attributeName isEqualToString:NSAccessibilityContentsAttribute])
return makeNSArray(backingObject->contents());
if (backingObject->isTable() && backingObject->isExposable()) {
if ([attributeName isEqualToString:NSAccessibilityRowsAttribute])
return makeNSArray(backingObject->rows());
if ([attributeName isEqualToString:NSAccessibilityVisibleRowsAttribute])
return makeNSArray(backingObject->visibleRows());
// TODO: distinguish between visible and non-visible columns
if ([attributeName isEqualToString:NSAccessibilityColumnsAttribute]
|| [attributeName isEqualToString:NSAccessibilityVisibleColumnsAttribute])
return makeNSArray(backingObject->columns());
if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy;
backingObject->selectedChildren(selectedChildrenCopy);
return makeNSArray(selectedChildrenCopy);
}
// HTML tables don't support these attributes.
if ([attributeName isEqualToString:NSAccessibilitySelectedColumnsAttribute]
|| [attributeName isEqualToString:NSAccessibilitySelectedCellsAttribute])
return nil;
if ([attributeName isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute])
return makeNSArray(backingObject->columnHeaders());
if ([attributeName isEqualToString:NSAccessibilityHeaderAttribute]) {
auto* headerContainer = backingObject->headerContainer();
return headerContainer ? headerContainer->wrapper() : nil;
}
if ([attributeName isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute])
return makeNSArray(backingObject->rowHeaders());
if ([attributeName isEqualToString:NSAccessibilityVisibleCellsAttribute])
return makeNSArray(backingObject->cells());
if ([attributeName isEqualToString:NSAccessibilityColumnCountAttribute])
return @(backingObject->columnCount());
if ([attributeName isEqualToString:NSAccessibilityRowCountAttribute])
return @(backingObject->rowCount());
if ([attributeName isEqualToString:NSAccessibilityARIAColumnCountAttribute])
return @(backingObject->axColumnCount());
if ([attributeName isEqualToString:NSAccessibilityARIARowCountAttribute])
return @(backingObject->axRowCount());
}
if (backingObject->isTableColumn()) {
if ([attributeName isEqualToString:NSAccessibilityIndexAttribute])
return @(backingObject->columnIndex());
// rows attribute for a column is the list of all the elements in that column at each row
if ([attributeName isEqualToString:NSAccessibilityRowsAttribute]
|| [attributeName isEqualToString:NSAccessibilityVisibleRowsAttribute])
return makeNSArray(backingObject->children());
if ([attributeName isEqualToString:NSAccessibilityHeaderAttribute]) {
auto* header = backingObject->columnHeader();
return header ? header->wrapper() : nil;
}
}
if (backingObject->isTableCell()) {
if ([attributeName isEqualToString:NSAccessibilityRowIndexRangeAttribute]) {
auto rowRange = backingObject->rowIndexRange();
return [NSValue valueWithRange:NSMakeRange(rowRange.first, rowRange.second)];
}
if ([attributeName isEqualToString:NSAccessibilityColumnIndexRangeAttribute]) {
auto columnRange = backingObject->columnIndexRange();
return [NSValue valueWithRange:NSMakeRange(columnRange.first, columnRange.second)];
}
if ([attributeName isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute])
return makeNSArray(backingObject->columnHeaders());
if ([attributeName isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute])
return makeNSArray(backingObject->rowHeaders());
if ([attributeName isEqualToString:NSAccessibilityARIAColumnIndexAttribute])
return @(backingObject->axColumnIndex());
if ([attributeName isEqualToString:NSAccessibilityARIARowIndexAttribute])
return @(backingObject->axRowIndex());
}
if (backingObject->isTree()) {
if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy;
backingObject->selectedChildren(selectedChildrenCopy);
return makeNSArray(selectedChildrenCopy);
}
if ([attributeName isEqualToString:NSAccessibilityRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector rowsCopy;
backingObject->ariaTreeRows(rowsCopy);
return makeNSArray(rowsCopy);
}
// TreeRoles do not support columns, but Mac AX expects to be able to ask about columns at the least.
if ([attributeName isEqualToString:NSAccessibilityColumnsAttribute])
return @[];
}
if ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) {
if (backingObject->isTreeItem()) {
AXCoreObject* parent = backingObject->parentObject();
for (; parent && !parent->isTree(); parent = parent->parentObject())
{ }
if (!parent)
return nil;
// Find the index of this item by iterating the parents.
AccessibilityObject::AccessibilityChildrenVector rowsCopy;
parent->ariaTreeRows(rowsCopy);
size_t count = rowsCopy.size();
for (size_t k = 0; k < count; ++k)
if (rowsCopy[k]->wrapper() == self)
return @(k);
return nil;
}
if (backingObject->isTableRow())
return @(backingObject->rowIndex());
}
// The rows that are considered inside this row.
if ([attributeName isEqualToString:NSAccessibilityDisclosedRowsAttribute]) {
if (backingObject->isTreeItem() || backingObject->isARIATreeGridRow())
return makeNSArray(backingObject->disclosedRows());
}
// The row that contains this row. It should be the same as the first parent that is a treeitem.
if ([attributeName isEqualToString:NSAccessibilityDisclosedByRowAttribute]) {
if (backingObject->isTreeItem()) {
AXCoreObject* parent = backingObject->parentObject();
while (parent) {
if (parent->isTreeItem())
return parent->wrapper();
// If the parent is the tree itself, then this value == nil.
if (parent->isTree())
return nil;
parent = parent->parentObject();
}
return nil;
}
if (backingObject->isARIATreeGridRow()) {
auto* row = backingObject->disclosedByRow();
return row ? row->wrapper() : nil;
}
}
if ([attributeName isEqualToString:NSAccessibilityDisclosureLevelAttribute]) {
// Convert from 1-based level (from aria-level spec) to 0-based level (Mac)
int level = backingObject->hierarchicalLevel();
if (level > 0)
level -= 1;
return @(level);
}
if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute])
return [NSNumber numberWithBool:backingObject->isExpanded()];
if (backingObject->isList() && [attributeName isEqualToString:NSAccessibilityOrientationAttribute])
return NSAccessibilityVerticalOrientationValue;
if ([attributeName isEqualToString:@"AXSelectedTextMarkerRange"])
return (id)[self selectedTextMarkerRange];
if ([attributeName isEqualToString:@"AXStartTextMarker"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
return (id)textMarkerForVisiblePosition(backingObject->axObjectCache(), startOfDocument(backingObject->document()));
});
}
if ([attributeName isEqualToString:@"AXEndTextMarker"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
return (id)textMarkerForVisiblePosition(backingObject->axObjectCache(), endOfDocument(backingObject->document()));
});
}
if ([attributeName isEqualToString:NSAccessibilityBlockQuoteLevelAttribute])
return @(backingObject->blockquoteLevel());
if ([attributeName isEqualToString:@"AXTableLevel"])
return @(backingObject->tableLevel());
if ([attributeName isEqualToString: NSAccessibilityLinkedUIElementsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector linkedUIElements;
backingObject->linkedUIElements(linkedUIElements);
return makeNSArray(linkedUIElements);
}
if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute])
return [NSNumber numberWithBool:backingObject->isSelected()];
if ([attributeName isEqualToString: NSAccessibilityARIACurrentAttribute])
return backingObject->currentValue();
if ([attributeName isEqualToString: NSAccessibilityServesAsTitleForUIElementsAttribute] && backingObject->isMenuButton()) {
AccessibilityObject* uiElement = downcast<AccessibilityRenderObject>(*backingObject).menuForMenuButton();
if (uiElement)
return @[uiElement->wrapper()];
}
if ([attributeName isEqualToString:NSAccessibilityTitleUIElementAttribute]) {
auto* object = backingObject->titleUIElement();
return object ? object->wrapper() : nil;
}
if ([attributeName isEqualToString:NSAccessibilityValueDescriptionAttribute])
return backingObject->valueDescription();
if ([attributeName isEqualToString:NSAccessibilityOrientationAttribute]) {
AccessibilityOrientation elementOrientation = backingObject->orientation();
if (elementOrientation == AccessibilityOrientation::Vertical)
return NSAccessibilityVerticalOrientationValue;
if (elementOrientation == AccessibilityOrientation::Horizontal)
return NSAccessibilityHorizontalOrientationValue;
if (elementOrientation == AccessibilityOrientation::Undefined)
return NSAccessibilityUnknownOrientationValue;
return nil;
}
if ([attributeName isEqualToString:NSAccessibilityHorizontalScrollBarAttribute]) {
AXCoreObject* scrollBar = backingObject->scrollBar(AccessibilityOrientation::Horizontal);
if (scrollBar)
return scrollBar->wrapper();
return nil;
}
if ([attributeName isEqualToString:NSAccessibilityVerticalScrollBarAttribute]) {
AXCoreObject* scrollBar = backingObject->scrollBar(AccessibilityOrientation::Vertical);
if (scrollBar)
return scrollBar->wrapper();
return nil;
}
if ([attributeName isEqualToString:NSAccessibilitySortDirectionAttribute]) {
switch (backingObject->sortDirection()) {
case AccessibilitySortDirection::Ascending:
return NSAccessibilityAscendingSortDirectionValue;
case AccessibilitySortDirection::Descending:
return NSAccessibilityDescendingSortDirectionValue;
default:
return NSAccessibilityUnknownSortDirectionValue;
}
}
if ([attributeName isEqualToString:NSAccessibilityLanguageAttribute])
return backingObject->language();
if ([attributeName isEqualToString:NSAccessibilityExpandedAttribute])
return [NSNumber numberWithBool:backingObject->isExpanded()];
if ([attributeName isEqualToString:NSAccessibilityRequiredAttribute])
return [NSNumber numberWithBool:backingObject->isRequired()];
if ([attributeName isEqualToString:NSAccessibilityInvalidAttribute])
return backingObject->invalidStatus();
if ([attributeName isEqualToString:NSAccessibilityOwnsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector ariaOwns;
backingObject->ariaOwnsElements(ariaOwns);
return makeNSArray(ariaOwns);
}
if ([attributeName isEqualToString:NSAccessibilityARIAPosInSetAttribute])
return @(backingObject->posInSet());
if ([attributeName isEqualToString:NSAccessibilityARIASetSizeAttribute])
return @(backingObject->setSize());
if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute])
return [NSNumber numberWithBool:backingObject->isGrabbed()];
if ([attributeName isEqualToString:NSAccessibilityDropEffectsAttribute])
return createNSArray(backingObject->determineDropEffects()).autorelease();
if ([attributeName isEqualToString:NSAccessibilityPlaceholderValueAttribute])
return backingObject->placeholderValue();
if ([attributeName isEqualToString:NSAccessibilityValueAutofillAvailableAttribute])
return @(backingObject->isValueAutofillAvailable());
if ([attributeName isEqualToString:NSAccessibilityValueAutofillTypeAttribute]) {
switch (backingObject->valueAutofillButtonType()) {
case AutoFillButtonType::None:
return @"none";
case AutoFillButtonType::Credentials:
return @"credentials";
case AutoFillButtonType::Contacts:
return @"contacts";
case AutoFillButtonType::StrongPassword:
return @"strong password";
case AutoFillButtonType::CreditCard:
return @"credit card";
}
}
if ([attributeName isEqualToString:NSAccessibilityValueAutofilledAttribute])
return @(backingObject->isValueAutofilled());
if ([attributeName isEqualToString:NSAccessibilityHasPopupAttribute])
return [NSNumber numberWithBool:backingObject->hasPopup()];
if ([attributeName isEqualToString:NSAccessibilityDatetimeValueAttribute])
return backingObject->datetimeAttributeValue();
if ([attributeName isEqualToString:NSAccessibilityInlineTextAttribute])
return @(backingObject->isInlineText());
// ARIA Live region attributes.
if ([attributeName isEqualToString:NSAccessibilityARIALiveAttribute])
return backingObject->liveRegionStatus();
if ([attributeName isEqualToString:NSAccessibilityARIARelevantAttribute])
return backingObject->liveRegionRelevant();
if ([attributeName isEqualToString:NSAccessibilityARIAAtomicAttribute])
return [NSNumber numberWithBool:backingObject->liveRegionAtomic()];
if ([attributeName isEqualToString:NSAccessibilityElementBusyAttribute])
return [NSNumber numberWithBool:backingObject->isBusy()];
// MathML Attributes.
if (backingObject->isMathElement()) {
if ([attributeName isEqualToString:NSAccessibilityMathRootIndexAttribute]) {
auto* rootIndex = backingObject->mathRootIndexObject();
return rootIndex ? rootIndex->wrapper() : nil;
}
if ([attributeName isEqualToString:NSAccessibilityMathRootRadicandAttribute]) {
auto radicand = backingObject->mathRadicand();
return radicand ? makeNSArray(*radicand) : nil;
}
if ([attributeName isEqualToString:NSAccessibilityMathFractionNumeratorAttribute])
return (backingObject->mathNumeratorObject()) ? backingObject->mathNumeratorObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathFractionDenominatorAttribute])
return (backingObject->mathDenominatorObject()) ? backingObject->mathDenominatorObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathBaseAttribute])
return (backingObject->mathBaseObject()) ? backingObject->mathBaseObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathSubscriptAttribute])
return (backingObject->mathSubscriptObject()) ? backingObject->mathSubscriptObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathSuperscriptAttribute])
return (backingObject->mathSuperscriptObject()) ? backingObject->mathSuperscriptObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathUnderAttribute])
return (backingObject->mathUnderObject()) ? backingObject->mathUnderObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathOverAttribute])
return (backingObject->mathOverObject()) ? backingObject->mathOverObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathFencedOpenAttribute])
return backingObject->mathFencedOpenString();
if ([attributeName isEqualToString:NSAccessibilityMathFencedCloseAttribute])
return backingObject->mathFencedCloseString();
if ([attributeName isEqualToString:NSAccessibilityMathLineThicknessAttribute])
return [NSNumber numberWithInteger:backingObject->mathLineThickness()];
if ([attributeName isEqualToString:NSAccessibilityMathPostscriptsAttribute])
return [self accessibilityMathPostscriptPairs];
if ([attributeName isEqualToString:NSAccessibilityMathPrescriptsAttribute])
return [self accessibilityMathPrescriptPairs];
}
if ([attributeName isEqualToString:NSAccessibilityExpandedTextValueAttribute])
return backingObject->expandedTextValue();
if ([attributeName isEqualToString:NSAccessibilityDOMIdentifierAttribute])
return backingObject->identifierAttribute();
if ([attributeName isEqualToString:NSAccessibilityDOMClassListAttribute]) {
Vector<String> classList;
backingObject->classList(classList);
return createNSArray(classList).autorelease();
}
if ([attributeName isEqualToString:@"AXResolvedEditingStyles"])
return [self baseAccessibilityResolvedEditingStyles];
// This allows us to connect to a plugin that creates a shadow node for editing (like PDFs).
if ([attributeName isEqualToString:@"_AXAssociatedPluginParent"])
return [self associatedPluginParent];
// this is used only by DumpRenderTree for testing
if ([attributeName isEqualToString:@"AXClickPoint"])
return [NSValue valueWithPoint:backingObject->clickPoint()];
// This is used by DRT to verify CSS3 speech works.
if ([attributeName isEqualToString:@"AXDRTSpeechAttribute"])
return [self baseAccessibilitySpeechHint];
if ([attributeName isEqualToString:@"AXAutocompleteValue"])
return backingObject->autoCompleteValue();
if ([attributeName isEqualToString:NSAccessibilityPopupValueAttribute])
return backingObject->popupValue();
if ([attributeName isEqualToString:@"AXKeyShortcutsValue"])
return backingObject->keyShortcutsValue();
if ([attributeName isEqualToString:@"AXARIAPressedIsPresent"])
return [NSNumber numberWithBool:backingObject->pressedIsPresent()];
if ([attributeName isEqualToString:@"AXIsMultiline"])
return [NSNumber numberWithBool:backingObject->ariaIsMultiline()];
if ([attributeName isEqualToString:@"AXReadOnlyValue"])
return backingObject->readOnlyValue();
if ([attributeName isEqualToString:@"AXIsActiveDescendantOfFocusedContainer"])
return [NSNumber numberWithBool:backingObject->isActiveDescendantOfFocusedContainer()];
if ([attributeName isEqualToString:AXHasDocumentRoleAncestorAttribute])
return [NSNumber numberWithBool:backingObject->hasDocumentRoleAncestor()];
if ([attributeName isEqualToString:AXHasWebApplicationAncestorAttribute])
return [NSNumber numberWithBool:backingObject->hasWebApplicationAncestor()];
if ([attributeName isEqualToString:@"AXIsInDescriptionListDetail"])
return [NSNumber numberWithBool:backingObject->isInDescriptionListDetail()];
if ([attributeName isEqualToString:@"AXIsInDescriptionListTerm"])
return [NSNumber numberWithBool:backingObject->isInDescriptionListTerm()];
if ([attributeName isEqualToString:@"AXIsInCell"])
return [NSNumber numberWithBool:backingObject->isInCell()];
if ([attributeName isEqualToString:@"AXDetailsElements"]) {
AccessibilityObject::AccessibilityChildrenVector details;
backingObject->ariaDetailsElements(details);
return makeNSArray(details);
}
if ([attributeName isEqualToString:NSAccessibilityBrailleLabelAttribute])
return backingObject->brailleLabel();
if ([attributeName isEqualToString:NSAccessibilityBrailleRoleDescriptionAttribute])
return backingObject->brailleRoleDescription();
if ([attributeName isEqualToString:NSAccessibilityRelativeFrameAttribute])
return [NSValue valueWithRect:(NSRect)backingObject->relativeFrame()];
if ([attributeName isEqualToString:@"AXErrorMessageElements"]) {
AccessibilityObject::AccessibilityChildrenVector errorMessages;
backingObject->ariaErrorMessageElements(errorMessages);
return makeNSArray(errorMessages);
}
// Multi-selectable
if ([attributeName isEqualToString:NSAccessibilityIsMultiSelectableAttribute])
return [NSNumber numberWithBool:backingObject->isMultiSelectable()];
// Document attributes
if ([attributeName isEqualToString:NSAccessibilityDocumentURIAttribute])
return backingObject->documentURI();
if ([attributeName isEqualToString:NSAccessibilityDocumentEncodingAttribute])
return backingObject->documentEncoding();
// Aria controls element
if ([attributeName isEqualToString:NSAccessibilityAriaControlsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector ariaControls;
backingObject->ariaControlsElements(ariaControls);
return makeNSArray(ariaControls);
}
if ([attributeName isEqualToString:NSAccessibilityFocusableAncestorAttribute]) {
AXCoreObject* object = backingObject->focusableAncestor();
return object ? object->wrapper() : nil;
}
if ([attributeName isEqualToString:NSAccessibilityEditableAncestorAttribute]) {
AXCoreObject* object = backingObject->editableAncestor();
return object ? object->wrapper() : nil;
}
if ([attributeName isEqualToString:NSAccessibilityHighestEditableAncestorAttribute]) {
AXCoreObject* object = backingObject->highestEditableAncestor();
return object ? object->wrapper() : nil;
}
if ([attributeName isEqualToString:@"AXIsOnScreen"])
return [NSNumber numberWithBool:backingObject->isOnScreen()];
return nil;
}
- (NSString *)accessibilityPlatformMathSubscriptKey
{
return NSAccessibilityMathSubscriptAttribute;
}
- (NSString *)accessibilityPlatformMathSuperscriptKey
{
return NSAccessibilityMathSuperscriptAttribute;
}
- (id)accessibilityFocusedUIElement
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return nil;
auto focusedObject = backingObject->focusedUIElement();
return focusedObject ? focusedObject->wrapper() : nil;
}
- (id)accessibilityHitTest:(NSPoint)point
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return nil;
backingObject->updateChildrenIfNecessary();
auto* axObject = backingObject->accessibilityHitTest(IntPoint(point));
id hit = nil;
if (axObject) {
if (axObject->isAttachment() && [axObject->wrapper() attachmentView])
return [axObject->wrapper() attachmentView];
hit = Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&axObject, &point] () -> RetainPtr<id> {
auto* widget = axObject->widget();
if (is<PluginViewBase>(widget))
return widget->accessibilityHitTest(IntPoint(point));
return nil;
});
if (!hit)
hit = axObject->wrapper();
} else
hit = self;
return NSAccessibilityUnignoredAncestor(hit);
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (BOOL)accessibilityIsAttributeSettable:(NSString*)attributeName
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return NO;
if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"])
return YES;
if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute])
return backingObject->canSetFocusAttribute();
if ([attributeName isEqualToString: NSAccessibilityValueAttribute])
return backingObject->canSetValueAttribute();
if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute])
return backingObject->canSetSelectedAttribute();
if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute])
return backingObject->canSetSelectedChildren();
if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute]
|| [attributeName isEqualToString:NSAccessibilityExpandedAttribute])
return backingObject->canSetExpandedAttribute();
if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute])
return YES;
if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]
|| [attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]
|| [attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute])
return backingObject->canSetTextRangeAttributes();
if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute])
return YES;
if (backingObject->isWebArea()
&& ([attributeName isEqualToString:NSAccessibilityPreventKeyboardDOMEventDispatchAttribute]
|| [attributeName isEqualToString:NSAccessibilityCaretBrowsingEnabledAttribute]))
return YES;
return NO;
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (BOOL)accessibilityIsIgnored
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return YES;
if (backingObject->isAttachment())
return [[self attachmentView] accessibilityIsIgnored];
return backingObject->accessibilityIsIgnored();
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (NSArray* )accessibilityParameterizedAttributeNames
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return nil;
if (backingObject->isAttachment())
return nil;
static NSArray *paramAttrs;
static NSArray *textParamAttrs;
static NSArray *tableParamAttrs;
static NSArray *webAreaParamAttrs;
if (paramAttrs == nil) {
paramAttrs = [[NSArray alloc] initWithObjects:
@"AXUIElementForTextMarker",
@"AXTextMarkerRangeForUIElement",
@"AXLineForTextMarker",
@"AXTextMarkerRangeForLine",
@"AXStringForTextMarkerRange",
@"AXTextMarkerForPosition",
@"AXBoundsForTextMarkerRange",
@"AXAttributedStringForTextMarkerRange",
@"AXAttributedStringForTextMarkerRangeWithOptions",
@"AXTextMarkerRangeForTextMarkers",
@"AXTextMarkerRangeForUnorderedTextMarkers",
@"AXNextTextMarkerForTextMarker",
@"AXPreviousTextMarkerForTextMarker",
@"AXLeftWordTextMarkerRangeForTextMarker",
@"AXRightWordTextMarkerRangeForTextMarker",
@"AXLeftLineTextMarkerRangeForTextMarker",
@"AXRightLineTextMarkerRangeForTextMarker",
@"AXSentenceTextMarkerRangeForTextMarker",
@"AXParagraphTextMarkerRangeForTextMarker",
@"AXNextWordEndTextMarkerForTextMarker",
@"AXPreviousWordStartTextMarkerForTextMarker",
@"AXNextLineEndTextMarkerForTextMarker",
@"AXPreviousLineStartTextMarkerForTextMarker",
@"AXNextSentenceEndTextMarkerForTextMarker",
@"AXPreviousSentenceStartTextMarkerForTextMarker",
@"AXNextParagraphEndTextMarkerForTextMarker",
@"AXPreviousParagraphStartTextMarkerForTextMarker",
@"AXStyleTextMarkerRangeForTextMarker",
@"AXLengthForTextMarkerRange",
NSAccessibilityBoundsForRangeParameterizedAttribute,
NSAccessibilityStringForRangeParameterizedAttribute,
NSAccessibilityUIElementCountForSearchPredicateParameterizedAttribute,
NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute,
NSAccessibilityEndTextMarkerForBoundsParameterizedAttribute,
NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute,
NSAccessibilityLineTextMarkerRangeForTextMarkerParameterizedAttribute,
NSAccessibilitySelectTextWithCriteriaParameterizedAttribute,
NSAccessibilitySearchTextWithCriteriaParameterizedAttribute,
NSAccessibilityTextOperationParameterizedAttribute,
nil];
}
if (textParamAttrs == nil) {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:paramAttrs]);
[tempArray addObject:(NSString*)kAXLineForIndexParameterizedAttribute];
[tempArray addObject:(NSString*)kAXRangeForLineParameterizedAttribute];
[tempArray addObject:(NSString*)kAXStringForRangeParameterizedAttribute];
[tempArray addObject:(NSString*)kAXRangeForPositionParameterizedAttribute];
[tempArray addObject:(NSString*)kAXRangeForIndexParameterizedAttribute];
[tempArray addObject:(NSString*)kAXBoundsForRangeParameterizedAttribute];
[tempArray addObject:(NSString*)kAXRTFForRangeParameterizedAttribute];
[tempArray addObject:(NSString*)kAXAttributedStringForRangeParameterizedAttribute];
[tempArray addObject:(NSString*)kAXStyleRangeForIndexParameterizedAttribute];
textParamAttrs = [[NSArray alloc] initWithArray:tempArray.get()];
}
if (tableParamAttrs == nil) {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:paramAttrs]);
[tempArray addObject:NSAccessibilityCellForColumnAndRowParameterizedAttribute];
tableParamAttrs = [[NSArray alloc] initWithArray:tempArray.get()];
}
if (!webAreaParamAttrs) {
auto tempArray = adoptNS([[NSMutableArray alloc] initWithArray:paramAttrs]);
[tempArray addObject:NSAccessibilityTextMarkerForIndexParameterizedAttribute];
[tempArray addObject:NSAccessibilityTextMarkerIsValidParameterizedAttribute];
[tempArray addObject:NSAccessibilityIndexForTextMarkerParameterizedAttribute];
webAreaParamAttrs = [[NSArray alloc] initWithArray:tempArray.get()];
}
if (backingObject->isPasswordField())
return @[ NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute ];
if (backingObject->isTextControl())
return textParamAttrs;
if (backingObject->isTable() && backingObject->isExposable())
return tableParamAttrs;
if (backingObject->isMenuRelated())
return nil;
if (backingObject->isWebArea())
return webAreaParamAttrs;
return paramAttrs;
}
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
- (void)accessibilityPerformPressAction
{
// In case anything we do by performing the press action causes an alert or other modal
// behaviors, we need to return now, so that VoiceOver doesn't hang indefinitely.
RunLoop::main().dispatch([self, protectedSelf = retainPtr(self)] {
[self _accessibilityPerformPressAction];
});
}
- (void)_accessibilityPerformPressAction
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return;
if (backingObject->isAttachment())
[[self attachmentView] accessibilityPerformAction:NSAccessibilityPressAction];
else
backingObject->press();
}
- (void)accessibilityPerformIncrementAction
{
RunLoop::main().dispatch([self, protectedSelf = retainPtr(self)] {
[self _accessibilityPerformIncrementAction];
});
}
- (void)_accessibilityPerformIncrementAction
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return;
if (backingObject->isAttachment())
[[self attachmentView] accessibilityPerformAction:NSAccessibilityIncrementAction];
else
backingObject->increment();
}
- (void)accessibilityPerformDecrementAction
{
RunLoop::main().dispatch([self, protectedSelf = retainPtr(self)] {
[self _accessibilityPerformDecrementAction];
});
}
- (void)_accessibilityPerformDecrementAction
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return;
if (backingObject->isAttachment())
[[self attachmentView] accessibilityPerformAction:NSAccessibilityDecrementAction];
else
backingObject->decrement();
}
ALLOW_DEPRECATED_DECLARATIONS_END
- (void)accessibilityPerformShowMenuAction
{
if (self.axBackingObject->roleValue() == AccessibilityRole::ComboBox)
self.axBackingObject->setIsExpanded(true);
else {
// This needs to be performed in an iteration of the run loop that did not start from an AX call.
// If it's the same run loop iteration, the menu open notification won't be sent
[self performSelector:@selector(accessibilityShowContextMenu) withObject:nil afterDelay:0.0];
}
}
- (void)accessibilityShowContextMenu
{
Accessibility::performFunctionOnMainThread([protectedSelf = retainPtr(self)] {
[protectedSelf _accessibilityShowContextMenu];
});
}
- (void)_accessibilityShowContextMenu
{
ASSERT(isMainThread());
if (!self.axBackingObject)
return;
Page* page = self.axBackingObject->page();
if (!page)
return;
IntRect rect = snappedIntRect(self.axBackingObject->elementRect());
FrameView* frameView = self.axBackingObject->documentFrameView();
// On WK2, we need to account for the scroll position with regards to root view.
// On WK1, we need to convert rect to window space to match mouse clicking.
if (frameView) {
// Find the appropriate scroll view to use to convert the contents to the window.
for (auto* parent = self.axBackingObject->parentObject(); parent; parent = parent->parentObject()) {
if (parent->isScrollView()) {
if (auto* scrollView = parent->scrollView()) {
if (!frameView->platformWidget())
rect = scrollView->contentsToRootView(rect);
else
rect = scrollView->contentsToWindow(rect);
}
break;
}
}
}
page->contextMenuController().showContextMenuAt(page->mainFrame(), rect.center());
}
- (void)accessibilityScrollToVisible
{
self.axBackingObject->scrollToMakeVisible();
}
- (void)_accessibilityScrollToMakeVisibleWithSubFocus:(NSRect)rect
{
self.axBackingObject->scrollToMakeVisibleWithSubFocus(IntRect(rect));
}
- (void)_accessibilityScrollToGlobalPoint:(NSPoint)point
{
self.axBackingObject->scrollToGlobalPoint(IntPoint(point));
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (void)accessibilityPerformAction:(NSString*)action
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return;
if ([action isEqualToString:NSAccessibilityPressAction])
[self accessibilityPerformPressAction];
else if ([action isEqualToString:@"AXSyncPressAction"]) {
// Used in layout tests, so that we don't have to wait for the async press action.
[self _accessibilityPerformPressAction];
}
else if ([action isEqualToString:@"AXSyncIncrementAction"])
[self _accessibilityPerformIncrementAction];
else if ([action isEqualToString:@"AXSyncDecrementAction"])
[self _accessibilityPerformDecrementAction];
else if ([action isEqualToString:NSAccessibilityShowMenuAction])
[self accessibilityPerformShowMenuAction];
else if ([action isEqualToString:NSAccessibilityIncrementAction])
[self accessibilityPerformIncrementAction];
else if ([action isEqualToString:NSAccessibilityDecrementAction])
[self accessibilityPerformDecrementAction];
else if ([action isEqualToString:NSAccessibilityScrollToVisibleAction])
[self accessibilityScrollToVisible];
else if ([action isEqualToString:@"AXDismissAction"])
backingObject->performDismissAction();
}
- (BOOL)accessibilityReplaceRange:(NSRange)range withText:(NSString *)string
{
auto* backingObject = self.updateObjectBackingStore;
return backingObject ? backingObject->replaceTextInRange(String(string), PlainTextRange(range)) : NO;
}
- (BOOL)accessibilityInsertText:(NSString *)text
{
auto* backingObject = self.updateObjectBackingStore;
return backingObject ? backingObject->insertText(String(text)) : NO;
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
#if PLATFORM(MAC)
// In case anything we do by changing values causes an alert or other modal
// behaviors, we need to return now, so that VoiceOver doesn't hang indefinitely.
callOnMainThread([value = retainPtr(value), attributeName = retainPtr(attributeName), protectedSelf = retainPtr(self)] {
[protectedSelf _accessibilitySetValue:value.get() forAttribute:attributeName.get()];
});
#else
// dispatch_async on earlier versions can cause focus not to track.
[self _accessibilitySetValue:value forAttribute:attributeName];
#endif
}
- (void)_accessibilitySetValue:(id)value forAttribute:(NSString *)attributeName
{
AXTRACE(makeString("WebAccessibilityObjectWrapper _accessibilitySetValue: forAttribute:", String(attributeName)));
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject) {
AXLOG("No backingObject!!!");
return;
}
AXTextMarkerRangeRef textMarkerRange = nil;
NSNumber* number = nil;
NSString* string = nil;
NSRange range = {0, 0};
NSArray* array = nil;
// decode the parameter
if (AXObjectIsTextMarkerRange(value))
textMarkerRange = (AXTextMarkerRangeRef)value;
else if ([value isKindOfClass:[NSNumber class]])
number = value;
else if ([value isKindOfClass:[NSString class]])
string = value;
else if ([value isKindOfClass:[NSValue class]])
range = [value rangeValue];
else if ([value isKindOfClass:[NSArray class]])
array = value;
// handle the command
if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) {
ASSERT(textMarkerRange);
Accessibility::performFunctionOnMainThread([&textMarkerRange, protectedSelf = retainPtr(self)] {
if (auto* backingObject = protectedSelf.get().axBackingObject)
backingObject->setSelectedVisiblePositionRange(visiblePositionRangeForTextMarkerRange(backingObject->axObjectCache(), textMarkerRange));
});
} else if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) {
backingObject->setFocused([number boolValue]);
} else if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) {
if (number && backingObject->canSetNumericValue())
backingObject->setValue([number floatValue]);
else if (string)
backingObject->setValue(string);
} else if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute]) {
if (!number)
return;
backingObject->setSelected([number boolValue]);
} else if ([attributeName isEqualToString:NSAccessibilitySelectedChildrenAttribute]) {
if (!array || !backingObject->canSetSelectedChildren())
return;
AXCoreObject::AccessibilityChildrenVector selectedChildren;
convertToVector(array, selectedChildren);
backingObject->setSelectedChildren(selectedChildren);
} else if (backingObject->isTextControl()) {
if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) {
backingObject->setSelectedText(string);
} else if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) {
backingObject->setSelectedTextRange(PlainTextRange(range.location, range.length));
} else if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) {
backingObject->makeRangeVisible(PlainTextRange(range.location, range.length));
}
} else if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute] || [attributeName isEqualToString:NSAccessibilityExpandedAttribute])
backingObject->setIsExpanded([number boolValue]);
else if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector selectedRows;
convertToVector(array, selectedRows);
if (backingObject->isTree() || (backingObject->isTable() && backingObject->isExposable()))
backingObject->setSelectedRows(selectedRows);
} else if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute])
backingObject->setARIAGrabbed([number boolValue]);
else if (backingObject->isWebArea() && [attributeName isEqualToString:NSAccessibilityPreventKeyboardDOMEventDispatchAttribute])
backingObject->setPreventKeyboardDOMEventDispatch([number boolValue]);
else if (backingObject->isWebArea() && [attributeName isEqualToString:NSAccessibilityCaretBrowsingEnabledAttribute])
backingObject->setCaretBrowsingEnabled([number boolValue]);
}
static RenderObject* rendererForView(NSView* view)
{
if (![view conformsToProtocol:@protocol(WebCoreFrameView)])
return nullptr;
NSView<WebCoreFrameView>* frameView = (NSView<WebCoreFrameView>*)view;
Frame* frame = [frameView _web_frame];
if (!frame)
return nullptr;
Node* node = frame->document()->ownerElement();
if (!node)
return nullptr;
return node->renderer();
}
- (id)_accessibilityParentForSubview:(NSView*)subview
{
RenderObject* renderer = rendererForView(subview);
if (!renderer)
return nil;
AccessibilityObject* obj = renderer->document().axObjectCache()->getOrCreate(renderer);
if (obj)
return obj->parentObjectUnignored()->wrapper();
return nil;
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (NSString*)accessibilityActionDescription:(NSString*)action
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
// we have no custom actions
return NSAccessibilityActionDescription(action);
}
// The CFAttributedStringType representation of the text associated with this accessibility
// object that is specified by the given range.
- (NSAttributedString *)doAXAttributedStringForRange:(NSRange)range
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<NSAttributedString *>([&range, protectedSelf = retainPtr(self)] () -> RetainPtr<NSAttributedString> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto webRange = backingObject->rangeForPlainTextRange(range);
return [protectedSelf doAXAttributedStringForTextMarkerRange:textMarkerRangeFromRange(backingObject->axObjectCache(), webRange) spellCheck:YES];
});
}
- (NSInteger)_indexForTextMarker:(AXTextMarkerRef)marker
{
if (!marker)
return NSNotFound;
return Accessibility::retrieveValueFromMainThread<NSInteger>([&marker, protectedSelf = retainPtr(self)] () -> NSInteger {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return NSNotFound;
if (auto* cache = backingObject->axObjectCache()) {
CharacterOffset characterOffset = characterOffsetForTextMarker(cache, marker);
auto range = cache->rangeForUnorderedCharacterOffsets(characterOffset, characterOffset);
if (!range)
return NSNotFound;
return makeNSRange(range).location;
}
return NSNotFound;
});
}
- (AXTextMarkerRef)_textMarkerForIndex:(NSInteger)textIndex
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<AXTextMarkerRef>([&textIndex, protectedSelf = retainPtr(self)] () -> RetainPtr<AXTextMarkerRef> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
auto* document = backingObject->document();
if (!document)
return nil;
auto* documentElement = document->documentElement();
if (!documentElement)
return nil;
auto boundary = resolveCharacterLocation(makeRangeSelectingNodeContents(*documentElement), textIndex);
auto characterOffset = cache->startOrEndCharacterOffsetForRange(makeSimpleRange(boundary), true);
return textMarkerForCharacterOffset(cache, characterOffset);
});
}
// The RTF representation of the text associated with this accessibility object that is
// specified by the given range.
- (NSData*)doAXRTFForRange:(NSRange)range
{
NSAttributedString* attrString = [self doAXAttributedStringForRange:range];
return [attrString RTFFromRange: NSMakeRange(0, [attrString length]) documentAttributes:@{ }];
}
#if ENABLE(TREE_DEBUGGING)
- (NSString *)debugDescriptionForTextMarker:(AXTextMarkerRef)textMarker
{
return visiblePositionForTextMarker(self.axBackingObject->axObjectCache(), textMarker).debugDescription();
}
- (NSString *)debugDescriptionForTextMarkerRange:(AXTextMarkerRangeRef)textMarkerRange
{
auto* backingObject = self.axBackingObject;
if (!backingObject)
return @"<null>";
auto visiblePositionRange = visiblePositionRangeForTextMarkerRange(backingObject->axObjectCache(), textMarkerRange);
if (visiblePositionRange.isNull())
return @"<null>";
char description[2048];
formatForDebugger(visiblePositionRange, description, sizeof(description));
return [NSString stringWithUTF8String:description];
}
- (void)showNodeForTextMarker:(AXTextMarkerRef)textMarker
{
auto visiblePosition = visiblePositionForTextMarker(self.axBackingObject->axObjectCache(), textMarker);
Node* node = visiblePosition.deepEquivalent().deprecatedNode();
if (!node)
return;
node->showNode();
node->showNodePathForThis();
}
- (void)showNodeTreeForTextMarker:(AXTextMarkerRef)textMarker
{
auto visiblePosition = visiblePositionForTextMarker(self.axBackingObject->axObjectCache(), textMarker);
Node* node = visiblePosition.deepEquivalent().deprecatedNode();
if (!node)
return;
node->showTreeForThis();
}
static void formatForDebugger(const VisiblePositionRange& range, char* buffer, unsigned length)
{
strlcpy(buffer, makeString("from ", range.start.debugDescription(), " to ", range.end.debugDescription()).utf8().data(), length);
}
#endif
enum class TextUnit {
LeftWord = 1,
RightWord,
NextWordEnd,
PreviousWordStart,
Sentence,
NextSentenceEnd,
PreviousSentenceStart,
Paragraph,
NextParagraphEnd,
PreviousParagraphStart,
Line,
LeftLine,
RightLine,
NextLineEnd,
PreviousLineStart,
};
- (AXTextMarkerRangeRef)textMarkerRangeAtTextMarker:(AXTextMarkerRef)textMarker forUnit:(TextUnit)textUnit
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<AXTextMarkerRangeRef>([&textMarker, &textUnit, protectedSelf = retainPtr(self)] () -> RetainPtr<AXTextMarkerRangeRef> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
auto characterOffset = characterOffsetForTextMarker(cache, textMarker);
std::optional<SimpleRange> range;
switch (textUnit) {
case TextUnit::LeftWord:
range = cache->leftWordRange(characterOffset);
break;
case TextUnit::RightWord:
range = cache->rightWordRange(characterOffset);
break;
case TextUnit::Sentence:
range = cache->sentenceForCharacterOffset(characterOffset);
break;
case TextUnit::Paragraph:
range = cache->paragraphForCharacterOffset(characterOffset);
break;
default:
ASSERT_NOT_REACHED();
break;
}
return textMarkerRangeFromRange(cache, range);
});
}
- (AXTextMarkerRangeRef)lineTextMarkerRangeForTextMarker:(AXTextMarkerRef)textMarker forUnit:(TextUnit)textUnit
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<AXTextMarkerRangeRef>([&textMarker, &textUnit, protectedSelf = retainPtr(self)] () -> RetainPtr<AXTextMarkerRangeRef> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
auto visiblePosition = visiblePositionForTextMarker(cache, textMarker);
VisiblePositionRange visiblePositionRange;
switch (textUnit) {
case TextUnit::Line:
visiblePositionRange = backingObject->lineRangeForPosition(visiblePosition);
break;
case TextUnit::LeftLine:
visiblePositionRange = backingObject->leftLineVisiblePositionRange(visiblePosition);
break;
case TextUnit::RightLine:
visiblePositionRange = backingObject->rightLineVisiblePositionRange(visiblePosition);
break;
default:
ASSERT_NOT_REACHED();
break;
}
return textMarkerRangeFromVisiblePositions(cache, visiblePositionRange.start, visiblePositionRange.end);
});
}
- (AXTextMarkerRef)textMarkerForTextMarker:(AXTextMarkerRef)textMarker atUnit:(TextUnit)textUnit
{
return Accessibility::retrieveAutoreleasedValueFromMainThread<AXTextMarkerRef>([&textMarker, &textUnit, protectedSelf = retainPtr(self)] () -> RetainPtr<AXTextMarkerRef> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
CharacterOffset oldOffset = characterOffsetForTextMarker(cache, textMarker);
CharacterOffset newOffset;
switch (textUnit) {
case TextUnit::NextWordEnd:
newOffset = cache->nextWordEndCharacterOffset(oldOffset);
break;
case TextUnit::PreviousWordStart:
newOffset = cache->previousWordStartCharacterOffset(oldOffset);
break;
case TextUnit::NextSentenceEnd:
newOffset = cache->nextSentenceEndCharacterOffset(oldOffset);
break;
case TextUnit::PreviousSentenceStart:
newOffset = cache->previousSentenceStartCharacterOffset(oldOffset);
break;
case TextUnit::NextParagraphEnd:
newOffset = cache->nextParagraphEndCharacterOffset(oldOffset);
break;
case TextUnit::PreviousParagraphStart:
newOffset = cache->previousParagraphStartCharacterOffset(oldOffset);
break;
case TextUnit::NextLineEnd: {
auto visiblePosition = visiblePositionForTextMarker(cache, textMarker);
return textMarkerForVisiblePosition(cache, backingObject->nextLineEndPosition(visiblePosition));
}
case TextUnit::PreviousLineStart: {
auto visiblePosition = visiblePositionForTextMarker(cache, textMarker);
return textMarkerForVisiblePosition(cache, backingObject->previousLineStartPosition(visiblePosition));
}
default:
ASSERT_NOT_REACHED();
break;
}
return textMarkerForCharacterOffset(cache, newOffset);
});
}
static bool isMatchingPlugin(AXCoreObject* axObject, const AccessibilitySearchCriteria& criteria)
{
if (!axObject->isWidget())
return false;
return Accessibility::retrieveValueFromMainThread<bool>([&axObject, &criteria] () -> bool {
auto* widget = axObject->widget();
if (!is<PluginViewBase>(widget))
return false;
return criteria.searchKeys.contains(AccessibilitySearchKey::AnyType)
&& (!criteria.visibleOnly || downcast<PluginViewBase>(widget)->isVisible());
});
}
ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN
- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter
ALLOW_DEPRECATED_IMPLEMENTATIONS_END
{
AXTRACE(makeString("WebAccessibilityObjectWrapper accessibilityAttributeValue:", String(attribute)));
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return nil;
// Basic parameter validation.
if (!attribute || !parameter)
return nil;
AXTextMarkerRef textMarker = nil;
AXTextMarkerRangeRef textMarkerRange = nil;
NSNumber* number = nil;
NSArray* array = nil;
NSDictionary* dictionary = nil;
RefPtr<AXCoreObject> uiElement;
NSPoint point = NSZeroPoint;
bool pointSet = false;
NSRange range = {0, 0};
bool rangeSet = false;
NSRect rect = NSZeroRect;
// common parameter type check/casting. Nil checks in handlers catch wrong type case.
// NOTE: This assumes nil is not a valid parameter, because it is indistinguishable from
// a parameter of the wrong type.
if (AXObjectIsTextMarker(parameter))
textMarker = (AXTextMarkerRef)parameter;
else if (AXObjectIsTextMarkerRange(parameter))
textMarkerRange = (AXTextMarkerRangeRef)parameter;
else if ([parameter isKindOfClass:[WebAccessibilityObjectWrapper class]])
uiElement = [(WebAccessibilityObjectWrapper*)parameter axBackingObject];
else if ([parameter isKindOfClass:[NSNumber class]])
number = parameter;
else if ([parameter isKindOfClass:[NSArray class]])
array = parameter;
else if ([parameter isKindOfClass:[NSDictionary class]])
dictionary = parameter;
else if ([parameter isKindOfClass:[NSValue class]] && !strcmp([(NSValue*)parameter objCType], @encode(NSPoint))) {
pointSet = true;
point = [(NSValue*)parameter pointValue];
} else if ([parameter isKindOfClass:[NSValue class]] && !strcmp([(NSValue*)parameter objCType], @encode(NSRange))) {
rangeSet = true;
range = [(NSValue*)parameter rangeValue];
} else if ([parameter isKindOfClass:[NSValue class]] && !strcmp([(NSValue*)parameter objCType], @encode(NSRect)))
rect = [(NSValue*)parameter rectValue];
else {
// Attribute type is not supported. Allow super to handle.
return [super accessibilityAttributeValue:attribute forParameter:parameter];
}
// dispatch
if ([attribute isEqualToString:NSAccessibilitySelectTextWithCriteriaParameterizedAttribute]) {
// To be deprecated.
auto result = Accessibility::retrieveValueFromMainThread<Vector<String>>([dictionary, protectedSelf = retainPtr(self)] () -> Vector<String> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return Vector<String>();
auto criteria = accessibilityTextCriteriaForParameterizedAttribute(dictionary);
criteria.second.textRanges = backingObject->findTextRanges(criteria.first);
ASSERT(criteria.second.textRanges.size() <= 1);
return backingObject->performTextOperation(criteria.second);
});
ASSERT(result.size() <= 1);
if (result.size() > 0)
return result[0];
return String();
}
if ([attribute isEqualToString:NSAccessibilitySearchTextWithCriteriaParameterizedAttribute]) {
auto criteria = accessibilitySearchTextCriteriaForParameterizedAttribute(dictionary);
return Accessibility::retrieveAutoreleasedValueFromMainThread<NSArray *>([&criteria, protectedSelf = retainPtr(self)] () -> RetainPtr<NSArray> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto ranges = backingObject->findTextRanges(criteria);
if (ranges.isEmpty())
return nil;
return createNSArray(ranges, [&] (auto& range) {
return (id)textMarkerRangeFromRange(backingObject->axObjectCache(), range);
}).autorelease();
});
}
if ([attribute isEqualToString:NSAccessibilityTextOperationParameterizedAttribute]) {
auto operationResult = Accessibility::retrieveValueFromMainThread<Vector<String>>([dictionary, protectedSelf = retainPtr(self)] () -> Vector<String> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return Vector<String>();
auto textOperation = accessibilityTextOperationForParameterizedAttribute(backingObject->axObjectCache(), dictionary);
return backingObject->performTextOperation(textOperation);
});
if (operationResult.isEmpty())
return nil;
return createNSArray(operationResult).autorelease();
}
if ([attribute isEqualToString:NSAccessibilityUIElementCountForSearchPredicateParameterizedAttribute]) {
AccessibilitySearchCriteria criteria = accessibilitySearchCriteriaForSearchPredicateParameterizedAttribute(dictionary);
NSUInteger widgetChildrenSize = 0;
if (isMatchingPlugin(backingObject, criteria)) {
// FIXME: We should also be searching the tree(s) resulting from `renderWidgetChildren` for matches.
// This is tracked by https://bugs.webkit.org/show_bug.cgi?id=230167.
if (auto* widgetChildren = [self renderWidgetChildren]) {
widgetChildrenSize = [widgetChildren count];
if (widgetChildrenSize >= criteria.resultsLimit)
return @(std::min(widgetChildrenSize, NSUInteger(criteria.resultsLimit)));
criteria.resultsLimit -= widgetChildrenSize;
}
}
AccessibilityObject::AccessibilityChildrenVector results;
backingObject->findMatchingObjects(&criteria, results);
return @(results.size() + widgetChildrenSize);
}
if ([attribute isEqualToString:NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute]) {
AccessibilitySearchCriteria criteria = accessibilitySearchCriteriaForSearchPredicateParameterizedAttribute(dictionary);
NSArray *widgetChildren = nil;
if (isMatchingPlugin(backingObject, criteria)) {
// FIXME: We should also be searching the tree(s) resulting from `renderWidgetChildren` for matches.
// This is tracked by https://bugs.webkit.org/show_bug.cgi?id=230167.
if (auto* children = [self renderWidgetChildren]) {
NSUInteger includedChildrenCount = std::min([children count], NSUInteger(criteria.resultsLimit));
widgetChildren = [children subarrayWithRange:NSMakeRange(0, includedChildrenCount)];
if ([widgetChildren count] >= criteria.resultsLimit)
return widgetChildren;
criteria.resultsLimit -= [widgetChildren count];
}
}
AccessibilityObject::AccessibilityChildrenVector results;
backingObject->findMatchingObjects(&criteria, results);
if (widgetChildren)
return [widgetChildren arrayByAddingObjectsFromArray:makeNSArray(results)];
return makeNSArray(results);
}
// TextMarker attributes.
if ([attribute isEqualToString:NSAccessibilityEndTextMarkerForBoundsParameterizedAttribute]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&rect, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
IntRect webCoreRect = [protectedSelf screenToContents:enclosingIntRect(rect)];
CharacterOffset characterOffset = cache->characterOffsetForBounds(webCoreRect, false);
return (id)textMarkerForCharacterOffset(cache, characterOffset);
});
}
if ([attribute isEqualToString:NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&rect, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
IntRect webCoreRect = [protectedSelf screenToContents:enclosingIntRect(rect)];
CharacterOffset characterOffset = cache->characterOffsetForBounds(webCoreRect, true);
return (id)textMarkerForCharacterOffset(cache, characterOffset);
});
}
// TextMarkerRange attributes.
if ([attribute isEqualToString:@"AXTextMarkerRangeForNSRange"])
return (id)backingObject->textMarkerRangeForNSRange(range);
if ([attribute isEqualToString:NSAccessibilityLineTextMarkerRangeForTextMarkerParameterizedAttribute])
return (id)[self lineTextMarkerRangeForTextMarker:textMarker forUnit:TextUnit::Line];
if ([attribute isEqualToString:NSAccessibilityMisspellingTextMarkerRangeParameterizedAttribute]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&dictionary, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
auto criteria = accessibilityMisspellingSearchCriteriaForParameterizedAttribute(cache, dictionary);
if (auto misspellingRange = backingObject->misspellingRange(*criteria.first, criteria.second))
return (id)textMarkerRangeFromRange(cache, *misspellingRange);
return nil;
});
}
if ([attribute isEqualToString:NSAccessibilityTextMarkerIsValidParameterizedAttribute]) {
bool result = Accessibility::retrieveValueFromMainThread<bool>([&textMarker, protectedSelf = retainPtr(self)] () -> bool {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return false;
return !visiblePositionForTextMarker(backingObject->axObjectCache(), textMarker).isNull();
});
return [NSNumber numberWithBool:result];
}
if ([attribute isEqualToString:NSAccessibilityIndexForTextMarkerParameterizedAttribute])
return [NSNumber numberWithInteger:[self _indexForTextMarker:textMarker]];
if ([attribute isEqualToString:NSAccessibilityTextMarkerForIndexParameterizedAttribute])
return (id)[self _textMarkerForIndex:[number integerValue]];
if ([attribute isEqualToString:@"AXUIElementForTextMarker"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&textMarker, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* axObject = accessibilityObjectForTextMarker(backingObject->axObjectCache(), textMarker);
if (!axObject)
return nil;
if (axObject->isAttachment() && [axObject->wrapper() attachmentView])
return [axObject->wrapper() attachmentView];
return axObject->wrapper();
});
}
if ([attribute isEqualToString:@"AXTextMarkerRangeForUIElement"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&uiElement] () -> RetainPtr<id> {
return (id)textMarkerRangeFromRange(uiElement.get()->axObjectCache(), uiElement.get()->elementRange());
});
}
if ([attribute isEqualToString:@"AXLineForTextMarker"]) {
int result = Accessibility::retrieveValueFromMainThread<int>([&textMarker, protectedSelf = retainPtr(self)] () -> int {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return -1;
auto visiblePos = visiblePositionForTextMarker(backingObject->axObjectCache(), textMarker);
return backingObject->lineForPosition(visiblePos);
});
return @(result);
}
if ([attribute isEqualToString:@"AXTextMarkerRangeForLine"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&number, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
VisiblePositionRange vpRange;
if ([number unsignedIntegerValue] != NSNotFound)
vpRange = backingObject->visiblePositionRangeForLine([number unsignedIntValue]);
return (id)textMarkerRangeFromVisiblePositions(backingObject->axObjectCache(), vpRange.start, vpRange.end);
});
}
if ([attribute isEqualToString:@"AXStringForTextMarkerRange"]) {
return Accessibility::retrieveValueFromMainThread<String>([&textMarkerRange, protectedSelf = retainPtr(self)] () -> String {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return String();
auto range = rangeForTextMarkerRange(backingObject->axObjectCache(), textMarkerRange);
return range ? backingObject->stringForRange(*range) : String();
});
}
if ([attribute isEqualToString:@"AXTextMarkerForPosition"]) {
if (!pointSet)
return nil;
IntPoint webCorePoint = IntPoint(point);
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&webCorePoint, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
return (id)textMarkerForVisiblePosition(backingObject->axObjectCache(), backingObject->visiblePositionForPoint(webCorePoint));
});
}
if ([attribute isEqualToString:@"AXBoundsForTextMarkerRange"]) {
NSRect rect = Accessibility::retrieveValueFromMainThread<NSRect>([&textMarkerRange, protectedSelf = retainPtr(self)] () -> NSRect {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return CGRectZero;
auto range = rangeForTextMarkerRange(backingObject->axObjectCache(), textMarkerRange);
if (!range)
return CGRectZero;
auto bounds = FloatRect(backingObject->boundsForRange(*range));
return [protectedSelf convertRectToSpace:bounds space:AccessibilityConversionSpace::Screen];
});
return [NSValue valueWithRect:rect];
}
if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) {
NSRect rect = Accessibility::retrieveValueFromMainThread<NSRect>([&range, protectedSelf = retainPtr(self)] () -> NSRect {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return CGRectZero;
auto start = backingObject->visiblePositionForIndex(range.location);
auto end = backingObject->visiblePositionForIndex(range.location + range.length);
auto webRange = makeSimpleRange({ start, end });
if (!webRange)
return CGRectZero;
auto bounds = FloatRect(backingObject->boundsForRange(*webRange));
return [protectedSelf convertRectToSpace:bounds space:AccessibilityConversionSpace::Screen];
});
return [NSValue valueWithRect:rect];
}
if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) {
if (backingObject->isTextControl()) {
PlainTextRange plainTextRange = PlainTextRange(range.location, range.length);
return backingObject->doAXStringForRange(plainTextRange);
}
return Accessibility::retrieveValueFromMainThread<String>([&range, protectedSelf = retainPtr(self)] () -> String {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return String();
auto* cache = backingObject->axObjectCache();
if (!cache)
return String();
auto start = cache->characterOffsetForIndex(range.location, backingObject);
auto end = cache->characterOffsetForIndex(range.location + range.length, backingObject);
auto range = cache->rangeForUnorderedCharacterOffsets(start, end);
if (!range)
return { };
return backingObject->stringForRange(*range);
});
}
if ([attribute isEqualToString:@"AXAttributedStringForTextMarkerRange"])
return [self doAXAttributedStringForTextMarkerRange:textMarkerRange spellCheck:YES];
if ([attribute isEqualToString:@"AXAttributedStringForTextMarkerRangeWithOptions"]) {
if (textMarkerRange)
return [self doAXAttributedStringForTextMarkerRange:textMarkerRange spellCheck:NO];
if (dictionary) {
AXTextMarkerRangeRef textMarkerRange = nil;
id parameter = [dictionary objectForKey:@"AXTextMarkerRange"];
if (AXObjectIsTextMarkerRange(parameter))
textMarkerRange = (AXTextMarkerRangeRef)parameter;
BOOL spellCheck = NO;
parameter = [dictionary objectForKey:@"AXSpellCheck"];
if ([parameter isKindOfClass:[NSNumber class]])
spellCheck = [parameter boolValue];
return [self doAXAttributedStringForTextMarkerRange:textMarkerRange spellCheck:spellCheck];
}
return nil;
}
if ([attribute isEqualToString:@"AXTextMarkerRangeForTextMarkers"]) {
if (array.count < 2
|| !AXObjectIsTextMarker([array objectAtIndex:0])
|| !AXObjectIsTextMarker([array objectAtIndex:1]))
return nil;
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&array] () -> RetainPtr<id> {
return (id)textMarkerRangeFromMarkers((AXTextMarkerRef)[array objectAtIndex:0], (AXTextMarkerRef)[array objectAtIndex:1]).get();
});
}
if ([attribute isEqualToString:@"AXTextMarkerRangeForUnorderedTextMarkers"]) {
if (array.count < 2
|| !AXObjectIsTextMarker([array objectAtIndex:0])
|| !AXObjectIsTextMarker([array objectAtIndex:1]))
return nil;
AXTextMarkerRef textMarker1 = (AXTextMarkerRef)[array objectAtIndex:0];
AXTextMarkerRef textMarker2 = (AXTextMarkerRef)[array objectAtIndex:1];
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&textMarker1, &textMarker2, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
auto characterOffset1 = characterOffsetForTextMarker(cache, textMarker1);
auto characterOffset2 = characterOffsetForTextMarker(cache, textMarker2);
auto range = cache->rangeForUnorderedCharacterOffsets(characterOffset1, characterOffset2);
return (id)textMarkerRangeFromRange(cache, range);
});
}
if ([attribute isEqualToString:@"AXNextTextMarkerForTextMarker"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&textMarker, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
CharacterOffset characterOffset = characterOffsetForTextMarker(cache, textMarker);
return bridge_id_cast([protectedSelf nextTextMarkerForCharacterOffset:characterOffset]);
});
}
if ([attribute isEqualToString:@"AXPreviousTextMarkerForTextMarker"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&textMarker, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
CharacterOffset characterOffset = characterOffsetForTextMarker(cache, textMarker);
return bridge_id_cast([protectedSelf previousTextMarkerForCharacterOffset:characterOffset]);
});
}
if ([attribute isEqualToString:@"AXLeftWordTextMarkerRangeForTextMarker"])
return (id)[self textMarkerRangeAtTextMarker:textMarker forUnit:TextUnit::LeftWord];
if ([attribute isEqualToString:@"AXRightWordTextMarkerRangeForTextMarker"])
return (id)[self textMarkerRangeAtTextMarker:textMarker forUnit:TextUnit::RightWord];
if ([attribute isEqualToString:@"AXLeftLineTextMarkerRangeForTextMarker"])
return (id)[self lineTextMarkerRangeForTextMarker:textMarker forUnit:TextUnit::LeftLine];
if ([attribute isEqualToString:@"AXRightLineTextMarkerRangeForTextMarker"])
return (id)[self lineTextMarkerRangeForTextMarker:textMarker forUnit:TextUnit::RightLine];
if ([attribute isEqualToString:@"AXSentenceTextMarkerRangeForTextMarker"])
return (id)[self textMarkerRangeAtTextMarker:textMarker forUnit:TextUnit::Sentence];
if ([attribute isEqualToString:@"AXParagraphTextMarkerRangeForTextMarker"])
return (id)[self textMarkerRangeAtTextMarker:textMarker forUnit:TextUnit::Paragraph];
if ([attribute isEqualToString:@"AXNextWordEndTextMarkerForTextMarker"])
return (id)[self textMarkerForTextMarker:textMarker atUnit:TextUnit::NextWordEnd];
if ([attribute isEqualToString:@"AXPreviousWordStartTextMarkerForTextMarker"])
return (id)[self textMarkerForTextMarker:textMarker atUnit:TextUnit::PreviousWordStart];
if ([attribute isEqualToString:@"AXNextLineEndTextMarkerForTextMarker"])
return (id)[self textMarkerForTextMarker:textMarker atUnit:TextUnit::NextLineEnd];
if ([attribute isEqualToString:@"AXPreviousLineStartTextMarkerForTextMarker"])
return (id)[self textMarkerForTextMarker:textMarker atUnit:TextUnit::PreviousLineStart];
if ([attribute isEqualToString:@"AXNextSentenceEndTextMarkerForTextMarker"])
return (id)[self textMarkerForTextMarker:textMarker atUnit:TextUnit::NextSentenceEnd];
if ([attribute isEqualToString:@"AXPreviousSentenceStartTextMarkerForTextMarker"])
return (id)[self textMarkerForTextMarker:textMarker atUnit:TextUnit::PreviousSentenceStart];
if ([attribute isEqualToString:@"AXNextParagraphEndTextMarkerForTextMarker"])
return (id)[self textMarkerForTextMarker:textMarker atUnit:TextUnit::NextParagraphEnd];
if ([attribute isEqualToString:@"AXPreviousParagraphStartTextMarkerForTextMarker"])
return (id)[self textMarkerForTextMarker:textMarker atUnit:TextUnit::PreviousParagraphStart];
if ([attribute isEqualToString:@"AXStyleTextMarkerRangeForTextMarker"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&textMarker, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto* cache = backingObject->axObjectCache();
if (!cache)
return nil;
VisiblePosition visiblePos = visiblePositionForTextMarker(cache, textMarker);
VisiblePositionRange vpRange = backingObject->styleRangeForPosition(visiblePos);
return (id)textMarkerRangeFromVisiblePositions(cache, vpRange.start, vpRange.end);
});
}
if ([attribute isEqualToString:@"AXLengthForTextMarkerRange"]) {
int length = Accessibility::retrieveValueFromMainThread<int>([&textMarkerRange, protectedSelf = retainPtr(self)] () -> int {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return 0;
auto range = rangeForTextMarkerRange(backingObject->axObjectCache(), textMarkerRange);
if (!range)
return 0;
return AXObjectCache::lengthForRange(SimpleRange { *range });
});
if (length < 0)
return nil;
return @(length);
}
// Used only by DumpRenderTree (so far).
if ([attribute isEqualToString:@"AXStartTextMarkerForTextMarkerRange"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&textMarkerRange, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto range = rangeForTextMarkerRange(backingObject->axObjectCache(), textMarkerRange);
return (id)startOrEndTextMarkerForRange(backingObject->axObjectCache(), range, true);
});
}
if ([attribute isEqualToString:@"AXEndTextMarkerForTextMarkerRange"]) {
return Accessibility::retrieveAutoreleasedValueFromMainThread<id>([&textMarkerRange, protectedSelf = retainPtr(self)] () -> RetainPtr<id> {
auto* backingObject = protectedSelf.get().axBackingObject;
if (!backingObject)
return nil;
auto range = rangeForTextMarkerRange(backingObject->axObjectCache(), textMarkerRange);
return (id)startOrEndTextMarkerForRange(backingObject->axObjectCache(), range, false);
});
}
#if ENABLE(TREE_DEBUGGING)
if ([attribute isEqualToString:@"AXTextMarkerDebugDescription"])
return [self debugDescriptionForTextMarker:textMarker];
if ([attribute isEqualToString:@"AXTextMarkerRangeDebugDescription"])
return [self debugDescriptionForTextMarkerRange:textMarkerRange];
if ([attribute isEqualToString:@"AXTextMarkerNodeDebugDescription"]) {
[self showNodeForTextMarker:textMarker];
return nil;
}
if ([attribute isEqualToString:@"AXTextMarkerNodeTreeDebugDescription"]) {
[self showNodeTreeForTextMarker:textMarker];
return nil;
}
#endif
if (backingObject->isTable() && backingObject->isExposable()) {
if ([attribute isEqualToString:NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
if (array == nil || [array count] != 2)
return nil;
auto* cell = backingObject->cellForColumnAndRow([[array objectAtIndex:0] unsignedIntValue], [[array objectAtIndex:1] unsignedIntValue]);
return cell ? cell->wrapper() : nil;
}
}
if (backingObject->isTextControl()) {
if ([attribute isEqualToString: (NSString *)kAXLineForIndexParameterizedAttribute]) {
int lineNumber = backingObject->doAXLineForIndex([number intValue]);
if (lineNumber < 0)
return nil;
return @(lineNumber);
}
if ([attribute isEqualToString: (NSString *)kAXRangeForLineParameterizedAttribute]) {
PlainTextRange textRange = backingObject->doAXRangeForLine([number intValue]);
return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)];
}
if ([attribute isEqualToString: (NSString*)kAXStringForRangeParameterizedAttribute]) {
PlainTextRange plainTextRange = PlainTextRange(range.location, range.length);
return rangeSet ? (id)(backingObject->doAXStringForRange(plainTextRange)) : nil;
}
if ([attribute isEqualToString: (NSString*)kAXRangeForPositionParameterizedAttribute]) {
if (!pointSet)
return nil;
IntPoint webCorePoint = IntPoint(point);
PlainTextRange textRange = backingObject->doAXRangeForPosition(webCorePoint);
return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)];
}
if ([attribute isEqualToString: (NSString*)kAXRangeForIndexParameterizedAttribute]) {
PlainTextRange textRange = backingObject->doAXRangeForIndex([number intValue]);
return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)];
}
if ([attribute isEqualToString: (NSString*)kAXBoundsForRangeParameterizedAttribute]) {
if (!rangeSet)
return nil;
PlainTextRange plainTextRange = PlainTextRange(range.location, range.length);
auto bounds = FloatRect(backingObject->doAXBoundsForRangeUsingCharacterOffset(plainTextRange));
NSRect rect = [self convertRectToSpace:bounds space:AccessibilityConversionSpace::Screen];
return [NSValue valueWithRect:rect];
}
if ([attribute isEqualToString: (NSString*)kAXRTFForRangeParameterizedAttribute])
return rangeSet ? [self doAXRTFForRange:range] : nil;
if ([attribute isEqualToString: (NSString*)kAXAttributedStringForRangeParameterizedAttribute])
return rangeSet ? [self doAXAttributedStringForRange:range] : nil;
if ([attribute isEqualToString: (NSString*)kAXStyleRangeForIndexParameterizedAttribute]) {
PlainTextRange textRange = backingObject->doAXStyleRangeForIndex([number intValue]);
return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)];
}
}
// There are some parameters that super handles that are not explicitly returned by the list of the element's attributes.
// In that case it must be passed to super.
return [super accessibilityAttributeValue:attribute forParameter:parameter];
}
- (BOOL)accessibilitySupportsOverriddenAttributes
{
return YES;
}
// accessibilityShouldUseUniqueId is an AppKit method we override so that
// objects will be given a unique ID, and therefore allow AppKit to know when they
// become obsolete (e.g. when the user navigates to a new web page, making this one
// unrendered but not deallocated because it is in the back/forward cache).
// It is important to call NSAccessibilityUnregisterUniqueIdForUIElement in the
// appropriate place (e.g. dealloc) to remove these non-retained references from
// AppKit's id mapping tables. We do this in detach by calling unregisterUniqueIdForUIElement.
//
// Registering an object is also required for observing notifications. Only registered objects can be observed.
- (BOOL)accessibilityShouldUseUniqueId
{
// All AX object wrappers should use unique ID's because it's faster within AppKit to look them up.
return YES;
}
// API that AppKit uses for faster access
- (NSUInteger)accessibilityIndexOfChild:(id)child
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return NSNotFound;
// Tree objects return their rows as their children. We can use the original method
// here, because we won't gain any speed up.
if (backingObject->isTree())
return [super accessibilityIndexOfChild:child];
NSArray *children = self.childrenVectorArray;
if (!children.count) {
if (auto *renderWidgetChildren = [self renderWidgetChildren])
return [renderWidgetChildren indexOfObject:child];
#if ENABLE(MODEL_ELEMENT)
if (backingObject->isModel())
return backingObject->modelElementChildren().find(child);
#endif
}
NSUInteger count = [children count];
for (NSUInteger i = 0; i < count; ++i) {
WebAccessibilityObjectWrapper *wrapper = children[i];
auto* object = wrapper.axBackingObject;
if (!object)
continue;
if (wrapper == child || (object->isAttachment() && [wrapper attachmentView] == child))
return i;
}
return NSNotFound;
}
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute
{
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return 0;
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
// Tree items object returns a different set of children than those that are in children()
// because an AXOutline (the mac role is becomes) has some odd stipulations.
if (backingObject->isTree() || backingObject->isTreeItem())
return [[self accessibilityAttributeValue:NSAccessibilityChildrenAttribute] count];
auto childrenSize = self.childrenVectorSize;
if (!childrenSize) {
#if ENABLE(MODEL_ELEMENT)
if (backingObject->isModel())
return backingObject->modelElementChildren().size();
#endif
if (NSArray *renderWidgetChildren = [self renderWidgetChildren])
return [renderWidgetChildren count];
}
return childrenSize;
}
return [super accessibilityArrayAttributeCount:attribute];
}
ALLOW_DEPRECATED_DECLARATIONS_END
- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount
{
AXTRACE(makeString("WebAccessibilityObjectWrapper accessibilityArrayAttributeValue:", String(attribute)));
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return nil;
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
if (!self.childrenVectorSize) {
NSArray *children = nil;
#if ENABLE(MODEL_ELEMENT)
if (backingObject->isModel()) {
children = createNSArray(backingObject->modelElementChildren(), [] (auto& child) -> id {
return child.get();
}).autorelease();
} else
#endif
children = [self renderWidgetChildren];
if (!children)
return nil;
NSUInteger childCount = [children count];
if (index >= childCount)
return nil;
NSUInteger arrayLength = std::min(childCount - index, maxCount);
return [children subarrayWithRange:NSMakeRange(index, arrayLength)];
}
if (backingObject->isTree() || backingObject->isTreeItem()) {
// Tree objects return their rows as their children & tree items return their contents sans rows.
// We can use the original method in this case.
return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount];
}
auto children = self.childrenVectorArray;
unsigned childCount = [children count];
if (index >= childCount)
return nil;
unsigned available = std::min(childCount - index, maxCount);
NSMutableArray *subarray = [NSMutableArray arrayWithCapacity:available];
for (unsigned added = 0; added < available; ++index, ++added) {
WebAccessibilityObjectWrapper* wrapper = children[index];
// The attachment view should be returned, otherwise AX palindrome errors occur.
BOOL isAttachment = [wrapper isKindOfClass:[WebAccessibilityObjectWrapper class]] && wrapper.axBackingObject && wrapper.axBackingObject->isAttachment() && [wrapper attachmentView];
[subarray addObject:isAttachment ? [wrapper attachmentView] : wrapper];
}
return subarray;
}
return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount];
}
@end
#endif // ENABLE(ACCESSIBILITY) && PLATFORM(MAC)