Bug 536344 - posinset and setsize aren't calculated right for the flatter trees, r=marcoz, davidb
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 13 Jan 2010 03:07:38 +0800
changeset 37109 b036d913b0bd369eee5c6c2eb77d4e273c0d295d
parent 37108 21362ffb3b0490c8da773bfcf0cbafa892e4a0a0
child 37110 b20eadfc68e0c9de9490cbab1a187b8470bde3be
push id11127
push usersurkov.alexander@gmail.com
push dateTue, 12 Jan 2010 19:08:19 +0000
treeherdermozilla-central@b036d913b0bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarcoz, davidb
bugs536344
milestone1.9.3a1pre
Bug 536344 - posinset and setsize aren't calculated right for the flatter trees, r=marcoz, davidb
accessible/src/base/nsAccUtils.cpp
accessible/src/base/nsAccUtils.h
accessible/src/base/nsAccessible.cpp
accessible/src/base/nsCoreUtils.cpp
accessible/src/base/nsCoreUtils.h
accessible/tests/mochitest/attributes/test_obj_group.html
--- a/accessible/src/base/nsAccUtils.cpp
+++ b/accessible/src/base/nsAccUtils.cpp
@@ -98,16 +98,56 @@ nsAccUtils::SetAccGroupAttrs(nsIPersiste
     SetAccAttr(aAttributes, nsAccessibilityAtoms::posinset, value);
 
     value.Truncate();
     value.AppendInt(aSetSize);
     SetAccAttr(aAttributes, nsAccessibilityAtoms::setsize, value);
   }
 }
 
+PRInt32
+nsAccUtils::GetDefaultLevel(nsAccessible *aAcc)
+{
+  PRUint32 role = nsAccUtils::Role(aAcc);
+
+  if (role == nsIAccessibleRole::ROLE_OUTLINEITEM)
+    return 1;
+
+  if (role == nsIAccessibleRole::ROLE_ROW) {
+    nsCOMPtr<nsIAccessible> parent = aAcc->GetParent();
+    if (Role(parent) == nsIAccessibleRole::ROLE_TREE_TABLE) {
+      // It is a row inside flatten treegrid. Group level is always 1 until it
+      // is overriden by aria-level attribute.
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+PRInt32
+nsAccUtils::GetARIAOrDefaultLevel(nsIAccessible *aAcc)
+{
+  nsRefPtr<nsAccessible> acc = nsAccUtils::QueryObject<nsAccessible>(aAcc);
+  NS_ENSURE_TRUE(acc, 0);
+
+  nsCOMPtr<nsIDOMNode> node;
+  acc->GetDOMNode(getter_AddRefs(node));
+  nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+  NS_ENSURE_TRUE(content, 0);
+
+  PRInt32 level = 0;
+  nsCoreUtils::GetUIntAttr(content, nsAccessibilityAtoms::aria_level, &level);
+
+  if (level != 0)
+    return level;
+
+  return GetDefaultLevel(acc);
+}
+
 void
 nsAccUtils::GetPositionAndSizeForXULSelectControlItem(nsIDOMNode *aNode,
                                                       PRInt32 *aPosInSet,
                                                       PRInt32 *aSetSize)
 {
   nsCOMPtr<nsIDOMXULSelectControlItemElement> item(do_QueryInterface(aNode));
   if (!item)
     return;
--- a/accessible/src/base/nsAccUtils.h
+++ b/accessible/src/base/nsAccUtils.h
@@ -88,16 +88,27 @@ public:
   /**
    * Set group attributes ('level', 'setsize', 'posinset').
    */
   static void SetAccGroupAttrs(nsIPersistentProperties *aAttributes,
                                PRInt32 aLevel, PRInt32 aSetSize,
                                PRInt32 aPosInSet);
 
   /**
+   * Get default value of the level for the given accessible.
+   */
+  static PRInt32 GetDefaultLevel(nsAccessible *aAcc);
+
+  /**
+   * Return ARIA level value or the default one if ARIA is missed for the
+   * given accessible.
+   */
+  static PRInt32 GetARIAOrDefaultLevel(nsIAccessible *aAcc);
+
+  /**
    * Compute position in group (posinset) and group size (setsize) for
    * nsIDOMXULSelectControlItemElement node.
    */
   static void GetPositionAndSizeForXULSelectControlItem(nsIDOMNode *aNode,
                                                         PRInt32 *aPosInSet,
                                                         PRInt32 *aSetSize);
 
   /**
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -1639,39 +1639,22 @@ nsAccessible::GroupPosition(PRInt32 *aGr
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
   // Get group position from ARIA attributes.
   nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
   if (!content)
     return NS_OK;
 
-  nsAutoString value;
-  PRInt32 error = NS_OK;
-
-  content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_level, value);
-  if (!value.IsEmpty()) {
-    PRInt32 level = value.ToInteger(&error);
-    if (NS_SUCCEEDED(error))
-      *aGroupLevel = level;
-  }
-
-  content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_posinset, value);
-  if (!value.IsEmpty()) {
-    PRInt32 posInSet = value.ToInteger(&error);
-    if (NS_SUCCEEDED(error))
-      *aPositionInGroup = posInSet;
-  }
-
-  content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_setsize, value);
-  if (!value.IsEmpty()) {
-    PRInt32 sizeSet = value.ToInteger(&error);
-    if (NS_SUCCEEDED(error))
-      *aSimilarItemsInGroup = sizeSet;
-  }
+  nsCoreUtils::GetUIntAttr(content, nsAccessibilityAtoms::aria_level,
+                           aGroupLevel);
+  nsCoreUtils::GetUIntAttr(content, nsAccessibilityAtoms::aria_posinset,
+                           aPositionInGroup);
+  nsCoreUtils::GetUIntAttr(content, nsAccessibilityAtoms::aria_setsize,
+                           aSimilarItemsInGroup);
 
   // If ARIA is missed and the accessible is visible then calculate group
   // position from hierarchy.
   if (nsAccUtils::State(this) & nsIAccessibleStates::STATE_INVISIBLE)
     return NS_OK;
 
   // Calculate group level if ARIA is missed.
   if (*aGroupLevel == 0) {
@@ -3289,97 +3272,128 @@ nsAccessible::GetPositionAndSizeInternal
       role != nsIAccessibleRole::ROLE_RADIOBUTTON &&
       role != nsIAccessibleRole::ROLE_PAGETAB &&
       role != nsIAccessibleRole::ROLE_OPTION &&
       role != nsIAccessibleRole::ROLE_OUTLINEITEM &&
       role != nsIAccessibleRole::ROLE_ROW &&
       role != nsIAccessibleRole::ROLE_GRID_CELL)
     return;
 
-  PRInt32 positionInGroup = 0;
-  PRInt32 setSize = 0;
-
   PRUint32 baseRole = role;
   if (role == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
       role == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
     baseRole = nsIAccessibleRole::ROLE_MENUITEM;
 
   nsAccessible* parent = GetParent();
   NS_ENSURE_TRUE(parent,);
 
-  PRBool foundCurrent = PR_FALSE;
-  PRInt32 siblingCount = parent->GetChildCount();
-  for (PRInt32 siblingIdx = 0; siblingIdx < siblingCount; siblingIdx++) {
-    nsAccessible* sibling = parent->GetChildAt(siblingIdx);
+  PRInt32 indexInParent = parent->GetIndexOf(this);
+  PRInt32 level = nsAccUtils::GetARIAOrDefaultLevel(this);
+
+  // Compute 'posinset'.
+  PRInt32 positionInGroup = 1;
+  for (PRInt32 idx = indexInParent - 1; idx >= 0; idx--) {
+    nsAccessible* sibling = parent->GetChildAt(idx);
 
     PRUint32 siblingRole = siblingRole = nsAccUtils::Role(sibling);
+
+    // If the sibling is separator then the group is ended.
+    if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR)
+      break;
+
     PRUint32 siblingBaseRole = siblingRole;
     if (siblingRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
         siblingRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
       siblingBaseRole = nsIAccessibleRole::ROLE_MENUITEM;
 
-    // If sibling is visible and has the same base role.
+    // If sibling is visible and has the same base role
     if (siblingBaseRole == baseRole &&
         !(nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE)) {
-      ++ setSize;
-      if (!foundCurrent) {
+
+      // and check if it's hierarchical flatten structure, i.e. if the sibling
+      // level is lesser than this one then group is ended, if the sibling level
+      // is greater than this one then the group is splited by some child
+      // elements (group will be continued).
+      PRInt32 siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling);
+      if (siblingLevel < level)
+        break;
+      else if (level == siblingLevel)
         ++ positionInGroup;
-        if (sibling == this)
-          foundCurrent = PR_TRUE;
-      }
     }
-
-    // If the sibling is separator
-    if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR) {
-      if (foundCurrent) // the our group is ended
+  }
+
+  // Compute 'setsize'.
+  PRInt32 setSize = positionInGroup;
+
+  PRInt32 siblingCount = parent->GetChildCount();
+  for (PRInt32 idx = indexInParent + 1; idx < siblingCount; idx++) {
+    nsAccessible* sibling = parent->GetChildAt(idx);
+    NS_ENSURE_TRUE(sibling,);
+
+    PRUint32 siblingRole = nsAccUtils::Role(sibling);
+
+    // If the sibling is separator then the group is ended.
+    if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR)
+      break;
+
+    PRUint32 siblingBaseRole = siblingRole;
+    if (siblingRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
+        siblingRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
+      siblingBaseRole = nsIAccessibleRole::ROLE_MENUITEM;
+
+    // If sibling is visible and has the same base role
+    if (siblingBaseRole == baseRole &&
+        !(nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE)) {
+
+      // and check if it's hierarchical flatten structure.
+      PRInt32 siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling);
+      if (siblingLevel < level)
         break;
-
-      // not our group, continue the searching
-      positionInGroup = 0;
-      setSize = 0;
+      else if (level == siblingLevel)
+        ++ setSize;
     }
   }
 
   *aPosInSet = positionInGroup;
   *aSetSize = setSize;
 }
 
 PRInt32
 nsAccessible::GetLevelInternal()
 {
+  PRInt32 level = nsAccUtils::GetDefaultLevel(this);
+
   PRUint32 role = nsAccUtils::Role(this);
   nsAccessible* parent = GetParent();
 
   if (role == nsIAccessibleRole::ROLE_OUTLINEITEM) {
     // Always expose 'level' attribute for 'outlineitem' accessible. The number
     // of nested 'grouping' accessibles containing 'outlineitem' accessible is
     // its level.
-    PRInt32 level = 1;
+    level = 1;
+
     while (parent) {
       PRUint32 parentRole = nsAccUtils::Role(parent);
 
       if (parentRole == nsIAccessibleRole::ROLE_OUTLINE)
         break;
       if (parentRole == nsIAccessibleRole::ROLE_GROUPING)
         ++ level;
 
       parent = parent->GetParent();
     }
 
-    return level;
-  }
-
-  if (role == nsIAccessibleRole::ROLE_LISTITEM) {
+  } else if (role == nsIAccessibleRole::ROLE_LISTITEM) {
     // Expose 'level' attribute on nested lists. We assume nested list is a last
     // child of listitem of parent list. We don't handle the case when nested
     // lists have more complex structure, for example when there are accessibles
     // between parent listitem and nested list.
 
     // Calculate 'level' attribute based on number of parent listitems.
-    PRInt32 level = 0;
+    level = 0;
 
     while (parent) {
       PRUint32 parentRole = nsAccUtils::Role(parent);
 
       if (parentRole == nsIAccessibleRole::ROLE_LISTITEM)
         ++ level;
       else if (parentRole != nsIAccessibleRole::ROLE_LIST)
         break;
@@ -3400,21 +3414,12 @@ nsAccessible::GetLevelInternal()
         if (nsAccUtils::Role(siblingChild) == nsIAccessibleRole::ROLE_LIST) {
           level = 1;
           break;
         }
       }
     } else {
       ++ level; // level is 1-index based
     }
-
-    return level;
   }
 
-  if (role == nsIAccessibleRole::ROLE_ROW &&
-      nsAccUtils::Role(parent) == nsIAccessibleRole::ROLE_TREE_TABLE) {
-    // It is a row inside flatten treegrid. Group level is always 1 until it is
-    // overriden by aria-level attribute.
-    return 1;
-  }
-
-  return 0;
+  return level;
 }
--- a/accessible/src/base/nsCoreUtils.cpp
+++ b/accessible/src/base/nsCoreUtils.cpp
@@ -577,16 +577,33 @@ nsCoreUtils::GetDOMNodeForContainer(nsID
 PRBool
 nsCoreUtils::GetID(nsIContent *aContent, nsAString& aID)
 {
   nsIAtom *idAttribute = aContent->GetIDAttributeName();
   return idAttribute ? aContent->GetAttr(kNameSpaceID_None, idAttribute, aID) : PR_FALSE;
 }
 
 PRBool
+nsCoreUtils::GetUIntAttr(nsIContent *aContent, nsIAtom *aAttr, PRInt32 *aUInt)
+{
+  nsAutoString value;
+  aContent->GetAttr(kNameSpaceID_None, aAttr, value);
+  if (!value.IsEmpty()) {
+    PRInt32 error = NS_OK;
+    PRInt32 integer = value.ToInteger(&error);
+    if (NS_SUCCEEDED(error) && integer > 0) {
+      *aUInt = integer;
+      return PR_TRUE;
+    }
+  }
+
+  return PR_FALSE;
+}
+
+PRBool
 nsCoreUtils::IsXLink(nsIContent *aContent)
 {
   if (!aContent)
     return PR_FALSE;
 
   return aContent->AttrValueIs(kNameSpaceID_XLink, nsAccessibilityAtoms::type,
                                nsAccessibilityAtoms::simple, eCaseMatters) &&
          aContent->HasAttr(kNameSpaceID_XLink, nsAccessibilityAtoms::href);
--- a/accessible/src/base/nsCoreUtils.h
+++ b/accessible/src/base/nsCoreUtils.h
@@ -246,16 +246,23 @@ public:
    * Get the ID for an element, in some types of XML this may not be the ID attribute
    * @param aContent  Node to get the ID for
    * @param aID       Where to put ID string
    * @return          PR_TRUE if there is an ID set for this node
    */
   static PRBool GetID(nsIContent *aContent, nsAString& aID);
 
   /**
+   * Convert attribute value of the given node to positive integer. If no
+   * attribute or wrong value then false is returned.
+   */
+  static PRBool GetUIntAttr(nsIContent *aContent, nsIAtom *aAttr,
+                            PRInt32 *aUInt);
+
+  /**
    * Check if the given element is XLink.
    *
    * @param aContent  the given element
    * @return          PR_TRUE if the given element is XLink
    */
   static PRBool IsXLink(nsIContent *aContent);
 
   /**
--- a/accessible/tests/mochitest/attributes/test_obj_group.html
+++ b/accessible/tests/mochitest/attributes/test_obj_group.html
@@ -93,36 +93,47 @@
 
       //////////////////////////////////////////////////////////////////////////
       // ARIA radio
       testGroupAttrs("r1", 1, 3);
       testGroupAttrs("r2", 2, 3);
       testGroupAttrs("r3", 3, 3);
 
       //////////////////////////////////////////////////////////////////////////
+      // ARIA tree
+      testGroupAttrs("ti1", 1, 3, 1);
+      testGroupAttrs("ti2", 1, 2, 2);
+      testGroupAttrs("ti3", 2, 2, 2);
+      testGroupAttrs("ti4", 2, 3, 1);
+      testGroupAttrs("ti5", 1, 3, 2);
+      testGroupAttrs("ti6", 2, 3, 2);
+      testGroupAttrs("ti7", 3, 3, 2);
+      testGroupAttrs("ti8", 3, 3, 1);
+
+      //////////////////////////////////////////////////////////////////////////
       // ARIA grid
       testGroupAttrs("grid_row1", 1, 2);
       testGroupAttrs("grid_cell1", 1, 2);
       testGroupAttrs("grid_cell2", 2, 2);
 
       testGroupAttrs("grid_row2", 2, 2);
       testGroupAttrs("grid_cell3", 1, 2);
       testGroupAttrs("grid_cell4", 2, 2);
 
       //////////////////////////////////////////////////////////////////////////
       // ARIA treegrid
-      testGroupAttrs("treegrid_row1", 1, 3, 1);
+      testGroupAttrs("treegrid_row1", 1, 2, 1);
       testGroupAttrs("treegrid_cell1", 1, 2);
       testGroupAttrs("treegrid_cell2", 2, 2);
 
-      testGroupAttrs("treegrid_row2", 2, 3, 2);
+      testGroupAttrs("treegrid_row2", 1, 1, 2);
       testGroupAttrs("treegrid_cell3", 1, 2);
       testGroupAttrs("treegrid_cell4", 2, 2);
 
-      testGroupAttrs("treegrid_row3", 3, 3, 1);
+      testGroupAttrs("treegrid_row3", 2, 2, 1);
       testGroupAttrs("treegrid_cell5", 1, 2);
       testGroupAttrs("treegrid_cell6", 2, 2);
 
       //////////////////////////////////////////////////////////////////////////
       // HTML headings
       testGroupAttrs("h1", 0, 0, 1);
       testGroupAttrs("h2", 0, 0, 2);
       testGroupAttrs("h3", 0, 0, 3);
@@ -229,16 +240,45 @@
   </ul>
 
   <ul id="rg1" role="radiogroup">
     <li id="r1" role="radio" aria-checked="false">Thai</li>
     <li id="r2" role="radio" aria-checked="false">Subway</li>
     <li id="r3" role="radio" aria-checked="false">Jimmy Johns</li>
   </ul>
 
+  <table role="tree">
+    <tr role="presentation">
+      <td role="treeitem" aria-expanded="true" aria-level="1"
+          id="ti1">vegetables</td>
+    </tr>
+    <tr role="presentation">
+      <td role="treeitem" aria-level="2" id="ti2">cucumber</td>
+    </tr>
+    <tr role="presentation">
+      <td role="treeitem" aria-level="2" id="ti3">carrot</td>
+    </tr>
+    <tr role="presentation">
+      <td role="treeitem" aria-expanded="false" aria-level="1"
+          id="ti4">cars</td>
+    </tr>
+    <tr role="presentation">
+      <td role="treeitem" aria-level="2" id="ti5">mercedes</td>
+    </tr>
+    <tr role="presentation">
+      <td role="treeitem" aria-level="2" id="ti6">BMW</td>
+    </tr>
+    <tr role="presentation">
+      <td role="treeitem" aria-level="2" id="ti7">Audi</td>
+    </tr>
+    <tr role="presentation">
+      <td role="treeitem" aria-level="1" id="ti8">people</td>
+    </tr>
+  </table>
+
   <table role="grid">
     <tr role="row" id="grid_row1">
       <td role="gridcell" id="grid_cell1">cell1</td>
       <td role="gridcell" id="grid_cell2">cell2</td>
     </tr>
     <tr role="row" id="grid_row2">
       <td role="gridcell" id="grid_cell3">cell3</td>
       <td role="gridcell" id="grid_cell4">cell4</td>