Bug 527461 - Implement RELATION_NODE_PARENT_OF, r=tbsaunde
authorAlexander Surkov <surkov.alexander@gmail.com>
Tue, 26 Feb 2013 16:17:10 +0900
changeset 122977 2d9e36ff5c1b774ab7b66dfd26cd41addc12dfb7
parent 122976 ce5e9bac612a7b2584dd468aa8030658b0583c06
child 122978 4245801bd26f73171f9c82f89a1765c0f2867fb1
push id24367
push userryanvm@gmail.com
push dateTue, 26 Feb 2013 15:44:35 +0000
treeherdermozilla-central@a4f834dd884f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde
bugs527461
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 527461 - Implement RELATION_NODE_PARENT_OF, r=tbsaunde
accessible/public/nsIAccessibleRelation.idl
accessible/src/atk/AccessibleWrap.cpp
accessible/src/base/AccGroupInfo.cpp
accessible/src/base/AccGroupInfo.h
accessible/src/base/AccIterator.cpp
accessible/src/base/AccIterator.h
accessible/src/base/Relation.h
accessible/src/base/Role.h
accessible/src/base/nsAccessibilityService.h
accessible/src/generic/Accessible.cpp
accessible/src/msaa/AccessibleWrap.cpp
accessible/src/windows/ia2/ia2AccessibleRelation.h
accessible/src/xul/XULTreeAccessible.cpp
accessible/src/xul/XULTreeAccessible.h
accessible/tests/mochitest/common.js
accessible/tests/mochitest/relations.js
accessible/tests/mochitest/relations/test_general.html
accessible/tests/mochitest/relations/test_tree.xul
--- a/accessible/public/nsIAccessibleRelation.idl
+++ b/accessible/public/nsIAccessibleRelation.idl
@@ -5,119 +5,114 @@
 
 #include "nsISupports.idl"
 #include "nsIArray.idl"
 
 interface nsIAccessible;
 
 /**
  * This interface gives access to an accessible's set of relations.
- * Be carefull, do not change constants until ATK has a structure to map gecko
- * constants into ATK constants.
  */
-[scriptable, uuid(f42a1589-70ab-4704-877f-4a9162bbe188)]
+[scriptable, uuid(9f85fc0d-2969-48e6-b822-68140f7e5770)]
 interface nsIAccessibleRelation : nsISupports
 {
+  /**
+   * This object is labelled by a target object.
+   */
+  const unsigned long RELATION_LABELLED_BY = 0x00;
 
-  const unsigned long RELATION_NUL = 0x00;
+  /**
+   * This object is label for a target object.
+   */
+  const unsigned long RELATION_LABEL_FOR = 0x01;
+
+  /**
+   * This object is described by the target object.
+   */
+  const unsigned long RELATION_DESCRIBED_BY = 0x02;
+
+  /**
+   * This object is describes the target object.
+   */
+  const unsigned long RELATION_DESCRIPTION_FOR = 0x3;
+
+  /**
+   * This object is a child of a target object.
+   */
+  const unsigned long RELATION_NODE_CHILD_OF = 0x4;
+
+  /**
+   * This object is a parent of a target object. A dual relation to
+   * RELATION_NODE_CHILD_OF
+   */
+  const unsigned long RELATION_NODE_PARENT_OF = 0x5;
 
   /**
    * Some attribute of this object is affected by a target object.
    */
-  const unsigned long RELATION_CONTROLLED_BY = 0x01;
-
-  // First relation
-  const unsigned long RELATION_FIRST = RELATION_CONTROLLED_BY;
+  const unsigned long RELATION_CONTROLLED_BY = 0x06;
 
   /**
    * This object is interactive and controls some attribute of a target object.
    */
-  const unsigned long RELATION_CONTROLLER_FOR = 0x02;
+  const unsigned long RELATION_CONTROLLER_FOR = 0x07;
 
   /**
-   * This object is label for a target object.
+   * Content flows from this object to a target object, i.e. has content that
+   * flows logically to another object in a sequential way, e.g. text flow.
    */
-  const unsigned long RELATION_LABEL_FOR = 0x03;
+  const unsigned long RELATION_FLOWS_TO = 0x08;
 
   /**
-   * This object is labelled by a target object.
+   * Content flows to this object from a target object, i.e. has content that
+   * flows logically from another object in a sequential way, e.g. text flow.
    */
-  const unsigned long RELATION_LABELLED_BY = 0x04;
+  const unsigned long RELATION_FLOWS_FROM = 0x09;
 
   /**
    * This object is a member of a group of one or more objects. When there is
    * more than one object in the group each member may have one and the same
    * target, e.g. a grouping object.  It is also possible that each member has
    * multiple additional targets, e.g. one for every other member in the group.
    */
-  const unsigned long RELATION_MEMBER_OF = 0x05;
-
-  /**
-   * This object is a child of a target object.
-   */
-  const unsigned long RELATION_NODE_CHILD_OF = 0x06;
-
-  /**
-   * Content flows from this object to a target object, i.e. has content that
-   * flows logically to another object in a sequential way, e.g. text flow.
-   */
-  const unsigned long RELATION_FLOWS_TO = 0x07;
-
-  /**
-   * Content flows to this object from a target object, i.e. has content that
-   * flows logically from another object in a sequential way, e.g. text flow.
-   */
-  const unsigned long RELATION_FLOWS_FROM = 0x08;
+  const unsigned long RELATION_MEMBER_OF = 0x0a;
 
   /**
    * This object is a sub window of a target object.
    */
-  const unsigned long RELATION_SUBWINDOW_OF = 0x09;
+  const unsigned long RELATION_SUBWINDOW_OF = 0x0b;
 
   /**
    * This object embeds a target object. This relation can be used on the
    * OBJID_CLIENT accessible for a top level window to show where the content
    * areas are.
    */
-  const unsigned long RELATION_EMBEDS = 0x0a;
+  const unsigned long RELATION_EMBEDS = 0x0c;
 
   /**
    * This object is embedded by a target object.
    */
-  const unsigned long RELATION_EMBEDDED_BY = 0x0b;
+  const unsigned long RELATION_EMBEDDED_BY = 0x0d;
 
   /**
    * This object is a transient component related to the target object. When
    * this object is activated the target object doesn't lose focus.
    */
-  const unsigned long RELATION_POPUP_FOR = 0x0c;
+  const unsigned long RELATION_POPUP_FOR = 0x0e;
 
   /**
    * This object is a parent window of the target object.
    */
-  const unsigned long RELATION_PARENT_WINDOW_OF = 0x0d;
-
-  /**
-   * This object is described by the target object.
-   */
-  const unsigned long RELATION_DESCRIBED_BY = 0x0e;
-
-  /**
-   * This object is describes the target object.
-   */
-  const unsigned long RELATION_DESCRIPTION_FOR = 0x0f;
-
-  // Last relation that is standard to desktop accessibility APIs
-  const unsigned long RELATION_LAST = RELATION_DESCRIPTION_FOR;
+  const unsigned long RELATION_PARENT_WINDOW_OF = 0x0f;
 
   /**
    * Part of a form/dialog with a related default button. It is used for
-   * MSAA only, no for IA2 nor ATK.
+   * MSAA/XPCOM, it isn't for IA2 or ATK.
    */
-  const unsigned long RELATION_DEFAULT_BUTTON = 0x4000;
+  const unsigned long RELATION_DEFAULT_BUTTON = 0x10;
 
   /**
    * Returns the type of the relation.
    */
   readonly attribute unsigned long relationType;
 
   /**
    * Returns the number of targets for this relation.
--- a/accessible/src/atk/AccessibleWrap.cpp
+++ b/accessible/src/atk/AccessibleWrap.cpp
@@ -848,31 +848,39 @@ refRelationSetCB(AtkObject *aAtkObj)
 {
   AtkRelationSet* relation_set =
     ATK_OBJECT_CLASS(parent_class)->ref_relation_set(aAtkObj);
 
   AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
   if (!accWrap)
     return relation_set;
 
-  uint32_t relationTypes[] = {
-    nsIAccessibleRelation::RELATION_LABELLED_BY,
-    nsIAccessibleRelation::RELATION_LABEL_FOR,
-    nsIAccessibleRelation::RELATION_NODE_CHILD_OF,
+  // Keep in sync with AtkRelationType enum.
+  static const uint32_t relationTypes[] = {
     nsIAccessibleRelation::RELATION_CONTROLLED_BY,
     nsIAccessibleRelation::RELATION_CONTROLLER_FOR,
-    nsIAccessibleRelation::RELATION_EMBEDS,
+    nsIAccessibleRelation::RELATION_LABEL_FOR,
+    nsIAccessibleRelation::RELATION_LABELLED_BY,
+    nsIAccessibleRelation::RELATION_MEMBER_OF,
+    nsIAccessibleRelation::RELATION_NODE_CHILD_OF,
     nsIAccessibleRelation::RELATION_FLOWS_TO,
     nsIAccessibleRelation::RELATION_FLOWS_FROM,
+    nsIAccessibleRelation::RELATION_SUBWINDOW_OF,
+    nsIAccessibleRelation::RELATION_EMBEDS,
+    nsIAccessibleRelation::RELATION_EMBEDDED_BY,
+    nsIAccessibleRelation::RELATION_POPUP_FOR,
+    nsIAccessibleRelation::RELATION_PARENT_WINDOW_OF,
     nsIAccessibleRelation::RELATION_DESCRIBED_BY,
     nsIAccessibleRelation::RELATION_DESCRIPTION_FOR,
+    nsIAccessibleRelation::RELATION_NODE_PARENT_OF
   };
 
   for (uint32_t i = 0; i < ArrayLength(relationTypes); i++) {
-    AtkRelationType atkType = static_cast<AtkRelationType>(relationTypes[i]);
+    // Shift to 1 to skip ATK_RELATION_NULL.
+    AtkRelationType atkType = static_cast<AtkRelationType>(i + 1);
     AtkRelation* atkRelation =
       atk_relation_set_get_relation_by_type(relation_set, atkType);
     if (atkRelation)
       atk_relation_set_remove(relation_set, atkRelation);
 
     Relation rel(accWrap->RelationByType(relationTypes[i]));
     nsTArray<AtkObject*> targets;
     Accessible* tempAcc = nullptr;
--- a/accessible/src/base/AccGroupInfo.cpp
+++ b/accessible/src/base/AccGroupInfo.cpp
@@ -133,16 +133,65 @@ AccGroupInfo::AccGroupInfo(Accessible* a
   }
 
   // Previous sibling of parent group is a tree item, this is the
   // conceptual tree item parent.
   if (parentPrevSiblingRole == roles::OUTLINEITEM)
     mParent = parentPrevSibling;
 }
 
+Accessible*
+AccGroupInfo::FirstItemOf(Accessible* aContainer)
+{
+  // ARIA trees can be arranged by ARIA groups, otherwise aria-level works.
+  a11y::role containerRole = aContainer->Role();
+  Accessible* item = aContainer->NextSibling();
+  if (item) {
+    if (containerRole == roles::OUTLINEITEM && item->Role() == roles::GROUPING)
+      item = item->FirstChild();
+
+    AccGroupInfo* itemGroupInfo = item->GetGroupInfo();
+    if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer)
+      return item;
+  }
+
+  // Otherwise it can be a direct child.
+  item = aContainer->FirstChild();
+  if (item && IsConceptualParent(BaseRole(item->Role()), containerRole))
+    return item;
+
+  return nullptr;
+}
+
+Accessible*
+AccGroupInfo::NextItemTo(Accessible* aItem)
+{
+  AccGroupInfo* groupInfo = aItem->GetGroupInfo();
+  if (!groupInfo)
+    return nullptr;
+
+  // If the item in middle of the group then search next item in siblings.
+  if (groupInfo->PosInSet() >= groupInfo->SetSize())
+    return nullptr;
+
+  Accessible* parent = aItem->Parent();
+  uint32_t childCount = parent->ChildCount();
+  for (int32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) {
+    Accessible* nextItem = parent->GetChildAt(idx);
+    AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo();
+    if (nextGroupInfo &&
+        nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) {
+      return nextItem;
+    }
+  }
+
+  NS_NOTREACHED("Item in the midle of the group but there's no next item!");
+  return nullptr;
+}
+
 bool
 AccGroupInfo::IsConceptualParent(role aRole, role aParentRole)
 {
   if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM)
     return true;
   if ((aParentRole == roles::TABLE || aParentRole == roles::TREE_TABLE) &&
       aRole == roles::ROW)
     return true;
--- a/accessible/src/base/AccGroupInfo.h
+++ b/accessible/src/base/AccGroupInfo.h
@@ -12,21 +12,32 @@ namespace mozilla {
 namespace a11y {
 
 /**
  * Calculate and store group information.
  */
 class AccGroupInfo
 {
 public:
-  AccGroupInfo(Accessible* aItem, mozilla::a11y::role aRole);
   ~AccGroupInfo() { MOZ_COUNT_DTOR(AccGroupInfo); }
 
-  int32_t PosInSet() const { return mPosInSet; }
+  /**
+   * Return 1-based position in the group.
+   */
+  uint32_t PosInSet() const { return mPosInSet; }
+
+  /**
+   * Return a number of items in the group.
+   */
   uint32_t SetSize() const { return mSetSize; }
+
+  /**
+   * Return a direct or logical parent of the accessible that this group info is
+   * created for.
+   */
   Accessible* ConceptualParent() const { return mParent; }
 
   /**
    * Create group info.
    */
   static AccGroupInfo* CreateGroupInfo(Accessible* aAccessible)
   {
     mozilla::a11y::role role = aAccessible->Role();
@@ -45,19 +56,33 @@ public:
         role != mozilla::a11y::roles::RADIOBUTTON &&
         role != mozilla::a11y::roles::PAGETAB)
       return nullptr;
 
     AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role));
     return info;
   }
 
+  /**
+   * Return a first item for the given container.
+   */
+  static Accessible* FirstItemOf(Accessible* aContainer);
+
+  /**
+   * Return next item of the same group to the given item.
+   */
+  static Accessible* NextItemTo(Accessible* aItem);
+
+protected:
+  AccGroupInfo(Accessible* aItem, a11y::role aRole);
+
 private:
-  AccGroupInfo(const AccGroupInfo&);
-  AccGroupInfo& operator =(const AccGroupInfo&);
+  AccGroupInfo() MOZ_DELETE;
+  AccGroupInfo(const AccGroupInfo&) MOZ_DELETE;
+  AccGroupInfo& operator =(const AccGroupInfo&) MOZ_DELETE;
 
   static mozilla::a11y::role BaseRole(mozilla::a11y::role aRole)
   {
     if (aRole == mozilla::a11y::roles::CHECK_MENU_ITEM ||
         aRole == mozilla::a11y::roles::PARENT_MENUITEM ||
         aRole == mozilla::a11y::roles::RADIO_MENU_ITEM)
       return mozilla::a11y::roles::MENUITEM;
 
@@ -66,18 +91,17 @@ private:
 
     return aRole;
   }
 
   /**
    * Return true if the given parent role is conceptual parent of the given
    * role.
    */
-  static bool IsConceptualParent(mozilla::a11y::role aRole,
-				 mozilla::a11y::role aParentRole);
+  static bool IsConceptualParent(a11y::role aRole, a11y::role aParentRole);
 
   uint32_t mPosInSet;
   uint32_t mSetSize;
   Accessible* mParent;
 };
 
 } // namespace mozilla
 } // namespace a11y
--- a/accessible/src/base/AccIterator.cpp
+++ b/accessible/src/base/AccIterator.cpp
@@ -1,16 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AccIterator.h"
 
 #include "nsAccessibilityService.h"
+#include "AccGroupInfo.h"
 #include "Accessible-inl.h"
+#ifdef MOZ_XUL
+#include "XULTreeAccessible.h"
+#endif
 
 #include "mozilla/dom/Element.h"
 #include "nsBindingManager.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -324,16 +328,74 @@ IDRefsIterator::GetElem(const nsDependen
 
 Accessible*
 IDRefsIterator::Next()
 {
   nsIContent* nextElm = NextElem();
   return nextElm ? mDoc->GetAccessible(nextElm) : nullptr;
 }
 
+
+////////////////////////////////////////////////////////////////////////////////
+// SingleAccIterator
+////////////////////////////////////////////////////////////////////////////////
+
 Accessible*
 SingleAccIterator::Next()
 {
   nsRefPtr<Accessible> nextAcc;
   mAcc.swap(nextAcc);
   return (nextAcc && !nextAcc->IsDefunct()) ? nextAcc : nullptr;
 }
 
+
+////////////////////////////////////////////////////////////////////////////////
+// ItemIterator
+////////////////////////////////////////////////////////////////////////////////
+
+Accessible*
+ItemIterator::Next()
+{
+  if (mContainer) {
+    mAnchor = AccGroupInfo::FirstItemOf(mContainer);
+    mContainer = nullptr;
+    return mAnchor;
+  }
+
+  return mAnchor ? (mAnchor = AccGroupInfo::NextItemTo(mAnchor)) : nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeItemIterator::XULTreeItemIterator(XULTreeAccessible* aXULTree,
+                                         nsITreeView* aTreeView,
+                                         int32_t aRowIdx) :
+  mXULTree(aXULTree), mTreeView(aTreeView), mRowCount(-1),
+  mContainerLevel(-1), mCurrRowIdx(aRowIdx + 1)
+{
+  mTreeView->GetRowCount(&mRowCount);
+  if (aRowIdx != -1)
+    mTreeView->GetLevel(aRowIdx, &mContainerLevel);
+}
+
+Accessible*
+XULTreeItemIterator::Next()
+{
+  while (mCurrRowIdx < mRowCount) {
+    int32_t level = 0;
+    mTreeView->GetLevel(mCurrRowIdx, &level);
+
+    if (level == mContainerLevel + 1)
+      return mXULTree->GetTreeItemAccessible(mCurrRowIdx++);
+
+    if (level <= mContainerLevel) { // got level up
+      mCurrRowIdx = mRowCount;
+      break;
+    }
+
+    mCurrRowIdx++;
+  }
+
+  return nullptr;
+}
--- a/accessible/src/base/AccIterator.h
+++ b/accessible/src/base/AccIterator.h
@@ -261,12 +261,59 @@ public:
 private:
   SingleAccIterator();
   SingleAccIterator(const SingleAccIterator&);
   SingleAccIterator& operator = (const SingleAccIterator&);
 
   nsRefPtr<Accessible> mAcc;
 };
 
+
+/**
+ * Used to iterate items of the given item container.
+ */
+class ItemIterator : public AccIterable
+{
+public:
+  ItemIterator(Accessible* aItemContainer) :
+    mContainer(aItemContainer), mAnchor(nullptr) { }
+  virtual ~ItemIterator() { }
+
+  virtual Accessible* Next();
+
+private:
+  ItemIterator() MOZ_DELETE;
+  ItemIterator(const ItemIterator&) MOZ_DELETE;
+  ItemIterator& operator = (const ItemIterator&) MOZ_DELETE;
+
+  Accessible* mContainer;
+  Accessible* mAnchor;
+};
+
+
+/**
+ * Used to iterate through XUL tree items of the same level.
+ */
+class XULTreeItemIterator : public AccIterable
+{
+public:
+  XULTreeItemIterator(XULTreeAccessible* aXULTree, nsITreeView* aTreeView,
+                      int32_t aRowIdx);
+  virtual ~XULTreeItemIterator() { }
+
+  virtual Accessible* Next();
+
+private:
+  XULTreeItemIterator() MOZ_DELETE;
+  XULTreeItemIterator(const XULTreeItemIterator&) MOZ_DELETE;
+  XULTreeItemIterator& operator = (const XULTreeItemIterator&) MOZ_DELETE;
+
+  XULTreeAccessible* mXULTree;
+  nsITreeView* mTreeView;
+  int32_t mRowCount;
+  int32_t mContainerLevel;
+  int32_t mCurrRowIdx;
+};
+
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/src/base/Relation.h
+++ b/accessible/src/base/Relation.h
@@ -14,37 +14,36 @@ namespace a11y {
 
 /**
  * This class is used to return Relation objects from functions.  A copy
  * constructor doesn't work here because we need to mutate the old relation to
  * have its nsAutoPtr forget what it points to.
  */
 struct RelationCopyHelper
 {
-  RelationCopyHelper(mozilla::a11y::AccIterable* aFirstIter,
-                     mozilla::a11y::AccIterable* aLastIter) :
+  RelationCopyHelper(AccIterable* aFirstIter, AccIterable* aLastIter) :
     mFirstIter(aFirstIter), mLastIter(aLastIter) { }
 
-  mozilla::a11y::AccIterable* mFirstIter;
-  mozilla::a11y::AccIterable* mLastIter;
+  AccIterable* mFirstIter;
+  AccIterable* mLastIter;
 };
 
 /**
  * A collection of relation targets of a certain type.  Targets are computed
  * lazily while enumerating.
  */
 class Relation
 {
 public:
   Relation() : mFirstIter(nullptr), mLastIter(nullptr) { }
 
   Relation(const RelationCopyHelper aRelation) :
     mFirstIter(aRelation.mFirstIter), mLastIter(aRelation.mLastIter) { }
 
-  Relation(mozilla::a11y::AccIterable* aIter) :
+  Relation(AccIterable* aIter) :
     mFirstIter(aIter), mLastIter(aIter) { }
 
   Relation(Accessible* aAcc) :
     mFirstIter(nullptr), mLastIter(nullptr)
     { AppendTarget(aAcc); }
 
   Relation(DocAccessible* aDocument, nsIContent* aContent) :
     mFirstIter(nullptr), mLastIter(nullptr)
@@ -64,33 +63,33 @@ public:
     return *this;
   }
 
   operator RelationCopyHelper()
   {
     return RelationCopyHelper(mFirstIter.forget(), mLastIter);
   }
 
-  inline void AppendIter(mozilla::a11y::AccIterable* aIter)
+  inline void AppendIter(AccIterable* aIter)
   {
     if (mLastIter)
       mLastIter->mNextIter = aIter;
     else
       mFirstIter = aIter;
 
     mLastIter = aIter;
   }
 
   /**
    * Append the given accessible to the set of related accessibles.
    */
   inline void AppendTarget(Accessible* aAcc)
   {
     if (aAcc)
-      AppendIter(new mozilla::a11y::SingleAccIterator(aAcc));
+      AppendIter(new SingleAccIterator(aAcc));
   }
 
   /**
    * Append the one accessible for this content node to the set of related
    * accessibles.
    */
   void AppendTarget(DocAccessible* aDocument, nsIContent* aContent)
   {
@@ -113,17 +112,17 @@ public:
       mLastIter = nullptr;
 
     return target;
   }
 
 private:
   Relation& operator = (const Relation&);
 
-  nsAutoPtr<mozilla::a11y::AccIterable> mFirstIter;
-  mozilla::a11y::AccIterable* mLastIter;
+  nsAutoPtr<AccIterable> mFirstIter;
+  AccIterable* mLastIter;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
 
--- a/accessible/src/base/Role.h
+++ b/accessible/src/base/Role.h
@@ -582,19 +582,18 @@ enum Role {
 
   /**
    * A toggle button. A specialized push button that can be checked or
    * unchecked, but does not provide a separate indicator for the current state.
    */
   TOGGLE_BUTTON = 93,
 
   /**
-   * Representas a control that is capable of expanding and collapsing rows as
+   * Represent a control that is capable of expanding and collapsing rows as
    * well as showing multiple columns of data.
-   * XXX: it looks like this role is dupe of OUTLINE.
    */
   TREE_TABLE = 94,
 
   /**
    * A viewport. An object usually used in a scroll pane. It represents the
    * portion of the entire data that the user can see. As the user manipulates
    * the scroll bars, the contents of the viewport can change. Also refer to
    * SCROLL_PANE.
--- a/accessible/src/base/nsAccessibilityService.h
+++ b/accessible/src/base/nsAccessibilityService.h
@@ -313,29 +313,29 @@ static const char kEventTypeNames[][40] 
   "virtual cursor changed"                   // EVENT_VIRTUALCURSOR_CHANGED
 };
 
 /**
  * Map nsIAccessibleRelation constants to strings. Used by
  * nsIAccessibleRetrieval::getStringRelationType() method.
  */
 static const char kRelationTypeNames[][20] = {
-  "unknown",             // RELATION_NUL
+  "labelled by",         // RELATION_LABELLED_BY
+  "label for",           // RELATION_LABEL_FOR
+  "described by",        // RELATION_DESCRIBED_BY
+  "description for",     // RELATION_DESCRIPTION_FOR
+  "node child of",       // RELATION_NODE_CHILD_OF
+  "node parent of",      // RELATION_NODE_PARENT_OF
   "controlled by",       // RELATION_CONTROLLED_BY
   "controller for",      // RELATION_CONTROLLER_FOR
-  "label for",           // RELATION_LABEL_FOR
-  "labelled by",         // RELATION_LABELLED_BY
-  "member of",           // RELATION_MEMBER_OF
-  "node child of",       // RELATION_NODE_CHILD_OF
   "flows to",            // RELATION_FLOWS_TO
   "flows from",          // RELATION_FLOWS_FROM
+  "member of",           // RELATION_MEMBER_OF
   "subwindow of",        // RELATION_SUBWINDOW_OF
   "embeds",              // RELATION_EMBEDS
   "embedded by",         // RELATION_EMBEDDED_BY
   "popup for",           // RELATION_POPUP_FOR
   "parent window of",    // RELATION_PARENT_WINDOW_OF
-  "described by",        // RELATION_DESCRIBED_BY
-  "description for",     // RELATION_DESCRIPTION_FOR
   "default button"       // RELATION_DEFAULT_BUTTON
 };
 
 #endif /* __nsIAccessibilityService_h__ */
 
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -1962,72 +1962,72 @@ Relation
 Accessible::RelationByType(uint32_t aType)
 {
   if (!HasOwnContent())
     return Relation();
 
   // Relationships are defined on the same content node that the role would be
   // defined on.
   switch (aType) {
-    case nsIAccessibleRelation::RELATION_LABEL_FOR: {
-      Relation rel(new RelatedAccIterator(Document(), mContent,
-                                          nsGkAtoms::aria_labelledby));
-      if (mContent->Tag() == nsGkAtoms::label)
-        rel.AppendIter(new IDRefsIterator(mDoc, mContent, mContent->IsHTML() ?
-                                          nsGkAtoms::_for :
-                                          nsGkAtoms::control));
-
-      return rel;
-    }
     case nsIAccessibleRelation::RELATION_LABELLED_BY: {
       Relation rel(new IDRefsIterator(mDoc, mContent,
                                       nsGkAtoms::aria_labelledby));
       if (mContent->IsHTML()) {
         rel.AppendIter(new HTMLLabelIterator(Document(), this));
       } else if (mContent->IsXUL()) {
         rel.AppendIter(new XULLabelIterator(Document(), mContent));
       }
 
       return rel;
     }
+
+    case nsIAccessibleRelation::RELATION_LABEL_FOR: {
+      Relation rel(new RelatedAccIterator(Document(), mContent,
+                                          nsGkAtoms::aria_labelledby));
+      if (mContent->Tag() == nsGkAtoms::label)
+        rel.AppendIter(new IDRefsIterator(mDoc, mContent, mContent->IsHTML() ?
+          nsGkAtoms::_for :
+          nsGkAtoms::control));
+
+      return rel;
+    }
+
     case nsIAccessibleRelation::RELATION_DESCRIBED_BY: {
       Relation rel(new IDRefsIterator(mDoc, mContent,
                                       nsGkAtoms::aria_describedby));
       if (mContent->IsXUL())
         rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
 
       return rel;
     }
+
     case nsIAccessibleRelation::RELATION_DESCRIPTION_FOR: {
       Relation rel(new RelatedAccIterator(Document(), mContent,
                                           nsGkAtoms::aria_describedby));
 
       // This affectively adds an optional control attribute to xul:description,
       // which only affects accessibility, by allowing the description to be
       // tied to a control.
       if (mContent->Tag() == nsGkAtoms::description &&
           mContent->IsXUL())
         rel.AppendIter(new IDRefsIterator(mDoc, mContent,
                                           nsGkAtoms::control));
 
       return rel;
     }
+
     case nsIAccessibleRelation::RELATION_NODE_CHILD_OF: {
       Relation rel(new RelatedAccIterator(Document(), mContent,
                                           nsGkAtoms::aria_owns));
-      
+
       // This is an ARIA tree or treegrid that doesn't use owns, so we need to
       // get the parent the hard way.
       if (mRoleMapEntry && (mRoleMapEntry->role == roles::OUTLINEITEM || 
                             mRoleMapEntry->role == roles::ROW)) {
-        AccGroupInfo* groupInfo = GetGroupInfo();
-        if (!groupInfo)
-          return rel;
-
-        rel.AppendTarget(groupInfo->ConceptualParent());
+        rel.AppendTarget(GetGroupInfo()->ConceptualParent());
       }
 
       // If accessible is in its own Window, or is the root of a document,
       // 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).
@@ -2038,31 +2038,62 @@ Accessible::RelationByType(uint32_t aTyp
           nsIScrollableFrame *scrollFrame = do_QueryFrame(frame);
           if (scrollFrame || view->GetWidget() || !frame->GetParent())
             rel.AppendTarget(Parent());
         }
       }
 
       return rel;
     }
+
+    case nsIAccessibleRelation::RELATION_NODE_PARENT_OF: {
+      Relation rel(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_owns));
+
+      // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
+      // also can be organized by groups.
+      if (mRoleMapEntry &&
+          (mRoleMapEntry->role == roles::OUTLINEITEM ||
+           mRoleMapEntry->role == roles::ROW ||
+           mRoleMapEntry->role == roles::OUTLINE ||
+           mRoleMapEntry->role == roles::TREE_TABLE)) {
+        rel.AppendIter(new ItemIterator(this));
+      }
+
+      return rel;
+    }
+
     case nsIAccessibleRelation::RELATION_CONTROLLED_BY:
       return Relation(new RelatedAccIterator(Document(), mContent,
                                              nsGkAtoms::aria_controls));
+
     case nsIAccessibleRelation::RELATION_CONTROLLER_FOR: {
       Relation rel(new IDRefsIterator(mDoc, mContent,
                                       nsGkAtoms::aria_controls));
       rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
       return rel;
     }
+
     case nsIAccessibleRelation::RELATION_FLOWS_TO:
       return Relation(new IDRefsIterator(mDoc, mContent,
                                          nsGkAtoms::aria_flowto));
+
     case nsIAccessibleRelation::RELATION_FLOWS_FROM:
       return Relation(new RelatedAccIterator(Document(), mContent,
                                              nsGkAtoms::aria_flowto));
+
+    case nsIAccessibleRelation::RELATION_MEMBER_OF:
+          return Relation(mDoc, GetAtomicRegion());
+
+    case nsIAccessibleRelation::RELATION_SUBWINDOW_OF:
+    case nsIAccessibleRelation::RELATION_EMBEDS:
+    case nsIAccessibleRelation::RELATION_EMBEDDED_BY:
+    case nsIAccessibleRelation::RELATION_POPUP_FOR:
+    case nsIAccessibleRelation::RELATION_PARENT_WINDOW_OF:
+      return Relation();
+
     case nsIAccessibleRelation::RELATION_DEFAULT_BUTTON: {
       if (mContent->IsHTML()) {
         // HTML form controls implements nsIFormControl interface.
         nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent));
         if (control) {
           nsCOMPtr<nsIForm> form(do_QueryInterface(control->GetFormElement()));
           if (form) {
             nsCOMPtr<nsIContent> formContent =
@@ -2100,46 +2131,57 @@ Accessible::RelationByType(uint32_t aTyp
             }
           }
           nsCOMPtr<nsIContent> relatedContent(do_QueryInterface(buttonEl));
           return Relation(mDoc, relatedContent);
         }
       }
       return Relation();
     }
-    case nsIAccessibleRelation::RELATION_MEMBER_OF:
-      return Relation(mDoc, GetAtomicRegion());
-    case nsIAccessibleRelation::RELATION_SUBWINDOW_OF:
-    case nsIAccessibleRelation::RELATION_EMBEDS:
-    case nsIAccessibleRelation::RELATION_EMBEDDED_BY:
-    case nsIAccessibleRelation::RELATION_POPUP_FOR:
-    case nsIAccessibleRelation::RELATION_PARENT_WINDOW_OF:
+
     default:
-    return Relation();
+      return Relation();
   }
 }
 
 NS_IMETHODIMP
 Accessible::GetRelations(nsIArray **aRelations)
 {
   NS_ENSURE_ARG_POINTER(aRelations);
   *aRelations = nullptr;
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIMutableArray> relations = do_CreateInstance(NS_ARRAY_CONTRACTID);
   NS_ENSURE_TRUE(relations, NS_ERROR_OUT_OF_MEMORY);
 
-  for (uint32_t relType = nsIAccessibleRelation::RELATION_FIRST;
-       relType < nsIAccessibleRelation::RELATION_LAST;
-       ++relType) {
-
+  static const uint32_t relationTypes[] = {
+    nsIAccessibleRelation::RELATION_LABELLED_BY,
+    nsIAccessibleRelation::RELATION_LABEL_FOR,
+    nsIAccessibleRelation::RELATION_DESCRIBED_BY,
+    nsIAccessibleRelation::RELATION_DESCRIPTION_FOR,
+    nsIAccessibleRelation::RELATION_NODE_CHILD_OF,
+    nsIAccessibleRelation::RELATION_NODE_PARENT_OF,
+    nsIAccessibleRelation::RELATION_CONTROLLED_BY,
+    nsIAccessibleRelation::RELATION_CONTROLLER_FOR,
+    nsIAccessibleRelation::RELATION_FLOWS_TO,
+    nsIAccessibleRelation::RELATION_FLOWS_FROM,
+    nsIAccessibleRelation::RELATION_MEMBER_OF,
+    nsIAccessibleRelation::RELATION_SUBWINDOW_OF,
+    nsIAccessibleRelation::RELATION_EMBEDS,
+    nsIAccessibleRelation::RELATION_EMBEDDED_BY,
+    nsIAccessibleRelation::RELATION_POPUP_FOR,
+    nsIAccessibleRelation::RELATION_PARENT_WINDOW_OF,
+    nsIAccessibleRelation::RELATION_DEFAULT_BUTTON
+  };
+
+  for (uint32_t idx = 0; idx < ArrayLength(relationTypes); idx++) {
     nsCOMPtr<nsIAccessibleRelation> relation;
-    nsresult rv = GetRelationByType(relType, getter_AddRefs(relation));
+    nsresult rv = GetRelationByType(relationTypes[idx], getter_AddRefs(relation));
 
     if (NS_SUCCEEDED(rv) && relation) {
       uint32_t targets = 0;
       relation->GetTargetsCount(&targets);
       if (targets)
         relations->AppendElement(relation, false);
     }
   }
--- a/accessible/src/msaa/AccessibleWrap.cpp
+++ b/accessible/src/msaa/AccessibleWrap.cpp
@@ -8,17 +8,16 @@
 
 #include "Compatibility.h"
 #include "DocAccessible-inl.h"
 #include "EnumVariant.h"
 #include "ia2AccessibleRelation.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 #include "nsIAccessibleEvent.h"
-#include "nsIAccessibleRelation.h"
 #include "nsWinUtils.h"
 #include "ServiceProvider.h"
 #include "Relation.h"
 #include "Role.h"
 #include "RootAccessible.h"
 #include "sdnAccessible.h"
 #include "States.h"
 
@@ -1043,19 +1042,18 @@ AccessibleWrap::get_nRelations(long *aNR
   if (!aNRelations)
     return E_INVALIDARG;
 
   *aNRelations = 0;
 
   if (IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  for (uint32_t relType = nsIAccessibleRelation::RELATION_FIRST;
-       relType <= nsIAccessibleRelation::RELATION_LAST; relType++) {
-    Relation rel = RelationByType(relType);
+  for (unsigned int idx = 0; idx < ArrayLength(sRelationTypesForIA2); idx++) {
+    Relation rel = RelationByType(sRelationTypesForIA2[idx]);
     if (rel.Next())
       (*aNRelations)++;
   }
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
@@ -1069,18 +1067,18 @@ AccessibleWrap::get_relation(long aRelat
     return E_INVALIDARG;
 
   *aRelation = NULL;
 
   if (IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
   long relIdx = 0;
-  for (uint32_t relType = nsIAccessibleRelation::RELATION_FIRST;
-       relType <= nsIAccessibleRelation::RELATION_LAST; relType++) {
+  for (unsigned int idx = 0; idx < ArrayLength(sRelationTypesForIA2); idx++) {
+    uint32_t relType = sRelationTypesForIA2[idx];
     Relation rel = RelationByType(relType);
     nsRefPtr<ia2AccessibleRelation> ia2Relation =
       new ia2AccessibleRelation(relType, &rel);
     if (ia2Relation->HasTargets()) {
       if (relIdx == aRelationIndex) {
         ia2Relation.forget(aRelation);
         return S_OK;
       }
@@ -1104,19 +1102,19 @@ AccessibleWrap::get_relations(long aMaxR
   if (!aRelation || !aNRelations)
     return E_INVALIDARG;
 
   *aNRelations = 0;
 
   if (IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  for (uint32_t relType = nsIAccessibleRelation::RELATION_FIRST;
-       relType <= nsIAccessibleRelation::RELATION_LAST &&
-       *aNRelations < aMaxRelations; relType++) {
+  for (unsigned int idx = 0; idx < ArrayLength(sRelationTypesForIA2) &&
+       *aNRelations < aMaxRelations; idx++) {
+    uint32_t relType = sRelationTypesForIA2[idx];
     Relation rel = RelationByType(relType);
     nsRefPtr<ia2AccessibleRelation> ia2Rel =
       new ia2AccessibleRelation(relType, &rel);
     if (ia2Rel->HasTargets()) {
       ia2Rel.forget(aRelation + (*aNRelations));
       (*aNRelations)++;
     }
   }
--- a/accessible/src/windows/ia2/ia2AccessibleRelation.h
+++ b/accessible/src/windows/ia2/ia2AccessibleRelation.h
@@ -4,16 +4,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _NS_ACCESSIBLE_RELATION_WRAP_H
 #define _NS_ACCESSIBLE_RELATION_WRAP_H
 
 #include "Accessible.h"
+#include "nsIAccessibleRelation.h"
 
 #include "nsTArray.h"
 
 #include "AccessibleRelation.h"
 
 namespace mozilla {
 namespace a11y {
 
@@ -55,13 +56,35 @@ private:
   ia2AccessibleRelation(const ia2AccessibleRelation&);
   ia2AccessibleRelation& operator = (const ia2AccessibleRelation&);
 
   uint32_t mType;
   nsTArray<nsRefPtr<Accessible> > mTargets;
   ULONG mReferences;
 };
 
+
+/**
+ * Relations exposed to IAccessible2.
+ */
+static const uint32_t sRelationTypesForIA2[] = {
+  nsIAccessibleRelation::RELATION_LABELLED_BY,
+  nsIAccessibleRelation::RELATION_LABEL_FOR,
+  nsIAccessibleRelation::RELATION_DESCRIBED_BY,
+  nsIAccessibleRelation::RELATION_DESCRIPTION_FOR,
+  nsIAccessibleRelation::RELATION_NODE_CHILD_OF,
+  nsIAccessibleRelation::RELATION_CONTROLLED_BY,
+  nsIAccessibleRelation::RELATION_CONTROLLER_FOR,
+  nsIAccessibleRelation::RELATION_FLOWS_TO,
+  nsIAccessibleRelation::RELATION_FLOWS_FROM,
+  nsIAccessibleRelation::RELATION_MEMBER_OF,
+  nsIAccessibleRelation::RELATION_SUBWINDOW_OF,
+  nsIAccessibleRelation::RELATION_EMBEDS,
+  nsIAccessibleRelation::RELATION_EMBEDDED_BY,
+  nsIAccessibleRelation::RELATION_POPUP_FOR,
+  nsIAccessibleRelation::RELATION_PARENT_WINDOW_OF
+};
+
 } // namespace a11y
 } // namespace mozilla
 
 #endif
 
--- a/accessible/src/xul/XULTreeAccessible.cpp
+++ b/accessible/src/xul/XULTreeAccessible.cpp
@@ -432,16 +432,29 @@ XULTreeAccessible::ChildCount() const
 
   int32_t rowCount = 0;
   mTreeView->GetRowCount(&rowCount);
   childCount += rowCount;
 
   return childCount;
 }
 
+Relation
+XULTreeAccessible::RelationByType(uint32_t aType)
+{
+  if (aType == nsIAccessibleRelation::RELATION_NODE_PARENT_OF) {
+    if (mTreeView)
+      return Relation(new XULTreeItemIterator(this, mTreeView, -1));
+
+    return Relation();
+  }
+
+  return Accessible::RelationByType(aType);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // XULTreeAccessible: Widgets
 
 bool
 XULTreeAccessible::IsWidget() const
 {
   return true;
 }
@@ -793,28 +806,44 @@ XULTreeItemAccessibleBase::TakeFocus()
 }
 
 Relation
 XULTreeItemAccessibleBase::RelationByType(uint32_t aType)
 {
   if (!mTreeView)
     return Relation();
 
-  if (aType != nsIAccessibleRelation::RELATION_NODE_CHILD_OF)
-    return Relation();
+  switch (aType) {
+    case nsIAccessibleRelation::RELATION_NODE_CHILD_OF: {
+      int32_t parentIndex = -1;
+      if (!NS_SUCCEEDED(mTreeView->GetParentIndex(mRow, &parentIndex)))
+        return Relation();
+
+      if (parentIndex == -1)
+        return Relation(mParent);
+
+      XULTreeAccessible* treeAcc = mParent->AsXULTree();
+      return Relation(treeAcc->GetTreeItemAccessible(parentIndex));
+    }
 
-  int32_t parentIndex = -1;
-  if (!NS_SUCCEEDED(mTreeView->GetParentIndex(mRow, &parentIndex)))
-    return Relation();
+    case nsIAccessibleRelation::RELATION_NODE_PARENT_OF: {
+      bool isTrue = false;
+      if (NS_FAILED(mTreeView->IsContainerEmpty(mRow, &isTrue)) || isTrue)
+        return Relation();
+
+      if (NS_FAILED(mTreeView->IsContainerOpen(mRow, &isTrue)) || !isTrue)
+        return Relation();
 
-  if (parentIndex == -1)
-    return Relation(mParent);
+      XULTreeAccessible* tree = mParent->AsXULTree();
+      return Relation(new XULTreeItemIterator(tree, mTreeView, mRow));
+    }
 
-  XULTreeAccessible* treeAcc = mParent->AsXULTree();
-  return Relation(treeAcc->GetTreeItemAccessible(parentIndex));
+    default:
+      return Relation();
+  }
 }
 
 uint8_t
 XULTreeItemAccessibleBase::ActionCount()
 {
   // "activate" action is available for all treeitems, "expand/collapse" action
   // is avaible for treeitem which is container.
   return IsExpandable() ? 2 : 1;
--- a/accessible/src/xul/XULTreeAccessible.h
+++ b/accessible/src/xul/XULTreeAccessible.h
@@ -42,16 +42,17 @@ public:
   virtual void Value(nsString& aValue);
   virtual a11y::role NativeRole();
   virtual uint64_t NativeState();
   virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
                                    EWhichChildAtPoint aWhichChild);
 
   virtual Accessible* GetChildAt(uint32_t aIndex);
   virtual uint32_t ChildCount() const;
+  virtual Relation RelationByType(uint32_t aType);
 
   // SelectAccessible
   virtual already_AddRefed<nsIArray> SelectedItems();
   virtual uint32_t SelectedItemCount();
   virtual Accessible* GetSelectedItem(uint32_t aIndex);
   virtual bool IsItemSelected(uint32_t aIndex);
   virtual bool AddItemToSelection(uint32_t aIndex);
   virtual bool RemoveItemFromSelection(uint32_t aIndex);
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -605,16 +605,27 @@ function getTextFromClipboard()
   return "";
 }
 
 /**
  * Return pretty name for identifier, it may be ID, DOM node or accessible.
  */
 function prettyName(aIdentifier)
 {
+  if (aIdentifier instanceof Array) {
+    var msg = "";
+    for (var idx = 0; idx < aIdentifier.length; idx++) {
+      if (msg != "")
+        msg += ", ";
+
+      msg += prettyName(aIdentifier[idx]);
+    }
+    return msg;
+  }
+
   if (aIdentifier instanceof nsIAccessible) {
     var acc = getAccessible(aIdentifier);
     var msg = "[" + getNodePrettyName(acc.DOMNode);
     try {
       msg += ", role: " + roleToString(acc.role);
       if (acc.name)
         msg += ", name: '" + shortenString(acc.name) + "'";
     } catch (e) {
--- a/accessible/tests/mochitest/relations.js
+++ b/accessible/tests/mochitest/relations.js
@@ -9,16 +9,17 @@ const RELATION_DESCRIPTION_FOR = nsIAcce
 const RELATION_EMBEDDED_BY = nsIAccessibleRelation.RELATION_EMBEDDED_BY;
 const RELATION_EMBEDS = nsIAccessibleRelation.RELATION_EMBEDS;
 const RELATION_FLOWS_FROM = nsIAccessibleRelation.RELATION_FLOWS_FROM;
 const RELATION_FLOWS_TO = nsIAccessibleRelation.RELATION_FLOWS_TO;
 const RELATION_LABEL_FOR = nsIAccessibleRelation.RELATION_LABEL_FOR;
 const RELATION_LABELLED_BY = nsIAccessibleRelation.RELATION_LABELLED_BY;
 const RELATION_MEMBER_OF = nsIAccessibleRelation.RELATION_MEMBER_OF;
 const RELATION_NODE_CHILD_OF = nsIAccessibleRelation.RELATION_NODE_CHILD_OF;
+const RELATION_NODE_PARENT_OF = nsIAccessibleRelation.RELATION_NODE_PARENT_OF;
 const RELATION_PARENT_WINDOW_OF = nsIAccessibleRelation.RELATION_PARENT_WINDOW_OF;
 const RELATION_POPUP_FOR = nsIAccessibleRelation.RELATION_POPUP_FOR;
 const RELATION_SUBWINDOW_OF = nsIAccessibleRelation.RELATION_SUBWINDOW_OF;
 
 ////////////////////////////////////////////////////////////////////////////////
 // General
 
 /**
--- a/accessible/tests/mochitest/relations/test_general.html
+++ b/accessible/tests/mochitest/relations/test_general.html
@@ -66,31 +66,45 @@
       // 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("treeitem3", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
       testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
-      testRelation("treeitem6", RELATION_NODE_CHILD_OF, "treeitem5");
+      testRelation("treeitem6", RELATION_NODE_CHILD_OF, "tree");
+      testRelation("treeitem7", RELATION_NODE_CHILD_OF, "treeitem6");
 
       // 'node child of' relation for row role of treegrid
       testRelation("treegridrow1", RELATION_NODE_CHILD_OF, "treegrid");
       testRelation("treegridrow2", RELATION_NODE_CHILD_OF, "treegrid");
       testRelation("treegridrow3", RELATION_NODE_CHILD_OF, "treegridrow2");
 
       // '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);
 
+      // 'node parent of' relation on ARIA tree and treegrid.
+      testRelation("tree", RELATION_NODE_PARENT_OF,
+                    ["treeitem1", "treeitem2", // aria-owns
+                     "treeitem3", "treeitem4", "treeitem6"]); // children
+      testRelation("treeitem4", RELATION_NODE_PARENT_OF,
+                   "treeitem5"); // aria-level
+      testRelation("treeitem6", RELATION_NODE_PARENT_OF,
+                   "treeitem7"); // // group role
+
+      testRelation("treegridrow2", RELATION_NODE_PARENT_OF, "treegridrow3");
+      testRelation("treegrid", RELATION_NODE_PARENT_OF,
+                   ["treegridrow1", "treegridrow2"]);
+
       // aria-controls
       getAccessible("tab");
       todo(false,
            "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
       testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
       testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
 
       // aria-controls, multiple relations
@@ -136,32 +150,37 @@
 
 </head>
 
 <body>
 
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=475298"
      title="mochitests for accessible relations">
-    Mozilla Bug 475298
+    Bug 475298
+  </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=527461"
+     title="Implement RELATION_NODE_PARENT_OF">
+    Bug 527461
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036"
      title="make HTML <output> accessible">
-    Mozilla Bug 558036
+    Bug 558036
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=682790"
      title="Ignore implicit label association when it's associated explicitly">
-    Mozilla Bug 682790
+    Bug 682790
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=687393"
      title="HTML select options gets relation from containing label">
-    Mozilla Bug 687393
+    Bug 687393
   </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <label id="label1_1" for="control1_1">label</label>
   <input id="control1_1">
@@ -222,18 +241,19 @@
   <span role="checkbox" id="checkbox5" aria-describedby="descr2 descr3"></span>
 
   <div role="treeitem" id="treeitem1">Yellow</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 role="treeitem" id="treeitem6" aria-level="1">Green2</div>
     <div role="group">
-      <div role="treeitem" id="treeitem6">Super light green</div>
+      <div role="treeitem" id="treeitem7">Super light green</div>
     </div>
   </div>
 
   <div role="treegrid" id="treegrid">
     <div role="row" id="treegridrow1">
       <span role="gridcell">cell1</span><span role="gridcell">cell2</span>
     </div>
     <div role="row" id="treegridrow2" aria-level="1">
--- a/accessible/tests/mochitest/relations/test_tree.xul
+++ b/accessible/tests/mochitest/relations/test_tree.xul
@@ -42,16 +42,21 @@
       testRelation(treeitem4, RELATION_NODE_CHILD_OF, [treeitem2]);
 
       var treeitem5 = treeitem4.nextSibling;
       testRelation(treeitem5, RELATION_NODE_CHILD_OF, [tree]);
 
       var treeitem6 = treeitem5.nextSibling;
       testRelation(treeitem6, RELATION_NODE_CHILD_OF, [tree]);
 
+      testRelation(tree, RELATION_NODE_PARENT_OF,
+                   [treeitem1, treeitem2, treeitem5, treeitem6]);
+      testRelation(treeitem2, RELATION_NODE_PARENT_OF,
+                   [treeitem3, treeitem4]);
+
       // treeitems and treecells shouldn't pick up relations from tree
       testRelation(treeitem1, RELATION_LABELLED_BY, null);
       testRelation(treeitem1.firstChild, RELATION_LABELLED_BY, null);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
@@ -59,22 +64,27 @@
   ]]>
   </script>
 
   <hbox flex="1" style="overflow: auto;">
     <body xmlns="http://www.w3.org/1999/xhtml">
       <a target="_blank"
          href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
          title="Reorganize implementation of XUL tree accessibility">
-        Mozilla Bug 503727
+        Bug 503727
+      </a>
+      <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=527461"
+         title="Implement RELATION_NODE_PARENT_OF">
+        Bug 527461
       </a>
       <a target="_blank"
          href="https://bugzilla.mozilla.org/show_bug.cgi?id=691248"
          title="XUL tree items shouldn't pick up relations from XUL tree">
-        Mozilla Bug 691248
+        Bug 691248
       </a>
       <p id="display"></p>
       <div id="content" style="display: none">
       </div>
       <pre id="test">
       </pre>
     </body>