Index: test/keystrokes/firefox/line_nav_wiki.py
===================================================================
--- test/keystrokes/firefox/line_nav_wiki.py	(revision 4088)
+++ test/keystrokes/firefox/line_nav_wiki.py	(working copy)
@@ -53,9 +53,7 @@
     "Line Down",
     ["BRAILLE LINE:  'live.gnome.org h1 Search $l Titles Button Text Button'",
      "     VISIBLE:  'live.gnome.org h1 Search $l Titl', cursor=1",
-     "SPEECH OUTPUT: 'live.gnome.org heading  '",
-     "SPEECH OUTPUT: 'level 1'",
-     "SPEECH OUTPUT: 'text Search Titles button grayed Text button grayed'"]))
+     "SPEECH OUTPUT: 'live.gnome.org heading level 1 text Search Titles button grayed Text button grayed'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -95,8 +93,7 @@
     "Line Down", 
     ["BRAILLE LINE:  'Welcome to Orca! h1'",
      "     VISIBLE:  'Welcome to Orca! h1', cursor=1",
-     "SPEECH OUTPUT: 'Welcome to Orca! heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Welcome to Orca! heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -192,8 +189,7 @@
     "Line Down", 
     ["BRAILLE LINE:  'About h1'",
      "     VISIBLE:  'About h1', cursor=1",
-     "SPEECH OUTPUT: 'About heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'About heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -239,7 +235,7 @@
 sequence.append(KeyComboAction("Down"))
 sequence.append(utils.AssertPresentationAction(
     "Line Down", 
-    [ "BRAILLE LINE:  'been led by the Accessibility Program Office of Sun Microsystems, Inc. Link'",
+    ["BRAILLE LINE:  'been led by the Accessibility Program Office of Sun Microsystems, Inc. Link'",
      "     VISIBLE:  'been led by the Accessibility Pr', cursor=1",
      "SPEECH OUTPUT: 'been led by the Accessibility Program Office of Sun Microsystems, Inc. link'"]))
 
@@ -297,8 +293,7 @@
     "Line Down", 
     ["BRAILLE LINE:  'Audio Guides h1'",
      "     VISIBLE:  'Audio Guides h1', cursor=1",
-     "SPEECH OUTPUT: 'Audio Guides heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Audio Guides heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -354,8 +349,7 @@
     "Line Down", 
     ["BRAILLE LINE:  'Download/Installation h1'",
      "     VISIBLE:  'Download/Installation h1', cursor=1",
-     "SPEECH OUTPUT: 'Download/Installation heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Download/Installation heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -395,8 +389,7 @@
     "Line Down", 
     ["BRAILLE LINE:  'Configuration/Use h1'",
      "     VISIBLE:  'Configuration/Use h1', cursor=1",
-     "SPEECH OUTPUT: 'Configuration/Use heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Configuration/Use heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -484,8 +477,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Accessible Applications h1'",
      "     VISIBLE:  'Accessible Applications h1', cursor=1",
-     "SPEECH OUTPUT: 'Accessible Applications heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Accessible Applications heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -581,8 +573,7 @@
     "Line Down",
     ["BRAILLE LINE:  'How Can I Help? h1'",
      "     VISIBLE:  'How Can I Help? h1', cursor=1",
-     "SPEECH OUTPUT: 'How Can I Help? heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'How Can I Help? heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -598,8 +589,7 @@
     "Line Down",
     ["BRAILLE LINE:  'More Information h1'",
      "     VISIBLE:  'More Information h1', cursor=1",
-     "SPEECH OUTPUT: 'More Information heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'More Information heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -719,8 +709,7 @@
     "Line Down",
     ["BRAILLE LINE:  'User h3'",
      "     VISIBLE:  'User h3', cursor=1",
-     "SPEECH OUTPUT: 'User heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'User heading level 3'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -736,8 +725,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Page h3'",
      "     VISIBLE:  'Page h3', cursor=1",
-     "SPEECH OUTPUT: 'Page heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'Page heading level 3'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -778,8 +766,7 @@
     "Line Down",
     ["BRAILLE LINE:  'GNOME World Wide h3'",
      "     VISIBLE:  'GNOME World Wide h3', cursor=1",
-     "SPEECH OUTPUT: 'GNOME World Wide heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'GNOME World Wide heading level 3'"]))
 
 ########################################################################
 # Up Arrow to the Top.
@@ -823,8 +810,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Page h3'",
      "     VISIBLE:  'Page h3', cursor=1",
-     "SPEECH OUTPUT: 'Page heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'Page heading level 3'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -840,8 +826,7 @@
     "Line Up",
     ["BRAILLE LINE:  'User h3'",
      "     VISIBLE:  'User h3', cursor=1",
-     "SPEECH OUTPUT: 'User heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'User heading level 3'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -961,8 +946,7 @@
     "Line Up",
     ["BRAILLE LINE:  'More Information h1'",
      "     VISIBLE:  'More Information h1', cursor=1",
-     "SPEECH OUTPUT: 'More Information heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'More Information heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -978,8 +962,7 @@
     "Line Up",
     ["BRAILLE LINE:  'How Can I Help? h1'",
      "     VISIBLE:  'How Can I Help? h1', cursor=1",
-     "SPEECH OUTPUT: 'How Can I Help? heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'How Can I Help? heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1075,8 +1058,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Accessible Applications h1'",
      "     VISIBLE:  'Accessible Applications h1', cursor=1",
-     "SPEECH OUTPUT: 'Accessible Applications heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Accessible Applications heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1164,8 +1146,7 @@
     "Line Up", 
     ["BRAILLE LINE:  'Configuration/Use h1'",
      "     VISIBLE:  'Configuration/Use h1', cursor=1",
-     "SPEECH OUTPUT: 'Configuration/Use heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Configuration/Use heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1205,8 +1186,7 @@
     "Line Up", 
     ["BRAILLE LINE:  'Download/Installation h1'",
      "     VISIBLE:  'Download/Installation h1', cursor=1",
-     "SPEECH OUTPUT: 'Download/Installation heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Download/Installation heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1262,8 +1242,7 @@
     "Line Up", 
     ["BRAILLE LINE:  'Audio Guides h1'",
      "     VISIBLE:  'Audio Guides h1', cursor=1",
-     "SPEECH OUTPUT: 'Audio Guides heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Audio Guides heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1367,8 +1346,7 @@
     "Line Up", 
     ["BRAILLE LINE:  'About h1'",
      "     VISIBLE:  'About h1', cursor=1",
-     "SPEECH OUTPUT: 'About heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'About heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1464,8 +1442,7 @@
     "Line Up", 
     ["BRAILLE LINE:  'Welcome to Orca! h1'",
      "     VISIBLE:  'Welcome to Orca! h1', cursor=1",
-     "SPEECH OUTPUT: 'Welcome to Orca! heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Welcome to Orca! heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1505,9 +1482,7 @@
     "Line Up",
     ["BRAILLE LINE:  'live.gnome.org h1 Search $l Titles Button Text Button'",
      "     VISIBLE:  'live.gnome.org h1 Search $l Titl', cursor=1",
-     "SPEECH OUTPUT: 'live.gnome.org heading  '",
-     "SPEECH OUTPUT: 'level 1'",
-     "SPEECH OUTPUT: 'text Search Titles button grayed Text button grayed'"]))
+     "SPEECH OUTPUT: 'live.gnome.org heading level 1 text Search Titles button grayed Text button grayed'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
Index: test/keystrokes/firefox/line_nav_multi_line_text.py
===================================================================
--- test/keystrokes/firefox/line_nav_multi_line_text.py	(revision 4088)
+++ test/keystrokes/firefox/line_nav_multi_line_text.py	(working copy)
@@ -91,8 +91,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Hello h3'",
      "     VISIBLE:  'Hello h3', cursor=1",
-     "SPEECH OUTPUT: 'Hello heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'Hello heading level 3'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -132,8 +131,7 @@
     "Line Down",
     ["BRAILLE LINE:  'World h3'",
      "     VISIBLE:  'World h3', cursor=1",
-     "SPEECH OUTPUT: 'World heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'World heading level 3'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -200,8 +198,7 @@
     "Line Up",
     ["BRAILLE LINE:  'World h3'",
      "     VISIBLE:  'World h3', cursor=1",
-     "SPEECH OUTPUT: 'World heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'World heading level 3'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -241,8 +238,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Hello h3'",
      "     VISIBLE:  'Hello h3', cursor=1",
-     "SPEECH OUTPUT: 'Hello heading  '",
-     "SPEECH OUTPUT: 'level 3'"]))
+     "SPEECH OUTPUT: 'Hello heading level 3'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
Index: test/keystrokes/firefox/html_struct_nav_large_obj.py
===================================================================
--- test/keystrokes/firefox/html_struct_nav_large_obj.py	(revision 4088)
+++ test/keystrokes/firefox/html_struct_nav_large_obj.py	(working copy)
@@ -34,8 +34,7 @@
     "Top of file", 
     ["BRAILLE LINE:  'Text Attributes h1'",
      "     VISIBLE:  'Text Attributes h1', cursor=1",
-     "SPEECH OUTPUT: 'Text Attributes heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Text Attributes heading level 1'"]))
 
 ########################################################################
 # Navigate to the first 'large chunk'.  It is the 1st paragraph under 'From 
Index: test/keystrokes/firefox/html_struct_nav_list_item.py
===================================================================
--- test/keystrokes/firefox/html_struct_nav_list_item.py	(revision 4088)
+++ test/keystrokes/firefox/html_struct_nav_list_item.py	(working copy)
@@ -37,8 +37,7 @@
     "Top of file", 
     ["BRAILLE LINE:  'Welcome to a List of Lists h1'",
      "     VISIBLE:  'Welcome to a List of Lists h1', cursor=1",
-     "SPEECH OUTPUT: 'Welcome to a List of Lists heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Welcome to a List of Lists heading level 1'"]))
 
 ########################################################################
 # Press L to move to the first list. 
Index: test/keystrokes/firefox/page_summary.py
===================================================================
--- test/keystrokes/firefox/page_summary.py	(revision 4088)
+++ test/keystrokes/firefox/page_summary.py	(working copy)
@@ -35,8 +35,7 @@
     "Navigate to 'This is a Heading 6.'",
     ["BRAILLE LINE:  'This is a Heading 6. h6'",
      "     VISIBLE:  'This is a Heading 6. h6', cursor=1",
-     "SPEECH OUTPUT: 'This is a Heading 6. heading  '",
-     "SPEECH OUTPUT: 'level 6'"]))
+     "SPEECH OUTPUT: 'This is a Heading 6. heading level 6'"]))
 
 ########################################################################
 # Do double-click "Where Am I" via KP_Enter. 
Index: test/keystrokes/firefox/html_struct_nav_lists.py
===================================================================
--- test/keystrokes/firefox/html_struct_nav_lists.py	(revision 4088)
+++ test/keystrokes/firefox/html_struct_nav_lists.py	(working copy)
@@ -37,8 +37,7 @@
     "Top of file", 
     ["BRAILLE LINE:  'Welcome to a List of Lists h1'",
      "     VISIBLE:  'Welcome to a List of Lists h1', cursor=1",
-     "SPEECH OUTPUT: 'Welcome to a List of Lists heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Welcome to a List of Lists heading level 1'"]))
 
 ########################################################################
 # Press L to move to the first list. 
Index: test/keystrokes/firefox/line_nav_heading_section.py
===================================================================
--- test/keystrokes/firefox/line_nav_heading_section.py	(revision 4088)
+++ test/keystrokes/firefox/line_nav_heading_section.py	(working copy)
@@ -38,8 +38,7 @@
     "Top of file",
     ["BRAILLE LINE:  'Heading 1. h1'",
      "     VISIBLE:  'Heading 1. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 1. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 1. heading level 1'"]))
 
 ########################################################################
 # Down Arrow.
@@ -50,8 +49,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Heading 2. h1'",
      "     VISIBLE:  'Heading 2. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 2. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 2. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -67,8 +65,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Heading 3. h1'",
      "     VISIBLE:  'Heading 3. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 3. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 3. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -84,8 +81,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Heading 4. h1'",
      "     VISIBLE:  'Heading 4. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 4. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 4. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -101,8 +97,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Heading 5. h1'",
      "     VISIBLE:  'Heading 5. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 5. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 5. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -110,8 +105,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Heading 6. h1'",
      "     VISIBLE:  'Heading 6. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 6. heading  '",
-     "SPEECH OUTPUT: 'level 1'",]))
+     "SPEECH OUTPUT: 'Heading 6. heading level 1'"]))
 
 ########################################################################
 # Up Arrow.
@@ -122,8 +116,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Heading 5. h1'",
      "     VISIBLE:  'Heading 5. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 5. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 5. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -139,8 +132,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Heading 4. h1'",
      "     VISIBLE:  'Heading 4. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 4. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 4. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -156,8 +148,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Heading 3. h1'",
      "     VISIBLE:  'Heading 3. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 3. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 3. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -173,8 +164,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Heading 2. h1'",
      "     VISIBLE:  'Heading 2. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 2. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 2. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -182,8 +172,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Heading 1. h1'",
      "     VISIBLE:  'Heading 1. h1', cursor=1",
-     "SPEECH OUTPUT: 'Heading 1. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Heading 1. heading level 1'"]))
 
 ########################################################################
 # Move to the location bar by pressing Control+L.  When it has focus
Index: test/keystrokes/firefox/line_nav_empty_anchor.py
===================================================================
--- test/keystrokes/firefox/line_nav_empty_anchor.py	(revision 4088)
+++ test/keystrokes/firefox/line_nav_empty_anchor.py	(working copy)
@@ -39,8 +39,7 @@
     ["BRAILLE LINE:  'FAQ h1'",
      "     VISIBLE:  'FAQ h1', cursor=1",
      "SPEECH OUTPUT: 'FAQ '",
-     "SPEECH OUTPUT: 'heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'heading level 1'"]))
 
 ########################################################################
 # Down Arrow.
@@ -51,8 +50,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Battery h2'",
      "     VISIBLE:  'Battery h2', cursor=1",
-     "SPEECH OUTPUT: 'Battery heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     "SPEECH OUTPUT: 'Battery heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -87,8 +85,7 @@
     ["BRAILLE LINE:  ''",
      "     VISIBLE:  '', cursor=0",
      "SPEECH OUTPUT: '",
-     " heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     " heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -97,8 +94,7 @@
     ["BRAILLE LINE:  'FOO h2'",
      "     VISIBLE:  'FOO h2', cursor=1",
      "SPEECH OUTPUT: 'FOO'",
-     "SPEECH OUTPUT: 'heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     "SPEECH OUTPUT: 'heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -125,8 +121,7 @@
     ["BRAILLE LINE:  ''",
      "     VISIBLE:  '', cursor=0",
      "SPEECH OUTPUT: '",
-     " heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     " heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -134,8 +129,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Battery h2'",
      "     VISIBLE:  'Battery h2', cursor=1",
-     "SPEECH OUTPUT: 'Battery heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     "SPEECH OUTPUT: 'Battery heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -240,8 +234,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Battery h2'",
      "     VISIBLE:  'Battery h2', cursor=1",
-     "SPEECH OUTPUT: 'Battery heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     "SPEECH OUTPUT: 'Battery heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -250,8 +243,7 @@
     ["BRAILLE LINE:  ''",
      "     VISIBLE:  '', cursor=0",
      "SPEECH OUTPUT: '",
-     " heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     " heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -278,8 +270,7 @@
     ["BRAILLE LINE:  'FOO h2'",
      "     VISIBLE:  'FOO h2', cursor=1",
      "SPEECH OUTPUT: 'FOO'",
-     "SPEECH OUTPUT: 'heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     "SPEECH OUTPUT: 'heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -288,8 +279,7 @@
     ["BRAILLE LINE:  ''",
      "     VISIBLE:  '', cursor=0",
      "SPEECH OUTPUT: '",
-     " heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     " heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -323,8 +313,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Battery h2'",
      "     VISIBLE:  'Battery h2', cursor=1",
-     "SPEECH OUTPUT: 'Battery heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     "SPEECH OUTPUT: 'Battery heading level 2'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -333,8 +322,7 @@
     ["BRAILLE LINE:  'FAQ h1'",
      "     VISIBLE:  'FAQ h1', cursor=1",
      "SPEECH OUTPUT: 'FAQ '",
-     "SPEECH OUTPUT: 'heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'heading level 1'"]))
 
 ########################################################################
 # Move to the location bar by pressing Control+L.  When it has focus
Index: test/keystrokes/firefox/html_role_lists.py
===================================================================
--- test/keystrokes/firefox/html_role_lists.py	(revision 4088)
+++ test/keystrokes/firefox/html_role_lists.py	(working copy)
@@ -37,8 +37,7 @@
     "Top of file", 
     ["BRAILLE LINE:  'Welcome to a List of Lists h1'",
      "     VISIBLE:  'Welcome to a List of Lists h1', cursor=1",
-     "SPEECH OUTPUT: 'Welcome to a List of Lists heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Welcome to a List of Lists heading level 1'"]))
 
 ########################################################################
 # Press Down Arrow to move through the lists.
Index: test/keystrokes/firefox/line_nav_bugzilla_search.py
===================================================================
--- test/keystrokes/firefox/line_nav_bugzilla_search.py	(revision 4088)
+++ test/keystrokes/firefox/line_nav_bugzilla_search.py	(working copy)
@@ -99,7 +99,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 8 items'"]))
+     "SPEECH OUTPUT: 'Admin multi-select List with 8 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -115,7 +115,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 379 items'"]))
+     "SPEECH OUTPUT: 'accerciser multi-select List with 379 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -131,7 +131,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 1248 items'"]))
+     "SPEECH OUTPUT: 'abiscan multi-select List with 1248 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -147,7 +147,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 857 items'"]))
+     "SPEECH OUTPUT: '0.0.1 multi-select List with 857 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -163,7 +163,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 555 items'"]))
+     "SPEECH OUTPUT: '--- multi-select List with 555 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -211,7 +211,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 8 items'"]))
+     "SPEECH OUTPUT: 'UNCONFIRMED multi-select List with 8 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -227,7 +227,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 12 items'"]))
+     "SPEECH OUTPUT: 'FIXED multi-select List with 12 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -243,7 +243,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 7 items'"]))
+     "SPEECH OUTPUT: 'blocker multi-select List with 7 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -259,7 +259,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 5 items'"]))
+     "SPEECH OUTPUT: 'Immediate multi-select List with 5 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -275,7 +275,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 21 items'"]))
+     "SPEECH OUTPUT: 'All multi-select List with 21 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -459,7 +459,7 @@
     "Line Down",
     ["BRAILLE LINE:  ' $l and Now $l'",
      "     VISIBLE:  ' $l and Now $l', cursor=1",
-     "SPEECH OUTPUT: 'Only bugs changed between: text and text Now  ",
+     "SPEECH OUTPUT: 'Only bugs changed between: text and text Now",
      "'"]))
 
 sequence.append(utils.StartRecordingAction())
@@ -484,7 +484,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 26 items'"]))
+     "SPEECH OUTPUT: '[Bug creation] multi-select List with 26 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -516,7 +516,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 14 items'"]))
+     "SPEECH OUTPUT: 'Unspecified multi-select List with 14 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -532,7 +532,7 @@
     "Line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 12 items'"]))
+     "SPEECH OUTPUT: 'Unspecified multi-select List with 12 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -695,7 +695,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 12 items'"]))
+     "SPEECH OUTPUT: 'Unspecified multi-select List with 12 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -711,7 +711,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 14 items'"]))
+     "SPEECH OUTPUT: 'Unspecified multi-select List with 14 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -743,7 +743,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 26 items'"]))
+     "SPEECH OUTPUT: '[Bug creation] multi-select List with 26 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -767,7 +767,7 @@
     "Line Up",
     ["BRAILLE LINE:  ' $l and Now $l'",
      "     VISIBLE:  ' $l and Now $l', cursor=1",
-     "SPEECH OUTPUT: 'Only bugs changed between: text and text Now  ",
+     "SPEECH OUTPUT: 'Only bugs changed between: text and text Now",
      "'"]))
 
 sequence.append(utils.StartRecordingAction())
@@ -952,7 +952,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 21 items'"]))
+     "SPEECH OUTPUT: 'All multi-select List with 21 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -968,7 +968,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 5 items'"]))
+     "SPEECH OUTPUT: 'Immediate multi-select List with 5 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -984,7 +984,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 7 items'"]))
+     "SPEECH OUTPUT: 'blocker multi-select List with 7 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1000,7 +1000,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 12 items'"]))
+     "SPEECH OUTPUT: 'FIXED multi-select List with 12 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1016,7 +1016,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 8 items'"]))
+     "SPEECH OUTPUT: 'UNCONFIRMED multi-select List with 8 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1064,7 +1064,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 555 items'"]))
+     "SPEECH OUTPUT: '--- multi-select List with 555 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1080,7 +1080,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 857 items'"]))
+     "SPEECH OUTPUT: '0.0.1 multi-select List with 857 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1096,7 +1096,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 1248 items'"]))
+     "SPEECH OUTPUT: 'abiscan multi-select List with 1248 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1112,7 +1112,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 379 items'"]))
+     "SPEECH OUTPUT: 'accerciser multi-select List with 379 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -1128,7 +1128,7 @@
     "Line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 8 items'"]))
+     "SPEECH OUTPUT: 'Admin multi-select List with 8 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
Index: test/keystrokes/firefox/uiuc_grid.py
===================================================================
--- test/keystrokes/firefox/uiuc_grid.py	(revision 4088)
+++ test/keystrokes/firefox/uiuc_grid.py	(working copy)
@@ -130,7 +130,7 @@
      "     VISIBLE:  'New message Image None Image Ima', cursor=1",
      "SPEECH OUTPUT: '3 New message None From Jane Johnson Subject Proposal for you to review panel'",
      "SPEECH OUTPUT: 'New message'",
-     "SPEECH OUTPUT: 'New message image None image Jane Johnson Proposal for you to review 2007-16-03 12K'"]))
+     "SPEECH OUTPUT: 'New message image None image image Jane Johnson Proposal for you to review 2007-16-03 12K'"]))
 
 ########################################################################
 # Close the demo
Index: test/keystrokes/firefox/label_guess_entries.py
===================================================================
--- test/keystrokes/firefox/label_guess_entries.py	(revision 4088)
+++ test/keystrokes/firefox/label_guess_entries.py	(working copy)
@@ -40,8 +40,7 @@
     "Top of file",
     ["BRAILLE LINE:  'Here are some entries h2'",
      "     VISIBLE:  'Here are some entries h2', cursor=1",
-     "SPEECH OUTPUT: 'Here are some entries heading  '",
-     "SPEECH OUTPUT: 'level 2'"]))
+     "SPEECH OUTPUT: 'Here are some entries heading level 2'"]))
 
 ########################################################################
 # Press Insert+Tab to move from form field to form field.
Index: test/keystrokes/firefox/line_nav_enter_bug.py
===================================================================
--- test/keystrokes/firefox/line_nav_enter_bug.py	(revision 4088)
+++ test/keystrokes/firefox/line_nav_enter_bug.py	(working copy)
@@ -62,11 +62,9 @@
      "     VISIBLE:  'Enter Bug: orca', cursor=0",
      "BRAILLE LINE:  'Enter Bug: orca – This page lets you enter a new bug into Bugzilla. h1'",
      "     VISIBLE:  'Enter Bug: orca – This page le', cursor=1",
-     "SPEECH OUTPUT: 'Enter Bug: orca – This page lets you enter a new bug into Bugzilla. heading  '",
-     "SPEECH OUTPUT: 'level 1'",
+     "SPEECH OUTPUT: 'Enter Bug: orca – This page lets you enter a new bug into Bugzilla. heading level 1'",
      "SPEECH OUTPUT: 'Enter Bug: orca page'",
-     "SPEECH OUTPUT: 'Enter Bug: orca – This page lets you enter a new bug into Bugzilla. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Enter Bug: orca – This page lets you enter a new bug into Bugzilla. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -115,7 +113,7 @@
     "Line Down",
     ["BRAILLE LINE:  'Version: List Component Link : List'",
      "     VISIBLE:  'Version: List Component Link : L', cursor=1",
-     "SPEECH OUTPUT: 'Version: List with 9 items Component link : List with 5 items'"]))
+     "SPEECH OUTPUT: 'Version: 2.21.x List with 9 items Component link : braille List with 5 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -423,7 +421,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Version: List Component Link : List'",
      "     VISIBLE:  'Version: List Component Link : L', cursor=1",
-     "SPEECH OUTPUT: 'Version: List with 9 items Component link : List with 5 items'"]))
+     "SPEECH OUTPUT: 'Version: 2.21.x List with 9 items Component link : braille List with 5 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
@@ -472,8 +470,7 @@
     "Line Up",
     ["BRAILLE LINE:  'Enter Bug: orca – This page lets you enter a new bug into Bugzilla. h1'",
      "     VISIBLE:  'Enter Bug: orca – This page le', cursor=1",
-     "SPEECH OUTPUT: 'Enter Bug: orca – This page lets you enter a new bug into Bugzilla. heading  '",
-     "SPEECH OUTPUT: 'level 1'"]))
+     "SPEECH OUTPUT: 'Enter Bug: orca – This page lets you enter a new bug into Bugzilla. heading level 1'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
Index: test/keystrokes/firefox/line_nav_simple_form.py
===================================================================
--- test/keystrokes/firefox/line_nav_simple_form.py	(revision 4088)
+++ test/keystrokes/firefox/line_nav_simple_form.py	(working copy)
@@ -62,7 +62,7 @@
 sequence.append(KeyComboAction("Down"))
 sequence.append(utils.AssertPresentationAction(
     "3. line Down",
-    [     "BRAILLE LINE:  'Tell me a little more about yourself:'",
+    ["BRAILLE LINE:  'Tell me a little more about yourself:'",
      "     VISIBLE:  'Tell me a little more about your', cursor=1",
      "SPEECH OUTPUT: 'Tell me a little more about yourself:",
      "'"]))
@@ -146,7 +146,7 @@
     "13. line Down",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 4 items'"]))
+     "SPEECH OUTPUT: 'Hockey multi-select List with 4 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Down"))
@@ -199,7 +199,7 @@
     "3. line Up",
     ["BRAILLE LINE:  'List'",
      "     VISIBLE:  'List', cursor=0",
-     "SPEECH OUTPUT: 'multi-select List with 4 items'"]))
+     "SPEECH OUTPUT: 'Hockey multi-select List with 4 items'"]))
 
 sequence.append(utils.StartRecordingAction())
 sequence.append(KeyComboAction("Up"))
Index: src/orca/scripts/apps/gnome-window-properties/speech_generator.py
===================================================================
--- src/orca/scripts/apps/gnome-window-properties/speech_generator.py	(revision 4088)
+++ src/orca/scripts/apps/gnome-window-properties/speech_generator.py	(working copy)
@@ -49,7 +49,7 @@
         utterances = []
         utterances.extend(self._getSpeechForObjectLabel(obj))
         utterances.extend(self._getSpeechForObjectName(obj))
-        utterances.extend(self._getSpeechForObjectRole(obj))
+        utterances.extend(self.getSpeechForObjectRole(obj))
 
         self._debugGenerator("gnome-window-properties._getSpeechForAlert",
                              obj,
Index: src/orca/scripts/apps/soffice/speech_generator.py
===================================================================
--- src/orca/scripts/apps/soffice/speech_generator.py	(revision 4088)
+++ src/orca/scripts/apps/soffice/speech_generator.py	(working copy)
@@ -80,7 +80,7 @@
             utterances.extend(name)
 
         if not already_focused:
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
 
         utterances.extend(self._getSpeechForObjectAvailability(obj))
 
Index: src/orca/scripts/toolkits/Gecko/speech_generator.py
===================================================================
--- src/orca/scripts/toolkits/Gecko/speech_generator.py	(revision 4088)
+++ src/orca/scripts/toolkits/Gecko/speech_generator.py	(working copy)
@@ -65,17 +65,32 @@
 
     def getSpeechForObjectRole(self, obj, role=None):
         """Prevents some roles from being spoken."""
-        if obj.getRole() in [pyatspi.ROLE_PARAGRAPH,
-                             pyatspi.ROLE_SECTION,
-                             pyatspi.ROLE_LABEL,
-                             pyatspi.ROLE_FORM,
-                             pyatspi.ROLE_LIST_ITEM,
-                             pyatspi.ROLE_MENU_ITEM,
-                             pyatspi.ROLE_UNKNOWN]:
-            return []
-        else:
-            return [rolenames.getSpeechForRoleName(obj, role)]
+        doNotSpeak = [pyatspi.ROLE_FORM,
+                      pyatspi.ROLE_LABEL,
+                      pyatspi.ROLE_MENU_ITEM,
+                      pyatspi.ROLE_PARAGRAPH,
+                      pyatspi.ROLE_SECTION,
+                      pyatspi.ROLE_UNKNOWN]
 
+        if self._script.inDocumentContent(obj):
+            doNotSpeak.append(pyatspi.ROLE_TABLE_CELL)
+            if not self._script.isAriaWidget(obj):
+                doNotSpeak.append(pyatspi.ROLE_LIST_ITEM)
+                doNotSpeak.append(pyatspi.ROLE_LIST)
+
+        utterances = []
+        if not obj.getRole() in doNotSpeak:
+            utterances.append(rolenames.getSpeechForRoleName(obj, role))
+            if obj.getRole() == pyatspi.ROLE_HEADING:
+                level = self._script.getHeadingLevel(obj)
+                if level:
+                    # Translators: this is in reference to a heading level
+                    # in HTML (e.g., For <h3>, the level is 3).
+                    #
+                    utterances.append(_("level %d") % level)
+
+        return utterances
+
     def _getSpeechForDocumentFrame(self, obj, already_focused):
         """Gets the speech for a document frame.
 
@@ -327,28 +342,27 @@
 
         utterances = []
 
-        if obj.getState().contains(pyatspi.STATE_FOCUSED):
-            label = self._script.getDisplayedLabel(obj)
-            if not label:
-                if not self._script.inDocumentContent():
-                    label = obj.name
-                else:
-                    label = self._script.guessTheLabel(obj)
+        label = self._script.getDisplayedLabel(obj)
+        if not label:
+            if not self._script.inDocumentContent():
+                label = obj.name
+            else:
+                label = self._script.guessTheLabel(obj)
 
-            if not already_focused and label:
-                utterances.append(label)
+        if not already_focused and label:
+            utterances.append(label)
 
-            item = None
-            selection = obj.querySelection()
-            for i in xrange(obj.childCount):
-                if selection.isChildSelected(i):
-                    item = obj[i]
-                    break
-            item = item or obj[0]
-            if item:
-                name = self._getSpeechForObjectName(item)
-                if name != label:
-                    utterances.extend(name)
+        item = None
+        selection = obj.querySelection()
+        for i in xrange(obj.childCount):
+            if selection.isChildSelected(i):
+                item = obj[i]
+                break
+        item = item or obj[0]
+        if item:
+            name = self._getSpeechForObjectName(item)
+            if name != label:
+                utterances.extend(name)
 
         if not already_focused:
             if obj.getState().contains(pyatspi.STATE_MULTISELECTABLE):
Index: src/orca/scripts/toolkits/Gecko/script.py
===================================================================
--- src/orca/scripts/toolkits/Gecko/script.py	(revision 4088)
+++ src/orca/scripts/toolkits/Gecko/script.py	(working copy)
@@ -1044,7 +1044,7 @@
             utterances = self.getUtterancesFromContents(contents)
             clumped = self.clumpUtterances(utterances)
             for i in xrange(len(clumped)):
-                [obj, startOffset, endOffset] = \
+                [obj, startOffset, endOffset, text] = \
                                              contents[min(i, len(contents)-1)]
                 if obj.getRole() == pyatspi.ROLE_LABEL \
                    and len(obj.getRelationSet()):
@@ -1849,7 +1849,7 @@
         Arguments:
         - obj: the Accessible
         - offset: the character offset within obj
-        - contents: a list of (obj, startOffset, endOffset) tuples
+        - contents: a list of (obj, startOffset, endOffset, string) tuples
 
         Returns the index of the item if found; -1 if not found.
         """
@@ -1859,7 +1859,7 @@
 
         index = -1
         for content in contents:
-            [candidate, start, end] = content
+            [candidate, start, end, string] = content
 
             # When we get the line contents, we include a focusable list
             # as a list and combo box as a combo box because that is what
@@ -1930,7 +1930,7 @@
         index = self.findObjectOnLine(obj, offset, contents)
         if index < 0:
             self.currentLineContents = self.getLineContentsAtOffset(obj,
-                                                                     offset)
+                                                                    offset)
         self.speakContents(self.currentLineContents)
         self.updateBraille(obj)
 
@@ -1990,7 +1990,7 @@
 
         focusedRegion = None
         for content in contents:
-            [obj, startOffset, endOffset] = content
+            [obj, startOffset, endOffset, string] = content
             if not obj:
                 continue
 
@@ -2195,7 +2195,7 @@
         if obj.getRole() != pyatspi.ROLE_ENTRY:
             self.speakContents(wordContents)
         else:
-            [textObj, startOffset, endOffset] = wordContents[0]
+            [textObj, startOffset, endOffset, word] = wordContents[0]
             word = textObj.queryText().getText(startOffset, endOffset)
             speech.speakUtterances([word], self.getACSS(textObj, word))
 
@@ -3311,9 +3311,9 @@
 
         return string
 
-    def getObjectsFromEOCs(self, obj, offset, boundary):
+    def getObjectsFromEOCs(self, obj, offset, boundary=None):
         """Expands the current object replacing EMBEDDED_OBJECT_CHARACTERS
-        with [obj, startOffset, endOffset] tuples.
+        with [obj, startOffset, endOffset, string] tuples.
 
         Arguments
         - obj: the object whose EOCs we need to expand into tuples
@@ -3344,28 +3344,43 @@
         objects = []
         text = self.queryNonEmptyText(obj)
         if text:
-            [string, start, end] = text.getTextAfterOffset(offset, boundary)
+            if boundary:
+                [string, start, end] = \
+                    text.getTextAfterOffset(offset, boundary)
+            else:
+                start = offset
+                end = text.characterCount
+                string = text.getText(start, end)
         else:
             string = ""
             start = 0
             end = 1
-        objects.append([obj, start, end])
 
-        pattern = re.compile(self.EMBEDDED_OBJECT_CHARACTER)
         unicodeText = string.decode("UTF-8")
+        objects.append([obj, start, end, unicodeText])
+        textSegments = unicodeText.split(self.EMBEDDED_OBJECT_CHARACTER)
+
+        pattern = re.compile(self.EMBEDDED_OBJECT_CHARACTER)
         matches = re.finditer(pattern, unicodeText)
         for m in matches:
             # Adjust the last object's endOffset to the last character
             # before the EOC.
             #
             childOffset = m.start(0) + start
-            objects[-1][2] = childOffset
-            if objects[-1][1] == objects[-1][2]:
+            lastObj = objects[-1]
+            lastObj[2] = childOffset
+            if lastObj[1] == lastObj[2]:
                 # A zero-length object is an indication of something
                 # whose sole contents was an EOC.  Delete it from the
                 # list.
                 #
                 objects.pop()
+            else:
+                # Adjust the string to reflect just this segment.
+                #
+                if not len(textSegments[0]):
+                    textSegments.pop(0)
+                lastObj[3] = textSegments.pop(0)
 
             # Recursively tack on the child's objects.
             #
@@ -3376,7 +3391,11 @@
             # Tack on the remainder of the original object, if any.
             #
             if end > childOffset + 1:
-                objects.append([obj, childOffset + 1, end])
+                if len(textSegments):
+                    restOfText = "".join(textSegments)
+                else:
+                    restOfText = ""
+                objects.append([obj, childOffset + 1, end, restOfText])
 
         if obj.getRole() == pyatspi.ROLE_IMAGE and obj.childCount:
             # Imagemaps that don't have alternative text won't implement
@@ -4555,8 +4574,8 @@
             return None
 
     def getWordContentsAtOffset(self, obj, characterOffset, boundary=None):
-        """Returns an ordered list where each element is composed of
-        an [obj, startOffset, endOffset] tuple.  The list is created
+        """Returns an ordered list where each element is composed of an
+        [obj, startOffset, endOffset, string] tuple.  The list is created
         via an in-order traversal of the document contents starting at
         the given object and characterOffset.  The first element in
         the list represents the beginning of the word.  The last
@@ -4587,8 +4606,8 @@
         return contents
 
     def getLineContentsAtOffset(self, obj, offset):
-        """Returns an ordered list where each element is composed of
-        an [obj, startOffset, endOffset] tuple.  The list is created
+        """Returns an ordered list where each element is composed of an
+        [obj, startOffset, endOffset, string] tuple.  The list is created
         via an in-order traversal of the document contents starting at
         the given object and characterOffset.  The first element in
         the list represents the beginning of the line.  The last
@@ -4669,7 +4688,7 @@
         lastExtents = (0, 0, 0, 0)
         done = False
         while not done:
-            [firstObj, start, end] = objects[0]
+            [firstObj, start, end, string] = objects[0]
             isAria = not self.isNavigableAria(firstObj)
 
             text = self.queryNonEmptyText(firstObj)
@@ -4708,7 +4727,7 @@
         lastExtents = (0, 0, 0, 0)
         done = False
         while not done:
-            [lastObj, start, end] = objects[-1]
+            [lastObj, start, end, string] = objects[-1]
             isAria = not self.isNavigableAria(lastObj)
 
             text = self.queryNonEmptyText(lastObj)
@@ -4776,39 +4795,13 @@
 
     def getObjectContentsAtOffset(self, obj, characterOffset):
         """Returns an ordered list where each element is composed of
-        an [obj, startOffset, endOffset] tuple.  The list is created
-        via an in-order traversal of the document contents starting and
-        stopping at the given object.
+        an [obj, startOffset, endOffset, string] tuple.  The list is 
+        created via an in-order traversal of the document contents 
+        starting and stopping at the given object.
         """
+  
+        return self.getObjectsFromEOCs(obj, characterOffset)
 
-        #if not obj.getState().contains(pyatspi.STATE_SHOWING):
-        #    return [[None, -1, -1]]
-        contents = []
-        if obj.getRole() == pyatspi.ROLE_TABLE:
-            for child in obj:
-                contents.extend(self.getObjectContentsAtOffset(child, 0))
-            return contents
-
-        elif not self.queryNonEmptyText(obj):
-            return [[obj, -1, -1]]
-
-        text = self.getUnicodeText(obj)
-        for offset in range(characterOffset, len(text)):
-            if text[offset] == self.EMBEDDED_OBJECT_CHARACTER:
-                child = obj[self.getChildIndex(obj, offset)]
-                if child:
-                    contents.extend(self.getObjectContentsAtOffset(child, 0))
-            elif len(contents):
-                [currentObj, startOffset, endOffset] = contents[-1]
-                if obj == currentObj:
-                    contents[-1] = [obj, startOffset, offset + 1]
-                else:
-                    contents.append([obj, offset, offset + 1])
-            else:
-                contents.append([obj, offset, offset + 1])
-
-        return contents
-
     ####################################################################
     #                                                                  #
     # Methods to speak current objects.                                #
@@ -4827,121 +4820,75 @@
         return acss
 
     def getUtterancesFromContents(self, contents, speakRole=True):
-        """Returns a list of [text, acss] tuples based upon the
-        list of [obj, startOffset, endOffset] tuples passed in.
+        """Returns a list of [text, acss] tuples based upon the list
+        of [obj, startOffset, endOffset, string] tuples passed in.
 
         Arguments:
-        -contents: a list of [obj, startOffset, endOffset] tuples
+        -contents: a list of [obj, startOffset, endOffset, string] tuples
         -speakRole: if True, speak the roles of objects
         """
 
         if not len(contents):
             return []
 
+        # Even if we want to speakRole, we don't want to do that for the
+        # document frame.  And we're going to special-case headings so that
+        # that we don't overspeak heading role info, which we're in danger
+        # of doing if a heading includes links or images.
+        #
+        doNotSpeakRoles = [pyatspi.ROLE_DOCUMENT_FRAME, pyatspi.ROLE_HEADING]
+
         utterances = []
         for content in contents:
-            [obj, startOffset, endOffset] = content
-            if not obj:
-                continue
+            [obj, startOffset, endOffset, string] = content
+            role = obj.getRole()
 
-            # If this is a label that's labelling something else, we'll
-            # get the label via a speech generator -- unless it's a
-            # focusable list that doesn't have focus.
+            # If we don't have an object, there's nothing to do. If we have
+            # a string, but it consists solely of spaces, we have nothing to
+            # say.  Finally, if it's a label for an object in our contents,
+            # we'll get that label via the speech generator for the object
+            # being labeled.
             #
-            labelledContent = self.isLabellingContents(obj, contents)
-            if labelledContent \
-               and labelledContent.getRole() != pyatspi.ROLE_LIST:
+            if not obj \
+               or len(string) and not len(string.strip(" ")) \
+               or self.isLabellingContents(obj, contents):
                 continue
 
-            # The radio button's label gets added to the context in
-            # default.locusOfFocusChanged() and not through the speech
-            # generator -- unless we wind up having to guess the label.
-            # Therefore, if we have a valid label for a radio button,
-            # we need to add it here.
+            # If we don't have a string, then use the speech generator.
+            # Otherwise, we'll want to speak the string and possibly the
+            # role.
             #
-            if (obj.getRole() == pyatspi.ROLE_RADIO_BUTTON) \
-                and not self.isAriaWidget(obj):
-                label = self.getDisplayedLabel(obj)
-                if label:
-                    utterances.append([label, self.getACSS(obj, label)])
-
-            # We only want to announce the heading role and level if we
-            # have found the final item in that heading, or if that
-            # heading contains no children.
-            #
-            containingHeading = \
-                self.getAncestor(obj,
-                                 [pyatspi.ROLE_HEADING],
-                                 [pyatspi.ROLE_DOCUMENT_FRAME])
-            isLastObject = contents.index(content) == (len(contents) - 1)
-            if obj.getRole() == pyatspi.ROLE_HEADING:
-                speakThisRole = isLastObject or not obj.childCount
+            if not len(string) \
+               or role in [pyatspi.ROLE_ENTRY, pyatspi.ROLE_PASSWORD_TEXT]:
+                utterance = self.speechGenerator.getSpeech(obj, False)
             else:
-                # We also don't want to speak the role if it's a documement
-                # frame or a table cell.  In addition, if the object is an
-                # entry or password_text, _getSpeechForText() will add the
-                # role after the label and before any text that is present
-                # in that field.  We don't want to repeat the role.
-                #
-                speakThisRole = \
-                    not obj.getRole() in [pyatspi.ROLE_DOCUMENT_FRAME,
-                                          pyatspi.ROLE_TABLE_CELL,
-                                          pyatspi.ROLE_ENTRY,
-                                          pyatspi.ROLE_PASSWORD_TEXT]
-            text = self.queryNonEmptyText(obj)
-            if self.isAriaWidget(obj):
-                # Treat ARIA widgets like normal default.py widgets
-                #
-                speakThisRole = False
-                strings = self.speechGenerator.getSpeech(obj, False)
-            elif text \
-               and not obj.getRole() in [pyatspi.ROLE_ENTRY,
-                                         pyatspi.ROLE_PASSWORD_TEXT,
-                                         pyatspi.ROLE_RADIO_BUTTON,
-                                         pyatspi.ROLE_MENU_ITEM]:
-                strings = [text.getText(startOffset, endOffset)]
-                if strings == [' '] and len(contents) > 1:
-                    strings = []
-            elif self.isLayoutOnly(obj):
-                continue
-            else:
-                strings = self.speechGenerator.getSpeech(obj, False)
-
-        # Pylint is confused and flags these errors:
-        #
-        # E1101:6957:Script.getUtterancesFromContents: Instance of
-        # 'SpeechGenerator' has no 'getSpeechForObjectRole' member
-        # E1101:6962:Script.getUtterancesFromContents: Instance of
-        # 'SpeechGenerator' has no 'getSpeechForObjectRole' member
-        #
-        # So for now, we just disable these errors in this method.
-        #
-        # pylint: disable-msg=E1101
-
-            if speakRole and speakThisRole:
-                if text:
-                    strings.extend(\
-                       self.speechGenerator.getSpeechForObjectRole(obj))
-
-                if containingHeading and isLastObject:
-                    obj = containingHeading
-                    strings.extend(\
+                utterance = [string]
+                if speakRole and not role in doNotSpeakRoles:
+                    utterance.extend(\
                         self.speechGenerator.getSpeechForObjectRole(obj))
+ 
+            for item in utterance:
+                utterances.append([item, self.getACSS(obj, item)])
+ 
+            # If the object is a heading, or is contained within a heading,
+            # speak that role information at the end of the object.
+            #
+            isLastObject = (contents.index(content) == (len(contents) - 1))
+            isHeading = (role == pyatspi.ROLE_HEADING)
+            if speakRole and (isLastObject or isHeading):
+                if isHeading:
+                    heading = obj
+                else:
+                    heading = self.getAncestor(obj,
+                                               [pyatspi.ROLE_HEADING],
+                                               [pyatspi.ROLE_DOCUMENT_FRAME])
 
-            for string in strings:
-                utterances.append([string, self.getACSS(obj, string)])
-
-            if speakRole and \
-               speakThisRole and \
-               obj.getRole() == pyatspi.ROLE_HEADING:
-                level = self.getHeadingLevel(obj)
-                if level:
-                    utterances.append([" ", self.getACSS(obj, " ")])
-                    # Translators: this is in reference to a heading level
-                    # in HTML (e.g., For <h3>, the level is 3).
-                    #
-                    utterances.append([_("level %d") % level, None])
-
+                if heading:
+                    roleInfo = \
+                        self.speechGenerator.getSpeechForObjectRole(heading)
+                    for item in roleInfo:
+                        utterances.append([item, self.getACSS(heading, item)])
+  
         return utterances
 
     def clumpUtterances(self, utterances):
@@ -5153,7 +5100,7 @@
         if not len(contents):
             return
 
-        [obj, startOffset, endOffset] = contents[0]
+        [obj, startOffset, endOffset, string] = contents[0]
         if len(contents) == 1 \
            and endOffset - startOffset == 1 \
            and self.getCharacterAtOffset(obj, startOffset) == " ":
@@ -5166,7 +5113,7 @@
             contents = \
                 self.getWordContentsAtOffset(obj, characterOffset, boundary)
             if len(contents):
-                [obj, startOffset, endOffset] = contents[0]
+                [obj, startOffset, endOffset, string] = contents[0]
 
         self.setCaretPosition(obj, startOffset)
         self.updateBraille(obj)
@@ -5193,7 +5140,7 @@
         if not (len(contents) and contents[-1][2]):
             return
 
-        [obj, startOffset, endOffset] = contents[-1]
+        [obj, startOffset, endOffset, string] = contents[-1]
         self.setCaretPosition(obj, endOffset)
         self.updateBraille(obj)
         self.speakContents(contents)
Index: src/orca/speechgenerator.py
===================================================================
--- src/orca/speechgenerator.py	(revision 4088)
+++ src/orca/speechgenerator.py	(working copy)
@@ -209,7 +209,7 @@
         else:
             return []
 
-    def _getSpeechForObjectRole(self, obj, role=None):
+    def getSpeechForObjectRole(self, obj, role=None):
         if (obj.getRole() != pyatspi.ROLE_UNKNOWN):
             return [rolenames.getSpeechForRoleName(obj, role)]
         else:
@@ -314,7 +314,7 @@
             if name != label:
                 utterances.extend(name)
             utterances.extend(self._getSpeechForAllTextSelection(obj))
-            utterances.extend(self._getSpeechForObjectRole(obj, role))
+            utterances.extend(self.getSpeechForObjectRole(obj, role))
 
         utterances.extend(self._getSpeechForObjectAvailability(obj))
 
@@ -440,10 +440,10 @@
                 utterances.extend(name)
             if obj.getRole() == pyatspi.ROLE_TABLE_CELL:
                 utterances.extend(
-                  self._getSpeechForObjectRole(
+                  self.getSpeechForObjectRole(
                     obj, pyatspi.ROLE_CHECK_BOX))
             else:
-                utterances.extend(self._getSpeechForObjectRole(obj))
+                utterances.extend(self.getSpeechForObjectRole(obj))
             utterances.append(checkedState)
             utterances.extend(self._getSpeechForObjectAvailability(obj))
             utterances.extend(self._getSpeechForRequiredObject(obj))
@@ -524,7 +524,7 @@
             utterances.extend(name)
 
         if not already_focused:
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
 
         utterances.extend(self._getSpeechForObjectAvailability(obj))
 
@@ -670,7 +670,7 @@
             if name != label:
                 utterances.extend(name)
             utterances.extend(self._getSpeechForAllTextSelection(obj))
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
 
             # If this application has more than one unfocused alert or
             # dialog window, then speak '<m> unfocused dialogs'
@@ -1001,7 +1001,7 @@
             utterances.append(settings.speechReadOnlyString)
 
         if obj.getRole() != pyatspi.ROLE_PARAGRAPH:
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
 
         [text, caretOffset, startOffset] = self._script.getTextLineAtCaret(obj)
         utterances.append(text)
@@ -1104,7 +1104,7 @@
             name = self._getSpeechForObjectName(obj)
             if name != label:
                 utterances.extend(name)
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
 
         utterances.append(percentage)
 
@@ -1170,7 +1170,7 @@
             #utterances.extend(self._getSpeechForObjectLabel(obj))
             utterances.extend(self._getSpeechForObjectName(obj))
             utterances.append(selectionState)
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
             utterances.extend(self._getSpeechForObjectAvailability(obj))
         else:
             utterances.append(selectionState)
@@ -1278,7 +1278,7 @@
             #
             if not utterances:
                 utterances.extend(self._getSpeechForObjectName(obj))
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
             utterances.append(valueString)
             utterances.extend(self._getSpeechForObjectAvailability(obj))
             utterances.extend(self._getSpeechForRequiredObject(obj))
@@ -1333,7 +1333,7 @@
             utterances.extend(self._getSpeechForObjectLabel(obj))
             if not utterances:
                 utterances.extend(self._getSpeechForObjectName(obj))
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
             utterances.append(valueString)
             utterances.extend(self._getSpeechForObjectAvailability(obj))
 
@@ -1719,7 +1719,7 @@
             name = self._getSpeechForObjectName(obj)
             if name != label:
                 utterances.extend(name)
-            utterances.extend(self._getSpeechForObjectRole(obj))
+            utterances.extend(self.getSpeechForObjectRole(obj))
             utterances.append(checkedState)
             utterances.extend(self._getSpeechForObjectAvailability(obj))
         else:
