blob: a102d363c9b316e6cdef5a91ae10700fce1a3619 [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 HAVE(ACCESSIBILITY) && PLATFORM(MAC)
#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 "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/spi/mac/HIServicesSPI.h>
#import <pal/spi/mac/NSAccessibilitySPI.h>
#import <wtf/ObjcRuntimeExtras.h>
#if ENABLE(TREE_DEBUGGING) || ENABLE(METER_ELEMENT)
#import <wtf/text/StringBuilder.h>
#endif
using namespace WebCore;
using namespace HTMLNames;
// 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
// Lists
#ifndef NSAccessibilityContentListSubrole
#define NSAccessibilityContentListSubrole @"AXContentList"
#endif
#ifndef NSAccessibilityDefinitionListSubrole
#define NSAccessibilityDefinitionListSubrole @"AXDefinitionList"
#endif
#ifndef NSAccessibilityDescriptionListSubrole
#define NSAccessibilityDescriptionListSubrole @"AXDescriptionList"
#endif
#ifndef NSAccessibilityContentSeparatorSubrole
#define NSAccessibilityContentSeparatorSubrole @"AXContentSeparator"
#endif
#ifndef NSAccessibilityRubyBaseSubRole
#define NSAccessibilityRubyBaseSubrole @"AXRubyBase"
#endif
#ifndef NSAccessibilityRubyBlockSubrole
#define NSAccessibilityRubyBlockSubrole @"AXRubyBlock"
#endif
#ifndef NSAccessibilityRubyInlineSubrole
#define NSAccessibilityRubyInlineSubrole @"AXRubyInline"
#endif
#ifndef NSAccessibilityRubyRunSubrole
#define NSAccessibilityRubyRunSubrole @"AXRubyRun"
#endif
#ifndef NSAccessibilityRubyTextSubrole
#define NSAccessibilityRubyTextSubrole @"AXRubyText"
#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 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
// 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
// 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
extern "C" AXUIElementRef NSAccessibilityCreateAXUIElementRef(id element);
@implementation WebAccessibilityObjectWrapper
- (void)unregisterUniqueIdForUIElement
{
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
{
ASSERT(m_object->isAttachment());
Widget* widget = m_object->widgetForAttachmentView();
if (!widget)
return nil;
return NSAccessibilityUnignoredDescendant(widget->platformWidget());
}
#pragma mark SystemInterface wrappers
static inline BOOL AXObjectIsTextMarker(id obj)
{
return obj != nil && CFGetTypeID(obj) == AXTextMarkerGetTypeID();
}
static inline BOOL AXObjectIsTextMarkerRange(id obj)
{
return obj != nil && CFGetTypeID(obj) == AXTextMarkerRangeGetTypeID();
}
static id AXTextMarkerRange(id startMarker, id endMarker)
{
ASSERT(startMarker != nil);
ASSERT(endMarker != nil);
ASSERT(CFGetTypeID(startMarker) == AXTextMarkerGetTypeID());
ASSERT(CFGetTypeID(endMarker) == AXTextMarkerGetTypeID());
return CFBridgingRelease(AXTextMarkerRangeCreate(kCFAllocatorDefault, (AXTextMarkerRef)startMarker, (AXTextMarkerRef)endMarker));
}
static id AXTextMarkerRangeStart(id range)
{
ASSERT(range != nil);
ASSERT(CFGetTypeID(range) == AXTextMarkerRangeGetTypeID());
return CFBridgingRelease(AXTextMarkerRangeCopyStartMarker((AXTextMarkerRangeRef)range));
}
static id AXTextMarkerRangeEnd(id range)
{
ASSERT(range != nil);
ASSERT(CFGetTypeID(range) == AXTextMarkerRangeGetTypeID());
return CFBridgingRelease(AXTextMarkerRangeCopyEndMarker((AXTextMarkerRangeRef)range));
}
#pragma mark Other helpers
- (IntRect)screenToContents:(const IntRect&)rect
{
Document* document = m_object->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
static AccessibilitySelectTextCriteria accessibilitySelectTextCriteriaForCriteriaParameterizedAttribute(const NSDictionary *parameterizedAttribute)
{
NSString *activityParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextActivity];
NSString *ambiguityResolutionParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextAmbiguityResolution];
NSString *replacementStringParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextReplacementString];
NSArray *searchStringsParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextSearchStrings];
AccessibilitySelectTextActivity activity = AccessibilitySelectTextActivity::FindAndSelect;
if ([activityParameter isKindOfClass:[NSString class]]) {
if ([activityParameter isEqualToString:NSAccessibilitySelectTextActivityFindAndReplace])
activity = AccessibilitySelectTextActivity::FindAndReplace;
else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndCapitalize])
activity = AccessibilitySelectTextActivity::FindAndCapitalize;
else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndLowercase])
activity = AccessibilitySelectTextActivity::FindAndLowercase;
else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndUppercase])
activity = AccessibilitySelectTextActivity::FindAndUppercase;
}
AccessibilitySelectTextAmbiguityResolution ambiguityResolution = AccessibilitySelectTextAmbiguityResolution::ClosestTo;
if ([ambiguityResolutionParameter isKindOfClass:[NSString class]]) {
if ([ambiguityResolutionParameter isEqualToString:NSAccessibilitySelectTextAmbiguityResolutionClosestAfterSelection])
ambiguityResolution = AccessibilitySelectTextAmbiguityResolution::ClosestAfter;
else if ([ambiguityResolutionParameter isEqualToString:NSAccessibilitySelectTextAmbiguityResolutionClosestBeforeSelection])
ambiguityResolution = AccessibilitySelectTextAmbiguityResolution::ClosestBefore;
}
String replacementString;
if ([replacementStringParameter isKindOfClass:[NSString class]])
replacementString = replacementStringParameter;
AccessibilitySelectTextCriteria criteria(activity, ambiguityResolution, replacementString);
if ([searchStringsParameter isKindOfClass:[NSArray class]]) {
size_t searchStringsCount = static_cast<size_t>([searchStringsParameter count]);
criteria.searchStrings.reserveInitialCapacity(searchStringsCount);
for (NSString *searchString in searchStringsParameter) {
if ([searchString isKindOfClass:[NSString class]])
criteria.searchStrings.uncheckedAppend(searchString);
}
}
return criteria;
}
#pragma mark Text Marker helpers
static BOOL getBytesFromAXTextMarker(CFTypeRef textMarker, void* bytes, size_t length)
{
if (!textMarker)
return NO;
AXTextMarkerRef ref = (AXTextMarkerRef)textMarker;
ASSERT(CFGetTypeID(ref) == AXTextMarkerGetTypeID());
if (CFGetTypeID(ref) != AXTextMarkerGetTypeID())
return NO;
CFIndex expectedLength = length;
if (AXTextMarkerGetLength(ref) != expectedLength)
return NO;
memcpy(bytes, AXTextMarkerGetBytePtr(ref), length);
return YES;
}
static bool isTextMarkerIgnored(id textMarker)
{
if (!textMarker)
return false;
TextMarkerData textMarkerData;
if (!getBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
return false;
return textMarkerData.ignored;
}
- (AccessibilityObject*)accessibilityObjectForTextMarker:(id)textMarker
{
return accessibilityObjectForTextMarker(m_object->axObjectCache(), textMarker);
}
static AccessibilityObject* accessibilityObjectForTextMarker(AXObjectCache* cache, id textMarker)
{
if (!textMarker || !cache || isTextMarkerIgnored(textMarker))
return nullptr;
TextMarkerData textMarkerData;
if (!getBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
return nullptr;
return cache->accessibilityObjectForTextMarkerData(textMarkerData);
}
- (id)textMarkerRangeFromRange:(const RefPtr<Range>)range
{
return textMarkerRangeFromRange(m_object->axObjectCache(), range);
}
static id textMarkerRangeFromRange(AXObjectCache* cache, const RefPtr<Range> range)
{
id startTextMarker = startOrEndTextmarkerForRange(cache, range, true);
id endTextMarker = startOrEndTextmarkerForRange(cache, range, false);
return textMarkerRangeFromMarkers(startTextMarker, endTextMarker);
}
- (id)startOrEndTextMarkerForRange:(const RefPtr<Range>)range isStart:(BOOL)isStart
{
return startOrEndTextmarkerForRange(m_object->axObjectCache(), range, isStart);
}
static id startOrEndTextmarkerForRange(AXObjectCache* cache, RefPtr<Range> range, bool isStart)
{
if (!cache)
return nil;
TextMarkerData textMarkerData;
cache->startOrEndTextMarkerDataForRange(textMarkerData, range, isStart);
if (!textMarkerData.axID)
return nil;
return CFBridgingRelease(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData)));
}
static id nextTextMarkerForCharacterOffset(AXObjectCache* cache, CharacterOffset& characterOffset)
{
if (!cache)
return nil;
TextMarkerData textMarkerData;
cache->textMarkerDataForNextCharacterOffset(textMarkerData, characterOffset);
if (!textMarkerData.axID)
return nil;
return CFBridgingRelease(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData)));
}
static id previousTextMarkerForCharacterOffset(AXObjectCache* cache, CharacterOffset& characterOffset)
{
if (!cache)
return nil;
TextMarkerData textMarkerData;
cache->textMarkerDataForPreviousCharacterOffset(textMarkerData, characterOffset);
if (!textMarkerData.axID)
return nil;
return CFBridgingRelease(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData)));
}
- (id)nextTextMarkerForCharacterOffset:(CharacterOffset&)characterOffset
{
return nextTextMarkerForCharacterOffset(m_object->axObjectCache(), characterOffset);
}
- (id)previousTextMarkerForCharacterOffset:(CharacterOffset&)characterOffset
{
return previousTextMarkerForCharacterOffset(m_object->axObjectCache(), characterOffset);
}
- (id)textMarkerForCharacterOffset:(CharacterOffset&)characterOffset
{
return textMarkerForCharacterOffset(m_object->axObjectCache(), characterOffset);
}
static id textMarkerForCharacterOffset(AXObjectCache* cache, const CharacterOffset& characterOffset)
{
if (!cache)
return nil;
TextMarkerData textMarkerData;
cache->textMarkerDataForCharacterOffset(textMarkerData, characterOffset);
if (!textMarkerData.axID && !textMarkerData.ignored)
return nil;
return CFBridgingRelease(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData, sizeof(textMarkerData)));
}
- (RefPtr<Range>)rangeForTextMarkerRange:(id)textMarkerRange
{
if (!textMarkerRange)
return nullptr;
id startTextMarker = AXTextMarkerRangeStart(textMarkerRange);
id endTextMarker = AXTextMarkerRangeEnd(textMarkerRange);
if (!startTextMarker || !endTextMarker)
return nullptr;
AXObjectCache* cache = m_object->axObjectCache();
if (!cache)
return nullptr;
CharacterOffset startCharacterOffset = [self characterOffsetForTextMarker:startTextMarker];
CharacterOffset endCharacterOffset = [self characterOffsetForTextMarker:endTextMarker];
return cache->rangeForUnorderedCharacterOffsets(startCharacterOffset, endCharacterOffset);
}
static CharacterOffset characterOffsetForTextMarker(AXObjectCache* cache, CFTypeRef textMarker)
{
if (!cache || !textMarker)
return CharacterOffset();
TextMarkerData textMarkerData;
if (!getBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
return CharacterOffset();
return cache->characterOffsetForTextMarkerData(textMarkerData);
}
- (CharacterOffset)characterOffsetForTextMarker:(id)textMarker
{
return characterOffsetForTextMarker(m_object->axObjectCache(), textMarker);
}
static id textMarkerForVisiblePosition(AXObjectCache* cache, const VisiblePosition& visiblePos)
{
ASSERT(cache);
auto textMarkerData = cache->textMarkerDataForVisiblePosition(visiblePos);
if (!textMarkerData)
return nil;
return CFBridgingRelease(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData.value(), sizeof(textMarkerData.value())));
}
- (id)textMarkerForVisiblePosition:(const VisiblePosition &)visiblePos
{
return textMarkerForVisiblePosition(m_object->axObjectCache(), visiblePos);
}
- (id)textMarkerForFirstPositionInTextControl:(HTMLTextFormControlElement &)textControl
{
auto *cache = m_object->axObjectCache();
if (!cache)
return nil;
auto textMarkerData = cache->textMarkerDataForFirstPositionInTextControl(textControl);
if (!textMarkerData)
return nil;
return CFBridgingRelease(AXTextMarkerCreate(kCFAllocatorDefault, (const UInt8*)&textMarkerData.value(), sizeof(textMarkerData.value())));
}
static VisiblePosition visiblePositionForTextMarker(AXObjectCache* cache, CFTypeRef textMarker)
{
ASSERT(cache);
if (!textMarker)
return VisiblePosition();
TextMarkerData textMarkerData;
if (!getBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
return VisiblePosition();
return cache->visiblePositionForTextMarkerData(textMarkerData);
}
- (VisiblePosition)visiblePositionForTextMarker:(id)textMarker
{
return visiblePositionForTextMarker(m_object->axObjectCache(), textMarker);
}
static VisiblePosition visiblePositionForStartOfTextMarkerRange(AXObjectCache* cache, id textMarkerRange)
{
return visiblePositionForTextMarker(cache, AXTextMarkerRangeStart(textMarkerRange));
}
static VisiblePosition visiblePositionForEndOfTextMarkerRange(AXObjectCache* cache, id textMarkerRange)
{
return visiblePositionForTextMarker(cache, AXTextMarkerRangeEnd(textMarkerRange));
}
static id textMarkerRangeFromMarkers(id textMarker1, id textMarker2)
{
if (!textMarker1 || !textMarker2)
return nil;
return AXTextMarkerRange(textMarker1, textMarker2);
}
// 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 *dict = [NSDictionary dictionaryWithObjectsAndKeys:
static_cast<NSString *>(adoptCF(CTFontCopyPostScriptName(font)).get()), NSAccessibilityFontNameKey,
static_cast<NSString *>(adoptCF(CTFontCopyFamilyName(font)).get()), NSAccessibilityFontFamilyKey,
static_cast<NSString *>(adoptCF(CTFontCopyDisplayName(font)).get()), NSAccessibilityVisibleNameKey,
[NSNumber numberWithFloat:CTFontGetSize(font)], NSAccessibilityFontSizeKey,
nil];
[attrString addAttribute:attribute value:dict 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;
CGColorRef existingColor = (CGColorRef)[attrString attribute:attribute atIndex:range.location effectiveRange:nil];
if (!existingColor || !CGColorEqualToColor(existingColor, cgColor))
[attrString addAttribute:attribute value:(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, nsColor(style.visitedDependentColor(CSSPropertyColor)), range);
AXAttributeStringSetColor(attrString, NSAccessibilityBackgroundColorTextAttribute, nsColor(style.visitedDependentColor(CSSPropertyBackgroundColor)), range);
// set super/sub scripting
EVerticalAlign alignment = style.verticalAlign();
if (alignment == SUB)
AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:(-1)], range);
else if (alignment == SUPER)
AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt: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
int decor = style.textDecorationsInEffect();
if ((decor & TextDecorationUnderline) == 0) {
[attrString removeAttribute:NSAccessibilityUnderlineTextAttribute range:range];
[attrString removeAttribute:NSAccessibilityUnderlineColorTextAttribute range:range];
}
if ((decor & TextDecorationLineThrough) == 0) {
[attrString removeAttribute:NSAccessibilityStrikethroughTextAttribute range:range];
[attrString removeAttribute:NSAccessibilityStrikethroughColorTextAttribute range:range];
}
if ((decor & (TextDecorationUnderline | TextDecorationLineThrough)) != 0) {
// FIXME: Should the underline style be reported here?
auto decorationStyles = TextDecorationPainter::stylesForRenderer(*renderer, decor);
if ((decor & TextDecorationUnderline) != 0) {
AXAttributeStringSetNumber(attrString, NSAccessibilityUnderlineTextAttribute, @YES, range);
AXAttributeStringSetColor(attrString, NSAccessibilityUnderlineColorTextAttribute, nsColor(decorationStyles.underlineColor), range);
}
if ((decor & TextDecorationLineThrough) != 0) {
AXAttributeStringSetNumber(attrString, NSAccessibilityStrikethroughTextAttribute, @YES, range);
AXAttributeStringSetColor(attrString, NSAccessibilityStrikethroughColorTextAttribute, nsColor(decorationStyles.linethroughColor), range);
}
}
// Indicate background highlighting.
for (Node* node = renderer->node(); node; node = node->parentNode()) {
if (node->hasTagName(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:[NSNumber numberWithInt: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().frame()->editor().textChecker();
// checkTextOfParagraph is the only spelling/grammar checker implemented in WK1 and WK2
Vector<TextCheckingResult> results;
checkTextOfParagraph(*checker, text, TextCheckingTypeSpelling, 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.location + range.location, result.length));
#if PLATFORM(MAC)
AXAttributeStringSetNumber(attrString, NSAccessibilityMarkedMisspelledTextAttribute, @YES, NSMakeRange(result.location + range.location, result.length));
#endif
}
return;
}
for (unsigned currentPosition = 0; currentPosition < text.length(); ) {
int misspellingLocation = -1;
int misspellingLength = 0;
node->document().frame()->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:[NSNumber numberWithInt: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];
AXUIElementRef axElement = NSAccessibilityCreateAXUIElementRef(objectWrapper);
if (axElement) {
[attrString addAttribute:attribute value:(id)axElement range:range];
CFRelease(axElement);
}
} 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)
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:(id)textMarkerRange spellCheck:(BOOL)spellCheck
{
if (!m_object)
return nil;
RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] init];
TextIterator it(range.get());
while (!it.atEnd()) {
// locate the node and starting offset for this range
Node& node = it.range()->startContainer();
ASSERT(&node == &it.range()->endContainer());
int offset = it.range()->startOffset();
// 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 = m_object->listMarkerTextForNodeAndPosition(&node, VisiblePosition(it.range()->startPosition()));
if (!listMarkerText.isEmpty())
AXAttributedStringAppendText(attrString, &node, listMarkerText, spellCheck);
AXAttributedStringAppendText(attrString, &node, it.text(), spellCheck);
} else {
Node* replacedNode = node.traverseToChildAt(offset);
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, NSAccessibilityAttachmentTextAttribute, obj, attrStringRange);
}
}
it.advance();
}
return [attrString autorelease];
}
static id textMarkerRangeFromVisiblePositions(AXObjectCache* cache, const VisiblePosition& startPosition, const VisiblePosition& endPosition)
{
id startTextMarker = textMarkerForVisiblePosition(cache, startPosition);
id endTextMarker = textMarkerForVisiblePosition(cache, endPosition);
return textMarkerRangeFromMarkers(startTextMarker, endTextMarker);
}
- (id)textMarkerRangeFromVisiblePositions:(const VisiblePosition&)startPosition endPosition:(const VisiblePosition&)endPosition
{
return textMarkerRangeFromVisiblePositions(m_object->axObjectCache(), startPosition, endPosition);
}
- (NSArray*)accessibilityActionNames
{
if (![self updateObjectBackingStore])
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 NSArray *defaultElementActions = [[NSArray alloc] initWithObjects:NSAccessibilityShowMenuAction, NSAccessibilityScrollToVisibleAction, nil];
// 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 NSArray *actionElementActions = [[NSArray alloc] initWithObjects:NSAccessibilityPressAction, NSAccessibilityShowMenuAction, NSAccessibilityScrollToVisibleAction, nil];
// Menu elements allow Press and Cancel.
static NSArray *menuElementActions = [[actionElementActions arrayByAddingObject:NSAccessibilityCancelAction] retain];
// Slider elements allow Increment/Decrement.
static NSArray *sliderActions = [[defaultElementActions arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:NSAccessibilityIncrementAction, NSAccessibilityDecrementAction, nil]] retain];
NSArray *actions;
if (m_object->supportsPressAction())
actions = actionElementActions;
else if (m_object->isMenuRelated())
actions = menuElementActions;
else if (m_object->isSlider())
actions = sliderActions;
else if (m_object->isAttachment())
actions = [[self attachmentView] accessibilityActionNames];
else
actions = defaultElementActions;
return actions;
}
- (NSArray*)additionalAccessibilityAttributeNames
{
if (!m_object)
return nil;
NSMutableArray *additional = [NSMutableArray array];
if (m_object->supportsARIAOwns())
[additional addObject:NSAccessibilityOwnsAttribute];
if (m_object->isToggleButton())
[additional addObject:NSAccessibilityValueAttribute];
if (m_object->supportsExpanded() || m_object->isSummary())
[additional addObject:NSAccessibilityExpandedAttribute];
if (m_object->isScrollbar()
|| m_object->isRadioGroup()
|| m_object->isSplitter()
|| m_object->isToolbar())
[additional addObject:NSAccessibilityOrientationAttribute];
if (m_object->supportsARIADragging())
[additional addObject:NSAccessibilityGrabbedAttribute];
if (m_object->supportsARIADropping())
[additional addObject:NSAccessibilityDropEffectsAttribute];
if (is<AccessibilityTable>(*m_object) && downcast<AccessibilityTable>(*m_object).isExposableThroughAccessibility() && downcast<AccessibilityTable>(*m_object).supportsSelectedRows())
[additional addObject:NSAccessibilitySelectedRowsAttribute];
if (m_object->supportsLiveRegion()) {
[additional addObject:NSAccessibilityARIALiveAttribute];
[additional addObject:NSAccessibilityARIARelevantAttribute];
}
if (m_object->supportsSetSize())
[additional addObject:NSAccessibilityARIASetSizeAttribute];
if (m_object->supportsPosInSet())
[additional addObject:NSAccessibilityARIAPosInSetAttribute];
AccessibilitySortDirection sortDirection = m_object->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 (m_object->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 (m_object->isPopUpButton()) {
[additional addObject:NSAccessibilityValueAttribute];
}
if (m_object->supportsDatetimeAttribute())
[additional addObject:NSAccessibilityDatetimeValueAttribute];
if (m_object->supportsRequiredAttribute()) {
[additional addObject:NSAccessibilityRequiredAttribute];
}
if (m_object->hasPopup())
[additional addObject:NSAccessibilityHasPopupAttribute];
if (m_object->isMathRoot()) {
// The index of a square root is always known, so there's no object associated with it.
if (!m_object->isMathSquareRoot())
[additional addObject:NSAccessibilityMathRootIndexAttribute];
[additional addObject:NSAccessibilityMathRootRadicandAttribute];
} else if (m_object->isMathFraction()) {
[additional addObject:NSAccessibilityMathFractionNumeratorAttribute];
[additional addObject:NSAccessibilityMathFractionDenominatorAttribute];
[additional addObject:NSAccessibilityMathLineThicknessAttribute];
} else if (m_object->isMathSubscriptSuperscript()) {
[additional addObject:NSAccessibilityMathBaseAttribute];
[additional addObject:NSAccessibilityMathSubscriptAttribute];
[additional addObject:NSAccessibilityMathSuperscriptAttribute];
} else if (m_object->isMathUnderOver()) {
[additional addObject:NSAccessibilityMathBaseAttribute];
[additional addObject:NSAccessibilityMathUnderAttribute];
[additional addObject:NSAccessibilityMathOverAttribute];
} else if (m_object->isMathFenced()) {
[additional addObject:NSAccessibilityMathFencedOpenAttribute];
[additional addObject:NSAccessibilityMathFencedCloseAttribute];
} else if (m_object->isMathMultiscript()) {
[additional addObject:NSAccessibilityMathBaseAttribute];
[additional addObject:NSAccessibilityMathPrescriptsAttribute];
[additional addObject:NSAccessibilityMathPostscriptsAttribute];
}
if (m_object->supportsPath())
[additional addObject:NSAccessibilityPathAttribute];
if (m_object->supportsExpandedTextValue())
[additional addObject:NSAccessibilityExpandedTextValueAttribute];
return additional;
}
- (NSArray*)accessibilityAttributeNames
{
if (![self updateObjectBackingStore])
return nil;
if (m_object->isAttachment())
return [[self attachmentView] accessibilityAttributeNames];
static NSArray* attributes = nil;
static NSArray* anchorAttrs = nil;
static NSArray* webAreaAttrs = nil;
static NSArray* textAttrs = nil;
static NSArray* listAttrs = nil;
static NSArray* listBoxAttrs = nil;
static NSArray* rangeAttrs = nil;
static NSArray* commonMenuAttrs = nil;
static NSArray* menuAttrs = nil;
static NSArray* menuBarAttrs = nil;
static NSArray* menuItemAttrs = nil;
static NSArray* menuButtonAttrs = nil;
static NSArray* controlAttrs = nil;
static NSArray* tableAttrs = nil;
static NSArray* tableRowAttrs = nil;
static NSArray* tableColAttrs = nil;
static NSArray* tableCellAttrs = nil;
static NSArray* groupAttrs = nil;
static NSArray* inputImageAttrs = nil;
static NSArray* passwordFieldAttrs = nil;
static NSArray* tabListAttrs = nil;
static NSArray* comboBoxAttrs = nil;
static NSArray* outlineAttrs = nil;
static NSArray* outlineRowAttrs = nil;
static NSArray* buttonAttrs = nil;
static NSArray* scrollViewAttrs = nil;
static NSArray* incrementorAttrs = nil;
NSMutableArray* tempArray;
if (attributes == nil) {
attributes = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute,
NSAccessibilitySubroleAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilityChildrenAttribute,
NSAccessibilityHelpAttribute,
NSAccessibilityParentAttribute,
NSAccessibilityPositionAttribute,
NSAccessibilitySizeAttribute,
NSAccessibilityTitleAttribute,
NSAccessibilityDescriptionAttribute,
NSAccessibilityValueAttribute,
NSAccessibilityFocusedAttribute,
NSAccessibilityEnabledAttribute,
NSAccessibilityWindowAttribute,
@"AXSelectedTextMarkerRange",
@"AXStartTextMarker",
@"AXEndTextMarker",
@"AXVisited",
NSAccessibilityLinkedUIElementsAttribute,
NSAccessibilitySelectedAttribute,
NSAccessibilityBlockQuoteLevelAttribute,
NSAccessibilityTopLevelUIElementAttribute,
NSAccessibilityLanguageAttribute,
NSAccessibilityDOMIdentifierAttribute,
NSAccessibilityDOMClassListAttribute,
NSAccessibilityFocusableAncestorAttribute,
NSAccessibilityEditableAncestorAttribute,
NSAccessibilityHighestEditableAncestorAttribute,
nil];
}
if (commonMenuAttrs == nil) {
commonMenuAttrs = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilityChildrenAttribute,
NSAccessibilityParentAttribute,
NSAccessibilityEnabledAttribute,
NSAccessibilityPositionAttribute,
NSAccessibilitySizeAttribute,
nil];
}
if (anchorAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityURLAttribute];
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
anchorAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (webAreaAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
// 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];
webAreaAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (textAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[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];
textAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (listAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilitySelectedChildrenAttribute];
[tempArray addObject:NSAccessibilityVisibleChildrenAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
listAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (listBoxAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:listAttrs];
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
[tempArray addObject:NSAccessibilityRequiredAttribute];
[tempArray addObject:NSAccessibilityInvalidAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
listBoxAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (rangeAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityMinValueAttribute];
[tempArray addObject:NSAccessibilityMaxValueAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
[tempArray addObject:NSAccessibilityValueDescriptionAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
rangeAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (menuBarAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs];
[tempArray addObject:NSAccessibilitySelectedChildrenAttribute];
[tempArray addObject:NSAccessibilityVisibleChildrenAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
menuBarAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (menuAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs];
[tempArray addObject:NSAccessibilitySelectedChildrenAttribute];
[tempArray addObject:NSAccessibilityVisibleChildrenAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
menuAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (menuItemAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs];
[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];
menuItemAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (menuButtonAttrs == nil) {
menuButtonAttrs = [[NSArray alloc] initWithObjects:NSAccessibilityRoleAttribute,
NSAccessibilityRoleDescriptionAttribute,
NSAccessibilityParentAttribute,
NSAccessibilityPositionAttribute,
NSAccessibilitySizeAttribute,
NSAccessibilityWindowAttribute,
NSAccessibilityEnabledAttribute,
NSAccessibilityFocusedAttribute,
NSAccessibilityTitleAttribute,
NSAccessibilityChildrenAttribute, nil];
}
if (controlAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
[tempArray addObject:NSAccessibilityRequiredAttribute];
[tempArray addObject:NSAccessibilityInvalidAttribute];
controlAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (incrementorAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityIncrementButtonAttribute];
[tempArray addObject:NSAccessibilityDecrementButtonAttribute];
[tempArray addObject:NSAccessibilityValueDescriptionAttribute];
[tempArray addObject:NSAccessibilityMinValueAttribute];
[tempArray addObject:NSAccessibilityMaxValueAttribute];
incrementorAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (buttonAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
// Buttons should not expose AXValue.
[tempArray removeObject:NSAccessibilityValueAttribute];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityAccessKeyAttribute];
buttonAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (comboBoxAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:controlAttrs];
[tempArray addObject:NSAccessibilityExpandedAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
comboBoxAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (tableAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[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];
tableAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (tableRowAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityIndexAttribute];
tableRowAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (tableColAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityIndexAttribute];
[tempArray addObject:NSAccessibilityHeaderAttribute];
[tempArray addObject:NSAccessibilityRowsAttribute];
[tempArray addObject:NSAccessibilityVisibleRowsAttribute];
tableColAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (tableCellAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityRowIndexRangeAttribute];
[tempArray addObject:NSAccessibilityColumnIndexRangeAttribute];
[tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute];
[tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute];
[tempArray addObject:NSAccessibilityARIAColumnIndexAttribute];
[tempArray addObject:NSAccessibilityARIARowIndexAttribute];
tableCellAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (groupAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
groupAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (inputImageAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:buttonAttrs];
[tempArray addObject:NSAccessibilityURLAttribute];
inputImageAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (passwordFieldAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityTitleUIElementAttribute];
[tempArray addObject:NSAccessibilityRequiredAttribute];
[tempArray addObject:NSAccessibilityInvalidAttribute];
[tempArray addObject:NSAccessibilityPlaceholderValueAttribute];
[tempArray addObject:NSAccessibilityValueAutofilledAttribute];
passwordFieldAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (tabListAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityTabsAttribute];
[tempArray addObject:NSAccessibilityContentsAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
tabListAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (outlineAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilitySelectedRowsAttribute];
[tempArray addObject:NSAccessibilityRowsAttribute];
[tempArray addObject:NSAccessibilityColumnsAttribute];
[tempArray addObject:NSAccessibilityOrientationAttribute];
outlineAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (outlineRowAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:tableRowAttrs];
[tempArray addObject:NSAccessibilityDisclosingAttribute];
[tempArray addObject:NSAccessibilityDisclosedByRowAttribute];
[tempArray addObject:NSAccessibilityDisclosureLevelAttribute];
[tempArray addObject:NSAccessibilityDisclosedRowsAttribute];
outlineRowAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (scrollViewAttrs == nil) {
tempArray = [[NSMutableArray alloc] initWithArray:attributes];
[tempArray addObject:NSAccessibilityContentsAttribute];
[tempArray addObject:NSAccessibilityHorizontalScrollBarAttribute];
[tempArray addObject:NSAccessibilityVerticalScrollBarAttribute];
scrollViewAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
NSArray *objectAttributes = attributes;
if (m_object->isPasswordField())
objectAttributes = passwordFieldAttrs;
else if (m_object->isWebArea())
objectAttributes = webAreaAttrs;
else if (m_object->isTextControl())
objectAttributes = textAttrs;
else if (m_object->isLink() || m_object->isImage())
objectAttributes = anchorAttrs;
else if (is<AccessibilityTable>(*m_object) && downcast<AccessibilityTable>(*m_object).isExposableThroughAccessibility())
objectAttributes = tableAttrs;
else if (m_object->isTableColumn())
objectAttributes = tableColAttrs;
else if (m_object->isTableCell())
objectAttributes = tableCellAttrs;
else if (m_object->isTableRow()) {
// An ARIA table row can be collapsed and expanded, so it needs the extra attributes.
if (m_object->isARIATreeGridRow())
objectAttributes = outlineRowAttrs;
else
objectAttributes = tableRowAttrs;
}
else if (m_object->isTree())
objectAttributes = outlineAttrs;
else if (m_object->isTreeItem())
objectAttributes = outlineRowAttrs;
else if (m_object->isListBox())
objectAttributes = listBoxAttrs;
else if (m_object->isList())
objectAttributes = listAttrs;
else if (m_object->isComboBox())
objectAttributes = comboBoxAttrs;
else if (m_object->isProgressIndicator() || m_object->isSlider())
objectAttributes = rangeAttrs;
// These are processed in order because an input image is a button, and a button is a control.
else if (m_object->isInputImage())
objectAttributes = inputImageAttrs;
else if (m_object->isButton())
objectAttributes = buttonAttrs;
else if (m_object->isControl())
objectAttributes = controlAttrs;
else if (m_object->isGroup() || m_object->isListItem())
objectAttributes = groupAttrs;
else if (m_object->isTabList())
objectAttributes = tabListAttrs;
else if (m_object->isScrollView())
objectAttributes = scrollViewAttrs;
else if (m_object->isSpinButton())
objectAttributes = incrementorAttrs;
else if (m_object->isMenu())
objectAttributes = menuAttrs;
else if (m_object->isMenuBar())
objectAttributes = menuBarAttrs;
else if (m_object->isMenuButton())
objectAttributes = menuButtonAttrs;
else if (m_object->isMenuItem())
objectAttributes = menuItemAttrs;
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 (m_object->currentState() != AccessibilityCurrentState::False)
objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:@[ NSAccessibilityARIACurrentAttribute ]];
return objectAttributes;
}
- (VisiblePositionRange)visiblePositionRangeForTextMarkerRange:(id)textMarkerRange
{
if (!textMarkerRange)
return VisiblePositionRange();
AXObjectCache* cache = m_object->axObjectCache();
return VisiblePositionRange(visiblePositionForStartOfTextMarkerRange(cache, textMarkerRange), visiblePositionForEndOfTextMarkerRange(cache, textMarkerRange));
}
- (NSArray*)renderWidgetChildren
{
Widget* widget = m_object->widget();
if (!widget)
return nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return [(widget->platformWidget()) accessibilityAttributeValue: NSAccessibilityChildrenAttribute];
#pragma clang diagnostic pop
}
- (id)remoteAccessibilityParentObject
{
if (!m_object)
return nil;
Document* document = m_object->document();
if (!document)
return nil;
Frame* frame = document->frame();
if (!frame)
return nil;
return frame->loader().client().accessibilityRemoteObject();
}
static void convertToVector(NSArray* array, AccessibilityObject::AccessibilityChildrenVector& vector)
{
unsigned length = [array count];
vector.reserveInitialCapacity(length);
for (unsigned i = 0; i < length; ++i) {
AccessibilityObject* obj = [[array objectAtIndex:i] accessibilityObject];
if (obj)
vector.append(obj);
}
}
static NSMutableArray *convertStringsToNSArray(const Vector<String>& vector)
{
NSMutableArray *array = [NSMutableArray arrayWithCapacity:vector.size()];
for (const auto& string : vector)
[array addObject:string];
return array;
}
- (id)textMarkerRangeForSelection
{
VisibleSelection selection = m_object->selection();
if (selection.isNone())
return nil;
return [self textMarkerRangeFromVisiblePositions:selection.visibleStart() endPosition:selection.visibleEnd()];
}
- (id)associatedPluginParent
{
if (!m_object || !m_object->hasAttribute(x_apple_pdf_annotationAttr))
return nil;
if (!m_object->document()->isPluginDocument())
return nil;
Widget* pluginWidget = static_cast<PluginDocument*>(m_object->document())->pluginWidget();
if (!pluginWidget || !pluginWidget->isPluginViewBase())
return nil;
return static_cast<PluginViewBase*>(pluginWidget)->accessibilityAssociatedPluginParentForElement(m_object->element());
}
- (CGPoint)convertPointToScreenSpace:(FloatPoint &)point
{
FrameView* frameView = m_object->documentFrameView();
// WebKit1 code path... platformWidget() exists.
if (frameView && frameView->platformWidget()) {
NSPoint nsPoint = (NSPoint)point;
NSView* view = frameView->documentView();
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
nsPoint = [[view window] convertBaseToScreen:[view convertPoint:nsPoint toView:nil]];
#pragma clang diagnostic pop
return CGPointMake(nsPoint.x, nsPoint.y);
} else {
// Find the appropriate scroll view to use to convert the contents to the window.
ScrollView* scrollView = nullptr;
AccessibilityObject* parent = nullptr;
for (parent = m_object->parentObject(); parent; parent = parent->parentObject()) {
if (is<AccessibilityScrollView>(*parent)) {
scrollView = downcast<AccessibilityScrollView>(*parent).scrollView();
break;
}
}
IntPoint intPoint = flooredIntPoint(point);
if (scrollView)
intPoint = scrollView->contentsToRootView(intPoint);
Page* page = m_object->page();
// If we have an empty chrome client (like SVG) then we should use the page
// of the scroll view parent to help us get to the screen rect.
if (parent && page && page->chrome().client().isEmptyChromeClient())
page = parent->page();
if (page) {
IntRect rect = IntRect(intPoint, IntSize(0, 0));
intPoint = page->chrome().rootViewToScreen(rect).location();
}
return intPoint;
}
}
static void WebTransformCGPathToNSBezierPath(void *info, const CGPathElement *element)
{
NSBezierPath *bezierPath = (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, bezierPath, WebTransformCGPathToNSBezierPath);
return bezierPath;
}
- (NSBezierPath *)path
{
Path path = m_object->elementPath();
if (path.isEmpty())
return NULL;
CGPathRef transformedPath = [self convertPathToScreenSpace:path];
return [self bezierPathFromPath:transformedPath];
}
- (NSValue *)position
{
IntRect rect = snappedIntRect(m_object->elementRect());
// The Cocoa accessibility API wants the lower-left corner.
FloatPoint floatPoint = FloatPoint(rect.x(), rect.maxY());
CGPoint cgPoint = [self convertPointToScreenSpace:floatPoint];
return [NSValue valueWithPoint:NSMakePoint(cgPoint.x, cgPoint.y)];
}
typedef HashMap<int, NSString*> AccessibilityRoleMap;
static const AccessibilityRoleMap& createAccessibilityRoleMap()
{
struct RoleEntry {
AccessibilityRole value;
NSString* string;
};
static const RoleEntry roles[] = {
{ AccessibilityRole::Unknown, NSAccessibilityUnknownRole },
{ AccessibilityRole::Button, NSAccessibilityButtonRole },
{ AccessibilityRole::RadioButton, NSAccessibilityRadioButtonRole },
{ AccessibilityRole::CheckBox, NSAccessibilityCheckBoxRole },
{ AccessibilityRole::Slider, NSAccessibilitySliderRole },
{ AccessibilityRole::TabGroup, NSAccessibilityTabGroupRole },
{ AccessibilityRole::TextField, NSAccessibilityTextFieldRole },
{ AccessibilityRole::StaticText, NSAccessibilityStaticTextRole },
{ AccessibilityRole::TextArea, NSAccessibilityTextAreaRole },
{ AccessibilityRole::ScrollArea, NSAccessibilityScrollAreaRole },
{ AccessibilityRole::PopUpButton, NSAccessibilityPopUpButtonRole },
{ AccessibilityRole::MenuButton, NSAccessibilityMenuButtonRole },
{ AccessibilityRole::Table, NSAccessibilityTableRole },
{ AccessibilityRole::Application, NSAccessibilityApplicationRole },
{ AccessibilityRole::Group, NSAccessibilityGroupRole },
{ AccessibilityRole::TextGroup, NSAccessibilityGroupRole },
{ AccessibilityRole::RadioGroup, NSAccessibilityRadioGroupRole },
{ AccessibilityRole::List, NSAccessibilityListRole },
{ AccessibilityRole::Directory, NSAccessibilityListRole },
{ AccessibilityRole::ScrollBar, NSAccessibilityScrollBarRole },
{ AccessibilityRole::ValueIndicator, NSAccessibilityValueIndicatorRole },
{ AccessibilityRole::Image, NSAccessibilityImageRole },
{ AccessibilityRole::MenuBar, NSAccessibilityMenuBarRole },
{ AccessibilityRole::Menu, NSAccessibilityMenuRole },
{ AccessibilityRole::MenuItem, NSAccessibilityMenuItemRole },
{ AccessibilityRole::MenuItemCheckbox, NSAccessibilityMenuItemRole },
{ AccessibilityRole::MenuItemRadio, NSAccessibilityMenuItemRole },
{ AccessibilityRole::Column, NSAccessibilityColumnRole },
{ AccessibilityRole::Row, NSAccessibilityRowRole },
{ AccessibilityRole::Toolbar, NSAccessibilityToolbarRole },
{ AccessibilityRole::BusyIndicator, NSAccessibilityBusyIndicatorRole },
{ AccessibilityRole::ProgressIndicator, NSAccessibilityProgressIndicatorRole },
{ AccessibilityRole::Window, NSAccessibilityWindowRole },
{ AccessibilityRole::Drawer, NSAccessibilityDrawerRole },
{ AccessibilityRole::SystemWide, NSAccessibilitySystemWideRole },
{ AccessibilityRole::Outline, NSAccessibilityOutlineRole },
{ AccessibilityRole::Incrementor, NSAccessibilityIncrementorRole },
{ AccessibilityRole::Browser, NSAccessibilityBrowserRole },
{ AccessibilityRole::ComboBox, NSAccessibilityComboBoxRole },
{ AccessibilityRole::SplitGroup, NSAccessibilitySplitGroupRole },
{ AccessibilityRole::Splitter, NSAccessibilitySplitterRole },
{ AccessibilityRole::ColorWell, NSAccessibilityColorWellRole },
{ AccessibilityRole::GrowArea, NSAccessibilityGrowAreaRole },
{ AccessibilityRole::Sheet, NSAccessibilitySheetRole },
{ AccessibilityRole::HelpTag, NSAccessibilityHelpTagRole },
{ AccessibilityRole::Matte, NSAccessibilityMatteRole },
{ AccessibilityRole::Ruler, NSAccessibilityRulerRole },
{ AccessibilityRole::RulerMarker, NSAccessibilityRulerMarkerRole },
{ AccessibilityRole::Link, NSAccessibilityLinkRole },
{ AccessibilityRole::DisclosureTriangle, NSAccessibilityDisclosureTriangleRole },
{ AccessibilityRole::Grid, NSAccessibilityTableRole },
{ AccessibilityRole::TreeGrid, NSAccessibilityTableRole },
{ AccessibilityRole::WebCoreLink, NSAccessibilityLinkRole },
{ AccessibilityRole::ImageMapLink, NSAccessibilityLinkRole },
{ AccessibilityRole::ImageMap, @"AXImageMap" },
{ AccessibilityRole::ListMarker, @"AXListMarker" },
{ AccessibilityRole::WebArea, @"AXWebArea" },
{ AccessibilityRole::Heading, @"AXHeading" },
{ AccessibilityRole::ListBox, NSAccessibilityListRole },
{ AccessibilityRole::ListBoxOption, NSAccessibilityStaticTextRole },
{ AccessibilityRole::Cell, NSAccessibilityCellRole },
{ AccessibilityRole::GridCell, NSAccessibilityCellRole },
{ AccessibilityRole::TableHeaderContainer, NSAccessibilityGroupRole },
{ AccessibilityRole::ColumnHeader, NSAccessibilityCellRole },
{ AccessibilityRole::RowHeader, NSAccessibilityCellRole },
{ AccessibilityRole::Definition, NSAccessibilityGroupRole },
{ AccessibilityRole::DescriptionListDetail, NSAccessibilityGroupRole },
{ AccessibilityRole::DescriptionListTerm, NSAccessibilityGroupRole },
{ AccessibilityRole::Term, NSAccessibilityGroupRole },
{ AccessibilityRole::DescriptionList, NSAccessibilityListRole },
{ AccessibilityRole::SliderThumb, NSAccessibilityValueIndicatorRole },
{ AccessibilityRole::WebApplication, NSAccessibilityGroupRole },
{ AccessibilityRole::LandmarkBanner, NSAccessibilityGroupRole },
{ AccessibilityRole::LandmarkComplementary, NSAccessibilityGroupRole },
{ AccessibilityRole::LandmarkDocRegion, NSAccessibilityGroupRole },
{ AccessibilityRole::LandmarkContentInfo, NSAccessibilityGroupRole },
{ AccessibilityRole::LandmarkMain, NSAccessibilityGroupRole },
{ AccessibilityRole::LandmarkNavigation, NSAccessibilityGroupRole },
{ AccessibilityRole::LandmarkRegion, NSAccessibilityGroupRole },
{ AccessibilityRole::LandmarkSearch, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationAlert, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationAlertDialog, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationDialog, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationGroup, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationTextGroup, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationLog, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationMarquee, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationStatus, NSAccessibilityGroupRole },
{ AccessibilityRole::ApplicationTimer, NSAccessibilityGroupRole },
{ AccessibilityRole::Document, NSAccessibilityGroupRole },
{ AccessibilityRole::DocumentArticle, NSAccessibilityGroupRole },
{ AccessibilityRole::DocumentMath, NSAccessibilityGroupRole },
{ AccessibilityRole::DocumentNote, NSAccessibilityGroupRole },
{ AccessibilityRole::UserInterfaceTooltip, NSAccessibilityGroupRole },
{ AccessibilityRole::Tab, NSAccessibilityRadioButtonRole },
{ AccessibilityRole::TabList, NSAccessibilityTabGroupRole },
{ AccessibilityRole::TabPanel, NSAccessibilityGroupRole },
{ AccessibilityRole::Tree, NSAccessibilityOutlineRole },
{ AccessibilityRole::TreeItem, NSAccessibilityRowRole },
{ AccessibilityRole::ListItem, NSAccessibilityGroupRole },
{ AccessibilityRole::Paragraph, NSAccessibilityGroupRole },
{ AccessibilityRole::Label, NSAccessibilityGroupRole },
{ AccessibilityRole::Div, NSAccessibilityGroupRole },
{ AccessibilityRole::Form, NSAccessibilityGroupRole },
{ AccessibilityRole::SpinButton, NSAccessibilityIncrementorRole },
{ AccessibilityRole::Footer, NSAccessibilityGroupRole },
{ AccessibilityRole::ToggleButton, NSAccessibilityCheckBoxRole },
{ AccessibilityRole::Canvas, NSAccessibilityImageRole },
{ AccessibilityRole::SVGRoot, NSAccessibilityGroupRole },
{ AccessibilityRole::Legend, NSAccessibilityGroupRole },
{ AccessibilityRole::MathElement, NSAccessibilityGroupRole },
{ AccessibilityRole::Audio, NSAccessibilityGroupRole },
{ AccessibilityRole::Video, NSAccessibilityGroupRole },
{ AccessibilityRole::HorizontalRule, NSAccessibilitySplitterRole },
{ AccessibilityRole::Blockquote, NSAccessibilityGroupRole },
{ AccessibilityRole::Switch, NSAccessibilityCheckBoxRole },
{ AccessibilityRole::SearchField, NSAccessibilityTextFieldRole },
{ AccessibilityRole::Pre, NSAccessibilityGroupRole },
{ AccessibilityRole::RubyBase, NSAccessibilityGroupRole },
{ AccessibilityRole::RubyBlock, NSAccessibilityGroupRole },
{ AccessibilityRole::RubyInline, NSAccessibilityGroupRole },
{ AccessibilityRole::RubyRun, NSAccessibilityGroupRole },
{ AccessibilityRole::RubyText, NSAccessibilityGroupRole },
{ AccessibilityRole::Details, NSAccessibilityGroupRole },
{ AccessibilityRole::Summary, NSAccessibilityButtonRole },
{ AccessibilityRole::SVGTextPath, NSAccessibilityGroupRole },
{ AccessibilityRole::SVGText, NSAccessibilityGroupRole },
{ AccessibilityRole::SVGTSpan, NSAccessibilityGroupRole },
{ AccessibilityRole::Inline, NSAccessibilityGroupRole },
{ AccessibilityRole::Mark, NSAccessibilityGroupRole },
{ AccessibilityRole::Time, NSAccessibilityGroupRole },
{ AccessibilityRole::Feed, NSAccessibilityGroupRole },
{ AccessibilityRole::Figure, NSAccessibilityGroupRole },
{ AccessibilityRole::Footnote, NSAccessibilityGroupRole },
{ AccessibilityRole::GraphicsDocument, NSAccessibilityGroupRole },
{ AccessibilityRole::GraphicsObject, NSAccessibilityGroupRole },
{ AccessibilityRole::GraphicsSymbol, NSAccessibilityImageRole },
};
AccessibilityRoleMap& roleMap = *new AccessibilityRoleMap;
const unsigned numRoles = sizeof(roles) / sizeof(roles[0]);
for (unsigned i = 0; i < numRoles; ++i)
roleMap.set(static_cast<int>(roles[i].value), roles[i].string);
return roleMap;
}
static NSString* roleValueToNSString(AccessibilityRole value)
{
ASSERT(static_cast<int>(value));
static const AccessibilityRoleMap& roleMap = createAccessibilityRoleMap();
return roleMap.get(static_cast<int>(value));
}
- (NSString*)role
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (m_object->isAttachment())
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleAttribute];
#pragma clang diagnostic pop
AccessibilityRole role = m_object->roleValue();
if (role == AccessibilityRole::Label && is<AccessibilityLabel>(*m_object) && downcast<AccessibilityLabel>(*m_object).containsOnlyStaticText())
role = AccessibilityRole::StaticText;
// The mfenced element creates anonymous RenderMathMLOperators with no RenderText
// descendants. Because these anonymous renderers are the only accessible objects
// containing the operator, assign AccessibilityRole::StaticText.
if (m_object->isAnonymousMathOperator())
role = AccessibilityRole::StaticText;
if (role == AccessibilityRole::Canvas && m_object->canvasHasFallbackContent())
role = AccessibilityRole::Group;
NSString* string = roleValueToNSString(role);
if (string != nil)
return string;
return NSAccessibilityUnknownRole;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (NSString*)subrole
{
if (m_object->isPasswordField())
return NSAccessibilitySecureTextFieldSubrole;
if (m_object->isSearchField())
return NSAccessibilitySearchFieldSubrole;
if (m_object->isAttachment()) {
NSView* attachView = [self attachmentView];
if ([[attachView accessibilityAttributeNames] containsObject:NSAccessibilitySubroleAttribute])
return [attachView accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
}
AccessibilityRole role = m_object->roleValue();
if (role == AccessibilityRole::HorizontalRule)
return NSAccessibilityContentSeparatorSubrole;
if (role == AccessibilityRole::ToggleButton)
return NSAccessibilityToggleSubrole;
if (is<AccessibilitySpinButtonPart>(*m_object)) {
if (downcast<AccessibilitySpinButtonPart>(*m_object).isIncrementor())
return NSAccessibilityIncrementArrowSubrole;
return NSAccessibilityDecrementArrowSubrole;
}
if (m_object->isFileUploadButton())
return @"AXFileUploadButton";
if (m_object->isTreeItem())
return NSAccessibilityOutlineRowSubrole;
if (m_object->isFieldset())
return @"AXFieldset";
if (is<AccessibilityList>(*m_object)) {
auto& listObject = downcast<AccessibilityList>(*m_object);
if (listObject.isUnorderedList() || listObject.isOrderedList())
return NSAccessibilityContentListSubrole;
if (listObject.isDescriptionList()) {
return NSAccessibilityDescriptionListSubrole;
}
}
// ARIA content subroles.
switch (role) {
case AccessibilityRole::LandmarkBanner:
return @"AXLandmarkBanner";
case AccessibilityRole::LandmarkComplementary:
return @"AXLandmarkComplementary";
// Footer roles should appear as content info types.
case AccessibilityRole::Footer:
case AccessibilityRole::LandmarkContentInfo:
return @"AXLandmarkContentInfo";
case AccessibilityRole::LandmarkMain:
return @"AXLandmarkMain";
case AccessibilityRole::LandmarkNavigation:
return @"AXLandmarkNavigation";
case AccessibilityRole::LandmarkDocRegion:
case AccessibilityRole::LandmarkRegion:
return @"AXLandmarkRegion";
case AccessibilityRole::LandmarkSearch:
return @"AXLandmarkSearch";
case AccessibilityRole::ApplicationAlert:
return @"AXApplicationAlert";
case AccessibilityRole::ApplicationAlertDialog:
return @"AXApplicationAlertDialog";
case AccessibilityRole::ApplicationDialog:
return @"AXApplicationDialog";
case AccessibilityRole::ApplicationGroup:
case AccessibilityRole::ApplicationTextGroup:
case AccessibilityRole::Feed:
case AccessibilityRole::Footnote:
return @"AXApplicationGroup";
case AccessibilityRole::ApplicationLog:
return @"AXApplicationLog";
case AccessibilityRole::ApplicationMarquee:
return @"AXApplicationMarquee";
case AccessibilityRole::ApplicationStatus:
return @"AXApplicationStatus";
case AccessibilityRole::ApplicationTimer:
return @"AXApplicationTimer";
case AccessibilityRole::Document:
case AccessibilityRole::GraphicsDocument:
return @"AXDocument";
case AccessibilityRole::DocumentArticle:
return @"AXDocumentArticle";
case AccessibilityRole::DocumentMath:
return @"AXDocumentMath";
case AccessibilityRole::DocumentNote:
return @"AXDocumentNote";
case AccessibilityRole::UserInterfaceTooltip:
return @"AXUserInterfaceTooltip";
case AccessibilityRole::TabPanel:
return @"AXTabPanel";
case AccessibilityRole::Definition:
return @"AXDefinition";
case AccessibilityRole::DescriptionListTerm:
case AccessibilityRole::Term:
return @"AXTerm";
case AccessibilityRole::DescriptionListDetail:
return @"AXDescription";
case AccessibilityRole::WebApplication:
return @"AXWebApplication";
// Default doesn't return anything, so roles defined below can be chosen.
default:
break;
}
if (role == AccessibilityRole::MathElement) {
if (m_object->isMathFraction())
return @"AXMathFraction";
if (m_object->isMathFenced())
return @"AXMathFenced";
if (m_object->isMathSubscriptSuperscript())
return @"AXMathSubscriptSuperscript";
if (m_object->isMathRow())
return @"AXMathRow";
if (m_object->isMathUnderOver())
return @"AXMathUnderOver";
if (m_object->isMathSquareRoot())
return @"AXMathSquareRoot";
if (m_object->isMathRoot())
return @"AXMathRoot";
if (m_object->isMathText())
return @"AXMathText";
if (m_object->isMathNumber())
return @"AXMathNumber";
if (m_object->isMathIdentifier())
return @"AXMathIdentifier";
if (m_object->isMathTable())
return @"AXMathTable";
if (m_object->isMathTableRow())
return @"AXMathTableRow";
if (m_object->isMathTableCell())
return @"AXMathTableCell";
if (m_object->isMathFenceOperator())
return @"AXMathFenceOperator";
if (m_object->isMathSeparatorOperator())
return @"AXMathSeparatorOperator";
if (m_object->isMathOperator())
return @"AXMathOperator";
if (m_object->isMathMultiscript())
return @"AXMathMultiscript";
}
if (role == AccessibilityRole::Video)
return @"AXVideo";
if (role == AccessibilityRole::Audio)
return @"AXAudio";
if (role == AccessibilityRole::Details)
return @"AXDetails";
if (role == AccessibilityRole::Summary)
return @"AXSummary";
if (role == AccessibilityRole::Time)
return @"AXTimeGroup";
if (m_object->isMediaTimeline())
return NSAccessibilityTimelineSubrole;
if (m_object->isSwitch())
return NSAccessibilitySwitchSubrole;
if (m_object->isStyleFormatGroup()) {
if (Node* node = m_object->node()) {
if (node->hasTagName(kbdTag))
return @"AXKeyboardInputStyleGroup";
if (node->hasTagName(codeTag))
return @"AXCodeStyleGroup";
if (node->hasTagName(preTag))
return @"AXPreformattedStyleGroup";
if (node->hasTagName(sampTag))
return @"AXSampleStyleGroup";
if (node->hasTagName(varTag))
return @"AXVariableStyleGroup";
if (node->hasTagName(citeTag))
return @"AXCiteStyleGroup";
if (node->hasTagName(insTag))
return @"AXInsertStyleGroup";
if (node->hasTagName(delTag))
return @"AXDeleteStyleGroup";
if (node->hasTagName(supTag))
return @"AXSuperscriptStyleGroup";
if (node->hasTagName(subTag))
return @"AXSubscriptStyleGroup";
}
}
// Ruby subroles
switch (role) {
case AccessibilityRole::RubyBase:
return NSAccessibilityRubyBaseSubrole;
case AccessibilityRole::RubyBlock:
return NSAccessibilityRubyBlockSubrole;
case AccessibilityRole::RubyInline:
return NSAccessibilityRubyInlineSubrole;
case AccessibilityRole::RubyRun:
return NSAccessibilityRubyRunSubrole;
case AccessibilityRole::RubyText:
return NSAccessibilityRubyTextSubrole;
default:
break;
}
return nil;
}
#pragma clang diagnostic pop
- (NSString*)roleDescription
{
if (!m_object)
return nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// attachments have the AXImage role, but a different subrole
if (m_object->isAttachment())
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleDescriptionAttribute];
#pragma clang diagnostic pop
const AtomicString& overrideRoleDescription = m_object->roleDescription();
if (!overrideRoleDescription.isNull() && !overrideRoleDescription.isEmpty())
return overrideRoleDescription;
NSString* axRole = [self role];
if ([axRole isEqualToString:NSAccessibilityGroupRole]) {
if (m_object->isOutput())
return AXOutputText();
NSString *ariaLandmarkRoleDescription = [self ariaLandmarkRoleDescription];
if (ariaLandmarkRoleDescription)
return ariaLandmarkRoleDescription;
switch (m_object->roleValue()) {
case AccessibilityRole::Audio:
return localizedMediaControlElementString("AudioElement");
case AccessibilityRole::Definition:
return AXDefinitionText();
case AccessibilityRole::DescriptionListTerm:
case AccessibilityRole::Term:
return AXDescriptionListTermText();
case AccessibilityRole::DescriptionListDetail:
return AXDescriptionListDetailText();
case AccessibilityRole::Details:
return AXDetailsText();
case AccessibilityRole::Feed:
return AXFeedText();
case AccessibilityRole::Figure:
return AXFigureText();
case AccessibilityRole::Footer:
return AXFooterRoleDescriptionText();
case AccessibilityRole::Mark:
return AXMarkText();
case AccessibilityRole::Video:
return localizedMediaControlElementString("VideoElement");
case AccessibilityRole::GraphicsDocument:
return AXARIAContentGroupText(@"ARIADocument");
default:
return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, [self subrole]);
}
}
if ([axRole isEqualToString:@"AXWebArea"])
return AXWebAreaText();
if ([axRole isEqualToString:@"AXLink"])
return AXLinkText();
if ([axRole isEqualToString:@"AXListMarker"])
return AXListMarkerText();
if ([axRole isEqualToString:@"AXImageMap"])
return AXImageMapText();
if ([axRole isEqualToString:@"AXHeading"])
return AXHeadingText();
if ([axRole isEqualToString:NSAccessibilityTextFieldRole]) {
auto* node = m_object->node();
if (is<HTMLInputElement>(node)) {
auto& input = downcast<HTMLInputElement>(*node);
if (input.isEmailField())
return AXEmailFieldText();
if (input.isTelephoneField())
return AXTelephoneFieldText();
if (input.isURLField())
return AXURLFieldText();
if (input.isNumberField())
return AXNumberFieldText();
// These input types are not enabled on mac yet, we check the type attribute for now.
auto& type = input.attributeWithoutSynchronization(typeAttr);
if (equalLettersIgnoringASCIICase(type, "date"))
return AXDateFieldText();
if (equalLettersIgnoringASCIICase(type, "time"))
return AXTimeFieldText();
if (equalLettersIgnoringASCIICase(type, "week"))
return AXWeekFieldText();
if (equalLettersIgnoringASCIICase(type, "month"))
return AXMonthFieldText();
if (equalLettersIgnoringASCIICase(type, "datetime-local"))
return AXDateTimeFieldText();
}
}
if (m_object->isFileUploadButton())
return AXFileUploadButtonText();
// Only returning for DL (not UL or OL) because description changed with HTML5 from 'definition list' to
// superset 'description list' and does not return the same values in AX API on some OS versions.
if (is<AccessibilityList>(*m_object)) {
if (downcast<AccessibilityList>(*m_object).isDescriptionList())
return AXDescriptionListText();
}
if (m_object->roleValue() == AccessibilityRole::HorizontalRule)
return AXHorizontalRuleDescriptionText();
// AppKit also returns AXTab for the role description for a tab item.
if (m_object->isTabItem())
return NSAccessibilityRoleDescription(@"AXTab", nil);
if (m_object->isSummary())
return AXSummaryText();
// We should try the system default role description for all other roles.
// If we get the same string back, then as a last resort, return unknown.
NSString* defaultRoleDescription = NSAccessibilityRoleDescription(axRole, [self 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, [self subrole]]])
defaultRoleDescription = NSAccessibilityRoleDescription(axRole, nil);
if (![defaultRoleDescription isEqualToString:axRole])
return defaultRoleDescription;
return NSAccessibilityRoleDescription(NSAccessibilityUnknownRole, nil);
}
- (NSString *)computedRoleString
{
if (!m_object)
return nil;
return m_object->computedRoleString();
}
- (id)scrollViewParent
{
if (!is<AccessibilityScrollView>(m_object))
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 (m_object->parentObject())
return nil;
ScrollView* scroll = downcast<AccessibilityScrollView>(*m_object).scrollView();
if (!scroll)
return nil;
if (scroll->platformWidget())
return NSAccessibilityUnignoredAncestor(scroll->platformWidget());
return [self remoteAccessibilityParentObject];
}
- (NSString *)valueDescriptionForMeter
{
if (!m_object)
return nil;
String valueDescription = m_object->valueDescription();
#if ENABLE(METER_ELEMENT)
if (!is<AccessibilityProgressIndicator>(m_object))
return valueDescription;
auto &meter = downcast<AccessibilityProgressIndicator>(*m_object);
String gaugeRegionValue = meter.gaugeRegionValueDescription();
if (!gaugeRegionValue.isEmpty()) {
StringBuilder builder;
builder.append(valueDescription);
if (builder.length())
builder.appendLiteral(", ");
builder.append(gaugeRegionValue);
return builder.toString();
}
#endif
return valueDescription;
}
// 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
- (id)accessibilityAttributeValue:(NSString*)attributeName
{
if (![self updateObjectBackingStore])
return nil;
if (m_object->isDetachedFromParent())
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 (m_object->isTreeItem()) {
AccessibilityObject* parent = m_object->parentObjectUnignored();
while (parent) {
if (parent->isTree())
return parent->wrapper();
parent = parent->parentObjectUnignored();
}
}
AccessibilityObject* parent = m_object->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 (m_object->isWebArea() && parent->isAttachment())
return [parent->wrapper() attachmentView];
return parent->wrapper();
}
if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) {
if (m_object->children().isEmpty()) {
NSArray* children = [self renderWidgetChildren];
if (children != nil)
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 (m_object->isTree())
return [self accessibilityAttributeValue:NSAccessibilityRowsAttribute];
// A tree item should only expose its content as its children (not its rows)
if (m_object->isTreeItem()) {
AccessibilityObject::AccessibilityChildrenVector contentCopy;
m_object->ariaTreeItemContent(contentCopy);
return convertToNSArray(contentCopy);
}
return convertToNSArray(m_object->children());
}
if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) {
if (m_object->canHaveSelectedChildren()) {
AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy;
m_object->selectedChildren(selectedChildrenCopy);
return convertToNSArray(selectedChildrenCopy);
}
return nil;
}
if ([attributeName isEqualToString: NSAccessibilityVisibleChildrenAttribute]) {
if (m_object->isListBox()) {
AccessibilityObject::AccessibilityChildrenVector visibleChildrenCopy;
m_object->visibleChildren(visibleChildrenCopy);
return convertToNSArray(visibleChildrenCopy);
}
else if (m_object->isList())
return [self accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
return nil;
}
if (m_object->isWebArea()) {
if ([attributeName isEqualToString:@"AXLinkUIElements"]) {
AccessibilityObject::AccessibilityChildrenVector links;
downcast<AccessibilityRenderObject>(*m_object).getDocumentLinks(links);
return convertToNSArray(links);
}
if ([attributeName isEqualToString:@"AXLoaded"])
return [NSNumber numberWithBool:m_object->isLoaded()];
if ([attributeName isEqualToString:@"AXLayoutCount"])
return [NSNumber numberWithInt:m_object->layoutCount()];
if ([attributeName isEqualToString:NSAccessibilityLoadingProgressAttribute])
return [NSNumber numberWithDouble:m_object->estimatedLoadingProgress()];
if ([attributeName isEqualToString:NSAccessibilityPreventKeyboardDOMEventDispatchAttribute])
return [NSNumber numberWithBool:m_object->preventKeyboardDOMEventDispatch()];
if ([attributeName isEqualToString:NSAccessibilityCaretBrowsingEnabledAttribute])
return [NSNumber numberWithBool:m_object->caretBrowsingEnabled()];
if ([attributeName isEqualToString:NSAccessibilityWebSessionIDAttribute]) {
if (Document* doc = m_object->topDocument())
return [NSNumber numberWithUnsignedLongLong:doc->sessionID().sessionID()];
}
}
if (m_object->isTextControl()) {
if ([attributeName isEqualToString: NSAccessibilityNumberOfCharactersAttribute]) {
int length = m_object->textLength();
if (length < 0)
return nil;
return [NSNumber numberWithUnsignedInt:length];
}
if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) {
String selectedText = m_object->selectedText();
if (selectedText.isNull())
return nil;
return (NSString*)selectedText;
}
if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) {
PlainTextRange textRange = m_object->selectedTextRange();
if (textRange.isNull())
return [NSValue valueWithRange:NSMakeRange(0, 0)];
return [NSValue valueWithRange:NSMakeRange(textRange.start, textRange.length)];
}
// TODO: Get actual visible range. <rdar://problem/4712101>
if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute])
return m_object->isPasswordField() ? nil : [NSValue valueWithRange: NSMakeRange(0, m_object->textLength())];
if ([attributeName isEqualToString: NSAccessibilityInsertionPointLineNumberAttribute]) {
// if selectionEnd > 0, then there is selected text and this question should not be answered
if (m_object->isPasswordField() || m_object->selectionEnd() > 0)
return nil;
AccessibilityObject* focusedObject = m_object->focusedUIElement();
if (focusedObject != m_object)
return nil;
VisiblePosition focusedPosition = focusedObject->visiblePositionForIndex(focusedObject->selectionStart(), true);
int lineNumber = m_object->lineForPosition(focusedPosition);
if (lineNumber < 0)
return nil;
return [NSNumber numberWithInt:lineNumber];
}
}
if ([attributeName isEqualToString: NSAccessibilityURLAttribute]) {
URL url = m_object->url();
if (url.isNull())
return nil;
return (NSURL*)url;
}
// Only native spin buttons have increment and decrement buttons.
if (is<AccessibilitySpinButton>(*m_object)) {
if ([attributeName isEqualToString:NSAccessibilityIncrementButtonAttribute])
return downcast<AccessibilitySpinButton>(*m_object).incrementButton()->wrapper();
if ([attributeName isEqualToString:NSAccessibilityDecrementButtonAttribute])
return downcast<AccessibilitySpinButton>(*m_object).decrementButton()->wrapper();
}
if ([attributeName isEqualToString: @"AXVisited"])
return [NSNumber numberWithBool: m_object->isVisited()];
if ([attributeName isEqualToString: NSAccessibilityTitleAttribute]) {
if (m_object->isAttachment()) {
if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityTitleAttribute])
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityTitleAttribute];
}
// Meter elements should communicate their content via AXValueDescription.
if (m_object->isMeter())
return [NSString string];
// Summary element should use its text node as AXTitle.
if (m_object->isSummary())
return m_object->textUnderElement();
return [self baseAccessibilityTitle];
}
if ([attributeName isEqualToString: NSAccessibilityDescriptionAttribute]) {
if (m_object->isAttachment()) {
if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityDescriptionAttribute])
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityDescriptionAttribute];
}
return [self baseAccessibilityDescription];
}
if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) {
if (m_object->isAttachment()) {
if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityValueAttribute])
return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityValueAttribute];
}
if (m_object->supportsRangeValue())
return [NSNumber numberWithFloat:m_object->valueForRange()];
if (m_object->roleValue() == AccessibilityRole::SliderThumb)
return [NSNumber numberWithFloat:m_object->parentObject()->valueForRange()];
if (m_object->isHeading())
return [NSNumber numberWithInt:m_object->headingLevel()];
if (m_object->isCheckboxOrRadio() || m_object->isMenuItem() || m_object->isSwitch() || m_object->isToggleButton()) {
switch (m_object->checkboxOrRadioValue()) {
case AccessibilityButtonState::Off:
return [NSNumber numberWithInt:0];
case AccessibilityButtonState::On:
return [NSNumber numberWithInt:1];
case AccessibilityButtonState::Mixed:
return [NSNumber numberWithInt:2];
}
}
// radio groups return the selected radio button as the AXValue
if (m_object->isRadioGroup()) {
AccessibilityObject* radioButton = m_object->selectedRadioButton();
if (!radioButton)
return nil;
return radioButton->wrapper();
}
if (m_object->isTabList()) {
AccessibilityObject* tabItem = m_object->selectedTabItem();
if (!tabItem)
return nil;
return tabItem->wrapper();
}
if (m_object->isTabItem())
return [NSNumber numberWithInt:m_object->isSelected()];
if (m_object->isColorWell()) {
int r, g, b;
m_object->colorValue(r, g, b);
return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1", r / 255., g / 255., b / 255.];
}
return m_object->stringValue();
}
if ([attributeName isEqualToString:(NSString *)kAXMenuItemMarkCharAttribute]) {
const unichar ch = 0x2713; // ✓ used on Mac for selected menu items.
return (m_object->isChecked()) ? [NSString stringWithCharacters:&ch length:1] : nil;
}
if ([attributeName isEqualToString: NSAccessibilityMinValueAttribute]) {
// Indeterminate progress indicator should return 0.
if (m_object->ariaRoleAttribute() == AccessibilityRole::ProgressIndicator && !m_object->hasAttribute(aria_valuenowAttr))
return @0;
return [NSNumber numberWithFloat:m_object->minValueForRange()];
}
if ([attributeName isEqualToString: NSAccessibilityMaxValueAttribute]) {
// Indeterminate progress indicator should return 0.
if (m_object->ariaRoleAttribute() == AccessibilityRole::ProgressIndicator && !m_object->hasAttribute(aria_valuenowAttr))
return @0;
return [NSNumber numberWithFloat:m_object->maxValueForRange()];
}
if ([attributeName isEqualToString: NSAccessibilityHelpAttribute])
return [self baseAccessibilityHelpText];
if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute])
return [NSNumber numberWithBool: m_object->isFocused()];
if ([attributeName isEqualToString: NSAccessibilityEnabledAttribute])
return [NSNumber numberWithBool: m_object->isEnabled()];
if ([attributeName isEqualToString: NSAccessibilitySizeAttribute]) {
IntSize s = snappedIntRect(m_object->elementRect()).size();
return [NSValue valueWithSize: NSMakeSize(s.width(), s.height())];
}
if ([attributeName isEqualToString: NSAccessibilityPositionAttribute])
return [self position];
if ([attributeName isEqualToString:NSAccessibilityPathAttribute])
return [self path];
if ([attributeName isEqualToString: NSAccessibilityWindowAttribute] ||
[attributeName isEqualToString: NSAccessibilityTopLevelUIElementAttribute]) {
id remoteParent = [self remoteAccessibilityParentObject];
if (remoteParent)
return [remoteParent accessibilityAttributeValue:attributeName];
FrameView* fv = m_object->documentFrameView();
if (fv)
return [fv->platformWidget() window];
return nil;
}
if ([attributeName isEqualToString:NSAccessibilityAccessKeyAttribute]) {
AtomicString accessKey = m_object->accessKey();
if (accessKey.isNull())
return nil;
return accessKey;
}
if ([attributeName isEqualToString:NSAccessibilityTabsAttribute]) {
if (m_object->isTabList()) {
AccessibilityObject::AccessibilityChildrenVector tabsChildren;
m_object->tabChildren(tabsChildren);
return convertToNSArray(tabsChildren);
}
}
if ([attributeName isEqualToString:NSAccessibilityContentsAttribute]) {
// The contents of a tab list are all the children except the tabs.
if (m_object->isTabList()) {
const auto& children = m_object->children();
AccessibilityObject::AccessibilityChildrenVector tabsChildren;
m_object->tabChildren(tabsChildren);
AccessibilityObject::AccessibilityChildrenVector contents;
unsigned childrenSize = children.size();
for (unsigned k = 0; k < childrenSize; ++k) {
if (tabsChildren.find(children[k]) == WTF::notFound)
contents.append(children[k]);
}
return convertToNSArray(contents);
} else if (m_object->isScrollView()) {
// A scrollView's contents are everything except the scroll bars.
AccessibilityObject::AccessibilityChildrenVector contents;
for (const auto& child : m_object->children()) {
if (!child->isScrollbar())
contents.append(child);
}
return convertToNSArray(contents);
}
}
if (is<AccessibilityTable>(*m_object) && downcast<AccessibilityTable>(*m_object).isExposableThroughAccessibility()) {
auto& table = downcast<AccessibilityTable>(*m_object);
if ([attributeName isEqualToString:NSAccessibilityRowsAttribute])
return convertToNSArray(table.rows());
if ([attributeName isEqualToString:NSAccessibilityVisibleRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector visibleRows;
table.visibleRows(visibleRows);
return convertToNSArray(visibleRows);
}
// TODO: distinguish between visible and non-visible columns
if ([attributeName isEqualToString:NSAccessibilityColumnsAttribute] ||
[attributeName isEqualToString:NSAccessibilityVisibleColumnsAttribute]) {
return convertToNSArray(table.columns());
}
if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy;
m_object->selectedChildren(selectedChildrenCopy);
return convertToNSArray(selectedChildrenCopy);
}
// HTML tables don't support these
if ([attributeName isEqualToString:NSAccessibilitySelectedColumnsAttribute] ||
[attributeName isEqualToString:NSAccessibilitySelectedCellsAttribute])
return nil;
if ([attributeName isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector columnHeaders;
table.columnHeaders(columnHeaders);
return convertToNSArray(columnHeaders);
}
if ([attributeName isEqualToString:NSAccessibilityHeaderAttribute]) {
AccessibilityObject* headerContainer = table.headerContainer();
if (headerContainer)
return headerContainer->wrapper();
return nil;
}
if ([attributeName isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector rowHeaders;
table.rowHeaders(rowHeaders);
return convertToNSArray(rowHeaders);
}
if ([attributeName isEqualToString:NSAccessibilityVisibleCellsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector cells;
table.cells(cells);
return convertToNSArray(cells);
}
if ([attributeName isEqualToString:NSAccessibilityColumnCountAttribute])
return @(table.columnCount());
if ([attributeName isEqualToString:NSAccessibilityRowCountAttribute])
return @(table.rowCount());
if ([attributeName isEqualToString:NSAccessibilityARIAColumnCountAttribute])
return @(table.axColumnCount());
if ([attributeName isEqualToString:NSAccessibilityARIARowCountAttribute])
return @(table.axRowCount());
}
if (is<AccessibilityTableColumn>(*m_object)) {
auto& column = downcast<AccessibilityTableColumn>(*m_object);
if ([attributeName isEqualToString:NSAccessibilityIndexAttribute])
return [NSNumber numberWithInt:column.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 convertToNSArray(column.children());
}
if ([attributeName isEqualToString:NSAccessibilityHeaderAttribute]) {
AccessibilityObject* header = column.headerObject();
if (!header)
return nil;
return header->wrapper();
}
}
if (is<AccessibilityTableCell>(*m_object)) {
auto& cell = downcast<AccessibilityTableCell>(*m_object);
if ([attributeName isEqualToString:NSAccessibilityRowIndexRangeAttribute]) {
std::pair<unsigned, unsigned> rowRange;
cell.rowIndexRange(rowRange);
return [NSValue valueWithRange:NSMakeRange(rowRange.first, rowRange.second)];
}
if ([attributeName isEqualToString:NSAccessibilityColumnIndexRangeAttribute]) {
std::pair<unsigned, unsigned> columnRange;
cell.columnIndexRange(columnRange);
return [NSValue valueWithRange:NSMakeRange(columnRange.first, columnRange.second)];
}
if ([attributeName isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector columnHeaders;
cell.columnHeaders(columnHeaders);
return convertToNSArray(columnHeaders);
}
if ([attributeName isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector rowHeaders;
cell.rowHeaders(rowHeaders);
return convertToNSArray(rowHeaders);
}
if ([attributeName isEqualToString:NSAccessibilityARIAColumnIndexAttribute])
return @(cell.axColumnIndex());
if ([attributeName isEqualToString:NSAccessibilityARIARowIndexAttribute])
return @(cell.axRowIndex());
}
if (m_object->isTree()) {
if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy;
m_object->selectedChildren(selectedChildrenCopy);
return convertToNSArray(selectedChildrenCopy);
}
if ([attributeName isEqualToString:NSAccessibilityRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector rowsCopy;
m_object->ariaTreeRows(rowsCopy);
return convertToNSArray(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 [NSArray array];
}
if ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) {
if (m_object->isTreeItem()) {
AccessibilityObject* parent = m_object->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 [NSNumber numberWithUnsignedInt:k];
return nil;
}
if (is<AccessibilityTableRow>(*m_object)) {
if ([attributeName isEqualToString:NSAccessibilityIndexAttribute])
return [NSNumber numberWithInt:downcast<AccessibilityTableRow>(*m_object).rowIndex()];
}
}
// The rows that are considered inside this row.
if ([attributeName isEqualToString:NSAccessibilityDisclosedRowsAttribute]) {
if (m_object->isTreeItem()) {
AccessibilityObject::AccessibilityChildrenVector rowsCopy;
m_object->ariaTreeItemDisclosedRows(rowsCopy);
return convertToNSArray(rowsCopy);
} else if (is<AccessibilityARIAGridRow>(*m_object)) {
AccessibilityObject::AccessibilityChildrenVector rowsCopy;
downcast<AccessibilityARIAGridRow>(*m_object).disclosedRows(rowsCopy);
return convertToNSArray(rowsCopy);
}
}
// The row that contains this row. It should be the same as the first parent that is a treeitem.
if ([attributeName isEqualToString:NSAccessibilityDisclosedByRowAttribute]) {
if (m_object->isTreeItem()) {
AccessibilityObject* parent = m_object->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;
} else if (is<AccessibilityARIAGridRow>(*m_object)) {
AccessibilityObject* row = downcast<AccessibilityARIAGridRow>(*m_object).disclosedByRow();
if (!row)
return nil;
return row->wrapper();
}
}
if ([attributeName isEqualToString:NSAccessibilityDisclosureLevelAttribute]) {
// Convert from 1-based level (from aria-level spec) to 0-based level (Mac)
int level = m_object->hierarchicalLevel();
if (level > 0)
level -= 1;
return [NSNumber numberWithInt:level];
}
if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute])
return [NSNumber numberWithBool:m_object->isExpanded()];
if (m_object->isList() && [attributeName isEqualToString:NSAccessibilityOrientationAttribute])
return NSAccessibilityVerticalOrientationValue;
if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"])
return [self textMarkerRangeForSelection];
if (m_object->renderer()) {
if ([attributeName isEqualToString: @"AXStartTextMarker"])
return [self textMarkerForVisiblePosition:startOfDocument(&m_object->renderer()->document())];
if ([attributeName isEqualToString: @"AXEndTextMarker"])
return [self textMarkerForVisiblePosition:endOfDocument(&m_object->renderer()->document())];
}
if ([attributeName isEqualToString:NSAccessibilityBlockQuoteLevelAttribute])
return [NSNumber numberWithUnsignedInt:m_object->blockquoteLevel()];
if ([attributeName isEqualToString:@"AXTableLevel"])
return [NSNumber numberWithInt:m_object->tableLevel()];
if ([attributeName isEqualToString: NSAccessibilityLinkedUIElementsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector linkedUIElements;
m_object->linkedUIElements(linkedUIElements);
return convertToNSArray(linkedUIElements);
}
if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute])
return [NSNumber numberWithBool:m_object->isSelected()];
if ([attributeName isEqualToString: NSAccessibilityARIACurrentAttribute])
return m_object->currentValue();
if ([attributeName isEqualToString: NSAccessibilityServesAsTitleForUIElementsAttribute] && m_object->isMenuButton()) {
AccessibilityObject* uiElement = downcast<AccessibilityRenderObject>(*m_object).menuForMenuButton();
if (uiElement)
return [NSArray arrayWithObject:uiElement->wrapper()];
}
if ([attributeName isEqualToString:NSAccessibilityTitleUIElementAttribute]) {
if (!m_object->exposesTitleUIElement())
return nil;
AccessibilityObject* obj = m_object->titleUIElement();
if (obj)
return obj->wrapper();
return nil;
}
if ([attributeName isEqualToString:NSAccessibilityValueDescriptionAttribute]) {
if (m_object->isMeter())
return [self valueDescriptionForMeter];
return m_object->valueDescription();
}
if ([attributeName isEqualToString:NSAccessibilityOrientationAttribute]) {
AccessibilityOrientation elementOrientation = m_object->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]) {
AccessibilityObject* scrollBar = m_object->scrollBar(AccessibilityOrientation::Horizontal);
if (scrollBar)
return scrollBar->wrapper();
return nil;
}
if ([attributeName isEqualToString:NSAccessibilityVerticalScrollBarAttribute]) {
AccessibilityObject* scrollBar = m_object->scrollBar(AccessibilityOrientation::Vertical);
if (scrollBar)
return scrollBar->wrapper();
return nil;
}
if ([attributeName isEqualToString:NSAccessibilitySortDirectionAttribute]) {
switch (m_object->sortDirection()) {
case AccessibilitySortDirection::Ascending:
return NSAccessibilityAscendingSortDirectionValue;
case AccessibilitySortDirection::Descending:
return NSAccessibilityDescendingSortDirectionValue;
default:
return NSAccessibilityUnknownSortDirectionValue;
}
}
if ([attributeName isEqualToString:NSAccessibilityLanguageAttribute])
return m_object->language();
if ([attributeName isEqualToString:NSAccessibilityExpandedAttribute])
return [NSNumber numberWithBool:m_object->isExpanded()];
if ([attributeName isEqualToString:NSAccessibilityRequiredAttribute])
return [NSNumber numberWithBool:m_object->isRequired()];
if ([attributeName isEqualToString:NSAccessibilityInvalidAttribute])
return m_object->invalidStatus();
if ([attributeName isEqualToString:NSAccessibilityOwnsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector ariaOwns;
m_object->ariaOwnsElements(ariaOwns);
return convertToNSArray(ariaOwns);
}
if ([attributeName isEqualToString:NSAccessibilityARIAPosInSetAttribute])
return [NSNumber numberWithInt:m_object->posInSet()];
if ([attributeName isEqualToString:NSAccessibilityARIASetSizeAttribute])
return [NSNumber numberWithInt:m_object->setSize()];
if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute])
return [NSNumber numberWithBool:m_object->isARIAGrabbed()];
if ([attributeName isEqualToString:NSAccessibilityDropEffectsAttribute]) {
Vector<String> dropEffects;
m_object->determineARIADropEffects(dropEffects);
return convertStringsToNSArray(dropEffects);
}
if ([attributeName isEqualToString:NSAccessibilityPlaceholderValueAttribute])
return m_object->placeholderValue();
if ([attributeName isEqualToString:NSAccessibilityValueAutofillAvailableAttribute])
return @(m_object->isValueAutofillAvailable());
if ([attributeName isEqualToString:NSAccessibilityValueAutofillTypeAttribute]) {
switch (m_object->valueAutofillButtonType()) {
case AutoFillButtonType::None:
return @"none";
case AutoFillButtonType::Credentials:
return @"credentials";
case AutoFillButtonType::Contacts:
return @"contacts";
case AutoFillButtonType::StrongConfirmationPassword:
return @"strong confirmation password";
case AutoFillButtonType::StrongPassword:
return @"strong password";
}
}
if ([attributeName isEqualToString:NSAccessibilityValueAutofilledAttribute])
return @(m_object->isValueAutofilled());
if ([attributeName isEqualToString:NSAccessibilityHasPopupAttribute])
return [NSNumber numberWithBool:m_object->hasPopup()];
if ([attributeName isEqualToString:NSAccessibilityDatetimeValueAttribute])
return m_object->datetimeAttributeValue();
if ([attributeName isEqualToString:NSAccessibilityInlineTextAttribute])
return @(m_object->renderer() && is<RenderInline>(m_object->renderer()));
// ARIA Live region attributes.
if ([attributeName isEqualToString:NSAccessibilityARIALiveAttribute])
return m_object->liveRegionStatus();
if ([attributeName isEqualToString:NSAccessibilityARIARelevantAttribute])
return m_object->liveRegionRelevant();
if ([attributeName isEqualToString:NSAccessibilityARIAAtomicAttribute])
return [NSNumber numberWithBool:m_object->liveRegionAtomic()];
if ([attributeName isEqualToString:NSAccessibilityElementBusyAttribute])
return [NSNumber numberWithBool:m_object->isBusy()];
// MathML Attributes.
if (m_object->isMathElement()) {
if ([attributeName isEqualToString:NSAccessibilityMathRootIndexAttribute])
return (m_object->mathRootIndexObject()) ? m_object->mathRootIndexObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathRootRadicandAttribute])
return (m_object->mathRadicandObject()) ? m_object->mathRadicandObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathFractionNumeratorAttribute])
return (m_object->mathNumeratorObject()) ? m_object->mathNumeratorObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathFractionDenominatorAttribute])
return (m_object->mathDenominatorObject()) ? m_object->mathDenominatorObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathBaseAttribute])
return (m_object->mathBaseObject()) ? m_object->mathBaseObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathSubscriptAttribute])
return (m_object->mathSubscriptObject()) ? m_object->mathSubscriptObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathSuperscriptAttribute])
return (m_object->mathSuperscriptObject()) ? m_object->mathSuperscriptObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathUnderAttribute])
return (m_object->mathUnderObject()) ? m_object->mathUnderObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathOverAttribute])
return (m_object->mathOverObject()) ? m_object->mathOverObject()->wrapper() : 0;
if ([attributeName isEqualToString:NSAccessibilityMathFencedOpenAttribute])
return m_object->mathFencedOpenString();
if ([attributeName isEqualToString:NSAccessibilityMathFencedCloseAttribute])
return m_object->mathFencedCloseString();
if ([attributeName isEqualToString:NSAccessibilityMathLineThicknessAttribute])
return [NSNumber numberWithInteger:m_object->mathLineThickness()];
if ([attributeName isEqualToString:NSAccessibilityMathPostscriptsAttribute])
return [self accessibilityMathPostscriptPairs];
if ([attributeName isEqualToString:NSAccessibilityMathPrescriptsAttribute])
return [self accessibilityMathPrescriptPairs];
}
if ([attributeName isEqualToString:NSAccessibilityExpandedTextValueAttribute])
return m_object->expandedTextValue();
if ([attributeName isEqualToString:NSAccessibilityDOMIdentifierAttribute])
return m_object->identifierAttribute();
if ([attributeName isEqualToString:NSAccessibilityDOMClassListAttribute]) {
Vector<String> classList;
m_object->classList(classList);
return convertStringsToNSArray(classList);
}
// 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:m_object->clickPoint()];
// This is used by DRT to verify CSS3 speech works.
if ([attributeName isEqualToString:@"AXDRTSpeechAttribute"])
return [self baseAccessibilitySpeechHint];
// Used by DRT to find an accessible node by its element id.
if ([attributeName isEqualToString:@"AXDRTElementIdAttribute"])
return m_object->getAttribute(idAttr);
if ([attributeName isEqualToString:@"AXAutocompleteValue"])
return m_object->autoCompleteValue();
if ([attributeName isEqualToString:@"AXHasPopUpValue"])
return m_object->hasPopupValue();
if ([attributeName isEqualToString:@"AXKeyShortcutsValue"])
return m_object->keyShortcutsValue();
if ([attributeName isEqualToString:@"AXARIAPressedIsPresent"])
return [NSNumber numberWithBool:m_object->pressedIsPresent()];
if ([attributeName isEqualToString:@"AXIsMultiline"])
return [NSNumber numberWithBool:m_object->ariaIsMultiline()];
if ([attributeName isEqualToString:@"AXReadOnlyValue"])
return m_object->readOnlyValue();
if ([attributeName isEqualToString:@"AXIsActiveDescendantOfFocusedContainer"])
return [NSNumber numberWithBool:m_object->isActiveDescendantOfFocusedContainer()];
if ([attributeName isEqualToString:@"AXDetailsElements"]) {
AccessibilityObject::AccessibilityChildrenVector details;
m_object->ariaDetailsElements(details);
return convertToNSArray(details);
}
if ([attributeName isEqualToString:@"AXErrorMessageElements"]) {
AccessibilityObject::AccessibilityChildrenVector errorMessages;
m_object->ariaErrorMessageElements(errorMessages);
return convertToNSArray(errorMessages);
}
// Multi-selectable
if ([attributeName isEqualToString:NSAccessibilityIsMultiSelectableAttribute])
return [NSNumber numberWithBool:m_object->isMultiSelectable()];
// Document attributes
if ([attributeName isEqualToString:NSAccessibilityDocumentURIAttribute]) {
if (Document* document = m_object->document())
return document->documentURI();
return nil;
}
if ([attributeName isEqualToString:NSAccessibilityDocumentEncodingAttribute]) {
if (Document* document = m_object->document())
return document->encoding();
return nil;
}
// Aria controls element
if ([attributeName isEqualToString:NSAccessibilityAriaControlsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector ariaControls;
m_object->ariaControlsElements(ariaControls);
return convertToNSArray(ariaControls);
}
if ([attributeName isEqualToString:NSAccessibilityFocusableAncestorAttribute]) {
AccessibilityObject* object = m_object->focusableAncestor();
return object ? object->wrapper() : nil;
}
if ([attributeName isEqualToString:NSAccessibilityEditableAncestorAttribute]) {
AccessibilityObject* object = m_object->editableAncestor();
return object ? object->wrapper() : nil;
}
if ([attributeName isEqualToString:NSAccessibilityHighestEditableAncestorAttribute]) {
AccessibilityObject* object = m_object->highestEditableAncestor();
return object ? object->wrapper() : nil;
}
return nil;
}
- (NSString *)accessibilityPlatformMathSubscriptKey
{
return NSAccessibilityMathSubscriptAttribute;
}
- (NSString *)accessibilityPlatformMathSuperscriptKey
{
return NSAccessibilityMathSuperscriptAttribute;
}
- (id)accessibilityFocusedUIElement
{
if (![self updateObjectBackingStore])
return nil;
RefPtr<AccessibilityObject> focusedObj = m_object->focusedUIElement();
if (!focusedObj)
return nil;
return focusedObj->wrapper();
}
- (id)accessibilityHitTest:(NSPoint)point
{
if (![self updateObjectBackingStore])
return nil;
m_object->updateChildrenIfNecessary();
RefPtr<AccessibilityObject> axObject = m_object->accessibilityHitTest(IntPoint(point));
if (axObject) {
if (axObject->isAttachment() && [axObject->wrapper() attachmentView])
return [axObject->wrapper() attachmentView];
return NSAccessibilityUnignoredAncestor(axObject->wrapper());
}
return NSAccessibilityUnignoredAncestor(self);
}
- (BOOL)accessibilityIsAttributeSettable:(NSString*)attributeName
{
if (![self updateObjectBackingStore])
return NO;
if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"])
return YES;
if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute])
return m_object->canSetFocusAttribute();
if ([attributeName isEqualToString: NSAccessibilityValueAttribute])
return m_object->canSetValueAttribute();
if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute])
return m_object->canSetSelectedAttribute();
if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute])
return m_object->canSetSelectedChildrenAttribute();
if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute]
|| [attributeName isEqualToString:NSAccessibilityExpandedAttribute])
return m_object->canSetExpandedAttribute();
if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute])
return YES;
if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute] ||
[attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute] ||
[attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute])
return m_object->canSetTextRangeAttributes();
if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute])
return YES;
if (m_object->isWebArea() && [attributeName isEqualToString:NSAccessibilityPreventKeyboardDOMEventDispatchAttribute])
return YES;
if (m_object->isWebArea() && [attributeName isEqualToString:NSAccessibilityCaretBrowsingEnabledAttribute])
return YES;
return NO;
}
// 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)accessibilityIsIgnored
{
if (![self updateObjectBackingStore])
return YES;
if (m_object->isAttachment())
return [[self attachmentView] accessibilityIsIgnored];
return m_object->accessibilityIsIgnored();
}
- (NSArray* )accessibilityParameterizedAttributeNames
{
if (![self updateObjectBackingStore])
return nil;
if (m_object->isAttachment())
return nil;
static NSArray* paramAttrs = nil;
static NSArray* textParamAttrs = nil;
static NSArray* tableParamAttrs = nil;
static NSArray* webAreaParamAttrs = nil;
if (paramAttrs == nil) {
paramAttrs = [[NSArray alloc] initWithObjects:
@"AXUIElementForTextMarker",
@"AXTextMarkerRangeForUIElement",
@"AXLineForTextMarker",
@"AXTextMarkerRangeForLine",
@"AXStringForTextMarkerRange",
@"AXTextMarkerForPosition",
@"AXBoundsForTextMarkerRange",
@"AXAttributedStringForTextMarkerRange",
@"AXAttributedStringForTextMarkerRangeWithOptions",
@"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,
nil];
}
if (textParamAttrs == nil) {
NSMutableArray* tempArray = [[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];
[tempArray release];
}
if (tableParamAttrs == nil) {
NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs];
[tempArray addObject:NSAccessibilityCellForColumnAndRowParameterizedAttribute];
tableParamAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (!webAreaParamAttrs) {
NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs];
[tempArray addObject:NSAccessibilityTextMarkerForIndexParameterizedAttribute];
[tempArray addObject:NSAccessibilityTextMarkerIsValidParameterizedAttribute];
[tempArray addObject:NSAccessibilityIndexForTextMarkerParameterizedAttribute];
webAreaParamAttrs = [[NSArray alloc] initWithArray:tempArray];
[tempArray release];
}
if (m_object->isPasswordField())
return @[ NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute ];
if (!m_object->isAccessibilityRenderObject())
return paramAttrs;
if (m_object->isTextControl())
return textParamAttrs;
if (is<AccessibilityTable>(*m_object) && downcast<AccessibilityTable>(*m_object).isExposableThroughAccessibility())
return tableParamAttrs;
if (m_object->isMenuRelated())
return nil;
if (m_object->isWebArea())
return webAreaParamAttrs;
return paramAttrs;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (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.
dispatch_async(dispatch_get_main_queue(), ^ {
[self _accessibilityPerformPressAction];
});
}
- (void)_accessibilityPerformPressAction
{
if (![self updateObjectBackingStore])
return;
if (m_object->isAttachment())
[[self attachmentView] accessibilityPerformAction:NSAccessibilityPressAction];
else
m_object->press();
}
- (void)accessibilityPerformIncrementAction
{
dispatch_async(dispatch_get_main_queue(), ^{
[self _accessibilityPerformIncrementAction];
});
}
- (void)_accessibilityPerformIncrementAction
{
if (![self updateObjectBackingStore])
return;
if (m_object->isAttachment())
[[self attachmentView] accessibilityPerformAction:NSAccessibilityIncrementAction];
else
m_object->increment();
}
- (void)accessibilityPerformDecrementAction
{
dispatch_async(dispatch_get_main_queue(), ^{
[self _accessibilityPerformDecrementAction];
});
}
- (void)_accessibilityPerformDecrementAction
{
if (![self updateObjectBackingStore])
return;
if (m_object->isAttachment())
[[self attachmentView] accessibilityPerformAction:NSAccessibilityDecrementAction];
else
m_object->decrement();
}
#pragma clang diagnostic pop
- (void)accessibilityPerformShowMenuAction
{
if (m_object && m_object->dispatchAccessibilityEventWithType(AccessibilityEventType::ContextMenu))
return;
if (m_object->roleValue() == AccessibilityRole::ComboBox)
m_object->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
{
if (!m_object)
return;
Page* page = m_object->page();
if (!page)
return;
IntRect rect = snappedIntRect(m_object->elementRect());
FrameView* frameView = m_object->documentFrameView();
// On WK2, we need to account for the scroll position.
// On WK1, this isn't necessary, it's taken care of by the attachment views.
if (frameView && !frameView->platformWidget()) {
// Find the appropriate scroll view to use to convert the contents to the window.
for (AccessibilityObject* parent = m_object->parentObject(); parent; parent = parent->parentObject()) {
if (is<AccessibilityScrollView>(*parent)) {
ScrollView* scrollView = downcast<AccessibilityScrollView>(*parent).scrollView();
rect = scrollView->contentsToRootView(rect);
break;
}
}
}
page->contextMenuController().showContextMenuAt(page->mainFrame(), rect.center());
}
- (void)accessibilityScrollToVisible
{
m_object->scrollToMakeVisible();
}
- (void)_accessibilityScrollToMakeVisibleWithSubFocus:(NSRect)rect
{
m_object->scrollToMakeVisibleWithSubFocus(IntRect(rect));
}
- (void)_accessibilityScrollToGlobalPoint:(NSPoint)point
{
m_object->scrollToGlobalPoint(IntPoint(point));
}
- (void)accessibilityPerformAction:(NSString*)action
{
if (![self updateObjectBackingStore])
return;
if ([action isEqualToString:NSAccessibilityPressAction])
[self accessibilityPerformPressAction];
// Used in layout tests, so that we don't have to wait for the async press action.
else if ([action isEqualToString:@"AXSyncPressAction"])
[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];
}
- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName
{
#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.
dispatch_async(dispatch_get_main_queue(), ^{
[self _accessibilitySetValue:value forAttribute:attributeName];
});
#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
{
if (![self updateObjectBackingStore])
return;
id textMarkerRange = nil;
NSNumber* number = nil;
NSString* string = nil;
NSRange range = {0, 0};
NSArray* array = nil;
// decode the parameter
if (AXObjectIsTextMarkerRange(value))
textMarkerRange = value;
else if ([value isKindOfClass:[NSNumber self]])
number = value;
else if ([value isKindOfClass:[NSString self]])
string = value;
else if ([value isKindOfClass:[NSValue self]])
range = [value rangeValue];
else if ([value isKindOfClass:[NSArray self]])
array = value;
// handle the command
if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) {
ASSERT(textMarkerRange);
m_object->setSelectedVisiblePositionRange([self visiblePositionRangeForTextMarkerRange:textMarkerRange]);
} else if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) {
ASSERT(number);
bool focus = [number boolValue];
// If focus is just set without making the view the first responder, then keyboard focus won't move to the right place.
if (focus && !m_object->document()->frame()->selection().isFocusedAndActive()) {
FrameView* frameView = m_object->documentFrameView();
Page* page = m_object->page();
if (page && frameView) {
ChromeClient& chromeClient = page->chrome().client();
chromeClient.focus();
// Legacy WebKit1 case.
if (frameView->platformWidget())
chromeClient.makeFirstResponder(frameView->platformWidget());
else
chromeClient.assistiveTechnologyMakeFirstResponder();
}
}
m_object->setFocused(focus);
} else if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) {
if (number && m_object->canSetNumericValue())
m_object->setValue([number floatValue]);
else if (string)
m_object->setValue(string);
} else if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute]) {
if (!number)
return;
m_object->setSelected([number boolValue]);
} else if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) {
if (!array)
return;
if (m_object->roleValue() != AccessibilityRole::ListBox)
return;
AccessibilityObject::AccessibilityChildrenVector selectedChildren;
convertToVector(array, selectedChildren);
downcast<AccessibilityListBox>(*m_object).setSelectedChildren(selectedChildren);
} else if (m_object->isTextControl()) {
if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) {
m_object->setSelectedText(string);
} else if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) {
m_object->setSelectedTextRange(PlainTextRange(range.location, range.length));
} else if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) {
m_object->makeRangeVisible(PlainTextRange(range.location, range.length));
}
} else if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute] || [attributeName isEqualToString:NSAccessibilityExpandedAttribute])
m_object->setIsExpanded([number boolValue]);
else if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) {
AccessibilityObject::AccessibilityChildrenVector selectedRows;
convertToVector(array, selectedRows);
if (m_object->isTree() || (is<AccessibilityTable>(*m_object) && downcast<AccessibilityTable>(*m_object).isExposableThroughAccessibility()))
m_object->setSelectedRows(selectedRows);
} else if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute])
m_object->setARIAGrabbed([number boolValue]);
else if (m_object->isWebArea() && [attributeName isEqualToString:NSAccessibilityPreventKeyboardDOMEventDispatchAttribute])
m_object->setPreventKeyboardDOMEventDispatch([number boolValue]);
else if (m_object->isWebArea() && [attributeName isEqualToString:NSAccessibilityCaretBrowsingEnabledAttribute])
m_object->setCaretBrowsingEnabled([number boolValue]);
}
// Used to set attributes synchronously on accessibility elements within tests.
// For use with DumpRenderTree only.
- (void)_accessibilitySetTestValue:(id)value forAttribute:(NSString*)attributeName
{
[self _accessibilitySetValue:value forAttribute:attributeName];
}
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;
}
- (NSString*)accessibilityActionDescription:(NSString*)action
{
// 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
{
PlainTextRange textRange = PlainTextRange(range.location, range.length);
RefPtr<Range> webRange = m_object->rangeForPlainTextRange(textRange);
return [self doAXAttributedStringForTextMarkerRange:[self textMarkerRangeFromRange:webRange] spellCheck:YES];
}
- (NSRange)_convertToNSRange:(Range*)range
{
NSRange result = NSMakeRange(NSNotFound, 0);
if (!range)
return result;
Document* document = m_object->document();
if (!document)
return result;
size_t location;
size_t length;
TextIterator::getLocationAndLengthFromRange(document->documentElement(), range, location, length);
result.location = location;
result.length = length;
return result;
}
- (NSInteger)_indexForTextMarker:(id)marker
{
if (!marker)
return NSNotFound;
if (AXObjectCache* cache = m_object->axObjectCache()) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:marker];
// Create a collapsed range from the CharacterOffset object.
RefPtr<Range> range = cache->rangeForUnorderedCharacterOffsets(characterOffset, characterOffset);
return [self _convertToNSRange:range.get()].location;
}
return NSNotFound;
}
- (id)_textMarkerForIndex:(NSInteger)textIndex
{
Document* document = m_object->document();
if (!document)
return nil;
RefPtr<Range> textRange = TextIterator::rangeFromLocationAndLength(document->documentElement(), textIndex, 0);
if (!textRange || !textRange->boundaryPointsValid())
return nil;
if (AXObjectCache* cache = m_object->axObjectCache()) {
CharacterOffset characterOffset = cache->startOrEndCharacterOffsetForRange(textRange, true);
return [self textMarkerForCharacterOffset:characterOffset];
}
return nil;
}
// 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:(id)textMarker
{
char description[1024];
[self visiblePositionForTextMarker:textMarker].formatForDebugger(description, sizeof(description));
return [NSString stringWithUTF8String:description];
}
- (NSString *)debugDescriptionForTextMarkerRange:(id)textMarkerRange
{
VisiblePositionRange visiblePositionRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
if (visiblePositionRange.isNull())
return @"<null>";
char description[2048];
formatForDebugger(visiblePositionRange, description, sizeof(description));
return [NSString stringWithUTF8String:description];
}
- (void)showNodeForTextMarker:(id)textMarker
{
VisiblePosition visiblePosition = [self visiblePositionForTextMarker:textMarker];
Node* node = visiblePosition.deepEquivalent().deprecatedNode();
if (!node)
return;
node->showNode();
node->showNodePathForThis();
}
- (void)showNodeTreeForTextMarker:(id)textMarker
{
VisiblePosition visiblePosition = [self visiblePositionForTextMarker:textMarker];
Node* node = visiblePosition.deepEquivalent().deprecatedNode();
if (!node)
return;
node->showTreeForThis();
}
static void formatForDebugger(const VisiblePositionRange& range, char* buffer, unsigned length)
{
StringBuilder result;
const int FormatBufferSize = 1024;
char format[FormatBufferSize];
result.appendLiteral("from ");
range.start.formatForDebugger(format, FormatBufferSize);
result.append(format);
result.appendLiteral(" to ");
range.end.formatForDebugger(format, FormatBufferSize);
result.append(format);
strlcpy(buffer, result.toString().utf8().data(), length);
}
#endif
- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter
{
id textMarker = nil;
id textMarkerRange = nil;
NSNumber* number = nil;
NSArray* array = nil;
NSDictionary* dictionary = nil;
RefPtr<AccessibilityObject> uiElement = nullptr;
NSPoint point = NSZeroPoint;
bool pointSet = false;
NSRange range = {0, 0};
bool rangeSet = false;
NSRect rect = NSZeroRect;
// basic parameter validation
if (!m_object || !attribute || !parameter)
return nil;
if (![self updateObjectBackingStore])
return nil;
AXObjectCache* cache = m_object->axObjectCache();
if (!cache)
return nil;
// 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 = parameter;
else if (AXObjectIsTextMarkerRange(parameter))
textMarkerRange = parameter;
else if ([parameter isKindOfClass:[WebAccessibilityObjectWrapper self]])
uiElement = [(WebAccessibilityObjectWrapper*)parameter accessibilityObject];
else if ([parameter isKindOfClass:[NSNumber self]])
number = parameter;
else if ([parameter isKindOfClass:[NSArray self]])
array = parameter;
else if ([parameter isKindOfClass:[NSDictionary self]])
dictionary = parameter;
else if ([parameter isKindOfClass:[NSValue self]] && !strcmp([(NSValue*)parameter objCType], @encode(NSPoint))) {
pointSet = true;
point = [(NSValue*)parameter pointValue];
} else if ([parameter isKindOfClass:[NSValue self]] && !strcmp([(NSValue*)parameter objCType], @encode(NSRange))) {
rangeSet = true;
range = [(NSValue*)parameter rangeValue];
} else if ([parameter isKindOfClass:[NSValue self]] && !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]) {
AccessibilitySelectTextCriteria criteria = accessibilitySelectTextCriteriaForCriteriaParameterizedAttribute(dictionary);
return m_object->selectText(&criteria);
}
if ([attribute isEqualToString:NSAccessibilityUIElementCountForSearchPredicateParameterizedAttribute]) {
AccessibilitySearchCriteria criteria = accessibilitySearchCriteriaForSearchPredicateParameterizedAttribute(dictionary);
AccessibilityObject::AccessibilityChildrenVector results;
m_object->findMatchingObjects(&criteria, results);
return @(results.size());
}
if ([attribute isEqualToString:NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute]) {
AccessibilitySearchCriteria criteria = accessibilitySearchCriteriaForSearchPredicateParameterizedAttribute(dictionary);
AccessibilityObject::AccessibilityChildrenVector results;
m_object->findMatchingObjects(&criteria, results);
return convertToNSArray(results);
}
if ([attribute isEqualToString:NSAccessibilityEndTextMarkerForBoundsParameterizedAttribute]) {
IntRect webCoreRect = [self screenToContents:enclosingIntRect(rect)];
CharacterOffset characterOffset = cache->characterOffsetForBounds(webCoreRect, false);
return [self textMarkerForCharacterOffset:characterOffset];
}
if ([attribute isEqualToString:NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute]) {
IntRect webCoreRect = [self screenToContents:enclosingIntRect(rect)];
CharacterOffset characterOffset = cache->characterOffsetForBounds(webCoreRect, true);
return [self textMarkerForCharacterOffset:characterOffset];
}
if ([attribute isEqualToString:NSAccessibilityLineTextMarkerRangeForTextMarkerParameterizedAttribute]) {
VisiblePosition visiblePosition = [self visiblePositionForTextMarker:textMarker];
VisiblePositionRange visiblePositionRange = m_object->lineRangeForPosition(visiblePosition);
return [self textMarkerRangeFromVisiblePositions:visiblePositionRange.start endPosition:visiblePositionRange.end];
}
if ([attribute isEqualToString:NSAccessibilityTextMarkerIsValidParameterizedAttribute]) {
VisiblePosition pos = [self visiblePositionForTextMarker:textMarker];
return [NSNumber numberWithBool:!pos.isNull()];
}
if ([attribute isEqualToString:NSAccessibilityIndexForTextMarkerParameterizedAttribute]) {
return [NSNumber numberWithInteger:[self _indexForTextMarker:textMarker]];
}
if ([attribute isEqualToString:NSAccessibilityTextMarkerForIndexParameterizedAttribute]) {
return [self _textMarkerForIndex:[number integerValue]];
}
if ([attribute isEqualToString:@"AXUIElementForTextMarker"]) {
AccessibilityObject* axObject = [self accessibilityObjectForTextMarker:textMarker];
if (!axObject)
return nil;
if (axObject->isAttachment() && [axObject->wrapper() attachmentView])
return [axObject->wrapper() attachmentView];
return axObject->wrapper();
}
if ([attribute isEqualToString:@"AXTextMarkerRangeForUIElement"]) {
RefPtr<Range> range = uiElement.get()->elementRange();
return [self textMarkerRangeFromRange:range];
}
if ([attribute isEqualToString:@"AXLineForTextMarker"]) {
VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
return [NSNumber numberWithUnsignedInt:m_object->lineForPosition(visiblePos)];
}
if ([attribute isEqualToString:@"AXTextMarkerRangeForLine"]) {
VisiblePositionRange vpRange = m_object->visiblePositionRangeForLine([number intValue]);
return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
}
if ([attribute isEqualToString:@"AXStringForTextMarkerRange"]) {
RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
return m_object->stringForRange(range);
}
if ([attribute isEqualToString:@"AXTextMarkerForPosition"]) {
IntPoint webCorePoint = IntPoint(point);
if (!pointSet)
return nil;
CharacterOffset characterOffset = cache->characterOffsetForPoint(webCorePoint, m_object);
return [self textMarkerForCharacterOffset:characterOffset];
}
if ([attribute isEqualToString:@"AXBoundsForTextMarkerRange"]) {
RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
NSRect rect = m_object->boundsForRange(range);
return [NSValue valueWithRect:rect];
}
if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) {
CharacterOffset start = cache->characterOffsetForIndex(range.location, m_object);
CharacterOffset end = cache->characterOffsetForIndex(range.location+range.length, m_object);
if (start.isNull() || end.isNull())
return nil;
RefPtr<Range> range = cache->rangeForUnorderedCharacterOffsets(start, end);
NSRect rect = m_object->boundsForRange(range);
return [NSValue valueWithRect:rect];
}
if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) {
if (m_object->isTextControl()) {
PlainTextRange plainTextRange = PlainTextRange(range.location, range.length);
return m_object->doAXStringForRange(plainTextRange);
}
CharacterOffset start = cache->characterOffsetForIndex(range.location, m_object);
CharacterOffset end = cache->characterOffsetForIndex(range.location + range.length, m_object);
if (start.isNull() || end.isNull())
return nil;
RefPtr<Range> range = cache->rangeForUnorderedCharacterOffsets(start, end);
return m_object->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) {
id textMarkerRange = nil;
id parameter = [dictionary objectForKey:@"AXTextMarkerRange"];
if (AXObjectIsTextMarkerRange(parameter))
textMarkerRange = 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:@"AXTextMarkerRangeForUnorderedTextMarkers"]) {
if ([array count] < 2)
return nil;
id textMarker1 = [array objectAtIndex:0];
id textMarker2 = [array objectAtIndex:1];
if (!AXObjectIsTextMarker(textMarker1) || !AXObjectIsTextMarker(textMarker2))
return nil;
CharacterOffset characterOffset1 = [self characterOffsetForTextMarker:textMarker1];
CharacterOffset characterOffset2 = [self characterOffsetForTextMarker:textMarker2];
RefPtr<Range> range = cache->rangeForUnorderedCharacterOffsets(characterOffset1, characterOffset2);
return [self textMarkerRangeFromRange:range];
}
if ([attribute isEqualToString:@"AXNextTextMarkerForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
return [self nextTextMarkerForCharacterOffset:characterOffset];
}
if ([attribute isEqualToString:@"AXPreviousTextMarkerForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
return [self previousTextMarkerForCharacterOffset:characterOffset];
}
if ([attribute isEqualToString:@"AXLeftWordTextMarkerRangeForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
RefPtr<Range> range = cache->leftWordRange(characterOffset);
return [self textMarkerRangeFromRange:range];
}
if ([attribute isEqualToString:@"AXRightWordTextMarkerRangeForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
RefPtr<Range> range = cache->rightWordRange(characterOffset);
return [self textMarkerRangeFromRange:range];
}
if ([attribute isEqualToString:@"AXLeftLineTextMarkerRangeForTextMarker"]) {
VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
VisiblePositionRange vpRange = m_object->leftLineVisiblePositionRange(visiblePos);
return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
}
if ([attribute isEqualToString:@"AXRightLineTextMarkerRangeForTextMarker"]) {
VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
VisiblePositionRange vpRange = m_object->rightLineVisiblePositionRange(visiblePos);
return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
}
if ([attribute isEqualToString:@"AXSentenceTextMarkerRangeForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
RefPtr<Range> range = cache->sentenceForCharacterOffset(characterOffset);
return [self textMarkerRangeFromRange:range];
}
if ([attribute isEqualToString:@"AXParagraphTextMarkerRangeForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
RefPtr<Range> range = cache->paragraphForCharacterOffset(characterOffset);
return [self textMarkerRangeFromRange:range];
}
if ([attribute isEqualToString:@"AXNextWordEndTextMarkerForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
CharacterOffset nextEnd = cache->nextWordEndCharacterOffset(characterOffset);
return [self textMarkerForCharacterOffset:nextEnd];
}
if ([attribute isEqualToString:@"AXPreviousWordStartTextMarkerForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
CharacterOffset previousStart = cache->previousWordStartCharacterOffset(characterOffset);
return [self textMarkerForCharacterOffset:previousStart];
}
if ([attribute isEqualToString:@"AXNextLineEndTextMarkerForTextMarker"]) {
VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
return [self textMarkerForVisiblePosition:m_object->nextLineEndPosition(visiblePos)];
}
if ([attribute isEqualToString:@"AXPreviousLineStartTextMarkerForTextMarker"]) {
VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
return [self textMarkerForVisiblePosition:m_object->previousLineStartPosition(visiblePos)];
}
if ([attribute isEqualToString:@"AXNextSentenceEndTextMarkerForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
CharacterOffset nextEnd = cache->nextSentenceEndCharacterOffset(characterOffset);
return [self textMarkerForCharacterOffset:nextEnd];
}
if ([attribute isEqualToString:@"AXPreviousSentenceStartTextMarkerForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
CharacterOffset previousStart = cache->previousSentenceStartCharacterOffset(characterOffset);
return [self textMarkerForCharacterOffset:previousStart];
}
if ([attribute isEqualToString:@"AXNextParagraphEndTextMarkerForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
CharacterOffset nextEnd = cache->nextParagraphEndCharacterOffset(characterOffset);
return [self textMarkerForCharacterOffset:nextEnd];
}
if ([attribute isEqualToString:@"AXPreviousParagraphStartTextMarkerForTextMarker"]) {
CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
CharacterOffset previousStart = cache->previousParagraphStartCharacterOffset(characterOffset);
return [self textMarkerForCharacterOffset:previousStart];
}
if ([attribute isEqualToString:@"AXStyleTextMarkerRangeForTextMarker"]) {
VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
VisiblePositionRange vpRange = m_object->styleRangeForPosition(visiblePos);
return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
}
if ([attribute isEqualToString:@"AXLengthForTextMarkerRange"]) {
RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
int length = AXObjectCache::lengthForRange(range.get());
if (length < 0)
return nil;
return [NSNumber numberWithInt:length];
}
// Used only by DumpRenderTree (so far).
if ([attribute isEqualToString:@"AXStartTextMarkerForTextMarkerRange"]) {
RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
return [self startOrEndTextMarkerForRange:range isStart:YES];
}
if ([attribute isEqualToString:@"AXEndTextMarkerForTextMarkerRange"]) {
RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
return [self startOrEndTextMarkerForRange:range isStart:NO];
}
#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 (is<AccessibilityTable>(*m_object) && downcast<AccessibilityTable>(*m_object).isExposableThroughAccessibility()) {
if ([attribute isEqualToString:NSAccessibilityCellForColumnAndRowParameterizedAttribute]) {
if (array == nil || [array count] != 2)
return nil;
AccessibilityTableCell* cell = downcast<AccessibilityTable>(*m_object).cellForColumnAndRow([[array objectAtIndex:0] unsignedIntValue], [[array objectAtIndex:1] unsignedIntValue]);
if (!cell)
return nil;
return cell->wrapper();
}
}
if (m_object->isTextControl()) {
if ([attribute isEqualToString: (NSString *)kAXLineForIndexParameterizedAttribute]) {
int lineNumber = m_object->doAXLineForIndex([number intValue]);
if (lineNumber < 0)
return nil;
return [NSNumber numberWithUnsignedInt:lineNumber];
}
if ([attribute isEqualToString: (NSString *)kAXRangeForLineParameterizedAttribute]) {
PlainTextRange textRange = m_object->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)(m_object->doAXStringForRange(plainTextRange)) : nil;
}
if ([attribute isEqualToString: (NSString*)kAXRangeForPositionParameterizedAttribute]) {
if (!pointSet)
return nil;
IntPoint webCorePoint = IntPoint(point);
PlainTextRange textRange = m_object->doAXRangeForPosition(webCorePoint);
return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)];
}
if ([attribute isEqualToString: (NSString*)kAXRangeForIndexParameterizedAttribute]) {
PlainTextRange textRange = m_object->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);
NSRect rect = m_object->doAXBoundsForRangeUsingCharacterOffset(plainTextRange);
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 = m_object->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;
}
- (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
{
if (![self updateObjectBackingStore])
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 (m_object->isTree())
return [super accessibilityIndexOfChild:child];
const auto& children = m_object->children();
if (children.isEmpty())
return [[self renderWidgetChildren] indexOfObject:child];
unsigned count = children.size();
for (unsigned k = 0; k < count; ++k) {
WebAccessibilityObjectWrapper* wrapper = children[k]->wrapper();
if (wrapper == child || (children[k]->isAttachment() && [wrapper attachmentView] == child))
return k;
}
return NSNotFound;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute
{
if (![self updateObjectBackingStore])
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 (m_object->isTree() || m_object->isTreeItem())
return [[self accessibilityAttributeValue:NSAccessibilityChildrenAttribute] count];
const auto& children = m_object->children();
if (children.isEmpty())
return [[self renderWidgetChildren] count];
return children.size();
}
return [super accessibilityArrayAttributeCount:attribute];
}
#pragma clang diagnostic pop
- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount
{
if (![self updateObjectBackingStore])
return nil;
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
if (m_object->children().isEmpty()) {
NSArray *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)];
} else if (m_object->isTree() || m_object->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];
}
const auto& children = m_object->children();
unsigned childCount = children.size();
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]->wrapper();
if (wrapper) {
// The attachment view should be returned, otherwise AX palindrome errors occur.
if (children[index]->isAttachment() && [wrapper attachmentView])
[subarray addObject:[wrapper attachmentView]];
else
[subarray addObject:wrapper];
}
}
return subarray;
}
return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount];
}
@end
#endif // HAVE(ACCESSIBILITY) && PLATFORM(MAC)