Bug 395081. AccessibleObjectFromPoint() returns errors. r=surkov, a=dsicore
authoraaronleventhal@moonset.net
Tue, 18 Sep 2007 14:44:43 -0700
changeset 6068 9ce7e6a3ece12bd080cc96f55798c5f73abed8c7
parent 6067 6671d1f815c38aa6b760279022f6016628a3fd13
child 6069 f9661d48c53ccc3eb63e79b9dae1747535b06784
push idunknown
push userunknown
push dateunknown
reviewerssurkov, dsicore
bugs395081
milestone1.9a8pre
Bug 395081. AccessibleObjectFromPoint() returns errors. r=surkov, a=dsicore
accessible/public/nsIAccessible.idl
accessible/src/base/nsAccessible.cpp
accessible/src/base/nsBaseWidgetAccessible.h
accessible/src/base/nsOuterDocAccessible.cpp
accessible/src/base/nsOuterDocAccessible.h
accessible/src/html/nsHTMLAreaAccessible.h
--- a/accessible/public/nsIAccessible.idl
+++ b/accessible/public/nsIAccessible.idl
@@ -189,16 +189,20 @@ interface nsIAccessible : nsISupports
    * @param similarItemsInGroup - 1-based, similar to ARIA 'setsize' property
    * @param positionInGroup - 1-based, similar to ARIA 'posinset' property
    */
   void groupPosition(out long aGroupLevel, out long aSimilarItemsInGroup,
                      out long aPositionInGroup);
 
   /**
    * Accessible child which contains the coordinate at (x, y) in screen pixels.
+   * If the point is in the current accessible but not in a child, the
+   * current accessible will be returned.
+   * If the point is in neither the current accessible or a child, then
+   * null will be returned.
    */
   nsIAccessible getChildAtPoint(in long x, in long y);
 
   /**
    * Nth accessible child using zero-based index or last child if index less than zero
    */
   nsIAccessible getChildAt(in long aChildIndex);
 
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -1059,75 +1059,129 @@ NS_IMETHODIMP nsAccessible::GetFocusedCh
   /* nsIAccessible getChildAtPoint (in long x, in long y); */
 NS_IMETHODIMP
 nsAccessible::GetChildAtPoint(PRInt32 aX, PRInt32 aY,
                               nsIAccessible **aAccessible)
 {
   NS_ENSURE_ARG_POINTER(aAccessible);
   *aAccessible = nsnull;
 
+  if (!mDOMNode) {
+    return NS_ERROR_FAILURE;  // Already shut down
+  }
+
+  // If we can't find the point in a child, we will return the fallback answer:
+  // we return |this| if the point is within it, otherwise nsnull
+  nsCOMPtr<nsIAccessible> fallbackAnswer;
+  PRInt32 x, y, width, height;
+  GetBounds(&x, &y, &width, &height);
+  if (aX >= x && aX < x + width &&
+      aY >= y && aY < y + height) {
+    fallbackAnswer = this;
+  }
+  if (MustPrune(this)) {  // Do not dig any further
+    NS_IF_ADDREF(*aAccessible = fallbackAnswer);
+    return NS_OK;
+  }
+
   // Search an accessible at the given point starting from accessible document
   // because containing block (see CSS2) for out of flow element (for example,
   // absolutely positioned element) may be different from its DOM parent and
   // therefore accessible for containing block may be different from accessible
   // for DOM parent but GetFrameForPoint() should be called for containing block
   // to get an out of flow element.
   nsCOMPtr<nsIAccessibleDocument> accDocument;
   nsresult rv = GetAccessibleDocument(getter_AddRefs(accDocument));
   NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!accDocument)
-    return NS_OK;
+  NS_ENSURE_TRUE(accDocument, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsPIAccessNode> accessNodeDocument(do_QueryInterface(accDocument));
   NS_ASSERTION(accessNodeDocument,
                "nsIAccessibleDocument doesn't implement nsPIAccessNode");
 
   nsIFrame *frame = accessNodeDocument->GetFrame();
   NS_ENSURE_STATE(frame);
 
   nsPresContext *presContext = frame->PresContext();
 
   nsIntRect screenRect = frame->GetScreenRectExternal();
   nsPoint offset(presContext->DevPixelsToAppUnits(aX - screenRect.x),
                  presContext->DevPixelsToAppUnits(aY - screenRect.y));
 
   nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
   nsIFrame *foundFrame = presShell->GetFrameForPoint(frame, offset);
-  if (!foundFrame)
+  nsCOMPtr<nsIContent> content;
+  if (!foundFrame || !(content = foundFrame->GetContent())) {
+    NS_IF_ADDREF(*aAccessible = fallbackAnswer);
     return NS_OK;
-
-  nsCOMPtr<nsIContent> content(foundFrame->GetContent());
-  if (!content)
-    return NS_OK;
+  }
 
   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content));
   nsCOMPtr<nsIAccessibilityService> accService = GetAccService();
 
   nsCOMPtr<nsIDOMNode> relevantNode;
   accService->GetRelevantContentNodeFor(node, getter_AddRefs(relevantNode));
-  if (!relevantNode)
+  if (!relevantNode) {
+    NS_IF_ADDREF(*aAccessible = fallbackAnswer);
     return NS_OK;
+  }
 
   nsCOMPtr<nsIAccessible> accessible;
   accService->GetAccessibleFor(relevantNode, getter_AddRefs(accessible));
-  if (!accessible)
-    return NS_OK;
-
-  nsCOMPtr<nsIAccessible> parent;
-  accessible->GetParent(getter_AddRefs(parent));
-
-  while (parent && parent != this) {
-    accessible.swap(parent);
-    accessible->GetParent(getter_AddRefs(parent));
+  if (!accessible) {
+    // No accessible for the node with the point, so find the first
+    // accessible in the DOM parent chain
+    accDocument->GetAccessibleInParentChain(relevantNode,
+                                            getter_AddRefs(accessible));
+    if (!accessible) {
+      NS_IF_ADDREF(*aAccessible = fallbackAnswer);
+      return NS_OK;
+    }
   }
 
-  if (parent)
-    NS_ADDREF(*aAccessible = accessible);
-
+  if (accessible == this) {
+    // Manually walk through accessible children and see if
+    // the are within this point.
+    // This takes care of cases where layout won't walk into
+    // things for us, such as image map areas and sub documents
+    nsCOMPtr<nsIAccessible> child;
+    while (NextChild(child)) {
+      PRInt32 childX, childY, childWidth, childHeight;
+      child->GetBounds(&childX, &childY, &childWidth, &childHeight);
+      if (aX >= childX && aX < childX + childWidth &&
+          aY >= childY && aY < childY + childHeight &&
+          (State(child) & nsIAccessibleStates::STATE_INVISIBLE) == 0) {
+        // Don't walk into offscreen or invisible items
+        NS_IF_ADDREF(*aAccessible = child);
+        return NS_OK;
+      }
+    }
+    // Fall through -- the point is in this accessible but not in a child
+    // We are allowed to return |this| as the answer
+  }
+  else {
+    nsCOMPtr<nsIAccessible> parent;
+    while (PR_TRUE) {
+      accessible->GetParent(getter_AddRefs(parent));
+      if (!parent) {
+        // Reached the top of the hierarchy
+        // these bounds were inside an accessible that is not a descendant of this one
+        NS_IF_ADDREF(*aAccessible = fallbackAnswer);
+        return NS_OK;
+      }
+      if (parent == this) {
+        // We reached |this|, so |accessible| is the
+        // child we want to return
+        break;
+      }
+      accessible.swap(parent);
+    }
+  }
+
+  NS_IF_ADDREF(*aAccessible = accessible);
   return NS_OK;
 }
 
 void nsAccessible::GetBoundsRect(nsRect& aTotalBounds, nsIFrame** aBoundingFrame)
 {
 /*
  * This method is used to determine the bounds of a content node.
  * Because HTML wraps and links are not always rectangular, this
--- a/accessible/src/base/nsBaseWidgetAccessible.h
+++ b/accessible/src/base/nsBaseWidgetAccessible.h
@@ -58,16 +58,18 @@ class nsLeafAccessible : public nsAccess
 {
 public:
   nsLeafAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell);
   NS_DECL_ISUPPORTS_INHERITED
   NS_IMETHOD GetFirstChild(nsIAccessible **_retval);
   NS_IMETHOD GetLastChild(nsIAccessible **_retval);
   NS_IMETHOD GetChildCount(PRInt32 *_retval);
   NS_IMETHOD GetAllowsAnonChildAccessibles(PRBool *aAllowsAnonChildren);
+  NS_IMETHOD GetChildAtPoint(PRInt32 aX, PRInt32 aY, nsIAccessible **aAccessible)
+    { *aAccessible = this; return NS_OK; } // Don't walk into these
 };
 
 /**
   * A type of accessible for DOM nodes containing an href="" attribute.
   *  It knows how to report the state of the link ( traveled or not )
   *  and can activate ( click ) the link programmatically.
   */
 class nsLinkableAccessible : public nsHyperTextAccessibleWrap
--- a/accessible/src/base/nsOuterDocAccessible.cpp
+++ b/accessible/src/base/nsOuterDocAccessible.cpp
@@ -81,16 +81,34 @@ NS_IMETHODIMP nsOuterDocAccessible::GetR
 NS_IMETHODIMP
 nsOuterDocAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState)
 {
   nsAccessible::GetState(aState, aExtraState);
   *aState &= ~nsIAccessibleStates::STATE_FOCUSABLE;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsOuterDocAccessible::GetChildAtPoint(PRInt32 aX, PRInt32 aY,
+                                      nsIAccessible **aAccessible)
+{
+  NS_ENSURE_ARG_POINTER(aAccessible);
+  *aAccessible = nsnull;
+  if (!mDOMNode) {
+    return NS_ERROR_FAILURE;
+  }
+  PRInt32 docX, docY, docWidth, docHeight;
+  GetBounds(&docX, &docY, &docWidth, &docHeight);
+  if (aX < docX || aX >= docX + docWidth || aY < docY || aY >= docY + docHeight) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return GetFirstChild(aAccessible);  // Always return the inner doc unless bounds outside of it
+}
+
 void nsOuterDocAccessible::CacheChildren()
 {  
   // An outer doc accessible usually has 1 nsDocAccessible child,
   // but could have none if we can't get to the inner documnet
   if (!mWeakShell) {
     mAccChildCount = eChildCountUninitialized;
     return;   // This outer doc node has been shut down
   }
--- a/accessible/src/base/nsOuterDocAccessible.h
+++ b/accessible/src/base/nsOuterDocAccessible.h
@@ -50,12 +50,14 @@ class nsOuterDocAccessible : public nsAc
 
   public:
     nsOuterDocAccessible(nsIDOMNode* aNode, 
                          nsIWeakReference* aShell);
 
     NS_IMETHOD GetName(nsAString& aName);
     NS_IMETHOD GetRole(PRUint32 *aRole);
     NS_IMETHOD GetState(PRUint32 *aState, PRUint32 *aExtraState);
+    NS_IMETHOD GetChildAtPoint(PRInt32 aX, PRInt32 aY,
+                               nsIAccessible **aAccessible);
     void CacheChildren();
 };
 
 #endif  
--- a/accessible/src/html/nsHTMLAreaAccessible.h
+++ b/accessible/src/html/nsHTMLAreaAccessible.h
@@ -51,11 +51,13 @@ public:
   nsHTMLAreaAccessible(nsIDOMNode *domNode, nsIAccessible *accParent, nsIWeakReference* aShell);
   NS_IMETHOD GetName(nsAString & _retval); 
   NS_IMETHOD GetRole(PRUint32 *_retval); 
   NS_IMETHOD GetFirstChild(nsIAccessible **_retval);
   NS_IMETHOD GetLastChild(nsIAccessible **_retval);
   NS_IMETHOD GetChildCount(PRInt32 *_retval);
   NS_IMETHOD GetDescription(nsAString& _retval);
   NS_IMETHOD GetBounds(PRInt32 *x, PRInt32 *y, PRInt32 *width, PRInt32 *height);
+  NS_IMETHOD GetChildAtPoint(PRInt32 aX, PRInt32 aY, nsIAccessible **aAccessible)
+    { *aAccessible = this; return NS_OK; } // Don't walk into these
 };
 
 #endif