Bug 689847 - Expose active state on current item of selectable widgets, r=davidb, tbsaunde
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 07 Dec 2011 17:10:22 +0800
changeset 82957 3a3633f1458cd9a6dc9bd6a8a650d0ed2ba26ccb
parent 82956 8941e2b7a0bf962bf854ad2cfb9a383ba388ee92
child 82958 76a1859359379af874b205a11618b64ba5e88a8f
push idunknown
push userunknown
push dateunknown
reviewersdavidb, tbsaunde
bugs689847
milestone11.0a1
Bug 689847 - Expose active state on current item of selectable widgets, r=davidb, tbsaunde
accessible/src/base/nsAccessible.cpp
accessible/tests/mochitest/states/test_aria.html
accessible/tests/mochitest/states/test_selects.html
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -1537,41 +1537,29 @@ nsAccessible::State()
     // XXX: Perhaps we will be able to make this less hacky if we support
     // extended states in nsARIAMap, e.g. derive COLLAPSED from
     // EXPANDABLE && !EXPANDED.
     state &= ~states::COLLAPSED;
   }
 
   if (!(state & states::UNAVAILABLE)) {
     state |= states::ENABLED | states::SENSITIVE;
+
+    // If the object is a current item of container widget then mark it as
+    // ACTIVE. This allows screen reader virtual buffer modes to know which
+    // descendant is the current one that would get focus if the user navigates
+    // to the container widget.
+    nsAccessible* widget = ContainerWidget();
+    if (widget && widget->CurrentItem() == this)
+      state |= states::ACTIVE;
   }
 
   if ((state & states::COLLAPSED) || (state & states::EXPANDED))
     state |= states::EXPANDABLE;
 
-  if (mRoleMapEntry) {
-    // If an object has an ancestor with the activedescendant property
-    // pointing at it, we mark it as ACTIVE even if it's not currently focused.
-    // This allows screen reader virtual buffer modes to know which descendant
-    // is the current one that would get focus if the user navigates to the container widget.
-    nsAutoString id;
-    if (nsCoreUtils::GetID(mContent, id)) {
-      nsIContent *ancestorContent = mContent;
-      nsAutoString activeID;
-      while ((ancestorContent = ancestorContent->GetParent()) != nsnull) {
-        if (ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, activeID)) {
-          if (id == activeID) {
-            state |= states::ACTIVE;
-          }
-          break;
-        }
-      }
-    }
-  }
-
   // For some reasons DOM node may have not a frame. We tract such accessibles
   // as invisible.
   nsIFrame *frame = GetFrame();
   if (!frame)
     return state;
 
   const nsStyleDisplay* display = frame->GetStyleDisplay();
   if (display && display->mOpacity == 1.0f &&
@@ -2950,25 +2938,28 @@ nsAccessible::CurrentItem()
 }
 
 nsAccessible*
 nsAccessible::ContainerWidget() const
 {
   nsIAtom* idAttribute = mContent->GetIDAttributeName();
   if (idAttribute) {
     if (mContent->HasAttr(kNameSpaceID_None, idAttribute)) {
-      nsAccessible* parent = Parent();
-      do {
+      for (nsAccessible* parent = Parent(); parent; parent = parent->Parent()) {
         nsIContent* parentContent = parent->GetContent();
         if (parentContent &&
             parentContent->HasAttr(kNameSpaceID_None,
                                    nsGkAtoms::aria_activedescendant)) {
           return parent;
         }
-      } while ((parent = parent->Parent()));
+
+        // Don't cross DOM document boundaries.
+        if (parent->IsDocumentNode())
+          break;
+      }
     }
   }
   return nsnull;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessible protected methods
 
--- a/accessible/tests/mochitest/states/test_aria.html
+++ b/accessible/tests/mochitest/states/test_aria.html
@@ -92,16 +92,20 @@
       // aria-checked
       testStates("aria_checked_checkbox", STATE_CHECKED);
       testStates("aria_mixed_checkbox", STATE_MIXED);
 
       // test disabled group and all its descendants to see if they are
       // disabled, too. See bug 429285.
       testAriaDisabledTree("group");
 
+      // active state caused by aria-activedescendant
+      testStates("as_item1", 0, EXT_STATE_ACTIVE);
+      testStates("as_item2", 0, 0, 0, EXT_STATE_ACTIVE);
+
       // universal ARIA properties inherited from file input control
       var fileTextField = getAccessible("fileinput").firstChild;
       testStates(fileTextField,
                  STATE_BUSY | STATE_UNAVAILABLE | STATE_REQUIRED | STATE_HASPOPUP | STATE_INVALID);
       var fileBrowseButton = getAccessible("fileinput").lastChild;
       testStates(fileBrowseButton,
                  STATE_BUSY | STATE_UNAVAILABLE | STATE_REQUIRED | STATE_HASPOPUP | STATE_INVALID);
 
@@ -179,16 +183,21 @@
      title="aria-orientation should be applied to separator and slider roles">
     Mozilla Bug 681674
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
      title="File input control should be propogate states to descendants">
     Mozilla Bug 699017
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847"
+     title="Expose active state on current item of selectable widgets">
+    Mozilla Bug 689847
+  </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <div id="textbox_autocomplete_inline" role="textbox" aria-autocomplete="inline"></div>
   <div id="textbox_autocomplete_list" role="textbox" aria-autocomplete="list"></div>
   <div id="textbox_autocomplete_both" role="textbox" aria-autocomplete="both"></div>
@@ -224,16 +233,23 @@
       <div role="option" id="item1">Item 1</div>
       <div role="option" id="item2">Item 2</div>
       <div role="option" id="item3">Item 3</div>
       <div role="option" id="item4">Item 4</div>
     </div>
     <div role="slider" tabindex="0">A slider</div>
   </div>
 
+  <!-- Test active state -->
+  <div id="as_listbox" tabindex="0" role="listbox"
+       aria-activedescendant="as_item1">
+    <div role="option" id="as_item1">Item 1</div>
+    <div role="option" id="as_item2">Item 2</div>
+  </div>
+
   <!-- universal ARIA properties should be inherited by text field of file input -->
   <input type="file" id="fileinput"
          aria-busy="true"
          aria-disabled="true"
          aria-required="true"
          aria-haspopup="true"
          aria-invalid="true">
 
--- a/accessible/tests/mochitest/states/test_selects.html
+++ b/accessible/tests/mochitest/states/test_selects.html
@@ -20,30 +20,34 @@
       testStates(combobox,
                  STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSABLE, 0,
                  STATE_FOCUSED, 0);
 
       var comboboxList = combobox.firstChild;
       testStates(comboboxList, 0, 0, STATE_FOCUSABLE, 0);
 
       var opt1 = comboboxList.firstChild;
-      testStates(opt1, STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE, 0,
-                 STATE_FOCUSED, 0);
+      testStates(opt1, STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+                 EXT_STATE_ACTIVE, STATE_FOCUSED, 0);
 
       var opt2 = comboboxList.lastChild;
       testStates(opt2, STATE_SELECTABLE | STATE_FOCUSABLE, 0, STATE_SELECTED, 0,
-                 STATE_FOCUSED, 0);
+                 STATE_FOCUSED, EXT_STATE_ACTIVE);
 
       // listbox
       var listbox = getAccessible("listbox");
       testStates(listbox, STATE_FOCUSABLE, 0,
-                 STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSED, 0);
+                 STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSED);
 
-      testStates(listbox.firstChild, STATE_SELECTABLE, 0,
-                 STATE_SELECTED | STATE_FOCUSED | STATE_FOCUSED, 0);
+      testStates(listbox.firstChild, STATE_SELECTABLE, EXT_STATE_ACTIVE,
+                 STATE_SELECTED | STATE_FOCUSED | STATE_FOCUSED);
+
+      testStates(listbox.lastChild, STATE_SELECTABLE, 0,
+                 STATE_SELECTED | STATE_FOCUSED | STATE_FOCUSED,
+                 0, 0, EXT_STATE_ACTIVE);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 
@@ -55,16 +59,21 @@
      title="mochitest for selects and lists">
     Mozilla Bug 443889
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=640716"
      title="mochitest for selects and lists">
     Mozilla Bug 640716
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847"
+     title="Expose active state on current item of selectable widgets">
+    Mozilla Bug 689847
+  </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <select id="combobox">
     <option>item 1</option>
     <option>item 2</option>