Bug 592913 - provide a way to quickly determine whether an accessible object is a descendant of a tab document, r=marcoz, davidb, a=blocking
authorAlexander Surkov <surkov.alexander@gmail.com>
Thu, 09 Sep 2010 23:44:56 +0900
changeset 52279 b76cfd9e1028fde3cc886c24106e6d76be5c5d77
parent 52278 84fdf8e52709f3e129dc6b0bc0f4cf9e1bfeedbf
child 52280 94a0c347256d04d1bbba72afc85e8424318f13a3
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)
reviewersmarcoz, davidb, blocking
bugs592913
milestone2.0b6pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 592913 - provide a way to quickly determine whether an accessible object is a descendant of a tab document, r=marcoz, davidb, a=blocking
accessible/public/nsIAccessibleDocument.idl
accessible/src/base/nsDocAccessible.cpp
accessible/src/base/nsDocAccessible.h
accessible/src/msaa/nsAccessibleWrap.cpp
accessible/src/msaa/nsDocAccessibleWrap.cpp
accessible/src/msaa/nsDocAccessibleWrap.h
accessible/tests/mochitest/tree/Makefile.in
accessible/tests/mochitest/tree/test_dochierarchy.html
--- a/accessible/public/nsIAccessibleDocument.idl
+++ b/accessible/public/nsIAccessibleDocument.idl
@@ -51,17 +51,17 @@ interface nsIDOMWindow;
  * there is an nsIAccessibleDocument for each document
  * whether it is XUL, HTML or whatever.
  * 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()
  */
-[scriptable, uuid(03c6ce8a-aa40-4484-9282-e6579c56e054)]
+[scriptable, uuid(451242bd-8a0c-4198-ae88-c053609a4e5d)]
 interface nsIAccessibleDocument : nsISupports
 {
   /**
    * The URL of the document
    */
   readonly attribute AString URL;
 
   /**
@@ -94,9 +94,24 @@ interface nsIAccessibleDocument : nsISup
    */
   AString getNameSpaceURIForID(in short nameSpaceID);
 
   /**
    * The window handle for the OS window the document is being displayed in.
    * For example, in Windows you can static cast it to an HWND.
    */
   [noscript] readonly attribute voidPtr windowHandle;
+
+  /**
+   * Return the parent document accessible.
+   */
+  readonly attribute nsIAccessibleDocument parentDocument;
+
+  /**
+   * Return the count of child document accessibles.
+   */
+  readonly attribute unsigned long childDocumentCount;
+
+  /**
+   * Return the child document accessible at the given index.
+   */
+  nsIAccessibleDocument getChildDocumentAt(in unsigned long index);
 };
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -128,21 +128,28 @@ nsDocAccessible::~nsDocAccessible()
 // nsISupports
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocAccessible)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEventQueue");
   cb.NoteXPCOMChild(tmp->mEventQueue.get());
 
+  PRUint32 i, length = tmp->mChildDocuments.Length();
+  for (i = 0; i < length; ++i) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildDocuments[i]");
+    cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mChildDocuments[i].get()));
+  }
+
   CycleCollectorTraverseCache(tmp->mAccessibleCache, &cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventQueue)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildDocuments)
   ClearCache(tmp->mAccessibleCache);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDocAccessible)
   NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsDocAccessible)
   NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument)
   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
@@ -493,16 +500,54 @@ nsDocAccessible::GetDOMDocument(nsIDOMDo
   *aDOMDocument = nsnull;
 
   if (mDocument)
     CallQueryInterface(mDocument, aDOMDocument);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDocAccessible::GetParentDocument(nsIAccessibleDocument** aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aDocument);
+  *aDocument = nsnull;
+
+  if (!IsDefunct())
+    NS_IF_ADDREF(*aDocument = ParentDocument());
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocAccessible::GetChildDocumentCount(PRUint32* aCount)
+{
+  NS_ENSURE_ARG_POINTER(aCount);
+  *aCount = 0;
+
+  if (!IsDefunct())
+    *aCount = ChildDocumentCount();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocAccessible::GetChildDocumentAt(PRUint32 aIndex,
+                                    nsIAccessibleDocument** aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aDocument);
+  *aDocument = nsnull;
+
+  if (IsDefunct())
+    return NS_OK;
+
+  NS_IF_ADDREF(*aDocument = GetChildDocumentAt(aIndex));
+  return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
 // nsIAccessibleHyperText method
 NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
   *aEditor = nsnull;
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
@@ -526,16 +571,17 @@ NS_IMETHODIMP nsDocAccessible::GetAssoci
   PRBool isEditable;
   editor->GetIsDocumentEditable(&isEditable);
   if (isEditable) {
     NS_ADDREF(*aEditor = editor);
   }
   return NS_OK;
 }
 
+// nsDocAccessible public method
 nsAccessible *
 nsDocAccessible::GetCachedAccessible(void *aUniqueID)
 {
   nsAccessible* accessible = mAccessibleCache.GetWeak(aUniqueID);
 
   // No accessible in the cache, check if the given ID is unique ID of this
   // document accessible.
   if (!accessible) {
@@ -598,16 +644,20 @@ nsDocAccessible::Init()
 
   // Initialize event queue.
   mEventQueue = new nsAccEventQueue(this);
   if (!mEventQueue)
     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 AccReorderEvent(mParent, PR_FALSE, PR_TRUE, mDocument);
   if (!reorderEvent)
     return PR_FALSE;
 
   FireDelayedAccessibleEvent(reorderEvent);
@@ -624,18 +674,25 @@ nsDocAccessible::Shutdown()
 
   if (mEventQueue) {
     mEventQueue->Shutdown();
     mEventQueue = nsnull;
   }
 
   RemoveEventListeners();
 
-  if (mParent)
+  if (mParent) {
+    nsDocAccessible* parentDocument = mParent->GetDocAccessible();
+    if (parentDocument)
+      parentDocument->RemoveChildDocument(this);
+
     mParent->RemoveChild(this);
+  }
+
+  mChildDocuments.Clear();
 
   mWeakShell = nsnull;  // Avoid reentrancy
 
   ClearCache(mAccessibleCache);
 
   nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocument;
   mDocument = nsnull;
 
@@ -1273,16 +1330,33 @@ nsDocAccessible::HandleAccEvent(AccEvent
   return nsHyperTextAccessible::HandleAccEvent(aAccEvent);
 
 }
 #endif
 
 ////////////////////////////////////////////////////////////////////////////////
 // Public members
 
+nsAccessible*
+nsDocAccessible::GetCachedAccessibleInSubtree(void* aUniqueID)
+{
+  nsAccessible* child = GetCachedAccessible(aUniqueID);
+  if (child)
+    return child;
+
+  PRUint32 childDocCount = mChildDocuments.Length();
+  for (PRUint32 childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
+    nsDocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
+    child = childDocument->GetCachedAccessibleInSubtree(aUniqueID);
+    if (child)
+      return child;
+  }
+
+  return nsnull;
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 // Protected members
 
 void
 nsDocAccessible::FireValueChangeForTextFields(nsAccessible *aAccessible)
 {
   if (aAccessible->Role() != nsIAccessibleRole::ROLE_ENTRY)
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -137,16 +137,34 @@ public:
   /**
    * Marks as loaded, used for error pages as workaround since they do not
    * receive pageshow event and as consequence nsIDocument::IsShowing() returns
    * false.
    */
   void MarkAsLoaded() { mIsLoaded = PR_TRUE; }
 
   /**
+   * Return the parent document.
+   */
+  nsDocAccessible* ParentDocument() const
+    { return mParent ? mParent->GetDocAccessible() : nsnull; }
+
+  /**
+   * Return the child document count.
+   */
+  PRUint32 ChildDocumentCount() const
+    { return mChildDocuments.Length(); }
+
+  /**
+   * Return the child document at the given index.
+   */
+  nsDocAccessible* GetChildDocumentAt(PRUint32 aIndex) const
+    { return mChildDocuments.SafeElementAt(aIndex, nsnull); }
+
+  /**
    * Non-virtual method to fire a delayed event after a 0 length timeout.
    *
    * @param aEventType   [in] the nsIAccessibleEvent event type
    * @param aDOMNode     [in] DOM node the accesible event should be fired for
    * @param aAllowDupes  [in] rule to process an event (see EEventRule constants)
    * @param aIsAsynch    [in] set to PR_TRUE if this is not being called from
    *                      code synchronous with a DOM event
    */
@@ -184,16 +202,22 @@ public:
    *
    * @param  aUniqueID  [in] the unique ID used to cache the node.
    *
    * @return the accessible object
    */
   nsAccessible* GetCachedAccessible(void *aUniqueID);
 
   /**
+   * Return the cached accessible by the given unique ID looking through
+   * this and nested documents.
+   */
+  nsAccessible* GetCachedAccessibleInSubtree(void* aUniqueID);
+
+  /**
    * Cache the accessible.
    *
    * @param  aUniquID     [in] the unique identifier of accessible
    * @param  aAccessible  [in] accessible to cache
    *
    * @return true if accessible being cached, otherwise false
    */
   PRBool CacheAccessible(void *aUniqueID, nsAccessible *aAccessible);
@@ -213,16 +237,34 @@ protected:
 
     virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame);
     virtual nsresult AddEventListeners();
     virtual nsresult RemoveEventListeners();
     void AddScrollListener();
     void RemoveScrollListener();
 
   /**
+   * Append the given document accessible to this document's child document
+   * accessibles.
+   */
+  bool AppendChildDocument(nsDocAccessible* aChildDocument)
+  {
+    return mChildDocuments.AppendElement(aChildDocument);
+  }
+
+  /**
+   * Remove the given document accessible from this document's child document
+   * accessibles.
+   */
+  void RemoveChildDocument(nsDocAccessible* aChildDocument)
+  {
+    mChildDocuments.RemoveElement(aChildDocument);
+  }
+
+  /**
    * Invalidate parent-child relations for any cached accessible in the DOM
    * subtree. Accessible objects aren't destroyed.
    *
    * @param aStartNode  [in] the root of the subrtee to invalidate accessible
    *                      child/parent refs in
    */
   void InvalidateChildrenInSubtree(nsINode *aStartNode);
 
@@ -332,14 +374,16 @@ protected:
 
   /**
    * Specifies if the document was loaded, used for error pages only.
    */
   PRPackedBool mIsLoaded;
 
     static PRUint32 gLastFocusedAccessiblesState;
     static nsIAtom *gLastFocusedFrameType;
+
+  nsTArray<nsRefPtr<nsDocAccessible> > mChildDocuments;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsDocAccessible,
                               NS_DOCACCESSIBLE_IMPL_CID)
 
 #endif  
--- a/accessible/src/msaa/nsAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsAccessibleWrap.cpp
@@ -277,34 +277,31 @@ STDMETHODIMP nsAccessibleWrap::get_accCh
 }
 
 STDMETHODIMP nsAccessibleWrap::get_accChild(
       /* [in] */ VARIANT varChild,
       /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild)
 {
 __try {
   *ppdispChild = NULL;
-  if (!mWeakShell || varChild.vt != VT_I4)
+  if (IsDefunct())
     return E_FAIL;
 
-  if (varChild.lVal == CHILDID_SELF) {
-    *ppdispChild = static_cast<IDispatch*>(this);
-    AddRef();
-    return S_OK;
-  }
+  // IAccessible::accChild is used to return this accessible or child accessible
+  // at the given index or to get an accessible by child ID in the case of
+  // document accessible (it's handled by overriden GetXPAccessibleFor method
+  // on the document accessible). The getting an accessible by child ID is used
+  // by AccessibleObjectFromEvent() called by AT when AT handles our MSAA event.
+  nsAccessible* child = GetXPAccessibleFor(varChild);
+  if (child)
+    *ppdispChild = NativeAccessible(child);
 
-  if (!nsAccUtils::MustPrune(this)) {
-    nsAccessible* child = GetChildAt(varChild.lVal - 1);
-    if (child) {
-      *ppdispChild = NativeAccessible(child);
-    }
-  }
 } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
 
-  return (*ppdispChild)? S_OK: E_FAIL;
+  return (*ppdispChild)? S_OK: E_INVALIDARG;
 }
 
 STDMETHODIMP nsAccessibleWrap::get_accName(
       /* [optional][in] */ VARIANT varChild,
       /* [retval][out] */ BSTR __RPC_FAR *pszName)
 {
 __try {
   *pszName = NULL;
@@ -1799,17 +1796,17 @@ nsAccessibleWrap::UnattachIEnumVariant()
 {
   if (mEnumVARIANTPosition > 0)
     mEnumVARIANTPosition = kIEnumVariantDisconnected;
 }
 
 nsAccessible*
 nsAccessibleWrap::GetXPAccessibleFor(const VARIANT& aVarChild)
 {
-  if (IsDefunct())
+  if (aVarChild.vt != VT_I4)
     return nsnull;
 
   // if its us real easy - this seems to always be the case
   if (aVarChild.lVal == CHILDID_SELF)
     return this;
 
   if (nsAccUtils::MustPrune(this))
     return nsnull;
--- a/accessible/src/msaa/nsDocAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsDocAccessibleWrap.cpp
@@ -98,51 +98,25 @@ STDMETHODIMP nsDocAccessibleWrap::QueryI
 
 nsAccessible*
 nsDocAccessibleWrap::GetXPAccessibleFor(const VARIANT& aVarChild)
 {
   // If lVal negative then it is treated as child ID and we should look for
   // accessible through whole accessible subtree including subdocuments.
   // Otherwise we treat lVal as index in parent.
 
-  if (aVarChild.lVal < 0)
-    return IsDefunct() ? nsnull : GetXPAccessibleForChildID(aVarChild);
+  if (aVarChild.vt == VT_I4 && aVarChild.lVal < 0) {
+    // Convert child ID to unique ID.
+    void* uniqueID = reinterpret_cast<void*>(-aVarChild.lVal);
+    return GetCachedAccessibleInSubtree(uniqueID);
+  }
 
   return nsAccessibleWrap::GetXPAccessibleFor(aVarChild);
 }
 
-STDMETHODIMP
-nsDocAccessibleWrap::get_accChild(VARIANT varChild,
-                                  IDispatch __RPC_FAR *__RPC_FAR *ppdispChild)
-{
-__try {
-  *ppdispChild = NULL;
-
-  if (varChild.vt == VT_I4 && varChild.lVal < 0) {
-    // IAccessible::accChild can be used to get an accessible by child ID.
-    // It is used by AccessibleObjectFromEvent() called by AT when AT handles
-    // our MSAA event.
-
-    nsAccessible *xpAccessible = GetXPAccessibleForChildID(varChild);
-    if (!xpAccessible)
-      return E_FAIL;
-
-    IAccessible *msaaAccessible = NULL;
-    xpAccessible->GetNativeInterface((void**)&msaaAccessible);
-    *ppdispChild = static_cast<IDispatch*>(msaaAccessible);
-
-    return S_OK;
-  }
-
-  // Otherwise, the normal get_accChild() will do
-  return nsAccessibleWrap::get_accChild(varChild, ppdispChild);
-} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
-  return E_FAIL;
-}
-
 STDMETHODIMP nsDocAccessibleWrap::get_URL(/* [out] */ BSTR __RPC_FAR *aURL)
 {
 __try {
   *aURL = NULL;
 
   nsAutoString URL;
   nsresult rv = GetURL(URL);
   if (NS_FAILED(rv))
@@ -266,19 +240,8 @@ STDMETHODIMP nsDocAccessibleWrap::get_ac
   if (role != nsIAccessibleRole::ROLE_DOCUMENT &&
       role != nsIAccessibleRole::ROLE_APPLICATION &&
       role != nsIAccessibleRole::ROLE_DIALOG &&
       role != nsIAccessibleRole::ROLE_ALERT)
     return hr;
 
   return get_URL(pszValue);
 }
-
-nsAccessible*
-nsDocAccessibleWrap::GetXPAccessibleForChildID(const VARIANT& aVarChild)
-{
-  NS_PRECONDITION(aVarChild.vt == VT_I4 && aVarChild.lVal < 0,
-                  "Variant doesn't point to child ID!");
-
-  // Convert child ID to unique ID.
-  void *uniqueID = reinterpret_cast<void*>(-aVarChild.lVal);
-  return GetAccService()->FindAccessibleInCache(uniqueID);
-}
--- a/accessible/src/msaa/nsDocAccessibleWrap.h
+++ b/accessible/src/msaa/nsDocAccessibleWrap.h
@@ -78,32 +78,19 @@ public:
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nameSpaceURIForID( 
         /* [in] */ short nameSpaceID,
         /* [out] */ BSTR __RPC_FAR *nameSpaceURI);
 
     virtual /* [id] */ HRESULT STDMETHODCALLTYPE put_alternateViewMediaTypes( 
         /* [in] */ BSTR __RPC_FAR *commaSeparatedMediaTypes);
 
     // IAccessible
-    // Override get_accChild so that it can get any child via the unique ID
-    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChild( 
-        /* [in] */ VARIANT varChild,
-        /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild);
 
     // Override get_accValue to provide URL when no other value is available
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accValue( 
         /* [optional][in] */ VARIANT varChild,
         /* [retval][out] */ BSTR __RPC_FAR *pszValue);
 
   // nsAccessibleWrap
   virtual nsAccessible *GetXPAccessibleFor(const VARIANT& varChild);
-
-  // nsDocAccessibleWrap
-
-  /**
-   * Find an accessible by the given child ID in cached documents.
-   *
-   * @param  aVarChild    [in] variant pointing to the child ID
-   */
-  static nsAccessible *GetXPAccessibleForChildID(const VARIANT& aVarChild);
 };
 
 #endif
--- a/accessible/tests/mochitest/tree/Makefile.in
+++ b/accessible/tests/mochitest/tree/Makefile.in
@@ -48,16 +48,17 @@ include $(topsrcdir)/config/rules.mk
 _TEST_FILES =\
   $(warning test_applicationacc.xul temporarily disabled, see bug 561508) \
 		test_aria_globals.html \
 		test_aria_imgmap.html \
 		test_button.xul \
 		test_colorpicker.xul \
 		test_combobox.xul \
 		test_cssoverflow.html \
+		test_dochierarchy.html \
 		test_filectrl.html \
 		test_formctrl.html \
 		test_formctrl.xul \
 		test_gencontent.html \
 		test_groupbox.xul \
 		test_iframe.html \
 		test_img.html \
 		test_list.html \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_dochierarchy.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Test document hierarchy</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+  <script type="application/javascript"
+          src="../states.js"></script>
+
+  <script type="application/javascript">
+  function doTest()
+  {
+    // tabDoc and testDoc are different documents depending on whether test
+    // is running in standalone mode or not.
+
+    var root = getRootAccessible();
+    var tabDoc = window.parent ?
+      getAccessible(window.parent.document, [nsIAccessibleDocument]) :
+      getAccessible(document, [nsIAccessibleDocument]);
+    var testDoc = getAccessible(document, [nsIAccessibleDocument]);
+    var iframeDoc = getAccessible("iframe").firstChild.
+      QueryInterface(nsIAccessibleDocument);
+
+    is(root.parentDocument, null,
+       "Wrong parent document of root accessible");
+    is(root.childDocumentCount, 1,
+       "Wrong child document count of root accessible");
+    is(root.getChildDocumentAt(0), tabDoc,
+       "Wrong child document at index 0 of root accessible");
+
+    is(tabDoc.parentDocument, root,
+       "Wrong parent document of tab document");
+    is(tabDoc.childDocumentCount, 1,
+       "Wrong child document count of tab document");
+    is(tabDoc.getChildDocumentAt(0), (tabDoc == testDoc ? iframeDoc : testDoc),
+       "Wrong child document at index 0 of tab document");
+
+    if (tabDoc != testDoc) {
+      is(testDoc.parentDocument, tabDoc,
+         "Wrong parent document of test document");
+      is(testDoc.childDocumentCount, 1,
+         "Wrong child document count of test document");
+      is(testDoc.getChildDocumentAt(0), iframeDoc,
+         "Wrong child document at index 0 of test document");
+    }
+
+    is(iframeDoc.parentDocument, (tabDoc == testDoc ? tabDoc : testDoc),
+       "Wrong parent document of iframe document");
+    is(iframeDoc.childDocumentCount, 0,
+       "Wrong child document count of iframe document");
+
+    SimpleTest.finish();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  addA11yLoadEvent(doTest);
+  </script>
+</head>
+
+<body>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=592913"
+     title="Provide a way to quickly determine whether an accessible object is a descendant of a tab document">
+    Mozilla Bug 592913
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <iframe src="about:mozilla" id="iframe"></iframe>
+</body>
+</html>