Bug 606924, part3 - handing document concept, r=davidb, a=blocking2.0Final+
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 26 Jan 2011 14:35:51 +0800
changeset 61317 e0fc18b3bc41ad073fb6bd48891c005b1496a4e2
parent 61316 0f592a9724829c19e1b8fecf218c11e3b802ca78
child 61318 af7e65f1ee6f04175e054674da78847f057fdbb6
push id18308
push usersurkov.alexander@gmail.com
push dateWed, 26 Jan 2011 06:37:03 +0000
treeherdermozilla-central@e0fc18b3bc41 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdavidb, blocking2.0Final
bugs606924
milestone2.0b11pre
first release with
nightly linux32
e0fc18b3bc41 / 4.0b11pre / 20110126030333 / files
nightly linux64
e0fc18b3bc41 / 4.0b11pre / 20110126030333 / files
nightly mac
e0fc18b3bc41 / 4.0b11pre / 20110126030333 / files
nightly win32
e0fc18b3bc41 / 4.0b11pre / 20110126064706 / files
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
Bug 606924, part3 - handing document concept, r=davidb, a=blocking2.0Final+
accessible/src/base/NotificationController.cpp
accessible/src/base/NotificationController.h
accessible/src/base/nsAccDocManager.cpp
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsDocAccessible.cpp
accessible/src/base/nsDocAccessible.h
accessible/tests/mochitest/events.js
accessible/tests/mochitest/events/docload_wnd.html
accessible/tests/mochitest/events/test_docload.html
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -77,16 +77,18 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(Notificat
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(NotificationController)
   tmp->Shutdown();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(NotificationController)
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDocument");
   cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mDocument.get()));
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mHangingChildDocuments,
+                                                    nsDocAccessible)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mContentInsertions,
                                                     ContentInsertion)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(mEvents, AccEvent)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
 
@@ -96,18 +98,26 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(N
 void
 NotificationController::Shutdown()
 {
   if (mObservingState != eNotObservingRefresh &&
       mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
     mObservingState = eNotObservingRefresh;
   }
 
+  // Shutdown handling child documents.
+  PRInt32 childDocCount = mHangingChildDocuments.Length();
+  for (PRInt32 idx = childDocCount - 1; idx >= 0; idx--)
+    mHangingChildDocuments[idx]->Shutdown();
+
+  mHangingChildDocuments.Clear();
+
   mDocument = nsnull;
   mPresShell = nsnull;
+
   mContentInsertions.Clear();
   mNotifications.Clear();
   mEvents.Clear();
 }
 
 void
 NotificationController::QueueEvent(AccEvent* aEvent)
 {
@@ -122,16 +132,24 @@ NotificationController::QueueEvent(AccEv
   AccMutationEvent* showOrHideEvent = downcast_accEvent(aEvent);
   if (showOrHideEvent && !showOrHideEvent->mTextChangeEvent)
     CreateTextChangeEventFor(showOrHideEvent);
 
   ScheduleProcessing();
 }
 
 void
+NotificationController::ScheduleChildDocBinding(nsDocAccessible* aDocument)
+{
+  // Schedule child document binding to the tree.
+  mHangingChildDocuments.AppendElement(aDocument);
+  ScheduleProcessing();
+}
+
+void
 NotificationController::ScheduleContentInsertion(nsAccessible* aContainer,
                                                  nsIContent* aStartChildNode,
                                                  nsIContent* aEndChildNode)
 {
   // Ignore content insertions until we constructed accessible tree.
   if (mTreeConstructedState == eTreeConstructionPending)
     return;
 
@@ -180,16 +198,21 @@ NotificationController::WillRefresh(mozi
     return;
 
   // Any generic notifications should be queued if we're processing content
   // insertions or generic notifications.
   mObservingState = eRefreshProcessingForUpdate;
 
   // Initial accessible tree construction.
   if (mTreeConstructedState == eTreeConstructionPending) {
+    // If document is not bound to parent at this point then the document is not
+    // ready yet (process notifications later).
+    if (!mDocument->IsBoundToParent())
+      return;
+
     mTreeConstructedState = eTreeConstructed;
     mDocument->CacheChildrenInSubtree(mDocument);
 
     NS_ASSERTION(mContentInsertions.Length() == 0,
                  "Pending content insertions while initial accessible tree isn't created!");
   }
 
   // Process content inserted notifications to update the tree. Process other
@@ -207,16 +230,46 @@ NotificationController::WillRefresh(mozi
 
   PRUint32 insertionCount = contentInsertions.Length();
   for (PRUint32 idx = 0; idx < insertionCount; idx++) {
     contentInsertions[idx]->Process();
     if (!mDocument)
       return;
   }
 
+  // Bind hanging child documents.
+  PRUint32 childDocCount = mHangingChildDocuments.Length();
+  for (PRUint32 idx = 0; idx < childDocCount; idx++) {
+    nsDocAccessible* childDoc = mHangingChildDocuments[idx];
+
+    nsIContent* ownerContent = mDocument->GetDocumentNode()->
+      FindContentForSubDocument(childDoc->GetDocumentNode());
+    if (ownerContent) {
+      nsAccessible* outerDocAcc = mDocument->GetCachedAccessible(ownerContent);
+      if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
+        if (mDocument->AppendChildDocument(childDoc)) {
+          // Fire reorder event to notify new accessible document has been
+          // attached to the tree.
+          nsRefPtr<AccEvent> reorderEvent =
+              new AccEvent(nsIAccessibleEvent::EVENT_REORDER, outerDocAcc,
+                           eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
+          if (reorderEvent)
+            QueueEvent(reorderEvent);
+
+          continue;
+        }
+        outerDocAcc->RemoveChild(childDoc);
+      }
+
+      // Failed to bind the child document, destroy it.
+      childDoc->Shutdown();
+    }
+  }
+  mHangingChildDocuments.Clear();
+
   // Process only currently queued generic notifications.
   nsTArray < nsRefPtr<Notification> > notifications;
   notifications.SwapElements(mNotifications);
 
   PRUint32 notificationCount = notifications.Length();
   for (PRUint32 idx = 0; idx < notificationCount; idx++) {
     notifications[idx]->Process();
     if (!mDocument)
--- a/accessible/src/base/NotificationController.h
+++ b/accessible/src/base/NotificationController.h
@@ -122,26 +122,39 @@ public:
   virtual ~NotificationController();
 
   NS_IMETHOD_(nsrefcnt) AddRef(void);
   NS_IMETHOD_(nsrefcnt) Release(void);
 
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController)
 
   /**
+   * Return true when tree is constructed.
+   */
+  inline bool IsTreeConstructed()
+  {
+    return mTreeConstructedState == eTreeConstructed;
+  }
+
+  /**
    * Shutdown the notification controller.
    */
   void Shutdown();
 
   /**
    * Put an accessible event into the queue to process it later.
    */
   void QueueEvent(AccEvent* aEvent);
 
   /**
+   * Schedule binding the child document to the tree of this document.
+   */
+  void ScheduleChildDocBinding(nsDocAccessible* aDocument);
+
+  /**
    * Pend accessible tree update for content insertion.
    */
   void ScheduleContentInsertion(nsAccessible* aContainer,
                                 nsIContent* aStartChildNode,
                                 nsIContent* aEndChildNode);
 
   /**
    * Process the generic notification synchronously if there are no pending
@@ -263,16 +276,21 @@ private:
    */
   enum eTreeConstructedState {
     eTreeConstructed,
     eTreeConstructionPending
   };
   eTreeConstructedState mTreeConstructedState;
 
   /**
+   * Child documents that needs to be bound to the tree.
+   */
+  nsTArray<nsRefPtr<nsDocAccessible> > mHangingChildDocuments;
+
+  /**
    * Storage for content inserted notification information.
    */
   class ContentInsertion
   {
   public:
     ContentInsertion(nsDocAccessible* aDocument, nsAccessible* aContainer,
                      nsIContent* aStartChildNode, nsIContent* aEndChildNode);
     virtual ~ContentInsertion() { mDocument = nsnull; }
--- a/accessible/src/base/nsAccDocManager.cpp
+++ b/accessible/src/base/nsAccDocManager.cpp
@@ -424,68 +424,68 @@ nsAccDocManager::CreateDocOrRootAccessib
   // Do not create document accessible until role content is loaded, otherwise
   // we get accessible document with wrong role.
   nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument);
   if (!rootElm)
     return nsnull;
 
   PRBool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
 
-  // Ensure the document container node is accessible, otherwise do not create
-  // document accessible.
-  nsAccessible *outerDocAcc = nsnull;
-  if (isRootDoc) {
-    outerDocAcc = nsAccessNode::GetApplicationAccessible();
-
-  } else {
-    nsIDocument* parentDoc = aDocument->GetParentDocument();
-    if (!parentDoc)
-      return nsnull;
-
-    nsIContent* ownerContent = parentDoc->FindContentForSubDocument(aDocument);
-    if (!ownerContent)
-      return nsnull;
-
+  nsDocAccessible* parentDocAcc = nsnull;
+  if (!isRootDoc) {
     // XXXaaronl: ideally we would traverse the presshell chain. Since there's
     // no easy way to do that, we cheat and use the document hierarchy.
     // GetAccessible() is bad because it doesn't support our concept of multiple
     // presshells per doc. It should be changed to use
     // GetAccessibleInWeakShell().
-    outerDocAcc = GetAccService()->GetAccessible(ownerContent);
+    parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
+    NS_ASSERTION(parentDocAcc,
+                 "Can't create an accessible for the document!");
+    if (!parentDocAcc)
+      return nsnull;
   }
 
-  if (!outerDocAcc)
-    return nsnull;
-
   // We only create root accessibles for the true root, otherwise create a
   // doc accessible.
   nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(presShell));
   nsRefPtr<nsDocAccessible> docAcc = isRootDoc ?
     new nsRootAccessibleWrap(aDocument, rootElm, weakShell) :
     new nsDocAccessibleWrap(aDocument, rootElm, weakShell);
 
   // Cache the document accessible into document cache.
   if (!docAcc || !mDocAccessibleCache.Put(aDocument, docAcc))
     return nsnull;
 
-  // Bind the document accessible into tree.
-  if (!outerDocAcc->AppendChild(docAcc)) {
-    mDocAccessibleCache.Remove(aDocument);
-    return nsnull;
-  }
-
-  // Initialize the document accessible. Note, Init() should be called after
-  // the document accessible is bound to the tree.
+  // Initialize the document accessible.
   if (!docAcc->Init()) {
     docAcc->Shutdown();
-    mDocAccessibleCache.Remove(aDocument);
     return nsnull;
   }
   docAcc->SetRoleMapEntry(nsAccUtils::GetRoleMapEntry(aDocument));
 
+  // Bind the document to the tree.
+  if (isRootDoc) {
+    nsAccessible* appAcc = nsAccessNode::GetApplicationAccessible();
+    if (!appAcc->AppendChild(docAcc)) {
+      docAcc->Shutdown();
+      return nsnull;
+    }
+
+    // Fire reorder event to notify new accessible document has been attached to
+    // the tree.
+    nsRefPtr<AccEvent> reorderEvent =
+      new AccEvent(nsIAccessibleEvent::EVENT_REORDER, appAcc, eAutoDetect,
+                   AccEvent::eCoalesceFromSameSubtree);
+    if (reorderEvent)
+      docAcc->FireDelayedAccessibleEvent(reorderEvent);
+
+  } else {
+    parentDocAcc->BindChildDocument(docAcc);
+  }
+
   NS_LOG_ACCDOCCREATE("document creation finished", aDocument)
 
   AddListeners(aDocument, isRootDoc);
   return docAcc;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccDocManager static
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -1242,17 +1242,17 @@ nsAccessibilityService::GetAccessibleByR
                                             nsIWeakReference* aWeakShell,
                                             EWhatAccToGet aWhatToGet)
 {
   if (!aNode || !aWeakShell)
     return nsnull;
 
   if (aWhatToGet & eGetAccForNode) {
     nsAccessible* cachedAcc = GetCachedAccessible(aNode, aWeakShell);
-    if (cachedAcc && cachedAcc->IsBoundToParent())
+    if (cachedAcc && (cachedAcc->IsBoundToParent() || cachedAcc->IsDocument()))
       return cachedAcc;
   }
 
   // Go up looking for the nearest accessible container having cached children.
   nsTArray<nsINode*> nodes;
 
   nsINode* node = aNode;
   nsAccessible* cachedAcc = nsnull;
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -315,17 +315,18 @@ nsDocAccessible::GetStateInternal(PRUint
     // XXX Need to invent better check to see if doc is focusable,
     // which it should be if it is scrollable. A XUL document could be focusable.
     // See bug 376803.
     *aState |= nsIAccessibleStates::STATE_FOCUSABLE;
     if (gLastFocusedNode == mDocument)
       *aState |= nsIAccessibleStates::STATE_FOCUSED;
   }
 
-  if (!mIsLoaded) {
+  // Expose state busy until the document is loaded or tree is constructed.
+  if (!mIsLoaded || !mNotificationController->IsTreeConstructed()) {
     *aState |= nsIAccessibleStates::STATE_BUSY;
     if (aExtraState) {
       *aExtraState |= nsIAccessibleStates::EXT_STATE_STALE;
     }
   }
  
   nsIFrame* frame = GetFrame();
   while (frame != nsnull && !frame->HasView()) {
@@ -624,32 +625,17 @@ nsDocAccessible::Init()
 
   // Initialize notification controller.
   nsCOMPtr<nsIPresShell> shell(GetPresShell());
   mNotificationController = new NotificationController(this, shell);
   if (!mNotificationController)
     return PR_FALSE;
 
   AddEventListeners();
-
-  nsDocAccessible* parentDocument = mParent->GetDocAccessible();
-  if (parentDocument)
-    parentDocument->AppendChildDocument(this);
-
-  // Fire reorder event to notify new accessible document has been created and
-  // attached to the tree.
-  nsRefPtr<AccEvent> reorderEvent =
-    new AccEvent(nsIAccessibleEvent::EVENT_REORDER, mParent, eAutoDetect,
-                 AccEvent::eCoalesceFromSameSubtree);
-  if (reorderEvent) {
-    FireDelayedAccessibleEvent(reorderEvent);
-    return PR_TRUE;
-  }
-
-  return PR_FALSE;
+  return PR_TRUE;
 }
 
 void
 nsDocAccessible::Shutdown()
 {
   if (!mWeakShell) // already shutdown
     return;
 
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -194,16 +194,24 @@ public:
    */
   inline void HandleAnchorJump(nsIContent* aTargetNode)
   {
     HandleNotification<nsDocAccessible, nsIContent>
       (this, &nsDocAccessible::ProcessAnchorJump, aTargetNode);
   }
 
   /**
+   * Bind the child document to the tree.
+   */
+  inline void BindChildDocument(nsDocAccessible* aDocument)
+  {
+    mNotificationController->ScheduleChildDocBinding(aDocument);
+  }
+
+  /**
    * Process the generic notification.
    *
    * @note  The caller must guarantee that the given instance still exists when
    *          notification is processed.
    * @see   NotificationController::HandleNotification
    */
   template<class Class, class Arg>
   inline void HandleNotification(Class* aInstance,
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -425,17 +425,17 @@ function eventQueue(aEventType)
         var eventType = this.getEventType(idx);
 
         if (gLogger.isEnabled()) {
           var msg = "registered";
           if (this.isEventUnexpected(idx))
             msg += " unexpected";
 
           msg += ": event type: " + this.getEventTypeAsString(idx) +
-            ", target: " + prettyName(this.getEventTarget(idx));
+            ", target: " + this.getEventTargetDescr(idx);
 
           gLogger.logToConsole(msg);
           gLogger.logToDOM(msg, true);
         }
 
         if (typeof eventType == "string") {
           // DOM event
           var target = this.getEventTarget(idx);
@@ -482,16 +482,22 @@ function eventQueue(aEventType)
     return (typeof type == "string") ? type : eventTypeToString(type);
   }
 
   this.getEventTarget = function eventQueue_getEventTarget(aIdx)
   {
     return this.mEventSeq[aIdx].target;
   }
 
+  this.getEventTargetDescr = function eventQueue_getEventTargetDescr(aIdx)
+  {
+    var descr = this.mEventSeq[aIdx].targetDescr;
+    return descr ? descr : "no target description";
+  }
+
   this.getEventPhase = function eventQueue_getEventPhase(aIdx)
   {
      var eventItem = this.mEventSeq[aIdx];
     if ("phase" in eventItem)
       return eventItem.phase;
 
     return true;
   }
@@ -890,16 +896,26 @@ function invokerChecker(aEventType, aTar
   }
 
   function invokerChecker_targetSetter(aValue)
   {
     this.mTarget = aValue;
     return this.mTarget;
   }
 
+  this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);
+
+  function invokerChecker_targetDescrGetter()
+  {
+    if (typeof this.mTarget == "function")
+      return this.mTarget.toSource() + this.mTargetFuncArg;
+
+    return prettyName(this.mTarget);
+  }
+
   this.mTarget = aTargetOrFunc;
   this.mTargetFuncArg = aTargetFuncArg;
 }
 
 /**
  * Text inserted/removed events checker.
  */
 function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted)
--- a/accessible/tests/mochitest/events/docload_wnd.html
+++ b/accessible/tests/mochitest/events/docload_wnd.html
@@ -1,21 +1,39 @@
 <html>
 <head>
   <title>Accessible events testing for document</title>
   <script>
+    const STATE_BUSY = Components.interfaces.nsIAccessibleStates.STATE_BUSY;
+
+    var gRetrieval = null;
+    function waitForDocLoad()
+    {
+      if (!gRetrieval) {
+        gRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
+         getService(Components.interfaces.nsIAccessibleRetrieval);
+      }
+
+      var accDoc = gRetrieval.getAccessibleFor(document);
+
+      var state = {};
+      accDoc.getState(state, {});
+      if (state.value & STATE_BUSY) {
+        window.setTimeout(waitForDocLoad, 0);
+        return;
+      }
+
+      hideIFrame();
+    }
+
     function hideIFrame()
     {
       var iframe = document.getElementById("iframe");
-
-      var accRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
-        getService(Components.interfaces.nsIAccessibleRetrieval);
-      accRetrieval.getAccessibleFor(iframe.contentDocument);
-
+      gRetrieval.getAccessibleFor(iframe.contentDocument);
       iframe.style.display = 'none';
     }
   </script>
 </head>
 
-<body onload="hideIFrame();">
+<body onload="waitForDocLoad();">
   <iframe id="iframe"></iframe>
 </body>
 </html>
--- a/accessible/tests/mochitest/events/test_docload.html
+++ b/accessible/tests/mochitest/events/test_docload.html
@@ -192,16 +192,20 @@
       var docChecker = {
         type: EVENT_HIDE,
         get target()
         {
           var iframe = this.invoker.mDialog.document.getElementById("iframe");
           this.invoker.iframeDoc = iframe.contentDocument;
           return iframe;
         },
+        get targetDescr()
+        {
+          return "inner iframe of docload_wnd.html document";
+        },
         invoker: thisObj
       };
 
       this.eventSeq.push(docChecker);
 
       this.finalCheck = function openWndShutdownDoc_finalCheck()
       {
         // After timeout after event hide for iframe was handled the document