Bug 396005. Severe performance degredation regression when a11y active. r=surkov, a=dsicore
authoraaronleventhal@moonset.net
Mon, 01 Oct 2007 11:27:13 -0700
changeset 6486 a1d66b1f4bc05dd4da2ac4dcbf72dc995eca33be
parent 6485 271678cd7e3be708e848ef45677d1a3b3a11ba1d
child 6487 c82bc2d6d3d03687f1efafc322c07b0785ff2409
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssurkov, dsicore
bugs396005
milestone1.9a9pre
Bug 396005. Severe performance degredation regression when a11y active. r=surkov, a=dsicore
accessible/public/nsIAccessibleDocument.idl
accessible/src/base/nsAccessible.cpp
accessible/src/base/nsDocAccessible.cpp
--- a/accessible/public/nsIAccessibleDocument.idl
+++ b/accessible/public/nsIAccessibleDocument.idl
@@ -53,17 +53,17 @@ interface nsIDOMWindow;
  * You can QueryInterface to nsIAccessibleDocument from
  * the nsIAccessible or nsIAccessNode for the root node
  * of a document. You can also get one from 
  * nsIAccessNode::GetAccessibleDocument() or 
  * nsIAccessibleEvent::GetAccessibleDocument()
  *
  * @status UNDER_REVIEW
  */
-[scriptable, uuid(d118c0e9-b5e7-4671-854a-65b4713d9552)]
+[scriptable, uuid(81ddd75f-adbd-4a1c-b87c-6522bcea0596)]
 interface nsIAccessibleDocument : nsISupports
 {
   /**
    * The URL of the document
    */
   readonly attribute AString URL;
 
   /**
@@ -115,17 +115,18 @@ interface nsIAccessibleDocument : nsISup
    * Returns the first accessible parent of a DOM node.
    * Guaranteed not to return nsnull if the DOM node is in a document.
    * @param aDOMNode The DOM node we need an accessible for.
    * @param aCanCreate Can accessibles be created or must it be the first 
    *                   cached accessible in the parent chain?
    * @return An first nsIAccessible found by crawling up the DOM node
    *         to the document root.
    */
-  nsIAccessible getAccessibleInParentChain(in nsIDOMNode aDOMNode);
+  nsIAccessible getAccessibleInParentChain(in nsIDOMNode aDOMNode,
+                                           in boolean aCanCreate);
 
   /**
    * A bit flag representing the type of ARIA properties which should be
    * checked in this document:
    * either eUnknownPropType, eCheckNamespaced, eCheckHyphenated or eCheckAny
    */
   readonly attribute unsigned long ariaPropTypes;
   
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -147,16 +147,22 @@ NS_IMPL_RELEASE_INHERITED(nsAccessible, 
  * static
  * help method. to detect whether this accessible object implements
  * nsIAccessibleText, when it is text or has text child node.
  */
 PRBool nsAccessible::IsTextInterfaceSupportCorrect(nsIAccessible *aAccessible)
 {
   PRBool foundText = PR_FALSE;
 
+  nsCOMPtr<nsIAccessibleDocument> accDoc = do_QueryInterface(aAccessible);
+  if (accDoc) {
+    // Don't test for accessible docs, it makes us create accessibles too
+    // early and fire mutation events before we need to
+    return PR_TRUE;
+  }
   nsCOMPtr<nsIAccessible> child, nextSibling;
   aAccessible->GetFirstChild(getter_AddRefs(child));
   while (child) {
     if (IsText(child)) {
       foundText = PR_TRUE;
       break;
     }
     child->GetNextSibling(getter_AddRefs(nextSibling));
@@ -579,17 +585,17 @@ NS_IMETHODIMP nsAccessible::GetParent(ns
   nsresult rv = GetCachedParent(aParent);
   if (NS_FAILED(rv) || *aParent) {
     return rv;
   }
 
   nsCOMPtr<nsIAccessibleDocument> docAccessible(GetDocAccessible());
   NS_ENSURE_TRUE(docAccessible, NS_ERROR_FAILURE);
 
-  return docAccessible->GetAccessibleInParentChain(mDOMNode, aParent);
+  return docAccessible->GetAccessibleInParentChain(mDOMNode, PR_TRUE, aParent);
 }
 
 NS_IMETHODIMP nsAccessible::GetCachedParent(nsIAccessible **  aParent)
 {
   *aParent = nsnull;
   if (!mWeakShell) {
     // This node has been shut down
     return NS_ERROR_FAILURE;
@@ -1157,17 +1163,17 @@ nsAccessible::GetChildAtPoint(PRInt32 aX
     return NS_OK;
   }
 
   nsCOMPtr<nsIAccessible> accessible;
   accService->GetAccessibleFor(relevantNode, getter_AddRefs(accessible));
   if (!accessible) {
     // No accessible for the node with the point, so find the first
     // accessible in the DOM parent chain
-    accDocument->GetAccessibleInParentChain(relevantNode,
+    accDocument->GetAccessibleInParentChain(relevantNode, PR_TRUE,
                                             getter_AddRefs(accessible));
     if (!accessible) {
       NS_IF_ADDREF(*aAccessible = fallbackAnswer);
       return NS_OK;
     }
   }
 
   if (accessible == this) {
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -1296,17 +1296,17 @@ nsDocAccessible::FireTextChangeEventForT
     return;
   }
 
   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aContent));
   if (!node)
     return;
 
   nsCOMPtr<nsIAccessible> accessible;
-  nsresult rv = GetAccessibleInParentChain(node, getter_AddRefs(accessible));
+  nsresult rv = GetAccessibleInParentChain(node, PR_TRUE, getter_AddRefs(accessible));
   if (NS_FAILED(rv) || !accessible)
     return;
 
   nsRefPtr<nsHyperTextAccessible> textAccessible;
   rv = accessible->QueryInterface(NS_GET_IID(nsHyperTextAccessible),
                                   getter_AddRefs(textAccessible));
   if (NS_FAILED(rv) || !textAccessible)
     return;
@@ -1562,17 +1562,17 @@ NS_IMETHODIMP nsDocAccessible::FlushPend
       // When a node is being made visible or is inserted, the text in an ancestor hyper text will gain characters
       // At this point we now have the frame and accessible for this node if there is one. That is why we
       // wait to fire this here, instead of in InvalidateCacheSubtree(), where we wouldn't be able to calculate
       // the offset, length and text for the text change.
       nsCOMPtr<nsIDOMNode> domNode;
       accessibleEvent->GetDOMNode(getter_AddRefs(domNode));
       if (domNode && domNode != mDOMNode) {
         if (!containerAccessible)
-          GetAccessibleInParentChain(domNode,
+          GetAccessibleInParentChain(domNode, PR_TRUE,
                                      getter_AddRefs(containerAccessible));
 
         nsCOMPtr<nsIAccessibleTextChangeEvent> textChangeEvent =
           CreateTextChangeEventForNode(containerAccessible, domNode, accessible, PR_TRUE, PR_TRUE);
         if (textChangeEvent) {
           PRBool isFromUserInput;
           accessibleEvent->GetIsFromUserInput(&isFromUserInput);
           nsCOMPtr<nsIDOMNode> hyperTextNode;
@@ -1752,25 +1752,49 @@ NS_IMETHODIMP nsDocAccessible::Invalidat
   // instead of just the accessible tree, although that would be faster
   // Otherwise we might miss the nsAccessNode's that are not nsAccessible's.
 
   NS_ENSURE_TRUE(mDOMNode, NS_ERROR_FAILURE);
   nsCOMPtr<nsIDOMNode> childNode = aChild ? do_QueryInterface(aChild) : mDOMNode;
   if (!IsNodeRelevant(childNode)) {
     return NS_OK;  // Don't fire event unless it can be for an attached accessible
   }
-  if (!mIsContentLoaded && mAccessNodeCache.Count() <= 1) {
-    // Still loading and no accessibles has yet been created other than this
-    // doc accessible. In this case we optimize
-    // by not firing SHOW/HIDE/REORDER events for every document mutation
-    // caused by page load, since AT is not going to want to grab the
-    // document and listen to these changes until after the page is first loaded
-    // Leave early, and ensure mAccChildCount stays uninitialized instead of 0,
-    // which it is if anyone asks for its children right now.
-    return InvalidateChildren();
+  if (!mIsContentLoaded) {
+    // Still loading document
+    if (mAccessNodeCache.Count() <= 1) {
+      // Still loading and no accessibles has yet been created other than this
+      // doc accessible. In this case we optimize
+      // by not firing SHOW/HIDE/REORDER events for every document mutation
+      // caused by page load, since AT is not going to want to grab the
+      // document and listen to these changes until after the page is first loaded
+      // Leave early, and ensure mAccChildCount stays uninitialized instead of 0,
+      // which it is if anyone asks for its children right now.
+      return InvalidateChildren();
+    }
+    if (aChangeEventType == nsIAccessibleEvent::EVENT_DOM_CREATE) {
+      nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+      NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+      nsIEventStateManager *esm = presShell->GetPresContext()->EventStateManager();
+      NS_ENSURE_TRUE(esm, NS_ERROR_FAILURE);
+      if (!esm->IsHandlingUserInputExternal()) {
+        // Adding content during page load, but not caused by user input
+        // Just invalidate accessible hierarchy and return,
+        // otherwise the page load time slows down way too much
+        nsCOMPtr<nsIAccessible> containerAccessible;
+        GetAccessibleInParentChain(childNode, PR_FALSE, getter_AddRefs(containerAccessible));
+        if (!containerAccessible) {
+          containerAccessible = this;
+        }
+        nsCOMPtr<nsPIAccessible> privateContainer = do_QueryInterface(containerAccessible);
+        return privateContainer->InvalidateChildren();
+      }     
+      // else: user input, so we must fall through and for full handling,
+      // e.g. fire the mutation events. Note: user input could cause DOM_CREATE
+      // during page load if user typed into an input field or contentEditable area
+    }
   }
 
   // Update last change state information
   nsCOMPtr<nsIAccessNode> childAccessNode;
   GetCachedAccessNode(childNode, getter_AddRefs(childAccessNode));
   nsCOMPtr<nsIAccessible> childAccessible = do_QueryInterface(childAccessNode);
   if (!childAccessible && !isHiding) {
     // If not about to hide it, make sure there's an accessible so we can fire an
@@ -1798,17 +1822,17 @@ NS_IMETHODIMP nsDocAccessible::Invalidat
     printf("[Destroy  %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
   }
   else if (aChangeEventType == nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE) {
     printf("[Type change %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
   }
 #endif
 
   nsCOMPtr<nsIAccessible> containerAccessible;
-  GetAccessibleInParentChain(childNode, getter_AddRefs(containerAccessible));
+  GetAccessibleInParentChain(childNode, PR_TRUE, getter_AddRefs(containerAccessible));
   if (!containerAccessible) {
     containerAccessible = this;
   }
 
   if (!isShowing) {
     // Fire EVENT_ASYNCH_HIDE or EVENT_DOM_DESTROY if previous accessible existed for node being hidden.
     // Fire this before the accessible goes away.
     if (childAccessible) {
@@ -1883,16 +1907,17 @@ NS_IMETHODIMP nsDocAccessible::Invalidat
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocAccessible::GetAccessibleInParentChain(nsIDOMNode *aNode,
+                                            PRBool aCanCreate,
                                             nsIAccessible **aAccessible)
 {
   // Find accessible in parent chain of DOM nodes, or return null
   *aAccessible = nsnull;
   nsCOMPtr<nsIDOMNode> currentNode(aNode), parentNode;
   nsCOMPtr<nsIAccessNode> accessNode;
 
   nsIAccessibilityService *accService = GetAccService();
@@ -1906,18 +1931,26 @@ nsDocAccessible::GetAccessibleInParentCh
       *aAccessible = this;
       break;
     }
 
     nsCOMPtr<nsIDOMNode> relevantNode;
     if (NS_SUCCEEDED(accService->GetRelevantContentNodeFor(currentNode, getter_AddRefs(relevantNode))) && relevantNode) {
       currentNode = relevantNode;
     }
-
-    accService->GetAccessibleInWeakShell(currentNode, mWeakShell, aAccessible);
+    if (aCanCreate) {
+      accService->GetAccessibleInWeakShell(currentNode, mWeakShell, aAccessible);
+    }
+    else { // Only return cached accessibles, don't create anything
+      nsCOMPtr<nsIAccessNode> accessNode;
+      GetCachedAccessNode(currentNode, getter_AddRefs(accessNode)); // AddRefs
+      if (accessNode) {
+        CallQueryInterface(accessNode, aAccessible); // AddRefs
+      }
+    }
   } while (!*aAccessible);
 
   return NS_OK;
 }
 
 void nsDocAccessible::DocLoadCallback(nsITimer *aTimer, void *aClosure)
 {
   // Doc has finished loading, fire "load finished" event