Bug 385070. Accessible subtrees for anonymous content need to be shut down. Patch by Evan Yan. r=aaronlev, r+sr=roc for layout part, a=dsicor
authoraaronleventhal@moonset.net
Tue, 20 Nov 2007 12:39:36 -0800
changeset 8231 20c46a02ec9203cfefc280b8aed9593ca0654713
parent 8230 0803ba216f221c176688f827b60c7872c3a0a57a
child 8232 89a257d2b1c9c367ef097982c6f38bd46f26a5cf
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherderautoland@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaaronlev, r, dsicor
bugs385070
milestone1.9b2pre
Bug 385070. Accessible subtrees for anonymous content need to be shut down. Patch by Evan Yan. r=aaronlev, r+sr=roc for layout part, a=dsicor
accessible/src/base/nsAccessNode.cpp
accessible/src/base/nsDocAccessible.cpp
layout/forms/nsFileControlFrame.cpp
--- a/accessible/src/base/nsAccessNode.cpp
+++ b/accessible/src/base/nsAccessNode.cpp
@@ -170,22 +170,39 @@ NS_IMETHODIMP nsAccessNode::Init()
         }
       }
     }
     NS_ASSERTION(docAccessible, "Cannot cache new nsAccessNode");
     if (!docAccessible) {
       return NS_ERROR_FAILURE;
     }
   }
+
   void* uniqueID;
   GetUniqueID(&uniqueID);
   nsCOMPtr<nsPIAccessibleDocument> privateDocAccessible =
     do_QueryInterface(docAccessible);
   NS_ASSERTION(privateDocAccessible, "No private docaccessible for docaccessible");
   privateDocAccessible->CacheAccessNode(uniqueID, this);
+
+  // Make sure an ancestor in real content is cached
+  // so that nsDocAccessible::RefreshNodes() can find the anonymous subtree to release when
+  // the root node goes away
+  nsCOMPtr<nsIContent> content = do_QueryInterface(mDOMNode);
+  if (content && (content->IsNativeAnonymous() ||
+                  content->GetBindingParent())) {
+    // Specific examples of where this is used: <input type="file"> and <xul:findbar>
+    nsCOMPtr<nsIAccessible> parentAccessible;
+    docAccessible->GetAccessibleInParentChain(mDOMNode, PR_TRUE, getter_AddRefs(parentAccessible));
+    if (parentAccessible) {
+      PRInt32 childCountUnused;
+      parentAccessible->GetChildCount(&childCountUnused);
+    }
+  }
+
 #ifdef DEBUG_A11Y
   mIsInitialized = PR_TRUE;
 #endif
 
   return NS_OK;
 }
 
 
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -1630,24 +1630,16 @@ NS_IMETHODIMP nsDocAccessible::FlushPend
       else {
         // The input state was previously stored with the nsIAccessibleEvent,
         // so use that state now when firing the event
         nsAccEvent::PrepareForEvent(accessibleEvent);
         FireAccessibleEvent(accessibleEvent);
         // Post event processing
         if (eventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
             eventType == nsIAccessibleEvent::EVENT_DOM_DESTROY) {
-          // Invalidate children
-          nsCOMPtr<nsIAccessible> containerAccessible;
-          accessible->GetParent(getter_AddRefs(containerAccessible));
-          nsCOMPtr<nsPIAccessible> privateContainerAccessible =
-            do_QueryInterface(containerAccessible);
-          if (privateContainerAccessible) {
-            privateContainerAccessible->InvalidateChildren();
-          }
           // Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in this subtree
           nsCOMPtr<nsIDOMNode> hidingNode;
           accessibleEvent->GetDOMNode(getter_AddRefs(hidingNode));
           if (hidingNode) {
             RefreshNodes(hidingNode); // Will this bite us with asynch events
           }
         }
       }
@@ -1661,81 +1653,88 @@ void nsDocAccessible::FlushEventsCallbac
 {
   nsPIAccessibleDocument *accessibleDoc = static_cast<nsPIAccessibleDocument*>(aClosure);
   NS_ASSERTION(accessibleDoc, "How did we get here without an accessible document?");
   accessibleDoc->FlushPendingEvents();
 }
 
 void nsDocAccessible::RefreshNodes(nsIDOMNode *aStartNode)
 {
-  nsCOMPtr<nsIDOMNode> iterNode(aStartNode), nextNode;
   nsCOMPtr<nsIAccessNode> accessNode;
+  GetCachedAccessNode(aStartNode, getter_AddRefs(accessNode));
+  nsCOMPtr<nsIDOMNode> nextNode, iterNode;
 
-  do {
-    GetCachedAccessNode(iterNode, getter_AddRefs(accessNode));
-    if (accessNode) {
-      // Accessibles that implement their own subtrees,
-      // like html combo boxes and xul trees must shutdown all of their own
-      // children when they override Shutdown()
-
-      // Don't shutdown our doc object!
-      if (accessNode != static_cast<nsIAccessNode*>(this)) {
+  // Shut down accessible subtree, which may have been created for
+  // anonymous content subtree
+  nsCOMPtr<nsIAccessible> accessible(do_QueryInterface(accessNode));
+  if (accessible) {
+    nsCOMPtr<nsPIAccessible> privateAccessible = do_QueryInterface(accessible);
+    NS_ASSERTION(privateAccessible, "No nsPIAccessible for nsIAccessible");
 
-        nsCOMPtr<nsIAccessible> accessible(do_QueryInterface(accessNode));
-        if (accessible) {
-          // Fire menupopupend events for menu popups that go away
-          PRUint32 role = Role(accessible);
-          if (role == nsIAccessibleRole::ROLE_MENUPOPUP) {
-            nsCOMPtr<nsIDOMNode> domNode;
-            accessNode->GetDOMNode(getter_AddRefs(domNode));
-            nsCOMPtr<nsIDOMXULPopupElement> popup(do_QueryInterface(domNode));
-            if (!popup) {
-              // Popup elements already fire these via DOMMenuInactive
-              // handling in nsRootAccessible::HandleEvent
-              nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
-                                       accessible);
-            }
-          }
+    nsCOMPtr<nsIAccessible> childAccessible;
+    // we only need to shutdown the accessibles here if one of them has been created
+    privateAccessible->GetCachedFirstChild(getter_AddRefs(childAccessible));
+    if (childAccessible) {
+      nsCOMPtr<nsIArray> children;
+      // use GetChildren() to fetch children at one time, instead of using
+      // GetNextSibling(), because after we shutdown the first child,
+      // mNextSibling will be set null.
+      accessible->GetChildren(getter_AddRefs(children));
+
+      PRUint32 childCount;
+      children->GetLength(&childCount);
+      for (PRUint32 index = 0; index < childCount; index++) {
+        nsCOMPtr<nsIAccessNode> childAccessNode;
+        children->QueryElementAt(index, NS_GET_IID(nsIAccessNode),
+                                 getter_AddRefs(childAccessNode));
+        childAccessNode->GetDOMNode(getter_AddRefs(iterNode));
+        nsCOMPtr<nsIContent> iterContent = do_QueryInterface(iterNode);
+        if (iterContent && (iterContent->IsNativeAnonymous() ||
+                            iterContent->GetBindingParent())) {
+          // GetBindingParent() check is a perf win -- make sure we don't
+          // shut down the same subtree twice since we'll reach non-anon content via
+          // DOM traversal later in this method
+          RefreshNodes(iterNode);
         }
-
-        void *uniqueID;
-        accessNode->GetUniqueID(&uniqueID);
-        nsCOMPtr<nsPIAccessNode> privateAccessNode(do_QueryInterface(accessNode));
-        privateAccessNode->Shutdown();
-        // Remove from hash table as well
-        mAccessNodeCache.Remove(uniqueID);
       }
     }
 
-    iterNode->GetFirstChild(getter_AddRefs(nextNode));
-    if (nextNode) {
-      iterNode = nextNode;
-      continue;
+    // Shutdown ordinary content subtree as well -- there may be
+    // access node children which are not full accessible objects
+    aStartNode->GetFirstChild(getter_AddRefs(nextNode));
+    while (nextNode) {
+      nextNode.swap(iterNode);
+      RefreshNodes(iterNode);
+      iterNode->GetNextSibling(getter_AddRefs(nextNode));
     }
 
-    if (iterNode == aStartNode)
-      break;
-    iterNode->GetNextSibling(getter_AddRefs(nextNode));
-    if (nextNode) {
-      iterNode = nextNode;
-      continue;
+    // Don't shutdown our doc object!
+    if (accessNode && accessNode != static_cast<nsIAccessNode*>(this)) {
+      // Fire menupopup end if a menu goes away
+      PRUint32 role = Role(accessible);
+      if (role == nsIAccessibleRole::ROLE_MENUPOPUP) {
+        nsCOMPtr<nsIDOMNode> domNode;
+        accessNode->GetDOMNode(getter_AddRefs(domNode));
+        nsCOMPtr<nsIDOMXULPopupElement> popup(do_QueryInterface(domNode));
+        if (!popup) {
+          // Popup elements already fire these via DOMMenuInactive
+          // handling in nsRootAccessible::HandleEvent
+          nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
+                                   accessible);
+        }
+      }
+      // Shut down the actual accessible or access node
+      void *uniqueID;
+      accessNode->GetUniqueID(&uniqueID);
+      nsCOMPtr<nsPIAccessNode> privateAccessNode(do_QueryInterface(accessNode));
+      privateAccessNode->Shutdown();
+      // Remove from hash table as well
+      mAccessNodeCache.Remove(uniqueID);
     }
-
-    do {
-      iterNode->GetParentNode(getter_AddRefs(nextNode));
-      if (!nextNode || nextNode == aStartNode) {
-        return;
-      }
-      nextNode->GetNextSibling(getter_AddRefs(iterNode));
-      if (iterNode)
-        break;
-      iterNode = nextNode;
-    } while (PR_TRUE);
   }
-  while (iterNode && iterNode != aStartNode);
 }
 
 NS_IMETHODIMP nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild,
                                                       PRUint32 aChangeEventType)
 {
   PRBool isHiding = 
     aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
     aChangeEventType == nsIAccessibleEvent::EVENT_DOM_DESTROY;
--- a/layout/forms/nsFileControlFrame.cpp
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -598,18 +598,23 @@ nsFileControlFrame::BuildDisplayList(nsD
   }
 
   return DisplaySelectionOverlay(aBuilder, aLists);
 }
 
 #ifdef ACCESSIBILITY
 NS_IMETHODIMP nsFileControlFrame::GetAccessible(nsIAccessible** aAccessible)
 {
-  // No accessible object for file control, only for child text frame and button
-  *aAccessible = nsnull;
+  // Accessible object exists just to hold onto its children, for later shutdown
+  nsCOMPtr<nsIAccessibilityService> accService = do_GetService("@mozilla.org/accessibilityService;1");
+
+  if (accService) {
+    return accService->CreateHTMLGenericAccessible(static_cast<nsIFrame*>(this), aAccessible);
+  }
+
   return NS_ERROR_FAILURE;
 }
 #endif
 
 ////////////////////////////////////////////////////////////
 // Mouse listener implementation
 
 NS_IMPL_ISUPPORTS1(nsFileControlFrame::MouseListener, nsIDOMMouseListener)