Bug 452710 - TakeFocus don't work on linkable accessibles, r=MarcoZ
authorAlexander Surkov <surkov.alexander@gmail.com>
Mon, 01 Sep 2008 09:52:40 +0800
changeset 18546 daedaaa2059e6e435df36de117d012a1b8afe452
parent 18545 903096fec9b4d1ae332cfc040de1f48067b81382
child 18547 42b53b1f04e6762c5c15ad41f1d31492666fed0e
push id1632
push usersurkov.alexander@gmail.com
push dateMon, 01 Sep 2008 00:52:55 +0000
treeherdermozilla-central@daedaaa2059e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMarcoZ
bugs452710
milestone1.9.1b1pre
Bug 452710 - TakeFocus don't work on linkable accessibles, r=MarcoZ
accessible/src/base/nsBaseWidgetAccessible.cpp
accessible/tests/mochitest/Makefile.in
accessible/tests/mochitest/common.js
accessible/tests/mochitest/test_nsIAccessible_focus.html
--- a/accessible/src/base/nsBaseWidgetAccessible.cpp
+++ b/accessible/src/base/nsBaseWidgetAccessible.cpp
@@ -113,17 +113,17 @@ NS_IMPL_ISUPPORTS_INHERITED0(nsLinkableA
 
 NS_IMETHODIMP
 nsLinkableAccessible::TakeFocus()
 {
   nsCOMPtr<nsIAccessible> actionAcc = GetActionAccessible();
   if (actionAcc)
     return actionAcc->TakeFocus();
 
-  return NS_OK;
+  return nsHyperTextAccessibleWrap::TakeFocus();
 }
 
 NS_IMETHODIMP
 nsLinkableAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState)
 {
   nsresult rv = nsHyperTextAccessibleWrap::GetState(aState, aExtraState);
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/accessible/tests/mochitest/Makefile.in
+++ b/accessible/tests/mochitest/Makefile.in
@@ -59,16 +59,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug420863.html \
 		test_cssattrs.html \
 		test_groupattrs.xul \
 	$(warning test_table_indexes.html temporarily disabled) \
 		test_nsIAccessible_actions.html \
 		test_nsIAccessible_actions.xul \
 		test_nsIAccessible_name.html \
 		test_nsIAccessible_name.xul \
+		test_nsIAccessible_focus.html \
 		test_nsIAccessibleDocument.html \
 		test_nsIAccessibleEditableText.html \
 		test_nsIAccessibleHyperLink.html \
 		test_nsIAccessibleHyperLink.xul \
 		test_nsIAccessibleHyperText.html \
 		test_nsIAccessibleImage.html \
 		test_nsIAccessibleTable_1.html \
 		test_nsIAccessibleTable_2.html \
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -26,60 +26,67 @@ const nsIAccessibleSelectable = Componen
 const nsIAccessibleTable = Components.interfaces.nsIAccessibleTable;
 const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue;
 
 const nsIObserverService = Components.interfaces.nsIObserverService;
 
 const nsIDOMNode = Components.interfaces.nsIDOMNode;
 
 ////////////////////////////////////////////////////////////////////////////////
-// General
+// Accessible general
 
 /**
  * nsIAccessibleRetrieval, initialized when test is loaded.
  */
 var gAccRetrieval = null;
 
 /**
  * Return accessible for the given ID attribute or DOM element.
  *
- * @param aElmOrID     [in] the ID attribute or DOM element to get an accessible
- *                     for
- * @param aInterfaces  [in, optional] the accessible interface or the array of
- *                     accessible interfaces to query it/them from obtained
- *                     accessible
- * @param aElmObj      [out, optional] object to store DOM element which
- *                     accessible is created for
+ * @param aAccOrElmOrID  [in] DOM element or ID attribute to get an accessible
+ *                        for or an accessible to query additional interfaces.
+ * @param aInterfaces    [in, optional] the accessible interface or the array of
+ *                        accessible interfaces to query it/them from obtained
+ *                        accessible
+ * @param aElmObj        [out, optional] object to store DOM element which
+ *                        accessible is obtained for
  */
-function getAccessible(aElmOrID, aInterfaces, aElmObj)
+function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj)
 {
   var elm = null;
 
-  if (aElmOrID instanceof nsIDOMNode) {
-    elm = aElmOrID;
+  if (aAccOrElmOrID instanceof nsIAccessible) {
+    aAccOrElmOrID.QueryInterface(nsIAccessNode);
+    elm = aAccOrElmOrID.DOMNode;
+
+  } else if (aAccOrElmOrID instanceof nsIDOMNode) {
+    elm = aAccOrElmOrID;
+
   } else {
-    var elm = document.getElementById(aElmOrID);
+    var elm = document.getElementById(aAccOrElmOrID);
     if (!elm) {
       ok(false, "Can't get DOM element for " + aID);
       return null;
     }
   }
 
   if (aElmObj && (typeof aElmObj == "object"))
     aElmObj.value = elm;
 
-  var acc = null;
-  try {
-    acc = gAccRetrieval.getAccessibleFor(elm);
-  } catch (e) {
-  }
-  
+  var acc = (aAccOrElmOrID instanceof nsIAccessible) ? aAccOrElmOrID : null;
   if (!acc) {
-    ok(false, "Can't get accessible for " + aID);
-    return null;
+    try {
+      acc = gAccRetrieval.getAccessibleFor(elm);
+    } catch (e) {
+    }
+    
+    if (!acc) {
+      ok(false, "Can't get accessible for " + aID);
+      return null;
+    }
   }
   
   if (!aInterfaces)
     return acc;
   
   if (aInterfaces instanceof Array) {
     for (var index = 0; index < aInterfaces.length; index++) {
       try {
@@ -98,21 +105,102 @@ function getAccessible(aElmOrID, aInterf
     ok(false, "Can't query " + aInterfaces + " for " + aID);
     return null;
   }
   
   return acc;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Accessible Events
+
+/**
+ * Register accessibility event listener.
+ *
+ * @param aEventType     the accessible event type (see nsIAccessibleEvent for
+ *                       available constants).
+ * @param aEventHandler  event listener object, when accessible event of the
+ *                       given type is handled then 'handleEvent' method of
+ *                       this object is invoked with nsIAccessibleEvent object
+ *                       as the first argument.
+ */
+function registerA11yEventListener(aEventType, aEventHandler)
+{
+  if (!gA11yEventListenersCount) {
+    gObserverService = Components.classes["@mozilla.org/observer-service;1"].
+      getService(nsIObserverService);
+
+    gObserverService.addObserver(gA11yEventObserver, "accessible-event",
+                                 false);
+  }
+
+  if (!(aEventType in gA11yEventListeners))
+    gA11yEventListeners[aEventType] = new Array();
+
+  gA11yEventListeners[aEventType].push(aEventHandler);
+  gA11yEventListenersCount++;
+}
+
+/**
+ * Unregister accessibility event listener. Must be called for every registered
+ * event listener (see registerA11yEventListener() function) when it's not
+ * needed.
+ */
+function unregisterA11yEventListener(aEventType, aEventHandler)
+{
+  var listenersArray = gA11yEventListeners[aEventType];
+  if (listenersArray) {
+    var index = listenersArray.indexOf(aEventHandler);
+    listenersArray.splice(index, 1);
+
+    if (!listenersArray.length) {
+      gA11yEventListeners[aEventType] = null;
+      delete gA11yEventListeners[aEventType];
+    }
+  }
+  
+  gA11yEventListenersCount--;
+  if (!gA11yEventListenersCount) {
+    gObserverService.removeObserver(gA11yEventObserver,
+                                    "accessible-event");
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
 // Private
 ////////////////////////////////////////////////////////////////////////////////
 
 ////////////////////////////////////////////////////////////////////////////////
-// General
+// Accessible general
 
 function initialize()
 {
   gAccRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
-  getService(nsIAccessibleRetrieval);
+    getService(nsIAccessibleRetrieval);
 }
 
 addLoadEvent(initialize);
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible Events
+
+var gObserverService = null;
+
+var gA11yEventListeners = {};
+var gA11yEventListenersCount = 0;
+
+var gA11yEventObserver =
+{
+  observe: function observe(aSubject, aTopic, aData)
+  {
+    if (aTopic != "accessible-event")
+      return;
+
+    var event = aSubject.QueryInterface(nsIAccessibleEvent);
+    var listenersArray = gA11yEventListeners[event.eventType];
+    if (!listenersArray)
+      return;
+
+    for (var index = 0; index < listenersArray.length; index++)
+      listenersArray[index].handleEvent(event);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/test_nsIAccessible_focus.html
@@ -0,0 +1,134 @@
+<html>
+
+<head>
+  <title>nsIAccessible::takeFocus testing</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/a11y/accessible/common.js"></script>
+
+  <script type="application/javascript">
+    ////////////////////////////////////////////////////////////////////////////
+    // Test
+
+    function doTest()
+    {
+      // focus ARIA link
+      var ID = "aria-link";
+      var linkAcc = getAccessible(ID);
+
+      gFocusManager.listenElement(linkAcc, ID, doTest2);
+      linkAcc.takeFocus();
+    }
+
+    function doTest2()
+    {
+      // focus first child of ARIA link
+      var ID = "aria-link2";
+      var linkAcc = getAccessible(ID);
+
+      gFocusManager.listenElement(linkAcc, ID, doTest3);
+      linkAcc.firstChild.takeFocus();
+    }
+
+    function doTest3()
+    {
+      // focus html:a
+      var ID = "link";
+      var linkAcc = getAccessible(ID);
+
+      gFocusManager.listenElement(linkAcc, ID, finishTest);
+      linkAcc.takeFocus();
+    }
+
+    function finishTest()
+    {
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addLoadEvent(doTest);
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Helpers
+
+    var gFocusManager =
+    {
+      // Public
+      listenElement: function listenElement(aAccOrID, aPrettyName, aCallback)
+      {
+        registerA11yEventListener(nsIAccessibleEvent.EVENT_FOCUS, this);
+
+        var elmObj = {};
+        this.mAcc = getAccessible(aAccOrID, null, elmObj);
+        this.mElm = elmObj.value;
+        this.mName = aPrettyName ? aPrettyName : aAccOrID;
+        this.mCallback = aCallback;
+
+        window.setTimeout(
+          function(aFocusMgr)
+          {
+            aFocusMgr.checkWasFocusHandled();
+          }, 0, this);
+      },
+
+      // Private
+      handleEvent: function handleEvent(aAccEvent)
+      {
+        var node = aAccEvent.DOMNode;
+        if (node == this.mElm)
+            this.mIsFocusHandled = true;
+      },
+
+      checkWasFocusHandled: function checkWasFocusHandled()
+      {
+        window.setTimeout(
+          function(aFocusMgr)
+          {
+            unregisterA11yEventListener(nsIAccessibleEvent.EVENT_FOCUS, this);
+
+            ok(aFocusMgr.mIsFocusHandled,
+               "Focus wasn't recieved for element with ID " + aFocusMgr.mName + ".");
+
+            var states = {}, extraStates = {};
+            aFocusMgr.mAcc.getFinalState(states, extraStates);
+            ok(states.value & nsIAccessibleStates.STATE_FOCUSED,
+               "No focused state for element with ID " + aFocusMgr.mName + ".");
+
+            aFocusMgr.mCallback();
+          }, 0, this);
+      },
+
+      mAcc: null,
+      mElm: null,
+      mName: "",
+      mIsFocusHandled: false
+    };
+  </script>
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=452710"
+     title="nsIAccessible::takeFocus testing">
+    Mozilla Bug 452710
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <span id="aria-link" role="link" tabindex="0">link</span>
+  <span id="aria-link2" role="link" tabindex="0">link</span>
+
+  <a id="link" href="">link</span>
+
+</body>
+</html>