/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #import "mozAccessible.h" #import "MacUtils.h" #import "mozView.h" #include "Accessible-inl.h" #include "nsAccUtils.h" #include "nsIAccessibleRelation.h" #include "nsIAccessibleEditableText.h" #include "nsIPersistentProperties2.h" #include "Relation.h" #include "Role.h" #include "RootAccessible.h" #include "TableAccessible.h" #include "TableCellAccessible.h" #include "mozilla/a11y/PDocAccessible.h" #include "OuterDocAccessible.h" #include "mozilla/Services.h" #include "nsRect.h" #include "nsCocoaUtils.h" #include "nsCoord.h" #include "nsObjCExceptions.h" #include "nsWhitespaceTokenizer.h" #include <prdtoa.h> using namespace mozilla; using namespace mozilla::a11y; #define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand" #define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex" #define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator" #define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator" #define NSAccessibilityMathBaseAttribute @"AXMathBase" #define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript" #define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript" #define NSAccessibilityMathUnderAttribute @"AXMathUnder" #define NSAccessibilityMathOverAttribute @"AXMathOver" #define NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness" // XXX WebKit also defines the following attributes. // See bugs 1176970 and 1176983. // - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen" // - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose" // - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts" // - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts" // convert an array of Gecko accessibles to an NSArray of native accessibles static inline NSMutableArray* ConvertToNSArray(nsTArray<Accessible*>& aArray) { NSMutableArray* nativeArray = [[NSMutableArray alloc] init]; // iterate through the list, and get each native accessible. size_t totalCount = aArray.Length(); for (size_t i = 0; i < totalCount; i++) { Accessible* curAccessible = aArray.ElementAt(i); mozAccessible* curNative = GetNativeFromGeckoAccessible(curAccessible); if (curNative) [nativeArray addObject:GetObjectOrRepresentedView(curNative)]; } return nativeArray; } // convert an array of Gecko proxy accessibles to an NSArray of native accessibles static inline NSMutableArray* ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray) { NSMutableArray* nativeArray = [[NSMutableArray alloc] init]; // iterate through the list, and get each native accessible. size_t totalCount = aArray.Length(); for (size_t i = 0; i < totalCount; i++) { ProxyAccessible* curAccessible = aArray.ElementAt(i); mozAccessible* curNative = GetNativeFromProxy(curAccessible); if (curNative) [nativeArray addObject:GetObjectOrRepresentedView(curNative)]; } return nativeArray; } #pragma mark - @implementation mozAccessible - (id)initWithAccessible:(uintptr_t)aGeckoAccessible { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; if ((self = [super init])) { mGeckoAccessible = aGeckoAccessible; if (aGeckoAccessible & IS_PROXY) mRole = [self getProxyAccessible]->Role(); else mRole = [self getGeckoAccessible]->Role(); } return self; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (void)dealloc { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; [mChildren release]; [super dealloc]; NS_OBJC_END_TRY_ABORT_BLOCK; } - (mozilla::a11y::AccessibleWrap*)getGeckoAccessible { // Check if mGeckoAccessible points at a proxy if (mGeckoAccessible & IS_PROXY) return nil; return reinterpret_cast<AccessibleWrap*>(mGeckoAccessible); } - (mozilla::a11y::ProxyAccessible*)getProxyAccessible { // Check if mGeckoAccessible points at a proxy if (!(mGeckoAccessible & IS_PROXY)) return nil; return reinterpret_cast<ProxyAccessible*>(mGeckoAccessible & ~IS_PROXY); } #pragma mark - - (BOOL)accessibilityIsIgnored { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; // unknown (either unimplemented, or irrelevant) elements are marked as ignored // as well as expired elements. bool noRole = [[self role] isEqualToString:NSAccessibilityUnknownRole]; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) return (noRole && !(accWrap->InteractiveState() & states::FOCUSABLE)); if (ProxyAccessible* proxy = [self getProxyAccessible]) return (noRole && !(proxy->State() & states::FOCUSABLE)); return true; NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); } - (NSArray*)additionalAccessibilityAttributeNames { NSMutableArray* additional = [NSMutableArray array]; switch (mRole) { case roles::MATHML_ROOT: [additional addObject:NSAccessibilityMathRootIndexAttribute]; [additional addObject:NSAccessibilityMathRootRadicandAttribute]; break; case roles::MATHML_SQUARE_ROOT: [additional addObject:NSAccessibilityMathRootRadicandAttribute]; break; case roles::MATHML_FRACTION: [additional addObject:NSAccessibilityMathFractionNumeratorAttribute]; [additional addObject:NSAccessibilityMathFractionDenominatorAttribute]; [additional addObject:NSAccessibilityMathLineThicknessAttribute]; break; case roles::MATHML_SUB: case roles::MATHML_SUP: case roles::MATHML_SUB_SUP: [additional addObject:NSAccessibilityMathBaseAttribute]; [additional addObject:NSAccessibilityMathSubscriptAttribute]; [additional addObject:NSAccessibilityMathSuperscriptAttribute]; break; case roles::MATHML_UNDER: case roles::MATHML_OVER: case roles::MATHML_UNDER_OVER: [additional addObject:NSAccessibilityMathBaseAttribute]; [additional addObject:NSAccessibilityMathUnderAttribute]; [additional addObject:NSAccessibilityMathOverAttribute]; break; // XXX bug 1176983 // roles::MATHML_MULTISCRIPTS should also have the following attributes: // - NSAccessibilityMathPrescriptsAttribute // - NSAccessibilityMathPostscriptsAttribute // XXX bug 1176970 // roles::MATHML_FENCED should also have the following attributes: // - NSAccessibilityMathFencedOpenAttribute // - NSAccessibilityMathFencedCloseAttribute default: break; } return additional; } - (NSArray*)accessibilityAttributeNames { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; // if we're expired, we don't support any attributes. AccessibleWrap* accWrap = [self getGeckoAccessible]; ProxyAccessible* proxy = [self getProxyAccessible]; if (!accWrap && !proxy) return [NSArray array]; static NSArray* generalAttributes = nil; if (!generalAttributes) { // standard attributes that are shared and supported by all generic elements. generalAttributes = [[NSArray alloc] initWithObjects: NSAccessibilityChildrenAttribute, NSAccessibilityParentAttribute, NSAccessibilityRoleAttribute, NSAccessibilityTitleAttribute, NSAccessibilityValueAttribute, NSAccessibilitySubroleAttribute, NSAccessibilityRoleDescriptionAttribute, NSAccessibilityPositionAttribute, NSAccessibilityEnabledAttribute, NSAccessibilitySizeAttribute, NSAccessibilityWindowAttribute, NSAccessibilityFocusedAttribute, NSAccessibilityHelpAttribute, NSAccessibilityTitleUIElementAttribute, NSAccessibilityTopLevelUIElementAttribute, #if DEBUG @"AXMozDescription", #endif nil]; } NSArray* objectAttributes = generalAttributes; NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames]; if ([additionalAttributes count]) objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes]; return objectAttributes; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (id)childAt:(uint32_t)i { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { Accessible* child = accWrap->GetChildAt(i); return child ? GetNativeFromGeckoAccessible(child) : nil; } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { ProxyAccessible* child = proxy->ChildAt(i); return child ? GetNativeFromProxy(child) : nil; } return nil; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (id)accessibilityAttributeValue:(NSString*)attribute { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; AccessibleWrap* accWrap = [self getGeckoAccessible]; ProxyAccessible* proxy = [self getProxyAccessible]; if (!accWrap && !proxy) return nil; #if DEBUG if ([attribute isEqualToString:@"AXMozDescription"]) return [NSString stringWithFormat:@"role = %u native = %@", mRole, [self class]]; #endif if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) return [self children]; if ([attribute isEqualToString:NSAccessibilityParentAttribute]) return [self parent]; #ifdef DEBUG_hakan NSLog (@"(%@ responding to attr %@)", self, attribute); #endif if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) return [self role]; if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) return [self position]; if ([attribute isEqualToString:NSAccessibilitySubroleAttribute]) return [self subrole]; if ([attribute isEqualToString:NSAccessibilityEnabledAttribute]) return [NSNumber numberWithBool:[self isEnabled]]; if ([attribute isEqualToString:NSAccessibilityValueAttribute]) return [self value]; if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) return [self roleDescription]; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) return [NSNumber numberWithBool:[self isFocused]]; if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) return [self size]; if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) return [self window]; if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) return [self window]; if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) return [self title]; if ([attribute isEqualToString:NSAccessibilityTitleUIElementAttribute]) { if (accWrap) { Relation rel = accWrap->RelationByType(RelationType::LABELLED_BY); Accessible* tempAcc = rel.Next(); return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil; } nsTArray<ProxyAccessible*> rel = proxy->RelationByType(RelationType::LABELLED_BY); ProxyAccessible* tempProxy = rel.SafeElementAt(0); return tempProxy ? GetNativeFromProxy(tempProxy) : nil; } if ([attribute isEqualToString:NSAccessibilityHelpAttribute]) return [self help]; switch (mRole) { case roles::MATHML_ROOT: if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute]) return [self childAt:0]; if ([attribute isEqualToString:NSAccessibilityMathRootIndexAttribute]) return [self childAt:1]; break; case roles::MATHML_SQUARE_ROOT: if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute]) return [self childAt:0]; break; case roles::MATHML_FRACTION: if ([attribute isEqualToString:NSAccessibilityMathFractionNumeratorAttribute]) return [self childAt:0]; if ([attribute isEqualToString:NSAccessibilityMathFractionDenominatorAttribute]) return [self childAt:1]; if ([attribute isEqualToString:NSAccessibilityMathLineThicknessAttribute]) { // WebKit sets line thickness to some logical value parsed in the // renderer object of the <mfrac> element. It's not clear whether the // exact value is relevant to assistive technologies. From a semantic // point of view, the only important point is to distinguish between // <mfrac> elements that have a fraction bar and those that do not. // Per the MathML 3 spec, the latter happens iff the linethickness // attribute is of the form [zero-float][optional-unit]. In that case we // set line thickness to zero and in the other cases we set it to one. nsAutoString thickness; if (accWrap) { nsCOMPtr<nsIPersistentProperties> attributes = accWrap->Attributes(); nsAccUtils::GetAccAttr(attributes, nsGkAtoms::linethickness_, thickness); } else { AutoTArray<Attribute, 10> attrs; proxy->Attributes(&attrs); for (size_t i = 0 ; i < attrs.Length() ; i++) { if (attrs.ElementAt(i).Name() == "thickness") { thickness = attrs.ElementAt(i).Value(); break; } } } double value = 1.0; if (!thickness.IsEmpty()) value = PR_strtod(NS_LossyConvertUTF16toASCII(thickness).get(), nullptr); return [NSNumber numberWithInteger:(value ? 1 : 0)]; } break; case roles::MATHML_SUB: if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) return [self childAt:0]; if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute]) return [self childAt:1]; #ifdef DEBUG if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute]) return nil; #endif break; case roles::MATHML_SUP: if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) return [self childAt:0]; #ifdef DEBUG if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute]) return nil; #endif if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute]) return [self childAt:1]; break; case roles::MATHML_SUB_SUP: if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) return [self childAt:0]; if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute]) return [self childAt:1]; if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute]) return [self childAt:2]; break; case roles::MATHML_UNDER: if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) return [self childAt:0]; if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute]) return [self childAt:1]; #ifdef DEBUG if ([attribute isEqualToString:NSAccessibilityMathOverAttribute]) return nil; #endif break; case roles::MATHML_OVER: if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) return [self childAt:0]; #ifdef DEBUG if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute]) return nil; #endif if ([attribute isEqualToString:NSAccessibilityMathOverAttribute]) return [self childAt:1]; break; case roles::MATHML_UNDER_OVER: if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) return [self childAt:0]; if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute]) return [self childAt:1]; if ([attribute isEqualToString:NSAccessibilityMathOverAttribute]) return [self childAt:2]; break; // XXX bug 1176983 // roles::MATHML_MULTISCRIPTS should also have the following attributes: // - NSAccessibilityMathPrescriptsAttribute // - NSAccessibilityMathPostscriptsAttribute // XXX bug 1176970 // roles::MATHML_FENCED should also have the following attributes: // - NSAccessibilityMathFencedOpenAttribute // - NSAccessibilityMathFencedCloseAttribute default: break; } #ifdef DEBUG NSLog (@"!!! %@ can't respond to attribute %@", self, attribute); #endif return nil; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) return [self canBeFocused]; return NO; NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); } - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; #ifdef DEBUG_hakan NSLog (@"[%@] %@='%@'", self, attribute, value); #endif // we only support focusing elements so far. if ([attribute isEqualToString:NSAccessibilityFocusedAttribute] && [value boolValue]) [self focus]; NS_OBJC_END_TRY_ABORT_BLOCK; } - (id)accessibilityHitTest:(NSPoint)point { AccessibleWrap* accWrap = [self getGeckoAccessible]; ProxyAccessible* proxy = [self getProxyAccessible]; if (!accWrap && !proxy) return nil; // Convert the given screen-global point in the cocoa coordinate system (with // origin in the bottom-left corner of the screen) into point in the Gecko // coordinate system (with origin in a top-left screen point). NSScreen* mainView = [[NSScreen screens] objectAtIndex:0]; NSPoint tmpPoint = NSMakePoint(point.x, [mainView frame].size.height - point.y); LayoutDeviceIntPoint geckoPoint = nsCocoaUtils:: CocoaPointsToDevPixels(tmpPoint, nsCocoaUtils::GetBackingScaleFactor(mainView)); mozAccessible* nativeChild = nil; if (accWrap) { Accessible* child = accWrap->ChildAtPoint(geckoPoint.x, geckoPoint.y, Accessible::eDeepestChild); if (child) nativeChild = GetNativeFromGeckoAccessible(child); } else if (proxy) { ProxyAccessible* child = proxy->ChildAtPoint(geckoPoint.x, geckoPoint.y, Accessible::eDeepestChild); if (child) nativeChild = GetNativeFromProxy(child); } if (nativeChild) return nativeChild; // if we didn't find anything, return ourself or child view. return GetObjectOrRepresentedView(self); } - (NSArray*)accessibilityActionNames { return nil; } - (NSString*)accessibilityActionDescription:(NSString*)action { // by default we return whatever the MacOS API know about. // if you have custom actions, override. return NSAccessibilityActionDescription(action); } - (void)accessibilityPerformAction:(NSString*)action { } - (id)accessibilityFocusedUIElement { AccessibleWrap* accWrap = [self getGeckoAccessible]; ProxyAccessible* proxy = [self getProxyAccessible]; if (!accWrap && !proxy) return nil; mozAccessible* focusedChild = nil; if (accWrap) { Accessible* focusedGeckoChild = accWrap->FocusedChild(); if (focusedGeckoChild) focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild); } else if (proxy) { ProxyAccessible* focusedGeckoChild = proxy->FocusedChild(); if (focusedGeckoChild) focusedChild = GetNativeFromProxy(focusedGeckoChild); } if (focusedChild) return GetObjectOrRepresentedView(focusedChild); // return ourself if we can't get a native focused child. return GetObjectOrRepresentedView(self); } #pragma mark - - (id <mozAccessible>)parent { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; id nativeParent = nil; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { Accessible* accessibleParent = accWrap->Parent(); if (accessibleParent) nativeParent = GetNativeFromGeckoAccessible(accessibleParent); if (nativeParent) return GetObjectOrRepresentedView(nativeParent); // Return native of root accessible if we have no direct parent nativeParent = GetNativeFromGeckoAccessible(accWrap->RootAccessible()); } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { if (ProxyAccessible* proxyParent = proxy->Parent()) { nativeParent = GetNativeFromProxy(proxyParent); } if (nativeParent) return GetObjectOrRepresentedView(nativeParent); Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser(); nativeParent = outerDoc ? GetNativeFromGeckoAccessible(outerDoc) : nil; } else { return nil; } NSAssert1 (nativeParent, @"!!! we can't find a parent for %@", self); return GetObjectOrRepresentedView(nativeParent); NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (BOOL)hasRepresentedView { return NO; } - (id)representedView { return nil; } - (BOOL)isRoot { return NO; } // gets our native children lazily. // returns nil when there are no children. - (NSArray*)children { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; if (mChildren) return mChildren; // get the array of children. mChildren = [[NSMutableArray alloc] init]; AccessibleWrap* accWrap = [self getGeckoAccessible]; if (accWrap) { uint32_t childCount = accWrap->ChildCount(); for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { mozAccessible* nativeChild = GetNativeFromGeckoAccessible(accWrap->GetChildAt(childIdx)); if (nativeChild) [mChildren addObject:nativeChild]; } // children from child if this is an outerdoc OuterDocAccessible* docOwner = accWrap->AsOuterDoc(); if (docOwner) { if (ProxyAccessible* proxyDoc = docOwner->RemoteChildDoc()) { mozAccessible* nativeRemoteChild = GetNativeFromProxy(proxyDoc); [mChildren insertObject:nativeRemoteChild atIndex:0]; NSAssert1 (nativeRemoteChild, @"%@ found a child remote doc missing a native\n", self); } } } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { uint32_t childCount = proxy->ChildrenCount(); for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { mozAccessible* nativeChild = GetNativeFromProxy(proxy->ChildAt(childIdx)); if (nativeChild) [mChildren addObject:nativeChild]; } } return mChildren; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (NSValue*)position { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; nsIntRect rect; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) rect = accWrap->Bounds(); else if (ProxyAccessible* proxy = [self getProxyAccessible]) rect = proxy->Bounds(); else return nil; NSScreen* mainView = [[NSScreen screens] objectAtIndex:0]; CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView); NSPoint p = NSMakePoint(static_cast<CGFloat>(rect.x) / scaleFactor, [mainView frame].size.height - static_cast<CGFloat>(rect.y + rect.height) / scaleFactor); return [NSValue valueWithPoint:p]; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (NSValue*)size { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; nsIntRect rect; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) rect = accWrap->Bounds(); else if (ProxyAccessible* proxy = [self getProxyAccessible]) rect = proxy->Bounds(); else return nil; CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor([[NSScreen screens] objectAtIndex:0]); return [NSValue valueWithSize:NSMakeSize(static_cast<CGFloat>(rect.width) / scaleFactor, static_cast<CGFloat>(rect.height) / scaleFactor)]; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (NSString*)role { AccessibleWrap* accWrap = [self getGeckoAccessible]; if (accWrap) { #ifdef DEBUG_A11Y NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap), "Does not support Text when it should"); #endif } else if (![self getProxyAccessible]) { return nil; } #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \ case roles::geckoRole: \ return macRole; switch (mRole) { #include "RoleMap.h" default: NS_NOTREACHED("Unknown role."); return NSAccessibilityUnknownRole; } #undef ROLE } - (NSString*)subrole { AccessibleWrap* accWrap = [self getGeckoAccessible]; ProxyAccessible* proxy = [self getProxyAccessible]; // Deal with landmarks first nsIAtom* landmark = nullptr; if (accWrap) landmark = accWrap->LandmarkRole(); else if (proxy) landmark = proxy->LandmarkRole(); if (landmark) { if (landmark == nsGkAtoms::application) return @"AXLandmarkApplication"; if (landmark == nsGkAtoms::banner) return @"AXLandmarkBanner"; if (landmark == nsGkAtoms::complementary) return @"AXLandmarkComplementary"; if (landmark == nsGkAtoms::contentinfo) return @"AXLandmarkContentInfo"; if (landmark == nsGkAtoms::form) return @"AXLandmarkForm"; if (landmark == nsGkAtoms::main) return @"AXLandmarkMain"; if (landmark == nsGkAtoms::navigation) return @"AXLandmarkNavigation"; if (landmark == nsGkAtoms::search) return @"AXLandmarkSearch"; if (landmark == nsGkAtoms::searchbox) return @"AXSearchField"; } // Now, deal with widget roles nsIAtom* roleAtom = nullptr; if (accWrap && accWrap->HasARIARole()) { const nsRoleMapEntry* roleMap = accWrap->ARIARoleMap(); roleAtom = *roleMap->roleAtom; } if (proxy) roleAtom = proxy->ARIARoleAtom(); if (roleAtom) { if (roleAtom == nsGkAtoms::alert) return @"AXApplicationAlert"; if (roleAtom == nsGkAtoms::alertdialog) return @"AXApplicationAlertDialog"; if (roleAtom == nsGkAtoms::article) return @"AXDocumentArticle"; if (roleAtom == nsGkAtoms::dialog) return @"AXApplicationDialog"; if (roleAtom == nsGkAtoms::document) return @"AXDocument"; if (roleAtom == nsGkAtoms::log_) return @"AXApplicationLog"; if (roleAtom == nsGkAtoms::marquee) return @"AXApplicationMarquee"; if (roleAtom == nsGkAtoms::math) return @"AXDocumentMath"; if (roleAtom == nsGkAtoms::note_) return @"AXDocumentNote"; if (roleAtom == nsGkAtoms::region) return @"AXDocumentRegion"; if (roleAtom == nsGkAtoms::status) return @"AXApplicationStatus"; if (roleAtom == nsGkAtoms::tabpanel) return @"AXTabPanel"; if (roleAtom == nsGkAtoms::timer) return @"AXApplicationTimer"; if (roleAtom == nsGkAtoms::tooltip) return @"AXUserInterfaceTooltip"; } switch (mRole) { case roles::LIST: return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole; case roles::ENTRY: if ((accWrap && accWrap->IsSearchbox()) || (proxy && proxy->IsSearchbox())) return @"AXSearchField"; break; case roles::DEFINITION_LIST: return @"AXDefinitionList"; // 10.6+ NSAccessibilityDefinitionListSubrole; case roles::TERM: return @"AXTerm"; case roles::DEFINITION: return @"AXDefinition"; case roles::MATHML_MATH: return @"AXDocumentMath"; case roles::MATHML_FRACTION: return @"AXMathFraction"; case roles::MATHML_FENCED: // XXX bug 1176970 // This should be AXMathFence, but doing so without implementing the // whole fence interface seems to make VoiceOver crash, so we present it // as a row for now. return @"AXMathRow"; case roles::MATHML_SUB: case roles::MATHML_SUP: case roles::MATHML_SUB_SUP: return @"AXMathSubscriptSuperscript"; case roles::MATHML_ROW: case roles::MATHML_STYLE: case roles::MATHML_ERROR: return @"AXMathRow"; case roles::MATHML_UNDER: case roles::MATHML_OVER: case roles::MATHML_UNDER_OVER: return @"AXMathUnderOver"; case roles::MATHML_SQUARE_ROOT: return @"AXMathSquareRoot"; case roles::MATHML_ROOT: return @"AXMathRoot"; case roles::MATHML_TEXT: return @"AXMathText"; case roles::MATHML_NUMBER: return @"AXMathNumber"; case roles::MATHML_IDENTIFIER: return @"AXMathIdentifier"; case roles::MATHML_TABLE: return @"AXMathTable"; case roles::MATHML_TABLE_ROW: return @"AXMathTableRow"; case roles::MATHML_CELL: return @"AXMathTableCell"; // XXX: NSAccessibility also uses subroles AXMathSeparatorOperator and // AXMathFenceOperator. We should use the NS_MATHML_OPERATOR_FENCE and // NS_MATHML_OPERATOR_SEPARATOR bits of nsOperatorFlags, but currently they // are only available from the MathML layout code. Hence we just fallback // to subrole AXMathOperator for now. // XXX bug 1175747 WebKit also creates anonymous operators for <mfenced> // which have subroles AXMathSeparatorOperator and AXMathFenceOperator. case roles::MATHML_OPERATOR: return @"AXMathOperator"; case roles::MATHML_MULTISCRIPTS: return @"AXMathMultiscript"; case roles::SWITCH: return @"AXSwitch"; case roles::ALERT: return @"AXApplicationAlert"; case roles::SEPARATOR: return @"AXContentSeparator"; case roles::PROPERTYPAGE: return @"AXTabPanel"; case roles::DETAILS: return @"AXDetails"; case roles::SUMMARY: return @"AXSummary"; default: break; } return nil; } struct RoleDescrMap { NSString* role; const nsString description; }; static const RoleDescrMap sRoleDescrMap[] = { { @"AXApplicationAlert", NS_LITERAL_STRING("alert") }, { @"AXApplicationAlertDialog", NS_LITERAL_STRING("alertDialog") }, { @"AXApplicationLog", NS_LITERAL_STRING("log") }, { @"AXApplicationMarquee", NS_LITERAL_STRING("marquee") }, { @"AXApplicationStatus", NS_LITERAL_STRING("status") }, { @"AXApplicationTimer", NS_LITERAL_STRING("timer") }, { @"AXContentSeparator", NS_LITERAL_STRING("separator") }, { @"AXDefinition", NS_LITERAL_STRING("definition") }, { @"AXDocument", NS_LITERAL_STRING("document") }, { @"AXDocumentArticle", NS_LITERAL_STRING("article") }, { @"AXDocumentMath", NS_LITERAL_STRING("math") }, { @"AXDocumentNote", NS_LITERAL_STRING("note") }, { @"AXDocumentRegion", NS_LITERAL_STRING("region") }, { @"AXLandmarkApplication", NS_LITERAL_STRING("application") }, { @"AXLandmarkBanner", NS_LITERAL_STRING("banner") }, { @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") }, { @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") }, { @"AXLandmarkMain", NS_LITERAL_STRING("main") }, { @"AXLandmarkNavigation", NS_LITERAL_STRING("navigation") }, { @"AXLandmarkSearch", NS_LITERAL_STRING("search") }, { @"AXSearchField", NS_LITERAL_STRING("searchTextField") }, { @"AXTabPanel", NS_LITERAL_STRING("tabPanel") }, { @"AXTerm", NS_LITERAL_STRING("term") }, { @"AXUserInterfaceTooltip", NS_LITERAL_STRING("tooltip") } }; struct RoleDescrComparator { const NSString* mRole; explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {} int operator()(const RoleDescrMap& aEntry) const { return [mRole compare:aEntry.role]; } }; - (NSString*)roleDescription { if (mRole == roles::DOCUMENT) return utils::LocalizedString(NS_LITERAL_STRING("htmlContent")); NSString* subrole = [self subrole]; if (subrole) { size_t idx = 0; if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap), RoleDescrComparator(subrole), &idx)) { return utils::LocalizedString(sRoleDescrMap[idx].description); } } return NSAccessibilityRoleDescription([self role], subrole); } - (NSString*)title { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; nsAutoString title; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) accWrap->Name(title); else if (ProxyAccessible* proxy = [self getProxyAccessible]) proxy->Name(title); return nsCocoaUtils::ToNSString(title); NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (id)value { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; nsAutoString value; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) accWrap->Value(value); else if (ProxyAccessible* proxy = [self getProxyAccessible]) proxy->Value(value); return nsCocoaUtils::ToNSString(value); NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (void)valueDidChange { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; #ifdef DEBUG_hakan NSLog(@"%@'s value changed!", self); #endif // sending out a notification is expensive, so we don't do it other than for really important objects, // like mozTextAccessible. NS_OBJC_END_TRY_ABORT_BLOCK; } - (void)selectedTextDidChange { // Do nothing. mozTextAccessible will. } - (void)documentLoadComplete { id realSelf = GetObjectOrRepresentedView(self); NSAccessibilityPostNotification(realSelf, NSAccessibilityFocusedUIElementChangedNotification); NSAccessibilityPostNotification(realSelf, @"AXLoadComplete"); NSAccessibilityPostNotification(realSelf, @"AXLayoutComplete"); } - (NSString*)help { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; // What needs to go here is actually the accDescription of an item. // The MSAA acc_help method has nothing to do with this one. nsAutoString helpText; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) accWrap->Description(helpText); else if (ProxyAccessible* proxy = [self getProxyAccessible]) proxy->Description(helpText); return nsCocoaUtils::ToNSString(helpText); NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } // objc-style description (from NSObject); not to be confused with the accessible description above. - (NSString*)description { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; return [NSString stringWithFormat:@"(%p) %@", self, [self role]]; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (BOOL)isFocused { return FocusMgr()->IsFocused([self getGeckoAccessible]); } - (BOOL)canBeFocused { if (AccessibleWrap* accWrap = [self getGeckoAccessible]) return accWrap->InteractiveState() & states::FOCUSABLE; if (ProxyAccessible* proxy = [self getProxyAccessible]) return proxy->State() & states::FOCUSABLE; return false; } - (BOOL)focus { if (AccessibleWrap* accWrap = [self getGeckoAccessible]) accWrap->TakeFocus(); else if (ProxyAccessible* proxy = [self getProxyAccessible]) proxy->TakeFocus(); else return NO; return YES; } - (BOOL)isEnabled { if (AccessibleWrap* accWrap = [self getGeckoAccessible]) return ((accWrap->InteractiveState() & states::UNAVAILABLE) == 0); if (ProxyAccessible* proxy = [self getProxyAccessible]) return ((proxy->State() & states::UNAVAILABLE) == 0); return false; } // The root accessible calls this when the focused node was // changed to us. - (void)didReceiveFocus { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; #ifdef DEBUG_hakan NSLog (@"%@ received focus!", self); #endif NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), NSAccessibilityFocusedUIElementChangedNotification); NS_OBJC_END_TRY_ABORT_BLOCK; } - (NSWindow*)window { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; // Get a pointer to the native window (NSWindow) we reside in. NSWindow *nativeWindow = nil; DocAccessible* docAcc = nullptr; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { docAcc = accWrap->Document(); } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser(); if (outerDoc) docAcc = outerDoc->Document(); } if (docAcc) nativeWindow = static_cast<NSWindow*>(docAcc->GetNativeWindow()); NSAssert1(nativeWindow, @"Could not get native window for %@", self); return nativeWindow; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (void)invalidateChildren { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; // make room for new children [mChildren release]; mChildren = nil; NS_OBJC_END_TRY_ABORT_BLOCK; } - (void)appendChild:(Accessible*)aAccessible { // if mChildren is nil, then we don't even need to bother if (!mChildren) return; mozAccessible *curNative = GetNativeFromGeckoAccessible(aAccessible); if (curNative) [mChildren addObject:curNative]; } - (void)expire { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; [self invalidateChildren]; mGeckoAccessible = 0; NS_OBJC_END_TRY_ABORT_BLOCK; } - (BOOL)isExpired { return ![self getGeckoAccessible] && ![self getProxyAccessible]; } #pragma mark - #pragma mark Debug methods #pragma mark - #ifdef DEBUG // will check that our children actually reference us as their // parent. - (void)sanityCheckChildren:(NSArray *)children { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; NSEnumerator *iter = [children objectEnumerator]; mozAccessible *curObj = nil; NSLog(@"sanity checking %@", self); while ((curObj = [iter nextObject])) { id realSelf = GetObjectOrRepresentedView(self); NSLog(@"checking %@", realSelf); NSAssert2([curObj parent] == realSelf, @"!!! %@ not returning %@ as AXParent, even though it is a AXChild of it!", curObj, realSelf); } NS_OBJC_END_TRY_ABORT_BLOCK; } - (void)sanityCheckChildren { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; [self sanityCheckChildren:[self children]]; NS_OBJC_END_TRY_ABORT_BLOCK; } - (void)printHierarchy { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; [self printHierarchyWithLevel:0]; NS_OBJC_END_TRY_ABORT_BLOCK; } - (void)printHierarchyWithLevel:(unsigned)level { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; NSAssert(![self isExpired], @"!!! trying to print hierarchy of expired object!"); // print this node NSMutableString *indent = [NSMutableString stringWithCapacity:level]; unsigned i=0; for (;i<level;i++) [indent appendString:@" "]; NSLog (@"%@(#%i) %@", indent, level, self); // use |children| method to make sure our children are lazily fetched first. NSArray *children = [self children]; if (!children) return; [self sanityCheckChildren]; NSEnumerator *iter = [children objectEnumerator]; mozAccessible *object = nil; while (iter && (object = [iter nextObject])) // print every child node's subtree, increasing the indenting // by two for every level. [object printHierarchyWithLevel:(level+1)]; NS_OBJC_END_TRY_ABORT_BLOCK; } #endif /* DEBUG */ @end