Index: test/keystrokes/firefox/codetalks_radio_button3.py
===================================================================
--- test/keystrokes/firefox/codetalks_radio_button3.py	(revision 0)
+++ test/keystrokes/firefox/codetalks_radio_button3.py	(revision 0)
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+#!/usr/bin/python
+
+"""Test of Codetalks radio button presentation using Firefox.
+"""
+
+from macaroon.playback import *
+import utils
+
+sequence = MacroSequence()
+
+########################################################################
+# Load the Codetalks Radio Example 3 demo.
+#
+sequence.append(WaitForWindowActivate(utils.firefoxFrameNames, None))
+sequence.append(KeyComboAction("<Control>l"))
+sequence.append(WaitForFocus(acc_role=pyatspi.ROLE_ENTRY))
+sequence.append(TypeAction("http://codetalks.org/source/widgets/radio/radio3.html"))
+sequence.append(KeyComboAction("Return"))
+sequence.append(WaitForDocLoad())
+sequence.append(WaitForFocus("ARIA Example: Radio Example 3", acc_role=pyatspi.ROLE_DOCUMENT_FRAME))
+
+########################################################################
+# Tab to the Lunch Options Panel
+#
+sequence.append(KeyComboAction("Tab"))
+sequence.append(KeyComboAction("Tab"))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "Tab to Lunch Options Panel", 
+    ["BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=1",
+     "SPEECH OUTPUT: 'Lunch Options panel'",
+     "SPEECH OUTPUT: 'Radio Maria selected radio button'"]))
+
+########################################################################
+# Arrow Up and Down Amongst the Options.
+#
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Up"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Up Arrow", 
+    ["BRAILLE LINE:  '&=y Jimmy Johns RadioButton'",
+     "     VISIBLE:  '&=y Jimmy Johns RadioButton', cursor=1",
+     "SPEECH OUTPUT: ''",
+     "SPEECH OUTPUT: 'Jimmy Johns selected radio button'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Up"))
+sequence.append(utils.AssertPresentationAction(
+    "2. Up Arrow", 
+    ["BRAILLE LINE:  '&=y Subway RadioButton'",
+     "     VISIBLE:  '&=y Subway RadioButton', cursor=1",
+     "SPEECH OUTPUT: ''",
+     "SPEECH OUTPUT: 'Subway selected radio button'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Up"))
+sequence.append(utils.AssertPresentationAction(
+    "3. Up Arrow", 
+    ["BRAILLE LINE:  '&=y Thai RadioButton'",
+     "     VISIBLE:  '&=y Thai RadioButton', cursor=1",
+     "SPEECH OUTPUT: ''",
+     "SPEECH OUTPUT: 'Thai selected radio button'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Down Arrow", 
+    ["BRAILLE LINE:  '&=y Subway RadioButton'",
+     "     VISIBLE:  '&=y Subway RadioButton', cursor=1",
+     "SPEECH OUTPUT: ''",
+     "SPEECH OUTPUT: 'Subway selected radio button'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "2. Down Arrow", 
+    ["BRAILLE LINE:  '&=y Jimmy Johns RadioButton'",
+     "     VISIBLE:  '&=y Jimmy Johns RadioButton', cursor=1",
+     "SPEECH OUTPUT: ''",
+     "SPEECH OUTPUT: 'Jimmy Johns selected radio button'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "3. Down Arrow", 
+    ["BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=1",
+     "SPEECH OUTPUT: ''",
+     "SPEECH OUTPUT: 'Radio Maria selected radio button'"]))
+
+########################################################################
+# Use Right and Left Arrow to review the text.
+#
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Right"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Right Arrow", 
+    ["BUG? - We're not indicating the cursor location correctly in braille",
+     "BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=0",
+     "SPEECH OUTPUT: 'a'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Right"))
+sequence.append(utils.AssertPresentationAction(
+    "2. Right Arrow", 
+    ["BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=0",
+     "SPEECH OUTPUT: 'd'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Right"))
+sequence.append(utils.AssertPresentationAction(
+    "3. Right Arrow", 
+    ["BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=0",
+     "SPEECH OUTPUT: 'i'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Left"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Left Arrow", 
+    ["BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=0",
+     "SPEECH OUTPUT: 'd'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Left"))
+sequence.append(utils.AssertPresentationAction(
+    "2. Left Arrow", 
+    ["BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=0",
+     "SPEECH OUTPUT: 'a'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Left"))
+sequence.append(utils.AssertPresentationAction(
+    "3. Left Arrow", 
+    ["BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=1",
+     "SPEECH OUTPUT: 'R'"]))
+
+########################################################################
+# Do a basic "Where Am I" via KP_Enter.
+#
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(PauseAction(3000))
+sequence.append(utils.AssertPresentationAction(
+    "basic whereamI", 
+    ["BUG? - Not indicating the item position or count",
+     "BRAILLE LINE:  '&=y Radio Maria RadioButton'",
+     "     VISIBLE:  '&=y Radio Maria RadioButton', cursor=1",
+     "SPEECH OUTPUT: 'Lunch Options'",
+     "SPEECH OUTPUT: 'Radio Maria radio button'",
+     "SPEECH OUTPUT: 'selected'",
+     "SPEECH OUTPUT: ''",
+     "SPEECH OUTPUT: ''"]))
+
+########################################################################
+# Tab to the Drink Options Panel
+#
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "Tab to Drink Options Panel", 
+    ["BRAILLE LINE:  '& y Water RadioButton'",
+     "     VISIBLE:  '& y Water RadioButton', cursor=1",
+     "SPEECH OUTPUT: 'Drink Options panel'",
+     "SPEECH OUTPUT: 'Water not selected radio button'"]))
+
+########################################################################
+# Close the demo
+#
+sequence.append(KeyComboAction("<Control>l"))
+sequence.append(WaitForFocus(acc_role=pyatspi.ROLE_ENTRY))
+sequence.append(TypeAction("about:blank"))
+sequence.append(KeyComboAction("Return"))
+sequence.append(WaitForDocLoad())
+
+# Just a little extra wait to let some events get through.
+#
+sequence.append(PauseAction(3000))
+
+sequence.append(utils.AssertionSummaryAction())
+
+sequence.start()
Index: src/orca/scripts/toolkits/Gecko/script.py
===================================================================
--- src/orca/scripts/toolkits/Gecko/script.py	(revision 4622)
+++ src/orca/scripts/toolkits/Gecko/script.py	(working copy)
@@ -1646,6 +1646,11 @@
                                              [pyatspi.ROLE_DOCUMENT_FRAME])
                 if self.isSameObject(containingPanel, event.source):
                     return
+                descendant = self.getAriaActiveDescendant(event.source)
+                if descendant:
+                    orca.setLocusOfFocus(event, descendant)
+                    self.setCaretPosition(descendant, 0)
+                    return
 
         # When we get a focus event on the document frame, it's usually
         # because we did a grabFocus on its parent in setCaretPosition.
@@ -1736,21 +1741,31 @@
         - event: the Event
         """
 
-        # HTML radio buttons don't automatically become selected when
-        # they receive focus.  The user has to press the space bar on
-        # them much like checkboxes.  But if the user clicks on the
-        # radio button with the mouse, we'll wind up speaking the
-        # state twice because of the focus event.
-        #
         if event.type.startswith("object:state-changed:checked") \
            and event.source \
            and (event.source.getRole() == pyatspi.ROLE_RADIO_BUTTON) \
            and (event.detail1 == 1) \
-           and self.inDocumentContent(event.source) \
-           and not self.isAriaWidget(event.source) \
-           and not isinstance(orca_state.lastInputEvent,
-                              input_event.MouseButtonEvent):
-            orca.visualAppearanceChanged(event, event.source)
+           and self.inDocumentContent(event.source):
+            # ARIA radio buttons which use the aria-activedescendent
+            # property don't seem to be focusable, or emit any events
+            # (focus related or active-descendent related) to let us
+            # know that our location has changed. As a result, this
+            # change in state (and location) won't be presented. See
+            # bug 570560.
+            #            
+            if not event.source.getState().contains(pyatspi.STATE_FOCUSABLE) \
+               and self.isAriaWidget(event.source):
+                orca.setLocusOfFocus(event, event.source)
+                self.setCaretPosition(event.source, 0)
+            # HTML radio buttons don't automatically become selected when
+            # they receive focus.  The user has to press the space bar on
+            # them much like checkboxes.  But if the user clicks on the
+            # radio button with the mouse, we'll wind up speaking the
+            # state twice because of the focus event.
+            #
+            elif not isinstance(orca_state.lastInputEvent,
+                                input_event.MouseButtonEvent):
+                orca.visualAppearanceChanged(event, event.source)
             return
 
         # If an autocomplete appears beneath an entry, we don't want
@@ -2782,15 +2797,13 @@
         if not self.inDocumentContent():
             return False
 
-        if not self.isNavigableAria(orca_state.locusOfFocus):
-            return False
-
         if keyboardEvent.event_string in ["Page_Up", "Page_Down"]:
             return False
 
         weHandleIt = True
         obj = orca_state.locusOfFocus
-        if obj and (obj.getRole() == pyatspi.ROLE_ENTRY):
+        if obj and (obj.getRole() == pyatspi.ROLE_ENTRY) \
+           and obj.parent.getRole() != pyatspi.ROLE_COMBO_BOX:
             text        = obj.queryText()
             length      = text.characterCount
             caretOffset = text.caretOffset
@@ -2830,6 +2843,18 @@
                and not self._autocompleteVisible:
                 weHandleIt = keyboardEvent.event_string in ["Up", "Down"]
 
+        elif obj and self.isAriaWidget(obj) \
+             and obj.getRole() in [pyatspi.ROLE_RADIO_BUTTON,
+                                   pyatspi.ROLE_CHECK_BOX]:
+            text = self.queryNonEmptyText(obj)
+            if not text:
+                weHandleIt = False
+            else:
+                weHandleIt = keyboardEvent.event_string in ["Left", "Right"]
+
+        elif not self.isNavigableAria(obj):
+            weHandleIt = False
+
         elif keyboardEvent.modifiers & settings.ALT_MODIFIER_MASK:
             # Alt+Down Arrow is the Firefox command to expand/collapse the
             # *currently focused* combo box.  When Orca is controlling the
@@ -2957,6 +2982,13 @@
                                    pyatspi.ROLE_SECTION]:
                 return obj.parent.getRole() not in [pyatspi.ROLE_COMBO_BOX,
                                                     pyatspi.ROLE_PAGE_TAB]
+            # ARIA radio buttons which use the aria-activedescendent
+            # property don't seem to be focusable, or emit any events
+            # (focus related or active-descendent related) to let us
+            # know that our location has changed. See bug 570560.
+            #
+            elif obj.getRole() == pyatspi.ROLE_RADIO_BUTTON:
+                return False
             # All other ARIA widgets we will assume are navigable if
             # they are not focused.
             #
@@ -5327,6 +5359,30 @@
 
         return self.getObjectsFromEOCs(obj, characterOffset)
 
+    def getAriaActiveDescendant(self, obj):
+        """Given an Aria widget, try to find the active descendant.
+
+        Arguments:
+        - obj: The accessible object whose active descendant we are trying
+          to find.
+
+        Returns the active descendant if found; otherwise, None.
+        """
+
+        if self.isAriaWidget(obj):
+            attrs = self._getAttrDictionary(obj)
+            # ARIA seems to spell it differently. We'll look for both. :-)
+            #
+            descendant = attrs.get('activedescendent') \
+                         or attrs.get('activedescendant')
+            if descendant:
+                for child in obj:
+                    attrs = self._getAttrDictionary(child)
+                    if attrs.get("id") == descendant:
+                        return child
+
+        return None
+
     ####################################################################
     #                                                                  #
     # Methods to speak current objects.                                #
