Bug 345780 - Support multiple targets for same relation, r=davidb, r=MarcoZ, sr=neil
authorAlexander Surkov <surkov.alexander@gmail.com>
Tue, 10 Feb 2009 11:03:30 +0100
changeset 24822 2681688f4cdf98a09ff08d6c26bcadbff3d92fa7
parent 24821 37b0d6169084a530a1fd2d6bf583c836d7e3f8e8
child 24823 3cb3ab57d0b3cb3f0b7bdef89455d4b527bb7087
push idunknown
push userunknown
push dateunknown
reviewersdavidb, MarcoZ, neil
bugs345780
milestone1.9.2a1pre
Bug 345780 - Support multiple targets for same relation, r=davidb, r=MarcoZ, sr=neil
accessible/public/nsIAccessible.idl
accessible/src/atk/nsAccessibleWrap.cpp
accessible/src/base/Makefile.in
accessible/src/base/nsAccessible.cpp
accessible/src/base/nsAccessible.h
accessible/src/base/nsAccessibleRelation.cpp
accessible/src/base/nsAccessibleRelation.h
accessible/src/base/nsCoreUtils.cpp
accessible/src/base/nsCoreUtils.h
accessible/src/base/nsRelUtils.cpp
accessible/src/base/nsRelUtils.h
accessible/src/base/nsRootAccessible.cpp
accessible/src/base/nsRootAccessible.h
accessible/src/html/nsHTMLFormControlAccessible.cpp
accessible/src/html/nsHTMLFormControlAccessible.h
accessible/src/html/nsHTMLTableAccessible.cpp
accessible/src/html/nsHTMLTableAccessible.h
accessible/src/msaa/nsAccessibleWrap.cpp
accessible/src/xul/nsXULFormControlAccessible.cpp
accessible/src/xul/nsXULFormControlAccessible.h
accessible/src/xul/nsXULTabAccessible.cpp
accessible/src/xul/nsXULTabAccessible.h
accessible/src/xul/nsXULTextAccessible.cpp
accessible/src/xul/nsXULTextAccessible.h
accessible/src/xul/nsXULTreeAccessible.cpp
accessible/src/xul/nsXULTreeAccessible.h
accessible/tests/mochitest/common.js
accessible/tests/mochitest/relations.js
accessible/tests/mochitest/test_relations.html
accessible/tests/mochitest/test_relations.xul
--- a/accessible/public/nsIAccessible.idl
+++ b/accessible/public/nsIAccessible.idl
@@ -53,17 +53,17 @@ interface nsIAccessibleRelation;
  * Can also be used by in-process accessibility clients to get information
  * about objects in the accessible tree. The accessible tree is a subset of 
  * nodes in the DOM tree -- such as documents, focusable elements and text.
  * Mozilla creates the implementations of nsIAccessible on demand.
  * See http://www.mozilla.org/projects/ui/accessibility for more information.
  *
  * @status UNDER_REVIEW
  */
-[scriptable, uuid(670fc322-14ec-4f3b-8279-9d62ab8895c0)]
+[scriptable, uuid(76c72c70-2c4b-4fce-8e75-b121db024333)]
 interface nsIAccessible : nsISupports
 {
   /**
    * Parent node in accessible tree.
    */
   readonly attribute nsIAccessible parent;
 
   /**
@@ -246,20 +246,20 @@ interface nsIAccessible : nsISupports
   nsIAccessible getAccessibleAbove();
 
   /**
    * Accessible node geometrically below this one
    */
   nsIAccessible getAccessibleBelow();
 
   /**
-   * Return accessible related to this one by the given relation type (see.
+   * Return accessible relation by the given relation type (see.
    * constants defined in nsIAccessibleRelation).
    */
-  nsIAccessible getAccessibleRelated(in unsigned long aRelationType);
+  nsIAccessibleRelation getRelationByType(in unsigned long aRelationType);
 
   /**
    * Returns the number of accessible relations for this object.
    */
   readonly attribute unsigned long relationsCount;
 
   /**
    * Returns one accessible relation for this object.
--- a/accessible/src/atk/nsAccessibleWrap.cpp
+++ b/accessible/src/atk/nsAccessibleWrap.cpp
@@ -1048,17 +1048,16 @@ refRelationSetCB(AtkObject *aAtkObj)
     AtkRelationSet *relation_set = nsnull;
     relation_set = ATK_OBJECT_CLASS(parent_class)->ref_relation_set(aAtkObj);
 
     nsAccessibleWrap *accWrap = GetAccessibleWrap(aAtkObj);
     if (!accWrap) {
         return relation_set;
     }
 
-    AtkObject *accessible_array[1];
     AtkRelation* relation;
     
     PRUint32 relationType[] = {nsIAccessibleRelation::RELATION_LABELLED_BY,
                                nsIAccessibleRelation::RELATION_LABEL_FOR,
                                nsIAccessibleRelation::RELATION_NODE_CHILD_OF,
                                nsIAccessibleRelation::RELATION_CONTROLLED_BY,
                                nsIAccessibleRelation::RELATION_CONTROLLER_FOR,
                                nsIAccessibleRelation::RELATION_EMBEDS,
@@ -1069,24 +1068,38 @@ refRelationSetCB(AtkObject *aAtkObj)
                                };
 
     for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(relationType); i++) {
         relation = atk_relation_set_get_relation_by_type(relation_set, static_cast<AtkRelationType>(relationType[i]));
         if (relation) {
             atk_relation_set_remove(relation_set, relation);
         }
 
-        nsIAccessible* accRelated;
-        nsresult rv = accWrap->GetAccessibleRelated(relationType[i], &accRelated);
-        if (NS_SUCCEEDED(rv) && accRelated) {
-            accessible_array[0] = nsAccessibleWrap::GetAtkObject(accRelated);
-            relation = atk_relation_new(accessible_array, 1,
-                                        static_cast<AtkRelationType>(relationType[i]));
-            atk_relation_set_add(relation_set, relation);
-            g_object_unref(relation);
+        nsCOMPtr<nsIAccessibleRelation> geckoRelation;
+        nsresult rv = accWrap->GetRelationByType(relationType[i],
+                                                 getter_AddRefs(geckoRelation));
+        if (NS_SUCCEEDED(rv) && geckoRelation) {
+            PRUint32 targetsCount = 0;
+            geckoRelation->GetTargetsCount(&targetsCount);
+            if (targetsCount) {
+                AtkObject** accessible_array = new AtkObject*[targetsCount];
+                for (PRUint32 index = 0; index < targetsCount; index++) {
+                    nsCOMPtr<nsIAccessible> geckoTarget;
+                    geckoRelation->GetTarget(index, getter_AddRefs(geckoTarget));
+                    accessible_array[index] =
+                        nsAccessibleWrap::GetAtkObject(geckoTarget);
+                }
+
+                relation = atk_relation_new(accessible_array, targetsCount,
+                                            static_cast<AtkRelationType>(relationType[i]));
+                atk_relation_set_add(relation_set, relation);
+                g_object_unref(relation);
+
+                delete [] accessible_array;
+            }
         }
     }
 
     return relation_set;
 }
 
 // Check if aAtkObj is a valid MaiAtkObject, and return the nsAccessibleWrap
 // for it.
--- a/accessible/src/base/Makefile.in
+++ b/accessible/src/base/Makefile.in
@@ -77,16 +77,17 @@ CPPSRCS = \
   nsAccessibleEventData.cpp \
   nsARIAMap.cpp \
   nsDocAccessible.cpp \
   nsOuterDocAccessible.cpp \
   nsAccessibilityAtoms.cpp \
   nsCoreUtils.cpp \
   nsAccUtils.cpp \
   nsNameUtils.cpp \
+  nsRelUtils.cpp \
   nsAccessibilityService.cpp \
   nsAccessible.cpp \
   nsAccessibleRelation.cpp \
   nsAccessibleTreeWalker.cpp \
   nsBaseWidgetAccessible.cpp \
   nsFormControlAccessible.cpp \
   nsRootAccessible.cpp \
   nsApplicationAccessible.cpp \
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -79,16 +79,17 @@
 #include "nsUnicharUtils.h"
 #include "nsReadableUtils.h"
 #include "prdtoa.h"
 #include "nsIAtom.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsIURI.h"
 #include "nsITimer.h"
+#include "nsArrayUtils.h"
 #include "nsIMutableArray.h"
 #include "nsIObserverService.h"
 #include "nsIServiceManager.h"
 #include "nsWhitespaceTokenizer.h"
 #include "nsAttrName.h"
 #include "nsNetUtil.h"
 
 #ifdef NS_DEBUG
@@ -1696,58 +1697,45 @@ nsAccessible::AppendFlatStringFromSubtre
   return NS_OK;
 }
 
 nsresult nsAccessible::GetTextFromRelationID(nsIAtom *aIDProperty, nsString &aName)
 {
   // Get DHTML name from content subtree pointed to by ID attribute
   aName.Truncate();
   NS_ASSERTION(mDOMNode, "Called from shutdown accessible");
+
   nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
   if (!content)
     return NS_OK;
 
-  nsAutoString ids;
-  if (!content->GetAttr(kNameSpaceID_None, aIDProperty, ids))
+  nsCOMPtr<nsIArray> refElms;
+  nsCoreUtils::GetElementsByIDRefsAttr(content, aIDProperty,
+                                       getter_AddRefs(refElms));
+
+  if (!refElms)
     return NS_OK;
 
-  ids.CompressWhitespace(PR_TRUE, PR_TRUE);
-
-  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->GetOwnerDoc());
-  NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
-
-  // Support idlist as in aria-labelledby="id1 id2 id3"
-  while (!ids.IsEmpty()) {
-    nsAutoString id;
-    PRInt32 idLength = ids.FindChar(' ');
-    NS_ASSERTION(idLength != 0, "Should not be 0 because of CompressWhitespace() call above");
-    if (idLength == kNotFound) {
-      id = ids;
-      ids.Truncate();
-    } else {
-      id = Substring(ids, 0, idLength);
-      ids.Cut(0, idLength + 1);
-    }
-
-    if (!aName.IsEmpty()) {
+  PRUint32 count = 0;
+  nsresult rv = refElms->GetLength(&count);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIContent> refContent;
+  for (PRUint32 idx = 0; idx < count; idx++) {
+    refContent = do_QueryElementAt(refElms, idx, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!aName.IsEmpty())
       aName += ' '; // Need whitespace between multiple labels or descriptions
-    }
-    nsCOMPtr<nsIDOMElement> labelElement;
-    domDoc->GetElementById(id, getter_AddRefs(labelElement));
-    content = do_QueryInterface(labelElement);
-    if (!content) {
-      return NS_OK;
-    }
-    // We have a label content
-    nsresult rv = AppendFlatStringFromSubtree(content, &aName);
-    if (NS_SUCCEEDED(rv)) {
-      aName.CompressWhitespace();
-    }
+
+    rv = AppendFlatStringFromSubtree(refContent, &aName);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
-  
+
+  aName.CompressWhitespace();
   return NS_OK;
 }
 
 nsresult
 nsAccessible::GetHTMLName(nsAString& aLabel)
 {
   nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
   if (!content) {
@@ -1917,17 +1905,18 @@ NS_IMETHODIMP nsAccessible::GetFinalRole
     else if (*aRole == nsIAccessibleRole::ROLE_LISTBOX) {
       // A listbox inside of a combo box needs a special role because of ATK mapping to menu
       nsCOMPtr<nsIAccessible> possibleCombo;
       GetParent(getter_AddRefs(possibleCombo));
       if (nsAccUtils::Role(possibleCombo) == nsIAccessibleRole::ROLE_COMBOBOX) {
         *aRole = nsIAccessibleRole::ROLE_COMBOBOX_LIST;
       }
       else {   // Check to see if combo owns the listbox instead
-        GetAccessibleRelated(nsIAccessibleRelation::RELATION_NODE_CHILD_OF, getter_AddRefs(possibleCombo));
+        possibleCombo = nsRelUtils::
+          GetRelatedAccessible(this, nsIAccessibleRelation::RELATION_NODE_CHILD_OF);
         if (nsAccUtils::Role(possibleCombo) == nsIAccessibleRole::ROLE_COMBOBOX)
           *aRole = nsIAccessibleRole::ROLE_COMBOBOX_LIST;
       }
     }
     else if (*aRole == nsIAccessibleRole::ROLE_OPTION) {
       nsCOMPtr<nsIAccessible> parent;
       GetParent(getter_AddRefs(parent));
       if (nsAccUtils::Role(parent) == nsIAccessibleRole::ROLE_COMBOBOX_LIST)
@@ -2223,20 +2212,18 @@ nsAccessible::GetState(PRUint32 *aState,
   GetARIAState(aState);
 
   if (mRoleMapEntry && mRoleMapEntry->role == nsIAccessibleRole::ROLE_PAGETAB) {
     if (*aState & nsIAccessibleStates::STATE_FOCUSED) {
       *aState |= nsIAccessibleStates::STATE_SELECTED;
     } else {
       // Expose 'selected' state on ARIA tab if the focus is on internal element
       // of related tabpanel.
-      nsCOMPtr<nsIAccessible> tabPanel;
-      rv = GetAccessibleRelated(nsIAccessibleRelation::RELATION_LABEL_FOR,
-                                getter_AddRefs(tabPanel));
-      NS_ENSURE_SUCCESS(rv, rv);
+      nsCOMPtr<nsIAccessible> tabPanel = nsRelUtils::
+        GetRelatedAccessible(this, nsIAccessibleRelation::RELATION_LABEL_FOR);
 
       if (nsAccUtils::Role(tabPanel) == nsIAccessibleRole::ROLE_PROPERTYPAGE) {
         nsCOMPtr<nsIAccessNode> tabPanelAccessNode(do_QueryInterface(tabPanel));
         nsCOMPtr<nsIDOMNode> tabPanelNode;
         tabPanelAccessNode->GetDOMNode(getter_AddRefs(tabPanelNode));
         NS_ENSURE_STATE(tabPanelNode);
 
         if (nsCoreUtils::IsAncestorOf(tabPanelNode, gLastFocusedNode))
@@ -2700,137 +2687,192 @@ nsIDOMNode* nsAccessible::GetAtomicRegio
 
   nsCOMPtr<nsIDOMNode> atomicRegion;
   if (atomic.EqualsLiteral("true")) {
     atomicRegion = do_QueryInterface(loopContent);
   }
   return atomicRegion;
 }
 
-/* nsIAccessible getAccessibleRelated(); */
-NS_IMETHODIMP nsAccessible::GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated)
+// nsIAccessible getRelationByType()
+NS_IMETHODIMP
+nsAccessible::GetRelationByType(PRUint32 aRelationType,
+                                nsIAccessibleRelation **aRelation)
 {
-  // When adding support for relations, make sure to add them to
-  // appropriate places in nsAccessibleWrap implementations
-  *aRelated = nsnull;
-
-  // Relationships are defined on the same content node
-  // that the role would be defined on
+  NS_ENSURE_ARG_POINTER(aRelation);
+  *aRelation = nsnull;
+
+  if (IsDefunct())
+    return NS_ERROR_FAILURE;
+
+  // Relationships are defined on the same content node that the role would be
+  // defined on.
   nsIContent *content = nsCoreUtils::GetRoleContent(mDOMNode);
-  if (!content) {
-    return NS_ERROR_FAILURE;  // Node already shut down
-  }
-
-  nsCOMPtr<nsIDOMNode> relatedNode;
-  nsAutoString relatedID;
-
-  // Search for the related DOM node according to the specified "relation type"
+  if (!content)
+    return NS_OK;
+
+  nsresult rv;
+
   switch (aRelationType)
   {
   case nsIAccessibleRelation::RELATION_LABEL_FOR:
     {
       if (content->Tag() == nsAccessibilityAtoms::label) {
-        nsIAtom *relatedIDAttr = content->IsNodeOfType(nsINode::eHTML) ?
+        nsIAtom *IDAttr = content->IsNodeOfType(nsINode::eHTML) ?
           nsAccessibilityAtoms::_for : nsAccessibilityAtoms::control;
-        content->GetAttr(kNameSpaceID_None, relatedIDAttr, relatedID);
+        rv = nsRelUtils::
+          AddTargetFromIDRefAttr(aRelationType, aRelation, content, IDAttr);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (rv != NS_OK_NO_RELATION_TARGET)
+          return NS_OK; // XXX bug 381599, avoid performance problems
       }
-      if (relatedID.IsEmpty()) {
-        relatedNode =
-          do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_labelledby));
-      }
-      break;
+
+      return nsRelUtils::
+        AddTargetFromNeighbour(aRelationType, aRelation, content,
+                               nsAccessibilityAtoms::aria_labelledby);
     }
+
   case nsIAccessibleRelation::RELATION_LABELLED_BY:
     {
-      if (!content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_labelledby, relatedID)) {
-        relatedNode = do_QueryInterface(nsCoreUtils::GetLabelContent(content));
-      }
-      break;
+      rv = nsRelUtils::
+        AddTargetFromIDRefsAttr(aRelationType, aRelation, content,
+                                nsAccessibilityAtoms::aria_labelledby);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (rv != NS_OK_NO_RELATION_TARGET)
+        return NS_OK; // XXX bug 381599, avoid performance problems
+
+      return nsRelUtils::
+        AddTargetFromContent(aRelationType, aRelation,
+                             nsCoreUtils::GetLabelContent(content));
     }
+
   case nsIAccessibleRelation::RELATION_DESCRIBED_BY:
     {
-      if (!content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_describedby, relatedID)) {
-        relatedNode = do_QueryInterface(
-          nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::control, nsAccessibilityAtoms::description));
-      }
-      break;
+      rv = nsRelUtils::
+        AddTargetFromIDRefsAttr(aRelationType, aRelation, content,
+                                nsAccessibilityAtoms::aria_describedby);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (rv != NS_OK_NO_RELATION_TARGET)
+        return NS_OK; // XXX bug 381599, avoid performance problems
+
+      return nsRelUtils::
+        AddTargetFromNeighbour(aRelationType, aRelation, content,
+                               nsAccessibilityAtoms::control,
+                               nsAccessibilityAtoms::description);
     }
+
   case nsIAccessibleRelation::RELATION_DESCRIPTION_FOR:
     {
-      relatedNode =
-        do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_describedby));
-
-      if (!relatedNode && content->Tag() == nsAccessibilityAtoms::description &&
+      rv = nsRelUtils::
+        AddTargetFromNeighbour(aRelationType, aRelation, content,
+                               nsAccessibilityAtoms::aria_describedby);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (rv != NS_OK_NO_RELATION_TARGET)
+        return NS_OK; // XXX bug 381599, avoid performance problems
+
+      if (content->Tag() == nsAccessibilityAtoms::description &&
           content->IsNodeOfType(nsINode::eXUL)) {
         // This affectively adds an optional control attribute to xul:description,
         // which only affects accessibility, by allowing the description to be
         // tied to a control.
-        content->GetAttr(kNameSpaceID_None,
-                         nsAccessibilityAtoms::control, relatedID);
+        return nsRelUtils::
+          AddTargetFromIDRefAttr(aRelationType, aRelation, content,
+                                 nsAccessibilityAtoms::control);
       }
-      break;
+
+      return NS_OK;
     }
+
   case nsIAccessibleRelation::RELATION_NODE_CHILD_OF:
     {
-      relatedNode =
-        do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_owns));
-      if (!relatedNode && mRoleMapEntry && mRoleMapEntry->role == nsIAccessibleRole::ROLE_OUTLINEITEM) {
-        // This is an ARIA tree that doesn't use owns, so we need to get the parent the hard way
-        nsAccUtils::GetARIATreeItemParent(this, content, aRelated);
-        return NS_OK;
+      rv = nsRelUtils::
+        AddTargetFromNeighbour(aRelationType, aRelation, content,
+                               nsAccessibilityAtoms::aria_owns);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (rv != NS_OK_NO_RELATION_TARGET)
+        return NS_OK; // XXX bug 381599, avoid performance problems
+
+      if (mRoleMapEntry &&
+          mRoleMapEntry->role == nsIAccessibleRole::ROLE_OUTLINEITEM) {
+        // This is an ARIA tree that doesn't use owns, so we need to get
+        // the parent the hard way.
+        nsCOMPtr<nsIAccessible> accTarget;
+        nsAccUtils::GetARIATreeItemParent(this, content,
+                                          getter_AddRefs(accTarget));
+        return nsRelUtils::AddTarget(aRelationType, aRelation, accTarget);
       }
+
       // If accessible is in its own Window then we should provide NODE_CHILD_OF relation
       // so that MSAA clients can easily get to true parent instead of getting to oleacc's
       // ROLE_WINDOW accessible which will prevent us from going up further (because it is
       // system generated and has no idea about the hierarchy above it).
       nsIFrame *frame = GetFrame();
       if (frame) {
         nsIView *view = frame->GetViewExternal();
         if (view) {
           nsIScrollableFrame *scrollFrame = do_QueryFrame(frame);
           if (scrollFrame || view->GetWidget()) {
-            return GetParent(aRelated);
+            nsCOMPtr<nsIAccessible> accTarget;
+            GetParent(getter_AddRefs(accTarget));
+            return nsRelUtils::AddTarget(aRelationType, aRelation, accTarget);
           }
         }
       }
-      break;
+
+      return NS_OK;
     }
+
   case nsIAccessibleRelation::RELATION_CONTROLLED_BY:
     {
-      relatedNode =
-        do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_controls));
-      break;
+      return nsRelUtils::
+        AddTargetFromNeighbour(aRelationType, aRelation, content,
+                               nsAccessibilityAtoms::aria_controls);
     }
+
   case nsIAccessibleRelation::RELATION_CONTROLLER_FOR:
     {
-      content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_controls, relatedID);
-      break;
+      return nsRelUtils::
+        AddTargetFromIDRefsAttr(aRelationType, aRelation, content,
+                                nsAccessibilityAtoms::aria_controls);
     }
+
   case nsIAccessibleRelation::RELATION_FLOWS_TO:
     {
-      content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_flowto, relatedID);
-      break;
+      return nsRelUtils::
+        AddTargetFromIDRefsAttr(aRelationType, aRelation, content,
+                                nsAccessibilityAtoms::aria_flowto);
     }
+
   case nsIAccessibleRelation::RELATION_FLOWS_FROM:
     {
-      relatedNode =
-        do_QueryInterface(nsCoreUtils::FindNeighbourPointingToNode(content, nsAccessibilityAtoms::aria_flowto));
-      break;
+      return nsRelUtils::
+        AddTargetFromNeighbour(aRelationType, aRelation, content,
+                               nsAccessibilityAtoms::aria_flowto);
     }
+
   case nsIAccessibleRelation::RELATION_DEFAULT_BUTTON:
     {
       if (content->IsNodeOfType(nsINode::eHTML)) {
         // HTML form controls implements nsIFormControl interface.
         nsCOMPtr<nsIFormControl> control(do_QueryInterface(content));
         if (control) {
           nsCOMPtr<nsIDOMHTMLFormElement> htmlform;
           control->GetForm(getter_AddRefs(htmlform));
           nsCOMPtr<nsIForm> form(do_QueryInterface(htmlform));
-          if (form)
-            relatedNode = do_QueryInterface(form->GetDefaultSubmitElement());
+          if (form) {
+            nsCOMPtr<nsIContent> formContent =
+              do_QueryInterface(form->GetDefaultSubmitElement());
+            return nsRelUtils::AddTargetFromContent(aRelationType, aRelation,
+                                                    formContent);
+          }
         }
       }
       else {
         // In XUL, use first <button default="true" .../> in the document
         nsCOMPtr<nsIDOMXULDocument> xulDoc = do_QueryInterface(content->GetDocument());
         nsCOMPtr<nsIDOMXULButtonElement> buttonEl;
         if (xulDoc) {
           nsCOMPtr<nsIDOMNodeList> possibleDefaultButtons;
@@ -2859,47 +2901,34 @@ NS_IMETHODIMP nsAccessible::GetAccessibl
                 xblDoc->GetAnonymousElementByAttribute(rootEl,
                                                       NS_LITERAL_STRING("default"),
                                                       NS_LITERAL_STRING("true"),
                                                       getter_AddRefs(possibleButtonEl));
                 buttonEl = do_QueryInterface(possibleButtonEl);
               }
             }
           }
-          relatedNode = do_QueryInterface(buttonEl);
+          nsCOMPtr<nsIContent> relatedContent(do_QueryInterface(buttonEl));
+          return nsRelUtils::AddTargetFromContent(aRelationType, aRelation,
+                                                  relatedContent);
         }
       }
-      break;
+      return NS_OK;
     }
+
   case nsIAccessibleRelation::RELATION_MEMBER_OF:
     {
-      relatedNode = GetAtomicRegion();
-      break;
+      nsCOMPtr<nsIContent> regionContent = do_QueryInterface(GetAtomicRegion());
+      return nsRelUtils::
+        AddTargetFromContent(aRelationType, aRelation, regionContent);
     }
+
   default:
-    return NS_ERROR_NOT_IMPLEMENTED;
+    return NS_ERROR_INVALID_ARG;
   }
-
-  if (!relatedID.IsEmpty()) {
-    // In some cases we need to get the relatedNode from an ID-style attribute
-    nsCOMPtr<nsIDOMDocument> domDoc;
-    mDOMNode->GetOwnerDocument(getter_AddRefs(domDoc));
-    NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
-    nsCOMPtr<nsIDOMElement> relatedEl;
-    domDoc->GetElementById(relatedID, getter_AddRefs(relatedEl));
-    relatedNode = do_QueryInterface(relatedEl);
-  }
-
-  // Return the corresponding accessible if the related DOM node is found
-  if (relatedNode) {
-    nsCOMPtr<nsIAccessibilityService> accService = GetAccService();
-    NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
-    accService->GetAccessibleInWeakShell(relatedNode, mWeakShell, aRelated);
-  }
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAccessible::GetRelationsCount(PRUint32 *aCount)
 {
   NS_ENSURE_ARG_POINTER(aCount);
   *aCount = 0;
 
@@ -2940,26 +2969,22 @@ nsAccessible::GetRelations(nsIArray **aR
   NS_ENSURE_ARG_POINTER(aRelations);
 
   nsCOMPtr<nsIMutableArray> relations = do_CreateInstance(NS_ARRAY_CONTRACTID);
   NS_ENSURE_TRUE(relations, NS_ERROR_OUT_OF_MEMORY);
 
   for (PRUint32 relType = nsIAccessibleRelation::RELATION_FIRST;
        relType < nsIAccessibleRelation::RELATION_LAST;
        ++relType) {
-    nsCOMPtr<nsIAccessible> accessible;
-    GetAccessibleRelated(relType, getter_AddRefs(accessible));
-
-    if (accessible) {
-      nsCOMPtr<nsIAccessibleRelation> relation =
-        new nsAccessibleRelationWrap(relType, accessible);
-      NS_ENSURE_TRUE(relation, NS_ERROR_OUT_OF_MEMORY);
-
+
+    nsCOMPtr<nsIAccessibleRelation> relation;
+    nsresult rv = GetRelationByType(relType, getter_AddRefs(relation));
+
+    if (NS_SUCCEEDED(rv) && relation)
       relations->AppendElement(relation, PR_FALSE);
-    }
   }
 
   NS_ADDREF(*aRelations = relations);
   return NS_OK;
 }
 
 /* void extendSelection (); */
 NS_IMETHODIMP nsAccessible::ExtendSelection()
--- a/accessible/src/base/nsAccessible.h
+++ b/accessible/src/base/nsAccessible.h
@@ -43,19 +43,20 @@
 
 #include "nsIAccessible.h"
 #include "nsPIAccessible.h"
 #include "nsIAccessibleHyperLink.h"
 #include "nsIAccessibleSelectable.h"
 #include "nsIAccessibleValue.h"
 #include "nsIAccessibleRole.h"
 #include "nsIAccessibleStates.h"
-#include "nsAccessibleRelationWrap.h"
 #include "nsIAccessibleEvent.h"
 
+#include "nsRelUtils.h"
+
 #include "nsIDOMNodeList.h"
 #include "nsINameSpaceManager.h"
 #include "nsWeakReference.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsIDOMDOMStringList.h"
 #include "nsARIAMap.h"
 
--- a/accessible/src/base/nsAccessibleRelation.cpp
+++ b/accessible/src/base/nsAccessibleRelation.cpp
@@ -33,64 +33,83 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsAccessibleRelation.h"
 
-#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
 #include "nsComponentManagerUtils.h"
 
 nsAccessibleRelation::
   nsAccessibleRelation(PRUint32 aType, nsIAccessible *aTarget) :
-  mType(aType), mTarget(aTarget)
+  mType(aType)
 {
+  mTargets = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (aTarget)
+    mTargets->AppendElement(aTarget, PR_FALSE);
 }
 
 // nsISupports
-NS_IMPL_ISUPPORTS1(nsAccessibleRelation, nsIAccessibleRelation)
+NS_IMPL_ISUPPORTS2(nsAccessibleRelation, nsAccessibleRelation,
+                   nsIAccessibleRelation)
 
 // nsIAccessibleRelation
 NS_IMETHODIMP
 nsAccessibleRelation::GetRelationType(PRUint32 *aType)
 {
   NS_ENSURE_ARG_POINTER(aType);
 
   *aType = mType;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAccessibleRelation::GetTargetsCount(PRUint32 *aCount)
 {
   NS_ENSURE_ARG_POINTER(aCount);
+  *aCount = 0;
 
-  *aCount = 1;
-  return NS_OK;
+  NS_ENSURE_TRUE(mTargets, NS_ERROR_NOT_INITIALIZED);
+
+  return mTargets->GetLength(aCount);
 }
 
 NS_IMETHODIMP
 nsAccessibleRelation::GetTarget(PRUint32 aIndex, nsIAccessible **aTarget)
 {
   NS_ENSURE_ARG_POINTER(aTarget);
+  *aTarget = nsnull;
 
-  if (aIndex != 0)
-    return NS_ERROR_INVALID_ARG;
+  NS_ENSURE_TRUE(mTargets, NS_ERROR_NOT_INITIALIZED);
 
-  NS_IF_ADDREF(*aTarget = mTarget);
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIAccessible> target = do_QueryElementAt(mTargets, aIndex, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  target.swap(*aTarget);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsAccessibleRelation::GetTargets(nsIArray **aRelations)
+nsAccessibleRelation::GetTargets(nsIArray **aTargets)
 {
-  NS_ENSURE_ARG_POINTER(aRelations);
+  NS_ENSURE_ARG_POINTER(aTargets);
+  *aTargets = nsnull;
 
-  nsCOMPtr<nsIMutableArray> relations = do_CreateInstance(NS_ARRAY_CONTRACTID);
-  NS_ENSURE_TRUE(relations, NS_ERROR_OUT_OF_MEMORY);
+  NS_ENSURE_TRUE(mTargets, NS_ERROR_NOT_INITIALIZED);
 
-  relations->AppendElement(mTarget, PR_FALSE);
-
-  NS_ADDREF(*aRelations = relations);
+  NS_ADDREF(*aTargets = mTargets);
   return NS_OK;
 }
+
+// nsAccessibleRelation
+nsresult
+nsAccessibleRelation::AddTarget(nsIAccessible *aTarget)
+{
+  NS_ENSURE_ARG(aTarget);
+
+  NS_ENSURE_TRUE(mTargets, NS_ERROR_NOT_INITIALIZED);
+
+  return mTargets->AppendElement(aTarget, PR_FALSE);
+}
--- a/accessible/src/base/nsAccessibleRelation.h
+++ b/accessible/src/base/nsAccessibleRelation.h
@@ -38,23 +38,46 @@
 
 #ifndef _nsAccessibleRelation_H_
 #define _nsAccessibleRelation_H_
 
 #include "nsIAccessible.h"
 #include "nsIAccessibleRelation.h"
 
 #include "nsCOMPtr.h"
+#include "nsIMutableArray.h"
 
+#define NS_ACCRELATION_IMPL_CID                         \
+{                                                       \
+  0xb20390d0,                                           \
+  0x40d3,                                               \
+  0x4c76,                                               \
+  { 0xb6, 0x2e, 0xc2, 0x30, 0xc8, 0xea, 0x0c, 0x1e }    \
+}
+
+/**
+ * Class represents an accessible relation.
+ */
 class nsAccessibleRelation: public nsIAccessibleRelation
 {
 public:
   nsAccessibleRelation(PRUint32 aType, nsIAccessible *aTarget);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIACCESSIBLERELATION
 
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCRELATION_IMPL_CID)
+
+  /**
+   * Add target for the given key.
+   *
+   * @param aTarget - accessible target for the given relation.
+   */
+  nsresult AddTarget(nsIAccessible *aTarget);
+
 private:
   PRUint32 mType;
-  nsCOMPtr<nsIAccessible> mTarget;
+  nsCOMPtr<nsIMutableArray> mTargets;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(nsAccessibleRelation, NS_ACCRELATION_IMPL_CID)
+
 #endif
--- a/accessible/src/base/nsCoreUtils.cpp
+++ b/accessible/src/base/nsCoreUtils.cpp
@@ -65,16 +65,17 @@
 #include "nsISelection2.h"
 #include "nsISelectionController.h"
 #include "nsPIDOMWindow.h"
 #include "nsGUIEvent.h"
 
 #include "nsContentCID.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
+#include "nsIMutableArray.h"
 
 static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID);
 
 PRBool
 nsCoreUtils::HasListener(nsIContent *aContent, const nsAString& aEventType)
 {
   NS_ENSURE_TRUE(aContent, PR_FALSE);
   nsCOMPtr<nsIEventListenerManager> listenerManager;
@@ -738,16 +739,61 @@ nsCoreUtils::GetLanguageFor(nsIContent *
   nsIContent *walkUp = aContent;
   while (walkUp && walkUp != aRootContent &&
          !walkUp->GetAttr(kNameSpaceID_None,
                           nsAccessibilityAtoms::lang, aLanguage))
     walkUp = walkUp->GetParent();
 }
 
 void
+nsCoreUtils::GetElementsByIDRefsAttr(nsIContent *aContent, nsIAtom *aAttr,
+                                     nsIArray **aRefElements)
+{
+  *aRefElements = nsnull;
+  
+  nsAutoString ids;
+  if (!aContent->GetAttr(kNameSpaceID_None, aAttr, ids))
+    return;
+  
+  ids.CompressWhitespace(PR_TRUE, PR_TRUE);
+  
+  nsCOMPtr<nsIDOMDocument> document = do_QueryInterface(aContent->GetOwnerDoc());
+  NS_ASSERTION(document, "The given node is not in document!");
+  if (!document)
+    return;
+
+  nsCOMPtr<nsIMutableArray> refElms = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+  while (!ids.IsEmpty()) {
+    nsAutoString id;
+    PRInt32 idLength = ids.FindChar(' ');
+    NS_ASSERTION(idLength != 0,
+                 "Should not be 0 because of CompressWhitespace() call above");
+
+    if (idLength == kNotFound) {
+      id = ids;
+      ids.Truncate();
+    } else {
+      id = Substring(ids, 0, idLength);
+      ids.Cut(0, idLength + 1);
+    }
+
+    nsCOMPtr<nsIDOMElement> refElement;
+    document->GetElementById(id, getter_AddRefs(refElement));
+    if (!refElement)
+      continue;
+
+    refElms->AppendElement(refElement, PR_FALSE);
+  }
+
+  NS_ADDREF(*aRefElements = refElms);
+  return;
+}
+
+void
 nsCoreUtils::GetComputedStyleDeclaration(const nsAString& aPseudoElt,
                                          nsIDOMNode *aNode,
                                          nsIDOMCSSStyleDeclaration **aCssDecl)
 {
   *aCssDecl = nsnull;
 
   nsCOMPtr<nsIDOMElement> domElement = GetDOMElementFor(aNode);
   if (!domElement)
--- a/accessible/src/base/nsCoreUtils.h
+++ b/accessible/src/base/nsCoreUtils.h
@@ -40,16 +40,17 @@
 #define nsCoreUtils_h_
 
 #include "nsAccessibilityAtoms.h"
 
 #include "nsIDOMNode.h"
 #include "nsIContent.h"
 #include "nsIFrame.h"
 #include "nsIDocShellTreeItem.h"
+#include "nsIArray.h"
 #include "nsPoint.h"
 
 class nsCoreUtils
 {
 public:
   /**
    * Return true if the given node has registered event listener of the given
    * type.
@@ -229,16 +230,27 @@ public:
    * @param aContent     [in] the given node
    * @param aRootContent [in] container of the given node
    * @param aLanguage    [out] language
    */
   static void GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
                              nsAString& aLanguage);
 
   /**
+   * Return the array of elements the given node is referred to by its
+   * IDRefs attribute.
+   *
+   * @param aContent     [in] the given node
+   * @param aAttr        [in] IDRefs attribute on the given node
+   * @param aRefElements [out] result array of elements
+   */
+  static void GetElementsByIDRefsAttr(nsIContent *aContent, nsIAtom *aAttr,
+                                      nsIArray **aRefElements);
+
+  /**
    * Return computed styles declaration for the given node.
    */
   static void GetComputedStyleDeclaration(const nsAString& aPseudoElt,
                                           nsIDOMNode *aNode,
                                           nsIDOMCSSStyleDeclaration **aCssDecl);
 
   /**
    * Search element in neighborhood of the given element by tag name and
new file mode 100644
--- /dev/null
+++ b/accessible/src/base/nsRelUtils.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Alexander Surkov <surkov.alexander@gmail.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsRelUtils.h"
+
+#include "nsAccessNode.h"
+
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+
+#include "nsAutoPtr.h"
+#include "nsArrayUtils.h"
+
+already_AddRefed<nsIAccessible>
+nsRelUtils::GetRelatedAccessible(nsIAccessible *aAccessible,
+                                 PRUint32 aRelationType)
+{
+  nsCOMPtr<nsIAccessibleRelation> relation;
+  nsresult rv = aAccessible->GetRelationByType(aRelationType,
+                                               getter_AddRefs(relation));
+  if (NS_FAILED(rv) || !relation)
+    return nsnull;
+
+  nsIAccessible *targetAccessible = nsnull;
+  rv = relation->GetTarget(0, &targetAccessible);
+  return targetAccessible;
+}
+
+nsresult
+nsRelUtils::AddTarget(PRUint32 aRelationType, nsIAccessibleRelation **aRelation,
+                      nsIAccessible *aTarget)
+{
+  if (!aTarget)
+    return NS_OK_NO_RELATION_TARGET;
+
+  if (*aRelation) {
+    nsRefPtr<nsAccessibleRelation> relation = QueryAccRelation(*aRelation);
+    return relation->AddTarget(aTarget);
+  }
+
+  *aRelation = new nsAccessibleRelationWrap(aRelationType, aTarget);
+  NS_ENSURE_TRUE(*aRelation, NS_ERROR_OUT_OF_MEMORY);
+
+  NS_ADDREF(*aRelation);
+  return NS_OK;
+}
+
+nsresult
+nsRelUtils::AddTargetFromContent(PRUint32 aRelationType,
+                                 nsIAccessibleRelation **aRelation,
+                                 nsIContent *aContent)
+{
+  if (!aContent)
+    return NS_OK_NO_RELATION_TARGET;
+
+  nsCOMPtr<nsIAccessibilityService> accService = nsAccessNode::GetAccService();
+  nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aContent));
+
+  nsCOMPtr<nsIAccessible> accessible;
+  accService->GetAccessibleFor(node, getter_AddRefs(accessible));
+  return AddTarget(aRelationType, aRelation, accessible);
+}
+
+nsresult
+nsRelUtils::AddTargetFromIDRefAttr(PRUint32 aRelationType,
+                                   nsIAccessibleRelation **aRelation,
+                                   nsIContent *aContent, nsIAtom *aAttr)
+{
+  nsAutoString id;
+  if (!aContent->GetAttr(kNameSpaceID_None, aAttr, id))
+    return NS_OK_NO_RELATION_TARGET;
+
+  nsCOMPtr<nsIDOMDocument> document =
+    do_QueryInterface(aContent->GetOwnerDoc());
+  NS_ASSERTION(document, "The given node is not in document!");
+  if (!document)
+    return NS_OK_NO_RELATION_TARGET;
+
+  nsCOMPtr<nsIDOMElement> refElm;
+  document->GetElementById(id, getter_AddRefs(refElm));
+
+  nsCOMPtr<nsIContent> refContent(do_QueryInterface(refElm));
+  return AddTargetFromContent(aRelationType, aRelation, refContent);
+}
+
+nsresult
+nsRelUtils::AddTargetFromIDRefsAttr(PRUint32 aRelationType,
+                                    nsIAccessibleRelation **aRelation,
+                                    nsIContent *aContent, nsIAtom *aAttr)
+{
+  nsCOMPtr<nsIArray> refElms;
+  nsCoreUtils::GetElementsByIDRefsAttr(aContent, aAttr, getter_AddRefs(refElms));
+
+  if (!refElms)
+    return NS_OK_NO_RELATION_TARGET;
+
+  PRUint32 count = 0;
+  nsresult rv = refElms->GetLength(&count);
+  if (NS_FAILED(rv) || count == 0)
+    return NS_OK_NO_RELATION_TARGET;
+
+  nsCOMPtr<nsIContent> content;
+  for (PRUint32 idx = 0; idx < count; idx++) {
+    content = do_QueryElementAt(refElms, idx, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = AddTargetFromContent(aRelationType, aRelation, content);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsRelUtils::AddTargetFromNeighbour(PRUint32 aRelationType,
+                                   nsIAccessibleRelation **aRelation,
+                                   nsIContent *aContent,
+                                   nsIAtom *aNeighboutAttr,
+                                   nsIAtom *aNeighboutTagName)
+{
+  return AddTargetFromContent(
+    aRelationType, aRelation,
+    nsCoreUtils::FindNeighbourPointingToNode(aContent, aNeighboutAttr,
+                                             aNeighboutTagName));
+}
new file mode 100644
--- /dev/null
+++ b/accessible/src/base/nsRelUtils.h
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Alexander Surkov <surkov.alexander@gmail.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _nsRelUtils_H_
+#define _nsRelUtils_H_
+
+#include "nsAccessibleRelationWrap.h"
+
+#include "nsIAtom.h"
+#include "nsIContent.h"
+
+// Used by AddTarget...() methods. Returned when can't get target accessible.
+#define NS_OK_NO_RELATION_TARGET \
+NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x24)
+
+/**
+ * Utils class designed to work with accessible relations.
+ */
+class nsRelUtils
+{
+public:
+  /**
+   * Return first target of the relation of the given relation type for
+   * the given accessible.
+   *
+   * @param aAccessible   [in] the accessible to get an relation
+   * @param aRelationType [in] relation type
+   * @return              an accessible
+   */
+  static already_AddRefed<nsIAccessible>
+    GetRelatedAccessible(nsIAccessible *aAccessible, PRUint32 aRelationType);
+
+  /**
+   * Create the relation if the given relation is null. Add target to it
+   * which is the given accessible.
+   *
+   * @param  aRelationType  [in] relation type
+   * @param  aRelation      [in, out] relation object
+   * @param  aTarget        [in] accessible object
+   */
+  static nsresult AddTarget(PRUint32 aRelationType,
+                            nsIAccessibleRelation **aRelation,
+                            nsIAccessible *aTarget);
+
+  /**
+   * Create the relation if the given relation is null and add the target to it
+   * which is the accessible for the given node.
+   *
+   * @param  aRelationType  [in] relation type
+   * @param  aRelation      [in, out] relation object
+   * @param  aContent       [in] accessible node
+   */
+  static nsresult AddTargetFromContent(PRUint32 aRelationType,
+                                       nsIAccessibleRelation **aRelation,
+                                       nsIContent *aContent);
+
+  /**
+   * Create the relation if the given relation is null and add the target to it
+   * pointed by IDRef attribute on the given node.
+   *
+   * @param  aRelationType  [in] relation type
+   * @param  aRelation      [in, out] relation object
+   * @param  aContent       [in] node having the given IDRef attribute
+   * @param  aAttr          [in] IDRef attribute
+   */
+  static nsresult AddTargetFromIDRefAttr(PRUint32 aRelationType,
+                                         nsIAccessibleRelation **aRelation,
+                                         nsIContent *aContent, nsIAtom *aAttr);
+
+  /**
+   * Create the relation if the given relation is null and add the targets to it
+   * that are pointed by IDRefs attribute on the given node.
+   *
+   * @param  aRelationType  [in] relation type
+   * @param  aRelation      [in, out] relation object
+   * @param  aContent       [in] node having the given IDRefs attribute
+   * @param  aAttr          [in] IDRefs attribute
+   */
+  static nsresult AddTargetFromIDRefsAttr(PRUint32 aRelationType,
+                                          nsIAccessibleRelation **aRelation,
+                                          nsIContent *aContent, nsIAtom *aAttr);
+
+  /**
+   * Create the relation if the given relation is null and add the target to it
+   * found in neighbour tree.
+   *
+   * @param  aRelationType      [in] relation type
+   * @param  aRelation          [in, out] relation object
+   * @param  aContent           [in] node defining neighbour tree
+   * @param  aNeighboutAttr     [in] IDRef attribute of the node in neighbour
+   *                            tree pointing to node defining neighbour tree
+   * @param  aNeighboutTagName  [in, optional] tag name of the node in neighbour
+   *                            tree having IDRef attribute pointed by previous
+   *                            argument
+   */
+  static nsresult AddTargetFromNeighbour(PRUint32 aRelationType,
+                                         nsIAccessibleRelation **aRelation,
+                                         nsIContent *aContent,
+                                         nsIAtom *aNeighboutAttr,
+                                         nsIAtom *aNeighboutTagName = nsnull);
+
+  /**
+   * Query nsAccessibleRelation from the given nsIAccessibleRelation.
+   */
+  static already_AddRefed<nsAccessibleRelation>
+  QueryAccRelation(nsIAccessibleRelation *aRelation)
+  {
+    nsAccessibleRelation* relation = nsnull;
+    if (aRelation)
+      CallQueryInterface(aRelation, &relation);
+
+    return relation;
+  }
+};
+
+#endif
--- a/accessible/src/base/nsRootAccessible.cpp
+++ b/accessible/src/base/nsRootAccessible.cpp
@@ -1024,36 +1024,39 @@ nsRootAccessible::GetContentDocShell(nsI
         NS_ADDREF(aStart = contentTreeItem);
         return aStart;
       }
     }
   }
   return nsnull;
 }
 
-NS_IMETHODIMP nsRootAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                                     nsIAccessible **aRelated)
+NS_IMETHODIMP
+nsRootAccessible::GetRelationByType(PRUint32 aRelationType,
+                                    nsIAccessibleRelation **aRelation)
 {
-  *aRelated = nsnull;
+  NS_ENSURE_ARG_POINTER(aRelation);
+  *aRelation = nsnull;
 
   if (!mDOMNode || aRelationType != nsIAccessibleRelation::RELATION_EMBEDS) {
-    return nsDocAccessibleWrap::GetAccessibleRelated(aRelationType, aRelated);
+    return nsDocAccessibleWrap::GetRelationByType(aRelationType, aRelation);
   }
 
   nsCOMPtr<nsIDocShellTreeItem> treeItem =
     nsCoreUtils::GetDocShellTreeItemFor(mDOMNode);
   nsCOMPtr<nsIDocShellTreeItem> contentTreeItem = GetContentDocShell(treeItem);
   // there may be no content area, so we need a null check
   if (contentTreeItem) {
     nsCOMPtr<nsIAccessibleDocument> accDoc =
       GetDocAccessibleFor(contentTreeItem, PR_TRUE);
 
-    if (accDoc)
-      CallQueryInterface(accDoc, aRelated);
+    nsCOMPtr<nsIAccessible> acc(do_QueryInterface(accDoc));
+    return nsRelUtils::AddTarget(aRelationType, aRelation, acc);
   }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP nsRootAccessible::FireDocLoadEvents(PRUint32 aEventType)
 {
   if (!mDocument || !mWeakShell) {
     return NS_OK;  // Document has been shut down
   }
--- a/accessible/src/base/nsRootAccessible.h
+++ b/accessible/src/base/nsRootAccessible.h
@@ -72,18 +72,18 @@ class nsRootAccessible : public nsDocAcc
   public:
     nsRootAccessible(nsIDOMNode *aDOMNode, nsIWeakReference* aShell);
     virtual ~nsRootAccessible();
 
     // nsIAccessible
     NS_IMETHOD GetName(nsAString& aName);
     NS_IMETHOD GetParent(nsIAccessible * *aParent);
     NS_IMETHOD GetRole(PRUint32 *aRole);
-    NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType,
-                                    nsIAccessible **aRelated);
+    NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                                 nsIAccessibleRelation **aRelation);
 
     // ----- nsPIAccessibleDocument -----------------------
     NS_IMETHOD FireDocLoadEvents(PRUint32 aEventType);
 
     // ----- nsIDOMEventListener --------------------------
     NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
 
     // nsAccessNode
--- a/accessible/src/html/nsHTMLFormControlAccessible.cpp
+++ b/accessible/src/html/nsHTMLFormControlAccessible.cpp
@@ -614,72 +614,58 @@ nsHTMLGroupboxAccessible::GetNameInterna
   if (legendContent) {
     return AppendFlatStringFromSubtree(legendContent, &aName);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsHTMLGroupboxAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                               nsIAccessible **aRelated)
+nsHTMLGroupboxAccessible::GetRelationByType(PRUint32 aRelationType,
+                                            nsIAccessibleRelation **aRelation)
 {
-  if (!mDOMNode) {
-    return NS_ERROR_FAILURE;
-  }
-  NS_ENSURE_ARG_POINTER(aRelated);
-
-  *aRelated = nsnull;
-
-  nsresult rv = nsHyperTextAccessibleWrap::GetAccessibleRelated(aRelationType, aRelated);
-  if (NS_FAILED(rv) || *aRelated) {
-    // Either the node is shut down, or another relation mechanism has been used
-    return rv;
-  }
+  nsresult rv = nsHyperTextAccessibleWrap::GetRelationByType(aRelationType,
+                                                             aRelation);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   if (aRelationType == nsIAccessibleRelation::RELATION_LABELLED_BY) {
     // No override for label, so use <legend> for this <fieldset>
-    nsCOMPtr<nsIDOMNode> legendNode = do_QueryInterface(GetLegend());
-    if (legendNode) {
-      GetAccService()->GetAccessibleInWeakShell(legendNode, mWeakShell, aRelated);
-    }
+    return nsRelUtils::
+      AddTargetFromContent(aRelationType, aRelation, GetLegend());
   }
 
   return NS_OK;
 }
 
 nsHTMLLegendAccessible::nsHTMLLegendAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell):
 nsHyperTextAccessibleWrap(aNode, aShell)
 { 
 }
 
 NS_IMETHODIMP
-nsHTMLLegendAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                             nsIAccessible **aRelated)
+nsHTMLLegendAccessible::GetRelationByType(PRUint32 aRelationType,
+                                          nsIAccessibleRelation **aRelation)
 {
-  *aRelated = nsnull;
-
-  nsresult rv = nsHyperTextAccessibleWrap::GetAccessibleRelated(aRelationType, aRelated);
-  if (NS_FAILED(rv) || *aRelated) {
-    // Either the node is shut down, or another relation mechanism has been used
-    return rv;
-  }
+  nsresult rv = nsHyperTextAccessibleWrap::
+    GetRelationByType(aRelationType, aRelation);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   if (aRelationType == nsIAccessibleRelation::RELATION_LABEL_FOR) {
     // Look for groupbox parent
-    nsCOMPtr<nsIContent> content = do_QueryInterface(mDOMNode);
-    if (!content) {
-      return NS_ERROR_FAILURE;  // Node already shut down
-    }
     nsCOMPtr<nsIAccessible> groupboxAccessible = GetParent();
     if (nsAccUtils::Role(groupboxAccessible) == nsIAccessibleRole::ROLE_GROUPING) {
-      nsCOMPtr<nsIAccessible> testLabelAccessible;
-      groupboxAccessible->GetAccessibleRelated(nsIAccessibleRelation::RELATION_LABELLED_BY,
-                                               getter_AddRefs(testLabelAccessible));
+      // XXX: if group box exposes more than one relation of the given type
+      // then we fail.
+      nsCOMPtr<nsIAccessible> testLabelAccessible =
+        nsRelUtils::GetRelatedAccessible(groupboxAccessible,
+                                         nsIAccessibleRelation::RELATION_LABELLED_BY);
+
       if (testLabelAccessible == this) {
-        // We're the first child of the parent groupbox
-        NS_ADDREF(*aRelated = groupboxAccessible);
+        // We're the first child of the parent groupbox, see
+        // nsHTMLGroupboxAccessible::GetRelationByType().
+        return nsRelUtils::
+          AddTarget(aRelationType, aRelation, groupboxAccessible);
       }
     }
   }
 
   return NS_OK;
 }
--- a/accessible/src/html/nsHTMLFormControlAccessible.h
+++ b/accessible/src/html/nsHTMLFormControlAccessible.h
@@ -135,26 +135,31 @@ public:
 
 class nsHTMLGroupboxAccessible : public nsHyperTextAccessibleWrap
 {
 public:
   nsHTMLGroupboxAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell);
 
   // nsIAccessible
   NS_IMETHOD GetRole(PRUint32 *aRole); 
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated);
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 
   // nsAccessible
   virtual nsresult GetNameInternal(nsAString& aName);
 
 protected:
   nsIContent* GetLegend();
 };
 
 class nsHTMLLegendAccessible : public nsHyperTextAccessibleWrap
 {
 public:
   nsHTMLLegendAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell);
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated);
+
+  // nsIAccessible
   NS_IMETHOD GetRole(PRUint32 *aRole) { *aRole = nsIAccessibleRole::ROLE_LABEL; return NS_OK; }
+
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 };
 
 #endif  
--- a/accessible/src/html/nsHTMLTableAccessible.cpp
+++ b/accessible/src/html/nsHTMLTableAccessible.cpp
@@ -236,34 +236,27 @@ nsHTMLTableAccessible::GetAttributesInte
     aAttributes->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"),
                                    NS_LITERAL_STRING("true"), oldValueUnused);
   }
   
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsHTMLTableAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                            nsIAccessible **aRelated)
+nsHTMLTableAccessible::GetRelationByType(PRUint32 aRelationType,
+                                         nsIAccessibleRelation **aRelation)
 {
-  NS_ENSURE_ARG_POINTER(aRelated);
-  *aRelated = nsnull;
-
-  if (!mDOMNode) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsresult rv = nsAccessibleWrap::GetAccessibleRelated(aRelationType, aRelated);
-  if (NS_FAILED(rv) || *aRelated) {
-    // Either the node is shut down, or another relation mechanism has been used
-    return rv;
-  }
+  nsresult rv = nsAccessibleWrap::GetRelationByType(aRelationType,
+                                                    aRelation);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   if (aRelationType == nsIAccessibleRelation::RELATION_DESCRIBED_BY) {
-    return GetCaption(aRelated);
+    nsCOMPtr<nsIAccessible> accCaption;
+    GetCaption(getter_AddRefs(accCaption));
+    return nsRelUtils::AddTarget(aRelationType, aRelation, accCaption);
   }
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsHTMLTableAccessible::GetCaption(nsIAccessible **aCaption)
@@ -1249,33 +1242,26 @@ nsHTMLTableHeadAccessible::GetRows(PRInt
   nsCOMPtr<nsIDOMHTMLCollection> rows;
   rv = head->GetRows(getter_AddRefs(rows));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return rows->GetLength((PRUint32 *)aRows);
 }
 
 NS_IMETHODIMP
-nsHTMLCaptionAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                              nsIAccessible **aRelated)
+nsHTMLCaptionAccessible::GetRelationByType(PRUint32 aRelationType,
+                                           nsIAccessibleRelation **aRelation)
 {
-  NS_ENSURE_ARG_POINTER(aRelated);
-  *aRelated = nsnull;
-
-  if (!mDOMNode) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsresult rv = nsHyperTextAccessible::GetAccessibleRelated(aRelationType, aRelated);
-  if (NS_FAILED(rv) || *aRelated) {
-    // Either the node is shut down, or another relation mechanism has been used
-    return rv;
-  }
+  nsresult rv = nsHyperTextAccessible::GetRelationByType(aRelationType,
+                                                         aRelation);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   if (aRelationType == nsIAccessibleRelation::RELATION_DESCRIPTION_FOR) {
-    return GetParent(aRelated);
+    nsCOMPtr<nsIAccessible> accParent;
+    GetParent(getter_AddRefs(accParent));
+    return nsRelUtils::AddTarget(aRelationType, aRelation, accParent);
   }
 
   return NS_OK;
 }
 
 
 
--- a/accessible/src/html/nsHTMLTableAccessible.h
+++ b/accessible/src/html/nsHTMLTableAccessible.h
@@ -70,17 +70,18 @@ public:
   nsHTMLTableAccessible(nsIDOMNode* aDomNode, nsIWeakReference* aShell);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIACCESSIBLETABLE
 
   // nsIAccessible
   NS_IMETHOD GetRole(PRUint32 *aResult); 
   NS_IMETHOD GetDescription(nsAString& aDescription);
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated);
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 
   // nsAccessible
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual nsresult GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState);
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
 
   // nsHTMLTableAccessible
 
@@ -155,12 +156,14 @@ class nsHTMLCaptionAccessible : public n
 {
 public:
   nsHTMLCaptionAccessible(nsIDOMNode *aDomNode, nsIWeakReference *aShell) :
     nsHyperTextAccessibleWrap(aDomNode, aShell) { }
 
   // nsIAccessible
   NS_IMETHOD GetRole(PRUint32 *aRole)
     { *aRole = nsIAccessibleRole::ROLE_CAPTION; return NS_OK; }
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated);
+
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 };
 
 #endif  
--- a/accessible/src/msaa/nsAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsAccessibleWrap.cpp
@@ -982,23 +982,18 @@ STDMETHODIMP nsAccessibleWrap::accNaviga
       break;
     case NAVRELATION_DESCRIPTION_FOR:
       xpRelation = nsIAccessibleRelation::RELATION_DESCRIPTION_FOR;
       break;
   }
 
   pvarEndUpAt->vt = VT_EMPTY;
 
-  if (xpRelation) {
-    nsresult rv = GetAccessibleRelated(xpRelation,
-                                       getter_AddRefs(xpAccessibleResult));
-    if (rv == NS_ERROR_NOT_IMPLEMENTED) {
-      return E_NOTIMPL;
-    }
-  }
+  if (xpRelation)
+    xpAccessibleResult = nsRelUtils::GetRelatedAccessible(this, xpRelation);
 
   if (xpAccessibleResult) {
     pvarEndUpAt->pdispVal = NativeAccessible(xpAccessibleResult);
     pvarEndUpAt->vt = VT_DISPATCH;
     return NS_OK;
   }
 } __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
   return E_FAIL;
--- a/accessible/src/xul/nsXULFormControlAccessible.cpp
+++ b/accessible/src/xul/nsXULFormControlAccessible.cpp
@@ -416,53 +416,52 @@ NS_IMETHODIMP nsXULGroupboxAccessible::G
 {
   *aRole = nsIAccessibleRole::ROLE_GROUPING;
   return NS_OK;
 }
 
 nsresult
 nsXULGroupboxAccessible::GetNameInternal(nsAString& aName)
 {
-  nsCOMPtr<nsIAccessible> label;
-  GetAccessibleRelated(nsIAccessibleRelation::RELATION_LABELLED_BY,
-                       getter_AddRefs(label));
+  // XXX: we use the first related accessible only.
+  nsCOMPtr<nsIAccessible> label =
+    nsRelUtils::GetRelatedAccessible(this, nsIAccessibleRelation::RELATION_LABELLED_BY);
+
   if (label) {
     return label->GetName(aName);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXULGroupboxAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                              nsIAccessible **aRelated)
+nsXULGroupboxAccessible::GetRelationByType(PRUint32 aRelationType,
+                                           nsIAccessibleRelation **aRelation)
 {
-  *aRelated = nsnull;
-
-  nsresult rv = nsAccessibleWrap::GetAccessibleRelated(aRelationType, aRelated);
-  if (NS_FAILED(rv) || *aRelated) {
-    // Either the node is shut down, or another relation mechanism has been used
-    return rv;
-  }
+  nsresult rv = nsAccessibleWrap::GetRelationByType(aRelationType, aRelation);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   if (aRelationType == nsIAccessibleRelation::RELATION_LABELLED_BY) {
     // The label for xul:groupbox is generated from xul:label that is
     // inside the anonymous content of the xul:caption.
     // The xul:label has an accessible object but the xul:caption does not
     nsCOMPtr<nsIAccessible> testLabelAccessible;
     while (NextChild(testLabelAccessible)) {
       if (nsAccUtils::Role(testLabelAccessible) == nsIAccessibleRole::ROLE_LABEL) {
         // Ensure that it's our label
-        nsCOMPtr<nsIAccessible> testGroupboxAccessible;
-        testLabelAccessible->GetAccessibleRelated(nsIAccessibleRelation::RELATION_LABEL_FOR,
-                                                  getter_AddRefs(testGroupboxAccessible));
+        // XXX: we'll fail if group accessible expose more than one relation
+        // targets.
+        nsCOMPtr<nsIAccessible> testGroupboxAccessible =
+          nsRelUtils::GetRelatedAccessible(testLabelAccessible,
+                                           nsIAccessibleRelation::RELATION_LABEL_FOR);
+
         if (testGroupboxAccessible == this) {
           // The <label> points back to this groupbox
-          NS_ADDREF(*aRelated = testLabelAccessible);
-          return NS_OK;
+          return nsRelUtils::
+            AddTarget(aRelationType, aRelation, testLabelAccessible);
         }
       }
     }
   }
 
   return NS_OK;
 }
 
--- a/accessible/src/xul/nsXULFormControlAccessible.h
+++ b/accessible/src/xul/nsXULFormControlAccessible.h
@@ -103,17 +103,18 @@ private:
 
 class nsXULGroupboxAccessible : public nsAccessibleWrap
 {
 public:
   nsXULGroupboxAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell);
 
   // nsIAccessible
   NS_IMETHOD GetRole(PRUint32 *_retval); 
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated);
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 
   // nsAccessible
   virtual nsresult GetNameInternal(nsAString& aName);
 };
 
 class nsXULProgressMeterAccessible : public nsFormControlAccessible
 {
   NS_DECL_ISUPPORTS_INHERITED
--- a/accessible/src/xul/nsXULTabAccessible.cpp
+++ b/accessible/src/xul/nsXULTabAccessible.cpp
@@ -125,58 +125,39 @@ nsXULTabAccessible::GetStateInternal(PRU
     PRBool selected = PR_FALSE;
     if (NS_SUCCEEDED(tab->GetSelected(&selected)) && selected)
       *aState |= nsIAccessibleStates::STATE_SELECTED;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXULTabAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                         nsIAccessible **aRelatedAccessible)
+nsXULTabAccessible::GetRelationByType(PRUint32 aRelationType,
+                                      nsIAccessibleRelation **aRelation)
 {
-  NS_ENSURE_ARG_POINTER(aRelatedAccessible);
-  *aRelatedAccessible = nsnull;
-
-  if (!mDOMNode)
-    return NS_ERROR_FAILURE;
-
-  nsresult rv = nsLeafAccessible::GetAccessibleRelated(aRelationType,
-                                                       aRelatedAccessible);
+  nsresult rv = nsLeafAccessible::GetRelationByType(aRelationType,
+                                                    aRelation);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (*aRelatedAccessible ||
-      aRelationType != nsIAccessibleRelation::RELATION_LABEL_FOR)
+  if (aRelationType != nsIAccessibleRelation::RELATION_LABEL_FOR)
     return NS_OK;
 
   // Expose 'LABEL_FOR' relation on tab accessible for tabpanel accessible.
   // XXX: It makes sense to require the interface from xul:tab to get linked
   // tabpanel element.
   nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
 
   // Check whether tab and tabpanel are related by 'linkedPanel' attribute on
   // xul:tab element.
-  nsAutoString linkedPanelID;
-  content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::linkedPanel,
-                   linkedPanelID);
-
-  if (!linkedPanelID.IsEmpty()) {
-    nsCOMPtr<nsIDOMDocument> document;
-    mDOMNode->GetOwnerDocument(getter_AddRefs(document));
-    NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+  rv = nsRelUtils::AddTargetFromIDRefAttr(aRelationType, aRelation, content,
+                                          nsAccessibilityAtoms::linkedPanel);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsIDOMElement> linkedPanel;
-    document->GetElementById(linkedPanelID, getter_AddRefs(linkedPanel));
-    if (linkedPanel) {
-      nsCOMPtr<nsIDOMNode> linkedPanelNode(do_QueryInterface(linkedPanel));
-      GetAccService()->GetAccessibleInWeakShell(linkedPanelNode, mWeakShell,
-                                                aRelatedAccessible);
-      return NS_OK;
-    }
-  }
+  if (rv != NS_OK_NO_RELATION_TARGET)
+    return NS_OK;
 
   // If there is no 'linkedPanel' attribute on xul:tab element then we
   // assume tab and tabpanels are related 1 to 1. We follow algorithm from
   // the setter 'selectedIndex' of tabbox.xml#tabs binding.
 
   nsCOMPtr<nsIAccessible> tabsAcc = GetParent();
   NS_ENSURE_TRUE(nsAccUtils::Role(tabsAcc) == nsIAccessibleRole::ROLE_PAGETABLIST,
                  NS_ERROR_FAILURE);
@@ -200,20 +181,18 @@ nsXULTabAccessible::GetAccessibleRelated
   nsCOMPtr<nsIAccessible> tabBoxAcc;
   tabsAcc->GetParent(getter_AddRefs(tabBoxAcc));
   NS_ENSURE_TRUE(nsAccUtils::Role(tabBoxAcc) == nsIAccessibleRole::ROLE_PANE,
                  NS_ERROR_FAILURE);
 
   tabBoxAcc->GetFirstChild(getter_AddRefs(childAcc));
   while (childAcc) {
     if (nsAccUtils::Role(childAcc) == nsIAccessibleRole::ROLE_PROPERTYPAGE) {
-      if (tabIndex == 0) {
-        NS_ADDREF(*aRelatedAccessible = childAcc);
-        return NS_OK;
-      }
+      if (tabIndex == 0)
+        return nsRelUtils::AddTarget(aRelationType, aRelation, childAcc);
 
       tabIndex--;
     }
 
     nsCOMPtr<nsIAccessible> acc;
     childAcc->GetNextSibling(getter_AddRefs(acc));
     childAcc.swap(acc);
   }
@@ -314,31 +293,23 @@ nsXULTabpanelAccessible::GetRole(PRUint3
 {
   NS_ENSURE_ARG_POINTER(aRole);
 
   *aRole = nsIAccessibleRole::ROLE_PROPERTYPAGE;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXULTabpanelAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                              nsIAccessible **aRelatedAccessible)
+nsXULTabpanelAccessible::GetRelationByType(PRUint32 aRelationType,
+                                           nsIAccessibleRelation **aRelation)
 {
-  NS_ENSURE_ARG_POINTER(aRelatedAccessible);
-  *aRelatedAccessible = nsnull;
-
-  if (!mDOMNode)
-    return NS_ERROR_FAILURE;
-
-  nsresult rv = nsAccessibleWrap::GetAccessibleRelated(aRelationType,
-                                                       aRelatedAccessible);
+  nsresult rv = nsAccessibleWrap::GetRelationByType(aRelationType, aRelation);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (*aRelatedAccessible ||
-      aRelationType != nsIAccessibleRelation::RELATION_LABELLED_BY)
+  if (aRelationType != nsIAccessibleRelation::RELATION_LABELLED_BY)
     return NS_OK;
 
   // Expose 'LABELLED_BY' relation on tabpanel accessible for tab accessible.
   nsCOMPtr<nsIAccessible> tabBoxAcc;
   GetParent(getter_AddRefs(tabBoxAcc));
   NS_ENSURE_TRUE(nsAccUtils::Role(tabBoxAcc) == nsIAccessibleRole::ROLE_PANE,
                  NS_ERROR_FAILURE);
 
@@ -379,18 +350,17 @@ nsXULTabpanelAccessible::GetAccessibleRe
         nsCOMPtr<nsIDOMNode> tabNode;
         tabAccNode->GetDOMNode(getter_AddRefs(tabNode));
         nsCOMPtr<nsIContent> tabContent(do_QueryInterface(tabNode));
         NS_ENSURE_TRUE(tabContent, NS_ERROR_FAILURE);
 
         if (tabContent->AttrValueIs(kNameSpaceID_None,
                                     nsAccessibilityAtoms::linkedPanel, atomID,
                                     eCaseMatters)) {
-          NS_ADDREF(*aRelatedAccessible = childAcc);
-          return NS_OK;
+          return nsRelUtils::AddTarget(aRelationType, aRelation, childAcc);
         }
       }
 
       if (tabpanelIndex == 0) {
         foundTabAcc = childAcc;
         if (!atomID)
           break;
       }
@@ -398,12 +368,11 @@ nsXULTabpanelAccessible::GetAccessibleRe
       tabpanelIndex--;
     }
 
     nsCOMPtr<nsIAccessible> acc;
     childAcc->GetNextSibling(getter_AddRefs(acc));
     childAcc.swap(acc);
   }
 
-  NS_IF_ADDREF(*aRelatedAccessible = foundTabAcc);
   return NS_OK;
 }
 
--- a/accessible/src/xul/nsXULTabAccessible.h
+++ b/accessible/src/xul/nsXULTabAccessible.h
@@ -53,18 +53,18 @@ public:
 
   nsXULTabAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell);
 
   // nsIAccessible
   NS_IMETHOD GetRole(PRUint32 *_retval); 
   NS_IMETHOD GetNumActions(PRUint8 *_retval);
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 index);
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType,
-                                  nsIAccessible **aRelatedAccessible);
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 
   // nsAccessible
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
   virtual nsresult GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState);
 };
 
 /** 
   * Contains a tabs object and a tabPanels object. A complete
@@ -103,14 +103,14 @@ public:
  */
 class nsXULTabpanelAccessible : public nsAccessibleWrap
 {
 public:
   nsXULTabpanelAccessible(nsIDOMNode *aNode, nsIWeakReference *aShell);
 
   // nsIAccessible
   NS_IMETHOD GetRole(PRUint32 *aRole);
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType,
-                                  nsIAccessible **aRelatedAccessible);
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 };
 
 #endif
 
--- a/accessible/src/xul/nsXULTextAccessible.cpp
+++ b/accessible/src/xul/nsXULTextAccessible.cpp
@@ -76,38 +76,36 @@ nsXULTextAccessible::GetStateInternal(PR
 
   // Labels and description have read only state
   // They are not focusable or selectable
   *aState |= nsIAccessibleStates::STATE_READONLY;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXULTextAccessible::GetAccessibleRelated(PRUint32 aRelationType,
-                                          nsIAccessible **aRelated)
+nsXULTextAccessible::GetRelationByType(PRUint32 aRelationType,
+                                       nsIAccessibleRelation **aRelation)
 {
   nsresult rv =
-    nsHyperTextAccessibleWrap::GetAccessibleRelated(aRelationType, aRelated);
+    nsHyperTextAccessibleWrap::GetRelationByType(aRelationType, aRelation);
   NS_ENSURE_SUCCESS(rv, rv);
-  if (*aRelated) {
-    return NS_OK;
-  }
 
   nsIContent *content = nsCoreUtils::GetRoleContent(mDOMNode);
   if (!content)
-    return NS_ERROR_FAILURE;
+    return NS_OK;
 
   if (aRelationType == nsIAccessibleRelation::RELATION_LABEL_FOR) {
     // Caption is the label for groupbox
     nsIContent *parent = content->GetParent();
     if (parent && parent->Tag() == nsAccessibilityAtoms::caption) {
       nsCOMPtr<nsIAccessible> parentAccessible;
       GetParent(getter_AddRefs(parentAccessible));
       if (nsAccUtils::Role(parentAccessible) == nsIAccessibleRole::ROLE_GROUPING)
-        parentAccessible.swap(*aRelated);
+        return nsRelUtils::
+          AddTarget(aRelationType, aRelation, parentAccessible);
     }
   }
 
   return NS_OK;
 }
 
 /**
   * For XUL tooltip
--- a/accessible/src/xul/nsXULTextAccessible.h
+++ b/accessible/src/xul/nsXULTextAccessible.h
@@ -49,18 +49,18 @@ class nsIWeakReference;
 class nsXULTextAccessible : public nsHyperTextAccessibleWrap
 {
 
 public:
   nsXULTextAccessible(nsIDOMNode* aDomNode, nsIWeakReference* aShell);
 
   // nsIAccessible
   NS_IMETHOD GetRole(PRUint32 *aRole) { *aRole = nsIAccessibleRole::ROLE_LABEL; return NS_OK; }
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType,
-                                  nsIAccessible **aRelated);
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 
   // nsAccessible
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual nsresult GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState);
 };
 
 class nsXULTooltipAccessible : public nsLeafAccessible
 {
--- a/accessible/src/xul/nsXULTreeAccessible.cpp
+++ b/accessible/src/xul/nsXULTreeAccessible.cpp
@@ -1276,41 +1276,50 @@ NS_IMETHODIMP nsXULTreeitemAccessible::T
   mTreeView->GetSelection(getter_AddRefs(selection));
   if (selection)
     selection->SetCurrentIndex(mRow);
 
   // focus event will be fired here
   return nsAccessible::TakeFocus();
 }
 
-NS_IMETHODIMP nsXULTreeitemAccessible::GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated)
+NS_IMETHODIMP
+nsXULTreeitemAccessible::GetRelationByType(PRUint32 aRelationType,
+                                           nsIAccessibleRelation **aRelation)
 {
+  NS_ENSURE_ARG_POINTER(aRelation);
+  *aRelation = nsnull;
+
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
-  *aRelated = nsnull;
   if (aRelationType == nsIAccessibleRelation::RELATION_NODE_CHILD_OF) {
     PRInt32 columnIndex;
     if (NS_SUCCEEDED(mColumn->GetIndex(&columnIndex)) && columnIndex == 0) {
       PRInt32 parentIndex;
       if (NS_SUCCEEDED(mTreeView->GetParentIndex(mRow, &parentIndex))) {
-        if (parentIndex == -1) {
-          NS_IF_ADDREF(*aRelated = mParent);
-          return NS_OK;
-        } else {
-          nsCOMPtr<nsIAccessibleTreeCache> cache =
-            do_QueryInterface(mParent);
-          return cache->GetCachedTreeitemAccessible(parentIndex, mColumn, aRelated);
-        }
+        if (parentIndex == -1)
+          return nsRelUtils::AddTarget(aRelationType, aRelation, mParent);
+  
+        nsCOMPtr<nsIAccessibleTreeCache> cache =
+          do_QueryInterface(mParent);
+        nsCOMPtr<nsIAccessible> accParent;
+        nsresult rv = cache->
+          GetCachedTreeitemAccessible(parentIndex, mColumn,
+                                      getter_AddRefs(accParent));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        return nsRelUtils::AddTarget(aRelationType, aRelation, accParent);
       }
     }
+
     return NS_OK;
   }
 
-  return nsAccessible::GetAccessibleRelated(aRelationType, aRelated);
+  return nsAccessible::GetRelationByType(aRelationType, aRelation);
 }
 
 // attribute AString nsIAccessibleTreeItem::cachedName
 NS_IMETHODIMP
 nsXULTreeitemAccessible::GetCachedName(nsAString &aName)
 {
   aName = mCachedName;
   return NS_OK;
--- a/accessible/src/xul/nsXULTreeAccessible.h
+++ b/accessible/src/xul/nsXULTreeAccessible.h
@@ -124,17 +124,18 @@ public:
   NS_IMETHOD GetNextSibling(nsIAccessible **_retval);
   NS_IMETHOD GetPreviousSibling(nsIAccessible **_retval);
 
   NS_IMETHOD DoAction(PRUint8 index);
   NS_IMETHOD GetBounds(PRInt32 *x, PRInt32 *y, PRInt32 *width, PRInt32 *height);
   NS_IMETHOD SetSelected(PRBool aSelect); 
   NS_IMETHOD TakeFocus(void); 
 
-  NS_IMETHOD GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated);
+  NS_IMETHOD GetRelationByType(PRUint32 aRelationType,
+                               nsIAccessibleRelation **aRelation);
 
   // nsIAccessNode
   NS_IMETHOD GetUniqueID(void **aUniqueID);
 
   // nsAccessNode
   virtual PRBool IsDefunct();
   virtual nsresult Init();
   virtual nsresult Shutdown();
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -278,22 +278,46 @@ function eventTypeToString(aEventType)
 /**
  * Convert relation type to human readable string.
  */
 function relationTypeToString(aRelationType)
 {
   return gAccRetrieval.getStringRelationType(aRelationType);
 }
 
+/**
+ * Return pretty name for identifier, it may be ID, DOM node or accessible.
+ */
+function prettyName(aIdentifier)
+{
+  if (aIdentifier instanceof nsIAccessible) {
+    var acc = getAccessible(aIdentifier, [nsIAccessNode]);
+    return getNodePrettyName(acc.DOMNode);
+  }
+
+  if (aIdentifier instanceof nsIDOMNode)
+    return getNodePrettyName(aIdentifier);
+
+  return " '" + aIdentifier + "' ";
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Private
 ////////////////////////////////////////////////////////////////////////////////
 
 ////////////////////////////////////////////////////////////////////////////////
 // Accessible general
 
 function initialize()
 {
   gAccRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
     getService(nsIAccessibleRetrieval);
 }
 
 addLoadEvent(initialize);
+
+function getNodePrettyName(aNode)
+{
+  if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE && aNode.hasAttribute("id"))
+    return " '" + aNode.getAttribute("id") + "' ";
+
+  return " '" + aNode.localName + " node' ";
+}
--- a/accessible/tests/mochitest/relations.js
+++ b/accessible/tests/mochitest/relations.js
@@ -19,64 +19,111 @@ const RELATION_POPUP_FOR = nsIAccessible
 const RELATION_SUBWINDOW_OF = nsIAccessibleRelation.RELATION_SUBWINDOW_OF;
 
 ////////////////////////////////////////////////////////////////////////////////
 // General
 
 /**
  * Test the accessible relation.
  *
- * @param aIdentifier        [in] identifier to get an accessible implementing
- *                           the given interfaces may be ID attribute or DOM
- *                           element or accessible object
- * @param aRelType           [in] relation type (see constants above)
- * @param aRelatedIdentifier [in] identifier of expected related accessible
+ * @param aIdentifier          [in] identifier to get an accessible, may be ID
+ *                             attribute or DOM element or accessible object
+ * @param aRelType             [in] relation type (see constants above)
+ * @param aRelatedIdentifiers  [in] identifier or array of identifiers of
+ *                             expected related accessibles
  */
-function testRelation(aIdentifier, aRelType, aRelatedIdentifier)
+function testRelation(aIdentifier, aRelType, aRelatedIdentifiers)
 {
-  var actualRelatedAcc = getRelatedAccessible(aIdentifier, aRelType);
+  var relation = getRelationByType(aIdentifier, aRelType);
 
   var relDescr = getRelationErrorMsg(aIdentifier, aRelType);
-  if (!actualRelatedAcc && !aRelatedIdentifier) {
-    ok(true, "No" + relDescr);
+  var relDescrStart = getRelationErrorMsg(aIdentifier, aRelType, true);
+
+  if (!relation || !relation.targetsCount) {
+    if (!aRelatedIdentifiers) {
+      ok(true, "No" + relDescr);
+      return;
+    }
+
+    var msg = relDescrStart + "has no expected targets: '" +
+      prettyName(aRelatedIdentifiers) + "'";
+
+    ok(false, msg);
+    return;
+
+  } else if (!aRelatedIdentifiers) {
+    ok(false, "There are unexpected targets of " + relDescr);
     return;
   }
 
-  var relatedAcc = getAccessible(aRelatedIdentifier);
-  if (!relatedAcc)
+  var relatedIds = (aRelatedIdentifiers instanceof Array) ?
+  aRelatedIdentifiers : [aRelatedIdentifiers];
+
+  var targets = [];
+   for (var idx = 0; idx < relatedIds.length; idx++)
+     targets.push(getAccessible(relatedIds[idx]));
+
+  if (targets.length != relatedIds.length)
     return;
 
-  is(actualRelatedAcc, relatedAcc,
-      aRelatedIdentifier + " is not a target of" + relDescr);
+  var actualTargets = relation.getTargets();
+
+  // Check if all given related accessibles are targets of obtained relation.
+  for (var idx = 0; idx < targets.length; idx++) {
+    var isFound = false;
+    var enumerate = actualTargets.enumerate();
+    while (enumerate.hasMoreElements()) {
+      var relatedAcc = enumerate.getNext().QueryInterface(nsIAccessible);
+      if (targets[idx] == relatedAcc) {
+        isFound = true;
+        break;
+      }
+    }
+
+    ok(isFound, relatedIds[idx] + " is not a target of" + relDescr);
+  }
+
+  // Check if all obtained targets are given related accessibles.
+  var enumerate = actualTargets.enumerate();
+  while (enumerate.hasMoreElements()) {
+    var relatedAcc = enumerate.getNext().QueryInterface(nsIAccessible);
+    for (var idx = 0; idx < targets.length && relatedAcc != targets[idx]; idx++);
+
+    if (idx == targets.length)
+      ok(false, "There is unexpected target" + prettyName(relatedAcc) + "of" + relDescr);
+  }
 }
 
 /**
  * Return related accessible for the given relation type.
  *
- * @param aIdentifier  [in] identifier to get an accessible implementing
- *                     the given interfaces may be ID attribute or DOM
- *                     element or accessible object
+ * @param aIdentifier  [in] identifier to get an accessible, may be ID attribute
+ *                     or DOM element or accessible object
  * @param aRelType     [in] relation type (see constants above)
  */
-function getRelatedAccessible(aIdentifier, aRelType)
+function getRelationByType(aIdentifier, aRelType)
 {
   var acc = getAccessible(aIdentifier);
   if (!acc)
     return;
 
-  var relatedAcc = null;
+  var relation = null;
   try {
-    relatedAcc = acc.getAccessibleRelated(aRelType);
+    relation = acc.getRelationByType(aRelType);
   } catch (e) {
     ok(false, "Can't get" + getRelationErrorMsg(aIdentifier, aRelType));
   }
 
-  return relatedAcc;
+  return relation;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Private implementation details
 
-function getRelationErrorMsg(aIdentifier, aRelType)
+function getRelationErrorMsg(aIdentifier, aRelType, aIsStartSentence)
 {
   var relStr = relationTypeToString(aRelType);
-  return " relation of '" + relStr + "' type for " + aIdentifier + ".";
+  var msg = aIsStartSentence ? "Relation of '" : " relation of '";
+  msg += relStr + "' type for '" + prettyName(aIdentifier) + "'";
+  msg += aIsStartSentence ? " " : ".";
+
+  return msg;
 }
--- a/accessible/tests/mochitest/test_relations.html
+++ b/accessible/tests/mochitest/test_relations.html
@@ -19,44 +19,65 @@
       // html:label@for
       testRelation("label1", RELATION_LABEL_FOR, "checkbox1");
       testRelation("checkbox1", RELATION_LABELLED_BY, "label1");
 
       // aria-labelledby
       testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
       testRelation("checkbox2", RELATION_LABELLED_BY, "label2");
 
+      // aria-labelledby, multiple relations
+      testRelation("label3", RELATION_LABEL_FOR, "checkbox3");
+      testRelation("label4", RELATION_LABEL_FOR, "checkbox3");
+      testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]);
+
       // aria-describedby
-      testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox3");
-      testRelation("checkbox3", RELATION_DESCRIBED_BY, "descr1");
+      testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox4");
+      testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr1");
 
-      // aria_owns
+      // aria-describedby, multiple relations
+      testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5");
+      testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5");
+      testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]);
+
+      // aria_owns, multiple relations
       testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree");
+      testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
 
       // 'node child of' relation for outlineitem role
-      testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree");
-      testRelation("treeitem4", RELATION_NODE_CHILD_OF, "treeitem3");
+      testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
+      testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
 
       // 'node child of' relation for the document having window, returns
       // direct accessible parent (fixed in bug 419770).
       var iframeElmObj = {};
       var iframeAcc = getAccessible("iframe", null, iframeElmObj);
       var iframeDoc = iframeElmObj.value.contentDocument;
       var iframeDocAcc = getAccessible(iframeDoc);
       testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
 
       // aria-controls
       testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
       testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
 
+      // aria-controls, multiple relations
+      testRelation("lr1", RELATION_CONTROLLED_BY, "button");
+      testRelation("lr2", RELATION_CONTROLLED_BY, "button");
+      testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]);
+
       // aria-flowto
       testRelation("flowto", RELATION_FLOWS_TO, "flowfrom");
       testRelation("flowfrom", RELATION_FLOWS_FROM, "flowto");
 
+      // aria-flowto, multiple relations
+      testRelation("flowto1", RELATION_FLOWS_TO, ["flowfrom1", "flowfrom2"]);
+      testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1");
+      testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1");
+
       // 'default button' relation
       testRelation("input", RELATION_DEFAULT_BUTTON, "submit");
 
       // 'described by'/'description for' relation for html:table and
       // html:caption
       testRelation("caption", RELATION_DESCRIPTION_FOR, "table");
       testRelation("table", RELATION_DESCRIBED_BY, "caption");
 
@@ -99,38 +120,56 @@
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <label id="label1" for="checkbox1">label</label>
   <input id="checkbox1" />
 
   <span id="label2">label</span>
-  <span role="checkbox" id="checkbox2" aria-labelledby="label2" />
+  <span role="checkbox" id="checkbox2" aria-labelledby="label2"></span>
+
+  <span id="label3">label1</span>
+  <span id="label4">label2</span>
+  <span role="checkbox" id="checkbox3" aria-labelledby="label3 label4"></span>
 
   <span id="descr1">description</span>
-  <span role="checkbox" id="checkbox3" aria-describedby="descr1" />
+  <span role="checkbox" id="checkbox4" aria-describedby="descr1"></span>
+
+  <span id="descr2">description1</span>
+  <span id="descr3">description2</span>
+  <span role="checkbox" id="checkbox5" aria-describedby="descr2 descr3"></span>
 
   <div role="treeitem" id="treeitem1">Yellow</div>
-  <div id="tree" role="tree" aria-owns="treeitem1">
-    <div role="treeitem" id="treeitem2">Blue</div>
-    <div role="treeitem" id="treeitem3" aria-level="1">Green</div>
-    <div role="treeitem" id="treeitem4" aria-level="2">Light green</div>
+  <div role="treeitem" id="treeitem2">Orange</div>
+  <div id="tree" role="tree" aria-owns="treeitem1 treeitem2">
+    <div role="treeitem" id="treeitem3">Blue</div>
+    <div role="treeitem" id="treeitem4" aria-level="1">Green</div>
+    <div role="treeitem" id="treeitem5" aria-level="2">Light green</div>
   </div>
 
   <iframe id="iframe"></iframe>
 
   <div id="tablist" role="tablist">
     <div id="tab" role="tab" aria-controls="tabpanel">tab</div>
   </div>
   <div id="tabpanel" role="tabpanel">tabpanel</div>
 
+  <div id="lr1" aria-live="assertive">1</div>
+  <div id="lr2" aria-live="assertive">a</div>
+  <input type="button" id="button" aria-controls="lr1 lr2"
+         onclick="getNode('lr1').textContent += '1'; getNode('lr2').textContent += 'a';"/>
+
   <span id="flowto" aria-flowto="flowfrom">flow to</span>
   <span id="flowfrom">flow from</span>
 
+  <span id="flowto1" aria-flowto="flowfrom1 flowfrom2">flow to</span>
+  <span id="flowfrom1">flow from</span>
+  <span id="flowfrom2">flow from</span>
+
   <form>
     <input id="input" />
     <input type="submit" id="submit" />
   </form>
 
   <table id="table">
     <caption id="caption">tabple caption</caption>
     <tr>
--- a/accessible/tests/mochitest/test_relations.xul
+++ b/accessible/tests/mochitest/test_relations.xul
@@ -23,48 +23,69 @@
       // xul:label@control
       testRelation("label1", RELATION_LABEL_FOR, "checkbox1");
       testRelation("checkbox1", RELATION_LABELLED_BY, "label1");
 
       // aria-labelledby
       testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
       testRelation("checkbox2", RELATION_LABELLED_BY, "label2");
 
+      // aria-labelledby, multiple relations
+      testRelation("label3", RELATION_LABEL_FOR, "checkbox3");
+      testRelation("label4", RELATION_LABEL_FOR, "checkbox3");
+      testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]);
+
       // aria-describedby
-      testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox3");
-      testRelation("checkbox3", RELATION_DESCRIBED_BY, "descr1");
+      testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox4");
+      testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr1");
+
+      // aria-describedby, multiple relations
+      testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5");
+      testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5");
+      testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]);
 
       // xul:description@control
-      testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox4");
-      testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr2");
+      testRelation("descr4", RELATION_DESCRIPTION_FOR, "checkbox6");
+      testRelation("checkbox6", RELATION_DESCRIBED_BY, "descr4");
 
-      // aria_owns
+      // aria_owns, multiple relations
       testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree");
+      testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
 
       // 'node child of' relation for outlineitem role
-      testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree");
-      testRelation("treeitem4", RELATION_NODE_CHILD_OF, "treeitem3");
+      testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
+      testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
 
       // 'node child of' relation for the document having window, returns
       // direct accessible parent (fixed in bug 419770).
       var iframeElmObj = {};
       var iframeAcc = getAccessible("iframe", null, iframeElmObj);
       var iframeDoc = iframeElmObj.value.contentDocument;
       var iframeDocAcc = getAccessible(iframeDoc);
       testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
 
       // aria-controls
       testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
       testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
 
+      // aria-controls, multiple relations
+      testRelation("lr1", RELATION_CONTROLLED_BY, "button");
+      testRelation("lr2", RELATION_CONTROLLED_BY, "button");
+      testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]);
+
       // aria-flowto
       testRelation("flowto", RELATION_FLOWS_TO, "flowfrom");
       testRelation("flowfrom", RELATION_FLOWS_FROM, "flowto");
 
+      // aria-flowto, multiple relations
+      testRelation("flowto1", RELATION_FLOWS_TO, ["flowfrom1", "flowfrom2"]);
+      testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1");
+      testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1");
+
       // 'default button' relation
       testRelation("textbox", RELATION_DEFAULT_BUTTON, "submit");
 
       // 'embeds' relation for root accessible
       var docAcc = null;
       var parentOfDocAcc = null;
       var parentDocAcc = getAccessible(document);
       do {
@@ -108,36 +129,56 @@
     </body>
 
     <label id="label1" control="checkbox1">label</label>
     <checkbox id="checkbox1"/>
 
     <description id="label2">label</description>
     <description role="checkbox" id="checkbox2" aria-labelledby="label2"/>
 
+    <description id="label3">label</description>
+    <description id="label4">label</description>
+    <description role="checkbox" id="checkbox3"
+                 aria-labelledby="label3 label4"/>
+
     <description id="descr1">description</description>
-    <description role="checkbox" id="checkbox3" aria-describedby="descr1"/>
+    <description role="checkbox" id="checkbox4" aria-describedby="descr1"/>
 
-    <description id="descr2" control="checkbox4">description</description>
-    <checkbox id="checkbox4"/>
+    <description id="descr2">label</description>
+    <description id="descr3">label</description>
+    <description role="checkbox" id="checkbox5"
+                 aria-describedby="descr2 descr3"/>
+
+    <description id="descr4" control="checkbox6">description</description>
+    <checkbox id="checkbox6"/>
 
     <description role="treeitem" id="treeitem1">Yellow</description>
-    <vbox id="tree" role="tree" aria-owns="treeitem1">
-      <description role="treeitem" id="treeitem2">Blue</description>
-      <description role="treeitem" id="treeitem3" aria-level="1">Green</description>
-      <description role="treeitem" id="treeitem4" aria-level="2">Light green</description>
+    <description role="treeitem" id="treeitem2">Orange</description>
+    <vbox id="tree" role="tree" aria-owns="treeitem1 treeitem2">
+      <description role="treeitem" id="treeitem3">Blue</description>
+      <description role="treeitem" id="treeitem4" aria-level="1">Green</description>
+      <description role="treeitem" id="treeitem5" aria-level="2">Light green</description>
     </vbox>
 
     <iframe id="iframe"/>
 
     <hbox id="tablist" role="tablist">
       <description id="tab" role="tab" aria-controls="tabpanel">tab</description>
     </hbox>
     <description id="tabpanel" role="tabpanel">tabpanel</description>
 
+    <description id="lr1" aria-live="assertive">1</description>
+    <description id="lr2" aria-live="assertive">a</description>
+    <button id="button" aria-controls="lr1 lr2" label="button"
+            oncommand="getNode('lr1').textContent += '1'; getNode('lr2').textContent += 'a';"/>
+
+    <description id="flowto1" aria-flowto="flowfrom1 flowfrom2">flow to</description>
+    <description id="flowfrom1">flow from</description>
+    <description id="flowfrom2">flow from</description>
+
     <description id="flowto" aria-flowto="flowfrom">flow to</description>
     <description id="flowfrom">flow from</description>
 
     <textbox id="textbox"/>
     <button id="submit" default="true" label="Default"/>
 
     <groupbox id="groupbox">
       <caption label="caption"/>