summaryrefslogtreecommitdiffstats
path: root/accessible/mac/mozAccessible.mm
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/mac/mozAccessible.mm')
-rw-r--r--accessible/mac/mozAccessible.mm1236
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