diff options
Diffstat (limited to 'accessible/mac/mozAccessible.mm')
-rw-r--r-- | accessible/mac/mozAccessible.mm | 1236 |
1 files changed, 1236 insertions, 0 deletions
diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm new file mode 100644 index 000000000..e1cdba694 --- /dev/null +++ b/accessible/mac/mozAccessible.mm @@ -0,0 +1,1236 @@ +/* -*- 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 |