Bug 573469 - part3, speed up HasRelatedContent, r=davidb, sr=neil, a=final+
authorAlexander Surkov <surkov.alexander@gmail.com>
Fri, 19 Nov 2010 13:44:47 +0800
changeset 57890 e3d3bcffefa7de908c6e1015ae137f5a94d51390
parent 57889 dc494e0db336ce376b47a56a87e6dfc19ceeeed7
child 57891 6957f4aa855f8906c4534964febbe7d28908cfd8
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersdavidb, neil, final
bugs573469
milestone2.0b8pre
Bug 573469 - part3, speed up HasRelatedContent, r=davidb, sr=neil, a=final+
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsAccessible.cpp
accessible/src/base/nsCoreUtils.cpp
accessible/src/base/nsCoreUtils.h
accessible/src/base/nsDocAccessible.cpp
accessible/src/base/nsDocAccessible.h
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -839,25 +839,21 @@ nsAccessibilityService::GetCachedAccessi
 
 static PRBool HasRelatedContent(nsIContent *aContent)
 {
   nsAutoString id;
   if (!aContent || !nsCoreUtils::GetID(aContent, id) || id.IsEmpty()) {
     return PR_FALSE;
   }
 
-  nsIAtom *relationAttrs[] = {nsAccessibilityAtoms::aria_labelledby,
-                              nsAccessibilityAtoms::aria_describedby,
-                              nsAccessibilityAtoms::aria_owns,
-                              nsAccessibilityAtoms::aria_controls,
-                              nsAccessibilityAtoms::aria_flowto};
-  if (nsCoreUtils::FindNeighbourPointingToNode(aContent, relationAttrs,
-                                               NS_ARRAY_LENGTH(relationAttrs))) {
+  // If the given ID is referred by relation attribute then create an accessible
+  // for it. Take care of HTML elements only for now.
+  if (aContent->IsHTML() &&
+      nsAccUtils::GetDocAccessibleFor(aContent)->IsDependentID(id))
     return PR_TRUE;
-  }
 
   nsIContent *ancestorContent = aContent;
   while ((ancestorContent = ancestorContent->GetParent()) != nsnull) {
     if (ancestorContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_activedescendant)) {
         // ancestor has activedescendant property, this content could be active
       return PR_TRUE;
     }
   }
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -3185,18 +3185,27 @@ nsAccessible::EnsureChildren()
     return PR_TRUE;
   }
 
   if (mChildrenFlags != eChildrenUninitialized)
     return PR_FALSE;
 
   // State is embedded children until text leaf accessible is appended.
   mChildrenFlags = eEmbeddedChildren; // Prevent reentry
+
+  // Notify the document about caching status.
+  nsDocAccessible* document = GetDocAccessible();
+  if (document)
+    document->NotifyOfCachingStart(this);
+
   CacheChildren();
 
+  if (document)
+    document->NotifyOfCachingEnd(this);
+
   return PR_FALSE;
 }
 
 nsAccessible*
 nsAccessible::GetSiblingAtOffset(PRInt32 aOffset, nsresult* aError)
 {
   if (IsDefunct()) {
     if (aError)
--- a/accessible/src/base/nsCoreUtils.cpp
+++ b/accessible/src/base/nsCoreUtils.cpp
@@ -1147,30 +1147,34 @@ IDRefsIterator::NextID()
 nsIContent*
 IDRefsIterator::NextElem()
 {
   while (true) {
     const nsDependentSubstring id = NextID();
     if (id.IsEmpty())
       break;
 
-    if (mXBLDocument) {
-      // If content is anonymous subtree then use "anonid" attribute to get
-      // elements, otherwise search elements in DOM by ID attribute.
-
-      nsCOMPtr<nsIDOMElement> refElm;
-      mXBLDocument->GetAnonymousElementByAttribute(mBindingParent,
-                                                   NS_LITERAL_STRING("anonid"),
-                                                   id,
-                                                   getter_AddRefs(refElm));
-      nsCOMPtr<nsIContent> refContent = do_QueryInterface(refElm);
-      if (refContent)
-        return refContent;
-
-    } else {
-      nsIContent* refContent = mDocument->GetElementById(id);
-      if (refContent)
-        return refContent;
-    }
+    nsIContent* refContent = GetElem(id);
+    if (refContent)
+      return refContent;
   }
 
   return nsnull;
 }
+
+nsIContent*
+IDRefsIterator::GetElem(const nsDependentSubstring& aID)
+{
+  if (mXBLDocument) {
+    // If content is anonymous subtree then use "anonid" attribute to get
+    // elements, otherwise search elements in DOM by ID attribute.
+
+    nsCOMPtr<nsIDOMElement> refElm;
+    mXBLDocument->GetAnonymousElementByAttribute(mBindingParent,
+                                                 NS_LITERAL_STRING("anonid"),
+                                                 aID,
+                                                 getter_AddRefs(refElm));
+    nsCOMPtr<nsIContent> refContent = do_QueryInterface(refElm);
+    return refContent;
+  }
+
+  return mDocument->GetElementById(aID);
+}
--- a/accessible/src/base/nsCoreUtils.h
+++ b/accessible/src/base/nsCoreUtils.h
@@ -520,16 +520,21 @@ public:
    */
   const nsDependentSubstring NextID();
 
   /**
    * Return next element.
    */
   nsIContent* NextElem();
 
+  /**
+   * Return the element with the given ID.
+   */
+  nsIContent* GetElem(const nsDependentSubstring& aID);
+
 private:
   nsString mIDs;
   nsAString::index_type mCurrIdx;
 
   nsIDocument* mDocument;
   nsCOMPtr<nsIDOMDocumentXBL> mXBLDocument;
   nsCOMPtr<nsIDOMElement> mBindingParent;
 };
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -98,17 +98,18 @@ static const PRUint32 kRelationAttrsLen 
 
 ////////////////////////////////////////////////////////////////////////////////
 // Constructor/desctructor
 
 nsDocAccessible::
   nsDocAccessible(nsIDocument *aDocument, nsIContent *aRootContent,
                   nsIWeakReference *aShell) :
   nsHyperTextAccessibleWrap(aRootContent, aShell),
-  mDocument(aDocument), mScrollPositionChangedTicks(0), mIsLoaded(PR_FALSE)
+  mDocument(aDocument), mScrollPositionChangedTicks(0), mIsLoaded(PR_FALSE),
+  mCacheRoot(nsnull), mIsPostCacheProcessing(PR_FALSE)
 {
   mDependentIDsHash.Init();
   // XXX aaronl should we use an algorithm for the initial cache size?
   mAccessibleCache.Init(kDefaultCacheSize);
   mNodeToAccessibleMap.Init(kDefaultCacheSize);
 
   // For GTK+ native window, we do nothing here.
   if (!mDocument)
@@ -1583,16 +1584,49 @@ nsDocAccessible::RecreateAccessible(nsIN
       new AccEvent(nsIAccessibleEvent::EVENT_REORDER, parent->GetNode(),
                    eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
 
     if (reorderEvent)
       FireDelayedAccessibleEvent(reorderEvent);
   }
 }
 
+void
+nsDocAccessible::NotifyOfCachingStart(nsAccessible* aAccessible)
+{
+  if (!mCacheRoot)
+    mCacheRoot = aAccessible;
+}
+
+void
+nsDocAccessible::NotifyOfCachingEnd(nsAccessible* aAccessible)
+{
+  if (mCacheRoot == aAccessible && !mIsPostCacheProcessing) {
+    // Allow invalidation list insertions while container children are recached.
+    mIsPostCacheProcessing = PR_TRUE;
+
+    // Invalidate children of container accessible for each element in
+    // invalidation list.
+    for (PRUint32 idx = 0; idx < mInvalidationList.Length(); idx++) {
+      nsIContent* content = mInvalidationList[idx];
+      nsAccessible* container =
+        GetAccService()->GetCachedContainerAccessible(content);
+      container->InvalidateChildren();
+
+      // Make sure we keep children updated. While we're inside of caching loop
+      // then we must exist it with cached children.
+      container->EnsureChildren();
+    }
+    mInvalidationList.Clear();
+
+    mCacheRoot = nsnull;
+    mIsPostCacheProcessing = PR_FALSE;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Protected members
 
 void
 nsDocAccessible::AddDependentIDsFor(nsAccessible* aRelProvider,
                                     nsIAtom* aRelAttr)
 {
   for (PRUint32 idx = 0; idx < kRelationAttrsLen; idx++) {
@@ -1615,18 +1649,28 @@ nsDocAccessible::AddDependentIDsFor(nsAc
             providers = nsnull;
           }
         }
       }
 
       if (providers) {
         AttrRelProvider* provider =
           new AttrRelProvider(relAttr, aRelProvider->GetContent());
-        if (provider)
+        if (provider) {
           providers->AppendElement(provider);
+
+          // We've got here during the children caching. If the referenced
+          // content is not accessible then store it to pend its container
+          // children invalidation (this happens immediately after the caching
+          // is finished).
+          nsIContent* dependentContent = iter.GetElem(id);
+          if (dependentContent && !GetCachedAccessible(dependentContent)) {
+            mInvalidationList.AppendElement(dependentContent);
+          }
+        }
       }
     }
 
     // If the relation attribute is given then we don't have anything else to
     // check.
     if (aRelAttr)
       break;
   }
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -208,16 +208,26 @@ public:
 
   /**
    * Return the cached accessible by the given unique ID looking through
    * this and nested documents.
    */
   nsAccessible* GetCachedAccessibleByUniqueIDInSubtree(void* aUniqueID);
 
   /**
+   * Return true if the given ID is referred by relation attribute.
+   *
+   * @note Different elements may share the same ID if they are hosted inside
+   *       XBL bindings. Be careful the result of this method may be  senseless
+   *       while it's called for XUL elements (where XBL is used widely).
+   */
+  PRBool IsDependentID(const nsAString& aID) const
+    { return mDependentIDsHash.Get(aID, nsnull); }
+
+  /**
    * Initialize the newly created accessible and put it into document caches.
    *
    * @param  aAccessible    [in] created accessible
    * @param  aRoleMapEntry  [in] the role map entry role the ARIA role or nsnull
    *                          if none
    */
   bool BindToDocument(nsAccessible* aAccessible, nsRoleMapEntry* aRoleMapEntry);
 
@@ -238,16 +248,27 @@ public:
   void UpdateTree(nsIContent* aContainerNode, nsIContent* aStartChildNode,
                   nsIContent* aEndChildNode, PRBool aIsInsert);
 
   /**
    * Recreate an accessible, results in hide/show events pair.
    */
   void RecreateAccessible(nsINode* aNode);
 
+  /**
+   * Used to notify the document that the accessible caching is started or
+   * finished.
+   *
+   * While children are cached we may encounter the case there's no accessible
+   * for referred content by related accessible. Keep the caching root and
+   * these related nodes to invalidate their containers after root caching.
+   */
+  void NotifyOfCachingStart(nsAccessible* aAccessible);
+  void NotifyOfCachingEnd(nsAccessible* aAccessible);
+
 protected:
 
     virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame);
     virtual nsresult AddEventListeners();
     virtual nsresult RemoveEventListeners();
     void AddScrollListener();
     void RemoveScrollListener();
 
@@ -404,14 +425,25 @@ protected:
 
   /**
    * The cache of IDs pointed by relation attributes.
    */
   typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
   nsClassHashtable<nsStringHashKey, AttrRelProviderArray> mDependentIDsHash;
 
   friend class RelatedAccIterator;
+
+  /**
+   * Used for our caching algorithm. We store the root of the tree that needs
+   * caching, the list of nodes that should be invalidated, and whether we are
+   * processing the invalidation list.
+   *
+   * @see NotifyOfCachingStart/NotifyOfCachingEnd
+   */
+  nsAccessible* mCacheRoot;
+  nsTArray<nsIContent*> mInvalidationList;
+  PRBool mIsPostCacheProcessing;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsDocAccessible,
                               NS_DOCACCESSIBLE_IMPL_CID)
 
 #endif