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