From 935b7af3952f7b33fd832d866f975dae981e651e Mon Sep 17 00:00:00 2001 From: "hwaara%gmail.com" Date: Fri, 1 Dec 2006 10:53:52 +0000 Subject: [PATCH] Make deep hit-testing work. This makes VoiceOver able to speak whatever you mouse over, both in XUL and web page content (if that pref is set in the accessibility settings) r=surkov, b=352329 --- accessible/src/mac/mozAccessible.h | 3 + accessible/src/mac/mozAccessible.mm | 144 +++++++++++++--------- accessible/src/mac/mozAccessibleWrapper.h | 4 + accessible/src/mac/mozDocAccessible.mm | 2 +- accessible/src/mac/nsAccessibleWrap.h | 8 +- accessible/src/mac/nsAccessibleWrap.mm | 8 ++ 6 files changed, 106 insertions(+), 63 deletions(-) diff --git a/accessible/src/mac/mozAccessible.h b/accessible/src/mac/mozAccessible.h index 096f982af6e..a205a833302 100644 --- a/accessible/src/mac/mozAccessible.h +++ b/accessible/src/mac/mozAccessible.h @@ -122,6 +122,9 @@ #ifdef DEBUG - (void)printHierarchy; - (void)printHierarchyWithLevel:(unsigned)numSpaces; + +- (void)sanityCheckChildren; +- (void)sanityCheckChildren:(NSArray *)theChildren; #endif // ---- NSAccessibility methods ---- // diff --git a/accessible/src/mac/mozAccessible.mm b/accessible/src/mac/mozAccessible.mm index e26f08f2033..b70605d9293 100644 --- a/accessible/src/mac/mozAccessible.mm +++ b/accessible/src/mac/mozAccessible.mm @@ -49,6 +49,8 @@ #include "nsIAccessibleText.h" #include "nsIAccessibleEditableText.h" +#include "nsRootAccessible.h" + // These constants are only defined in OS X SDK 10.4, so we define them in order // to be able to use for earlier OS versions. const NSString *kInstanceDescriptionAttribute = @"AXDescription"; // NSAccessibilityDescriptionAttribute @@ -65,32 +67,50 @@ ConvertCocoaToGeckoPoint(NSPoint &aInPoint, nsPoint &aOutPoint) aOutPoint.MoveTo ((nscoord)aInPoint.x, (nscoord)(mainScreenHeight - aInPoint.y)); } -// returns an object if it is not ignored. if it's ignored, will return -// the first unignored ancestor. -static inline id -ObjectOrUnignoredAncestor(id anObject) +// all mozAccessibles are either abstract objects (that correspond to XUL widgets, HTML frames, etc) or are +// attached to a certain view; for example a document view. when we hand an object off to an AT, we always want +// to give it the represented view, in the latter case. +static inline id +GetObjectOrRepresentedView(id anObject) { - if ([anObject accessibilityIsIgnored]) - return NSAccessibilityUnignoredAncestor(anObject); + if ([anObject hasRepresentedView]) + return [anObject representedView]; return anObject; } +// returns the passed in object if it is not ignored. if it's ignored, will return +// the first unignored ancestor. +static inline id +GetClosestInterestingAccessible(id anObject) +{ + // this object is not ignored, so let's return it. + if (![anObject accessibilityIsIgnored]) + return GetObjectOrRepresentedView(anObject); + + // find the closest ancestor that is not ignored. + id unignoredObject = anObject; + while ((unignoredObject = [unignoredObject accessibilityAttributeValue:NSAccessibilityParentAttribute])) { + if (![unignoredObject accessibilityIsIgnored]) + // object is not ignored, so let's stop the search. + break; + } + + // if it's a mozAccessible, we need to take care to maybe return the view we + // represent, to the AT. + if ([unignoredObject respondsToSelector:@selector(hasRepresentedView)]) + return GetObjectOrRepresentedView(unignoredObject); + + return unignoredObject; +} + static inline mozAccessible* -NativeFromGeckoAccessible(nsIAccessible *anAccessible) +GetNativeFromGeckoAccessible(nsIAccessible *anAccessible) { mozAccessible *native = nil; anAccessible->GetNativeInterface ((void**)&native); - return [native hasRepresentedView] ? [native representedView] : native; + return native; } -#pragma mark - - -@interface mozAccessible (Private) -#ifdef DEBUG -- (void)sanityCheckChildren:(NSArray*)theChildren; -#endif -@end - #pragma mark - @implementation mozAccessible @@ -233,20 +253,26 @@ NativeFromGeckoAccessible(nsIAccessible *anAccessible) // the point we're given is guaranteed to be bottom-left screen coordinates. nsPoint geckoPoint; ConvertCocoaToGeckoPoint (point, geckoPoint); + + // start iterating as deep down as we can on this point, with the current accessible as the root. + // as soon as GetChildAtPoint() returns null, or can't descend further (without getting the same accessible again) + // we stop. + nsCOMPtr deepestFoundChild, newChild(mGeckoAccessible); + do { + deepestFoundChild = newChild; + deepestFoundChild->GetChildAtPoint((PRInt32)geckoPoint.x, (PRInt32)geckoPoint.y, getter_AddRefs(newChild)); + } while (newChild && newChild.get() != deepestFoundChild.get()); - // see if we can find an accessible at that point. - nsCOMPtr foundChild; - mGeckoAccessible->GetChildAtPoint ((PRInt32)geckoPoint.x, (PRInt32)geckoPoint.y, getter_AddRefs (foundChild)); - - if (foundChild) { - // if we found something, return its native accessible. - mozAccessible *nativeChild = NativeFromGeckoAccessible(foundChild); + + // if we found something, return its native accessible. + if (deepestFoundChild) { + mozAccessible *nativeChild = GetNativeFromGeckoAccessible(deepestFoundChild); if (nativeChild) - return ObjectOrUnignoredAncestor (nativeChild); + return GetClosestInterestingAccessible(nativeChild); } // if we didn't find anything, return ourself (or the first unignored ancestor). - return ObjectOrUnignoredAncestor ([self hasRepresentedView] ? [self representedView] : self); + return GetClosestInterestingAccessible(self); } - (NSArray*)accessibilityActionNames @@ -272,13 +298,13 @@ NativeFromGeckoAccessible(nsIAccessible *anAccessible) mGeckoAccessible->GetFocusedChild (getter_AddRefs (focusedGeckoChild)); if (focusedGeckoChild) { - mozAccessible *focusedChild = NativeFromGeckoAccessible(focusedGeckoChild); + mozAccessible *focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild); if (focusedChild) - return ObjectOrUnignoredAncestor(focusedChild); + return GetClosestInterestingAccessible(focusedChild); } // return ourself if we can't get a native focused child. - return ObjectOrUnignoredAncestor([self hasRepresentedView] ? [self representedView] : self); + return GetClosestInterestingAccessible(self); } #pragma mark - @@ -287,26 +313,21 @@ NativeFromGeckoAccessible(nsIAccessible *anAccessible) { nsCOMPtr accessibleParent(mGeckoAccessible->GetUnignoredParent()); if (accessibleParent) { - id nativeParent = NativeFromGeckoAccessible(accessibleParent); - if (nativeParent) { - nativeParent = ObjectOrUnignoredAncestor(nativeParent); - NSAssert(![nativeParent accessibilityIsIgnored], @"returned parent should not be ignored!"); - - // if the object doesn't have a representedView method, we might have the NSWindow or any kind of - // parent object far up in the hierarchy. - if ([nativeParent respondsToSelector:@selector(hasRepresentedView)] && - [nativeParent hasRepresentedView]) - return [nativeParent representedView]; - - return nativeParent; - } + id nativeParent = GetNativeFromGeckoAccessible(accessibleParent); + if (nativeParent) + return GetClosestInterestingAccessible(nativeParent); } -#ifdef DEBUG - NSLog (@"!!! we can't find a parent for %@", self); -#endif - // we didn't find a parent - return nil; + // GetUnignoredParent() returns null when there is no unignored accessible all the way up to + // the root accessible. so we'll have to return whatever native accessible is above our root accessible + // (which might be the owning NSWindow in the application, for example). + // + // get the native root accessible, and tell it to return its first parent unignored accessible. + nsRefPtr root(mGeckoAccessible->GetRootAccessible()); + id nativeParent = GetNativeFromGeckoAccessible(NS_STATIC_CAST(nsIAccessible*, root)); + NSAssert1 (nativeParent, @"!!! we can't find a parent for %@", self); + + return GetClosestInterestingAccessible(nativeParent); } - (BOOL)hasRepresentedView @@ -330,9 +351,8 @@ NativeFromGeckoAccessible(nsIAccessible *anAccessible) { if (mChildren) return mChildren; - - if (!mChildren) - mChildren = [[NSMutableArray alloc] init]; + + mChildren = [[NSMutableArray alloc] init]; // get the array of children. nsTArray > childrenArray; @@ -345,9 +365,9 @@ NativeFromGeckoAccessible(nsIAccessible *anAccessible) for (; index < totalCount; index++) { nsAccessibleWrap *curAccessible = childrenArray.ElementAt(index); if (curAccessible) { - mozAccessible *curNative = NativeFromGeckoAccessible(curAccessible); + mozAccessible *curNative = GetNativeFromGeckoAccessible(curAccessible); if (curNative) - [mChildren addObject:curNative]; + [mChildren addObject:GetObjectOrRepresentedView(curNative)]; } } @@ -471,7 +491,7 @@ NativeFromGeckoAccessible(nsIAccessible *anAccessible) NSLog (@"%@ received focus!", self); #endif NSAssert1(![self accessibilityIsIgnored], @"trying to set focus to ignored element! (%@)", self); - NSAccessibilityPostNotification([self hasRepresentedView] ? [self representedView] : self, + NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), NSAccessibilityFocusedUIElementChangedNotification); } @@ -511,15 +531,25 @@ NativeFromGeckoAccessible(nsIAccessible *anAccessible) // will check that our children actually reference us as their // parent. -- (void)sanityCheckChildren +- (void)sanityCheckChildren:(NSArray *)children { NSAssert(![self accessibilityIsIgnored], @"can't sanity check children of an ignored accessible!"); - NSEnumerator *iter = [[self children] objectEnumerator]; + NSEnumerator *iter = [children objectEnumerator]; mozAccessible *curObj = nil; - while ((curObj = [iter nextObject])) - NSAssert2([curObj parent] == self, - @"!!! %@ not returning %@ as AXParent, even though it is a AXChild of it!", curObj, self); + 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); + } +} + +- (void)sanityCheckChildren +{ + [self sanityCheckChildren:[self children]]; } - (void)printHierarchy diff --git a/accessible/src/mac/mozAccessibleWrapper.h b/accessible/src/mac/mozAccessibleWrapper.h index d33d262712f..2252689a250 100644 --- a/accessible/src/mac/mozAccessibleWrapper.h +++ b/accessible/src/mac/mozAccessibleWrapper.h @@ -70,4 +70,8 @@ struct AccessibleWrapper { mozAccessible* getNativeObject () { return object; } + + PRBool isIgnored () { + return (PRBool)[object accessibilityIsIgnored]; + } }; diff --git a/accessible/src/mac/mozDocAccessible.mm b/accessible/src/mac/mozDocAccessible.mm index 2283941e520..fecebf56e42 100644 --- a/accessible/src/mac/mozDocAccessible.mm +++ b/accessible/src/mac/mozDocAccessible.mm @@ -80,7 +80,7 @@ static id getNativeViewFromRootAccessible (nsAccessible mParallelView = getNativeViewFromRootAccessible (mGeckoAccessible); - NSAssert(mParallelView, @" can't return root accessible's native parallel view."); + NSAssert(mParallelView, @"can't return root accessible's native parallel view."); return mParallelView; } diff --git a/accessible/src/mac/nsAccessibleWrap.h b/accessible/src/mac/nsAccessibleWrap.h index 407aafcb97a..daaa962c128 100644 --- a/accessible/src/mac/nsAccessibleWrap.h +++ b/accessible/src/mac/nsAccessibleWrap.h @@ -89,11 +89,9 @@ class nsAccessibleWrap : public nsAccessible role == ROLE_AUTOCOMPLETE); } - // if we for some reason have no native accessible, we should be skipped over (and traversed) - // when fetching all unignored children, etc. when counting unignored children, we will not be counted. - PRBool IsIgnored() { - return (mNativeWrapper == nsnull); - } + // ignored means that the accessible might still have children, but is not displayed + // to the user. it also has no native accessible object represented for it. + PRBool IsIgnored(); PRInt32 GetUnignoredChildCount(PRBool aDeepCount); diff --git a/accessible/src/mac/nsAccessibleWrap.mm b/accessible/src/mac/nsAccessibleWrap.mm index 9f1b96fef8e..5df9c674535 100644 --- a/accessible/src/mac/nsAccessibleWrap.mm +++ b/accessible/src/mac/nsAccessibleWrap.mm @@ -188,6 +188,14 @@ nsAccessibleWrap::GetUnignoredChildCount(PRBool aDeepCount) return childCount; } +// if we for some reason have no native accessible, we should be skipped over (and traversed) +// when fetching all unignored children, etc. when counting unignored children, we will not be counted. +PRBool +nsAccessibleWrap::IsIgnored() +{ + return (mNativeWrapper == nsnull) || mNativeWrapper->isIgnored(); +} + void nsAccessibleWrap::GetUnignoredChildren(nsTArray > &aChildrenArray) {